Commit graph

132 commits

Author SHA1 Message Date
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
74c0e4144f [position] Remove the to_move_factor from symmetric evaluation
Just use material balance.
2025-06-24 20:04:41 -07:00
1ae6d5df48 [core, position] Rename the type of Score's inner value → Value 2025-06-24 20:01:05 -07:00
9f2dc3fa76 [position] Update import ordering in position.rs 2025-06-21 21:09:01 -07:00
54d9c3838d [position] Export Position::active_color()
Passes through to the Board method.
2025-06-21 21:08:32 -07:00
f84319272c [explorer, position] Make Position.board private to the crate
Export a Position::board() method that returns a reference to the internal Board.
2025-06-21 21:07:26 -07:00
4ae1fd62b7 [perft] Remove an unused Move import
This was causing a warning.
2025-06-20 14:25:27 -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
0f5a538f0a [explorer, perft, position] Move node count into a new PerftCounters struct 2025-06-19 11:34:59 -07:00
c5cc0646ef [perft] Add back the block on searching into seen positions
Check if the board position has been seen and stop recursion if so.
2025-06-18 08:22:12 -07:00
6996cbeb15 [position] Remove the Perft trait
It wasn't serving a purpose.
2025-06-17 16:42:57 -07:00
d73630c85e [perft, position] Print moves and nodes counted at first level
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.
2025-06-17 16:17:46 -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
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
6a8cc0dc5b [position] Remove unused CapturesList::last() 2025-06-12 17:37:53 -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
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
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
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
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
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
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
f60cb8cf69 [moves, position] Implement unmaking moves on a board
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.
2025-05-31 20:17:18 -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
ecde338602 [position] Remove some unused imports 2025-05-31 15:14:42 -07:00
34e8c08c36 [moves, position] Move MoveRecord to the moves crate 2025-05-31 14:32:39 -07:00
b8f45aaece [position] Add some documentation to MoveRecord 2025-05-31 14:28:27 -07:00
a4b713a558 [position] Remove dead move_generator code 2025-05-29 09:21:25 -07:00
942d9fe47b [explorer, moves, position] Implement a moves command in explorer
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.
2025-05-28 16:25:55 -07:00
43abbe3fe2 [position] Register a captured piece in the MoveRecord
For unmake_move, so the captured piece can be restored.
2025-05-28 16:23:46 -07:00
19c6c6701a [position] Fix broken tests build
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.
2025-05-27 12:04:24 -07:00
085697d38f [position] Remove position::position module
Move the implementation of Position to the top-level position.rs file.
2025-05-27 11:59:42 -07:00
db489af50b [position] Move unmake move stuff to an unmake_move module 2025-05-27 11:52:17 -07:00
a8ea248972 [position] Derive Default implementation for Position 2025-05-27 11:51:08 -07:00
e3ca466737 [position] Move capture list to a CapturesList struct
This one has a custom Display implementation for easier integration with Positon's.
2025-05-27 11:51:08 -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
5466693c1b [position] Remove empty implementation of Position::unmake_move 2025-05-23 18:39:38 -07:00
588f049290 [position] Remove display module 2025-05-23 14:15:38 -07:00
a9268ad194 [position] Add move tracking to Position
Create a new MoveRecord struct that tracks the move (aka ply) and irreversible
board properties. This should make it easier to unmake moves in the future.
2025-05-23 10:00:20 -07:00
05c62dcd99 [position] Remove CastleEvaluationError from this crate
It moved to board.
2025-05-23 09:58:29 -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