diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/.lock b/docs/.lock new file mode 100644 index 00000000..e69de29b diff --git a/docs/crates.js b/docs/crates.js new file mode 100644 index 00000000..5ee7b966 --- /dev/null +++ b/docs/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["pabi"]; \ No newline at end of file diff --git a/docs/help.html b/docs/help.html new file mode 100644 index 00000000..788144c5 --- /dev/null +++ b/docs/help.html @@ -0,0 +1,2 @@ +
pub(super) struct AttackInfo {
+ pub(super) attacks: Bitboard,
+ pub(super) checkers: Bitboard,
+ pub(super) pins: Bitboard,
+ pub(super) xrays: Bitboard,
+ pub(super) safe_king_squares: Bitboard,
+}
attacks: Bitboard
§checkers: Bitboard
§pins: Bitboard
§xrays: Bitboard
§safe_king_squares: Bitboard
self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreBitboard
-based representation for crate::chess::position::Position
.
+Bitboards utilize the fact that modern processors operate on 64 bit
+integers, and the bit operations can be performed simultaneously. This
+results in very efficient calculation of possible attack vectors and other
+meaningful features that are required to calculate possible moves and
+evaluate position. The disadvantage is complexity that comes with bitboard
+implementation and inefficiency of some operations like “get piece type on
+given square” (efficiently handled by Square-centric board implementations
+that can be used together bitboard-based approach to compensate its
+shortcomings).
crate::chess::position::Position
, Bitboard is not very useful on
+its own.pub struct Bitboard {
+ bits: u64,
+}
Represents a set of squares and provides common operations (e.g. AND, OR, +XOR) over these sets. Each bit corresponds to one of 64 squares of the chess +board.
+Mirroring Square
semantics, the least significant
+bit corresponds to A1, and the most significant bit - to H8.
Bitboard is a thin wrapper around u64.
+bits: u64
Constructs Bitboard from pre-calculated bits.
+Constructs a bitboard representing the universal set, it contains all +squares by setting all bits to binary one.
+Returns true if this bitboard contains given square.
+An efficient way to iterate over the set squares.
+&=
operation. Read more|=
operation. Read more-=
operation. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub(super) struct BitboardIterator {
+ bits: u64,
+}
bits: u64
iter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moren
th element of the iterator. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moren
elements in the base iterator
+for each iteration. Read more.map_ok()
.Result::Ok
value. Result::Err
values are
+unchanged. Read moreResult::Ok
+value with the provided closure. Result::Err
values are
+unchanged. Read moreResult::Ok
value with the provided closure. Result::Err
+values are unchanged. Read moreResult::Ok
value into
+a series of Result::Ok
values. Result::Err
values are unchanged. Read moreResult
values instead. Read moreself
and J
. Read moreself
. Read moreaccept
returns true
. Read moreClone
-able iterator
+to only pick off elements while the predicate accept
returns true
. Read moretrue
, including the element for which the predicate
+first returned false
. Read moreOption<A>
iterator elements
+and produces A
. Stops on the first None
encountered. Read morek
-length combinations of
+the elements from an iterator. Read morek
-length combinations of
+the elements from an iterator, with replacement. Read moremin
by filling missing elements using a closure f
. Read morePosition
to
+ease special-case handling of the first or last elements. Read moretrue
if the given item is present in this iterator. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moref
eagerly on each element of the iterator. Read more.collect_vec()
is simply a type specialization of Iterator::collect
,
+for convenience.self
from the from
iterator,
+stopping at the shortest of the two iterators. Read moresep
. Read moresep
. Read moresep
. Read more.fold_ok()
.Result
values from an iterator. Read moreOption
values from an iterator. Read moreIterator::reduce
insteadIterator::partition
, each partition may
+have a distinct type. Read moreResult
s into one list of all the Ok
elements
+and another list of all the Err
elements. Read moreHashMap
of keys mapped to Vec
s of values. Keys and values
+are taken from (Key, Value)
tuple pairs yielded by the input iterator. Read moreIterator
on a HashMap
. Keys mapped to Vec
s of values. The key is specified
+in the closure. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read more.next()
+values without advancing the base iterator. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears,
+determining identity using a keying function. Read moreParallelIterator
.pub(super) struct Board {
+ pub(super) white_pieces: Pieces,
+ pub(super) black_pieces: Pieces,
+}
Piece-centric implementation of the chess board. This is the “back-end” of +the chess engine, an efficient board representation is crucial for +performance. An alternative implementation would be Square-Piece table but +both have different trade-offs and scenarios where they are efficient. It is +likely that the best overall performance can be achieved by keeping both to +complement each other.
+white_pieces: Pieces
§black_pieces: Pieces
self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub(super) struct Pieces {
+ pub(super) king: Bitboard,
+ pub(super) queens: Bitboard,
+ pub(super) rooks: Bitboard,
+ pub(super) bishops: Bitboard,
+ pub(super) knights: Bitboard,
+ pub(super) pawns: Bitboard,
+}
Piece-centric representation of all material owned by one player. Uses
+Bitboard to store a set of squares occupied by each piece. The main user
+is crate::chess::position::Position
, Bitboard is not very useful on
+its own.
king: Bitboard
§queens: Bitboard
§rooks: Bitboard
§bishops: Bitboard
§knights: Bitboard
§pawns: Bitboard
self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub enum Direction {
+ Up,
+ Down,
+}
Directions on the board from a perspective of White player.
+Traditionally those are North (Up), West (Left), East (Right), South (Down) +and their combinations. However, using cardinal directions is confusing, +hence they are replaced by relative directions.
+self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read more#[repr(u8)]pub enum File {
+ A = 0,
+ B = 1,
+ C = 2,
+ D = 3,
+ E = 4,
+ F = 5,
+ G = 6,
+ H = 7,
+}
Represents a column (vertical row) of the chessboard. In chess notation, it +is normally represented with a lowercase letter.
+self
and other
) and is used by the <=
+operator. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub enum PieceKind {
+ King = 1,
+ Queen = 2,
+ Rook = 3,
+ Bishop = 4,
+ Knight = 5,
+ Pawn = 6,
+}
Standard chess pieces.
+self
and other
) and is used by the <=
+operator. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub enum Player {
+ White,
+ Black,
+}
A standard game of chess is played between two players: White (having the +advantage of the first turn) and Black.
+self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub enum Promotion {
+ Queen,
+ Rook,
+ Bishop,
+ Knight,
+}
A pawn can be promoted to a queen, rook, bishop or a knight.
+self
and other
) and is used by the <=
+operator. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read more#[repr(u8)]pub enum Rank {
+ One = 0,
+ Two = 1,
+ Three = 2,
+ Four = 3,
+ Five = 4,
+ Six = 5,
+ Seven = 6,
+ Eight = 7,
+}
Represents a horizontal row of the chessboard. In chess notation, it is +represented with a number. The implementation assumes zero-based values +(i.e. rank 1 would be 0).
+self
and other
) and is used by the <=
+operator. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read more#[repr(u8)]pub enum Square {
+Show 64 variants
A1 = 0,
+ B1 = 1,
+ C1 = 2,
+ D1 = 3,
+ E1 = 4,
+ F1 = 5,
+ G1 = 6,
+ H1 = 7,
+ A2 = 8,
+ B2 = 9,
+ C2 = 10,
+ D2 = 11,
+ E2 = 12,
+ F2 = 13,
+ G2 = 14,
+ H2 = 15,
+ A3 = 16,
+ B3 = 17,
+ C3 = 18,
+ D3 = 19,
+ E3 = 20,
+ F3 = 21,
+ G3 = 22,
+ H3 = 23,
+ A4 = 24,
+ B4 = 25,
+ C4 = 26,
+ D4 = 27,
+ E4 = 28,
+ F4 = 29,
+ G4 = 30,
+ H4 = 31,
+ A5 = 32,
+ B5 = 33,
+ C5 = 34,
+ D5 = 35,
+ E5 = 36,
+ F5 = 37,
+ G5 = 38,
+ H5 = 39,
+ A6 = 40,
+ B6 = 41,
+ C6 = 42,
+ D6 = 43,
+ E6 = 44,
+ F6 = 45,
+ G6 = 46,
+ H6 = 47,
+ A7 = 48,
+ B7 = 49,
+ C7 = 50,
+ D7 = 51,
+ E7 = 52,
+ F7 = 53,
+ G7 = 54,
+ H7 = 55,
+ A8 = 56,
+ B8 = 57,
+ C8 = 58,
+ D8 = 59,
+ E8 = 60,
+ F8 = 61,
+ G8 = 62,
+ H8 = 63,
+}
Board squares: from left to right, from bottom to the top (Little-Endian Rank-File Mapping):
+ +use pabi::chess::core::Square;
+
+assert_eq!(Square::A1 as u8, 0);
+assert_eq!(Square::E1 as u8, 4);
+assert_eq!(Square::H1 as u8, 7);
+assert_eq!(Square::A4 as u8, 8 * 3);
+assert_eq!(Square::H8 as u8, 63);
Square is a compact representation using only one byte.
+ +use pabi::chess::core::Square;
+use std::mem;
+
+assert_eq!(std::mem::size_of::<Square>(), 1);
self
and other
) and is used by the <=
+operator. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreChess primitives commonly used within crate::chess
.
crate::chess::position::Position
and change the board state. Moves are
+not sorted according to their potential “value” by the move generator. The
+move representation has one-to-one correspondence with the UCI move
+representation. The moves can also be indexed and fed as an input to the
+Neural Network evaluators that would be able assess their potential without
+evaluating post-states.MoveList
and an upper bound of moves in a chess position (which
+seems to be 218. 256 provides the best
+performance through optimal memory alignment.std::Vec
with unknown capacity.pub struct CastleRights(<CastleRights as PublicFlags>::Internal);
Track the ability to castle each side (kingside is often referred to +as O-O or h-side castle, queenside – O-O-O or a-side castle). When the +king moves, player loses ability to castle. When the rook moves, player +loses ability to castle to the side from which the rook moved.
+Castling is relatively straightforward in the Standard Chess but is +often misunderstood in Fischer Random Chess (also known as FRC or +Chess960). An easy mnemonic is that the king and the rook end up on the +same files for both Standard and FRC:
+File::G
and the
+rook on File::F
File::C
and the
+rook on File::D
The full rules are:
+0: <CastleRights as PublicFlags>::Internal
Get the underlying bits value.
+The returned value is exactly the bits set in this flags value.
+Convert from a bits value.
+This method will return None
if any unknown bits are set.
Convert from a bits value, unsetting any unknown bits.
+Convert from a bits value exactly.
+Get a flags value with the bits of a flag with the given name set.
+This method will return None
if name
is empty or doesn’t
+correspond to any named flag.
Whether any set bits in a source flags value are also set in a target flags value.
+Whether all set bits in a source flags value are also set in a target flags value.
+The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+remove
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
Call insert
when value
is true
or remove
when value
is false
.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
The bitwise exclusive-or (^
) of the bits in two flags values.
The bitwise negation (!
) of the bits in a flags value, truncating the result.
Yield a set of contained flags values.
+Each yielded flags value will correspond to a defined named flag. Any unknown bits +will be yielded together as a final flags value.
+Yield a set of contained named flags values.
+This method is like iter
, except only yields bits in contained named flags.
+Any unknown bits, or bits not corresponding to a contained flag will not be yielded.
The bitwise and (&
) of the bits in two flags values.
The bitwise or (|
) of the bits in two flags values.
|
operator.The bitwise or (|
) of the bits in two flags values.
The bitwise exclusive-or (^
) of the bits in two flags values.
source
. Read moreThe bitwise or (|
) of the bits in each flags value.
extend_one
)extend_one
)|
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.Flags::insert
] when value
is true
or [Flags::remove
] when value
is false
.&
) of the bits in two flags values.&!
). Read more^
) of the bits in two flags values.!
) of the bits in a flags value, truncating the result.The bitwise or (|
) of the bits in each flags value.
self
and other
values to be equal, and is used
+by ==
.The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
-
operator.The intersection of a source flags value with the complement of a target flags value (&!
).
This method is not equivalent to self & !other
when other
has unknown bits set.
+difference
won’t truncate other
, but the !
operator will.
Parses CastleRights
for both players from the FEN format. The user
+is responsible for providing valid input cleaned up from the actual FEN
+chunk.
Returns anyhow::Error
if given pattern does not match
CastleRights
:= (K)? (Q)? (k)? (q)?
Note that both letters have to be either uppercase or lowercase.
+self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub struct DirectionIter {
+ idx: usize,
+ back_idx: usize,
+ marker: PhantomData<()>,
+}
An iterator over the variants of Direction
+idx: usize
§back_idx: usize
§marker: PhantomData<()>
iter_advance_by
)n
elements. Read moren
th element from the end of the iterator. Read moreIterator::try_fold()
: it takes
+elements starting from the back of the iterator. Read moren
th element of the iterator. Read moreiter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_partition_in_place
)true
precede all those that return false
.
+Returns the number of true
elements found. Read moreiter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moren
elements in the base iterator
+for each iteration. Read more.map_ok()
.Result::Ok
value. Result::Err
values are
+unchanged. Read moreResult::Ok
+value with the provided closure. Result::Err
values are
+unchanged. Read moreResult::Ok
value with the provided closure. Result::Err
+values are unchanged. Read moreResult::Ok
value into
+a series of Result::Ok
values. Result::Err
values are unchanged. Read moreResult
values instead. Read moreself
and J
. Read moreself
. Read moreaccept
returns true
. Read moreClone
-able iterator
+to only pick off elements while the predicate accept
returns true
. Read moretrue
, including the element for which the predicate
+first returned false
. Read moreOption<A>
iterator elements
+and produces A
. Stops on the first None
encountered. Read morek
-length combinations of
+the elements from an iterator. Read morek
-length combinations of
+the elements from an iterator, with replacement. Read moremin
by filling missing elements using a closure f
. Read morePosition
to
+ease special-case handling of the first or last elements. Read moretrue
if the given item is present in this iterator. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moref
eagerly on each element of the iterator. Read more.collect_vec()
is simply a type specialization of Iterator::collect
,
+for convenience.self
from the from
iterator,
+stopping at the shortest of the two iterators. Read moresep
. Read moresep
. Read moresep
. Read more.fold_ok()
.Result
values from an iterator. Read moreOption
values from an iterator. Read moreIterator::reduce
insteadIterator::partition
, each partition may
+have a distinct type. Read moreResult
s into one list of all the Ok
elements
+and another list of all the Err
elements. Read moreHashMap
of keys mapped to Vec
s of values. Keys and values
+are taken from (Key, Value)
tuple pairs yielded by the input iterator. Read moreIterator
on a HashMap
. Keys mapped to Vec
s of values. The key is specified
+in the closure. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read more.next()
+values without advancing the base iterator. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears,
+determining identity using a keying function. Read moreParallelIterator
.pub struct FileIter {
+ idx: usize,
+ back_idx: usize,
+ marker: PhantomData<()>,
+}
An iterator over the variants of File
+idx: usize
§back_idx: usize
§marker: PhantomData<()>
iter_advance_by
)n
elements. Read moren
th element from the end of the iterator. Read moreIterator::try_fold()
: it takes
+elements starting from the back of the iterator. Read moren
th element of the iterator. Read moreiter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_partition_in_place
)true
precede all those that return false
.
+Returns the number of true
elements found. Read moreiter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moren
elements in the base iterator
+for each iteration. Read more.map_ok()
.Result::Ok
value. Result::Err
values are
+unchanged. Read moreResult::Ok
+value with the provided closure. Result::Err
values are
+unchanged. Read moreResult::Ok
value with the provided closure. Result::Err
+values are unchanged. Read moreResult::Ok
value into
+a series of Result::Ok
values. Result::Err
values are unchanged. Read moreResult
values instead. Read moreself
and J
. Read moreself
. Read moreaccept
returns true
. Read moreClone
-able iterator
+to only pick off elements while the predicate accept
returns true
. Read moretrue
, including the element for which the predicate
+first returned false
. Read moreOption<A>
iterator elements
+and produces A
. Stops on the first None
encountered. Read morek
-length combinations of
+the elements from an iterator. Read morek
-length combinations of
+the elements from an iterator, with replacement. Read moremin
by filling missing elements using a closure f
. Read morePosition
to
+ease special-case handling of the first or last elements. Read moretrue
if the given item is present in this iterator. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moref
eagerly on each element of the iterator. Read more.collect_vec()
is simply a type specialization of Iterator::collect
,
+for convenience.self
from the from
iterator,
+stopping at the shortest of the two iterators. Read moresep
. Read moresep
. Read moresep
. Read more.fold_ok()
.Result
values from an iterator. Read moreOption
values from an iterator. Read moreIterator::reduce
insteadIterator::partition
, each partition may
+have a distinct type. Read moreResult
s into one list of all the Ok
elements
+and another list of all the Err
elements. Read moreHashMap
of keys mapped to Vec
s of values. Keys and values
+are taken from (Key, Value)
tuple pairs yielded by the input iterator. Read moreIterator
on a HashMap
. Keys mapped to Vec
s of values. The key is specified
+in the closure. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read more.next()
+values without advancing the base iterator. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears,
+determining identity using a keying function. Read moreParallelIterator
.pub struct Move {
+ pub(super) from: Square,
+ pub(super) to: Square,
+ pub(super) promotion: Option<Promotion>,
+}
Represents any kind of a legal chess move. A move is the only way to mutate
+crate::chess::position::Position
and change the board state. Moves are
+not sorted according to their potential “value” by the move generator. The
+move representation has one-to-one correspondence with the UCI move
+representation. The moves can also be indexed and fed as an input to the
+Neural Network evaluators that would be able assess their potential without
+evaluating post-states.
For a move to be serialized in Standard Algebraic Notation (SAN), it also
+also requires the crate::chess::position::Position
it will be applied
+in, because SAN requires additional flags (e.g. indicating
+“check”/“checkmate” or moving piece disambiguation).
from: Square
§to: Square
§promotion: Option<Promotion>
self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub struct Piece {
+ pub owner: Player,
+ pub kind: PieceKind,
+}
Represents a specific piece owned by a player.
+owner: Player
§kind: PieceKind
self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub struct RankIter {
+ idx: usize,
+ back_idx: usize,
+ marker: PhantomData<()>,
+}
An iterator over the variants of Rank
+idx: usize
§back_idx: usize
§marker: PhantomData<()>
iter_advance_by
)n
elements. Read moren
th element from the end of the iterator. Read moreIterator::try_fold()
: it takes
+elements starting from the back of the iterator. Read moren
th element of the iterator. Read moreiter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_partition_in_place
)true
precede all those that return false
.
+Returns the number of true
elements found. Read moreiter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moren
elements in the base iterator
+for each iteration. Read more.map_ok()
.Result::Ok
value. Result::Err
values are
+unchanged. Read moreResult::Ok
+value with the provided closure. Result::Err
values are
+unchanged. Read moreResult::Ok
value with the provided closure. Result::Err
+values are unchanged. Read moreResult::Ok
value into
+a series of Result::Ok
values. Result::Err
values are unchanged. Read moreResult
values instead. Read moreself
and J
. Read moreself
. Read moreaccept
returns true
. Read moreClone
-able iterator
+to only pick off elements while the predicate accept
returns true
. Read moretrue
, including the element for which the predicate
+first returned false
. Read moreOption<A>
iterator elements
+and produces A
. Stops on the first None
encountered. Read morek
-length combinations of
+the elements from an iterator. Read morek
-length combinations of
+the elements from an iterator, with replacement. Read moremin
by filling missing elements using a closure f
. Read morePosition
to
+ease special-case handling of the first or last elements. Read moretrue
if the given item is present in this iterator. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moref
eagerly on each element of the iterator. Read more.collect_vec()
is simply a type specialization of Iterator::collect
,
+for convenience.self
from the from
iterator,
+stopping at the shortest of the two iterators. Read moresep
. Read moresep
. Read moresep
. Read more.fold_ok()
.Result
values from an iterator. Read moreOption
values from an iterator. Read moreIterator::reduce
insteadIterator::partition
, each partition may
+have a distinct type. Read moreResult
s into one list of all the Ok
elements
+and another list of all the Err
elements. Read moreHashMap
of keys mapped to Vec
s of values. Keys and values
+are taken from (Key, Value)
tuple pairs yielded by the input iterator. Read moreIterator
on a HashMap
. Keys mapped to Vec
s of values. The key is specified
+in the closure. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read more.next()
+values without advancing the base iterator. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears,
+determining identity using a keying function. Read moreParallelIterator
.pub struct SquareIter {
+ idx: usize,
+ back_idx: usize,
+ marker: PhantomData<()>,
+}
An iterator over the variants of Square
+idx: usize
§back_idx: usize
§marker: PhantomData<()>
iter_advance_by
)n
elements. Read moren
th element from the end of the iterator. Read moreIterator::try_fold()
: it takes
+elements starting from the back of the iterator. Read moren
th element of the iterator. Read moreiter_next_chunk
)N
values. Read moreiter_advance_by
)n
elements. Read moreiter_intersperse
)separator
between adjacent
+items of the original iterator. Read moreiter_intersperse
)separator
+between adjacent items of the original iterator. Read moren
elements. Read moren
elements, or fewer
+if the underlying iterator ends sooner. Read moreiter_map_windows
)f
for each contiguous window of size N
over
+self
and returns an iterator over the outputs of f
. Like slice::windows()
,
+the windows during mapping overlap as well. Read moreiter_collect_into
)iter_partition_in_place
)true
precede all those that return false
.
+Returns the number of true
elements found. Read moreiter_is_partitioned
)true
precede all those that return false
. Read moreiterator_try_reduce
)try_find
)iter_array_chunks
)N
elements of the iterator at a time. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read morePartialOrd
elements of
+this Iterator
with those of another. The comparison works like short-circuit
+evaluation, returning a result without comparing the remaining elements.
+As soon as an order can be determined, the evaluation stops and a result is returned. Read moreiter_order_by
)Iterator
with those
+of another with respect to the specified comparison function. Read moreiter_order_by
)Iterator
are lexicographically
+less than those of another. Read moreIterator
are lexicographically
+less or equal to those of another. Read moreIterator
are lexicographically
+greater than those of another. Read moreIterator
are lexicographically
+greater than or equal to those of another. Read moreis_sorted
)is_sorted
)is_sorted
)self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moren
elements in the base iterator
+for each iteration. Read more.map_ok()
.Result::Ok
value. Result::Err
values are
+unchanged. Read moreResult::Ok
+value with the provided closure. Result::Err
values are
+unchanged. Read moreResult::Ok
value with the provided closure. Result::Err
+values are unchanged. Read moreResult::Ok
value into
+a series of Result::Ok
values. Result::Err
values are unchanged. Read moreResult
values instead. Read moreself
and J
. Read moreself
. Read moreaccept
returns true
. Read moreClone
-able iterator
+to only pick off elements while the predicate accept
returns true
. Read moretrue
, including the element for which the predicate
+first returned false
. Read moreOption<A>
iterator elements
+and produces A
. Stops on the first None
encountered. Read morek
-length combinations of
+the elements from an iterator. Read morek
-length combinations of
+the elements from an iterator, with replacement. Read moremin
by filling missing elements using a closure f
. Read morePosition
to
+ease special-case handling of the first or last elements. Read moretrue
if the given item is present in this iterator. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moren
elements from the iterator eagerly,
+and return the same iterator again. Read moref
eagerly on each element of the iterator. Read more.collect_vec()
is simply a type specialization of Iterator::collect
,
+for convenience.self
from the from
iterator,
+stopping at the shortest of the two iterators. Read moresep
. Read moresep
. Read moresep
. Read more.fold_ok()
.Result
values from an iterator. Read moreOption
values from an iterator. Read moreIterator::reduce
insteadIterator::partition
, each partition may
+have a distinct type. Read moreResult
s into one list of all the Ok
elements
+and another list of all the Err
elements. Read moreHashMap
of keys mapped to Vec
s of values. Keys and values
+are taken from (Key, Value)
tuple pairs yielded by the input iterator. Read moreIterator
on a HashMap
. Keys mapped to Vec
s of values. The key is specified
+in the closure. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read moreGroupingMap
to be used later with one of the efficient
+group-and-fold operations it allows to perform. Read more.next()
+values without advancing the base iterator. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears. Read moreHashMap
which
+contains each item that appears in the iterator and the number
+of times it appears,
+determining identity using a keying function. Read moreParallelIterator
.pub type MoveList = ArrayVec<Move, { MAX_MOVES }>;
Moves are stored on stack to avoid memory allocations. This is important for
+performance reasons and also prevents unnecessary copying that would occur
+if the moves would be stored in std::Vec
with unknown capacity.
struct MoveList {
+ xs: [MaybeUninit<Move>; 256],
+ len: u32,
+}
xs: [MaybeUninit<Move>; 256]
§len: u32
Implementation of chess environment, its rules and specifics.
+Bitboard
-based representation for crate::chess::position::Position
.
+Bitboards utilize the fact that modern processors operate on 64 bit
+integers, and the bit operations can be performed simultaneously. This
+results in very efficient calculation of possible attack vectors and other
+meaningful features that are required to calculate possible moves and
+evaluate position. The disadvantage is complexity that comes with bitboard
+implementation and inefficiency of some operations like “get piece type on
+given square” (efficiently handled by Square-centric board implementations
+that can be used together bitboard-based approach to compensate its
+shortcomings).crate::chess
.fn generate_pawn_moves(
+ pawns: Bitboard,
+ us: Player,
+ they: Player,
+ their_pieces: &Pieces,
+ their_occupancy: Bitboard,
+ their_or_empty: Bitboard,
+ blocking_ray: Bitboard,
+ pins: Bitboard,
+ checkers: Bitboard,
+ king: Square,
+ en_passant_square: Option<Square>,
+ occupied_squares: Bitboard,
+ moves: &mut MoveList
+)
Provides fully-specified Chess Position implementation: stores +information about the board and tracks the state of castling, 50-move rule +draw, etc.
+The core of Move Generator and move making is also implemented here as a way
+to produce ways of mutating Position
.
pub struct Position {
+ board: Board,
+ castling: CastleRights,
+ side_to_move: Player,
+ halfmove_clock: u8,
+ fullmove_counter: u16,
+ en_passant_square: Option<Square>,
+}
State of the chess game: board, half-move counters and castling rights, +etc. It has 1:1 relationship with Forsyth-Edwards Notation (FEN).
+Position::try_from()
provides a convenient interface for creating a
+Position
. It will clean up the input (trim newlines and whitespace) and
+attempt to parse in either FEN or a version of Extended Position
+Description (EPD). The EPD format Pabi accepts does not support
+Operations: even though it is an important part of EPD, in practice it is
+rarely needed. The EPD support exists for compatibility with some databases
+which provide trimmed FEN lines (all FEN parts except Halfmove Clock and
+Fullmove Counter). Parsing these positions is important to utilize that
+data.
board: Board
§castling: CastleRights
§side_to_move: Player
§halfmove_clock: u8
Halfmove Clock1 keeps track of the number of (half-)moves +since the last capture or pawn move and is used to enforce +fifty2-move draw rule.
+fullmove_counter: u16
§en_passant_square: Option<Square>
Creates the starting position of the standard chess.
+ +use pabi::chess::position::Position;
+
+let starting_position = Position::starting();
+assert_eq!(
+ &starting_position.to_string(),
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
+);
Parses board from Forsyth-Edwards Notation and checks its correctness. +The parser will accept trimmed full FEN and trimmed FEN (4 first parts).
+FEN ::= +Piece Placement +’ ’ Side to move +’ ’ Castling ability +’ ’ En passant target square +’ ’ Halfmove clock +’ ’ Fullmove counter
+The last two parts (together) are optional and will default to “0 1”. +Technically, that is not a full FEN position, but it is supported +because EPD-style position strings are common in public position books +and datasets where halfmove clock and fullmove counters do not matter. +Supporting these datasets is important but distinguishing between full +and trimmed FEN strings is not.
+Correctness check employs a small set of simple heuristics to check if
+the position can be analyzed by the engine and will reject the most
+obvious incorrect positions (e.g. missing kings, pawns on the wrong
+ranks, problems with en passant square). The only public way of creating
+a Position
is by parsing it from string, so this acts as a filter
+for positions that won’t cause undefined behavior or crashes. It’s
+important that positions that are known to be dubious are filtered out.
NOTE: This expects properly-formatted inputs: no extra symbols or
+additional whitespace. Use Position::try_from
for cleaning up the
+input if it is coming from untrusted source and is likely to contain
+extra symbols.
Calculates a list of legal moves (i.e. the moves that do not leave our +king in check).
+This is a performance and correctness-critical path: every modification +should be benchmarked and carefully tested.
+NOTE: BMI Instruction Set (and specifically efficient PEXT) is not +widely available on all processors (e.g. the AMD only started providing +an efficient PEXT since Ryzen 3). The current implementation will +rely on PEXT for performance because it is the most efficient move +generator technique available.
+self
into a Left
variant of Either<Self, Self>
+if into_left
is true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read moreself
into a Left
variant of Either<Self, Self>
+if into_left(&self)
returns true
.
+Converts self
into a Right
variant of Either<Self, Self>
+otherwise. Read morepub fn print_system_info()
Prints information about the host system.
+Convenient utility functions for Pabi that can be used from benchmarks and +public tests.
+Bitboard
-based representation for …","Chess primitives commonly used within crate::chess
.","Provides fully-specified Chess Position implementation: …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self)
.","","","","","","","","","","","","","","","","Represents a set of squares and provides common operations …","Iterates over set squares in a given Bitboard from least …","Piece-centric implementation of the chess board. This is …","","Piece-centric representation of all material owned by one …","","","","","","","","","","","","Returns raw bits.","","","","","","","","","","","","","Adds given square to the set.","","","","","","","","Returns true if this bitboard contains given square.","","","","","","","","","","","","","","Constructs a bitboard representing empty set of squares.","","","","","","Adds given square to the set.","","Prints board representation in FEN format.","Dumps the board in a simple format (‘.’ for empty …","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Constructs Bitboard from pre-calculated bits.","","Constructs a bitboard representing the universal set, it …","","","","","","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","An efficient way to iterate over the set squares.","","","","","","","Returns complement set of Self, i.e. flipping the set …","","","","","","","Shifts the bits to the left and ignores overflow.","Shifts the bits to the right and ignores overflow.","","Relative component, i.e. Result = LHS \\\\ RHS.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Track the ability to castle each side (kingside is often …","","","","","","","","","","Directions on the board from a perspective of White player.","An iterator over the variants of Direction","Also known as South.","","","","","","","","","","","","","","","","","","","","Represents a column (vertical row) of the chessboard. In …","An iterator over the variants of File","","","","","","","","","","","","","","","","","","","","","","","","Size of MoveList
and an upper bound of moves in a chess …","Represents any kind of a legal chess move. A move is the …","Moves are stored on stack to avoid memory allocations. …","","","","Represents a specific piece owned by a player.","Standard chess pieces.","A standard game of chess is played between two players: …","A pawn can be promoted to a queen, rook, bishop or a …","","","Represents a horizontal row of the chessboard. In chess …","An iterator over the variants of Rank","","","","","Board squares: from left to right, from bottom to the top (…","An iterator over the variants of Square","","","Also known as North.","","","","","Get a flags value with all known bits set.","","","","","","The bitwise and (&
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","The bitwise or (|
) of the bits in two flags values.","","Get the underlying bits value.","The bitwise exclusive-or (^
) of the bits in two flags …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The bitwise negation (!
) of the bits in a flags value, …","Whether all set bits in a source flags value are also set …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The intersection of a source flags value with the …","","","","","","","","","","","","","","","Get a flags value with all bits unset.","","","","","","","","The bitwise or (|
) of the bits in each flags value.","Returns file (column) on which the square is located.","","Serializes a move in UCI format.","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Convert from a bits value.","","Convert from a bits value exactly.","Convert from a bits value, unsetting any unknown bits.","The bitwise or (|
) of the bits in each flags value.","Get a flags value with the bits of a flag with the given …","","","","","","","","","","","","","","","","","","","","","","","","","The bitwise or (|
) of the bits in two flags values.","The bitwise and (&
) of the bits in two flags values.","Whether any set bits in a source flags value are also set …","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","Calls U::from(self)
.","","","","","","Whether all known bits in this flags value are set.","Whether all bits in this flags value are unset.","","","","Yield a set of contained flags values.","","Yield a set of contained named flags values.","","","","","","","","","","","Returns a pre-calculated bitboard mask with 1s set for …","Returns a pre-calculated bitboard mask with 1s set for …","","","Connects file (column) and rank (row) to form a full …","","","","","","","","","The bitwise negation (!
) of the bits in a flags value, …","","","","","“Flips” the color.","","","","","","","","","","","","","","","Returns rank (row) on which the square is located.","The intersection of a source flags value with the …","Call insert
when value
is true
or remove
when value
is …","","","","","","The intersection of a source flags value with the …","The intersection of a source flags value with the …","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","","","","","","","","","","","","","","","","","","The bitwise exclusive-or (^
) of the bits in two flags …","","","","","","Creates a square given its position on the board.","","","","","","","","","","","","","Parses CastleRights
for both players from the FEN format. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The bitwise or (|
) of the bits in two flags values.","","State of the chess game: board, half-move counters and …","","","","","","","","","","","","","Returns a string representation of the position in FEN …","Prints board in Forsyth-Edwards Notation.","","Returns the argument unchanged.","Parses board from Forsyth-Edwards Notation and checks its …","","","","","","Calculates a list of legal moves (i.e. the moves that do …","","","Halfmove Clock keeps track of the number of (half-)moves …","","","Calls U::from(self)
.","","","","","Perft (performance testing) is a technique for checking …","","","Creates the starting position of the standard chess.","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,5,5,5,5,5,5,5,5,5,5,0,0,5,0,0,5,0,0,0,0,5,5,5,5,5,0,0,0,0,0,0,9,3,9,15,9,3,3,9,3,3,3,20,3,3,15,20,3,9,15,20,3,9,15,3,9,3,9,15,3,9,15,3,3,20,3,9,15,20,3,9,15,20,3,9,15,3,9,15,3,9,15,3,3,15,15,20,3,3,9,15,3,3,3,3,20,3,9,15,20,3,9,15,20,3,3,9,9,20,9,9,20,3,20,9,15,9,9,3,3,3,15,3,3,3,9,15,15,20,3,9,15,20,3,3,9,15,20,3,9,15,15,30,2,2,2,2,2,2,2,2,25,30,2,2,2,2,2,2,2,2,25,25,25,0,0,13,33,8,30,2,2,2,2,2,2,2,2,0,30,2,2,2,2,2,2,2,2,0,0,22,30,2,2,2,2,2,2,2,2,26,30,2,2,2,2,2,2,2,2,0,0,26,26,30,2,2,2,2,2,2,2,2,30,2,2,2,2,2,2,2,2,13,13,33,0,0,0,25,26,13,0,0,0,0,13,33,0,0,13,33,26,26,0,0,26,26,22,25,25,25,8,25,29,31,32,34,26,25,25,25,25,25,25,25,25,16,28,29,2,30,31,26,32,8,13,25,33,22,34,16,28,29,2,30,31,26,32,8,13,25,33,22,34,28,29,2,30,31,26,32,8,13,25,33,22,34,28,29,2,30,31,26,32,8,13,25,33,22,34,2,30,26,25,25,16,28,29,2,30,31,26,32,8,13,25,33,22,34,16,28,29,2,30,31,26,32,8,13,25,33,22,34,25,16,28,29,2,30,31,26,32,8,13,25,33,22,34,25,2,30,26,8,13,25,33,25,2,16,28,28,29,2,2,30,30,31,26,26,32,8,8,13,13,25,25,25,25,25,25,33,22,34,16,28,29,2,30,31,26,32,8,13,13,25,33,22,34,28,25,25,25,25,25,25,28,29,31,32,34,25,29,31,32,34,16,28,29,2,30,31,26,32,8,13,25,33,22,34,25,25,25,16,28,29,2,30,31,26,32,8,13,25,33,22,34,29,31,32,25,34,25,25,2,30,26,25,22,25,16,29,31,32,34,45,29,31,32,34,30,26,25,28,2,29,31,32,34,29,31,32,34,25,29,31,32,34,8,22,16,29,31,32,34,2,30,26,13,33,26,28,8,2,25,25,2,29,31,32,34,25,25,25,28,28,29,2,30,31,26,32,8,13,25,33,22,34,16,28,2,30,26,8,13,25,25,16,16,28,29,2,2,2,30,30,30,31,26,26,26,32,8,8,13,25,25,33,22,34,16,28,29,2,30,31,26,32,8,13,25,33,22,34,16,28,29,2,30,31,26,32,8,13,25,33,22,34,25,45,0,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,0,0,0,0,39,0,0,39,39,39,39,39,39,39,39,0,39,39,39,39,39,39,39,39,39,39,39,0,0,0,0,0,0],"f":"````{{}b}``````````````````````````````{{df}f}{{dd}f}{ce{}{}}0`{hc{}}0{hb}{{jl}n}{cc{}}{{}h}5{df}0{{A`Abdff}j}{{dA`}f}{{AdAd}Ad}`;:;:`{c{{Af{e}}}{}{}}0{cAh{}}```````{Abf}{fd}{{Abd}{{Al{Aj}}}}{{And}{{Al{B`}}}}`{{ff}c{}}{{ff}b}{{AbAj}f}21{fAd}``3`{ce{}{}}0000000{{fd}b}{{Abd}b}{ff}{AbAb}{AnAn}{{ce}b{}{}}00{{fd}Bb}{fBd}{hc{}}0000000{hb}000{{}f}{{}Ab}{{}An}{{ff}Bb}{{AbAb}Bb}{{AnAn}Bb}?{{fl}n}{{Anl}n}0{cc{}}0{df}11{Adf}{{{Bf{d}}}f};{fBb}{{}h}000{ce{}{}}00002{fBh}``{Bhh}??{Bh{{Al{c}}}{}}{fc{}}{c{{Bj{e}}}{}{}}`{{AnA`}Ab}``{{fBl}f}{{fBd}c{}}0{{}An}{{ff}c{}}{{ff}b};;;{cBn{}}{c{{Af{e}}}{}{}}00000{f{{C`{d}}}}11{cAh{}}000`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````{{}Cb}````{A`Cd}{{CbCb}Cb}{{CbCb}b}10{CbCf}021{ce{}{}}000000000000000000000000000{ChCh}{CjCj}{dd}{ClCl}{CnCn}{CdCd}{D`D`}{A`A`}{AjAj}{CbCb}{DbDb}{BlBl}{DdDd}{{ce}b{}{}}000000000000{{dd}Df}{{ClCl}Df}{{CdCd}Df}7{{CbCb}Bb}{hc{}}000000000000000000000000000{{CbCb}Cb}{hb}0000000000000{{}Cb}{{dd}Bb}{{ClCl}Bb}{{CdCd}Bb}{{A`A`}Bb}{{AjAj}Bb}9{{DbDb}Bb}{{Cbc}b{{Dj{}{{Dh{Cb}}}}}}{dCl}{{B`l}n}{{Chl}n}0{{Cjl}n}{{dl}n}0{{Cll}n}0{{Cnl}n}{{Cdl}n}0{{D`l}n}{{A`l}n}0{{Ajl}n}0{{Cbl}n}00000{{Dbl}n}{{Bll}n}{{Ddl}n}{cc{}}000000000{DbAj}1111`{Cf{{Al{Cb}}}}{CfCb}00{cCb{{Dj{}{{Dh{Cb}}}}}}{Dl{{Al{Cb}}}}{DnCh}{{Cjh}{{Al{d}}}}{{Cnh}{{Al{Cl}}}}{{D`h}{{Al{Cd}}}}{{Ddh}{{Al{Bl}}}}{{Cbc}bE`}````{{}h}0000000000000{{CbCb}b}{{CbCb}Cb}{{CbCb}Bb}{ce{}{}}0000000000000000{Cbc{}}1{CbBb}0{{}Cj}{{}Cn}{{}D`}{Cb{{Eb{Cb}}}}{{}Dd}{Cb{{Ed{Cb}}}}`{Cjh}{Cnh}{D`h}{Ddh}`````{Clf}{Cdf}{A`Cb}{{dd{Al{Db}}}Ch}{{ClCd}d}{Cj{{Al{c}}}{}}{Cn{{Al{c}}}{}}{D`{{Al{c}}}{}}{Dd{{Al{c}}}{}}3210{CbCb}{{Cjh}{{Al{c}}}{}}{{Cnh}{{Al{c}}}{}}{{D`h}{{Al{c}}}{}}{{Ddh}{{Al{c}}}{}}{A`A`}{BlBl}`{c{{Bj{e}}}{}{}}000{{dd}{{Al{Df}}}}{{ClCl}{{Al{Df}}}}{{CdCd}{{Al{Df}}}}{{AjAj}{{Al{Df}}}}{{DbDb}{{Al{Df}}}}{A`Cd}`{A`Bl}{dCd}{{CbCb}b}{{CbCbBb}b}{{dBl}{{Al{d}}}}{Cj{{Ef{h{Al{h}}}}}}{Cn{{Ef{h{Al{h}}}}}}{D`{{Ef{h{Al{h}}}}}}{Dd{{Ef{h{Al{h}}}}}}{{CbCb}Cb}70`{ce{}{}}000000000000{cBn{}}00000009{Eh{{C`{B`}}}}{c{{Af{e}}}{}{}}00{Dl{{C`{d}}}}{Cf{{C`{d}}}}2{Cf{{C`{Cl}}}}3{Eh{{C`{Cl}}}}44{Cf{{C`{Cd}}}}{Eh{{C`{Cd}}}}66{Dl{{C`{A`}}}}7{Dl{{C`{Cb}}}}888888888888888888{cAh{}}0000000000000=``{Dnj}`==`{DnDn}{{ce}b{}{}}{hc{}}0{hb}{{}Dn}`{DnBn}{{Dnl}n}0{cc{}}{Dl{{C`{Dn}}}}`{{fffffdEj}b}{{A`fCbffEj}b}{{dfEj}b}{{ffffEj}b}{DnEj}{{fA`A`Abfffffd{Al{d}}fEj}b}5`{DnBb}{{}h}{ce{}{}}2{{DnCh}b}{{DnA`}f}{Dnf}{{DnCf}Ad}{{DnA`}Ab}`{{}Dn}{DnA`}7{cBn{}}{Dl{{C`{Dn}}}}{c{{Af{e}}}{}{}}0{cAh{}}4{Dn{{C`{b}}}}```{{}b}{DlBn}","c":[],"p":[[1,"unit"],[6,"Square",179],[5,"Bitboard",62],[1,"usize"],[5,"AttackInfo",10],[5,"Formatter",708],[8,"Result",708],[6,"Player",179],[5,"Pieces",62],[1,"u64"],[6,"Result",709],[5,"TypeId",710],[6,"PieceKind",179],[6,"Option",711],[5,"Board",62],[5,"Piece",179],[1,"bool"],[1,"u32"],[1,"slice"],[5,"BitboardIterator",62],[5,"IterBridge",712],[6,"Direction",179],[5,"String",713],[8,"Result",714],[5,"CastleRights",179],[6,"Rank",179],[1,"u8"],[5,"Move",179],[5,"SquareIter",179],[6,"File",179],[5,"FileIter",179],[5,"RankIter",179],[6,"Promotion",179],[5,"DirectionIter",179],[6,"Ordering",715],[17,"Item"],[10,"IntoIterator",716],[1,"str"],[5,"Position",656],[10,"Hasher",717],[5,"Iter",718],[5,"IterNames",718],[1,"tuple"],[1,"char"],[8,"MoveList",179]],"b":[[121,"impl-Display-for-Board"],[122,"impl-Debug-for-Board"],[309,"impl-Flags-for-CastleRights"],[310,"impl-CastleRights"],[426,"impl-Display-for-Move"],[427,"impl-Debug-for-Move"],[429,"impl-Display-for-Square"],[430,"impl-Debug-for-Square"],[431,"impl-Debug-for-File"],[432,"impl-Display-for-File"],[434,"impl-Display-for-Rank"],[435,"impl-Debug-for-Rank"],[437,"impl-Display-for-Player"],[438,"impl-Debug-for-Player"],[439,"impl-Display-for-PieceKind"],[440,"impl-Debug-for-PieceKind"],[441,"impl-Binary-for-CastleRights"],[442,"impl-LowerHex-for-CastleRights"],[443,"impl-Display-for-CastleRights"],[444,"impl-UpperHex-for-CastleRights"],[445,"impl-Debug-for-CastleRights"],[446,"impl-Octal-for-CastleRights"],[467,"impl-Flags-for-CastleRights"],[468,"impl-CastleRights"],[607,"impl-TryFrom%3C%26str%3E-for-Square"],[608,"impl-TryFrom%3Cu8%3E-for-Square"],[610,"impl-TryFrom%3Cu8%3E-for-File"],[612,"impl-TryFrom%3Cchar%3E-for-File"],[615,"impl-TryFrom%3Cu8%3E-for-Rank"],[616,"impl-TryFrom%3Cchar%3E-for-Rank"],[670,"impl-Display-for-Position"],[671,"impl-Debug-for-Position"]]}]\
+]'));
+if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
+else if (window.initSearch) window.initSearch(searchIndex);
diff --git a/docs/settings.html b/docs/settings.html
new file mode 100644
index 00000000..0d2f6334
--- /dev/null
+++ b/docs/settings.html
@@ -0,0 +1,2 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +
//! Mappings of occupied squares to the attacked squares for each piece. The
+//! mappings are pre-calculated where possible to provide an efficient way of
+//! generating moves.
+// TODO: This code is probably by far the less appealing in the project.
+// Refactor it and make it nicer.
+
+use crate::chess::bitboard::{Bitboard, Pieces};
+use crate::chess::core::{Player, Square, BOARD_SIZE};
+
+pub(super) fn king_attacks(from: Square) -> Bitboard {
+ KING_ATTACKS[from as usize]
+}
+
+pub(super) fn queen_attacks(from: Square, occupancy: Bitboard) -> Bitboard {
+ bishop_attacks(from, occupancy) | rook_attacks(from, occupancy)
+}
+
+pub(super) fn rook_attacks(from: Square, occupancy: Bitboard) -> Bitboard {
+ ROOK_ATTACKS[ROOK_ATTACK_OFFSETS[from as usize]
+ + pext(occupancy.bits(), ROOK_RELEVANT_OCCUPANCIES[from as usize]) as usize]
+}
+
+pub(super) fn bishop_attacks(from: Square, occupancy: Bitboard) -> Bitboard {
+ BISHOP_ATTACKS[BISHOP_ATTACK_OFFSETS[from as usize]
+ + pext(occupancy.bits(), BISHOP_RELEVANT_OCCUPANCIES[from as usize]) as usize]
+}
+
+pub(super) fn knight_attacks(square: Square) -> Bitboard {
+ KNIGHT_ATTACKS[square as usize]
+}
+
+pub(super) fn pawn_attacks(square: Square, player: Player) -> Bitboard {
+ match player {
+ Player::White => WHITE_PAWN_ATTACKS[square as usize],
+ Player::Black => BLACK_PAWN_ATTACKS[square as usize],
+ }
+}
+
+pub(super) fn ray(from: Square, to: Square) -> Bitboard {
+ RAYS[(from as usize) * (BOARD_SIZE as usize) + to as usize]
+}
+
+pub(super) fn bishop_ray(from: Square, to: Square) -> Bitboard {
+ BISHOP_RAYS[(from as usize) * (BOARD_SIZE as usize) + to as usize]
+}
+
+fn rook_ray(from: Square, to: Square) -> Bitboard {
+ ROOK_RAYS[(from as usize) * (BOARD_SIZE as usize) + to as usize]
+}
+
+// TODO: Document.
+fn pext(a: u64, mask: u64) -> u64 {
+ if cfg!(target_feature = "bmi2") {
+ unsafe { core::arch::x86_64::_pext_u64(a, mask) }
+ } else {
+ let mut result = 0u64;
+ let mut mask = mask;
+ let mut scanning_bit = 1u64;
+ while mask != 0 {
+ let ls1b = 1u64 << mask.trailing_zeros();
+ if (a & ls1b) != 0 {
+ result |= scanning_bit;
+ }
+ mask ^= ls1b;
+ scanning_bit <<= 1;
+ }
+ result
+ }
+}
+
+#[derive(Debug)]
+pub(super) struct AttackInfo {
+ pub(super) attacks: Bitboard,
+ pub(super) checkers: Bitboard,
+ pub(super) pins: Bitboard,
+ // TODO: Get rid of the XRays.
+ pub(super) xrays: Bitboard,
+ pub(super) safe_king_squares: Bitboard,
+}
+
+impl AttackInfo {
+ // TODO: Handle each piece separately.
+ pub(super) fn new(
+ they: Player,
+ their: &Pieces,
+ king: Square,
+ our_occupancy: Bitboard,
+ occupancy: Bitboard,
+ ) -> Self {
+ let mut result = Self {
+ attacks: Bitboard::empty(),
+ checkers: Bitboard::empty(),
+ pins: Bitboard::empty(),
+ xrays: Bitboard::empty(),
+ safe_king_squares: Bitboard::empty(),
+ };
+ result.safe_king_squares = !our_occupancy & king_attacks(king);
+ let occupancy_without_king = occupancy - Bitboard::from(king);
+ // King.
+ let their_king = their.king.as_square();
+ result.attacks |= king_attacks(their_king);
+ // Knights.
+ for knight in their.knights.iter() {
+ let targets = knight_attacks(knight);
+ result.attacks |= targets;
+ if targets.contains(king) {
+ result.checkers.extend(knight);
+ }
+ }
+ // Pawns.
+ for pawn in their.pawns.iter() {
+ let targets = pawn_attacks(pawn, they);
+ result.attacks |= targets;
+ if targets.contains(king) {
+ result.checkers.extend(pawn);
+ }
+ }
+ // Queens.
+ // TODO: Sliders repeat each other. Pull this into a function.
+ for queen in their.queens.iter() {
+ let targets = queen_attacks(queen, occupancy);
+ result.attacks |= targets;
+ if targets.contains(king) {
+ result.checkers.extend(queen);
+ result.safe_king_squares -= queen_attacks(queen, occupancy_without_king);
+ // An attack can be either a check or a (potential) pin, not
+ // both.
+ continue;
+ }
+ let attack_ray = ray(queen, king);
+ let blocker = (attack_ray & occupancy) - Bitboard::from(queen);
+ if blocker.count() == 1 {
+ if (blocker & our_occupancy).has_any() {
+ result.pins |= blocker;
+ } else {
+ result.xrays |= blocker;
+ }
+ }
+ }
+ for bishop in their.bishops.iter() {
+ let targets = bishop_attacks(bishop, occupancy);
+ result.attacks |= targets;
+ if targets.contains(king) {
+ result.checkers.extend(bishop);
+ result.safe_king_squares -= bishop_attacks(bishop, occupancy_without_king);
+ // An attack can be either a check or a (potential) pin, not
+ // both.
+ continue;
+ }
+ let attack_ray = bishop_ray(bishop, king);
+ let blocker = (attack_ray & occupancy) - Bitboard::from(bishop);
+ if blocker.count() == 1 {
+ if (blocker & our_occupancy).has_any() {
+ result.pins |= blocker;
+ } else {
+ result.xrays |= blocker;
+ }
+ }
+ }
+ for rook in their.rooks.iter() {
+ let targets = rook_attacks(rook, occupancy);
+ result.attacks |= targets;
+ if targets.contains(king) {
+ result.checkers.extend(rook);
+ result.safe_king_squares -= rook_attacks(rook, occupancy_without_king);
+ // An attack can be either a check or a (potential) pin, not
+ // both.
+ continue;
+ }
+ let attack_ray = rook_ray(rook, king);
+ let blocker = (attack_ray & occupancy) - Bitboard::from(rook);
+ if blocker.count() == 1 {
+ if (blocker & our_occupancy).has_any() {
+ result.pins |= blocker;
+ } else {
+ result.xrays |= blocker;
+ }
+ }
+ }
+ result.safe_king_squares -= result.attacks;
+ result
+ }
+}
+
+// Generated in build.rs.
+// TODO: Document PEXT bitboards.
+const BISHOP_ATTACKS_COUNT: usize = 5248;
+const BISHOP_ATTACKS: [Bitboard; BISHOP_ATTACKS_COUNT] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/bishop_attacks.rs"
+));
+const BISHOP_ATTACK_OFFSETS: [usize; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/bishop_attack_offsets.rs"
+));
+const BISHOP_RELEVANT_OCCUPANCIES: [u64; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/bishop_relevant_occupancies.rs"
+));
+
+const ROOK_ATTACKS_COUNT: usize = 102_400;
+const ROOK_ATTACKS: [Bitboard; ROOK_ATTACKS_COUNT] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/rook_attacks.rs"
+));
+const ROOK_RELEVANT_OCCUPANCIES: [u64; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/rook_relevant_occupancies.rs"
+));
+const ROOK_ATTACK_OFFSETS: [usize; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/rook_attack_offsets.rs"
+));
+
+const RAYS: [Bitboard; BOARD_SIZE as usize * BOARD_SIZE as usize] =
+ include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/rays.rs"));
+const BISHOP_RAYS: [Bitboard; BOARD_SIZE as usize * BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/bishop_rays.rs"
+));
+const ROOK_RAYS: [Bitboard; BOARD_SIZE as usize * BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/rook_rays.rs"
+));
+
+const KNIGHT_ATTACKS: [Bitboard; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/knight_attacks.rs"
+));
+const KING_ATTACKS: [Bitboard; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/king_attacks.rs"
+));
+const WHITE_PAWN_ATTACKS: [Bitboard; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/white_pawn_attacks.rs"
+));
+const BLACK_PAWN_ATTACKS: [Bitboard; BOARD_SIZE as usize] = include!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/generated/black_pawn_attacks.rs"
+));
+
+// TODO: Abstract it out and support Fischer Random Chess.
+pub(super) const WHITE_SHORT_CASTLE_KING_WALK: Bitboard =
+ Bitboard::from_bits(0x0000_0000_0000_0060);
+pub(super) const WHITE_SHORT_CASTLE_ROOK_WALK: Bitboard =
+ Bitboard::from_bits(0x0000_0000_0000_0060);
+pub(super) const WHITE_LONG_CASTLE_KING_WALK: Bitboard = Bitboard::from_bits(0x0000_0000_0000_000C);
+pub(super) const WHITE_LONG_CASTLE_ROOK_WALK: Bitboard = Bitboard::from_bits(0x0000_0000_0000_000E);
+pub(super) const BLACK_SHORT_CASTLE_KING_WALK: Bitboard =
+ Bitboard::from_bits(0x6000_0000_0000_0000);
+pub(super) const BLACK_SHORT_CASTLE_ROOK_WALK: Bitboard =
+ Bitboard::from_bits(0x6000_0000_0000_0000);
+pub(super) const BLACK_LONG_CASTLE_KING_WALK: Bitboard = Bitboard::from_bits(0x0C00_0000_0000_0000);
+pub(super) const BLACK_LONG_CASTLE_ROOK_WALK: Bitboard = Bitboard::from_bits(0x0E00_0000_0000_0000);
+
+#[cfg(test)]
+mod test {
+ use pretty_assertions::assert_eq;
+ use strum::IntoEnumIterator;
+
+ use super::*;
+ use crate::chess::core::Rank;
+ use crate::chess::position::Position;
+
+ #[test]
+ fn sliders() {
+ let occupancy = Bitboard::from_squares(&[
+ Square::F4,
+ Square::C4,
+ Square::A4,
+ Square::B1,
+ Square::D5,
+ Square::G5,
+ Square::G6,
+ Square::E8,
+ Square::E2,
+ ]);
+ assert_eq!(
+ format!("{:?}", occupancy),
+ ". . . . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . 1 . . 1 .\n\
+ 1 . 1 . . 1 . .\n\
+ . . . . . . . .\n\
+ . . . . 1 . . .\n\
+ . 1 . . . . . ."
+ );
+ assert_eq!(
+ format!(
+ "{:?}",
+ Bitboard::from_bits(BISHOP_RELEVANT_OCCUPANCIES[Square::E4 as usize])
+ ),
+ ". . . . . . . .\n\
+ . 1 . . . . . .\n\
+ . . 1 . . . 1 .\n\
+ . . . 1 . 1 . .\n\
+ . . . . . . . .\n\
+ . . . 1 . 1 . .\n\
+ . . 1 . . . 1 .\n\
+ . . . . . . . ."
+ );
+ let attacks = bishop_attacks(Square::E4, occupancy);
+ println!("{:064b}", attacks.bits());
+ assert_eq!(
+ format!("{:?}", attacks),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . 1 . 1 . .\n\
+ . . . . . . . .\n\
+ . . . 1 . 1 . .\n\
+ . . 1 . . . 1 .\n\
+ . 1 . . . . . 1"
+ );
+ assert_eq!(
+ format!(
+ "{:?}",
+ Bitboard::from_bits(ROOK_RELEVANT_OCCUPANCIES[Square::E4 as usize])
+ ),
+ ". . . . . . . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . 1 1 1 . 1 1 .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . . . . ."
+ );
+ let attacks = rook_attacks(Square::E4, occupancy);
+ println!("{:064b}", attacks.bits());
+ assert_eq!(
+ format!("{:?}", attacks),
+ ". . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . 1 1 . 1 . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn king() {
+ assert_eq!(
+ format!("{:?}", king_attacks(Square::A1)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ 1 1 . . . . . .\n\
+ . 1 . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", king_attacks(Square::H3)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 1\n\
+ . . . . . . 1 .\n\
+ . . . . . . 1 1\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", king_attacks(Square::D4)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . 1 1 1 . . .\n\
+ . . 1 . 1 . . .\n\
+ . . 1 1 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", king_attacks(Square::F8)),
+ ". . . . 1 . 1 .\n\
+ . . . . 1 1 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn knight() {
+ assert_eq!(
+ format!("{:?}", knight_attacks(Square::A1)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . 1 . . . . . .\n\
+ . . 1 . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", knight_attacks(Square::B1)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ 1 . 1 . . . . .\n\
+ . . . 1 . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", knight_attacks(Square::H3)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . . . 1 . .\n\
+ . . . . . . . .\n\
+ . . . . . 1 . .\n\
+ . . . . . . 1 ."
+ );
+ assert_eq!(
+ format!("{:?}", knight_attacks(Square::D4)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . 1 . 1 . . .\n\
+ . 1 . . . 1 . .\n\
+ . . . . . . . .\n\
+ . 1 . . . 1 . .\n\
+ . . 1 . 1 . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", knight_attacks(Square::F8)),
+ ". . . . . . . .\n\
+ . . . 1 . . . 1\n\
+ . . . . 1 . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn pawn() {
+ // Pawns can not be on the back ranks, hence the attack maps are empty.
+ for square in Rank::One.mask().iter().chain(Rank::Eight.mask().iter()) {
+ assert!(pawn_attacks(square, Player::White).is_empty());
+ assert!(pawn_attacks(square, Player::Black).is_empty());
+ }
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::A2, Player::White)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . 1 . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::A2, Player::Black)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . 1 . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::D4, Player::White)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . 1 . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::D4, Player::Black)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . 1 . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::H5, Player::White)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", pawn_attacks(Square::H5, Player::Black)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn rays() {
+ // Rays with source == destination don't exist.
+ for square in Square::iter() {
+ assert!(ray(square, square).is_empty());
+ }
+ // Rays don't exist for squares not on the same diagonal or vertical.
+ assert!(ray(Square::A1, Square::B3).is_empty());
+ assert!(ray(Square::A1, Square::H7).is_empty());
+ assert!(ray(Square::B2, Square::H5).is_empty());
+ assert!(ray(Square::F2, Square::H8).is_empty());
+ assert_eq!(
+ format!("{:?}", ray(Square::B3, Square::F7)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . 1 . . .\n\
+ . . . 1 . . . .\n\
+ . . 1 . . . . .\n\
+ . 1 . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", ray(Square::F7, Square::B3)),
+ ". . . . . . . .\n\
+ . . . . . 1 . .\n\
+ . . . . 1 . . .\n\
+ . . . 1 . . . .\n\
+ . . 1 . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", ray(Square::C8, Square::H8)),
+ ". . 1 1 1 1 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", ray(Square::H1, Square::H8)),
+ ". . . . . . . .\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1\n\
+ . . . . . . . 1"
+ );
+ assert_eq!(
+ format!("{:?}", ray(Square::E4, Square::B4)),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . 1 1 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn basic_attack_info() {
+ let position = Position::try_from("3kn3/3p4/8/6B1/8/6K1/3R4/8 b - - 0 1").unwrap();
+ let attacks = position.attack_info();
+ assert_eq!(
+ format!("{:?}", attacks.attacks),
+ ". . . 1 . . . .\n\
+ . . . 1 1 . . .\n\
+ . . . 1 . 1 . 1\n\
+ . . . 1 . . . .\n\
+ . . . 1 . 1 1 1\n\
+ . . . 1 1 1 . 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ . . . 1 . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", attacks.checkers),
+ "\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", attacks.pins),
+ ". . . . . . . .\n\
+ . . . 1 . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert!(attacks.xrays.is_empty());
+ }
+
+ #[test]
+ fn xrays() {
+ let position = Position::try_from("b6k/8/8/3p4/8/8/8/7K w - - 0 1").unwrap();
+ let attacks = position.attack_info();
+ assert_eq!(
+ format!("{:?}", attacks.attacks),
+ ". . . . . . 1 .\n\
+ . 1 . . . . 1 1\n\
+ . . 1 . . . . .\n\
+ . . . 1 . . . .\n\
+ . . 1 . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert!(attacks.checkers.is_empty());
+ assert!(attacks.pins.is_empty());
+ assert_eq!(
+ format!("{:?}", attacks.xrays),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . 1 . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn rich_attack_info() {
+ let position =
+ Position::try_from("1k3q2/8/8/4PP2/q4K2/3nRBR1/3b1Nr1/5r2 w - - 0 1").unwrap();
+ let attacks = position.attack_info();
+ assert_eq!(
+ format!("{:?}", attacks.attacks),
+ "1 1 1 1 1 . 1 1\n\
+ 1 1 1 1 1 1 1 .\n\
+ 1 . 1 1 . 1 . 1\n\
+ 1 1 1 . 1 1 . .\n\
+ . 1 1 1 1 1 . .\n\
+ 1 1 1 . 1 . 1 .\n\
+ 1 1 1 . . 1 . 1\n\
+ 1 1 1 1 1 . 1 1"
+ );
+ assert_eq!(
+ format!("{:?}", attacks.checkers),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ 1 . . . . . . .\n\
+ . . . 1 . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", attacks.pins),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . 1 . .\n\
+ . . . . . . . .\n\
+ . . . . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert!(attacks.xrays.is_empty());
+ }
+
+ #[test]
+ fn complicated_attack_info() {
+ let position =
+ Position::try_from("2r3r1/3p3k/1p3pp1/1B5P/5P2/2P1pqP1/PP4KP/3R4 w - - 0 34").unwrap();
+ let attacks = position.attack_info();
+ assert_eq!(
+ format!("{:?}", attacks.checkers),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . 1 . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", attacks.safe_king_squares),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . 1 . 1\n\
+ . . . . . . . .\n\
+ . . . . . . 1 ."
+ );
+ assert!(attacks.xrays.is_empty());
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +
//! [`Bitboard`]-based representation for [`crate::chess::position::Position`].
+//! [Bitboards] utilize the fact that modern processors operate on 64 bit
+//! integers, and the bit operations can be performed simultaneously. This
+//! results in very efficient calculation of possible attack vectors and other
+//! meaningful features that are required to calculate possible moves and
+//! evaluate position. The disadvantage is complexity that comes with bitboard
+//! implementation and inefficiency of some operations like "get piece type on
+//! given square" (efficiently handled by Square-centric board implementations
+//! that can be used together bitboard-based approach to compensate its
+//! shortcomings).
+//!
+//! [Bitboards]: https://www.chessprogramming.org/Bitboards
+
+use std::fmt::Write;
+use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Not, Shl, Shr, Sub, SubAssign};
+use std::{fmt, mem};
+
+use itertools::Itertools;
+use strum::IntoEnumIterator;
+
+use crate::chess::core::{
+ Direction,
+ File,
+ Piece,
+ PieceKind,
+ Player,
+ Rank,
+ Square,
+ BOARD_SIZE,
+ BOARD_WIDTH,
+};
+
+/// Represents a set of squares and provides common operations (e.g. AND, OR,
+/// XOR) over these sets. Each bit corresponds to one of 64 squares of the chess
+/// board.
+///
+/// Mirroring [`Square`] semantics, the least significant
+/// bit corresponds to A1, and the most significant bit - to H8.
+///
+/// Bitboard is a thin wrapper around [u64].
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Bitboard {
+ bits: u64,
+}
+
+impl Bitboard {
+ /// Constructs Bitboard from pre-calculated bits.
+ #[must_use]
+ pub(super) const fn from_bits(bits: u64) -> Self {
+ Self { bits }
+ }
+
+ /// Constructs a bitboard representing empty set of squares.
+ #[must_use]
+ pub const fn empty() -> Self {
+ Self::from_bits(0)
+ }
+
+ /// Constructs a bitboard representing the universal set, it contains all
+ /// squares by setting all bits to binary one.
+ #[must_use]
+ pub const fn full() -> Self {
+ Self::from_bits(u64::MAX)
+ }
+
+ /// Returns raw bits.
+ #[must_use]
+ pub const fn bits(self) -> u64 {
+ self.bits
+ }
+
+ #[must_use]
+ pub(super) fn from_squares(squares: &[Square]) -> Self {
+ let mut result = Self::empty();
+ for square in squares {
+ result |= Self::from(*square);
+ }
+ result
+ }
+
+ /// Adds given square to the set.
+ pub(super) fn extend(&mut self, square: Square) {
+ *self |= Self::from(square)
+ }
+
+ /// Adds given square to the set.
+ pub(super) fn clear(&mut self, square: Square) {
+ *self &= !Self::from(square)
+ }
+
+ /// Returns true if this bitboard contains given square.
+ #[must_use]
+ pub(super) fn contains(self, square: Square) -> bool {
+ (self.bits & (1u64 << square as u8)) != 0
+ }
+
+ #[must_use]
+ pub(super) fn as_square(self) -> Square {
+ debug_assert!(self.bits.count_ones() == 1);
+ unsafe { std::mem::transmute(self.bits.trailing_zeros() as u8) }
+ }
+
+ #[must_use]
+ pub(super) fn count(self) -> u32 {
+ self.bits.count_ones()
+ }
+
+ #[must_use]
+ pub(super) fn is_empty(self) -> bool {
+ self.bits == 0
+ }
+
+ #[must_use]
+ pub(super) fn has_any(self) -> bool {
+ !self.is_empty()
+ }
+
+ #[must_use]
+ pub(super) fn shift(self, direction: Direction) -> Self {
+ match direction {
+ Direction::Up => self << u32::from(BOARD_WIDTH),
+ Direction::Down => self >> u32::from(BOARD_WIDTH),
+ }
+ }
+
+ /// An efficient way to iterate over the set squares.
+ #[must_use]
+ pub(super) fn iter(self) -> BitboardIterator {
+ BitboardIterator { bits: self.bits }
+ }
+}
+
+impl fmt::Debug for Bitboard {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // TODO: This is quite verbose. Refactor or explain what is happening.
+ write!(
+ f,
+ "{}",
+ format!("{:#066b}", self.bits)
+ .chars()
+ .rev()
+ .take(BOARD_SIZE as usize)
+ .chunks(BOARD_WIDTH as usize)
+ .into_iter()
+ .map(|chunk| chunk
+ .map(|ch| match ch {
+ '1' => '1',
+ '0' => '.',
+ _ => unreachable!(),
+ })
+ .join(SQUARE_SEPARATOR))
+ .collect::<Vec<String>>()
+ .iter()
+ .rev()
+ .join(LINE_SEPARATOR)
+ )
+ }
+}
+
+impl BitOr for Bitboard {
+ type Output = Self;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self::from_bits(self.bits.bitor(rhs.bits))
+ }
+}
+
+impl BitOrAssign for Bitboard {
+ fn bitor_assign(&mut self, rhs: Self) {
+ self.bits.bitor_assign(rhs.bits);
+ }
+}
+
+impl BitAnd for Bitboard {
+ type Output = Self;
+
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self::from_bits(self.bits.bitand(rhs.bits))
+ }
+}
+
+impl BitAndAssign for Bitboard {
+ fn bitand_assign(&mut self, rhs: Self) {
+ self.bits.bitand_assign(rhs.bits)
+ }
+}
+
+impl BitXor for Bitboard {
+ type Output = Self;
+
+ fn bitxor(self, rhs: Self) -> Self::Output {
+ Self::from_bits(self.bits.bitxor(rhs.bits))
+ }
+}
+
+impl Sub for Bitboard {
+ type Output = Self;
+
+ /// [Relative component], i.e. Result = LHS \ RHS.
+ ///
+ /// [Relative component]: https://en.wikipedia.org/wiki/Complement_%28set_theory%29#Relative_complement
+ fn sub(self, rhs: Self) -> Self::Output {
+ self & !rhs
+ }
+}
+
+impl SubAssign for Bitboard {
+ fn sub_assign(&mut self, rhs: Self) {
+ self.bitand_assign(!rhs)
+ }
+}
+
+impl Not for Bitboard {
+ type Output = Self;
+
+ /// Returns [complement
+ /// set](https://en.wikipedia.org/wiki/Complement_%28set_theory%29) of Self,
+ /// i.e. flipping the set squares to unset and vice versa.
+ fn not(self) -> Self::Output {
+ Self::from_bits(!self.bits)
+ }
+}
+
+impl Shl<u32> for Bitboard {
+ type Output = Self;
+
+ /// Shifts the bits to the left and ignores overflow.
+ fn shl(self, rhs: u32) -> Self::Output {
+ let (bits, _) = self.bits.overflowing_shl(rhs);
+ Self::from_bits(bits)
+ }
+}
+
+impl Shr<u32> for Bitboard {
+ type Output = Self;
+
+ /// Shifts the bits to the right and ignores overflow.
+ fn shr(self, rhs: u32) -> Self::Output {
+ let (bits, _) = self.bits.overflowing_shr(rhs);
+ Self::from_bits(bits)
+ }
+}
+
+impl From<Square> for Bitboard {
+ fn from(square: Square) -> Self {
+ Self::from_bits(1u64 << square as u8)
+ }
+}
+
+/// Iterates over set squares in a given [Bitboard] from least significant 1
+/// bits (LS1B) to most significant 1 bits (MS1B) through implementing
+/// [bitscan] forward operation.
+///
+/// [bitscan]: https://www.chessprogramming.org/BitScan
+// TODO: Try De Brujin Multiplication and see if it's faster (via benchmarks)
+// than trailing zeros as reported by some developers (even though intuitively
+// trailing zeros should be much faster because it would compile to a processor
+// instruction):
+// https://www.chessprogramming.org/BitScan#De_Bruijn_Multiplication
+pub(super) struct BitboardIterator {
+ bits: u64,
+}
+
+impl Iterator for BitboardIterator {
+ type Item = Square;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.bits == 0 {
+ return None;
+ }
+ // Get the LS1B and consume it from the iterator.
+ let next_index = self.bits.trailing_zeros();
+ self.bits ^= 1 << next_index;
+ // For performance reasons, it's better to convert directly: the
+ // conversion is safe because trailing_zeros() will return a number in
+ // 0..64 range.
+ Some(unsafe { mem::transmute(next_index as u8) })
+ }
+}
+
+impl ExactSizeIterator for BitboardIterator {
+ fn len(&self) -> usize {
+ self.bits.count_ones() as usize
+ }
+}
+
+impl TryInto<Square> for Bitboard {
+ type Error = anyhow::Error;
+
+ fn try_into(self) -> anyhow::Result<Square> {
+ if self.bits.count_ones() != 1 {
+ anyhow::bail!(
+ "bitboard should contain exactly 1 bit, got {}",
+ self.bits.count_ones()
+ );
+ }
+ Ok(unsafe { std::mem::transmute(self.bits.trailing_zeros() as u8) })
+ }
+}
+
+/// Piece-centric representation of all material owned by one player. Uses
+/// [Bitboard] to store a set of squares occupied by each piece. The main user
+/// is [`crate::chess::position::Position`], [Bitboard] is not very useful on
+/// its own.
+#[derive(Clone, PartialEq, Eq)]
+pub(super) struct Pieces {
+ pub(super) king: Bitboard,
+ pub(super) queens: Bitboard,
+ pub(super) rooks: Bitboard,
+ pub(super) bishops: Bitboard,
+ // TODO: Store "all" instead.
+ pub(super) knights: Bitboard,
+ pub(super) pawns: Bitboard,
+}
+
+impl Pieces {
+ pub(super) fn empty() -> Self {
+ Self {
+ king: Bitboard::empty(),
+ queens: Bitboard::empty(),
+ rooks: Bitboard::empty(),
+ bishops: Bitboard::empty(),
+ knights: Bitboard::empty(),
+ pawns: Bitboard::empty(),
+ }
+ }
+
+ pub(super) fn new_white() -> Self {
+ Self {
+ king: Square::E1.into(),
+ queens: Square::D1.into(),
+ rooks: Bitboard::from_squares(&[Square::A1, Square::H1]),
+ bishops: Bitboard::from_squares(&[Square::C1, Square::F1]),
+ knights: Bitboard::from_squares(&[Square::B1, Square::G1]),
+ pawns: Bitboard::from_squares(&[
+ Square::A2,
+ Square::B2,
+ Square::C2,
+ Square::D2,
+ Square::E2,
+ Square::F2,
+ Square::G2,
+ Square::H2,
+ ]),
+ }
+ }
+
+ pub(super) fn new_black() -> Self {
+ // TODO: Implement flip and return new_white().flip() to prevent copying
+ // code.
+ Self {
+ king: Square::E8.into(),
+ queens: Square::D8.into(),
+ rooks: Bitboard::from_squares(&[Square::A8, Square::H8]),
+ bishops: Bitboard::from_squares(&[Square::C8, Square::F8]),
+ knights: Bitboard::from_squares(&[Square::B8, Square::G8]),
+ pawns: Bitboard::from_squares(&[
+ Square::A7,
+ Square::B7,
+ Square::C7,
+ Square::D7,
+ Square::E7,
+ Square::F7,
+ Square::G7,
+ Square::H7,
+ ]),
+ }
+ }
+
+ pub(super) fn all(&self) -> Bitboard {
+ self.king | self.queens | self.rooks | self.bishops | self.knights | self.pawns
+ }
+
+ pub(super) fn bitboard_for(&mut self, piece: PieceKind) -> &mut Bitboard {
+ match piece {
+ PieceKind::King => &mut self.king,
+ PieceKind::Queen => &mut self.queens,
+ PieceKind::Rook => &mut self.rooks,
+ PieceKind::Bishop => &mut self.bishops,
+ PieceKind::Knight => &mut self.knights,
+ PieceKind::Pawn => &mut self.pawns,
+ }
+ }
+
+ // TODO: Maybe completely disallow this? If we have the Square ->
+ // Option<Piece> mapping, this is potentially obsolete.
+ pub(super) fn at(&self, square: Square) -> Option<PieceKind> {
+ if self.all().contains(square) {
+ let mut kind = if self.king.contains(square) {
+ PieceKind::King
+ } else {
+ PieceKind::Pawn
+ };
+ if self.king.contains(square) {
+ kind = PieceKind::King;
+ }
+ if self.queens.contains(square) {
+ kind = PieceKind::Queen;
+ }
+ if self.rooks.contains(square) {
+ kind = PieceKind::Rook;
+ }
+ if self.bishops.contains(square) {
+ kind = PieceKind::Bishop;
+ }
+ if self.knights.contains(square) {
+ kind = PieceKind::Knight;
+ }
+ return Some(kind);
+ }
+ None
+ }
+
+ pub(super) fn clear(&mut self, square: Square) {
+ self.king.clear(square);
+ self.queens.clear(square);
+ self.rooks.clear(square);
+ self.bishops.clear(square);
+ self.knights.clear(square);
+ self.pawns.clear(square);
+ }
+}
+
+/// Piece-centric implementation of the chess board. This is the "back-end" of
+/// the chess engine, an efficient board representation is crucial for
+/// performance. An alternative implementation would be Square-Piece table but
+/// both have different trade-offs and scenarios where they are efficient. It is
+/// likely that the best overall performance can be achieved by keeping both to
+/// complement each other.
+#[derive(Clone, PartialEq, Eq)]
+pub(super) struct Board {
+ pub(super) white_pieces: Pieces,
+ pub(super) black_pieces: Pieces,
+}
+
+impl Board {
+ #[must_use]
+ pub(super) fn starting() -> Self {
+ Self {
+ white_pieces: Pieces::new_white(),
+ black_pieces: Pieces::new_black(),
+ }
+ }
+
+ // Constructs an empty Board to be filled by the board and position builder.
+ #[must_use]
+ pub(super) fn empty() -> Self {
+ Self {
+ white_pieces: Pieces::empty(),
+ black_pieces: Pieces::empty(),
+ }
+ }
+
+ #[must_use]
+ pub(super) fn player_pieces(&self, player: Player) -> &Pieces {
+ match player {
+ Player::White => &self.white_pieces,
+ Player::Black => &self.black_pieces,
+ }
+ }
+
+ // WARNING: This is slow and inefficient for Bitboard-based piece-centric
+ // representation. Use with caution.
+ // TODO: Completely disallow bitboard.at()?
+ #[must_use]
+ pub(super) fn at(&self, square: Square) -> Option<Piece> {
+ if let Some(kind) = self.white_pieces.at(square) {
+ return Some(Piece {
+ owner: Player::White,
+ kind,
+ });
+ }
+ if let Some(kind) = self.black_pieces.at(square) {
+ return Some(Piece {
+ owner: Player::Black,
+ kind,
+ });
+ }
+ None
+ }
+}
+
+impl fmt::Display for Board {
+ /// Prints board representation in FEN format.
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for rank in Rank::iter().rev() {
+ let mut empty_squares = 0i32;
+ for file in File::iter() {
+ let square = Square::new(file, rank);
+ if let Some(piece) = self.at(square) {
+ if empty_squares != 0 {
+ write!(f, "{empty_squares}")?;
+ empty_squares = 0;
+ }
+ write!(f, "{}", piece)?;
+ } else {
+ empty_squares += 1;
+ }
+ }
+ if empty_squares != 0 {
+ write!(f, "{empty_squares}")?;
+ }
+ if rank != Rank::One {
+ const RANK_SEPARATOR: char = '/';
+ write!(f, "{RANK_SEPARATOR}")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl fmt::Debug for Board {
+ /// Dumps the board in a simple format ('.' for empty square, FEN algebraic
+ /// symbol for piece) a-la Stockfish "debug" command in UCI mode.
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for rank in Rank::iter().rev() {
+ for file in File::iter() {
+ match self.at(Square::new(file, rank)) {
+ Some(piece) => write!(f, "{piece}"),
+ None => f.write_char('.'),
+ }?;
+ if file != File::H {
+ write!(f, "{}", SQUARE_SEPARATOR)?;
+ }
+ }
+ if rank != Rank::One {
+ write!(f, "{}", LINE_SEPARATOR)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+const LINE_SEPARATOR: &str = "\n";
+const SQUARE_SEPARATOR: &str = " ";
+
+#[cfg(test)]
+mod test {
+ use pretty_assertions::assert_eq;
+
+ use super::*;
+ use crate::chess::core::{Rank, Square, BOARD_WIDTH};
+
+ #[test]
+ fn basics() {
+ assert_eq!(std::mem::size_of::<Bitboard>(), 8);
+ assert_eq!(Bitboard::full().bits, u64::MAX);
+ assert_eq!(Bitboard::empty().bits, u64::MIN);
+
+ assert_eq!(Bitboard::from(Square::A1).bits, 1);
+ assert_eq!(Bitboard::from(Square::B1).bits, 2);
+ assert_eq!(Bitboard::from(Square::D1).bits, 8);
+ assert_eq!(Bitboard::from(Square::H8).bits, 1u64 << 63);
+
+ assert_eq!(
+ Bitboard::from(Square::D1) | Bitboard::from(Square::B1),
+ Bitboard::from_bits(0b10 | 0b1000)
+ );
+ }
+
+ #[test]
+ fn set_basics() {
+ // Create a starting position.
+ let white = Pieces::new_white();
+ let black = Pieces::new_black();
+
+ // Check that each player has 16 pieces.
+ assert_eq!(white.all().bits.count_ones(), 16);
+ assert_eq!(black.all().bits.count_ones(), 16);
+ // Check that each player has correct number of pieces (previous check
+ // was not enough to confirm there are no overlaps).
+ assert_eq!(white.king.bits.count_ones(), 1);
+ assert_eq!(black.king.bits.count_ones(), 1);
+ assert_eq!(white.queens.bits.count_ones(), 1);
+ assert_eq!(black.queens.bits.count_ones(), 1);
+ assert_eq!(white.rooks.bits.count_ones(), 2);
+ assert_eq!(black.rooks.bits.count_ones(), 2);
+ assert_eq!(white.bishops.bits.count_ones(), 2);
+ assert_eq!(black.bishops.bits.count_ones(), 2);
+ assert_eq!(white.knights.bits.count_ones(), 2);
+ assert_eq!(black.knights.bits.count_ones(), 2);
+ assert_eq!(white.pawns.bits.count_ones(), 8);
+ assert_eq!(black.pawns.bits.count_ones(), 8);
+
+ // Check few positions manually.
+ assert_eq!(white.queens.bits, 1 << 3);
+ assert_eq!(black.queens.bits, 1 << (3 + 8 * 7));
+
+ // Rank masks.
+ assert_eq!(Rank::One.mask() << u32::from(BOARD_WIDTH), Rank::Two.mask());
+ assert_eq!(
+ Rank::Five.mask() >> u32::from(BOARD_WIDTH),
+ Rank::Four.mask()
+ );
+ }
+
+ #[test]
+ fn bitboard_iterator() {
+ let white = Pieces::new_white();
+
+ let mut it = white.king.iter();
+ assert_eq!(it.next(), Some(Square::E1));
+ assert_eq!(it.next(), None);
+
+ let mut it = white.bishops.iter();
+ assert_eq!(it.next(), Some(Square::C1));
+ assert_eq!(it.next(), Some(Square::F1));
+ assert_eq!(it.next(), None);
+
+ // The order is important here: we are iterating from least significant
+ // bits to most significant bits.
+ assert_eq!(
+ white.pawns.iter().collect::<Vec<_>>(),
+ vec![
+ Square::A2,
+ Square::B2,
+ Square::C2,
+ Square::D2,
+ Square::E2,
+ Square::F2,
+ Square::G2,
+ Square::H2,
+ ]
+ );
+ }
+
+ #[test]
+ fn set_ops() {
+ let bitboard = Bitboard::from_squares(&[
+ Square::A1,
+ Square::B1,
+ Square::C1,
+ Square::D1,
+ Square::E1,
+ Square::F1,
+ Square::H1,
+ Square::A2,
+ Square::B2,
+ Square::C2,
+ Square::D2,
+ Square::G2,
+ Square::F2,
+ Square::H2,
+ Square::F3,
+ Square::E4,
+ Square::E5,
+ Square::C6,
+ Square::A7,
+ Square::B7,
+ Square::C7,
+ Square::D7,
+ Square::F7,
+ Square::G7,
+ Square::H7,
+ Square::A8,
+ Square::C8,
+ Square::D8,
+ Square::E8,
+ Square::F8,
+ Square::G8,
+ Square::H8,
+ ]);
+ assert_eq!(
+ format!("{:?}", bitboard),
+ "1 . 1 1 1 1 1 1\n\
+ 1 1 1 1 . 1 1 1\n\
+ . . 1 . . . . .\n\
+ . . . . 1 . . .\n\
+ . . . . 1 . . .\n\
+ . . . . . 1 . .\n\
+ 1 1 1 1 . 1 1 1\n\
+ 1 1 1 1 1 1 . 1"
+ );
+ assert_eq!(
+ format!("{:?}", !bitboard),
+ ". 1 . . . . . .\n\
+ . . . . 1 . . .\n\
+ 1 1 . 1 1 1 1 1\n\
+ 1 1 1 1 . 1 1 1\n\
+ 1 1 1 1 . 1 1 1\n\
+ 1 1 1 1 1 . 1 1\n\
+ . . . . 1 . . .\n\
+ . . . . . . 1 ."
+ );
+ assert_eq!(
+ format!(
+ "{:?}",
+ bitboard - Bitboard::from_squares(&[Square::A1, Square::E4, Square::G8])
+ ),
+ "1 . 1 1 1 1 . 1\n\
+ 1 1 1 1 . 1 1 1\n\
+ . . 1 . . . . .\n\
+ . . . . 1 . . .\n\
+ . . . . . . . .\n\
+ . . . . . 1 . .\n\
+ 1 1 1 1 . 1 1 1\n\
+ . 1 1 1 1 1 . 1"
+ );
+ assert_eq!(!!bitboard, bitboard);
+ assert_eq!(bitboard - !bitboard, bitboard);
+ }
+
+ #[test]
+ // Check the debug output for few bitboards.
+ fn bitboard_dump() {
+ assert_eq!(
+ format!("{:?}", Bitboard::empty()),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", Bitboard::full()),
+ "1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1"
+ );
+ assert_eq!(
+ format!(
+ "{:?}",
+ Bitboard::from(Square::G5) | Bitboard::from(Square::B8)
+ ),
+ ". 1 . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn set_dump() {
+ let white = Pieces::new_white();
+ let black = Pieces::new_black();
+
+ assert_eq!(
+ format!("{:?}", black.all()),
+ "1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", white.all() | black.all()),
+ "1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ 1 1 1 1 1 1 1 1\n\
+ 1 1 1 1 1 1 1 1"
+ );
+
+ assert_eq!(
+ format!("{:?}", white.king),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . 1 . . ."
+ );
+ assert_eq!(
+ format!("{:?}", black.pawns),
+ ". . . . . . . .\n\
+ 1 1 1 1 1 1 1 1\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(
+ format!("{:?}", black.knights),
+ ". 1 . . . . 1 .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ }
+
+ #[test]
+ fn starting_board() {
+ let starting_board = Board::starting();
+ assert_eq!(
+ format!("{:?}", starting_board),
+ "r n b q k b n r\n\
+ p p p p p p p p\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ P P P P P P P P\n\
+ R N B Q K B N R"
+ );
+ assert_eq!(
+ starting_board.white_pieces.all() | starting_board.black_pieces.all(),
+ Rank::One.mask() | Rank::Two.mask() | Rank::Seven.mask() | Rank::Eight.mask()
+ );
+ assert_eq!(
+ !(starting_board.white_pieces.all() | starting_board.black_pieces.all()),
+ Rank::Three.mask() | Rank::Four.mask() | Rank::Five.mask() | Rank::Six.mask()
+ );
+ assert_eq!(
+ starting_board.to_string(),
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
+ );
+ }
+
+ #[test]
+ fn empty_board() {
+ assert_eq!(
+ format!("{:?}", Board::empty()),
+ ". . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . .\n\
+ . . . . . . . ."
+ );
+ assert_eq!(Board::empty().to_string(), "8/8/8/8/8/8/8/8");
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +
//! Chess primitives commonly used within [`crate::chess`].
+
+use std::fmt::{self, Write};
+use std::mem;
+
+use anyhow::bail;
+use itertools::Itertools;
+
+use crate::chess::bitboard::Bitboard;
+use crate::chess::position::Position;
+
+#[allow(missing_docs)]
+pub const BOARD_WIDTH: u8 = 8;
+#[allow(missing_docs)]
+pub const BOARD_SIZE: u8 = BOARD_WIDTH * BOARD_WIDTH;
+
+/// Represents any kind of a legal chess move. A move is the only way to mutate
+/// [`crate::chess::position::Position`] and change the board state. Moves are
+/// not sorted according to their potential "value" by the move generator. The
+/// move representation has one-to-one correspondence with the UCI move
+/// representation. The moves can also be indexed and fed as an input to the
+/// Neural Network evaluators that would be able assess their potential without
+/// evaluating post-states.
+///
+/// For a move to be serialized in Standard Algebraic Notation (SAN), it also
+/// also requires the [`crate::chess::position::Position`] it will be applied
+/// in, because SAN requires additional flags (e.g. indicating
+/// "check"/"checkmate" or moving piece disambiguation).
+// TODO: Implement bijection for a move and a numeric index.
+// TODO: Switch this to an enum representation (regular, en passant, castling).
+#[derive(Copy, Clone, Debug)]
+pub struct Move {
+ pub(super) from: Square,
+ pub(super) to: Square,
+ pub(super) promotion: Option<Promotion>,
+}
+
+impl Move {
+ #[must_use]
+ pub const fn new(from: Square, to: Square, promotion: Option<Promotion>) -> Self {
+ Self {
+ from,
+ to,
+ promotion,
+ }
+ }
+
+ #[must_use]
+ pub fn from_san(_position: &Position) -> Self {
+ todo!()
+ }
+}
+
+impl fmt::Display for Move {
+ /// Serializes a move in [UCI format].
+ ///
+ /// [UCI format]: http://wbec-ridderkerk.nl/html/UCIProtocol.html
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}{}", self.from, self.to)?;
+ if let Some(promotion) = self.promotion {
+ write!(f, "{}", PieceKind::from(promotion))?;
+ }
+ Ok(())
+ }
+}
+
+/// Size of [`MoveList`] and an upper bound of moves in a chess position (which
+/// [seems to be 218](https://www.chessprogramming.org/Chess_Position). 256 provides the best
+/// performance through optimal memory alignment.
+const MAX_MOVES: usize = 256;
+
+/// Moves are stored on stack to avoid memory allocations. This is important for
+/// performance reasons and also prevents unnecessary copying that would occur
+/// if the moves would be stored in `std::Vec` with unknown capacity.
+pub type MoveList = arrayvec::ArrayVec<Move, { MAX_MOVES }>;
+
+/// Board squares: from left to right, from bottom to the top ([Little-Endian Rank-File Mapping]):
+///
+/// ```
+/// use pabi::chess::core::Square;
+///
+/// assert_eq!(Square::A1 as u8, 0);
+/// assert_eq!(Square::E1 as u8, 4);
+/// assert_eq!(Square::H1 as u8, 7);
+/// assert_eq!(Square::A4 as u8, 8 * 3);
+/// assert_eq!(Square::H8 as u8, 63);
+/// ```
+///
+/// Square is a compact representation using only one byte.
+///
+/// ```
+/// use pabi::chess::core::Square;
+/// use std::mem;
+///
+/// assert_eq!(std::mem::size_of::<Square>(), 1);
+/// ```
+///
+/// [Little-Endian Rank-File Mapping]: https://www.chessprogramming.org/Square_Mapping_Considerations#LittleEndianRankFileMapping
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
+#[rustfmt::skip]
+#[allow(missing_docs)]
+pub enum Square {
+ A1 = 0, B1, C1, D1, E1, F1, G1, H1,
+ A2, B2, C2, D2, E2, F2, G2, H2,
+ A3, B3, C3, D3, E3, F3, G3, H3,
+ A4, B4, C4, D4, E4, F4, G4, H4,
+ A5, B5, C5, D5, E5, F5, G5, H5,
+ A6, B6, C6, D6, E6, F6, G6, H6,
+ A7, B7, C7, D7, E7, F7, G7, H7,
+ A8, B8, C8, D8, E8, F8, G8, H8,
+}
+
+impl Square {
+ /// Connects file (column) and rank (row) to form a full square.
+ #[must_use]
+ pub const fn new(file: File, rank: Rank) -> Self {
+ unsafe { mem::transmute(file as u8 + (rank as u8) * BOARD_WIDTH) }
+ }
+
+ /// Returns file (column) on which the square is located.
+ #[must_use]
+ pub const fn file(self) -> File {
+ unsafe { mem::transmute(self as u8 % BOARD_WIDTH) }
+ }
+
+ /// Returns rank (row) on which the square is located.
+ #[must_use]
+ pub const fn rank(self) -> Rank {
+ unsafe { mem::transmute(self as u8 / BOARD_WIDTH) }
+ }
+
+ #[must_use]
+ pub fn shift(self, direction: Direction) -> Option<Self> {
+ let shift: i8 = match direction {
+ Direction::Up => BOARD_WIDTH as i8,
+ Direction::Down => -(BOARD_WIDTH as i8),
+ };
+ // TODO: Should this be TryFrom<i8> instead?
+ let candidate = self as i8 + shift;
+ if candidate < 0 {
+ return None;
+ }
+ match Self::try_from(candidate as u8) {
+ Ok(square) => Some(square),
+ Err(_) => None,
+ }
+ }
+}
+
+impl TryFrom<u8> for Square {
+ type Error = anyhow::Error;
+
+ /// Creates a square given its position on the board.
+ ///
+ /// # Errors
+ ///
+ /// If given square index is outside 0..[`BOARD_SIZE`] range.
+ fn try_from(square_index: u8) -> anyhow::Result<Self> {
+ // Exclusive range patterns are not allowed:
+ // https://github.com/rust-lang/rust/issues/37854
+ const MAX_INDEX: u8 = BOARD_SIZE - 1;
+ match square_index {
+ 0..=MAX_INDEX => Ok(unsafe { mem::transmute(square_index) }),
+ _ => bail!("square index should be in 0..BOARD_SIZE, got {square_index}"),
+ }
+ }
+}
+
+impl TryFrom<&str> for Square {
+ type Error = anyhow::Error;
+
+ fn try_from(square: &str) -> anyhow::Result<Self> {
+ let (file, rank) = match square.chars().collect_tuple() {
+ Some((file, rank)) => (file, rank),
+ None => bail!(
+ "square should be two-char, got {square} with {} chars",
+ square.bytes().len()
+ ),
+ };
+ Ok(Self::new(file.try_into()?, rank.try_into()?))
+ }
+}
+
+impl fmt::Display for Square {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}{}", self.file(), self.rank())
+ }
+}
+
+/// Represents a column (vertical row) of the chessboard. In chess notation, it
+/// is normally represented with a lowercase letter.
+// TODO: Re-export in lib.rs for convenience?
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
+#[allow(missing_docs)]
+pub enum File {
+ A = 0,
+ B = 1,
+ C = 2,
+ D = 3,
+ E = 4,
+ F = 5,
+ G = 6,
+ H = 7,
+}
+
+impl File {
+ /// Returns a pre-calculated bitboard mask with 1s set for squares of the
+ /// given file.
+ pub(super) fn mask(self) -> Bitboard {
+ match self {
+ File::A => Bitboard::from_bits(0x101010101010101),
+ File::B => Bitboard::from_bits(0x202020202020202),
+ File::C => Bitboard::from_bits(0x404040404040404),
+ File::D => Bitboard::from_bits(0x808080808080808),
+ File::E => Bitboard::from_bits(0x1010101010101010),
+ File::F => Bitboard::from_bits(0x2020202020202020),
+ File::G => Bitboard::from_bits(0x4040404040404040),
+ File::H => Bitboard::from_bits(0x8080808080808080),
+ }
+ }
+}
+
+impl fmt::Display for File {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", (b'a' + *self as u8) as char)
+ }
+}
+
+// TODO: Here and in Rank: implement From<u8> and see whether/how much faster it
+// is than the safe checked version.
+impl TryFrom<char> for File {
+ type Error = anyhow::Error;
+
+ fn try_from(file: char) -> anyhow::Result<Self> {
+ match file {
+ 'a'..='h' => Ok(unsafe { mem::transmute(file as u8 - b'a') }),
+ _ => bail!("file should be within 'a'..='h', got '{file}'"),
+ }
+ }
+}
+
+impl TryFrom<u8> for File {
+ type Error = anyhow::Error;
+
+ fn try_from(column: u8) -> anyhow::Result<Self> {
+ match column {
+ 0..=7 => Ok(unsafe { mem::transmute(column) }),
+ _ => bail!("file should be within 0..BOARD_WIDTH, got {column}"),
+ }
+ }
+}
+
+/// Represents a horizontal row of the chessboard. In chess notation, it is
+/// represented with a number. The implementation assumes zero-based values
+/// (i.e. rank 1 would be 0).
+// TODO: Check if implementing iterators manually (instead of using strum) would
+// be faster.
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
+#[allow(missing_docs)]
+pub enum Rank {
+ One = 0,
+ Two = 1,
+ Three = 2,
+ Four = 3,
+ Five = 4,
+ Six = 5,
+ Seven = 6,
+ Eight = 7,
+}
+
+impl Rank {
+ /// Returns a pre-calculated bitboard mask with 1s set for squares of the
+ /// given rank.
+ pub(super) fn mask(self) -> Bitboard {
+ match self {
+ Rank::One => Bitboard::from_bits(0x0000_0000_0000_00FF),
+ Rank::Two => Bitboard::from_bits(0x0000_0000_0000_FF00),
+ Rank::Three => Bitboard::from_bits(0x0000_0000_00FF_0000),
+ Rank::Four => Bitboard::from_bits(0x0000_0000_FF00_0000),
+ Rank::Five => Bitboard::from_bits(0x0000_00FF_0000_0000),
+ Rank::Six => Bitboard::from_bits(0x0000_FF00_0000_0000),
+ Rank::Seven => Bitboard::from_bits(0x00FF_0000_0000_0000),
+ Rank::Eight => Bitboard::from_bits(0xFF00_0000_0000_0000),
+ }
+ }
+
+ pub(super) fn backrank(player: Player) -> Self {
+ match player {
+ Player::White => Self::One,
+ Player::Black => Self::Eight,
+ }
+ }
+
+ pub(super) fn pawns_starting(player: Player) -> Self {
+ match player {
+ Player::White => Self::Two,
+ Player::Black => Self::Seven,
+ }
+ }
+}
+
+impl TryFrom<char> for Rank {
+ type Error = anyhow::Error;
+
+ fn try_from(rank: char) -> anyhow::Result<Self> {
+ match rank {
+ '1'..='8' => Ok(unsafe { mem::transmute(rank as u8 - b'1') }),
+ _ => bail!("rank should be within '1'..='8', got '{rank}'"),
+ }
+ }
+}
+
+impl TryFrom<u8> for Rank {
+ type Error = anyhow::Error;
+
+ fn try_from(row: u8) -> anyhow::Result<Self> {
+ match row {
+ 0..=7 => Ok(unsafe { mem::transmute(row) }),
+ _ => bail!("rank should be within 0..BOARD_WIDTH, got {row}"),
+ }
+ }
+}
+
+impl fmt::Display for Rank {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", *self as u8 + 1)
+ }
+}
+
+/// A standard game of chess is played between two players: White (having the
+/// advantage of the first turn) and Black.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Player {
+ White,
+ Black,
+}
+
+impl Player {
+ /// "Flips" the color.
+ #[must_use]
+ pub fn opponent(self) -> Self {
+ match self {
+ Self::White => Self::Black,
+ Self::Black => Self::White,
+ }
+ }
+
+ pub(super) fn push_direction(self) -> Direction {
+ match self {
+ Self::White => Direction::Up,
+ Self::Black => Direction::Down,
+ }
+ }
+}
+
+impl TryFrom<&str> for Player {
+ type Error = anyhow::Error;
+
+ fn try_from(player: &str) -> anyhow::Result<Self> {
+ match player {
+ "w" => Ok(Self::White),
+ "b" => Ok(Self::Black),
+ _ => bail!("player should be 'w' or 'b', got '{player}'"),
+ }
+ }
+}
+
+impl fmt::Display for Player {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match &self {
+ Player::White => 'w',
+ Player::Black => 'b',
+ }
+ )
+ }
+}
+
+/// Standard [chess pieces].
+///
+/// [chess pieces]: https://en.wikipedia.org/wiki/Chess_piece
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+pub enum PieceKind {
+ King = 1,
+ Queen,
+ Rook,
+ Bishop,
+ Knight,
+ Pawn,
+}
+
+impl From<Promotion> for PieceKind {
+ fn from(promotion: Promotion) -> Self {
+ match promotion {
+ Promotion::Queen => Self::Queen,
+ Promotion::Rook => Self::Rook,
+ Promotion::Bishop => Self::Bishop,
+ Promotion::Knight => Self::Knight,
+ }
+ }
+}
+
+impl fmt::Display for PieceKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_char(match &self {
+ Self::King => 'k',
+ Self::Queen => 'q',
+ Self::Rook => 'r',
+ Self::Bishop => 'b',
+ Self::Knight => 'n',
+ Self::Pawn => 'p',
+ })
+ }
+}
+
+/// Represents a specific piece owned by a player.
+pub struct Piece {
+ #[allow(missing_docs)]
+ pub owner: Player,
+ #[allow(missing_docs)]
+ pub kind: PieceKind,
+}
+
+impl TryFrom<char> for Piece {
+ type Error = anyhow::Error;
+
+ fn try_from(symbol: char) -> anyhow::Result<Self> {
+ match symbol {
+ 'K' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::King,
+ }),
+ 'Q' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::Queen,
+ }),
+ 'R' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::Rook,
+ }),
+ 'B' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::Bishop,
+ }),
+ 'N' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::Knight,
+ }),
+ 'P' => Ok(Self {
+ owner: Player::White,
+ kind: PieceKind::Pawn,
+ }),
+ 'k' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::King,
+ }),
+ 'q' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::Queen,
+ }),
+ 'r' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::Rook,
+ }),
+ 'b' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::Bishop,
+ }),
+ 'n' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::Knight,
+ }),
+ 'p' => Ok(Self {
+ owner: Player::Black,
+ kind: PieceKind::Pawn,
+ }),
+ _ => bail!("piece symbol should be within \"KQRBNPkqrbnp\", got '{symbol}'"),
+ }
+ }
+}
+
+impl fmt::Display for Piece {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_char(match (&self.owner, &self.kind) {
+ // White player: uppercase symbols.
+ (Player::White, PieceKind::King) => 'K',
+ (Player::White, PieceKind::Queen) => 'Q',
+ (Player::White, PieceKind::Rook) => 'R',
+ (Player::White, PieceKind::Bishop) => 'B',
+ (Player::White, PieceKind::Knight) => 'N',
+ (Player::White, PieceKind::Pawn) => 'P',
+ // Black player: lowercase symbols.
+ (Player::Black, PieceKind::King) => 'k',
+ (Player::Black, PieceKind::Queen) => 'q',
+ (Player::Black, PieceKind::Rook) => 'r',
+ (Player::Black, PieceKind::Bishop) => 'b',
+ (Player::Black, PieceKind::Knight) => 'n',
+ (Player::Black, PieceKind::Pawn) => 'p',
+ })
+ }
+}
+
+bitflags::bitflags! {
+ /// Track the ability to [castle] each side (kingside is often referred to
+ /// as O-O or h-side castle, queenside -- O-O-O or a-side castle). When the
+ /// king moves, player loses ability to castle. When the rook moves, player
+ /// loses ability to castle to the side from which the rook moved.
+ ///
+ /// Castling is relatively straightforward in the Standard Chess but is
+ /// often misunderstood in Fischer Random Chess (also known as FRC or
+ /// Chess960). An easy mnemonic is that the king and the rook end up on the
+ /// same files for both Standard and FRC:
+ ///
+ /// - When castling h-side (short), the king ends up on [`File::G`] and the
+ /// rook on [`File::F`]
+ /// - When castling a-side (long), the king ends up on [`File::C`] and the
+ /// rook on [`File::D`]
+ ///
+ /// The full rules are:
+ ///
+ /// - The king and the castling rook must not have previously moved.
+ /// - No square from the king's initial square to its final square may be under
+ /// attack by an enemy piece.
+ /// - All the squares between the king's initial and final squares
+ /// (including the final square), and all the squares between the castling
+ /// rook's initial and final squares (including the final square), must be
+ /// vacant except for the king and castling rook.
+ ///
+ /// [castle]: https://www.chessprogramming.org/Castling
+ // TODO: Update docs for FCR.
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct CastleRights : u8 {
+ #[allow(missing_docs)]
+ const NONE = 0;
+ #[allow(missing_docs)]
+ const WHITE_SHORT = 0b1000;
+ #[allow(missing_docs)]
+ const WHITE_LONG = 0b0100;
+ #[allow(missing_docs)]
+ const WHITE_BOTH = Self::WHITE_SHORT.bits() | Self::WHITE_LONG.bits();
+ #[allow(missing_docs)]
+ const BLACK_SHORT = 0b0010;
+ #[allow(missing_docs)]
+ const BLACK_LONG = 0b0001;
+ #[allow(missing_docs)]
+ const BLACK_BOTH = Self::BLACK_SHORT.bits() | Self::BLACK_LONG.bits();
+ #[allow(missing_docs)]
+ const ALL = Self::WHITE_BOTH.bits() | Self::BLACK_BOTH.bits();
+ }
+}
+
+impl CastleRights {
+ fn mask(player: Player) -> Self {
+ match player {
+ Player::White => Self::WHITE_BOTH,
+ Player::Black => Self::BLACK_BOTH,
+ }
+ }
+}
+
+impl TryFrom<&str> for CastleRights {
+ type Error = anyhow::Error;
+
+ /// Parses [`CastleRights`] for both players from the FEN format. The user
+ /// is responsible for providing valid input cleaned up from the actual FEN
+ /// chunk.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`anyhow::Error`] if given pattern does not match
+ ///
+ /// [`CastleRights`] := (K)? (Q)? (k)? (q)?
+ ///
+ /// Note that both letters have to be either uppercase or lowercase.
+ fn try_from(input: &str) -> anyhow::Result<Self> {
+ // Enumerate all possibilities.
+ match input.as_bytes() {
+ // K Q k q
+ // - - - -
+ // 0 0 0 0
+ b"-" => Ok(Self::NONE),
+ // 0 0 0 1
+ b"q" => Ok(Self::BLACK_LONG),
+ // 0 0 1 0
+ b"k" => Ok(Self::BLACK_SHORT),
+ // 0 0 1 1
+ b"kq" => Ok(Self::BLACK_BOTH),
+ // 0 1 0 0
+ b"Q" => Ok(Self::WHITE_LONG),
+ // 0 1 0 1
+ b"Qq" => Ok(Self::WHITE_LONG | Self::BLACK_LONG),
+ // 0 1 1 0
+ b"Qk" => Ok(Self::WHITE_LONG | Self::BLACK_SHORT),
+ // 0 1 1 1
+ b"Qkq" => Ok(Self::WHITE_LONG | Self::BLACK_BOTH),
+ // 1 0 0 0
+ b"K" => Ok(Self::WHITE_SHORT),
+ // 1 0 0 1
+ b"Kq" => Ok(Self::WHITE_SHORT | Self::BLACK_LONG),
+ // 1 0 1 0
+ b"Kk" => Ok(Self::WHITE_SHORT | Self::BLACK_SHORT),
+ // 1 0 1 1
+ b"Kkq" => Ok(Self::WHITE_SHORT | Self::BLACK_BOTH),
+ // 1 1 0 0
+ b"KQ" => Ok(Self::WHITE_BOTH),
+ // 1 1 0 1
+ b"KQq" => Ok(Self::WHITE_BOTH | Self::BLACK_LONG),
+ // 1 1 1 0
+ b"KQk" => Ok(Self::WHITE_BOTH | Self::BLACK_SHORT),
+ // 1 1 1 1
+ b"KQkq" => Ok(Self::ALL),
+ _ => bail!("unknown castle rights: {input}"),
+ }
+ }
+}
+
+impl fmt::Display for CastleRights {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if *self == Self::NONE {
+ return f.write_char('-');
+ }
+ if *self & Self::WHITE_SHORT != Self::NONE {
+ f.write_char('K')?;
+ }
+ if *self & Self::WHITE_LONG != Self::NONE {
+ f.write_char('Q')?;
+ }
+ if *self & Self::BLACK_SHORT != Self::NONE {
+ f.write_char('k')?;
+ }
+ if *self & Self::BLACK_LONG != Self::NONE {
+ f.write_char('q')?;
+ }
+ Ok(())
+ }
+}
+
+/// A pawn can be promoted to a queen, rook, bishop or a knight.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+pub enum Promotion {
+ Queen,
+ Rook,
+ Bishop,
+ Knight,
+}
+
+/// Directions on the board from a perspective of White player.
+///
+/// Traditionally those are North (Up), West (Left), East (Right), South (Down)
+/// and their combinations. However, using cardinal directions is confusing,
+/// hence they are replaced by relative directions.
+// TODO: Either use double directions in en passant calculations or only leave Up an Down.
+#[derive(Copy, Clone, Debug, strum::EnumIter)]
+pub enum Direction {
+ /// Also known as North.
+ Up,
+ /// Also known as South.
+ Down,
+}
+
+impl Direction {
+ pub(super) fn opposite(self) -> Self {
+ match self {
+ Self::Up => Self::Down,
+ Self::Down => Self::Up,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::mem::{size_of, size_of_val};
+
+ use pretty_assertions::assert_eq;
+
+ use super::*;
+
+ #[test]
+ fn rank() {
+ assert_eq!(
+ ('1'..='9')
+ .filter_map(|ch| Rank::try_from(ch).ok())
+ .collect::<Vec<Rank>>(),
+ vec![
+ Rank::One,
+ Rank::Two,
+ Rank::Three,
+ Rank::Four,
+ Rank::Five,
+ Rank::Six,
+ Rank::Seven,
+ Rank::Eight,
+ ]
+ );
+ assert_eq!(
+ ('1'..='9')
+ .filter_map(|idx| Rank::try_from(idx).ok())
+ .collect::<Vec<Rank>>(),
+ vec![
+ Rank::One,
+ Rank::Two,
+ Rank::Three,
+ Rank::Four,
+ Rank::Five,
+ Rank::Six,
+ Rank::Seven,
+ Rank::Eight,
+ ]
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "rank should be within '1'..='8', got '9'")]
+ fn rank_from_incorrect_char() {
+ let _ = Rank::try_from('9').unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "rank should be within '1'..='8', got '0'")]
+ fn rank_from_incorrect_char_zero() {
+ let _ = Rank::try_from('0').unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "rank should be within 0..BOARD_WIDTH, got 8")]
+ fn rank_from_incorrect_index() {
+ let _ = Rank::try_from(BOARD_WIDTH).unwrap();
+ }
+
+ #[test]
+ fn file() {
+ assert_eq!(
+ ('a'..='i')
+ .filter_map(|ch| File::try_from(ch).ok())
+ .collect::<Vec<File>>(),
+ vec![
+ File::A,
+ File::B,
+ File::C,
+ File::D,
+ File::E,
+ File::F,
+ File::G,
+ File::H,
+ ]
+ );
+ assert_eq!(
+ (0..=BOARD_WIDTH)
+ .filter_map(|idx| File::try_from(idx).ok())
+ .collect::<Vec<File>>(),
+ vec![
+ File::A,
+ File::B,
+ File::C,
+ File::D,
+ File::E,
+ File::F,
+ File::G,
+ File::H,
+ ]
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "file should be within 'a'..='h', got 'i'")]
+ fn file_from_incorrect_char() {
+ let _ = File::try_from('i').unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "file should be within 0..BOARD_WIDTH, got 8")]
+ fn file_from_incorrect_index() {
+ let _ = File::try_from(BOARD_WIDTH).unwrap();
+ }
+
+ #[test]
+ fn square() {
+ let squares: Vec<_> = [
+ 0u8,
+ BOARD_SIZE - 1,
+ BOARD_WIDTH - 1,
+ BOARD_WIDTH,
+ BOARD_WIDTH * 2 + 5,
+ BOARD_SIZE,
+ ]
+ .iter()
+ .filter_map(|square| Square::try_from(*square).ok())
+ .collect();
+ assert_eq!(
+ squares,
+ vec![Square::A1, Square::H8, Square::H1, Square::A2, Square::F3,]
+ );
+ let squares: Vec<_> = [
+ (File::B, Rank::Three),
+ (File::F, Rank::Five),
+ (File::H, Rank::Eight),
+ (File::E, Rank::Four),
+ ]
+ .iter()
+ .map(|(file, rank)| Square::new(*file, *rank))
+ .collect();
+ assert_eq!(
+ squares,
+ vec![Square::B3, Square::F5, Square::H8, Square::E4]
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "square index should be in 0..BOARD_SIZE, got 64")]
+ fn square_from_incorrect_index() {
+ let _ = Square::try_from(BOARD_SIZE).unwrap();
+ }
+
+ #[test]
+ fn primitive_size() {
+ assert_eq!(size_of::<Square>(), 1);
+ // Primitives will have small size thanks to the niche optimizations:
+ // https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#layout-of-a-data-carrying-enums-without-a-repr-annotation
+ assert_eq!(size_of::<PieceKind>(), size_of::<Option<PieceKind>>());
+ // This is going to be very useful for square-centric board implementation.
+ let square_to_pieces: [Option<PieceKind>; BOARD_SIZE as usize] =
+ [None; BOARD_SIZE as usize];
+ assert_eq!(size_of_val(&square_to_pieces), BOARD_SIZE as usize);
+ }
+
+ #[test]
+ fn square_shift() {
+ assert_eq!(Square::A2.shift(Direction::Up), Some(Square::A3));
+ assert_eq!(Square::B5.shift(Direction::Down), Some(Square::B4));
+ assert_eq!(Square::C1.shift(Direction::Down), None);
+ assert_eq!(Square::G8.shift(Direction::Up), None);
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +
//! Provides fully-specified [Chess Position] implementation: stores
+//! information about the board and tracks the state of castling, 50-move rule
+//! draw, etc.
+//!
+//! The core of Move Generator and move making is also implemented here as a way
+//! to produce ways of mutating [`Position`].
+//!
+//! [Chess Position]: https://www.chessprogramming.org/Chess_Position
+
+use std::fmt;
+
+use anyhow::{bail, Context};
+
+use crate::chess::attacks;
+use crate::chess::bitboard::{Bitboard, Board, Pieces};
+use crate::chess::core::{
+ CastleRights,
+ File,
+ Move,
+ MoveList,
+ Piece,
+ Player,
+ Promotion,
+ Rank,
+ Square,
+ BOARD_WIDTH,
+};
+
+/// State of the chess game: board, half-move counters and castling rights,
+/// etc. It has 1:1 relationship with [Forsyth-Edwards Notation] (FEN).
+///
+/// [`Position::try_from()`] provides a convenient interface for creating a
+/// [`Position`]. It will clean up the input (trim newlines and whitespace) and
+/// attempt to parse in either FEN or a version of [Extended Position
+/// Description] (EPD). The EPD format Pabi accepts does not support
+/// [Operations]: even though it is an important part of EPD, in practice it is
+/// rarely needed. The EPD support exists for compatibility with some databases
+/// which provide trimmed FEN lines (all FEN parts except Halfmove Clock and
+/// Fullmove Counter). Parsing these positions is important to utilize that
+/// data.
+///
+/// [Forsyth-Edwards Notation]: https://www.chessprogramming.org/Forsyth-Edwards_Notation
+/// [Extended Position Description]: https://www.chessprogramming.org/Extended_Position_Description
+/// [Operations]: https://www.chessprogramming.org/Extended_Position_Description#Operations
+// TODO: Make the fields private, expose appropriate assessors.
+// TODO: Store Zobrist hash, possibly other info.
+#[derive(Clone)]
+pub struct Position {
+ board: Board,
+ castling: CastleRights,
+ side_to_move: Player,
+ /// [Halfmove Clock][^ply] keeps track of the number of (half-)moves
+ /// since the last capture or pawn move and is used to enforce
+ /// fifty[^fifty]-move draw rule.
+ ///
+ ///
+ /// [Halfmove Clock]: https://www.chessprogramming.org/Halfmove_Clock
+ /// [^ply]: "Half-move" or ["ply"](https://www.chessprogramming.org/Ply) means a move of only
+ /// one side.
+ /// [^fifty]: 50 __full__ moves
+ halfmove_clock: u8,
+ fullmove_counter: u16,
+ en_passant_square: Option<Square>,
+}
+
+impl Position {
+ /// Creates the starting position of the standard chess.
+ ///
+ /// ```
+ /// use pabi::chess::position::Position;
+ ///
+ /// let starting_position = Position::starting();
+ /// assert_eq!(
+ /// &starting_position.to_string(),
+ /// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
+ /// );
+ /// ```
+ #[must_use]
+ pub fn starting() -> Self {
+ Self {
+ board: Board::starting(),
+ castling: CastleRights::ALL,
+ ..Self::empty()
+ }
+ }
+
+ fn empty() -> Self {
+ Self {
+ board: Board::empty(),
+ castling: CastleRights::NONE,
+ side_to_move: Player::White,
+ halfmove_clock: 0,
+ fullmove_counter: 1,
+ en_passant_square: None,
+ }
+ }
+
+ pub(super) fn us(&self) -> Player {
+ self.side_to_move
+ }
+
+ pub(super) fn they(&self) -> Player {
+ self.us().opponent()
+ }
+
+ pub(super) fn pieces(&self, player: Player) -> &Pieces {
+ self.board.player_pieces(player)
+ }
+
+ fn occupancy(&self, player: Player) -> Bitboard {
+ self.board.player_pieces(player).all()
+ }
+
+ fn occupied_squares(&self) -> Bitboard {
+ self.occupancy(self.us()) | self.occupancy(self.they())
+ }
+
+ /// Parses board from Forsyth-Edwards Notation and checks its correctness.
+ /// The parser will accept trimmed full FEN and trimmed FEN (4 first parts).
+ ///
+ /// FEN ::=
+ /// Piece Placement
+ /// ' ' Side to move
+ /// ' ' Castling ability
+ /// ' ' En passant target square
+ /// ' ' Halfmove clock
+ /// ' ' Fullmove counter
+ ///
+ /// The last two parts (together) are optional and will default to "0 1".
+ /// Technically, that is not a full FEN position, but it is supported
+ /// because EPD-style position strings are common in public position books
+ /// and datasets where halfmove clock and fullmove counters do not matter.
+ /// Supporting these datasets is important but distinguishing between full
+ /// and trimmed FEN strings is not.
+ ///
+ /// Correctness check employs a small set of simple heuristics to check if
+ /// the position can be analyzed by the engine and will reject the most
+ /// obvious incorrect positions (e.g. missing kings, pawns on the wrong
+ /// ranks, problems with en passant square). The only public way of creating
+ /// a [`Position`] is by parsing it from string, so this acts as a filter
+ /// for positions that won't cause undefined behavior or crashes. It's
+ /// important that positions that are known to be dubious are filtered out.
+ ///
+ /// NOTE: This expects properly-formatted inputs: no extra symbols or
+ /// additional whitespace. Use [`Position::try_from`] for cleaning up the
+ /// input if it is coming from untrusted source and is likely to contain
+ /// extra symbols.
+ // was not legal.
+ pub fn from_fen(input: &str) -> anyhow::Result<Self> {
+ let mut parts = input.split(' ');
+ // Parse Piece Placement.
+ let mut result = Self::empty();
+ let pieces_placement = match parts.next() {
+ Some(placement) => placement,
+ None => bail!("incorrect FEN: missing pieces placement"),
+ };
+ let ranks = pieces_placement.split('/');
+ let mut rank_id = 8;
+ for rank_fen in ranks {
+ if rank_id == 0 {
+ bail!("incorrect FEN: expected 8 ranks, got {pieces_placement}");
+ }
+ rank_id -= 1;
+ let rank = Rank::try_from(rank_id)?;
+ let mut file: u8 = 0;
+ for symbol in rank_fen.chars() {
+ if file > BOARD_WIDTH {
+ bail!("file exceeded {BOARD_WIDTH}");
+ }
+ match symbol {
+ '0' => bail!("increment can not be 0"),
+ '1'..='9' => {
+ file += symbol as u8 - b'0';
+ continue;
+ },
+ _ => (),
+ }
+ match Piece::try_from(symbol) {
+ Ok(piece) => {
+ let owner = match piece.owner {
+ Player::White => &mut result.board.white_pieces,
+ Player::Black => &mut result.board.black_pieces,
+ };
+ let square = Square::new(file.try_into()?, rank);
+ *owner.bitboard_for(piece.kind) |= Bitboard::from(square);
+ },
+ Err(e) => return Err(e),
+ }
+ file += 1;
+ }
+ if file != BOARD_WIDTH {
+ bail!("incorrect FEN: rank size should be exactly {BOARD_WIDTH}, got {rank_fen} of length {file}");
+ }
+ }
+ if rank_id != 0 {
+ bail!("incorrect FEN: there should be 8 ranks, got {pieces_placement}");
+ }
+ result.side_to_move = match parts.next() {
+ Some(value) => value.try_into()?,
+ None => bail!("incorrect FEN: missing side to move"),
+ };
+ result.castling = match parts.next() {
+ Some(value) => value.try_into()?,
+ None => bail!("incorrect FEN: missing castling rights"),
+ };
+ result.en_passant_square = match parts.next() {
+ Some("-") => None,
+ Some(value) => Some(value.try_into()?),
+ None => bail!("incorrect FEN: missing en passant square"),
+ };
+ result.halfmove_clock = match parts.next() {
+ Some(value) => {
+ // TODO: Here and below: parse manually just by getting through
+ // ASCII digits since we're already checking them.
+ if !value.bytes().all(|c| c.is_ascii_digit()) {
+ bail!("halfmove clock can not contain anything other than digits");
+ }
+ match value.parse::<u8>() {
+ Ok(num) => num,
+ Err(e) => {
+ return Err(e).with_context(|| {
+ format!("incorrect FEN: halfmove clock can not be parsed {value}")
+ });
+ },
+ }
+ },
+ // This is a correct EPD: exit early.
+ None => {
+ return match validate(&result) {
+ Ok(_) => Ok(result),
+ Err(e) => Err(e.context("illegal position")),
+ }
+ },
+ };
+ result.fullmove_counter = match parts.next() {
+ Some(value) => {
+ if !value.bytes().all(|c| c.is_ascii_digit()) {
+ bail!("fullmove counter clock can not contain anything other than digits");
+ }
+ match value.parse::<u16>() {
+ Ok(0) => {
+ bail!("fullmove counter can not be 0")
+ },
+ Ok(num) => num,
+ Err(e) => {
+ return Err(e).with_context(|| {
+ format!("incorrect FEN: fullmove counter can not be parsed {value}")
+ });
+ },
+ }
+ },
+ None => bail!("incorrect FEN: missing halfmove clock"),
+ };
+ match parts.next() {
+ None => match validate(&result) {
+ Ok(_) => Ok(result),
+ Err(e) => Err(e.context("illegal position")),
+ },
+ Some(_) => bail!("trailing symbols are not allowed in FEN"),
+ }
+ }
+
+ /// Returns a string representation of the position in FEN format.
+ #[must_use]
+ pub fn fen(&self) -> String {
+ self.to_string()
+ }
+
+ #[must_use]
+ pub fn has_insufficient_material(&self) -> bool {
+ todo!()
+ }
+
+ #[must_use]
+ pub fn is_legal(&self) -> bool {
+ validate(self).is_ok()
+ }
+
+ pub(super) fn attack_info(&self) -> attacks::AttackInfo {
+ let (us, they) = (self.us(), self.they());
+ let (our_pieces, their_pieces) = (self.pieces(us), self.pieces(they));
+ let king: Square = our_pieces.king.as_square();
+ let (our_occupancy, their_occupancy) = (our_pieces.all(), their_pieces.all());
+ let occupancy = our_occupancy | their_occupancy;
+ attacks::AttackInfo::new(they, their_pieces, king, our_occupancy, occupancy)
+ }
+
+ /// Calculates a list of legal moves (i.e. the moves that do not leave our
+ /// king in check).
+ ///
+ /// This is a performance and correctness-critical path: every modification
+ /// should be benchmarked and carefully tested.
+ ///
+ /// NOTE: [BMI Instruction Set] (and specifically efficient [PEXT]) is not
+ /// widely available on all processors (e.g. the AMD only started providing
+ /// an *efficient* PEXT since Ryzen 3). The current implementation will
+ /// rely on PEXT for performance because it is the most efficient move
+ /// generator technique available.
+ ///
+ /// [generation]: https://www.chessprogramming.org/Table-driven_Move_Generation
+ /// [BMI2 Pext Bitboards]: https://www.chessprogramming.org/BMI2#PEXTBitboards
+ /// [BMI Instruction Set]: https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set
+ /// [PEXT]: https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#Parallel_bit_deposit_and_extract
+ // TODO: Fall back to Fancy Magic Bitboards if BMI2 is not available for
+ // portability?
+ // TODO: Look at and compare speed with https://github.com/jordanbray/chess
+ // TODO: Another source for comparison:
+ // https://github.com/sfleischman105/Pleco/blob/b825cecc258ad25cba65919208727994f38a06fb/pleco/src/board/movegen.rs#L68-L85
+ // TODO: Maybe use python-chess testset of perft moves:
+ // https://github.com/niklasf/python-chess/blob/master/examples/perft/random.perft
+ // TODO: Compare with other engines and perft generators
+ // (https://github.com/jniemann66/juddperft).
+ // TODO: Check movegen comparison (https://github.com/Gigantua/Chess_Movegen).
+ // TODO: Use monomorphization to generate code for calculating attacks for both sides to reduce
+ // branching? https://rustc-dev-guide.rust-lang.org/backend/monomorph.html
+ // TODO: Split into subroutines so that it's easier to tune performance.
+ #[must_use]
+ pub fn generate_moves(&self) -> MoveList {
+ let mut moves = MoveList::new();
+ // debug_assert!(validate(&self).is_ok(), "{}", self.fen());
+ // TODO: Try caching more e.g. all()s? Benchmark to confirm that this is an
+ // improvement.
+ let (us, they) = (self.us(), self.they());
+ let (our_pieces, their_pieces) = (self.pieces(us), self.pieces(they));
+ let king: Square = our_pieces.king.as_square();
+ let (our_occupancy, their_occupancy) = (our_pieces.all(), their_pieces.all());
+ let occupied_squares = our_occupancy | their_occupancy;
+ let their_or_empty = !our_occupancy;
+ let attack_info =
+ attacks::AttackInfo::new(they, their_pieces, king, our_occupancy, occupied_squares);
+ // Moving the king to safety is always a valid move.
+ generate_king_moves(king, attack_info.safe_king_squares, &mut moves);
+ // If there are checks, the moves are restricted to resolving them.
+ let blocking_ray = match attack_info.checkers.count() {
+ 0 => Bitboard::full(),
+ // There are two ways of getting out of check:
+ //
+ // - Moving king to safety (calculated above)
+ // - Blocking the checker or capturing it
+ //
+ // The former is calculated above, the latter is dealt with below.
+ 1 => {
+ let checker: Square = attack_info.checkers.as_square();
+ let ray = attacks::ray(checker, king);
+ if ray.is_empty() {
+ // This means the checker is a knight: capture is the only
+ // way left to resolve this check.
+ attack_info.checkers
+ } else {
+ // Checker is a sliding piece: both capturing and blocking
+ // resolves the check.
+ ray
+ }
+ },
+ // Double checks can only be evaded by the king moves to safety: no
+ // need to consider other moves.
+ 2 => return moves,
+ _ => unreachable!("more than two pieces can not check the king"),
+ };
+ generate_knight_moves(
+ our_pieces.knights,
+ their_or_empty,
+ attack_info.pins,
+ blocking_ray,
+ &mut moves,
+ );
+ generate_rook_moves(
+ our_pieces.rooks | our_pieces.queens,
+ occupied_squares,
+ their_or_empty,
+ blocking_ray,
+ attack_info.pins,
+ king,
+ &mut moves,
+ );
+ generate_bishop_moves(
+ our_pieces.bishops | our_pieces.queens,
+ occupied_squares,
+ their_or_empty,
+ blocking_ray,
+ attack_info.pins,
+ king,
+ &mut moves,
+ );
+ generate_pawn_moves(
+ our_pieces.pawns,
+ us,
+ they,
+ their_pieces,
+ their_occupancy,
+ their_or_empty,
+ blocking_ray,
+ attack_info.pins,
+ attack_info.checkers,
+ king,
+ self.en_passant_square,
+ occupied_squares,
+ &mut moves,
+ );
+ generate_castle_moves(
+ us,
+ attack_info.checkers,
+ self.castling,
+ attack_info.attacks,
+ occupied_squares,
+ &mut moves,
+ );
+ moves
+ }
+
+ // TODO: Docs: this is the only way to mutate a position....
+ // TODO: Make an checked version of it? With the move coming from the UCI
+ // it's best to check if it's valid or not.
+ // TODO: Is it better to clone and return a new Position? It seems that the
+ // most usecases (e.g. for search) would clone the position and then mutate
+ // it anyway. This would prevent (im)mutability reference problems.
+ pub fn make_move(&mut self, next_move: &Move) {
+ // debug_assert!(self.is_legal());
+ let (us, they) = (self.us(), self.they());
+ let our_backrank = Rank::backrank(us);
+ let (our_pieces, their_pieces) = match self.us() {
+ Player::White => (&mut self.board.white_pieces, &mut self.board.black_pieces),
+ Player::Black => (&mut self.board.black_pieces, &mut self.board.white_pieces),
+ };
+ let previous_en_passant = self.en_passant_square;
+ self.en_passant_square = None;
+ if us == Player::Black {
+ self.fullmove_counter += 1;
+ }
+ self.halfmove_clock += 1;
+ // NOTE: We reset side_to_move early! To access the moving side, use cached
+ // `us`.
+ self.side_to_move = us.opponent();
+ // Handle captures.
+ if our_pieces.rooks.contains(next_move.from) {
+ match (us, next_move.from) {
+ (Player::White, Square::A1) => self.castling.remove(CastleRights::WHITE_LONG),
+ (Player::White, Square::H1) => self.castling.remove(CastleRights::WHITE_SHORT),
+ (Player::Black, Square::A8) => self.castling.remove(CastleRights::BLACK_LONG),
+ (Player::Black, Square::H8) => self.castling.remove(CastleRights::BLACK_SHORT),
+ _ => (),
+ }
+ }
+ if their_pieces.all().contains(next_move.to) {
+ // Capturing a piece resets the clock.
+ self.halfmove_clock = 0;
+ match (they, next_move.to) {
+ (Player::White, Square::H1) => self.castling.remove(CastleRights::WHITE_SHORT),
+ (Player::White, Square::A1) => self.castling.remove(CastleRights::WHITE_LONG),
+ (Player::Black, Square::H8) => self.castling.remove(CastleRights::BLACK_SHORT),
+ (Player::Black, Square::A8) => self.castling.remove(CastleRights::BLACK_LONG),
+ _ => (),
+ };
+ their_pieces.clear(next_move.to);
+ }
+ if our_pieces.pawns.contains(next_move.from) {
+ // Pawn move resets the clock.
+ self.halfmove_clock = 0;
+ // Check en passant.
+ if let Some(en_passant_square) = previous_en_passant {
+ if next_move.to == en_passant_square {
+ let captured_pawn = Square::new(next_move.to.file(), next_move.from.rank());
+ their_pieces.pawns.clear(captured_pawn);
+ }
+ }
+ our_pieces.pawns.clear(next_move.from);
+ // Check promotions.
+ // TODO: Debug assertions to make sure the promotion is valid.
+ if let Some(promotion) = next_move.promotion {
+ match promotion {
+ Promotion::Queen => our_pieces.queens.extend(next_move.to),
+ Promotion::Rook => our_pieces.rooks.extend(next_move.to),
+ Promotion::Bishop => our_pieces.bishops.extend(next_move.to),
+ Promotion::Knight => our_pieces.knights.extend(next_move.to),
+ };
+ return;
+ }
+ our_pieces.pawns.extend(next_move.to);
+ let single_push_square = next_move.from.shift(us.push_direction()).unwrap();
+ if next_move.from.rank() == Rank::pawns_starting(us)
+ && next_move.from.file() == next_move.to.file()
+ && single_push_square != next_move.to
+ // Technically, this is not correct: https://github.com/jhlywa/chess.js/issues/294
+ && (their_pieces.pawns & attacks::pawn_attacks(single_push_square, us)).has_any()
+ {
+ self.en_passant_square = Some(single_push_square);
+ }
+ return;
+ }
+ if our_pieces.king.contains(next_move.from) {
+ // Check if the move is castling.
+ if next_move.from.rank() == our_backrank
+ && next_move.to.rank() == our_backrank
+ && next_move.from.file() == File::E
+ {
+ if next_move.to.file() == File::G {
+ // TODO: debug_assert!(self.can_castle_short())
+ our_pieces.rooks.clear(Square::new(File::H, our_backrank));
+ our_pieces.rooks.extend(Square::new(File::F, our_backrank));
+ } else if next_move.to.file() == File::C {
+ // TODO: debug_assert!(self.can_castle_long())
+ our_pieces.rooks.clear(Square::new(File::A, our_backrank));
+ our_pieces.rooks.extend(Square::new(File::D, our_backrank));
+ }
+ }
+ our_pieces.king.clear(next_move.from);
+ our_pieces.king.extend(next_move.to);
+ // The king has moved: reset castling.
+ match us {
+ Player::White => self.castling.remove(CastleRights::WHITE_BOTH),
+ Player::Black => self.castling.remove(CastleRights::BLACK_BOTH),
+ };
+ return;
+ }
+ // Regular moves: put the piece from the source to destination. We
+ // already cleared the opponent piece if there was a capture.
+ for piece in [
+ &mut our_pieces.queens,
+ &mut our_pieces.rooks,
+ &mut our_pieces.bishops,
+ &mut our_pieces.knights,
+ ] {
+ if piece.contains(next_move.from) {
+ piece.clear(next_move.from);
+ piece.extend(next_move.to);
+ return;
+ }
+ }
+ }
+}
+
+impl TryFrom<&str> for Position {
+ type Error = anyhow::Error;
+
+ // TODO: Docs.
+ // TODO: Parse UCI position move1 move2 ...
+ fn try_from(input: &str) -> anyhow::Result<Self> {
+ let input = input.trim();
+ for prefix in ["fen ", "epd "] {
+ if let Some(stripped) = input.strip_prefix(prefix) {
+ return Self::from_fen(stripped);
+ }
+ }
+ Self::from_fen(input)
+ }
+}
+
+impl fmt::Display for Position {
+ /// Prints board in Forsyth-Edwards Notation.
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} ", &self.board)?;
+ write!(f, "{} ", &self.side_to_move)?;
+ write!(f, "{} ", &self.castling)?;
+ match self.en_passant_square {
+ Some(square) => write!(f, "{square} "),
+ None => write!(f, "- "),
+ }?;
+ write!(f, "{} ", &self.halfmove_clock)?;
+ write!(f, "{}", &self.fullmove_counter)?;
+ Ok(())
+ }
+}
+
+impl fmt::Debug for Position {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "{:?}", &self.board)?;
+ writeln!(f, "Player to move: {:?}", &self.side_to_move)?;
+ writeln!(f, "Fullmove counter: {:?}", &self.fullmove_counter)?;
+ writeln!(f, "En Passant: {:?}", &self.en_passant_square)?;
+ // bitflags' default fmt::Debug implementation is not very convenient:
+ // dump FEN instead.
+ writeln!(f, "Castling rights: {}", &self.castling)?;
+ writeln!(f, "FEN: {}", &self.to_string())?;
+ Ok(())
+ }
+}
+
+/// [Perft] (**per**formance **t**esting) is a technique for checking
+/// correctness of move generation by traversing the tree of possible positions
+/// (nodes) and calculating all the leaf nodes at certain depth.
+///
+/// [Perft]: https://www.chessprogramming.org/Perft
+#[must_use]
+pub fn perft(position: &Position, depth: u8) -> u64 {
+ debug_assert!(position.is_legal());
+ if depth == 0 {
+ return 1;
+ }
+ let mut nodes = 0;
+ for next_move in position.generate_moves().iter() {
+ let mut next_position = position.clone();
+ next_position.make_move(next_move);
+ nodes += perft(&next_position, depth - 1);
+ }
+ nodes
+}
+
+// Checks if the position is "legal", i.e. if it can be reasoned about by
+// the engine. Checking whether the position is truly reachable from the
+// starting position (either in standard chess or Fischer Random Chess)
+// requires retrograde analysis and potentially unreasonable amount of time.
+// This check employs a limited number of heuristics that filter out the
+// most obvious incorrect positions and prevents them from being analyzed.
+// This helps set up barrier (constructing positions from FEN) between the
+// untrusted environment (UCI front-end, user input) and the engine.
+#[must_use]
+fn validate(position: &Position) -> anyhow::Result<()> {
+ if position.fullmove_counter == 0 {
+ bail!("fullmove counter cannot be zero")
+ }
+ // TODO: Probe opposite checks.
+ // TODO: The following patterns look repetitive; maybe refactor the
+ // common structure even though it's quite short?
+ if position.board.white_pieces.king.count() != 1 {
+ bail!(
+ "expected 1 white king, got {}",
+ position.board.white_pieces.king.count()
+ )
+ }
+ if position.board.black_pieces.king.count() != 1 {
+ bail!(
+ "expected 1 black king, got {}",
+ position.board.black_pieces.king.count()
+ )
+ }
+ if position.board.white_pieces.pawns.count() > 8 {
+ bail!(
+ "expected <= 8 white pawns, got {}",
+ position.board.white_pieces.pawns.count()
+ )
+ }
+ if position.board.black_pieces.pawns.count() > 8 {
+ bail!(
+ "expected <= 8 black pawns, got {}",
+ position.board.black_pieces.pawns.count()
+ )
+ }
+ if ((position.board.white_pieces.pawns | position.board.black_pieces.pawns)
+ & (Rank::One.mask() | Rank::Eight.mask()))
+ .has_any()
+ {
+ bail!("pawns can not be placed on backranks")
+ }
+ let attack_info = position.attack_info();
+ // Can't have more than two checks.
+ if attack_info.checkers.count() > 2 {
+ bail!("expected <= 2 checks, got {}", attack_info.checkers.count())
+ }
+ if let Some(en_passant_square) = position.en_passant_square {
+ let expected_rank = match position.side_to_move {
+ Player::White => Rank::Six,
+ Player::Black => Rank::Three,
+ };
+ if en_passant_square.rank() != expected_rank {
+ bail!(
+ "expected en passant square to be on rank {}, got {}",
+ expected_rank,
+ en_passant_square.rank()
+ )
+ }
+ // A pawn that was just pushed by our opponent should be in front of
+ // en_passant_square.
+ let pushed_pawn = en_passant_square
+ .shift(position.they().push_direction())
+ .unwrap();
+ if !position.pieces(position.they()).pawns.contains(pushed_pawn) {
+ bail!("en passant square is not beyond pushed pawn")
+ }
+ // If en-passant was played and there's a check, doubly pushed pawn
+ // should be the only checker or it should be a discovery.
+ let king = position.pieces(position.us()).king.as_square();
+ if attack_info.checkers.has_any() {
+ if attack_info.checkers.count() > 1 {
+ bail!("more than 1 check after double pawn push is impossible")
+ }
+ // The check wasn't delivered by pushed pawn.
+ if attack_info.checkers != Bitboard::from(pushed_pawn) {
+ let checker = attack_info.checkers.as_square();
+ let original_square = en_passant_square
+ .shift(position.us().push_direction())
+ .unwrap();
+ if !(attacks::ray(checker, king).contains(original_square)) {
+ bail!(
+ "the only possible checks after double pawn push are either discovery \
+ targeting the original pawn square or the pushed pawn itself"
+ )
+ }
+ }
+ }
+ // Doubly pushed pawn can not block a diagonal check.
+ for attacker in (position.pieces(position.they()).queens
+ | position.pieces(position.they()).bishops)
+ .iter()
+ {
+ let xray = attacks::bishop_ray(attacker, king);
+ if (xray & (position.occupied_squares())).count() == 2
+ && xray.contains(attacker)
+ && xray.contains(pushed_pawn)
+ {
+ bail!("doubly pushed pawn can not be the only blocker on a diagonal")
+ }
+ }
+ }
+ Ok(())
+}
+
+fn generate_king_moves(king: Square, safe_squares: Bitboard, moves: &mut MoveList) {
+ for safe_square in safe_squares.iter() {
+ unsafe {
+ moves.push_unchecked(Move::new(king, safe_square, None));
+ }
+ }
+}
+
+fn generate_knight_moves(
+ knights: Bitboard,
+ their_or_empty: Bitboard,
+ pins: Bitboard,
+ blocking_ray: Bitboard,
+ moves: &mut MoveList,
+) {
+ // When a knight is pinned, it can not move at all because it can't stay on
+ // the same horizontal, vertical or diagonal.
+ for from in (knights - pins).iter() {
+ let targets = attacks::knight_attacks(from) & their_or_empty & blocking_ray;
+ for to in targets.iter() {
+ unsafe {
+ moves.push_unchecked(Move::new(from, to, None));
+ }
+ }
+ }
+}
+
+fn generate_rook_moves(
+ rooks: Bitboard,
+ occupied_squares: Bitboard,
+ their_or_empty: Bitboard,
+ blocking_ray: Bitboard,
+ pins: Bitboard,
+ king: Square,
+ moves: &mut MoveList,
+) {
+ for from in rooks.iter() {
+ let targets = attacks::rook_attacks(from, occupied_squares) & their_or_empty & blocking_ray;
+ for to in targets.iter() {
+ // TODO: This block is repeated several times; abstract it out.
+ if pins.contains(from) && (attacks::ray(from, king) & attacks::ray(to, king)).is_empty()
+ {
+ continue;
+ }
+ unsafe { moves.push_unchecked(Move::new(from, to, None)) }
+ }
+ }
+}
+
+fn generate_bishop_moves(
+ bishops: Bitboard,
+ occupied_squares: Bitboard,
+ their_or_empty: Bitboard,
+ blocking_ray: Bitboard,
+ pins: Bitboard,
+ king: Square,
+ moves: &mut MoveList,
+) {
+ for from in bishops.iter() {
+ let targets =
+ attacks::bishop_attacks(from, occupied_squares) & their_or_empty & blocking_ray;
+ for to in targets.iter() {
+ // TODO: This block is repeated several times; abstract it out.
+ if pins.contains(from) && (attacks::ray(from, king) & attacks::ray(to, king)).is_empty()
+ {
+ continue;
+ }
+ unsafe { moves.push_unchecked(Move::new(from, to, None)) }
+ }
+ }
+}
+
+fn generate_pawn_moves(
+ pawns: Bitboard,
+ us: Player,
+ they: Player,
+ their_pieces: &Pieces,
+ their_occupancy: Bitboard,
+ their_or_empty: Bitboard,
+ blocking_ray: Bitboard,
+ pins: Bitboard,
+ checkers: Bitboard,
+ king: Square,
+ en_passant_square: Option<Square>,
+ occupied_squares: Bitboard,
+ moves: &mut MoveList,
+) {
+ // TODO: Get rid of the branch: AND pawns getting to the promotion rank and the
+ // rest.
+ for from in pawns.iter() {
+ let targets =
+ (attacks::pawn_attacks(from, us) & their_occupancy) & their_or_empty & blocking_ray;
+ for to in targets.iter() {
+ // TODO: This block is repeated several times; abstract it out.
+ if pins.contains(from) && (attacks::ray(from, king) & attacks::ray(to, king)).is_empty()
+ {
+ continue;
+ }
+ match to.rank() {
+ Rank::One | Rank::Eight => unsafe {
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Queen)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Rook)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Bishop)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Knight)));
+ },
+ _ => unsafe { moves.push_unchecked(Move::new(from, to, None)) },
+ }
+ }
+ }
+ // Generate en passant moves.
+ if let Some(en_passant_square) = en_passant_square {
+ let en_passant_pawn = en_passant_square.shift(they.push_direction()).unwrap();
+ // Check if capturing en passant resolves the check.
+ let candidate_pawns = attacks::pawn_attacks(en_passant_square, they) & pawns;
+ if checkers.contains(en_passant_pawn) {
+ for our_pawn in candidate_pawns.iter() {
+ if pins.contains(our_pawn) {
+ continue;
+ }
+ unsafe {
+ moves.push_unchecked(Move::new(our_pawn, en_passant_square, None));
+ }
+ }
+ } else {
+ // Check if capturing en passant does not create a discovered check.
+ for our_pawn in candidate_pawns.iter() {
+ let mut occupancy_after_capture = occupied_squares;
+ occupancy_after_capture.clear(our_pawn);
+ occupancy_after_capture.clear(en_passant_pawn);
+ occupancy_after_capture.extend(en_passant_square);
+ if (attacks::queen_attacks(king, occupancy_after_capture) & their_pieces.queens)
+ .is_empty()
+ && (attacks::rook_attacks(king, occupancy_after_capture) & their_pieces.rooks)
+ .is_empty()
+ && (attacks::bishop_attacks(king, occupancy_after_capture)
+ & their_pieces.bishops)
+ .is_empty()
+ {
+ unsafe {
+ moves.push_unchecked(Move::new(our_pawn, en_passant_square, None));
+ }
+ }
+ }
+ }
+ }
+ // Regular pawn pushes.
+ let push_direction = us.push_direction();
+ let pawn_pushes = pawns.shift(push_direction) - occupied_squares;
+ let original_squares = pawn_pushes.shift(push_direction.opposite());
+ let add_pawn_moves = |moves: &mut MoveList, from, to: Square| {
+ // TODO: This is probably better with self.side_to_move.opponent().backrank()
+ // but might be slower.
+ match to.rank() {
+ Rank::Eight | Rank::One => unsafe {
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Queen)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Rook)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Bishop)));
+ moves.push_unchecked(Move::new(from, to, Some(Promotion::Knight)));
+ },
+ _ => unsafe { moves.push_unchecked(Move::new(from, to, None)) },
+ }
+ };
+ for (from, to) in itertools::zip(original_squares.iter(), pawn_pushes.iter()) {
+ if !blocking_ray.contains(to) {
+ continue;
+ }
+ if pins.contains(from) && (attacks::ray(from, king) & attacks::ray(to, king)).is_empty() {
+ continue;
+ }
+ add_pawn_moves(moves, from, to);
+ }
+ // Double pawn pushes.
+ // TODO: Come up with a better name for it.
+ let third_rank = Rank::pawns_starting(us).mask().shift(push_direction);
+ let double_pushes = (pawn_pushes & third_rank).shift(push_direction) - occupied_squares;
+ let original_squares = double_pushes
+ .shift(push_direction.opposite())
+ .shift(push_direction.opposite());
+ // Double pawn pushes are never promoting.
+ for (from, to) in itertools::zip(original_squares.iter(), double_pushes.iter()) {
+ if !blocking_ray.contains(to) {
+ continue;
+ }
+ if pins.contains(from) && (attacks::ray(from, king) & attacks::ray(to, king)).is_empty() {
+ continue;
+ }
+ unsafe {
+ moves.push_unchecked(Move::new(from, to, None));
+ }
+ }
+}
+
+fn generate_castle_moves(
+ us: Player,
+ checkers: Bitboard,
+ castling: CastleRights,
+ attacks: Bitboard,
+ occupied_squares: Bitboard,
+ moves: &mut MoveList,
+) {
+ // TODO: Generalize castling to FCR.
+ // TODO: In FCR we should check if the rook is pinned or not.
+ if checkers.is_empty() {
+ match us {
+ Player::White => {
+ if castling.contains(CastleRights::WHITE_SHORT)
+ && (attacks & attacks::WHITE_SHORT_CASTLE_KING_WALK).is_empty()
+ && (occupied_squares
+ & (attacks::WHITE_SHORT_CASTLE_KING_WALK
+ | attacks::WHITE_SHORT_CASTLE_ROOK_WALK))
+ .is_empty()
+ {
+ unsafe {
+ moves.push_unchecked(Move::new(Square::E1, Square::G1, None));
+ }
+ }
+ if castling.contains(CastleRights::WHITE_LONG)
+ && (attacks & attacks::WHITE_LONG_CASTLE_KING_WALK).is_empty()
+ && (occupied_squares
+ & (attacks::WHITE_LONG_CASTLE_KING_WALK
+ | attacks::WHITE_LONG_CASTLE_ROOK_WALK))
+ .is_empty()
+ {
+ unsafe {
+ moves.push_unchecked(Move::new(Square::E1, Square::C1, None));
+ }
+ }
+ },
+ Player::Black => {
+ if castling.contains(CastleRights::BLACK_SHORT)
+ && (attacks & attacks::BLACK_SHORT_CASTLE_KING_WALK).is_empty()
+ && (occupied_squares
+ & (attacks::BLACK_SHORT_CASTLE_KING_WALK
+ | attacks::BLACK_SHORT_CASTLE_ROOK_WALK))
+ .is_empty()
+ {
+ unsafe {
+ moves.push_unchecked(Move::new(Square::E8, Square::G8, None));
+ }
+ }
+ if castling.contains(CastleRights::BLACK_LONG)
+ && (attacks & attacks::BLACK_LONG_CASTLE_KING_WALK).is_empty()
+ && (occupied_squares
+ & (attacks::BLACK_LONG_CASTLE_KING_WALK
+ | attacks::BLACK_LONG_CASTLE_ROOK_WALK))
+ .is_empty()
+ {
+ unsafe {
+ moves.push_unchecked(Move::new(Square::E8, Square::C8, None));
+ }
+ }
+ },
+ }
+ }
+}
+
1 +
pub mod evaluator;
+
1 +
pub trait Evaluator {}
+
1 +
fn run() {}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +
//! Modern and high-quality chess engine. For more information, see
+//!
+//! - [README] explaining about design and implementation goals
+//! - [Resources] for information on important papers, other engines and
+//! prominent research ideas
+//!
+//! [README]: https://github.com/kirillbobyrev/pabi/blob/main/README.md
+//! [Resources]: https://github.com/kirillbobyrev/pabi/wiki/Resources
+
+// TODO: Gradually move most of warnings to deny.
+#![warn(missing_docs, variant_size_differences)]
+// Rustc lints.
+#![warn(
+ absolute_paths_not_starting_with_crate,
+ keyword_idents,
+ macro_use_extern_crate,
+ trivial_casts,
+ trivial_numeric_casts,
+ unreachable_pub,
+ unused_extern_crates,
+ unused_import_braces,
+ unused_lifetimes,
+ unused_qualifications,
+ unused_results
+)]
+// Rustdoc lints.
+#![warn(
+ rustdoc::missing_doc_code_examples,
+ rustdoc::private_doc_tests,
+ rustdoc::missing_crate_level_docs,
+ rustdoc::broken_intra_doc_links,
+ rustdoc::invalid_codeblock_attributes,
+ rustdoc::invalid_html_tags,
+ rustdoc::invalid_rust_codeblocks,
+ rustdoc::bare_urls
+)]
+// Clippy lints.
+#![warn(
+ clippy::perf,
+ clippy::pedantic,
+ clippy::style,
+ clippy::nursery,
+ clippy::complexity,
+ clippy::correctness,
+ clippy::cargo
+)]
+
+// TODO: Re-export types for convenience.
+pub mod chess;
+pub mod evaluation;
+pub mod interface;
+
+pub mod util;
+
+use sysinfo::System;
+
+pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/version"));
+
+/// Prints information about the host system.
+pub fn print_system_info() {
+ let sys = System::new_all();
+ println!(
+ "System: {}",
+ System::long_os_version().unwrap_or_else(|| "UNKNOWN".to_string())
+ );
+ println!(
+ "System kernel version: {}",
+ System::kernel_version().unwrap_or_else(|| "UNKNOWN".to_string())
+ );
+ println!(
+ "Host name: {}",
+ System::host_name().unwrap_or_else(|| "UNKNOWN".to_string())
+ );
+ // Convert returned KB to GB.
+ println!("RAM: {} GB", sys.total_memory() / 1_000_000);
+ println!("Physical cores: {}", sys.physical_core_count().unwrap());
+}
+
//! Convenient utility functions for Pabi that can be used from benchmarks and
+//! public tests.
+
+// TODO: Docs.
+#[must_use]
+pub fn sanitize_fen(position: &str) -> String {
+ let mut position = position.trim();
+ for prefix in ["fen ", "epd "] {
+ if let Some(stripped) = position.strip_prefix(prefix) {
+ position = stripped;
+ }
+ }
+ match position.split_ascii_whitespace().count() {
+ 6 => position.to_string(),
+ // Patch EPD to validate produced FEN.
+ 4 => position.to_string() + " 0 1",
+ _ => unreachable!(),
+ }
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`Create a new empty ArrayVec
.
The maximum capacity is given by the generic parameter CAP
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 16>::new();\narray.push(1);\narray.push(2);\nassert_eq!(&array[..], &[1, 2]);\nassert_eq!(array.capacity(), 16);
Create a new empty ArrayVec
(const fn).
The maximum capacity is given by the generic parameter CAP
.
use arrayvec::ArrayVec;\n\nstatic ARRAY: ArrayVec<u8, 1024> = ArrayVec::new_const();
Return the number of elements in the ArrayVec
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\narray.pop();\nassert_eq!(array.len(), 2);
Returns whether the ArrayVec
is empty.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1]);\narray.pop();\nassert_eq!(array.is_empty(), true);
Return the capacity of the ArrayVec
.
use arrayvec::ArrayVec;\n\nlet array = ArrayVec::from([1, 2, 3]);\nassert_eq!(array.capacity(), 3);
Return true if the ArrayVec
is completely filled to its capacity, false otherwise.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 1>::new();\nassert!(!array.is_full());\narray.push(1);\nassert!(array.is_full());
Returns the capacity left in the ArrayVec
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\narray.pop();\nassert_eq!(array.remaining_capacity(), 1);
Push element
to the end of the vector.
Panics if the vector is already full.
\n\nuse arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\narray.push(1);\narray.push(2);\n\nassert_eq!(&array[..], &[1, 2]);
Push element
to the end of the vector.
Return Ok
if the push succeeds, or return an error if the vector\nis already full.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\nlet push1 = array.try_push(1);\nlet push2 = array.try_push(2);\n\nassert!(push1.is_ok());\nassert!(push2.is_ok());\n\nassert_eq!(&array[..], &[1, 2]);\n\nlet overflow = array.try_push(3);\n\nassert!(overflow.is_err());
Push element
to the end of the vector without checking the capacity.
It is up to the caller to ensure the capacity of the vector is\nsufficiently large.
\nThis method uses debug assertions to check that the arrayvec is not full.
\n\nuse arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\nif array.len() + 2 <= array.capacity() {\n unsafe {\n array.push_unchecked(1);\n array.push_unchecked(2);\n }\n}\n\nassert_eq!(&array[..], &[1, 2]);
Shortens the vector, keeping the first len
elements and dropping\nthe rest.
If len
is greater than the vector’s current length this has no\neffect.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3, 4, 5]);\narray.truncate(3);\nassert_eq!(&array[..], &[1, 2, 3]);\narray.truncate(4);\nassert_eq!(&array[..], &[1, 2, 3]);
Insert element
at position index
.
Shift up all elements after index
.
It is an error if the index is greater than the length or if the\narrayvec is full.
\nPanics if the array is full or the index
is out of bounds. See\ntry_insert
for fallible version.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\narray.insert(0, \"x\");\narray.insert(0, \"y\");\nassert_eq!(&array[..], &[\"y\", \"x\"]);\n
Insert element
at position index
.
Shift up all elements after index
; the index
must be less than\nor equal to the length.
Returns an error if vector is already at full capacity.
\nPanics index
is out of bounds.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\nassert!(array.try_insert(0, \"x\").is_ok());\nassert!(array.try_insert(0, \"y\").is_ok());\nassert!(array.try_insert(0, \"z\").is_err());\nassert_eq!(&array[..], &[\"y\", \"x\"]);\n
Remove the last element in the vector and return it.
\nReturn Some(
element )
if the vector is non-empty, else None
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::<_, 2>::new();\n\narray.push(1);\n\nassert_eq!(array.pop(), Some(1));\nassert_eq!(array.pop(), None);
Remove the element at index
and swap the last element into its place.
This operation is O(1).
\nReturn the element if the index is in bounds, else panic.
\nPanics if the index
is out of bounds.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\n\nassert_eq!(array.swap_remove(0), 1);\nassert_eq!(&array[..], &[3, 2]);\n\nassert_eq!(array.swap_remove(1), 2);\nassert_eq!(&array[..], &[3]);
Remove the element at index
and swap the last element into its place.
This is a checked version of .swap_remove
.
\nThis operation is O(1).
Return Some(
element )
if the index is in bounds, else None
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\n\nassert_eq!(array.swap_pop(0), Some(1));\nassert_eq!(&array[..], &[3, 2]);\n\nassert_eq!(array.swap_pop(10), None);
Remove the element at index
and shift down the following elements.
The index
must be strictly less than the length of the vector.
Panics if the index
is out of bounds.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\n\nlet removed_elt = array.remove(0);\nassert_eq!(removed_elt, 1);\nassert_eq!(&array[..], &[2, 3]);
Remove the element at index
and shift down the following elements.
This is a checked version of .remove(index)
. Returns None
if there\nis no element at index
. Otherwise, return the element inside Some
.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\n\nassert!(array.pop_at(0).is_some());\nassert_eq!(&array[..], &[2, 3]);\n\nassert!(array.pop_at(2).is_none());\nassert!(array.pop_at(10).is_none());
Retains only the elements specified by the predicate.
\nIn other words, remove all elements e
such that f(&mut e)
returns false.\nThis method operates in place and preserves the order of the retained\nelements.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3, 4]);\narray.retain(|x| *x & 1 != 0 );\nassert_eq!(&array[..], &[1, 3]);
Set the vector’s length without dropping or moving out elements
\nThis method is unsafe
because it changes the notion of the\nnumber of “valid” elements in the vector. Use with care.
This method uses debug assertions to check that length
is\nnot greater than the capacity.
Copy all elements from the slice and append to the ArrayVec
.
use arrayvec::ArrayVec;\n\nlet mut vec: ArrayVec<usize, 10> = ArrayVec::new();\nvec.push(1);\nvec.try_extend_from_slice(&[2, 3]).unwrap();\nassert_eq!(&vec[..], &[1, 2, 3]);
This method will return an error if the capacity left (see\nremaining_capacity
) is smaller then the length of the provided\nslice.
Create a draining iterator that removes the specified range in the vector\nand yields the removed items from start to end. The element range is\nremoved even if the iterator is not consumed until the end.
\nNote: It is unspecified how many elements are removed from the vector,\nif the Drain
value is leaked.
Panics if the starting point is greater than the end point or if\nthe end point is greater than the length of the vector.
\n\nuse arrayvec::ArrayVec;\n\nlet mut v1 = ArrayVec::from([1, 2, 3]);\nlet v2: ArrayVec<_, 3> = v1.drain(0..2).collect();\nassert_eq!(&v1[..], &[3]);\nassert_eq!(&v2[..], &[1, 2]);
Return the inner fixed size array, if it is full to its capacity.
\nReturn an Ok
value with the array if length equals capacity,\nreturn an Err
with self otherwise.
Return the inner fixed size array.
\nSafety:\nThis operation is safe if and only if length equals capacity.
\nReturns the ArrayVec, replacing the original with a new empty ArrayVec.
\n\nuse arrayvec::ArrayVec;\n\nlet mut v = ArrayVec::from([0, 1, 2, 3]);\nassert_eq!([0, 1, 2, 3], v.take().into_inner().unwrap());\nassert!(v.is_empty());
Return a mutable slice containing all elements of the vector.
\nReturn a raw mutable pointer to the vector’s buffer.
\nExtend the ArrayVec
with an iterator.
Panics if extending the vector exceeds its capacity.
\nExtend the ArrayVec
with an iterator.
Panics if extending the vector exceeds its capacity.
\nextend_one
)extend_one
)Create an ArrayVec
from an array.
use arrayvec::ArrayVec;\n\nlet mut array = ArrayVec::from([1, 2, 3]);\nassert_eq!(array.len(), 3);\nassert_eq!(array.capacity(), 3);
Create an ArrayVec
from an iterator.
Panics if the number of elements in the iterator exceeds the arrayvec’s capacity.
\nCreate an ArrayVec
from an iterator.
Panics if the number of elements in the iterator exceeds the arrayvec’s capacity.
\nIterate the ArrayVec
with each element by value.
The vector is consumed by this operation.
\n\nuse arrayvec::ArrayVec;\n\nfor elt in ArrayVec::from([1, 2, 3]) {\n // ...\n}
self
and other
) and is used by the <=
\noperator. Read moreTry to create an ArrayVec
from a slice. This will return an error if the slice was too big to\nfit.
use arrayvec::ArrayVec;\nuse std::convert::TryInto as _;\n\nlet array: ArrayVec<_, 4> = (&[1, 2, 3] as &[_]).try_into().unwrap();\nassert_eq!(array.len(), 3);\nassert_eq!(array.capacity(), 4);