2012-09-09

And now?

When last we spoke, I was busy realigning my piece code with cold, hard reality. May I say again how much I love the agility of writing Erlang; I can change my mind as often as I want with little effort.

Or maybe it’s because I have so little code. Or maybe I have so little code because it’s Erlang. Or…

Anyway.

As I noted as I raced out the door yesterday (the pork buns were quite good) I hadn’t actually tested any of the code changes I made. So let’s do some quick shell work, if for no other reason than it’d be good to help me remember where things landed.

To spare anyone a trip to GitHub, here are the important bits of the current piece module (I expect all move functions and related calculations to be migrated to another module, and they’re not important for the current discussion anyway):

blank_square() ->
    piece_loop(blank_square, none, [],
               fun(Source, _Dest, _team) -> { Source, [] } end,
               fun(Source, _Dest, _team) -> { Source, [] } end).

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, testcapture, Start, End, Xtra } ->
            move_response(Pid, Piece, Start, End, Capture(Start, End, Team), Xtra),
            piece_loop(Piece, Team, History, Move, Capture);
        { replace, NewPiece, NewTeam, NewHistory, NewMove, NewCapture } ->
            piece_loop(NewPiece, NewTeam, NewHistory, NewMove, NewCapture);
        { moveto, Start, End, CapturedPiece, NewSquarePid } ->
            NewSquarePid ! { replace, Piece, Team,
                             [ { Start, End, CapturedPiece } | History ], Move, Capture },
            blank_square();
        { Pid, what_am_i, Xtra } ->
            Pid ! { Piece, Team, History, Xtra };
        done ->
            done;
        Message ->
            throw({unknown_message, Message})
    end.

Verification

First things first, let’s run our test suite. All of my changes were applied to code outside the auxiliary movement-related functions, however, so this is basically a non-event:

7> piece:test().
  All 25 tests passed.
ok

Ooh, ahh.

Anyway.

What I really need to test is messaging. Functional tests, of which I have none.

