2012-09-01

Does not compute

It’s a Saturday, and I have little time to program. What’s wrong with this picture?

To keep it simple, I’ll just work on movement functions for other pieces.

Stream of consciousness

King

Hard to get simpler than the King. He’ll have all sorts of complexities once we start tackling dependencies with other pieces (checks, castling, etc) but for the moment, let’s just deal with the basics.

As a reminder, here’s what we settled on for pawnmove:

  fun({ X, OldY }, { X, NewY }, white) when NewY - OldY =:= 1 -> { X, NewY };
     ({ X, OldY }, { X, NewY }, black) when NewY - OldY =:= -1 -> { X, NewY };
     ({ X, 2 }, { X, 4 }, white) -> { X, 4 };
     ({ X, 7 }, { X, 5 }, black) -> { X, 5 };
     (Loc, _, _) -> Loc end

We need to allow for a one-square move in 8 different directions. Perhaps we can use abs in a guard:

  fun({ OldX, OldY }, { NewX, NewY }, _)
        when abs(NewY - OldY) < 2, abs(NewX - OldX) < 2 -> { NewX, NewY };
     (Loc, _, _) -> Loc end

Looks promising. Let’s take it out for a spin.

First, embed this in our movefuns function:

movefuns() ->
    [ 
      { pawnmove, fun({ X, OldY }, { X, NewY }, white) when NewY - OldY =:= 1 -> { X, NewY };
                     ({ X, OldY }, { X, NewY }, black) when NewY - OldY =:= -1 -> { X, NewY };
                     ({ X, 2 }, { X, 4 }, white) -> { X, 4 };
                     ({ X, 7 }, { X, 5 }, black) -> { X, 5 };
                     (Loc, _, _) -> Loc end
      },

      { pawncap, fun({ OldX, OldY }, { NewX, NewY }, white)
                       when abs(NewX - OldX) =:= 1, NewY - OldY =:= 1 -> { NewX, NewY };
                    ({ OldX, OldY }, { NewX, NewY }, black)
                       when abs(NewX - OldX) =:= 1, NewY - OldY =:= -1 -> { NewX, NewY };
                    (Loc, _, _) -> Loc end
      },

      { kingmove, fun({ OldX, OldY }, { NewX, NewY }, _)
                        when abs(NewY - OldY) < 2, abs(NewX - OldX) < 2 -> { NewX, NewY };
                     (Loc, _, _) -> Loc end
      }

    ].

Now, at the shell:

