2012-09-11

Proplists!

I wasn’t really happy with how I deployed the new Context concept on day 10, and digging a bit has led me to proplists. Much better.

Property lists are a list of tuples or atoms with a simple interface.1

For example, if I define a Context value as a propery list, like Context = [ { team, white }, { is_castle, true } ], I can use simple functions to evaluate it:

true = proplists:get_bool(is_castle, Context).
Team = proplists:get_value(team, Context).

Unfortunately, I can’t use property list accessor functions in pattern matching or as guards.

Here’s the current first pattern match for the pawnmove anonymous function:

({ X, OldY }, { X, NewY }, white) when NewY - OldY =:= 1 -> { { X, NewY }, [] };

If I passed a property list as the 3rd argument (the value that’s currently white) I’d have to make the code much longer, because I’d have to build a proper function body to evaluate its contents.

On the gripping hand, I could perform the proplist parsing before calling the move functions.

Here’s the testmove message processing currently:

{ Pid, testmove, Start, End, Context, Xtra } ->
    move_response(Pid, Piece, Start, End, Move(Start, End, Context), Xtra),
    piece_loop(Piece, Team, History, Move, Capture);

I could do this, instead (code broken apart a bit for clarity):

{ Pid, testmove, Start, End, Context, Xtra } ->
    move_response(Pid, Piece, Start, End,
                  Move(Start, End,
                       proplists:get_value(team, Context),
                       proplists:get_bool(is_castle)
                      ),
                  Xtra),
    piece_loop(Piece, Team, History, Move, Capture);

The new parameter list for each move function becomes Start, End, Team, IsCastle, Xtra.

It’s annoying that the castling special case has to tag along for every move function parameter list, but I don’t see any clean way around that.

But wait, yeah, there’s a better way

I’m going to leave the above section in place because this is a (mostly) complete record of my mental path through the quagmire, and there are definitely some twists and turns. Besides, I think property lists will likely be useful elsewhere.

However, I think I’m overthinking this whole castling problem.

I’m going to add castle as a completely separate message. That’ll rip out the whole Context parameter, and it’ll revert to just being a team value.

If I wanted to make this code more general, perhaps to allow checkers or go, I’d probably find a way to handle this via callbacks.

piece_loop/5 before

piece_loop(Piece, Team, History, Move, Capture) ->
    receive
        { Pid, testmove, Start, End, Context, Xtra } ->
            move_response(Pid, Piece, Start, End, Move(Start, End, Context), Xtra),
            piece_loop(Piece, Team, History, Move, Capture);
        ...

piece_loop/5 after

piece_loop(Piece, Team, History, Move, Capture) ->
    receive
        { Pid, testmove, Start, End, Xtra } ->
            move_response(Pid, Piece, Start, End, Move(Start, End, Team), Xtra),
            piece_loop(Piece, Team, History, Move, Capture);
        { Pid, testcastle, Start, End, Xtra } ->
            move_response(Pid, Piece, Start, End, Move(Start, End, {Team, castle}), Xtra),
            piece_loop(Piece, Team, History, Move, Capture);
        ...

Now the current move functions as I have redefined them to support the more complex Context concept still apply; pawns look for an atom there (the team), kings look for tuples, nobody else cares.

And castleto is a much simpler version of moveto:

{ castleto, Start, End, NewSquarePid } ->
    %% Ask the target square for more information
    case Move(Start, End, { Team, castle }) of
        { Start, _ } ->
            throw({cannot_moveto, Piece, Start, End});
        { End, _ } ->
            NewSquarePid ! { replace, Piece, Team,
                             [ { Start, End, none } | History ], Move, Capture }
    end;

Time to move on. Please

It’s clear from thinking through the harder problems that developing the (perhaps misnamed) piece module in a void is misleading. I really need the broader context of a chessboard and chess game to think through the implications of some of these design decisions.

We have a board prototype; let’s go ahead and create a fully-populated chessboard.

Easier said than done

I have the code to create an 8x8 matrix of squares. I know how to convert” a blank square to a chess piece.

I have no idea how to map the standard initial layout onto my matrix.

Let’s start with the obvious: my board module hasn’t been updated to reflect recent changes.

Specifically, this code no longer does anything:

{ Pid, move, {X, Y}, End } ->
    element(X, element(Y, Matrix)) ! { self(), move, {X, Y}, End, Pid },

