[board] Reimplement Square as an enum
Replace the implementation of Square as an enum. Add Rank and File enums to support this. Along the way, change implementations of various methods taking Squares and BitBoards to take them by value instead of by reference. I don't think much is gained passing these types by reference because they're such small POD structs.
This commit is contained in:
commit
bcab682bb1
12 changed files with 392 additions and 408 deletions
|
@ -42,24 +42,22 @@ impl BitBoard {
|
|||
moves_getter!(queen_moves);
|
||||
moves_getter!(king_moves);
|
||||
|
||||
pub fn from_square(sq: Square) -> BitBoard {
|
||||
BitBoard(1 << sq.index())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
pub fn has_piece_at(&self, sq: &Square) -> bool {
|
||||
(self.0 & (1 << sq.index())) > 0
|
||||
pub fn has_piece_at(self, sq: Square) -> bool {
|
||||
!(self & sq.into()).is_empty()
|
||||
}
|
||||
|
||||
pub fn place_piece_at(&mut self, sq: &Square) {
|
||||
self.0 |= 1 << sq.index()
|
||||
pub fn place_piece_at(&mut self, sq: Square) {
|
||||
let sq_bb: BitBoard = sq.into();
|
||||
*self |= sq_bb
|
||||
}
|
||||
|
||||
fn remove_piece_at(&mut self, sq: &Square) {
|
||||
self.0 &= !(1 << sq.index())
|
||||
fn remove_piece_at(&mut self, sq: Square) {
|
||||
let sq_bb: BitBoard = sq.into();
|
||||
*self &= !sq_bb
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +66,13 @@ impl BitBoard {
|
|||
BitScanner::new(self.0)
|
||||
.map(|x| u8::try_from(x))
|
||||
.take_while(|x| x.is_ok())
|
||||
.map(|x| Square::from_index_unsafe(x.unwrap()))
|
||||
.map(|x| Square::from_index(x.unwrap() as usize))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Square> for BitBoard {
|
||||
fn from(value: Square) -> Self {
|
||||
BitBoard(1 << value as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,41 +129,35 @@ impl fmt::Debug for BitBoard {
|
|||
}
|
||||
|
||||
macro_rules! infix_op {
|
||||
($trait_type:ident, $func_name:ident, $left_type:ty, $right_type:ty, $op:tt) => {
|
||||
($trait_type:ident, $func_name:ident, $left_type:ty, $right_type:ty) => {
|
||||
impl $trait_type<$right_type> for $left_type {
|
||||
type Output = BitBoard;
|
||||
|
||||
#[inline]
|
||||
fn $func_name(self, rhs: $right_type) -> Self::Output {
|
||||
BitBoard(self.0 $op rhs.0)
|
||||
BitBoard($trait_type::$func_name(self.0, rhs.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assign_op {
|
||||
($trait_type:ident, $func_name:ident, $left_type:ty, $op:tt) => {
|
||||
($trait_type:ident, $func_name:ident, $left_type:ty) => {
|
||||
impl $trait_type for $left_type {
|
||||
#[inline]
|
||||
fn $func_name(&mut self, rhs: Self) {
|
||||
self.0 $op rhs.0
|
||||
$trait_type::$func_name(&mut self.0, rhs.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
infix_op!(BitAnd, bitand, BitBoard, BitBoard, &);
|
||||
infix_op!(BitAnd, bitand, &BitBoard, BitBoard, &);
|
||||
infix_op!(BitAnd, bitand, BitBoard, &BitBoard, &);
|
||||
infix_op!(BitAnd, bitand, &BitBoard, &BitBoard, &);
|
||||
infix_op!(BitAnd, bitand, BitBoard, BitBoard);
|
||||
|
||||
assign_op!(BitAndAssign, bitand_assign, BitBoard, &=);
|
||||
assign_op!(BitOrAssign, bitor_assign, BitBoard, |=);
|
||||
assign_op!(BitAndAssign, bitand_assign, BitBoard);
|
||||
assign_op!(BitOrAssign, bitor_assign, BitBoard);
|
||||
|
||||
infix_op!(BitOr, bitor, BitBoard, BitBoard, |);
|
||||
infix_op!(BitOr, bitor, &BitBoard, BitBoard, |);
|
||||
infix_op!(BitOr, bitor, BitBoard, &BitBoard, |);
|
||||
infix_op!(BitOr, bitor, &BitBoard, &BitBoard, |);
|
||||
infix_op!(BitOr, bitor, BitBoard, BitBoard);
|
||||
|
||||
impl Not for BitBoard {
|
||||
type Output = BitBoard;
|
||||
|
@ -212,41 +210,44 @@ mod tests {
|
|||
#[test]
|
||||
fn has_piece_at() {
|
||||
let bb = BitBoard(0b1001100);
|
||||
|
||||
let c1 = Square::from_algebraic_str("c1").expect("Unable to get square for 'c1'");
|
||||
assert!(bb.has_piece_at(&c1));
|
||||
|
||||
let b1 = Square::from_algebraic_str("b1").expect("Unable to get square for 'b1'");
|
||||
assert!(!bb.has_piece_at(&b1));
|
||||
assert!(bb.has_piece_at(Square::C1));
|
||||
assert!(!bb.has_piece_at(Square::B1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn place_piece_at() {
|
||||
let sq = Square::from_index(34).expect("Invalid square index");
|
||||
|
||||
let sq = Square::E4;
|
||||
let mut bb = BitBoard(0b1001100);
|
||||
bb.place_piece_at(&sq);
|
||||
assert!(bb.has_piece_at(&sq));
|
||||
bb.place_piece_at(sq);
|
||||
assert!(bb.has_piece_at(sq));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_piece_at() {
|
||||
let sq = Square::from_index(2).expect("Invalid square index");
|
||||
|
||||
let sq = Square::A3;
|
||||
let mut bb = BitBoard(0b1001100);
|
||||
bb.remove_piece_at(&sq);
|
||||
assert!(!bb.has_piece_at(&sq));
|
||||
bb.remove_piece_at(sq);
|
||||
assert!(!bb.has_piece_at(sq));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pieces() {
|
||||
let bb = BitBoard(0x1001_1010); // e4
|
||||
fn single_rank_occupancy() {
|
||||
let bb = BitBoard(0b01010100);
|
||||
let expected_squares = [Square::G1, Square::E1, Square::C1];
|
||||
for (a, b) in bb.occupied_squares().zip(expected_squares.iter().cloned()) {
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
let mut occupied_squares = bb.occupied_squares();
|
||||
assert_eq!(occupied_squares.next(), Some(Square::from_index_unsafe(28)));
|
||||
assert_eq!(occupied_squares.next(), Some(Square::from_index_unsafe(16)));
|
||||
assert_eq!(occupied_squares.next(), Some(Square::from_index_unsafe(12)));
|
||||
assert_eq!(occupied_squares.next(), Some(Square::from_index_unsafe(4)));
|
||||
assert_eq!(occupied_squares.next(), None);
|
||||
#[test]
|
||||
fn occupancy_spot_check() {
|
||||
let bb =
|
||||
BitBoard(0b10000000_00000000_00100000_00000100_00000000_00000000_00010000_00001000);
|
||||
|
||||
let expected_squares = [Square::H8, Square::F6, Square::C5, Square::E2, Square::D1];
|
||||
|
||||
for (a, b) in bb.occupied_squares().zip(expected_squares.iter().cloned()) {
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ impl DiagonalRays {
|
|||
macro_rules! library_getter {
|
||||
($name:ident) => {
|
||||
pub(super) fn $name(&self, sq: Square) -> BitBoard {
|
||||
self.$name[sq.index() as usize]
|
||||
self.$name[sq as usize]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -91,8 +91,8 @@ macro_rules! library_getter {
|
|||
#[derive(Debug)]
|
||||
pub(super) struct MoveLibrary {
|
||||
// Rays
|
||||
diagonal_rays: [DiagonalRays; 64],
|
||||
orthogonal_rays: [OrthogonalRays; 64],
|
||||
diagonal_rays: [DiagonalRays; Square::NUM as usize],
|
||||
orthogonal_rays: [OrthogonalRays; Square::NUM as usize],
|
||||
|
||||
// Piecewise move tables
|
||||
knight_moves: [BitBoard; 64],
|
||||
|
@ -116,33 +116,31 @@ impl MoveLibrary {
|
|||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
for i in 0..64 {
|
||||
self.init_orthogonal_rays(i);
|
||||
self.init_diagonal_rays(i);
|
||||
self.init_knight_moves(i as usize);
|
||||
self.init_bishop_moves(i as usize);
|
||||
self.init_rook_moves(i as usize);
|
||||
self.init_queen_moves(i as usize);
|
||||
self.init_king_moves(i as usize);
|
||||
for sq in Square::ALL {
|
||||
self.init_orthogonal_rays(sq);
|
||||
self.init_diagonal_rays(sq);
|
||||
self.init_knight_moves(sq as usize);
|
||||
self.init_bishop_moves(sq as usize);
|
||||
self.init_rook_moves(sq as usize);
|
||||
self.init_queen_moves(sq as usize);
|
||||
self.init_king_moves(sq as usize);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_orthogonal_rays(&mut self, idx: u8) {
|
||||
let sq = Square::from_index_unsafe(idx);
|
||||
let sq_bb = BitBoard::from_square(sq);
|
||||
fn init_orthogonal_rays(&mut self, sq: Square) {
|
||||
let sq_bb: BitBoard = sq.into();
|
||||
|
||||
let ortho_rays = &mut self.orthogonal_rays[idx as usize];
|
||||
let ortho_rays = &mut self.orthogonal_rays[sq as usize];
|
||||
ortho_rays.positive_file = Self::generate_ray(sq_bb, BitBoard::shift_north_one);
|
||||
ortho_rays.negative_file = Self::generate_ray(sq_bb, BitBoard::shift_south_one);
|
||||
ortho_rays.positive_rank = Self::generate_ray(sq_bb, BitBoard::shift_east_one);
|
||||
ortho_rays.negative_rank = Self::generate_ray(sq_bb, BitBoard::shift_west_one);
|
||||
}
|
||||
|
||||
fn init_diagonal_rays(&mut self, idx: u8) {
|
||||
let sq = Square::from_index_unsafe(idx);
|
||||
let sq_bb = BitBoard::from_square(sq);
|
||||
fn init_diagonal_rays(&mut self, sq: Square) {
|
||||
let sq_bb: BitBoard = sq.into();
|
||||
|
||||
let diag_rays = &mut self.diagonal_rays[idx as usize];
|
||||
let diag_rays = &mut self.diagonal_rays[sq as usize];
|
||||
diag_rays.positive_diagonal = Self::generate_ray(sq_bb, BitBoard::shift_north_east_one);
|
||||
diag_rays.positive_antidiagonal = Self::generate_ray(sq_bb, BitBoard::shift_north_west_one);
|
||||
diag_rays.negative_diagonal = Self::generate_ray(sq_bb, BitBoard::shift_south_west_one);
|
||||
|
@ -204,13 +202,13 @@ impl MoveLibrary {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_ray(sq: BitBoard, shift: fn(&BitBoard) -> BitBoard) -> BitBoard {
|
||||
fn generate_ray(sq: BitBoard, shift: fn(BitBoard) -> BitBoard) -> BitBoard {
|
||||
let mut ray = BitBoard::empty();
|
||||
|
||||
let mut iter = shift(&sq);
|
||||
let mut iter = shift(sq);
|
||||
while !iter.is_empty() {
|
||||
ray |= iter;
|
||||
iter = shift(&iter);
|
||||
iter = shift(iter);
|
||||
}
|
||||
|
||||
ray
|
||||
|
|
|
@ -7,58 +7,58 @@ impl BitBoard {
|
|||
const NOT_H_FILE: u64 = 0x7f7f7f7f7f7f7f7f;
|
||||
|
||||
#[inline]
|
||||
pub fn shift_north(&self, n: u8) -> BitBoard {
|
||||
pub fn shift_north(self, n: u8) -> BitBoard {
|
||||
BitBoard(self.0 << (8 * n))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_north_one(&self) -> BitBoard {
|
||||
pub fn shift_north_one(self) -> BitBoard {
|
||||
BitBoard(self.0 << 8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_north_east_one(&self) -> BitBoard {
|
||||
pub fn shift_north_east_one(self) -> BitBoard {
|
||||
BitBoard(self.0 << 9 & BitBoard::NOT_A_FILE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_east(&self, n: u8) -> BitBoard {
|
||||
pub fn shift_east(self, n: u8) -> BitBoard {
|
||||
// TODO: Implement a bounds check here.
|
||||
BitBoard(self.0 << n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_east_one(&self) -> BitBoard {
|
||||
pub fn shift_east_one(self) -> BitBoard {
|
||||
BitBoard(self.0 << 1 & BitBoard::NOT_A_FILE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_south_east_one(&self) -> BitBoard {
|
||||
pub fn shift_south_east_one(self) -> BitBoard {
|
||||
BitBoard(self.0 >> 7 & BitBoard::NOT_A_FILE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_south(&self, n: u8) -> BitBoard {
|
||||
pub fn shift_south(self, n: u8) -> BitBoard {
|
||||
BitBoard(self.0 >> (8 * n))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_south_one(&self) -> BitBoard {
|
||||
pub fn shift_south_one(self) -> BitBoard {
|
||||
BitBoard(self.0 >> 8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_south_west_one(&self) -> BitBoard {
|
||||
pub fn shift_south_west_one(self) -> BitBoard {
|
||||
BitBoard(self.0 >> 9 & BitBoard::NOT_H_FILE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_west_one(&self) -> BitBoard {
|
||||
pub fn shift_west_one(self) -> BitBoard {
|
||||
BitBoard(self.0 >> 1 & BitBoard::NOT_H_FILE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_north_west_one(&self) -> BitBoard {
|
||||
pub fn shift_north_west_one(self) -> BitBoard {
|
||||
BitBoard(self.0 << 7 & BitBoard::NOT_H_FILE)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@ pub mod piece;
|
|||
pub mod position;
|
||||
mod square;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use moves::Move;
|
||||
pub use position::Position;
|
||||
pub use square::Square;
|
||||
pub use square::{File, Rank, Square};
|
||||
|
|
|
@ -161,13 +161,13 @@ impl<'pos> Iterator for KingMoveGenerator<'pos> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Position, Square};
|
||||
use crate::{piece::Piece, Position, Square};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn one_king() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(&Piece::king(Color::White), &Square::e4())
|
||||
pos.place_piece(Piece::king(Color::White), Square::E4)
|
||||
.expect("Failed to place king on e4");
|
||||
|
||||
let generator = KingMoveGenerator::new(&pos, Color::White);
|
||||
|
@ -181,14 +181,14 @@ mod tests {
|
|||
);
|
||||
|
||||
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()),
|
||||
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<Move> = generator.iter().cloned().collect();
|
||||
|
@ -211,7 +211,7 @@ mod tests {
|
|||
#[test]
|
||||
fn one_king_corner() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(&Piece::king(Color::White), &Square::a1())
|
||||
pos.place_piece(Piece::king(Color::White), Square::A1)
|
||||
.expect("Failed to place king on a1");
|
||||
|
||||
let generator = KingMoveGenerator::new(&pos, Color::White);
|
||||
|
@ -225,9 +225,9 @@ mod tests {
|
|||
);
|
||||
|
||||
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()),
|
||||
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<Move> = generator.iter().cloned().collect();
|
||||
|
|
|
@ -97,7 +97,7 @@ mod tests {
|
|||
#[test]
|
||||
fn one_knight() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(&Piece::knight(Color::White), &Square::e4())
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E4)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
let generator = KnightMoveGenerator::new(&pos, Color::White);
|
||||
|
@ -113,14 +113,14 @@ mod tests {
|
|||
*/
|
||||
|
||||
let expected_moves = [
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::c3()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::d2()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::f2()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::g3()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::c5()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::d6()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::g5()),
|
||||
Move::new(Piece::knight(Color::White), Square::e4(), Square::f6()),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::C3),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::D2),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::F2),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::G3),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::C5),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::D6),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::G5),
|
||||
Move::new(Piece::knight(Color::White), Square::E4, Square::F6),
|
||||
];
|
||||
|
||||
let mut generated_moves: HashSet<Move> = generator.iter().cloned().collect();
|
||||
|
|
|
@ -18,9 +18,9 @@ struct MoveIterator(usize, usize);
|
|||
struct MoveGenerationParameters {
|
||||
starting_rank: BitBoard,
|
||||
promotion_rank: BitBoard,
|
||||
push_shift: fn(&BitBoard) -> BitBoard,
|
||||
left_capture_shift: fn(&BitBoard) -> BitBoard,
|
||||
right_capture_shift: fn(&BitBoard) -> BitBoard,
|
||||
push_shift: fn(BitBoard) -> BitBoard,
|
||||
left_capture_shift: fn(BitBoard) -> BitBoard,
|
||||
right_capture_shift: fn(BitBoard) -> BitBoard,
|
||||
}
|
||||
|
||||
pub(super) struct PawnMoveGenerator<'pos> {
|
||||
|
@ -106,7 +106,7 @@ impl<'pos> PawnMoveGenerator<'pos> {
|
|||
|
||||
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;
|
||||
(parameters.push_shift)(legal_1square_pushes & BitBoard::rank(2)) & empty_squares;
|
||||
|
||||
self.pushes = legal_1square_pushes | legal_2square_pushes;
|
||||
}
|
||||
|
@ -145,9 +145,9 @@ impl<'pos> PawnMoveGenerator<'pos> {
|
|||
let black_pieces = self.position.bitboard_for_color(self.color.other());
|
||||
|
||||
for from_sq in bb.occupied_squares() {
|
||||
let pawn = BitBoard::from_square(from_sq);
|
||||
let pawn: BitBoard = from_sq.into();
|
||||
|
||||
let push = (parameters.push_shift)(&pawn);
|
||||
let push = (parameters.push_shift)(pawn);
|
||||
if !(push & empty_squares).is_empty() {
|
||||
let to_sq = push.occupied_squares().next().unwrap();
|
||||
|
||||
|
@ -162,7 +162,7 @@ impl<'pos> PawnMoveGenerator<'pos> {
|
|||
}
|
||||
|
||||
if !(pawn & parameters.starting_rank).is_empty() {
|
||||
let push = (parameters.push_shift)(&push);
|
||||
let push = (parameters.push_shift)(push);
|
||||
if !(push & empty_squares).is_empty() {
|
||||
let to_sq = push.occupied_squares().next().unwrap();
|
||||
self.quiet_move_list()
|
||||
|
@ -172,8 +172,8 @@ impl<'pos> PawnMoveGenerator<'pos> {
|
|||
}
|
||||
|
||||
for attack in [
|
||||
(parameters.left_capture_shift)(&pawn),
|
||||
(parameters.right_capture_shift)(&pawn),
|
||||
(parameters.left_capture_shift)(pawn),
|
||||
(parameters.right_capture_shift)(pawn),
|
||||
] {
|
||||
if !(attack & black_pieces).is_empty() {
|
||||
let to_sq = attack.occupied_squares().next().unwrap();
|
||||
|
@ -254,15 +254,16 @@ mod tests {
|
|||
#[test]
|
||||
fn one_2square_push() {
|
||||
let mut pos = Position::empty();
|
||||
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");
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[
|
||||
Move::new(Piece::pawn(Color::White), Square::e2(), Square::e3()),
|
||||
Move::new(Piece::pawn(Color::White), Square::e2(), Square::e4()),
|
||||
Move::new(Piece::pawn(Color::White), Square::E2, Square::E3),
|
||||
Move::new(Piece::pawn(Color::White), Square::E2, Square::E4),
|
||||
]
|
||||
.into_iter(),
|
||||
);
|
||||
|
@ -275,18 +276,14 @@ mod tests {
|
|||
#[test]
|
||||
fn one_1square_push() {
|
||||
let mut pos = Position::empty();
|
||||
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");
|
||||
|
||||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[Move::new(
|
||||
Piece::pawn(Color::White),
|
||||
Square::e3(),
|
||||
Square::e4(),
|
||||
)]
|
||||
.into_iter(),
|
||||
[Move::new(Piece::pawn(Color::White), Square::E3, Square::E4)].into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
@ -297,9 +294,10 @@ mod tests {
|
|||
#[test]
|
||||
fn one_obstructed_2square_push() {
|
||||
let mut pos = Position::empty();
|
||||
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");
|
||||
pos.place_piece(&Piece::knight(Color::White), &Square::e4())
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E4)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
@ -307,12 +305,7 @@ mod tests {
|
|||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[Move::new(
|
||||
Piece::pawn(Color::White),
|
||||
Square::e2(),
|
||||
Square::e3(),
|
||||
)]
|
||||
.into_iter(),
|
||||
[Move::new(Piece::pawn(Color::White), Square::E2, Square::E3)].into_iter(),
|
||||
);
|
||||
|
||||
let generated_moves: HashSet<Move> = generator.collect();
|
||||
|
@ -323,9 +316,10 @@ mod tests {
|
|||
#[test]
|
||||
fn one_obstructed_1square_push() {
|
||||
let mut pos = Position::empty();
|
||||
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");
|
||||
pos.place_piece(&Piece::knight(Color::White), &Square::e3())
|
||||
pos.place_piece(Piece::knight(Color::White), Square::E3)
|
||||
.expect("Failed to place knight on e4");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
@ -340,11 +334,11 @@ mod tests {
|
|||
#[test]
|
||||
fn one_attack() {
|
||||
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");
|
||||
pos.place_piece(&Piece::bishop(Color::White), &Square::e5())
|
||||
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");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
@ -352,10 +346,8 @@ mod tests {
|
|||
let generator = PawnMoveGenerator::new(&pos, Color::White);
|
||||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[
|
||||
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::D5)
|
||||
.capturing(PlacedPiece::new(Piece::knight(Color::Black), Square::D5))]
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
|
@ -367,13 +359,13 @@ mod tests {
|
|||
#[test]
|
||||
fn one_double_attack() {
|
||||
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");
|
||||
pos.place_piece(&Piece::bishop(Color::White), &Square::e5())
|
||||
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");
|
||||
pos.place_piece(&Piece::queen(Color::Black), &Square::f5())
|
||||
pos.place_piece(Piece::queen(Color::Black), Square::F5)
|
||||
.expect("Failed to place knight on f5");
|
||||
|
||||
println!("{}", DiagramFormatter::new(&pos));
|
||||
|
@ -382,10 +374,10 @@ mod tests {
|
|||
|
||||
let expected_moves = HashSet::from_iter(
|
||||
[
|
||||
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())),
|
||||
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(),
|
||||
);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::{Position, Square};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
use crate::{File, Position, Rank, Square};
|
||||
use std::{fmt, fmt::Write};
|
||||
|
||||
pub struct DiagramFormatter<'a>(&'a Position);
|
||||
|
||||
|
@ -18,11 +17,11 @@ impl<'a> fmt::Display for DiagramFormatter<'a> {
|
|||
|
||||
output.push_str(" +-----------------+\n");
|
||||
|
||||
for rank in (0..8).rev() {
|
||||
write!(output, "{} | ", rank + 1)?;
|
||||
for rank in Rank::ALL.iter().rev() {
|
||||
write!(output, "{} | ", rank)?;
|
||||
|
||||
for file in 0..8 {
|
||||
let square = Square::from_rank_file(rank, file).unwrap();
|
||||
for file in File::ALL.iter() {
|
||||
let square = Square::from_file_rank(*file, *rank);
|
||||
match self.0.piece_on_square(square) {
|
||||
Some(placed_piece) => write!(output, "{} ", placed_piece.piece())?,
|
||||
None => output.push_str(". "),
|
||||
|
@ -56,8 +55,8 @@ mod tests {
|
|||
fn one_king() {
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(
|
||||
&Piece::king(Color::Black),
|
||||
&Square::from_algebraic_str("h3").expect("h3"),
|
||||
Piece::king(Color::Black),
|
||||
Square::from_algebraic_str("h3").expect("h3"),
|
||||
)
|
||||
.expect("Unable to place piece");
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ impl<'a> Iterator for Pieces<'a> {
|
|||
}
|
||||
|
||||
let mut current_shape: Option<Shape> = None;
|
||||
let mut next_nonempty_bitboard: Option<&BitBoard> = None;
|
||||
let mut next_nonempty_bitboard: Option<BitBoard> = None;
|
||||
|
||||
while let Some(shape) = self.shape_iterator.next() {
|
||||
let piece = Piece::new(self.color, *shape);
|
||||
|
@ -80,12 +80,8 @@ mod tests {
|
|||
use crate::Square;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn square_at(sq: &str) -> Square {
|
||||
Square::from_algebraic_str(sq).expect(sq)
|
||||
}
|
||||
|
||||
fn place_piece_in_position(pos: &mut Position, sq: &str, piece: Piece) {
|
||||
pos.place_piece(&piece, &square_at(sq))
|
||||
fn place_piece_in_position(pos: &mut Position, piece: Piece, sq: Square) {
|
||||
pos.place_piece(piece, sq)
|
||||
.expect("Unable to place {piece:?} queen on {sq}");
|
||||
}
|
||||
|
||||
|
@ -98,11 +94,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn one() {
|
||||
let sq = Square::from_algebraic_str("e4").expect("e4");
|
||||
let sq = Square::E4;
|
||||
|
||||
let mut pos = Position::empty();
|
||||
pos.place_piece(&Piece::new(Color::White, Shape::Queen), &sq)
|
||||
.expect("Unable to place white queen on e4");
|
||||
place_piece_in_position(&mut pos, Piece::new(Color::White, Shape::Queen), Square::E4);
|
||||
println!("{:?}", &pos);
|
||||
|
||||
let mut pieces = pos.pieces(Color::White);
|
||||
|
@ -117,18 +112,18 @@ mod tests {
|
|||
fn multiple_pieces() {
|
||||
let mut pos = Position::empty();
|
||||
|
||||
place_piece_in_position(&mut pos, "e4", Piece::new(Color::White, Shape::Queen));
|
||||
place_piece_in_position(&mut pos, "e1", Piece::new(Color::White, Shape::King));
|
||||
place_piece_in_position(&mut pos, "b2", Piece::new(Color::White, Shape::Pawn));
|
||||
place_piece_in_position(&mut pos, "c2", Piece::new(Color::White, Shape::Pawn));
|
||||
place_piece_in_position(&mut pos, Piece::new(Color::White, Shape::Queen), Square::E4);
|
||||
place_piece_in_position(&mut pos, Piece::new(Color::White, Shape::King), Square::E1);
|
||||
place_piece_in_position(&mut pos, Piece::new(Color::White, Shape::Pawn), Square::B2);
|
||||
place_piece_in_position(&mut pos, Piece::new(Color::White, Shape::Pawn), Square::C2);
|
||||
|
||||
println!("{:?}", &pos);
|
||||
|
||||
let expected_placed_pieces = HashSet::from([
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Queen), square_at("e4")),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::King), square_at("e1")),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Pawn), square_at("b2")),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Pawn), square_at("c2")),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Queen), Square::E4),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::King), Square::E1),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Pawn), Square::B2),
|
||||
PlacedPiece::new(Piece::new(Color::White, Shape::Pawn), Square::C2),
|
||||
]);
|
||||
|
||||
let placed_pieces = HashSet::from_iter(pos.pieces(Color::White));
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use super::Pieces;
|
||||
use crate::bitboard::BitBoard;
|
||||
use crate::moves::Moves;
|
||||
use crate::piece::{Color, Piece, PiecePlacementError, PlacedPiece, Shape};
|
||||
use crate::Square;
|
||||
use crate::{
|
||||
bitboard::BitBoard,
|
||||
moves::Moves,
|
||||
piece::{Color, Piece, PiecePlacementError, PlacedPiece, Shape},
|
||||
Square,
|
||||
};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
|
@ -68,28 +70,24 @@ impl Position {
|
|||
Position {
|
||||
color_to_move: Color::White,
|
||||
pieces_per_color: [
|
||||
white_pieces.iter().fold(BitBoard::empty(), |a, b| a | b),
|
||||
black_pieces.iter().fold(BitBoard::empty(), |a, b| a | b),
|
||||
white_pieces.iter().fold(BitBoard::empty(), |a, b| a | *b),
|
||||
black_pieces.iter().fold(BitBoard::empty(), |a, b| a | *b),
|
||||
],
|
||||
pieces_per_type: [white_pieces, black_pieces],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_piece(
|
||||
&mut self,
|
||||
piece: &Piece,
|
||||
square: &Square,
|
||||
) -> Result<(), PiecePlacementError> {
|
||||
pub fn place_piece(&mut self, piece: Piece, square: Square) -> Result<(), PiecePlacementError> {
|
||||
let type_bb = self.bitboard_for_piece_mut(piece);
|
||||
|
||||
if type_bb.has_piece_at(&square) {
|
||||
if type_bb.has_piece_at(square) {
|
||||
return Err(PiecePlacementError::ExistsOnSquare);
|
||||
}
|
||||
|
||||
type_bb.place_piece_at(&square);
|
||||
type_bb.place_piece_at(square);
|
||||
|
||||
let color_bb = &mut self.bitboard_for_color_mut(piece.color());
|
||||
color_bb.place_piece_at(&square);
|
||||
color_bb.place_piece_at(square);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -110,16 +108,16 @@ impl Position {
|
|||
| self.pieces_per_color[Color::Black as usize])
|
||||
}
|
||||
|
||||
pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> &BitBoard {
|
||||
&self.pieces_per_type[piece.color() as usize][piece.shape() as usize]
|
||||
pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
||||
self.pieces_per_type[piece.color() as usize][piece.shape() as usize]
|
||||
}
|
||||
|
||||
fn bitboard_for_piece_mut(&mut self, piece: &Piece) -> &mut BitBoard {
|
||||
fn bitboard_for_piece_mut(&mut self, piece: Piece) -> &mut BitBoard {
|
||||
&mut self.pieces_per_type[piece.color() as usize][piece.shape() as usize]
|
||||
}
|
||||
|
||||
pub(crate) fn bitboard_for_color(&self, color: Color) -> &BitBoard {
|
||||
&self.pieces_per_color[color as usize]
|
||||
pub(crate) fn bitboard_for_color(&self, color: Color) -> BitBoard {
|
||||
self.pieces_per_color[color as usize]
|
||||
}
|
||||
|
||||
fn bitboard_for_color_mut(&mut self, color: Color) -> &mut BitBoard {
|
||||
|
@ -131,7 +129,7 @@ impl Position {
|
|||
for shape in Shape::iter() {
|
||||
let piece = Piece::new(color, *shape);
|
||||
let bb = self.bitboard_for_piece(piece);
|
||||
if bb.has_piece_at(&sq) {
|
||||
if bb.has_piece_at(sq) {
|
||||
return Some(PlacedPiece::new(piece, sq));
|
||||
}
|
||||
}
|
||||
|
@ -182,10 +180,10 @@ mod tests {
|
|||
let mut position = Position::empty();
|
||||
|
||||
let piece = Piece::new(Color::White, Shape::Queen);
|
||||
let square = Square::e4();
|
||||
let square = Square::E4;
|
||||
|
||||
position
|
||||
.place_piece(&piece, &square)
|
||||
.place_piece(piece, square)
|
||||
.expect("Unable to place white queen on e4");
|
||||
|
||||
assert_eq!(
|
||||
|
@ -198,7 +196,7 @@ mod tests {
|
|||
);
|
||||
|
||||
position
|
||||
.place_piece(&piece, &square)
|
||||
.place_piece(piece, square)
|
||||
.expect_err("Placed white queen on e4 a second time?!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,110 +14,198 @@ pub enum Direction {
|
|||
NorthEast,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseFileError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseSquareError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SquareOutOfBoundsError;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Square {
|
||||
rank: u8,
|
||||
file: u8,
|
||||
index: u8,
|
||||
macro_rules! coordinate_enum {
|
||||
($name: ident, $($variant:ident),*) => {
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum $name {
|
||||
$($variant), *
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const NUM: usize = [$(Self::$variant), *].len();
|
||||
pub const ALL: [Self; Self::NUM] = [$(Self::$variant), *];
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_index(index: usize) -> Self {
|
||||
assert!(
|
||||
index < Self::NUM,
|
||||
"Index {} out of bounds for {}.",
|
||||
index,
|
||||
stringify!($name)
|
||||
);
|
||||
Self::try_index(index).unwrap()
|
||||
}
|
||||
|
||||
pub fn try_index(index: usize) -> Option<Self> {
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $variant: usize = $name::$variant as usize;
|
||||
)*
|
||||
|
||||
match index {
|
||||
$($variant => Some($name::$variant),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(Rank,
|
||||
One, Two, Three, Four, Five, Six, Seven, Eight
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(File,
|
||||
A, B, C, D, E, F, G, H
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(Square,
|
||||
A1, B1, C1, D1, E1, F1, G1, H1,
|
||||
A2, B2, C2, D2, E2, F2, G2, H2,
|
||||
A3, B3, C3, D3, E3, F3, G3, H3,
|
||||
A4, B4, C4, D4, E4, F4, G4, H4,
|
||||
A5, B5, C5, D5, E5, F5, G5, H5,
|
||||
A6, B6, C6, D6, E6, F6, G6, H6,
|
||||
A7, B7, C7, D7, E7, F7, G7, H7,
|
||||
A8, B8, C8, D8, E8, F8, G8, H8
|
||||
);
|
||||
|
||||
impl Into<char> for File {
|
||||
fn into(self) -> char {
|
||||
('a' as u8 + self as u8) as char
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for File {
|
||||
type Error = ParseFileError;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
let lowercase_value = value.to_ascii_lowercase();
|
||||
for file in File::ALL.iter() {
|
||||
if lowercase_value == (*file).into() {
|
||||
return Ok(*file);
|
||||
}
|
||||
}
|
||||
|
||||
Err(ParseFileError)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for File {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", Into::<char>::into(*self).to_uppercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<char> for Rank {
|
||||
fn into(self) -> char {
|
||||
('1' as u8 + self as u8) as char
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Rank {
|
||||
type Error = ParseFileError;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
let lowercase_value = value.to_ascii_lowercase();
|
||||
for rank in Self::ALL.iter().cloned() {
|
||||
if lowercase_value == rank.into() {
|
||||
return Ok(rank);
|
||||
}
|
||||
}
|
||||
|
||||
Err(ParseFileError)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Rank {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", Into::<char>::into(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Square {
|
||||
pub fn from_rank_file(rank: u8, file: u8) -> Result<Square, SquareOutOfBoundsError> {
|
||||
if rank >= 8 || file >= 8 {
|
||||
return Err(SquareOutOfBoundsError);
|
||||
}
|
||||
|
||||
Ok(Square {
|
||||
rank,
|
||||
file,
|
||||
index: rank * 8 + file,
|
||||
})
|
||||
#[inline]
|
||||
pub fn from_file_rank(file: File, rank: Rank) -> Square {
|
||||
Self::from_index((rank as usize) << 3 | file as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn file(self) -> File {
|
||||
File::from_index(self as usize & 0b000111)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rank(self) -> Rank {
|
||||
Rank::from_index(self as usize >> 3)
|
||||
}
|
||||
}
|
||||
|
||||
impl Square {
|
||||
pub fn from_algebraic_str(s: &str) -> Result<Square, ParseSquareError> {
|
||||
s.parse()
|
||||
}
|
||||
|
||||
pub fn rank_file(&self) -> (u8, u8) {
|
||||
(self.rank, self.file)
|
||||
}
|
||||
|
||||
pub fn neighbor(&self, direction: Direction) -> Option<Square> {
|
||||
pub fn neighbor(self, direction: Direction) -> Option<Square> {
|
||||
match direction {
|
||||
Direction::North => Square::from_index(self.index + 8),
|
||||
Direction::North => Square::try_index(self as usize + 8),
|
||||
Direction::NorthWest => {
|
||||
if self.rank < 7 {
|
||||
Square::from_index(self.index + 7)
|
||||
if self.rank() != Rank::Eight {
|
||||
Square::try_index(self as usize + 7)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::West => {
|
||||
if self.file > 0 {
|
||||
Square::from_index(self.index - 1)
|
||||
if self.file() != File::A {
|
||||
Square::try_index(self as usize - 1)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::SouthWest => {
|
||||
if self.rank > 0 {
|
||||
Square::from_index(self.index - 9)
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 9)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::South => {
|
||||
if self.rank > 0 {
|
||||
Square::from_index(self.index - 8)
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 8)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::SouthEast => {
|
||||
if self.rank > 0 {
|
||||
Square::from_index(self.index - 7)
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 7)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::East => {
|
||||
if self.file < 7 {
|
||||
Square::from_index(self.index + 1)
|
||||
if self.file() != File::H {
|
||||
Square::try_index(self as usize + 1)
|
||||
} else {
|
||||
Err(SquareOutOfBoundsError)
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::NorthEast => Square::from_index(self.index + 9),
|
||||
Direction::NorthEast => Square::try_index(self as usize + 9),
|
||||
}
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Square {
|
||||
pub(crate) fn from_index(index: u8) -> Result<Square, SquareOutOfBoundsError> {
|
||||
if index >= 64 {
|
||||
return Err(SquareOutOfBoundsError);
|
||||
}
|
||||
|
||||
Ok(Square::from_index_unsafe(index))
|
||||
}
|
||||
|
||||
pub(crate) fn from_index_unsafe(index: u8) -> Square {
|
||||
Square {
|
||||
rank: index / 8,
|
||||
file: index % 8,
|
||||
index: index,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn index(&self) -> u8 {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,47 +213,34 @@ impl FromStr for Square {
|
|||
type Err = ParseSquareError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if !s.is_ascii() || s.len() != 2 {
|
||||
return Err(ParseSquareError);
|
||||
}
|
||||
|
||||
let mut chars = s.chars();
|
||||
|
||||
let file_char = chars.next().unwrap().to_ascii_lowercase();
|
||||
if !file_char.is_ascii_lowercase() {
|
||||
return Err(ParseSquareError);
|
||||
}
|
||||
|
||||
let file = (file_char as u8) - ('a' as u8);
|
||||
if file >= 8 {
|
||||
return Err(ParseSquareError);
|
||||
}
|
||||
|
||||
let converted_rank_digit = chars
|
||||
let file: File = chars
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_digit(10)
|
||||
.and_then(|x| if x >= 1 && x <= 8 { Some(x) } else { None })
|
||||
.and_then(|c| c.try_into().ok())
|
||||
.ok_or(ParseSquareError)?;
|
||||
let rank = u8::try_from(converted_rank_digit).map_err(|_| ParseSquareError)? - 1;
|
||||
|
||||
Ok(Square {
|
||||
rank,
|
||||
file,
|
||||
index: rank * 8 + file,
|
||||
})
|
||||
}
|
||||
}
|
||||
let rank: Rank = chars
|
||||
.next()
|
||||
.and_then(|c| c.try_into().ok())
|
||||
.ok_or(ParseSquareError)?;
|
||||
|
||||
impl Into<BitBoard> for Square {
|
||||
fn into(self) -> BitBoard {
|
||||
BitBoard::new(1 << self.index)
|
||||
if !chars.next().is_none() {
|
||||
return Err(ParseSquareError);
|
||||
}
|
||||
|
||||
Ok(Square::from_file_rank(file, rank))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Square {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}{}", ('a' as u8 + self.file) as char, self.rank + 1)
|
||||
write!(
|
||||
f,
|
||||
"{}{}",
|
||||
('a' as u8 + self.file() as u8) as char,
|
||||
self.rank() as usize + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,17 +250,17 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn good_algebraic_input() {
|
||||
let sq1 = Square::from_algebraic_str("a4").expect("Failed to parse 'a4' square");
|
||||
assert_eq!(sq1.file, 0);
|
||||
assert_eq!(sq1.rank, 3);
|
||||
let sq = Square::from_algebraic_str("a4").expect("Failed to parse 'a4' square");
|
||||
assert_eq!(sq.file(), File::A);
|
||||
assert_eq!(sq.rank(), Rank::Four);
|
||||
|
||||
let sq2 = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square");
|
||||
assert_eq!(sq2.file, 1);
|
||||
assert_eq!(sq2.rank, 7);
|
||||
let sq = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square");
|
||||
assert_eq!(sq.file(), File::B);
|
||||
assert_eq!(sq.rank(), Rank::Eight);
|
||||
|
||||
let sq3 = Square::from_algebraic_str("e4").expect("Failed to parse 'B8' square");
|
||||
assert_eq!(sq3.rank, 3, "Expected rank of e4 to be 3");
|
||||
assert_eq!(sq3.file, 4, "Expected file of e4 to be 4");
|
||||
let sq = Square::from_algebraic_str("e4").expect("Failed to parse 'B8' square");
|
||||
assert_eq!(sq.file(), File::E);
|
||||
assert_eq!(sq.rank(), Rank::Four);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -200,87 +275,56 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn from_index() {
|
||||
let sq1 = Square::from_index(4).expect("Unable to get Square from index");
|
||||
assert_eq!(sq1.rank, 0);
|
||||
assert_eq!(sq1.file, 4);
|
||||
let sq = Square::try_index(4).expect("Unable to get Square from index");
|
||||
assert_eq!(sq.file(), File::E);
|
||||
assert_eq!(sq.rank(), Rank::One);
|
||||
|
||||
let sq1 = Square::from_index(28).expect("Unable to get Square from index");
|
||||
assert_eq!(sq1.rank, 3);
|
||||
assert_eq!(sq1.file, 4);
|
||||
let sq = Square::try_index(28).expect("Unable to get Square from index");
|
||||
assert_eq!(sq.file(), File::E);
|
||||
assert_eq!(sq.rank(), Rank::Four);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_neighbors() {
|
||||
let sq = Square::from_index_unsafe(28);
|
||||
let sq = Square::E4;
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::North),
|
||||
Some(Square::from_index_unsafe(36))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::NorthEast),
|
||||
Some(Square::from_index_unsafe(37))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::East),
|
||||
Some(Square::from_index_unsafe(29))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::SouthEast),
|
||||
Some(Square::from_index_unsafe(21))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::South),
|
||||
Some(Square::from_index_unsafe(20))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::SouthWest),
|
||||
Some(Square::from_index_unsafe(19))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::West),
|
||||
Some(Square::from_index_unsafe(27))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sq.neighbor(Direction::NorthWest),
|
||||
Some(Square::from_index_unsafe(35))
|
||||
);
|
||||
assert_eq!(sq.neighbor(Direction::North), Some(Square::E5));
|
||||
assert_eq!(sq.neighbor(Direction::NorthEast), Some(Square::F5));
|
||||
assert_eq!(sq.neighbor(Direction::East), Some(Square::F4));
|
||||
assert_eq!(sq.neighbor(Direction::SouthEast), Some(Square::F3));
|
||||
assert_eq!(sq.neighbor(Direction::South), Some(Square::E3));
|
||||
assert_eq!(sq.neighbor(Direction::SouthWest), Some(Square::D3));
|
||||
assert_eq!(sq.neighbor(Direction::West), Some(Square::D4));
|
||||
assert_eq!(sq.neighbor(Direction::NorthWest), Some(Square::D5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_neighbors() {
|
||||
let square0 = Square::from_index_unsafe(0);
|
||||
assert!(square0.neighbor(Direction::West).is_none());
|
||||
assert!(square0.neighbor(Direction::SouthWest).is_none());
|
||||
assert!(square0.neighbor(Direction::South).is_none());
|
||||
let sq = Square::A1;
|
||||
assert!(sq.neighbor(Direction::West).is_none());
|
||||
assert!(sq.neighbor(Direction::SouthWest).is_none());
|
||||
assert!(sq.neighbor(Direction::South).is_none());
|
||||
|
||||
let square7 = Square::from_index_unsafe(7);
|
||||
assert!(square7.neighbor(Direction::East).is_none());
|
||||
assert!(square7.neighbor(Direction::SouthEast).is_none());
|
||||
assert!(square7.neighbor(Direction::South).is_none());
|
||||
let sq = Square::H1;
|
||||
assert!(sq.neighbor(Direction::East).is_none());
|
||||
assert!(sq.neighbor(Direction::SouthEast).is_none());
|
||||
assert!(sq.neighbor(Direction::South).is_none());
|
||||
|
||||
let square56 = Square::from_index_unsafe(56);
|
||||
assert!(square56.neighbor(Direction::North).is_none());
|
||||
assert!(square56.neighbor(Direction::NorthWest).is_none());
|
||||
assert!(square56.neighbor(Direction::West).is_none());
|
||||
let sq = Square::A8;
|
||||
assert!(sq.neighbor(Direction::North).is_none());
|
||||
assert!(sq.neighbor(Direction::NorthWest).is_none());
|
||||
assert!(sq.neighbor(Direction::West).is_none());
|
||||
|
||||
let square63 = Square::from_index_unsafe(63);
|
||||
assert!(square63.neighbor(Direction::North).is_none());
|
||||
assert!(square63.neighbor(Direction::NorthEast).is_none());
|
||||
assert!(square63.neighbor(Direction::East).is_none());
|
||||
let sq = Square::H8;
|
||||
assert!(sq.neighbor(Direction::North).is_none());
|
||||
assert!(sq.neighbor(Direction::NorthEast).is_none());
|
||||
assert!(sq.neighbor(Direction::East).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
assert_eq!(format!("{}", Square::c5()), "c5");
|
||||
assert_eq!(format!("{}", Square::a1()), "a1");
|
||||
assert_eq!(format!("{}", Square::h8()), "h8");
|
||||
assert_eq!(format!("{}", Square::C5), "c5");
|
||||
assert_eq!(format!("{}", Square::A1), "a1");
|
||||
assert_eq!(format!("{}", Square::H8), "h8");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
/// Test helper utilities.
|
||||
use crate::Square;
|
||||
|
||||
/// A constructor function that returns a Square representing the square on the
|
||||
/// chessboard indicated by the algebraic notation.
|
||||
macro_rules! sq_constructor {
|
||||
($func_name:ident) => {
|
||||
pub(crate) fn $func_name() -> Square {
|
||||
Square::from_algebraic_str(stringify!($func_name)).expect(stringify!($func_name))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Square {
|
||||
sq_constructor!(a1);
|
||||
sq_constructor!(a2);
|
||||
sq_constructor!(b1);
|
||||
sq_constructor!(b2);
|
||||
sq_constructor!(c3);
|
||||
sq_constructor!(c5);
|
||||
sq_constructor!(d2);
|
||||
sq_constructor!(d3);
|
||||
sq_constructor!(d4);
|
||||
sq_constructor!(d5);
|
||||
sq_constructor!(d6);
|
||||
sq_constructor!(e2);
|
||||
sq_constructor!(e3);
|
||||
sq_constructor!(e4);
|
||||
sq_constructor!(e5);
|
||||
sq_constructor!(f2);
|
||||
sq_constructor!(f3);
|
||||
sq_constructor!(f4);
|
||||
sq_constructor!(f5);
|
||||
sq_constructor!(f6);
|
||||
sq_constructor!(g3);
|
||||
sq_constructor!(g5);
|
||||
sq_constructor!(h8);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue