[board] Implement a pawn move generator
Holy heck I went on a *journey* here. Ultimately, I needed to implement my own index-based iterator instead of using the Vec's Iterator. This type establishes some patterns I want to carry forward to other move generators. 1. The use of a Parameters struct to fully parameterize the move generation per-color. That lets these types only need a single color-based branch 2. A list of move lists, one list for each of captures, promotions, and quiet moves. 3. An index-based move iterator. 4. Separate impl for generating bitboard representations of these moves Additional changes: - Implement BitBoard::from_square() - Implement a Square::e5() for tests This class doesn't implement en passant yet. It also doesn't yet have tests for the bitboard stuff.
This commit is contained in:
parent
750b16970f
commit
af36b75df7
3 changed files with 300 additions and 70 deletions
|
|
@ -28,6 +28,10 @@ impl BitBoard {
|
||||||
FILES[file]
|
FILES[file]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_square(sq: Square) -> BitBoard {
|
||||||
|
BitBoard(1 << sq.index())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0 == 0
|
self.0 == 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,251 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::bitboard::BitBoard;
|
use crate::{
|
||||||
use crate::piece::{Color, Piece};
|
bitboard::BitBoard,
|
||||||
use crate::Position;
|
piece::{Color, Piece, Shape},
|
||||||
|
Move, Position,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) struct PawnMoveGenerator<'a> {
|
enum MoveList {
|
||||||
position: &'a Position,
|
Quiet = 0,
|
||||||
|
Promotions = 1,
|
||||||
|
Captures = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PawnMoveGenerator<'a> {
|
#[derive(Debug)]
|
||||||
pub(super) fn new(position: &Position) -> PawnMoveGenerator {
|
struct MoveIterator(usize, usize);
|
||||||
PawnMoveGenerator { position }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn pawn_moves(&self, color: Color) -> BitBoard {
|
struct MoveGenerationParameters {
|
||||||
match color {
|
starting_rank: BitBoard,
|
||||||
Color::White => self.white_pawn_pushes() & self.white_pawn_attacks(),
|
promotion_rank: BitBoard,
|
||||||
Color::Black => self.black_pawn_pushes() & self.black_pawn_attacks(),
|
push_shift: fn(&BitBoard) -> BitBoard,
|
||||||
|
left_capture_shift: fn(&BitBoard) -> BitBoard,
|
||||||
|
right_capture_shift: fn(&BitBoard) -> BitBoard,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct PawnMoveGenerator<'pos> {
|
||||||
|
color: Color,
|
||||||
|
position: &'pos Position,
|
||||||
|
|
||||||
|
did_populate_move_lists: bool,
|
||||||
|
|
||||||
|
pushes: BitBoard,
|
||||||
|
attacks: BitBoard,
|
||||||
|
|
||||||
|
move_lists: [Vec<Move>; 3],
|
||||||
|
move_iterator: MoveIterator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'pos> PawnMoveGenerator<'pos> {
|
||||||
|
pub(super) fn new(position: &Position, color: Color) -> PawnMoveGenerator {
|
||||||
|
PawnMoveGenerator {
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
did_populate_move_lists: false,
|
||||||
|
pushes: BitBoard::empty(),
|
||||||
|
attacks: BitBoard::empty(),
|
||||||
|
move_lists: [Vec::new(), Vec::new(), Vec::new()],
|
||||||
|
move_iterator: MoveIterator(0, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn pawn_attacks(&self, color: Color) -> BitBoard {
|
pub(super) fn generate_moves(&mut self) {
|
||||||
match color {
|
self.generate_move_bitboards();
|
||||||
Color::White => self.white_pawn_attacks(),
|
self.populate_move_lists();
|
||||||
Color::Black => self.black_pawn_attacks(),
|
}
|
||||||
|
|
||||||
|
fn move_generation_parameters(&self) -> MoveGenerationParameters {
|
||||||
|
match self.color {
|
||||||
|
Color::White => MoveGenerationParameters {
|
||||||
|
starting_rank: BitBoard::rank(1),
|
||||||
|
promotion_rank: BitBoard::rank(7),
|
||||||
|
push_shift: BitBoard::shift_north_one,
|
||||||
|
left_capture_shift: BitBoard::shift_north_west_one,
|
||||||
|
right_capture_shift: BitBoard::shift_north_east_one,
|
||||||
|
},
|
||||||
|
Color::Black => MoveGenerationParameters {
|
||||||
|
starting_rank: BitBoard::rank(6),
|
||||||
|
promotion_rank: BitBoard::rank(0),
|
||||||
|
push_shift: BitBoard::shift_south_one,
|
||||||
|
left_capture_shift: BitBoard::shift_south_east_one,
|
||||||
|
right_capture_shift: BitBoard::shift_south_west_one,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn pawn_pushes(&self, color: Color) -> BitBoard {
|
#[inline]
|
||||||
match color {
|
fn quiet_move_list(&mut self) -> &mut Vec<Move> {
|
||||||
Color::White => self.white_pawn_pushes(),
|
&mut self.move_lists[MoveList::Quiet as usize]
|
||||||
Color::Black => self.black_pawn_pushes(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn white_pawn_pushes(&self) -> BitBoard {
|
#[inline]
|
||||||
|
fn promotion_move_list(&mut self) -> &mut Vec<Move> {
|
||||||
|
&mut self.move_lists[MoveList::Promotions as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn capture_move_list(&mut self) -> &mut Vec<Move> {
|
||||||
|
&mut self.move_lists[MoveList::Captures as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'pos> PawnMoveGenerator<'pos> {
|
||||||
|
fn generate_move_bitboards(&mut self) {
|
||||||
|
let parameters = self.move_generation_parameters();
|
||||||
|
self.generate_pushes_bitboard(¶meters);
|
||||||
|
self.generate_attacks_bitboard(¶meters);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_pushes_bitboard(&mut self, parameters: &MoveGenerationParameters) {
|
||||||
let empty_squares = self.position.empty_squares();
|
let empty_squares = self.position.empty_squares();
|
||||||
|
let bb = self.position.bitboard_for_piece(Piece::pawn(self.color));
|
||||||
|
|
||||||
|
let legal_1square_pushes = (parameters.push_shift)(bb) & empty_squares;
|
||||||
|
let legal_2square_pushes =
|
||||||
|
(parameters.push_shift)(&(legal_1square_pushes & BitBoard::rank(2))) & empty_squares;
|
||||||
|
|
||||||
|
self.pushes = legal_1square_pushes | legal_2square_pushes;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_attacks_bitboard(&mut self, parameters: &MoveGenerationParameters) {
|
||||||
|
let opponent_pieces = self.position.bitboard_for_color(self.color.other());
|
||||||
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::White));
|
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::White));
|
||||||
|
|
||||||
let legal_1square_pushes = bb.shift_north_one() & empty_squares;
|
self.attacks = ((parameters.left_capture_shift)(bb) | (parameters.right_capture_shift)(bb))
|
||||||
let legal_2square_pushes =
|
& opponent_pieces;
|
||||||
(legal_1square_pushes & BitBoard::rank(2)).shift_north_one() & empty_squares;
|
|
||||||
|
|
||||||
legal_1square_pushes | legal_2square_pushes
|
#[allow(unused_variables)]
|
||||||
|
if let Some(en_passant) = self.en_passant() {
|
||||||
|
// TODO: Add en passant move to the attacks bitboard.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'pos> PawnMoveGenerator<'pos> {
|
||||||
|
fn populate_move_lists(&mut self) {
|
||||||
|
let parameters = self.move_generation_parameters();
|
||||||
|
|
||||||
|
self._populate_move_lists(¶meters);
|
||||||
|
self.did_populate_move_lists = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn white_pawn_attacks(&self) -> BitBoard {
|
fn _populate_move_lists(&mut self, parameters: &MoveGenerationParameters) {
|
||||||
let black_pieces = self.position.bitboard_for_color(Color::Black);
|
let piece = Piece::pawn(self.color);
|
||||||
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::White));
|
|
||||||
|
|
||||||
(bb.shift_north_east_one() | bb.shift_north_west_one()) & black_pieces
|
let bb = self.position.bitboard_for_piece(piece);
|
||||||
}
|
if bb.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fn black_pawn_pushes(&self) -> BitBoard {
|
|
||||||
let empty_squares = self.position.empty_squares();
|
let empty_squares = self.position.empty_squares();
|
||||||
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::Black));
|
let black_pieces = self.position.bitboard_for_color(self.color.other());
|
||||||
|
|
||||||
let legal_1square_pushes = bb.shift_south_one() & empty_squares;
|
for from_sq in bb.occupied_squares() {
|
||||||
let legal_2square_pushes =
|
let pawn = BitBoard::from_square(from_sq);
|
||||||
(legal_1square_pushes & BitBoard::rank(5)).shift_south_one() & empty_squares;
|
|
||||||
|
|
||||||
legal_1square_pushes | legal_2square_pushes
|
let push = (parameters.push_shift)(&pawn);
|
||||||
|
if !(push & empty_squares).is_empty() {
|
||||||
|
let to_sq = push.occupied_squares().next().unwrap();
|
||||||
|
|
||||||
|
let r#move = Move::new(piece, from_sq, to_sq);
|
||||||
|
if !(push & parameters.promotion_rank).is_empty() {
|
||||||
|
for shape in Shape::promotable() {
|
||||||
|
self.promotion_move_list()
|
||||||
|
.push(r#move.clone().promoting_to(*shape));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.quiet_move_list().push(r#move);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(pawn & parameters.starting_rank).is_empty() {
|
||||||
|
let push = (parameters.push_shift)(&push);
|
||||||
|
if !(push & empty_squares).is_empty() {
|
||||||
|
let to_sq = push.occupied_squares().next().unwrap();
|
||||||
|
self.quiet_move_list()
|
||||||
|
.push(Move::new(piece, from_sq, to_sq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for attack in [
|
||||||
|
(parameters.left_capture_shift)(&pawn),
|
||||||
|
(parameters.right_capture_shift)(&pawn),
|
||||||
|
] {
|
||||||
|
if !(attack & black_pieces).is_empty() {
|
||||||
|
let to_sq = attack.occupied_squares().next().unwrap();
|
||||||
|
let captured_piece = self.position.piece_on_square(to_sq).unwrap();
|
||||||
|
|
||||||
|
let r#move = Move::new(piece, from_sq, to_sq).capturing(captured_piece);
|
||||||
|
if !(attack & parameters.promotion_rank).is_empty() {
|
||||||
|
for shape in Shape::promotable() {
|
||||||
|
self.capture_move_list()
|
||||||
|
.push(r#move.clone().promoting_to(*shape));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.capture_move_list().push(r#move);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(en_passant) = self.en_passant() {
|
||||||
|
self.capture_move_list().push(en_passant);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn black_pawn_attacks(&self) -> BitBoard {
|
fn en_passant(&self) -> Option<Move> {
|
||||||
let white_pieces = self.position.bitboard_for_color(Color::White);
|
// TODO: En passant. I think the way to do this is to have the position mark
|
||||||
let bb = self.position.bitboard_for_piece(Piece::pawn(Color::Black));
|
// an en passant square when the conditions are correct, i.e. when the
|
||||||
|
// opposing player has pushed a pawn two squares off the initial rank, and
|
||||||
|
// then check in these routines if a pawn of this color attacks that square.
|
||||||
|
|
||||||
(bb.shift_south_east_one() | bb.shift_south_west_one()) & white_pieces
|
None
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: En passant
|
impl<'pos> Iterator for PawnMoveGenerator<'pos> {
|
||||||
|
type Item = Move;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if !self.did_populate_move_lists {
|
||||||
|
self.generate_moves();
|
||||||
|
}
|
||||||
|
|
||||||
|
let iter = &mut self.move_iterator;
|
||||||
|
|
||||||
|
// Find the next non-empty list.
|
||||||
|
loop {
|
||||||
|
if iter.0 >= self.move_lists.len() || !self.move_lists[iter.0].is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter.0 += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(move_list) = self.move_lists.get(iter.0) {
|
||||||
|
let next_move = move_list[iter.1].clone();
|
||||||
|
|
||||||
|
iter.1 += 1;
|
||||||
|
if iter.1 >= move_list.len() {
|
||||||
|
// Increment the list index here. On the next iteration, find the next non-empty list.
|
||||||
|
iter.0 += 1;
|
||||||
|
iter.1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(next_move)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::piece::PlacedPiece;
|
||||||
use crate::position::DiagramFormatter;
|
use crate::position::DiagramFormatter;
|
||||||
use crate::{Position, Square};
|
use crate::{Position, Square};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_2square_push() {
|
fn one_2square_push() {
|
||||||
|
|
@ -85,14 +253,19 @@ mod tests {
|
||||||
pos.place_piece(&Piece::pawn(Color::White), &Square::e2())
|
pos.place_piece(&Piece::pawn(Color::White), &Square::e2())
|
||||||
.expect("Failed to place pawn on e2");
|
.expect("Failed to place pawn on e2");
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
let pawn_pushes = generator.pawn_pushes(Color::White);
|
|
||||||
|
|
||||||
let mut expected = BitBoard::empty();
|
let expected_moves = HashSet::from_iter(
|
||||||
expected.place_piece_at(&Square::e3());
|
[
|
||||||
expected.place_piece_at(&Square::e4());
|
Move::new(Piece::pawn(Color::White), Square::e2(), Square::e3()),
|
||||||
|
Move::new(Piece::pawn(Color::White), Square::e2(), Square::e4()),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(pawn_pushes, expected);
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -101,13 +274,20 @@ mod tests {
|
||||||
pos.place_piece(&Piece::pawn(Color::White), &Square::e3())
|
pos.place_piece(&Piece::pawn(Color::White), &Square::e3())
|
||||||
.expect("Failed to place pawn on e3");
|
.expect("Failed to place pawn on e3");
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
let pawn_pushes = generator.pawn_pushes(Color::White);
|
|
||||||
|
|
||||||
let mut expected = BitBoard::empty();
|
let expected_moves = HashSet::from_iter(
|
||||||
expected.place_piece_at(&Square::e4());
|
[Move::new(
|
||||||
|
Piece::pawn(Color::White),
|
||||||
|
Square::e3(),
|
||||||
|
Square::e4(),
|
||||||
|
)]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(pawn_pushes, expected);
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -120,13 +300,37 @@ mod tests {
|
||||||
|
|
||||||
println!("{}", DiagramFormatter::new(&pos));
|
println!("{}", DiagramFormatter::new(&pos));
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
let pawn_pushes = generator.white_pawn_pushes();
|
|
||||||
|
|
||||||
let mut expected = BitBoard::empty();
|
let expected_moves = HashSet::from_iter(
|
||||||
expected.place_piece_at(&Square::e3());
|
[Move::new(
|
||||||
|
Piece::pawn(Color::White),
|
||||||
|
Square::e2(),
|
||||||
|
Square::e3(),
|
||||||
|
)]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(pawn_pushes, expected);
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(generated_moves, expected_moves);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_obstructed_1square_push() {
|
||||||
|
let mut pos = Position::empty();
|
||||||
|
pos.place_piece(&Piece::pawn(Color::White), &Square::e2())
|
||||||
|
.expect("Failed to place pawn on e2");
|
||||||
|
pos.place_piece(&Piece::knight(Color::White), &Square::e3())
|
||||||
|
.expect("Failed to place knight on e4");
|
||||||
|
|
||||||
|
println!("{}", DiagramFormatter::new(&pos));
|
||||||
|
|
||||||
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
|
|
||||||
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(generated_moves, HashSet::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -134,18 +338,26 @@ mod tests {
|
||||||
let mut pos = Position::empty();
|
let mut pos = Position::empty();
|
||||||
pos.place_piece(&Piece::pawn(Color::White), &Square::e4())
|
pos.place_piece(&Piece::pawn(Color::White), &Square::e4())
|
||||||
.expect("Failed to place pawn on e4");
|
.expect("Failed to place pawn on e4");
|
||||||
|
pos.place_piece(&Piece::bishop(Color::White), &Square::e5())
|
||||||
|
.expect("Failed to place pawn on e4");
|
||||||
pos.place_piece(&Piece::knight(Color::Black), &Square::d5())
|
pos.place_piece(&Piece::knight(Color::Black), &Square::d5())
|
||||||
.expect("Failed to place knight on d5");
|
.expect("Failed to place knight on d5");
|
||||||
|
|
||||||
println!("{}", DiagramFormatter::new(&pos));
|
println!("{}", DiagramFormatter::new(&pos));
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
let pawn_attacks = generator.white_pawn_attacks();
|
|
||||||
|
|
||||||
let mut expected = BitBoard::empty();
|
let expected_moves = HashSet::from_iter(
|
||||||
expected.place_piece_at(&Square::d5());
|
[
|
||||||
|
Move::new(Piece::pawn(Color::White), Square::e4(), Square::d5())
|
||||||
|
.capturing(PlacedPiece::new(Piece::knight(Color::Black), Square::d5())),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(pawn_attacks, expected);
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(generated_moves, expected_moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -153,6 +365,8 @@ mod tests {
|
||||||
let mut pos = Position::empty();
|
let mut pos = Position::empty();
|
||||||
pos.place_piece(&Piece::pawn(Color::White), &Square::e4())
|
pos.place_piece(&Piece::pawn(Color::White), &Square::e4())
|
||||||
.expect("Failed to place pawn on e4");
|
.expect("Failed to place pawn on e4");
|
||||||
|
pos.place_piece(&Piece::bishop(Color::White), &Square::e5())
|
||||||
|
.expect("Failed to place pawn on e4");
|
||||||
pos.place_piece(&Piece::knight(Color::Black), &Square::d5())
|
pos.place_piece(&Piece::knight(Color::Black), &Square::d5())
|
||||||
.expect("Failed to place knight on d5");
|
.expect("Failed to place knight on d5");
|
||||||
pos.place_piece(&Piece::queen(Color::Black), &Square::f5())
|
pos.place_piece(&Piece::queen(Color::Black), &Square::f5())
|
||||||
|
|
@ -160,13 +374,24 @@ mod tests {
|
||||||
|
|
||||||
println!("{}", DiagramFormatter::new(&pos));
|
println!("{}", DiagramFormatter::new(&pos));
|
||||||
|
|
||||||
let generator = PawnMoveGenerator::new(&pos);
|
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||||
let pawn_attacks = generator.white_pawn_attacks();
|
|
||||||
|
|
||||||
let mut expected = BitBoard::empty();
|
let expected_moves = HashSet::from_iter(
|
||||||
expected.place_piece_at(&Square::d5());
|
[
|
||||||
expected.place_piece_at(&Square::f5());
|
Move::new(Piece::pawn(Color::White), Square::e4(), Square::d5())
|
||||||
|
.capturing(PlacedPiece::new(Piece::knight(Color::Black), Square::d5())),
|
||||||
|
Move::new(Piece::pawn(Color::White), Square::e4(), Square::f5())
|
||||||
|
.capturing(PlacedPiece::new(Piece::queen(Color::Black), Square::f5())),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(pawn_attacks, expected);
|
let generated_moves: HashSet<Move> = generator.collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
generated_moves, expected_moves,
|
||||||
|
"generated: {:#?}\nexpected: {:#?}",
|
||||||
|
generated_moves, expected_moves
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,6 @@ impl Square {
|
||||||
sq_constructor!(e2);
|
sq_constructor!(e2);
|
||||||
sq_constructor!(e3);
|
sq_constructor!(e3);
|
||||||
sq_constructor!(e4);
|
sq_constructor!(e4);
|
||||||
|
sq_constructor!(e5);
|
||||||
sq_constructor!(f5);
|
sq_constructor!(f5);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue