As penalty for being distracted by other Erlang projects yesterday and not continuing my chess project, I hereby sentence myself to a bacon and egg biscuit for breakfast. Yum.

Before we continue fleshing out the board code, let’s revisit the move evaluation needs from day 8 and see if our current design, such as it is, allows for those to be accomplished.

Move evalution

Are there pieces in the way?

We need a way to express a path and find any obstacles. This will be useful for several tasks.

The slope/row/column/diagonal logic will come into play here; I suspect that code will move out of piece.erl into a utility module.

We also need, and have, a way to ask each square whether it contains a piece (recall blank_square?).

Is this piece pinned against its king?

To avoid contortions around which piece is under discussion, let’s simplify this question: given a white king, white bishop, and assorted black pieces.

This involves 3 distinct questions: for each black piece, can that piece attack the white king (assuming no obstacles), can it attack the white bishop (same assumption), and are there no obstacles in the path? Only if the answer to each question is yes is the bishop pinned.

We can already answer the first two questions today by asking each black piece whether it can capture the bishop and king squares; we have enough code in place that I’m confident the 3rd question can be answered.

Can this king castle right now?

If any of the 3 squares involved for the king (its current, future, or intermediate square) is under attack, castling is not a legal move.

As with pinning, this is effectively a solved problem.

Can this king/rook pair ever castle?

If a king has moved, it can never castle.

If a rook has moved, the king cannot castle to that side.

Let’s set this question aside for the moment: we have bigger problems.

Lo, the existential angst

Once again, I may have led myself down a wrong path.

If you’ll recall, at day 5 I changed tack: instead of having one process per piece with retained state, I decided on a stateless process that would evaluate move functions based on incoming state. So no location, no team would be stored.

At day 8 I discovered that I would need no-op” pieces at each blank square.

So now I have 64 pieces” on a board, some of which are blank, and none of which know what team they represent.

How am I supposed to find each black piece to evaluate pinning?

How am I supposed to know whether a rook or a king has previously moved if it has no self-contained state?

To answer that last question, I discussed having a record of moves, so there may be a way to evaluate that list. Still, though, I think it is going to be necessary to reintroduce state to each piece.

Next iteration

I was inspired to remove state from piece.erl because having the current location as part of a piece’s state struck me as risky. And, in fact, at this point my expectation is that the process at square {4, 7} will never actually move, rather that process will represent different pieces over time.

In fact, perhaps my piece module should be renamed to square, but I’m inclined to think not. All of the code is related to piece movement.

Anyway, if we reintroduce1 state, I don’t want location to be included, but team and movement history are reasonable additions.

I think we also need a new message to a piece process: give your state to another process. So, if a rook moves from {4, 7} to {2, 7} the process representing {4, 7} will be asked to send a swap message to {2, 7}.

In pseudo-code, then, moving a piece looks like this:

  1. Ask process at {4, 7} whether it can conceptually move to {2, 7}.
  2. Evaluate other constraints, such as whether {4, 7} is pinned or whether another piece sits at {3, 7} or {2, 7}.
  3. Send message to {4, 7} telling it to forward its state to {2, 7}.
  4. Send message to {2, 7} asking it to add the latest move to its state.
    • Debate: should this be encapsulated by the forward message logic?
  5. Send message to {4, 7} giving it a new blank_square state.
    • Debate: should this also be handled by the forward message?
  6. Evaluate whether this move results in check or checkmate.

A capture move would be largely the same, but a piece of the opposing team at the destination square would no longer be an obstacle.

En passant has an extra step: the captured” square will receive a blank_square state.

And now we have a simple way to determine whether a king or rook has previously moved and thus can participate in a castle move: ask to see their move history.

And checkmate?

Brute force: if any white piece can attack the black king, we have to evaluate whether any black piece can intervene and whether the king can move.

Piece rework

The History list contains tuples: starting square, ending square, and piece name (as atom) captured (if any). The first tuple in the list is the most recent move.2

Cleanup items:

Revised code:

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) ->
        { 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 },
        { Pid, what_am_i, Xtra } ->
            Pid ! { Piece, Team, History, Xtra };
        done ->
        Message ->
            throw({unknown_message, Message})

The board prototype also underwent some small changes.

None of the above has been tested beyond compilation, but the Indianapolis Chinese Festival is underway, so I must be headed downtown!

  1. Technically there is already state (the piece type and movement functions), so we’re not reintroducing, just re-expanding.

  2. One consequence of going with the current approach of moving piece state between processes instead of rebuilding the board data structure after each move (see day 8) is that I lose the option of retaining each board state as one discrete snapshot to make rollbacks easier. Will have to mull on this.

  3. If I were serious about supporting other games with this codebase I’d have to give more thought to how to allow two pieces to exchange places on the board.

erlang chessboard
Previous post
Chessboard: Day 8 Board yet?
Next post
Erlang, Twitter, and OAuth Deconstruction, a prologue