We now have five (sigh) distinct move messages: testmove, testcapture, testcastle, moveto, and castleto. This flow originally mapped to what is now called testmove, so I’ll just rename both atoms to testmove and move (hah) on.

Ok, moving on to moving on

We need to map 16 pieces onto a matrix of 64 squares. I can envision an external configuration file, but to keep things in code for now I’ll use an internal structure.

I’ll could use something like this (a property list!):

[ { rook, [ { 1, 1 }, { 8, 1 }, { 1, 8 }, { 8, 8 } ] },
  { knight, [ { 2, 1 }, { 7, 1 }, { 2, 8 }, { 7, 8 } ] },
  { bishop, [ { 3, 1 }, { 6, 1 }, { 3, 8 }, { 6, 8 } ] },
  ...

I’ll have to know” that anything on ranks 1 and 2 are white, and 7/8 are black. Awful but fine for prototyping.

On the other hand, it’d be easy to place a piece on the wrong square, and computers are better at keeping numbers straight than I am (by far), so we’ll try this property list instead:

[ { pieces, [ { rook, rookmove, rookmove },
              { knight, knightmove, knightmove },
              { bishop, bishopmove, bishopmove },
              { queen, queenmove, queenmove },
              { king, kingmove, kingmove },
              { bishop, bishopmove, bishopmove },
              { knight, knightmove, knightmove },
              { rook, rookmove, rookmove } ] },
  { pawns, [ { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap },
             { pawn, pawnmove, pawncap } ] }
]

Not my finest idea ever, but it’ll work.

We’ll need to have a function to replace the existing square state with new state:

replace(Matrix, {X, Y}, Piece, Team, Move, Capture) ->
    element_of(Matrix, { X, Y }) ! { replace, Piece, Team, [], Move, Capture }.

element_of/2 is another new function to simplify the matrix access:

element_of(Matrix, { X, Y }) ->
    element(X, element(Y, Matrix)).

The initialize function for the matrix is surprisingly straightforward:

initialize(Matrix, Proplist) ->
    Pieces = proplists:get_value(pieces, Proplist),
    Pawns = proplists:get_value(pawns, Proplist),
    initialize(Matrix, Pieces, 1, 1, white),
    initialize(Matrix, Pawns, 1, 2, white),
    initialize(Matrix, Pawns, 1, 7, black),
    initialize(Matrix, Pieces, 1, 8, black).

initialize(_, [], _, _, _) ->
    done;
initialize(Matrix, [{Piece, M, C}|T], X, Y, Team) ->
    Move = proplists:get_value(M, piece:movefuns()),
    Capture = proplists:get_value(C, piece:movefuns()),
    replace(Matrix, {X, Y}, Piece, Team, Move, Capture),
    initialize(Matrix, T, X + 1, Y, Team).

And finally, an init function (that I’m probably misusing per Erlang convention):

init() ->
    Matrix = mkmatrix(8, 8),
    Pieces = [ { pieces, [ { rook, rookmove, rookmove },
                           { knight, knightmove, knightmove },
                           { bishop, bishopmove, bishopmove },
                           { queen, queenmove, queenmove },
                           { king, kingmove, kingmove },
                           { bishop, bishopmove, bishopmove },
                           { knight, knightmove, knightmove },
                           { rook, rookmove, rookmove }
                         ]
               },
               { pawns, [ { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap },
                          { pawn, pawnmove, pawncap }
                        ]
               }
             ],
    initialize(Matrix, Pieces),
    spawn(?MODULE, board_loop, [ Matrix ]).

Compile, hold our breath, and…

12> Board = board:init().
<0.141.0>
13> Board ! { self(), testmove, {2, 1}, {3, 3} }.
{<0.60.0>,testmove,{2,1},{3,3}}
14> flush().
Shell got {yes,knight}
ok

I feel like I should quit while I’m ahead. As always, code on github if you’re really, really bored.


  1. This data structure is so intuitive that I’ve been using it for my anonymous move functions data structure all along. I can replace the hairy list comprehension [ Move ] = [ X || { knightmove, X } <- piece:movefuns() ] with proplists:get_value

erlang chessboard
Previous post
Chessboard: Day 10 Need moar tests
Next post
Where is I? Confessions and hopes