19> [ K ] = [ X || {kingmove, X} <- piece:movefuns() ].
[#Fun<piece.2.14952569>]
20> King = spawn(piece, piece_loop, [ K, K, {5, 1}, white ]).
<0.321.0>
21> King ! { self(), move, 5, 2 }.
{<0.275.0>,move,5,2}
22> flush().
Shell got {yes,{5,2}}
ok
23> King ! { self(), move, 4, 3 }.
{<0.275.0>,move,4,3}
24> flush().
Shell got {yes,{4,3}}
ok
25> King ! { self(), move, 4, 3 }.
{<0.275.0>,move,4,3}
26> flush().
Shell got {no,{4,3}}
ok

So here’s an interesting result. I realized at the end of testing that I hadn’t accounted for the possibility that the piece would be sent a message telling it to move to its own square. I ran the test expecting to see another {yes,{4,3}} reply, but I got a no instead.

In retrospect, it’s perfectly obvious: as far as kingmove is concerned, it’s a valid destination, because it’s fewer than 2 squares away from the starting point, but move_response compares the old square to the new square, finds that they’re the same location, and interprets that as a failed move.

The net effect is correct, it’s just more of a fortunate side-effect of our architecture than a deliberate plan. Perhaps that just means that this is a solid design.

Knight

I’m a little nervous about the diagonals, since I’ll have to do something more complicated than addition or subtraction (my math skills are rather suspect). Let’s go with a Knight instead.

Knights move 2 squares in one cardinal direction and 1 square perpendicular to that direction, so:

fun({ OldX, OldY }, { NewX, NewY }, _)
      when abs(NewY - OldY) =:= 2, abs(NewX - OldX) =:= 1 -> { NewX, NewY };
   ({ OldX, OldY }, { NewX, NewY }, _)
      when abs(NewY - OldY) =:= 1, abs(NewX - OldX) =:= 2 -> { NewX, NewY };
   (Loc, _, _) -> Loc end

Hey, this isn’t so bad. Either 2 columns + 1 row difference or 1 column and 2 rows.

2> [ Kn ] = [ X || {knightmove, X} <- piece:movefuns() ].
[#Fun<piece.3.1936592>]
3> Knight = spawn(piece, piece_loop, [ Kn, Kn, {2, 1}, white ]).
<0.343.0>
4> Knight ! { self(), move, 4, 2 }.
{<0.335.0>,move,4,2}
5> Knight ! { self(), move, 3, 4 }.
{<0.335.0>,move,3,4}
6> Knight ! { self(), move, 5, 4 }.
{<0.335.0>,move,5,4}
7> Knight ! { self(), move, 5, 6 }.
{<0.335.0>,move,5,6}
8> flush().
Shell got {yes,{4,2}}
Shell got {yes,{3,4}}
Shell got {no,{3,4}}
Shell got {no,{3,4}}
ok

Looks good.

This brings up something I hadn’t thought much about. Let me try a different error mode:

9> Knight ! { self(), move, 1, 3 }.
{<0.335.0>,move,1,3}
10> flush().
Shell got {yes,{1,3}}
ok
11> Knight ! { self(), move, 0, 1 }.
{<0.335.0>,move,0,1}
12> flush().
ok

If I try to move my knight completely off the chessboard, I don’t get a response, because the guards in the receive pattern matches in piece_loop specifically preclude the possibility of X or Y being 0.

Once again, my knowledge of customary Erlang patterns (or lack thereof) rears its ugly head. I can see three distinct approaches:

  1. Leave the silent failure as is. This doesn’t appeal to me.
  2. Introduce a pattern match that handles X or Y values being out of bounds, and use move_response to send back a {no,Loc} message. Seems reasonable.
  3. Introduce the same pattern match but throw an exception.
  4. Introduce the same pattern match but terminate the process.

The last is quite radical in most contexts, but in Erlang it’s apparently a fairly common approach. I’m still trying to grasp the implications; Joe Armstrong’s thesis (PDF) discusses this in great detail, or you can read Learn You Some Erlang - errors and exceptions.

For now, let’s go with #3.

Here are the current pattern matches:

{ From, move, X, Y } when X > 0, X < 9, Y > 0, Y < 9
{ From, capture, X, Y } when X > 0, X < 9, Y > 0, Y < 9
{ From, location }
done

We could add a catch-all expression at the end for any unknown messages, or we match move/capture messages that don’t fit our criteria. We could add both and handle them differently.

piece_loop(Move, Capture, Location, Team) ->
    receive
        { From, move, X, Y } when X > 0, X < 9, Y > 0, Y < 9 ->
            piece_loop(Move, Capture,
                       move_response(From, Location, Move(Location, {X, Y}, Team)),
                       Team);
        { From, capture, X, Y } when X > 0, X < 9, Y > 0, Y < 9 ->
            piece_loop(Move, Capture,
                       move_response(From, Location, Capture(Location, {X, Y}, Team)),
                       Team);
        { _, _, _X, _Y } ->
            throw(invalid_destination);
        { From, location } ->
            From ! Location,
            piece_loop(Move, Capture, Location, Team);
        done ->
            done;
        _ ->
            throw(unknown_message)
    end.

I’ve not talked about the _ wildcard pattern in this series. Any variable which starts with an underscore is a wildcard; using _X and _Y just emphasizes what values are supposed to be there, but has no impact on the running of the program.

2> [ Kn ] = [ X || {knightmove, X} <- piece:movefuns() ].
[#Fun<piece.3.31170156>]
3> Knight = spawn(piece, piece_loop, [ Kn, Kn, {2, 1}, white ]).
<0.373.0>
4> Knight ! foo.
foo

=ERROR REPORT==== 1-Sep-2012::18:23:11 ===
Error in process <0.373.0> with exit value: {{nocatch,unknown_message},[{piece,piece_loop,4,[{file,"/Users/jdaily/github/local/Erlang-Chessboard/piece.erl"},{line,40}]}]}

5> Knight ! { self(), move, 4, 2 }.
{<0.365.0>,move,4,2}
6> flush().
ok
7> f().
ok
8> [ Kn ] = [ X || {knightmove, X} <- piece:movefuns() ].
[#Fun<piece.3.31170156>]
9> Knight = spawn(piece, piece_loop, [ Kn, Kn, {2, 1}, white ]).
<0.380.0>
10> Knight ! { self(), move, 0, 2 }.

=ERROR REPORT==== 1-Sep-2012::18:23:58 ===
Error in process <0.380.0> with exit value: {{nocatch,invalid_destination},[{piece,piece_loop,4,[{file,"/Users/jdaily/github/local/Erlang-Chessboard/piece.erl"},{line,33}]}]}

{<0.365.0>,move,0,2}

Note that throwing an exception without catching it does kill the process, although you can’t prove it from what you see above; the way I’ve written it would terminate the loop because I don’t call piece_loop again to keep it going after using throw.

erlang chessboard
Previous post
Chessboard: Day 2 Of Git and finer things.
Next post
More Erlang Resources Maybe I need a wiki instead of a blog.