Commit graph

284 commits

Author SHA1 Message Date
182bf81126 [board] Fix a counter underflow in the piece set
During perft runs, the PieceSet counter would occasionally underflow, causing
the whole program to crash. This is because, when building a Board from a list
of bitboards, Counts::increment() was only being called once, even when the
bitboard had more than one piece in it. Fix the bug by incrementing during the
loop that sets up the mailbox.

Additionally, refactor the increment() and decrement() methods to be a little
more succinct.
2025-08-15 16:15:09 -07:00
3d73760146 [bitboard, board] Remove BitBoard::empty() and BitBoard::full()
These have been deprecated for a while. Clean up the remaining uses and remove
the methods from BitBoard.
2025-08-15 16:14:34 -07:00
484fcf342e [board] Remove a useless .into() call
Clippy pointed this out to me. This .into() call serves no purpose.
2025-07-12 20:27:47 -07:00
a904e4a5bb [bitboard, board] Replace ray_in_direction! macro with a function
This is simpler than writing a macro, at the expense of some overhead for calling
a function. But the Rust compiler might inline it anyway!

To support this change, implement BitBoard::first_occupied_square_direction, which
iterates a bitboard in a direction (i.e. leading or trailing) depending on the
core::Direction value passed to it.
2025-06-30 15:37:35 -07:00
e3d17219ad [board, position] Simplify check methods
Only one check-testing method, Board::is_in_check(), that tests if the current
active player is in check. It doesn't make sense to test if the non-active player
is in check.
2025-06-29 09:25:08 -07:00
a30553503f [board, explorer, position] Clean up naming of sight and movement methods
These methods have a prefix, either `sight` or `movement`, and then follow the conventions
for other "trio" clusters where there's an un-suffixed method that takes an
Option<Color>, a _active method that uses the active color, and a _unwrapped
method that takes a bare Color.
2025-06-29 09:23:20 -07:00
e7fd65672d [bitboard, board] Make BitBoard::EMPTY and BitBoard::FULL public
Deprecate the methods.

I think I'm undoing a change I made earlier. 🙃
2025-06-29 09:18:44 -07:00
8db533cb52 [board] Use $crate in the fen! macro so you don't have to import Board to get one back 2025-06-27 08:44:56 -07:00
4b96db230d [board, moves] Derive Clone on several error types
- PlacePieceError
- MakeMoveError
- UnmakeMoveError
2025-06-21 21:08:04 -07:00
a91bb8c983 [board] Remove the unused Mailbox::new method
Just use Mailbox::default().
2025-06-20 14:24:16 -07:00
7f25548335 [board, core, position] A simple static evaluation method for scoring positions
Implement a new Evaluator struct that evaluates a Board and returns a score. This
evaluation mechanism uses only a material balance function. It doesn't account
for anything else.

Supporting this, add a Counts struct to the internal piece set structure of a
Board. This struct is responsible for keeping counts of how many pieces of each
shape are on the board for each color. Export a count_piece() method on Board
that returns a count of the number of pieces of a particular color and shape.

Implement a newtype wrapper around i32 called Score that represents the score of
a position in centipawns, i.e. hundredths of a pawn. Add piece values to the
Shape enum.
2025-06-20 14:23:57 -07:00
4ce7e89cdb [board, explorer, moves] Clean up the castling rights API
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.
2025-06-18 23:44:40 +00:00
933924d37a [board] When loading a FEN string, start with no castling rights
The default value of the castle::Rights struct is with all rights granted. When
loading a FEN string, start with none and add to it.
2025-06-18 08:26:29 -07:00
37cb9bcaa0 [board, moves] Reorganize castling_right API on Board
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.
2025-06-17 08:28:39 -07:00
9815a63ebb [explorer] Print some question marks if a move is generated without target/origin squares
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.
2025-06-11 08:15:06 -07:00
a7d42c6c1d [board] Remove unused imports and const variables from zobrist.rs 2025-06-08 17:17:05 -07:00
5326c1ee6a [board] Remove some unused internal PieceSet API and tests 2025-06-08 17:16:45 -07:00
634876822b [explorer] Add a load command
Loads a board position from a FEN string.

