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.
There was a bug in the code that revokes castling rights after a king move where
it revoked the rights for *all* players, rather than just the current player.
Fix it.
flags
: Print flags for the current board position. This prints the castling rights
and whether the player can castle (regardless of whether they have the right).
make
: Finally reimplement the make command. Change the format so it takes a move in
the UCI long algebraic style.
perft
: Run perft to a given depth on the current board position.
When the King moves, revoke all rights for the moving player. When the rook moves,
revoke castling rights for that side of the board, if it's moving off its starting
square.
At the first level of depth, print the move and the number of nodes counted in
the tree underneath that node. This behavior imitates Stockfish, and helps with
debugging.
Clean up the output of the Perft binary, and update the check-positions script
to compensate.
Board::color_has_castling_right takes an Option<Color> which it unwraps. With that
unwrapped Color, it can call…
Board::color_has_castling_right_unwrapped, which performs the evaluation with a
non-Option Color.
Clean up some imports.
Move the Perft trait into the position crate, and let the perft binary call into
that.
Amend Position::make_move to return a bool in the Ok case that indicates whether
the position has been seen before. Use this to decide whether to continue
recursing during the Perft run. I haven't seen that this makes a difference in
the counts returned by Perft yet.
UCI uses a move format it calls "long algebraic". They look like either "e2e4"
for a regular move, or "h7h8q" for a promotion. Implement parsing these move
strings as a two step process. First define an AlgebraicMoveComponents struct
in the moves crate that implements FromStr. This struct reads out an origin
square, a target square, and an optional promotion shape from a string. Then,
implement a pair of methods on Position that take the move components struct
and return a fully encoded Move struct with them.
This process is required because the algebraic string is not enough by itself to
know what kind of move was made. The current position is required to understand
that.
Implement Shape::is_promotable().
Add a NULL move to the Move struct. I'm not sure what this is used for yet, but
the UCI spec specifically calls out a string that encodes a null move, so I added
it. It may end up being unused!
Do a little bit of cleanup in the core crate as well. Use deeper imports (import
std::fmt instead of requring the fully qualified type path) and remove some
unnecessary From implementations.
This commit is also the first instance (I think) of defining an errors module
in lib.rs for the core crate that holds the various error types the crate exports.
A small Python script that reads a JSON list of positions and their known Perft
node counts to a certain depth, then invokes the Perft program for each position
and validates the output.
Peter Ellis Jones shared such a JSON list on GitHub. Import that file.
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.
I've lived with this warning for a long time because I didn't really understand
it.
```
warning: `chessfriend_core` (lib) generated 1 warning (run `cargo fix --lib -p chessfriend_core` to apply 1 suggestion)
warning: creating a shared reference to mutable static is discouraged
--> bitboard/src/library.rs:66:9
```
I was able to fix this by creating a new type with a single OnceLock attribute.
The OnceLock acts as a cell, making it mutable, even if self is not. So, you can
declare the MoveLibraryWrapper non-mutable static, but still initialize the
library inside the Cell.
This thing isn't used by any of the "modern" code I've written. It was difficult
to write, but kinda neat architecturally. It made a lot of invalid configurations
of moves into build-time errors. Anyway…
Remove several of the tests that relied on it, but that hadn't been updated to
use the newer code either.
Instead of generating e.p. moves in a separate pass, check whether each capture
move (left and right) targets the en passant square. If so, yield an e.p. capture
otherwise just a regular capture.
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.