8> [ B ] = [ X || {bishopmove, X} <- piece:movefuns() ].
[#Fun<piece.6.42012460>]
9> Bishop = spawn(piece, piece_loop, [ bishop, white, [], B, B ]).
<0.102.0>
10> Bishop ! { self(), what_am_i, nada }.
{<0.31.0>,what_am_i,nada}
11> flush().
Shell got {bishop,white,[],nada}
ok

Remembering to pass the extra argument1 is going to be tedious, I can tell.

12> Empty = spawn(piece, blank_square, []).
<0.106.0>
13> Empty ! { self(), what_am_i, zilch }.
{<0.31.0>,what_am_i,zilch}
14> flush().
Shell got {blank_square,none,[],zilch}
ok
15> Bishop ! { self(), testmove, { 3, 1 }, { 6, 4 }, nada }.
{<0.31.0>,testmove,{3,1},{6,4},nada}
16> flush().
ok
17> flush().
ok

Ok, first bug, and one I’ve made before: I forgot to include the recursive call to piece_loop/5 after answering the what_am_i message. Wonder if there’s a lint-like tool for Erlang that would help detect this gaffe.

19> f(Bishop).
ok
20> Bishop = spawn(piece, piece_loop, [ bishop, white, [], B, B ]).
<0.123.0>
21> Bishop ! { self(), what_am_i, nada }.
{<0.31.0>,what_am_i,nada}
22> flush().
Shell got {bishop,white,[],nada}
ok
23> Bishop ! { self(), testmove, { 3, 1 }, { 6, 4 }, [] }.
{<0.31.0>,testmove,{3,1},{6,4},[]}
24> flush().
Shell got {yes,bishop,{3,1},{6,4},[{4,2},{5,3}],[]}
ok

Now comes the new messaging: let’s move that bishop to the empty square. Rather, let’s move the state from the square represented by the Bishop process to the square represented by the Empty process.

5> Bishop ! { moveto, {3, 1}, {6, 4}, none, Empty }.
{moveto,{3,1},{6,4},none,<0.159.0>}
6> Empty ! { self(), what_am_i, nada }.
{<0.145.0>,what_am_i,nada}
7> flush().
Shell got {bishop,white,[},[{piece,piece_loop,5,[{file,"/Users/jdaily/github/local/Erlang-Chessboard/piece.erl"},{line,43}]}]}

The second bishop, also white, is instructed to move to a square that already has a white piece, resulting in a thrown (and uncaught) exception.

Here is the new code for the moveto message. I regret that it’s significantly longer than the old, dumb code, but I’ve introduced comments to (hopefully) make it comprehensible.

{ moveto, Start, End, Context, NewSquarePid } ->
    %% Ask the target square for more information
    NewSquarePid ! { self(), what_am_i, none },
    receive
        %% If we're moving to a blank square, verify Move() before
        %% transferring
        { blank_square, _Team, _History, none } ->
            case Move(Start, End, Context) of
                { Start, _ } ->
                    throw({cannot_moveto, Piece, Start, End});
                { End, _ } ->
                    NewSquarePid ! { replace, Piece, Team,
                                     [ { Start, End, none } | History ], Move, Capture }
            end;

        %% If we're trying to capture the same team color, throw an exception
        { CapturedPiece, Team, _, _ } ->
            throw({capturing_same_team, Piece, CapturedPiece, Start, End});

        %% Successful capture
        { CapturedPiece, _Team, _, _ } ->
            case Capture(Start, End, Context) of
                { Start, _ } ->
                    throw({cannot_moveto, Piece, Start, End});
                { End, _ } ->
                    NewSquarePid ! { replace, Piece, Team,
                                     [ { Start, End, CapturedPiece } | History ], Move, Capture }
            end
    end,
    %% Now we've lost our piece, so reinitialize as a blank square
    blank_square();

Here’s an example of trying to force a bishop to move to a square which isn’t legal:

34> Bishop ! { moveto, {3, 1}, {6, 5}, none, Empty }.
{moveto,{3,1},{6,5},none,<0.244.0>}

=ERROR REPORT==== 9-Sep-2012::23:08:46 ===
Error in process <0.242.0> with exit value: },[{piece,piece_loop,5,[{file,"/Users/jdaily/github/local/Erlang-Chessboard/piece.erl"},{line,36}]}]}

One concern about all of this message passing is that if any receiving process is not functioning properly, and thus is not able to reply, other processes will also end up in a waiting state. I assume this is a typical Erlang behavior pattern, and thus there’s a standard way to make sure those waiting states turn into exceptions.

Hardcastle4

By now I’ve almost forgotten where this all started, but now we can introduce castling, finally.

Again, the end result:

2> [ K ] = [ X || {kingmove, X} <- piece:movefuns() ].
[#Fun<piece.4.9735311>]
3> King = spawn(piece, piece_loop, [ king, white, [], K, K ]).
<0.277.0>
4> King ! { self(), testmove, {5, 1}, {7, 1}, white, none }.
{<0.265.0>,testmove,{5,1},{7,1},white,none}
5> flush().
Shell got {no,king,{5,1},{7,1},[],none}
ok

6> King ! { self(), testmove, {5, 1}, {7, 1}, castle, none }.
{<0.265.0>,testmove,{5,1},{7,1},castle,none}
7> flush().
Shell got {yes,king,{5,1},{7,1},[],none}
ok
8> Empty = spawn(piece, blank_square, []).
<0.283.0>
9> King ! { moveto, {5, 1}, {7, 1}, castle, Empty }.
{moveto,{5,1},{7,1},castle,<0.283.0>}
10> Empty ! { self(), what_am_i, none }.
{<0.265.0>,what_am_i,none}
11> flush().
Shell got {king,white,[,[{piece,piece_loop,5,[{file,"/Users/jdaily/github/local/Erlang-Chessboard/piece.erl"},{line,34}]}]}

The above means that the expression in the case statement on line 34 is failing to match any of the patterns provided.

  • I can’t believe I remembered this old TV show.

  • erlang chessboard
    Previous post
    Erlang, Twitter, and OAuth Deconstruction, a prologue
    Next post
    Chessboard: Day 11 Board, board, I must a board to make