Plumb through setting the Zobrist state on an existing board. Rebuild the hash
when setting the state.
2025-06-08 16:57:36 -07:00
d44c5d6a11 [board] Remove an unused test helper function
test_hash() was never used. Remove it.
2025-06-06 21:46:13 -07:00
1686eeb35a [board] A small set of unit tests for Zobrist hashes on Board
A few tests to ensure the zobrist hash changes when changes are made to the Board.
This set isn't exhaustive.
2025-06-06 21:45:48 -07:00
651c982ead [board, moves, position] Make the Peter Ellis Jones gotcha unit tests work
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!
2025-06-06 21:45:07 -07:00
d7f426697d [board, position] Implement Zobrist hashing
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.
2025-06-05 08:22:34 -07:00
404212363e [board, moves] Make Board::castling_rights and Board::en_passant_target private
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.
2025-06-03 20:25:53 -07:00
eaab34587c [board, explorer, moves] Make Board::active_color private
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.
2025-06-02 17:29:52 -07:00
cae93cb090 [board, position] Return replaced piece when placing a piece with PlacePieceStrategy::Replace
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.
2025-06-02 15:54:00 -07:00
09fbe1be22 [board] Make Board::pieces private
Do not allow direct access to the internal piece set. Update call sites to use
Board API instead.
2025-06-02 15:46:10 -07:00
f47654cc98 [board] Trying out a new naming convention in the check methods
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.
2025-06-01 19:07:58 -07:00
724a98c2e2 [board] Implement iter() on board
This iterator yields (Square, Piece) tuples for all pieces on the board.
2025-06-01 17:28:47 -07:00
40e8e055f9 [board, moves, position] Move make_move routines to moves crate
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.
2025-05-31 19:04:21 -07:00
c3a43fd2ed [board, moves] Update some comments and docs 2025-05-31 15:14:24 -07:00
086f9c5666 [board] Replace PieceSet's derived Hash and PartialEq with bespoke implementation
The piece set duplicates some data to make lookups more efficient. Only use the
necessary, unique data for these functions.
2025-05-31 15:07:20 -07:00
eb6f2000a9 [board, moves, position] Implement KingMoveGenerator
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. 🙃
2025-05-26 23:37:33 -07:00
f005d94fc2 [bitboard, board, core, moves] Implement SliderMoveGenerator
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.
2025-05-26 17:41:43 -07:00
faca844733 [moves] Knight move generator and tests 2025-05-25 11:05:10 -07:00
ab587f379f [board] Fix a bug in PieceSet::opposing_occupancy
This method should compute occupancy of everything except the provided color. Instead
it was computing the inverse.
2025-05-24 17:54:46 -07:00
1da08df430 [board] Implement a couple handy piece getters
Board::find_pieces returns a BitBoard for all the pieces matching the given Piece.

Board::enemies returns a BitBoard of all the enemy pieces of a given Color.

Board::pawns returns a BitBoard of all the pawns of a given Color.
2025-05-23 18:39:18 -07:00
994f17091b [board] Implement Board::unwrap_color
Unwrap an Option<Color> using the board's active color in the default case.
2025-05-23 18:37:13 -07:00
3684e9b425 [board, core, bitboard] Clean up casts between Rank, File and BitBoard
Let BitBoard::rank and BitBoard::file take a Rank and File directly, instead of a
u8 by reference. And then make the Rank/File::as_index const and return a value
rather than a reference.

All this allows you to convert between Rank, File, and BitBoard at compile tile
(i.e. as a const method) rather than needing to do runtime stuff.
2025-05-23 18:32:18 -07:00
5c5d9d5018 [board] Remove some dead code from Board 2025-05-23 14:14:49 -07:00
b8a51990a3 [board, position] Implement some methods to check for whether a king is in check
Remove some dead code from Position.
2025-05-23 09:57:48 -07:00
0abe9b6c19 [board, position] Add a color argument to opposing_sight
New convention: active_color_ methods operate on the active color of the Board.
Methods without that prefix take a color parameter and operate on that.

Refactor opposing_sight to do this.
2025-05-23 09:56:47 -07:00
a92ec9aba3 [board] Add an option to display a board with ASCII characters 2025-05-23 09:53:59 -07:00
ddd14e8999 [board] Define two new types for the Clock properties of Board
Helpful for other parts of the code.
2025-05-23 09:53:29 -07:00
d5c0330fbe [board] Add PieceSet::find_pieces
Takes a Piece and returns a BitBoard representing where pieces of that shape and
color are on the board.
2025-05-23 09:52:22 -07:00
e89bca9877 [board] Remove empty errors module 2025-05-21 10:09:55 -07:00
dbca7b4f88 [position, board] Move castle, movement, and sight modules to the board crate
Nothing about this code depends on Position, so push it down to a lower layer.
2025-05-21 10:08:59 -07:00
7c9c5484ba [position, board] Remove a bunch of dead code 2025-05-20 19:29:39 -07:00
54ac88aaf7 [board] Remove some old PlacedPiece code 2025-05-19 16:50:44 -07:00
0c1863acb9 [board, core, moves, position] Implement castling
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.
2025-05-19 16:50:30 -07:00
6e0e33b5f9 [board] Remove the FromIterator<PlacedPiece> impl for PieceSet 2025-05-19 08:37:04 -07:00