aboutsummaryrefslogtreecommitdiff
path: root/src/board
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2023-02-23 14:49:06 +0300
committereug-vs <eugene@eug-vs.xyz>2023-02-23 14:49:06 +0300
commit573d674bffd9275ea98abc119e747e74e2abc941 (patch)
tree4ee83259209e769de133369d53677dafc3a56f56 /src/board
parentc299065952155a94c51fcf5398525dc858fdf8b5 (diff)
downloadchessnost-573d674bffd9275ea98abc119e747e74e2abc941.tar.gz
refactor: return move generation to board module
Diffstat (limited to 'src/board')
-rw-r--r--src/board/mod.rs1
-rw-r--r--src/board/move_generation.rs200
2 files changed, 201 insertions, 0 deletions
diff --git a/src/board/mod.rs b/src/board/mod.rs
index aab3136..ff01ddc 100644
--- a/src/board/mod.rs
+++ b/src/board/mod.rs
@@ -4,6 +4,7 @@ use self::{zobrist::{ZobristSeed, Zobrist}, piece::Piece, color::Color};
pub mod io;
pub mod color;
pub mod piece;
+pub mod move_generation;
mod zobrist;
#[derive(Debug, Clone, Copy)]
diff --git a/src/board/move_generation.rs b/src/board/move_generation.rs
new file mode 100644
index 0000000..77714c8
--- /dev/null
+++ b/src/board/move_generation.rs
@@ -0,0 +1,200 @@
+use crate::{moves::{Move, MoveKind}, board::{color::Color, piece::Piece, CastlingSide}, bitboard::BitboardFns, square::Square};
+
+use super::Board;
+
+
+impl Board {
+ pub fn generate_pseudolegal_moves(&self) -> Vec<Move> {
+ let mut result = Vec::new();
+ result.append(&mut self.generate_moves_core(true));
+ result.append(&mut self.generate_moves_core(false));
+ result
+ }
+
+ fn generate_moves_core(&self, tactical_only: bool) -> Vec<Move> {
+ let color = self.color();
+ let player_pieces = self.pieces_by_color(color);
+ let mut moves = Vec::with_capacity(256);
+ let empty = self.empty();
+
+ let targets = if tactical_only {
+ self.color_occupancy(color.flip()) ^ match color {
+ // Exclude opponent king because we can't capture it
+ Color::White => self.piece_sets[Piece::KingBlack as usize],
+ Color::Black => self.piece_sets[Piece::King as usize],
+ }
+ } else {
+ empty
+ };
+
+ let kind = if tactical_only {
+ MoveKind::Capture
+ } else {
+ MoveKind::Quiet
+ };
+
+ for (piece_id, piece) in player_pieces.iter().enumerate() {
+ for source in piece.serialize() {
+ let piece_type = Piece::from(piece_id);
+ let move_targets = match piece_type {
+ Piece::Bishop => self.attacks.bishop(self.occupancy, source),
+ Piece::Rook => self.attacks.rook(self.occupancy, source),
+ Piece::Queen => self.attacks.queen(self.occupancy, source),
+ Piece::Knight => self.attacks.knight[source as usize],
+ Piece::King => self.attacks.king[source as usize],
+ Piece::Pawn => if tactical_only {
+ self.attacks.pawn[color as usize][source as usize]
+ } else {
+ self.attacks.pawn_pushes[color as usize][source as usize]
+ },
+ _ => panic!("Incorrect piece type"),
+ };
+
+ // Generalized attack/move pattern for all pieces
+ for target in (move_targets & targets).serialize() {
+ moves.push(Move { source, target, kind });
+
+ // Promotions
+ if piece_type == Piece::Pawn {
+ if target.rank() == 7 {
+ for promo_type in [Piece::Bishop, Piece::Knight, Piece::Rook, Piece::Queen] {
+ moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
+ }
+ } else if target.rank() == 0 {
+ for promo_type in [Piece::BishopBlack, Piece::KnightBlack, Piece::RookBlack, Piece::QueenBlack] {
+ moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
+ }
+ }
+ }
+ }
+
+ // En Passant
+ if piece_type == Piece::Pawn && tactical_only {
+ for target in (move_targets & self.ep_bitboard()).serialize() {
+ moves.push(Move { source, target, kind: MoveKind::EnPassant });
+ }
+ }
+ }
+ }
+
+ if !tactical_only {
+ // Double Pushes
+ // Make sure no blocking piece is standing in front of the pawns
+ // that are potential double-push sources
+ let able_to_double_push_mask = match color {
+ Color::White => empty >> 8,
+ Color::Black => empty << 8,
+ };
+ for source in (player_pieces[Piece::Pawn as usize] & able_to_double_push_mask).serialize() {
+ for target in (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).serialize() {
+ moves.push(Move { source, target, kind: MoveKind::DoublePush });
+ };
+ }
+
+ // Castling
+ let king_home_position = match color {
+ Color::White => Square::E1,
+ Color::Black => Square::E8,
+ };
+ if player_pieces[Piece::King as usize].bitscan() == king_home_position {
+ for rook_square in player_pieces[Piece::Rook as usize]
+ .serialize()
+ .iter()
+ .filter(|rook_square| rook_square.rank() == king_home_position.rank())
+ {
+ match rook_square.file() {
+ 0 => {
+ let all_empty = [
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ king_home_position.west_one().west_one().west_one(),
+
+ ].iter().all(|square| empty & square.to_bitboard() > 0);
+
+ let any_checks = [
+ king_home_position,
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
+ moves.push(Move {
+ source: king_home_position,
+ target: king_home_position.west_one().west_one(),
+ kind: MoveKind::Castle,
+ })
+ }
+ },
+ 7 => {
+ let all_empty = [
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+
+ ].iter().all(|square| empty & square.to_bitboard() > 0);
+
+ let any_checks = [
+ king_home_position,
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+ ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
+ moves.push(Move {
+ source: king_home_position,
+ target: king_home_position.east_one().east_one(),
+ kind: MoveKind::Castle,
+ })
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ moves
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::board::{Board, io::IO};
+
+ use super::*;
+
+ #[test]
+ fn generate_pseudolegal_moves_starting_position() {
+ let mut board = Board::new();
+ let moves = board.generate_pseudolegal_moves();
+ board.ply += 1;
+ let black_moves = board.generate_pseudolegal_moves();
+
+ assert_eq!(moves.len(), 20);
+ assert_eq!(black_moves.len(), 20);
+
+ for mov in moves {
+ mov.print();
+ }
+ }
+
+
+ #[test]
+ fn moved_king_castle() {
+ let fen = String::from("4k2r/ppp1n3/8/4R1Pp/5P2/q1P5/P1P1BP2/1K1R4 b - - 2 22");
+ let mut board = Board::from_FEN(fen);
+ board.ply += 1;
+
+ // Shuffle kings around, returning to the same position
+ board.make_move(Move { source: Square::E8, target: Square::F8, kind: MoveKind::Quiet });
+ board.make_move(Move { source: Square::B1, target: Square::A1, kind: MoveKind::Quiet });
+ board.make_move(Move { source: Square::F8, target: Square::E8, kind: MoveKind::Quiet });
+ board.make_move(Move { source: Square::A1, target: Square::B1, kind: MoveKind::Quiet });
+ board.print();
+
+ let moves = board.generate_pseudolegal_moves();
+
+ let castle = moves.iter().find(|m| **m == Move { source: Square::E8, target: Square::G8, kind: MoveKind::Castle });
+ println!("{:?}", board.castling_rights);
+ assert!(castle.is_none(), "Castle should not be allowed after king has moved");
+ }
+}
+