Commit graph

500 commits

Author SHA1 Message Date
45037d6fc1 [explorer, moves, perft] A few rustfmt changes that reorder imports 2025-06-16 13:49:29 -07:00
bd112efdf3 Reformat the members list in Cargo.toml
When I added the perft crate, it added it alphabetically at the end of the line
with the moves crate. Why?
2025-06-16 09:02:36 -07:00
7744dd06f0 [position, perft] Move Perft into the position crate
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.
2025-06-16 09:01:58 -07:00
f0b6cb5f08 [core] Do a little cleanup in core::coordinates
Import std::fmt and remove some commented out code.
2025-06-16 08:58:22 -07:00
3951af76cb [core, moves, position] Implement parsing long algebraic moves
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.
2025-06-16 08:57:48 -07:00
fb182e7ac0 [perft] Print the depth of the position being checked in check-positions 2025-06-15 16:24:19 -07:00
df49a4950b [moves] Implement GeneratedMove::ply()
Returns a copy of the generated move. This is simplier to use than ::into().
2025-06-13 11:27:10 -07:00
e9cea33c20 [moves] Update ply! to take a color to the castle move
This supports castle moves having target and origin squares.
2025-06-12 17:41:31 -07:00
6a8cc0dc5b [position] Remove unused CapturesList::last() 2025-06-12 17:37:53 -07:00
3c31f900ea [perft] A script that checks a list of positions against known node counts
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.
2025-06-11 08:16:59 -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
659ffb6130 [core] Remove an unused import 2025-06-08 17:34:52 -07:00
4b148529a1 [bitboard] Fix the warning about shared references to mutable static data
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.
2025-06-08 17:34:42 -07:00
0167794346 [perft] A small Perft program 2025-06-08 17:19:00 -07:00
ae0ae49093 [core] Remove PlacedPiece
This has been deprecated for a while now. Finally remove it!
2025-06-08 17:18:41 -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
651544fdd9 [explorer, moves, position] Remove unused MoveBuilder
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.
2025-06-08 17:16:23 -07:00
035a83723d [position] Remove a debugging println! I accidentally committed 2025-06-08 16:58:54 -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
428ace13dc [moves] Remove special handling of EnPassant moves
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.
2025-06-08 16:49:18 -07:00
bba910090c [bitboard] Specify array lengths with ::NUM values from core types
These were static bare integer values. Use ::NUM properties of various core types
instead.
2025-06-07 20:53:41 -07:00
93c17a2740 [moves] Lowercase expect() error message
Rust convention is short lowercase strings for these kinds of messages.
2025-06-07 20:52:27 -07:00
3bdeea00e5 [position] Re-export moves::ValidateMove from the position crate 2025-06-07 20:09:51 -07:00
5085cef197 [position] Remove some unused imports from peterellisjones tests 2025-06-07 20:09:15 -07:00
638ef5e66a [moves] Make sure en passant state is cleared after making moves
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.
2025-06-07 20:08:25 -07:00
e2ce778247 [core, moves] Improve bounds checking of Square::neighbor
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.
2025-06-07 20:06:14 -07:00
a8d83ad81d [moves] Remove common suffix of attributes of AllPiecesMoveGenerator
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.
2025-06-07 19:36:51 -07:00
8fd7ffa586 [moves, position] Improve the error messages when asserting during legal move generation
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.
2025-06-07 19:35:32 -07:00
a9674e3215 [moves] Mark generated moves with square brackets
Update the implementation of Display for GeneratedMove to wrap the ply in [ ].
2025-06-07 19:33:05 -07:00
6cca3a0f52 [moves] Bug: En passant moves were generated when no pawn was available to capture
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.
2025-06-07 10:09:33 -07:00
6b5a54f6b4 [explorer] Remove unused MakeMove import 2025-06-07 08:55:54 -07:00
d2fc285af5 [position] Track seen board positions in a HashSet
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.
2025-06-07 08:55:34 -07:00
c70cefc848 [position] Pop captured piece from CapturesList on unmake 2025-06-07 08:53:42 -07:00
c476e93f33 [position] Remove cfg(test) from test_position! macro 2025-06-07 08:51:46 -07:00
bba88bd0e6 [position] Export Position::zobrist_hash()
Pass-through call to self.board.zobrist_hash() to return the hash value of the
current board position.
2025-06-07 08:50:20 -07:00
046aae72c8 Add a rustfmt.toml file
Reorganize and reformat imports, and make sure comments are wrapped to 80.
2025-06-07 08:48:45 -07:00
fb8a8d6110 [explorer] Add zobrist command
It prints the hash of the board!
2025-06-07 08:09: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
6d0df32f74 [core] Random Number Generator
Implement a random number generator as a thin wrapper around rand::StdRng. Provide
some default seed data to get consistent random numbers.
2025-06-02 15:44:38 -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
f8a3d5831e [moves] Implement From<GeneratedMove> for Move
For easy conversion of a GeneratedMove to a Move.
2025-06-01 19:03:28 -07:00
8f42a4c94e [explorer, moves, position] Implement bespoke make_move and unmake_move methods on Position
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.
2025-06-01 19:02:53 -07:00