Reorganize castling rights API on Board into methods named according to
conventions applied to other API.
Board::has_castling_right
Board::has_castling_right_active
Board::has_castling_right_unwrapped
These all check if a color has the right to castle on a particular side
(wing) of the board. The first takes an Option<Color>, the latter two
operate on bare Colors: the active color, or an explicit Color.
Board::grant_castling_right
Board::grant_castling_right_active
Board::grant_castling_right_unwrapped
Grant castling rights to a color. Color arguments follow the pattern above.
Board::revoke_castling_right
Board::revoke_castling_right_active
Board::revoke_castling_right_unwrapped
Revoke castling rights from a color. Color arguments follow the pattern
above.
The latter two groups of methods take a new CastleRightsOption type that
specifies either a single Wing or All.
Rework the implementation of CastleRights to take a CastleRightsOption. Update
the unit tests and make sure everything builds.
The move I observed in my testing was a castling move, which doesn't set target
and origin squares because those are provided by the castling parameters struct
from the board crate.
Fix the missing squares on castle moves by looking up castling parameters and
populating them. This requires Move::castle() to take a Color in addition to the
Wing. Update all the call sites.
This change builds on several previous changes to implement Zobrist hashing of the
board. This hash can be updated incrementally as changes are made to the board.
In order to do that, various properties of the Board struct had to made internal.
In the setters and various mutating members of Board, the hash is updated as
state changes.
The entire hashing mechanism is optional. If no ZobristState is provided when the
Board is created, the hash is never computed.
Plumb the Zobrist state through Position as well so that clients of Position (the
ultimate interface for interacting with the chess engine) can provide global
state to the whole engine.
The explorer crate gives an example of how this works. Some global state is
computed during initialization and then passed to the Position when it's created.
Implement a move generator that emits moves for the king(s) of a particular color.
There will, of course, only ever be one king per side in any valid board, but
this iterator can (in theory) handle multiple kings on the board. This iterator
is almost entirely copypasta of the SliderMoveGenerator. The major difference is
castling.
Castle moves are emitted by a helper CastleIterator type. This struct collects
information about whether the given color can castle on each side of the board
and then emits moves for each side, if indicated.
Do some light refactoring of the castle-related methods on Board to accommodate
this move generator. Remove the dependency on internal state and rename the
"can_castle" method to color_can_castle.
In order to facilitate creating castling moves without relying on Board, remove
the origin and target squares from the encoded castling move. Code that makes
a castling move already looks up castling parameters to move the king and rook to
the right squares, so encoding those squares was redundant. This change
necessitated some updates to position.
Lastly, bring in a handful of unit tests courtesy of Claude. Apparently, it's my
new best coding friend. 🙃
Implement a new method on Position that evaluates whether the active color can castle
on a given wing of the board. Then, implement making a castling move in the position.
Make a new Wing enum in the core crate to specify kingside or queenside. Replace the
Castle enum from the board crate with this one. This caused a lot of churn...
Along the way fix a bunch of tests.
Note: there's still no way to actually make a castling move in explorer.
Encapsulate castling rights within a small module. Castle provides the API to the
rest of the board package. Rights encodes the castling rights for each player.
Parameters defines castling parameters for each player.