From 4a54d8b8772db7d7404fe583e0bc8715cd34cfd3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 31 Dec 2023 16:33:47 -0800 Subject: [PATCH] [board] Implement KingMoveGenerator Generate moves for kings. This struct is trying out a different approach to iterators where the type itself isn't one, but it has an iter() method that returns an iterator over all the move lists. This struct also builds all the moves up front, in new() before the new instance is returned. --- board/src/moves/king.rs | 249 ++++++++++++++++++++++++++++++ board/src/moves/mod.rs | 1 + board/src/moves/move_generator.rs | 2 + board/src/tests.rs | 8 + 4 files changed, 260 insertions(+) create mode 100644 board/src/moves/king.rs diff --git a/board/src/moves/king.rs b/board/src/moves/king.rs new file mode 100644 index 0000000..0884a86 --- /dev/null +++ b/board/src/moves/king.rs @@ -0,0 +1,249 @@ +// Eryn Wells + +//! Declares the KingMoveGenerator type. This struct is responsible for +//! generating the possible moves for the king in the given position. + +use crate::bitboard::BitBoard; +use crate::piece::{Color, Piece}; +use crate::{Move, Position}; + +enum MoveList { + Pushes = 0, + Captures = 1, +} + +pub(super) struct KingMoveGenerator<'a> { + position: &'a Position, + color: Color, + + did_populate_move_lists: bool, + + pushes: BitBoard, + captures: BitBoard, + + move_lists: [Vec; 2], +} + +impl<'a> KingMoveGenerator<'a> { + pub(super) fn new(position: &Position, color: Color) -> KingMoveGenerator { + let mut generator = KingMoveGenerator { + position, + color, + did_populate_move_lists: false, + pushes: BitBoard::empty(), + captures: BitBoard::empty(), + move_lists: [Vec::new(), Vec::new()], + }; + + generator.generate_moves(); + + generator + } + + fn iter(&self) -> impl Iterator + '_ { + self.move_lists.iter().flatten().cloned() + } + + fn bitboard(&self) -> BitBoard { + self.quiet_moves_bitboard() | self.capture_moves_bitboard() + } + + fn quiet_moves_iter(&self) -> impl Iterator + '_ { + self.pushes_move_list().iter().cloned() + } + + fn quiet_moves_bitboard(&self) -> BitBoard { + self.pushes + } + + fn capture_moves_bitboard(&self) -> BitBoard { + self.captures + } + + fn capture_moves_iter(&self) -> impl Iterator + '_ { + self.captures_move_list().iter().cloned() + } + + fn generate_moves(&mut self) { + self.generate_bitboards(); + self.populate_move_lists(); + self.did_populate_move_lists = true; + } + + fn pushes_move_list(&self) -> &Vec { + &self.move_lists[MoveList::Pushes as usize] + } + + fn pushes_move_list_mut(&mut self) -> &mut Vec { + &mut self.move_lists[MoveList::Pushes as usize] + } + + fn captures_move_list(&self) -> &Vec { + &self.move_lists[MoveList::Captures as usize] + } + + fn captures_move_list_mut(&mut self) -> &mut Vec { + &mut self.move_lists[MoveList::Captures as usize] + } + + fn generate_bitboards(&mut self) { + let mut king = self + .position + .bitboard_for_piece(Piece::king(self.color)) + .clone(); + let mut all_moves = king.shift_east_one() | king.shift_west_one(); + king |= all_moves; + all_moves |= king.shift_north_one() | king.shift_south_one(); + + let empty_squares = self.position.empty_squares(); + let opposing_pieces = self.position.bitboard_for_color(self.color.other()); + + self.pushes = all_moves & empty_squares; + self.captures = all_moves & opposing_pieces; + + // TODO: Handle checks. Prevent moving a king to a square attacked by a + // piece of the opposite color. + } + + fn populate_move_lists(&mut self) { + let king = Piece::king(self.color); + + let from_sq = self + .position + .bitboard_for_piece(king) + .occupied_squares() + .next() + .unwrap(); + + for to_sq in self.pushes.occupied_squares() { + self.pushes_move_list_mut() + .push(Move::new(king, from_sq, to_sq)); + } + + for to_sq in self.captures.occupied_squares() { + let captured_piece = self.position.piece_on_square(to_sq).unwrap(); + self.captures_move_list_mut() + .push(Move::new(king, from_sq, to_sq).capturing(captured_piece)); + } + + #[allow(unused_variables)] + if let Some(castle) = self.king_side_castle() { + // TODO: Add castle to the pushes move list. + } + + #[allow(unused_variables)] + if let Some(castle) = self.queen_side_castle() { + // TODO: Add castle to the pushes move list. + } + } + + fn king_side_castle(&self) -> Option { + None + } + + fn queen_side_castle(&self) -> Option { + None + } +} + +impl<'pos> Iterator for KingMoveGenerator<'pos> { + type Item = Move; + + fn next(&mut self) -> Option { + if !self.did_populate_move_lists { + self.generate_moves(); + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Position, Square}; + use std::collections::HashSet; + + #[test] + fn one_king() { + let mut pos = Position::empty(); + pos.place_piece(&Piece::king(Color::White), &Square::e4()) + .expect("Failed to place king on e4"); + + let generator = KingMoveGenerator::new(&pos, Color::White); + + let bb = generator.bitboard(); + assert_eq!( + bb, + BitBoard::new( + 0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000 + ) + ); + + let expected_moves = [ + Move::new(Piece::king(Color::White), Square::e4(), Square::d5()), + Move::new(Piece::king(Color::White), Square::e4(), Square::e5()), + Move::new(Piece::king(Color::White), Square::e4(), Square::f5()), + Move::new(Piece::king(Color::White), Square::e4(), Square::f4()), + Move::new(Piece::king(Color::White), Square::e4(), Square::f3()), + Move::new(Piece::king(Color::White), Square::e4(), Square::e3()), + Move::new(Piece::king(Color::White), Square::e4(), Square::d3()), + Move::new(Piece::king(Color::White), Square::e4(), Square::d4()), + ]; + + let mut generated_moves: HashSet = generator.iter().collect(); + + for ex_move in expected_moves { + assert!( + generated_moves.remove(&ex_move), + "{:#?} was not generated", + &ex_move + ); + } + + assert!( + generated_moves.is_empty(), + "Moves unexpectedly present: {:#?}", + generated_moves + ); + } + + #[test] + fn one_king_corner() { + let mut pos = Position::empty(); + pos.place_piece(&Piece::king(Color::White), &Square::a1()) + .expect("Failed to place king on a1"); + + let generator = KingMoveGenerator::new(&pos, Color::White); + + let bb = generator.bitboard(); + assert_eq!( + bb, + BitBoard::new( + 0b00000000_00000000_00000000_00000000_00000000_00000000_00000011_00000010 + ) + ); + + let expected_moves = [ + Move::new(Piece::king(Color::White), Square::a1(), Square::a2()), + Move::new(Piece::king(Color::White), Square::a1(), Square::b1()), + Move::new(Piece::king(Color::White), Square::a1(), Square::b2()), + ]; + + let mut generated_moves: HashSet = generator.iter().collect(); + + for ex_move in expected_moves { + assert!( + generated_moves.remove(&ex_move), + "{:#?} was not generated", + &ex_move + ); + } + + assert!( + generated_moves.is_empty(), + "Moves unexpectedly present: {:#?}", + generated_moves + ); + } +} diff --git a/board/src/moves/mod.rs b/board/src/moves/mod.rs index b0fa3a9..1745e63 100644 --- a/board/src/moves/mod.rs +++ b/board/src/moves/mod.rs @@ -1,5 +1,6 @@ // Eryn Wells +mod king; mod r#move; mod move_generator; mod pawn; diff --git a/board/src/moves/move_generator.rs b/board/src/moves/move_generator.rs index c7d5800..e93d46b 100644 --- a/board/src/moves/move_generator.rs +++ b/board/src/moves/move_generator.rs @@ -1,5 +1,6 @@ // Eryn Wells +use super::king::KingMoveGenerator; use super::pawn::PawnMoveGenerator; use super::Move; use crate::piece::Color; @@ -19,6 +20,7 @@ impl<'a> Moves<'a> { color, iterators: vec![ Box::new(PawnMoveGenerator::new(position, color)), + Box::new(KingMoveGenerator::new(position, color)), ], index: 0, } diff --git a/board/src/tests.rs b/board/src/tests.rs index 5924d5b..35ac163 100644 --- a/board/src/tests.rs +++ b/board/src/tests.rs @@ -14,10 +14,18 @@ macro_rules! sq_constructor { } impl Square { + sq_constructor!(a1); + sq_constructor!(a2); + sq_constructor!(b1); + sq_constructor!(b2); + sq_constructor!(d3); + sq_constructor!(d4); sq_constructor!(d5); sq_constructor!(e2); sq_constructor!(e3); sq_constructor!(e4); sq_constructor!(e5); + sq_constructor!(f3); + sq_constructor!(f4); sq_constructor!(f5); }