En passant state should be cleared after any move that isn't a double push.
To support this fix, rename advance_clocks to advance_board_state and let it take
an Option<Square>. Every supporting make_move method calls this method to update
board state when the make is done. So this is the ideal place to also update
e.p. state.
Remove the <n> argument from this method. I think it was a bad idea to begin with
but at the time I was looking for an expedient solution for getting neighbor
squares 2 squares away.
Overhaul bounds checking in this method so horizontal (west and east) bounds are
checked in addition to vertical (north and south) bounds. For the diagonal
directions in particular, it was easy to generate some bogus neighbor squares
because this method didn't check for wrapping when calculating horizontal
neighbors.
Clippy complained that all the properties of the AllPiecesMoveGenerator struct
had the same suffix. It suggested I remove the _move_generator suffix. So I did.
The move generators should only generate moves that can be made, so calling
make_move() and unmake_move() should never give an error during legal move
generation. Both of these calls assert that the result is not an Err(). Improve
the error messaging so that they log the move, the current board position, and
the error message. Highlight the squares relevant to the move (origin, target,
and capture) when printing the board.
Found another bug in the pawn move generator related to en passant moves. The
generator was emitting e.p. captures even when no pawn was available to capture
on that target square.
The solution was to include the e.p. square in the enemies list, effectively
treating the e.p. target as if it were occupied by an enemy piece, and then remove
it from the capture bitboards before move generation.
Include a couple tests to exercise this functionality.
As moves are made, keep track of the hashes of those board position. When moves
are unmade, pop seen hashes from the HashSet.
Reformat the imports in position.rs.
Move this file over to position/tests. That makes it an integration test, technically.
Update it to comply with the current API conventions. This is a pretty radical
change from when I first wrote these!
In the process of running these tests, I found a bug in my PawnMoveGenerator where
it was generating the origin squares for en passant captures incorrectly. Fix
those bugs and write two new tests to exercise those code paths.
One of the test_board! variants was setting .active_color instead of using the
setter. This is a build failure. Fix it.
Move the assert_move_list! macro to the moves crate. This is a more natural home
for it. Additionally, add assert_move_list_contains! and assert_move_list_does_not_contain!
to generate assertions that a collection of moves (anything that implements
.contains()) has or doesn't have a set of moves. Also remove formatted_move_list!,
which is no longer used.
All that done, make the tests pass!
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.
Make the struct fields private and export getters and various setters for
manipulating the data.
Update all the references to these fields to use the getters and setters instead.
Make the struct attribute private, and export two new methods. A getter, active_color(),
and a setter, set_active_color().
Update all references to the attribute to use the methods.
When place_piece() is called with PlacePieceStrategy::Replace, return the replaced
piece in the Ok() variant of the Result as an Option<Piece>.
Plumb this new return type through Board and Position.
I've been bothered by certain instances where the color has already been unwrapped
from an Option<Color> but a subsequent call takes an Option, so you have to rewrap
it to call the method. I might be overthinking this…
For the Board::*_color_is_in_check set of methods, try out this naming convention:
active_color_is_in_check()
: Operates directly on the Board's active color, no unwrapping required
color_is_in_check()
: Takes an Option<Color> and unwraps it with the active color if None is given
unwrapped_color_is_in_check()
: Takes a Color and operates on it. This method is called by the two above, but
is also public.
Instead of inheriting the MakeMove and UnmakeMove traits by being a BoardProvider,
implement bespoke versions of these two methods. This gives Position a chance to
do some of its own work (tracking captures, move records, etc) when making a move.
Pass the move record by reference to the unmake_move() method. Saves a copy.
Export the Result types for MakeMove and UnmakeMove.
Declare an UnmakeMove trait in the moves crate, just like the MakeMove trait from
an earlier commit. Implement this trait for all types that also implement
BoardProvider.
Bring in a whole pile of unit tests from Claude. (Holy shit, using Claude really
saves time on these tests…) Several of these tests failed, and all of those
failures revealed bugs in either MakeMove or UnmakeMove. Huzzah! Include fixes for
those bugs here.
Declare a MakeMove trait and export it from chessfriend_moves. Declare a
BoardProvider trait that both Board and Position implement.
Implement the MakeMove trait for all types that implement BoardProvider, and move
all the move making code to the moves crate.
This change makes it possible to make moves directly on a Board, rather than
requiring a Position. The indirection of declaring and implementing the trait
in the moves crate is required because chessfriend_board is a dependency of
chessfriend_moves. So, it would be a layering violation for Board to implement
make_move() directly. The board crate cannot link the moves crate because that
would introduce a circular dependency.
The moves command writes all possible moves to the terminal.
Move the previous implementation of the moves command, which marked squares that
a piece could move to, to a 'movement' command.
A generator that yields moves for all pieces on the board. This generator combines
the shape-specific move generators developed in prior commits into a single
iterator that yields all moves.
Implement FusedIterator for all generators so AllPiecesMoveGenerator can avoid
doing a bunch of extra work once each iterator has yielded None.
The make_move tests were trying to access the last capture piece directly from a
slice of captures pieces (implementation prior to CapturesList). Implement
CapturesList::last() to return an Option<&Piece> and use it in the tests.
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. 🙃
This generator produces moves for slider pieces: bishops, rooks, and queens. All
of these pieces behave identically, though with different sets of rays that
emanate from the origin square. Claude helped me significantly with the
implementation and unit testing. All the unit tests that took advantage of Claude
for implementation are marked as such with an _ai_claude suffix to the test name.
One unique aspect of this move generator that Claude suggested to me was to use
loop { } instead of a recursive call to next() when the internal iterators expire.
I may try to port this to the other move generators in the future.
To support this move generator, implement a Slider enum in core that represents
one of the three slider pieces.
Add Board::bishops(), Board::rooks() and Board::queens() to return BitBoards of
those pieces. These are analogous to the pawns() and knights() methods that return
their corresponding pieces.
Also in the board create, replace the separate sight method implementations with
a macro. These are all the same, but with a different sight method called under
the hood.
Finally, derive Clone and Debug for the bit_scanner types.
ply! implements a small DSL for writing moves in code using a natural-ish
algebraic notation.
assert_move_list! takes a generator and an expected list of moves and asserts
that they're equal. This macro is mostly a copy from one I wrote earlier in the
position crate.