[board] Replace implementations of the king and knight move generators with a more generic version

While implementing these move generators, I realized I was duplicating a lot of code. So, I started thinking about how to do less of that. I create a MoveGeneratorInternal trait that these move generators implement. This trait provides default implementations of several methods.

I also discovered that I needed a way to keep track of move sets per piece for each kind (shape) of piece. The new move generators, and the generators still to come, can now keep track of moves per piece separately in MoveSet instances.
This commit is contained in:
Eryn Wells 2024-01-06 17:01:16 -08:00
parent bcab682bb1
commit e2d93b8c3c
3 changed files with 151 additions and 210 deletions

View file

@ -3,158 +3,66 @@
//! 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};
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::{
bitboard::BitBoard,
piece::{Color, Piece, PlacedPiece},
Move, Position, Square,
};
use std::collections::BTreeMap;
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<Move>; 2],
}
move_generator_declaration!(KingMoveGenerator, struct);
move_generator_declaration!(KingMoveGenerator, new);
move_generator_declaration!(KingMoveGenerator, getters);
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
#[allow(unused_variables)]
fn king_side_castle(position: &Position) -> Option<Move> {
// TODO: Implement king side castle.
None
}
pub(super) fn iter(&self) -> impl Iterator<Item = &Move> + '_ {
self.move_lists.iter().flatten()
#[allow(unused_variables)]
fn queen_side_castle(position: &Position) -> Option<Move> {
// TODO: Implement queen side castle.
None
}
}
impl<'pos> MoveGeneratorInternal for KingMoveGenerator<'pos> {
fn piece(color: Color) -> Piece {
Piece::king(color)
}
fn bitboard(&self) -> BitBoard {
self.quiet_moves_bitboard() | self.capture_moves_bitboard()
}
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
let piece = placed_piece.piece();
let color = piece.color();
let square = placed_piece.square();
fn quiet_moves_iter(&self) -> impl Iterator<Item = &Move> + '_ {
self.pushes_move_list().iter()
}
let empty_squares = position.empty_squares();
let opposing_pieces = position.bitboard_for_color(color.other());
fn quiet_moves_bitboard(&self) -> BitBoard {
self.pushes
}
fn capture_moves_bitboard(&self) -> BitBoard {
self.captures
}
fn capture_moves_iter(&self) -> impl Iterator<Item = &Move> + '_ {
self.captures_move_list().iter()
}
fn generate_moves(&mut self) {
self.generate_bitboards();
self.populate_move_lists();
self.did_populate_move_lists = true;
}
fn pushes_move_list(&self) -> &Vec<Move> {
&self.move_lists[MoveList::Pushes as usize]
}
fn pushes_move_list_mut(&mut self) -> &mut Vec<Move> {
&mut self.move_lists[MoveList::Pushes as usize]
}
fn captures_move_list(&self) -> &Vec<Move> {
&self.move_lists[MoveList::Captures as usize]
}
fn captures_move_list_mut(&mut self) -> &mut Vec<Move> {
&mut self.move_lists[MoveList::Captures as usize]
}
fn generate_bitboards(&mut self) {
let king = self
.position
.bitboard_for_piece(Piece::king(self.color))
.occupied_squares()
.next()
.unwrap();
let all_moves = BitBoard::king_moves(king);
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;
let all_moves = BitBoard::king_moves(square);
let quiet_moves_bb = all_moves & empty_squares;
let capture_moves_bb = 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 map_to_move = |sq| Move::new(piece, square, sq);
let from_sq = self
.position
.bitboard_for_piece(king)
let king_side_castle = Self::king_side_castle(position);
let queen_side_castle = Self::queen_side_castle(position);
let quiet_moves = quiet_moves_bb
.occupied_squares()
.next()
.unwrap();
.map(map_to_move)
.chain(king_side_castle.iter().cloned())
.chain(queen_side_castle.iter().cloned());
let capture_moves = capture_moves_bb.occupied_squares().map(map_to_move);
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<Move> {
None
}
fn queen_side_castle(&self) -> Option<Move> {
None
}
}
impl<'pos> Iterator for KingMoveGenerator<'pos> {
type Item = Move;
fn next(&mut self) -> Option<Self::Item> {
if !self.did_populate_move_lists {
self.generate_moves();
}
None
MoveSet::new(placed_piece)
.quiet_moves(quiet_moves_bb, quiet_moves)
.capture_moves(capture_moves_bb, capture_moves)
}
}

View file

