My solution can be found at http://phrogz.net/RubyLibs/GKChess.rb
My solution does not allow castling or en-passant. (Castling would be
far easier if you didn't have to determine if any other piece
challenged the intermediary space :p En-passant is annoying because
of the turn-based restriction.) It also does not check for checkmate.
Things I'm proud of:
* Subclassing Piece for each type to delegate the possible moves
calculation.
* Some of the DRY stuff in calculating possible moves along various
axes for everyone but the Pawn
* Some nice robustness and messages when you try to pick an invalid
piece, or send a piece somewhere it can't go. (After you pick the
piece to move, if you give it an invalid destination it tells you
what moves are legal.)
* Subclassing RuntimeError for the first time only and using it to my
advantage. (OutOfBoundsError and IllegalMoveError).
I'm actually not sure if I should be proud of the last one...is it
'good' to run code you know will throw an error and then catch that
as a way of detecting bounds, versus calculating the bounds properly?
When calculating the moves that a rook could do, for example, I moved
along a particular axis until I found another piece, adding each
square to the possible moves. However, if no pieces existed between
me and the edge of the board, I let the algorithm charge off the edge
of the board, waiting for the Move constructor to throw an
OutOfBoundsError as another way to let me know that it was time to stop.
Things I'm not so proud of:
* Sometimes I use 'a2' to access information, sometimes ['a','2'];
consequently a lot of my methods have code like:
def foo( col, row=nil )
col, row = (col.to_s+row.to_s).split('')
so that I can pass the information around as separate arguments,
one string, or an array. Tricky, but hacky.
* The pieces know what they are allowed to do on the board...but
don't know anything about check or checkmate. That's up to the Board.
Consequently, pieces think they can make a move that would result in
self-check, and list those choices among the options. The error
message when you try to make a move that would put your own king in
check is not helpful.
* I use #deep_clone with the Marshal hack to set up a scenario to
detect check without altering the current board. This feels
wrong...the board should be able to try out a move and then undo it
without having to clone itself. (I was concerned about putting pieces
back on the board afterwards.) Further, when I make the clone, I
can't the real #move method to try out the change, because that
method has all sorts of warnings that I'd have to hide. Ugly.
The trickiest part for me (that I ended up not solving) is figuring
out where the logic of the game goes. Shouldn't each piece know what
it can do? But then it needs to know about the entire board. And (for
en-passant) the recent history of moves for each piece. Ugly.
Thanks for a challenging puzzle. Having refactored some pieces twice,
it was a very useful exercise in thinking about roles and
responsibilities, and my own need to plan a little more before coding.