From 6af74c7fbe3abc8d514ccbe91f6edfcc283d1173 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Sat, 14 Sep 2024 16:25:04 +0530 Subject: [PATCH] chore: some semblence of a movegen --- games/src/ataxx/position.rs | 12 ++ games/src/chess/bitboard.rs | 22 ++- games/src/chess/mod.rs | 2 + games/src/chess/movegen.rs | 334 ++++++++++++++++++++++++++++++++ games/src/chess/position.rs | 23 ++- games/src/chess/square.rs | 53 ++++- games/src/interface/position.rs | 3 + 7 files changed, 442 insertions(+), 7 deletions(-) create mode 100644 games/src/chess/movegen.rs diff --git a/games/src/ataxx/position.rs b/games/src/ataxx/position.rs index 8654f16..c821fd9 100644 --- a/games/src/ataxx/position.rs +++ b/games/src/ataxx/position.rs @@ -86,6 +86,18 @@ impl PositionType for Position { self.bitboards[piece as usize] } + fn side_to_move(&self) -> interface::Color { + self.side_to_move + } + + fn half_move_clock(&self) -> usize { + self.half_move_clock as usize + } + + fn ply_count(&self) -> usize { + self.ply_count as usize + } + fn hash(&self) -> Hash { self.checksum } diff --git a/games/src/chess/bitboard.rs b/games/src/chess/bitboard.rs index 9d4b46d..a379fae 100644 --- a/games/src/chess/bitboard.rs +++ b/games/src/chess/bitboard.rs @@ -13,7 +13,7 @@ use crate::interface::{bitboard_type, BitBoardType, RepresentableType}; -use super::Square; +use super::{Direction, Square}; bitboard_type! { /// A set of Squares implemented as a bitset where the `1 << sq.into()` bit @@ -28,7 +28,7 @@ bitboard_type! { // BitBoards containing the squares of the first file and the first rank. FirstFile = Self(0x0101010101010101); - FirstRank = Self(0xff00000000000000); + FirstRank = Self(0x00000000000000ff); } } @@ -36,6 +36,19 @@ impl BitBoard { pub fn new(raw: u64) -> BitBoard { BitBoard(raw) } + + pub fn shift(&self, dir: Direction) -> BitBoard { + match dir { + Direction::North => self.north(), + Direction::South => self.south(), + Direction::East => self.east(), + Direction::West => self.west(), + Direction::NorthEast => self.north().east(), + Direction::NorthWest => self.north().west(), + Direction::SouthEast => self.south().east(), + Direction::SouthWest => self.south().west(), + } + } } impl BitBoard { @@ -87,6 +100,11 @@ impl BitBoard { BitBoard(BitBoard::BETWEEN[sq_1 as usize][sq_2 as usize]) } + pub fn between2(sq_1: Square, sq_2: Square) -> BitBoard { + BitBoard(BitBoard::BETWEEN[sq_1 as usize][sq_2 as usize]) + | BitBoard::from(sq_2) + } + #[rustfmt::skip] const BETWEEN: [[u64; Square::N]; Square::N] = [ [ 0x0101010101010100, 0x0000000000000000, 0x0000000000000002, 0x0000000000000006, 0x000000000000000e, 0x000000000000001e, 0x000000000000003e, 0x000000000000007e, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000100, 0x0000000000000000, 0x0000000000000200, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000010100, 0x0000000000000000, 0x0000000000000000, 0x0000000000040200, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000001010100, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000008040200, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000101010100, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000001008040200, 0x0000000000000000, 0x0000000000000000, 0x0000010101010100, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000201008040200, 0x0000000000000000, 0x0001010101010100, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0040201008040200 ], diff --git a/games/src/chess/mod.rs b/games/src/chess/mod.rs index b3ccb68..0073cdd 100644 --- a/games/src/chess/mod.rs +++ b/games/src/chess/mod.rs @@ -3,6 +3,8 @@ pub mod castling; pub mod moves; pub mod zobrist; +mod movegen; + // Non-namespaced modules. mod bitboard; mod color; diff --git a/games/src/chess/movegen.rs b/games/src/chess/movegen.rs new file mode 100644 index 0000000..a7697cb --- /dev/null +++ b/games/src/chess/movegen.rs @@ -0,0 +1,334 @@ +use crate::interface::{ + BitBoardType, ColoredPieceType, MoveStore, PositionType, +}; + +use super::{ + moves, BitBoard, ColoredPiece, Direction, Move, MoveFlag, Piece, Position, + Square, +}; + +pub struct MoveGenerationInfo<'a> { + position: &'a Position, + + king: Square, + + friends: BitBoard, + enemies: BitBoard, + blocker: BitBoard, + + checkers: BitBoard, + + territory: BitBoard, + + checkmask: BitBoard, + + pinmask_l: BitBoard, + pinmask_d: BitBoard, +} + +impl<'a> MoveGenerationInfo<'a> { + fn serialize>( + &self, + source: Square, + targets: BitBoard, + movelist: &mut ML, + ) { + let targets = targets & self.checkmask & self.territory; + for target in targets { + movelist.push(Move::new(source, target, MoveFlag::Normal)) + } + } + + fn serialize_towards>( + &self, + offset: Direction, + flag: MoveFlag, + targets: BitBoard, + movelist: &mut ML, + ) { + let targets = targets & self.checkmask & self.territory; + for target in targets { + movelist.push(Move::new(target.shift(-offset), target, flag)) + } + } + + fn serialize_promotions>( + &self, + offset: Direction, + targets: BitBoard, + movelist: &mut ML, + ) { + let targets = targets & self.checkmask & self.territory; + for target in targets { + movelist.push(Move::new( + target.shift(-offset), + target, + MoveFlag::NPromotion, + )); + movelist.push(Move::new( + target.shift(-offset), + target, + MoveFlag::BPromotion, + )); + movelist.push(Move::new( + target.shift(-offset), + target, + MoveFlag::RPromotion, + )); + movelist.push(Move::new( + target.shift(-offset), + target, + MoveFlag::QPromotion, + )); + } + } +} + +impl<'a> MoveGenerationInfo<'a> { + fn generate_checkers(position: &Position, king: Square) -> BitBoard { + let stm = position.side_to_move(); + let xtm = !stm; + + let friends = position.color_bb(stm); + let enemies = position.color_bb(xtm); + let blocker = friends | enemies; + + let p = position.piece_bb(Piece::Pawn); + let n = position.piece_bb(Piece::Knight); + let b = position.piece_bb(Piece::Bishop); + let r = position.piece_bb(Piece::Rook); + let q = position.piece_bb(Piece::Queen); + + let checking_p = p & moves::pawn_attacks(king, stm); + let checking_n = n & moves::knight(king); + let checking_b = (b | q) & moves::bishop(king, blocker); + let checking_r = (r | q) & moves::rook(king, blocker); + + (checking_p | checking_n | checking_b | checking_r) & enemies + } + + fn generate_checkmask( + position: &Position, + checkers: BitBoard, + king: Square, + ) -> BitBoard { + match checkers.len() { + 0 => BitBoard::UNIVERSE, + 2 => BitBoard::EMPTY, + _ => { + let checker_sq = + unsafe { checkers.clone().next().unwrap_unchecked() }; + + let checker_pc = unsafe { + position.at(checker_sq).unwrap_unchecked().piece() + }; + + if checker_pc == Piece::Pawn || checker_pc == Piece::Knight { + checkers + } else { + BitBoard::between2(king, checker_sq) + } + } + } + } + + fn generate_pinmask( + position: &Position, + pinners: BitBoard, + king: Square, + ) -> BitBoard { + let friends = position.color_bb(position.side_to_move()); + let mut pinmask = BitBoard::EMPTY; + + for possible_pinner in pinners { + let possible_pin = BitBoard::between2(king, possible_pinner); + if (friends & possible_pin).len() == 1 { + pinmask |= possible_pin; + } + } + + pinmask + } + + fn generate_pinmasks( + position: &Position, + king: Square, + ) -> (BitBoard, BitBoard) { + let enemies = position.color_bb(!position.side_to_move()); + + let b = enemies & position.piece_bb(Piece::Bishop); + let r = enemies & position.piece_bb(Piece::Rook); + let q = enemies & position.piece_bb(Piece::Queen); + + ( + Self::generate_pinmask( + position, + (r | q) & moves::rook(king, enemies), + king, + ), + Self::generate_pinmask( + position, + (b | q) & moves::bishop(king, enemies), + king, + ), + ) + } +} + +impl<'a> MoveGenerationInfo<'a> { + fn pawn_moves>(&self, movelist: &mut ML) { + let up = Direction::up(self.position.side_to_move()); + let ue = up + Direction::East; + let uw = up + Direction::West; + + let pawns = self.position.piece_bb(Piece::Pawn) & self.friends; + + { + let attackers = pawns - self.pinmask_l; + + let pinned_attackers = attackers & self.pinmask_d; + let unpinned_attackers = attackers ^ pinned_attackers; + + let pinned_attacks_east = pinned_attackers.shift(ue); + let pinned_attacks_west = pinned_attackers.shift(uw); + let unpinned_attacks_east = unpinned_attackers.shift(ue); + let unpinned_attacks_west = unpinned_attackers.shift(uw); + + let attacks_east = + (pinned_attacks_east & self.pinmask_d) | unpinned_attacks_east; + let attacks_west = + (pinned_attacks_west & self.pinmask_d) | unpinned_attacks_west; + + self.serialize_towards( + ue, + MoveFlag::Normal, + attacks_east & self.enemies, + movelist, + ); + self.serialize_towards( + uw, + MoveFlag::Normal, + attacks_west & self.enemies, + movelist, + ); + } + + { + let pushers = pawns - self.pinmask_d; + + let pinned_pushers = pushers & self.pinmask_l; + let unpinned_pushers = pushers ^ pinned_pushers; + + let pinned_single_push = pinned_pushers.shift(up) - self.blocker; + let unpinned_single_push = + unpinned_pushers.shift(up) - self.blocker; + + let single_pushes = + (pinned_single_push & self.pinmask_l) | unpinned_single_push; + + self.serialize_towards( + up, + MoveFlag::Normal, + single_pushes, + movelist, + ); + } + } + + fn knight_moves>(&self, movelist: &mut ML) { + let knights = (self.position.piece_bb(Piece::Knight) & self.friends) + - (self.pinmask_l | self.pinmask_d); + for knight in knights { + self.serialize(knight, moves::knight(knight), movelist) + } + } + + fn bishop_moves>(&self, movelist: &mut ML) { + let bishops = ((self.position.piece_bb(Piece::Bishop) + | self.position.piece_bb(Piece::Queen)) + & self.friends) + - self.pinmask_l; + + let pinned = bishops & self.pinmask_d; + for bishop in pinned { + self.serialize( + bishop, + moves::bishop(bishop, self.blocker) & self.pinmask_d, + movelist, + ) + } + + let unpinned = bishops ^ pinned; + for bishop in unpinned { + self.serialize( + bishop, + moves::bishop(bishop, self.blocker), + movelist, + ) + } + } + + fn rook_moves>(&self, movelist: &mut ML) { + let rooks = ((self.position.piece_bb(Piece::Rook) + | self.position.piece_bb(Piece::Queen)) + & self.friends) + - self.pinmask_d; + + let pinned = rooks & self.pinmask_l; + for rook in pinned { + self.serialize( + rook, + moves::rook(rook, self.blocker) & self.pinmask_l, + movelist, + ) + } + + let unpinned = rooks ^ pinned; + for rook in unpinned { + self.serialize(rook, moves::rook(rook, self.blocker), movelist) + } + } +} + +impl<'a> MoveGenerationInfo<'a> { + pub fn new(position: &'a Position) -> Self { + let king = unsafe { + position + .colored_piece_bb(ColoredPiece::new( + Piece::King, + position.side_to_move(), + )) + .next() + .unwrap_unchecked() + }; + let checkers = Self::generate_checkers(position, king); + let checkmask = Self::generate_checkmask(position, checkers, king); + let pinmasks = Self::generate_pinmasks(position, king); + + let friends = position.color_bb(position.side_to_move()); + let enemies = position.color_bb(position.side_to_move()); + let blocker = friends | enemies; + + let territory = !position.color_bb(position.side_to_move()); + + Self { + position, + king, + friends, + enemies, + blocker, + checkers, + checkmask, + territory, + pinmask_l: pinmasks.0, + pinmask_d: pinmasks.1, + } + } + + pub fn generate_moves_into>(&self, movelist: &mut ML) { + self.pawn_moves(movelist); + self.knight_moves(movelist); + self.bishop_moves(movelist); + self.rook_moves(movelist); + } +} diff --git a/games/src/chess/position.rs b/games/src/chess/position.rs index d3e302e..f701b9d 100644 --- a/games/src/chess/position.rs +++ b/games/src/chess/position.rs @@ -33,6 +33,7 @@ use crate::chess::{ }; use crate::interface::MoveStore; +use super::movegen; use super::MoveFlag; /// Position represents the snapshot of an Ataxx Board, the state of the an @@ -98,6 +99,18 @@ impl PositionType for Position { self.piece_bb(piece.piece()) & self.color_bb(piece.color()) } + fn side_to_move(&self) -> interface::Color { + self.side_to_move + } + + fn half_move_clock(&self) -> usize { + self.half_move_clock as usize + } + + fn ply_count(&self) -> usize { + self.ply_count as usize + } + fn hash(&self) -> Hash { self.checksum } @@ -170,8 +183,10 @@ impl PositionType for Position { T: MoveStore, >( &self, - _movelist: &mut T, + movelist: &mut T, ) { + let info = movegen::MoveGenerationInfo::new(self); + info.generate_moves_into(movelist); } } @@ -198,14 +213,14 @@ impl FromStr for Position { fn from_str(s: &str) -> Result { let parts = s.split(' ').collect::>(); - if parts.len() != 4 { + if parts.len() != 6 { return Err(PositionParseError::TooManyFields(parts.len())); } let pos = parts[0]; let stm = parts[1]; - let hmc = parts[2]; - let fmc = parts[3]; + let hmc = parts[4]; + let fmc = parts[5]; let mut position = Position { color_bbs: [BitBoard::EMPTY; Color::N], diff --git a/games/src/chess/square.rs b/games/src/chess/square.rs index d7ce76c..6bc4ded 100644 --- a/games/src/chess/square.rs +++ b/games/src/chess/square.rs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt; +use std::{fmt, ops}; use crate::interface::{representable_type, RepresentableType, SquareType}; @@ -52,6 +52,10 @@ impl Square { } } + pub fn shift(self, dir: Direction) -> Square { + unsafe { Square::unsafe_from((self as i8 + dir as i8) as u8) } + } + pub fn diagonal(self) -> usize { 14 - self.rank() as usize - self.file() as usize } @@ -75,3 +79,50 @@ representable_type!( Fifth "5", Sixth "6", Seventh "7", Eighth "8", } ); + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Direction { + North = 8, + South = -8, + + East = 1, + West = -1, + + NorthEast = 8 + 1, + NorthWest = 8 - 1, + SouthEast = -8 + 1, + SouthWest = -8 - 1, +} + +impl Direction { + pub fn up(stm: Color) -> Direction { + match stm { + Color::White => Direction::North, + Color::Black => Direction::South, + } + } +} + +impl ops::Add for Direction { + type Output = Direction; + + fn add(self, rhs: Self) -> Self::Output { + unsafe { std::mem::transmute_copy(&(self as i8 + rhs as i8)) } + } +} + +impl ops::Sub for Direction { + type Output = Direction; + + fn sub(self, rhs: Self) -> Self::Output { + unsafe { std::mem::transmute_copy(&(self as i8 - rhs as i8)) } + } +} + +impl ops::Neg for Direction { + type Output = Direction; + + fn neg(self) -> Self::Output { + unsafe { std::mem::transmute_copy(&(-(self as i8))) } + } +} diff --git a/games/src/interface/position.rs b/games/src/interface/position.rs index 7cf3ca3..4e519a0 100644 --- a/games/src/interface/position.rs +++ b/games/src/interface/position.rs @@ -49,6 +49,9 @@ where #[must_use] fn colored_piece_bb(&self, piece: Self::ColoredPiece) -> Self::BitBoard; + fn side_to_move(&self) -> Color; + fn half_move_clock(&self) -> usize; + fn ply_count(&self) -> usize; /// Returns a semi-unique checksum of the current Position. #[must_use] fn hash(&self) -> Hash;