@ -1,90 +1,40 @@
// Eryn Wells <eryn@erynwells.me>
use std::io::empty;
use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
use crate::{
bitboard::BitBoard,
piece::{Color, Piece},
piece::{Color, Piece, PlacedPiece},
Move, Position,
};
enum MoveList {
Quiet,
Captures,
}
move_generator_declaration!(KnightMoveGenerator, struct);
move_generator_declaration!(KnightMoveGenerator, new);
move_generator_declaration!(KnightMoveGenerator, getters);
pub(super) struct KnightMoveGenerator<'pos> {
position: &'pos Position,
color: Color,
bitboards: [BitBoard; 2],
move_lists: [Vec<Move>; 2],
}
impl<'pos> KnightMoveGenerator<'pos> {
pub(super) fn new(position: &'pos Position, color: Color) -> KnightMoveGenerator<'pos> {
let mut generator = KnightMoveGenerator {
position,
color,
bitboards: [BitBoard::empty(), BitBoard::empty()],
move_lists: [Vec::new(), Vec::new()],
};
generator.generate_moves();
generator
impl<'pos> MoveGeneratorInternal for KnightMoveGenerator<'pos> {
fn piece(color: Color) -> Piece {
Piece::knight(color)
}
pub(super) fn iter(&self) -> impl Iterator<Item = &Move> + '_ {
self.move_lists.iter().flatten()
}
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
let opposing_pieces = position.bitboard_for_color(placed_piece.piece().color().other());
let empty_squares = position.empty_squares();
let knight_moves = BitBoard::knight_moves(placed_piece.square());
fn quiet_moves_list_mut(&mut self) -> &mut Vec<Move> {
&mut self.move_lists[MoveList::Quiet as usize]
}
let quiet_moves_bb = knight_moves & empty_squares;
let capture_moves_bb = knight_moves & opposing_pieces;
fn capture_moves_list_mut(&mut self) -> &mut Vec<Move> {
&mut self.move_lists[MoveList::Captures as usize]
}
let quiet_moves = quiet_moves_bb
.occupied_squares()
.map(|to_sq| Move::new(placed_piece.piece(), placed_piece.square(), to_sq));
let capture_moves = capture_moves_bb.occupied_squares().map(|to_sq| {
let captured_piece = position.piece_on_square(to_sq).unwrap();
Move::new(placed_piece.piece(), placed_piece.square(), to_sq).capturing(captured_piece)
});
fn generate_moves(&mut self) {
self.generate_bitboards();
self.populate_move_lists();
}
fn generate_bitboards(&mut self) {
// TODO: Calculate bitboards for each knight.
}
fn populate_move_lists(&mut self) {
let piece = Piece::knight(self.color);
let bb = self.position.bitboard_for_piece(piece);
if bb.is_empty() {
return;
}
let empty_squares = self.position.empty_squares();
let opposing_pieces = self.position.bitboard_for_color(self.color.other());
for from_sq in bb.occupied_squares() {
let knight_moves = BitBoard::knight_moves(from_sq);
println!("{}", knight_moves);
let new_moves = (knight_moves & empty_squares)
.occupied_squares()
.map(|to_sq| Move::new(piece, from_sq, to_sq));
self.quiet_moves_list_mut().extend(new_moves);
let new_capture_moves =
(knight_moves & opposing_pieces)
.occupied_squares()
.map(|to_sq| {
let captured_piece = self.position.piece_on_square(to_sq).unwrap();
Move::new(piece, from_sq, to_sq).capturing(captured_piece)
});
self.capture_moves_list_mut().extend(new_capture_moves);
}
MoveSet::new(placed_piece)
.quiet_moves(quiet_moves_bb, quiet_moves)
.capture_moves(capture_moves_bb, capture_moves)
}
}

View file

@ -11,3 +11,86 @@ pub use move_generator::Moves;
pub use r#move::Move;
pub(self) use move_set::MoveSet;
use crate::{
bitboard::BitBoard,
piece::{Color, Piece, PlacedPiece},
Position, Square,
};
use std::collections::BTreeMap;
trait MoveGenerator {
fn iter(&self) -> dyn Iterator<Item = Move>;
fn moves(&self, color: Color) -> dyn Iterator<Item = Move>;
fn attacks(&self, color: Color) -> dyn Iterator<Item = Move>;
}
macro_rules! move_generator_declaration {
($name:ident) => {
move_generator_declaration!($name, struct);
move_generator_declaration!($name, new);
move_generator_declaration!($name, getters);
};
($name:ident, struct) => {
pub(super) struct $name<'pos> {
position: &'pos Position,
color: crate::piece::Color,
move_sets: std::collections::BTreeMap<crate::Square, crate::moves::MoveSet>,
}
};
($name:ident, new) => {
impl<'pos> $name<'pos> {
pub(super) fn new(position: &Position, color: crate::piece::Color) -> $name {
$name {
position,
color,
move_sets: Self::move_sets(position, color),
}
}
}
};
($name:ident, getters) => {
impl<'pos> $name<'pos> {
pub(super) fn iter(&self) -> impl Iterator<Item = &crate::Move> + '_ {
self.move_sets.values().map(|set| set.moves()).flatten()
}
fn bitboard(&self) -> crate::bitboard::BitBoard {
self.move_sets
.values()
.fold(crate::bitboard::BitBoard::empty(), |partial, mv_set| {
partial | mv_set.bitboard()
})
}
}
};
}
pub(self) use move_generator_declaration;
trait MoveGeneratorInternal {
fn piece(color: Color) -> Piece;
fn move_sets(position: &Position, color: Color) -> BTreeMap<Square, MoveSet> {
let piece = Self::piece(color);
BTreeMap::from_iter(
position
.bitboard_for_piece(piece)
.occupied_squares()
.map(|sq| {
let placed_piece = PlacedPiece::new(piece, sq);
let move_set = Self::move_set_for_piece(position, placed_piece);
(sq, move_set)
}),
)
}
fn move_set_for_piece(position: &Position, placed_piece: PlacedPiece) -> MoveSet {
MoveSet::new(placed_piece)
}
fn move_list_from_bitboard(piece: Piece, square: Square, bitboard: BitBoard) -> Vec<Move> {
let map_to_move = |sq| Move::new(piece, square, sq);
bitboard.occupied_squares().map(map_to_move).collect()
}
}