2012-09-29

And the winner is…

If you’ve been following along, you know that I paused for a while to do more reading and consider whether my approach (using processes like objects) was reasonable.

And?

Yeah, twas a bad idea. All of it. Every last decision…ok, it wasn’t that bad.

Still, it was bad.

So what now?

My new approach: use records to make state much more explicit, and a dictionary to track the board. I’ll still have a board and a piece module, but I’m no longer creating separate processes for each piece.

Instead, the dictionary will have a square in the usual tuple form {X, Y} as a key, with a piece record as value. Blank squares will have a piece type of none.1

So, show me the money

Most of the code I wrote can be reused, fortunately, albeit twisted around quite a bit. Here’s what I’ve done so far.

Records

If you haven’t seen Erlang record syntax, good luck. The only record syntax I can understand is what I’ve written myself. Trying to follow someone else’s code with records is awful.

Better yet, I have nested records. Egads, the pain.

Anyway, here are my basic records:

-record(move, { piece,
                start,
                target,
                movetype }).
-record(piece, { type=pawn,
                 team=white,
                 history=[],
                 movefun=pawnmove,
                 capturefun=pawncap }).
-record(historyitem, { endsquare,
                       traversed=[] }).
-record(boardstate, { pieces,
                      whiteking={5, 1},
                      blackking={5, 8} }).

I have these in a new records.hrl file to be included into both my new board and piece modules.

Piece

Actually, nothing to show here yet. I’ve commented out virtually everything, and changed nothing.

Board

Initialization

Abandon all hope, ye who enter here.

initialize(Board, Proplist) ->
    Pieces = proplists:get_value(pieces, Proplist),
    Pawns = proplists:get_value(pawns, Proplist),
    %% The Board dictionary gets revised with each call to initialize_row
    Board1 = initialize_row(Board, Pieces, {1, 1}, white),
    Board2 = initialize_row(Board1, Pawns, {1, 2}, white),
    Board3 = initialize_blanks(Board2, 1, 3),
    Board4 = initialize_row(Board3, Pawns, {1, 7}, black),
    initialize_row(Board4, Pieces, {1, 8}, black).

initialize_blanks(Board, _, 7) ->
    Board;
initialize_blanks(Board, 9, Y) ->
    initialize_blanks(Board, 1, Y + 1);
initialize_blanks(Board, X, Y) ->
    initialize_blanks(store({X, Y}, #piece{type=none}, Board), X + 1, Y).

initialize_row(Board, [], _, _) ->
    Board;
initialize_row(Board, [{Piece, M, C}|T], {X, Y}, Team) ->
    Move = proplists:get_value(M, newpiece:movefuns()),
    Capture = proplists:get_value(C, newpiece:movefuns()),
    initialize_row(store({X, Y}, #piece{type=Piece,
                                        team=Team,
                                        movefun=Move,
                                        capturefun=Capture},
                        Board),
                   T, {X + 1, Y}, Team).

Board to text translation

This is the only logic I’ve written in thus far, other than the hairy initialization code. Without this it’s tough to see whether anything works at all.

matrix_to_text(Matrix) ->
    row_to_text(8, 1, Matrix, []).

%% X and Y are swapped here because we're focusing on rows.  Made more
%% sense when I had a tuple of rows to navigate as my data structure.
row_to_text(0, _, _, Accum) ->
    Accum ++ "\n";
row_to_text(Y, 9, Matrix, Accum) ->
    row_to_text(Y - 1, 1, Matrix, ["\n\n|" | Accum]);
row_to_text(Y, X, Matrix, Accum) ->
    row_to_text(Y, X + 1, Matrix, [ cell_to_text({X, Y}, Matrix) ++ "|" | Accum ]).

cell_to_text(Square, Matrix) ->
    {ok, #piece{type=Type}} = find(Square, Matrix),
    piece_to_shorthand(Type).

piece_to_shorthand(Piece) ->
    case Piece of 
        none ->
            "   ";
        pawn ->
            " p ";
        rook ->
            " R ";
        knight ->
            " N ";
        bishop ->
            " B ";
        queen ->
            " Q ";
        king ->
            " K "
    end.

And what does this look like?

38> Board = newboard:init().
[,
...
 {...}|...]
39> io:format(newboard:matrix_to_text(Board)).


| R | N | B | K | Q | B | N | R |

| p | p | p | p | p | p | p | p |

|   |   |   |   |   |   |   |   |

|   |   |   |   |   |   |   |   |

|   |   |   |   |   |   |   |   |

|   |   |   |   |   |   |   |   |

| p | p | p | p | p | p | p | p |

| R | N | B | K | Q | B | N | R |
ok

I’ve taken several big steps backward, but I should be on a much sounder footing now.

So why, again, is this better?

Here’s an example of the conundrum I faced as I worked through the old, process-based model.

Let’s say that I want to take advantage of concurrency and ask all pieces: can you attack the white king?

As far as I can see, there are 3 ways I can tackle that:

  1. Send a message to every piece and wait for all of them to respond.
    • How do I know they’ve all responded?
    • How long do I wait before giving up?
    • What do I do after giving up?
  2. Send a message to every piece and wait for any of them to respond in the positive; those who can’t attack the king don’t respond at all.
    • Again, how long do I wait before assuming none of them can attack?
  3. Send a message to each piece individually and wait for the response before proceeding to the next piece.
    • Simplest, sanest solution, but what have I gained by using processes? I’m no longer taking advantage of concurrency, so I’ve introduced message-passing overhead with no real benefit.

From the reading I’ve been doing, it seems clear: processes should be defined and used deliberately to manage an independent task. In this case, there’s no independent task.

Per my original plan, the benefit of having each piece run its own state loop (i.e., be its own process) was for state management, but the cost doesn’t seem worth it, or at least it doesn’t seem to be a typical Erlang pattern to handle it this way.


  1. We could just not store blank squares in the dictionary, but the syntax is generally cleaner if we populate all entries.

erlang chessboard
Previous post
Erlang v Scala Why Erlang makes life difficult for me
Next post
Chessboard: Day 13 Stay on target. Stay on target!