use crate::chess::Piece::*;
use crate::errors::*;
use regex::Regex;
use std::collections::HashMap;
const BOARD_SIZE: u8 = 128;
const COLOR_MASK: u8 = 128; const MOVED_MASK: u8 = 64; const EN_PASSANT_SQUARE: u8 = 5;
pub const WHITE: u8 = 0;
pub const BLACK: u8 = 128;
#[allow(non_snake_case)]
pub mod Piece {
use crate::chess::*;
pub type PieceIndex = u8;
pub type PieceType = u8;
pub const EMPTY: PieceType = 0; pub const MOVED_PAWN: PieceType = PAWN | MOVED_MASK; pub const MOVED_BLACK_PAWN: PieceType = MOVED_PAWN | BLACK; pub const MOVED_KING: PieceType = KING | MOVED_MASK;
pub const MOVED_ROOK: PieceType = ROOK | MOVED_MASK; pub const MOVED_BLACK_ROOK: PieceType = BLACK_ROOK | MOVED_MASK;
pub const PAWN: PieceType = 1; pub const ROOK: PieceType = 2; pub const KNIGHT: PieceType = 4; pub const BISHOP: PieceType = 8; pub const KING: PieceType = 16; pub const QUEEN: PieceType = 32;
pub const BLACK_PAWN: PieceType = PAWN | BLACK;
pub const BLACK_ROOK: PieceType = ROOK | BLACK;
pub const BLACK_KNIGHT: PieceType = KNIGHT | BLACK;
pub const BLACK_BISHOP: PieceType = BISHOP | BLACK;
pub const BLACK_KING: PieceType = KING | BLACK;
pub const BLACK_QUEEN: PieceType = QUEEN | BLACK;
}
const WHITE_PAWN_DELTAS: [i8; 4] = [-16, -32, -17, -15];
const BLACK_PAWN_DELTAS: [i8; 4] = [16, 32, 17, 15];
const MOVED_WHITE_PAWN_DELTAS: [i8; 4] = [-16, 0, -17, -15];
const MOVED_BLACK_PAWN_DELTAS: [i8; 4] = [16, 0, 17, 15];
const BISHOP_DELTAS: [i8; 4] = [17, 15, -17, -15];
const ROOK_DELTAS: [i8; 4] = [16, -16, 1, -1];
const QUEEN_DELTAS: [i8; 8] = [16, -16, 1, -1, 17, 15, -17, -15];
const KNIGHT_DELTAS: [i8; 8] = [14, 31, 18, 33, -14, -31, -18, -33];
const KING_DELTAS: [i8; 10] = [1, 16, 17, 15, -1, -16, -17, -15, 2, -2];
const MOVED_KING_DELTAS: [i8; 8] = [1, 16, 17, 15, -1, -16, -17, -15];
#[rustfmt::skip]
const ATTACKS: [u8; 239]= [
40, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0,40, 0, 0, 40, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0,40, 0, 0,
0, 0, 40, 0, 0, 0, 0, 34, 0, 0, 0, 0,40, 0, 0, 0,
0, 0, 0, 40, 0, 0, 0, 34, 0, 0, 0,40, 0, 0, 0, 0,
0, 0, 0, 0, 40, 0, 0, 34, 0, 0,40, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 40, 4, 34, 4,40, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 4, 57, 50, 57, 4, 0, 0, 0, 0, 0, 0,
34,34,34,34,34,34,50, 0, 50,34,34,34,34,34,34, 0, 0, 0, 0, 0, 0, 4,185, 50,185, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 4, 34, 4, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 34, 0, 0, 40, 0, 0, 0, 0, 0,
0, 0, 0, 40, 0, 0, 0, 34, 0, 0, 0, 40, 0, 0, 0, 0,
0, 0, 40, 0, 0, 0, 0, 34, 0, 0, 0, 0, 40, 0, 0, 0,
0, 40, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 40, 0, 0,
40, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 40,
];
#[rustfmt::skip]
const DELTAS: [i8; 239]= [
-17, 0, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0, 0,-15, 0,
0,-17, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0,-15, 0, 0,
0, 0,-17, 0, 0, 0, 0,-16, 0, 0, 0, 0,-15, 0, 0, 0,
0, 0, 0,-17, 0, 0, 0,-16, 0, 0, 0,-15, 0, 0, 0, 0,
0, 0, 0, 0,-17, 0, 0,-16, 0, 0,-15, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,-17, 0,-16, 0,-15, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,-17,-16,-15, 0, 0, 0, 0, 0, 0, 0,
-1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 15, 16, 17, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 15, 0, 16, 0, 17, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 15, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0,
0, 0, 0, 15, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0, 0, 16, 0, 0, 0, 0, 17, 0, 0, 0,
0, 15, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 17, 0, 0,
15, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 17
];
const BOARD_MAP: [u8; 64] = [
0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, 32, 33, 34, 35, 36, 37, 38, 39, 48, 49,
50, 51, 52, 53, 54, 55, 64, 65, 66, 67, 68, 69, 70, 71, 80, 81, 82, 83, 84, 85, 86, 87, 96, 97,
98, 99, 100, 101, 102, 103, 112, 113, 114, 115, 116, 117, 118, 119,
];
const FILES: [&str; 8] = ["a", "b", "c", "d", "e", "f", "g", "h"];
fn is_number(arg: &str) -> bool {
arg.chars()
.collect::<Vec<char>>()
.iter()
.all(|&c| c.is_digit(10))
}
#[derive(Debug)]
struct HistoryEntry {
from_idx: PieceIndex,
to_idx: PieceIndex,
from_piece: PieceType,
to_piece: PieceType,
kings: King,
castle: bool,
capture: bool,
turn: u8,
last_turn: u8,
en_passant_capture: bool,
en_passant_move: bool,
promotion: bool,
half_moves: u8,
full_moves: u8,
en_passant_square: Option<String>,
can_white_king_side_castle: bool,
can_white_queen_side_castle: bool,
can_black_king_side_castle: bool,
can_black_queen_side_castle: bool,
}
#[derive(Debug)]
pub struct Move {
from: PieceIndex,
to: PieceIndex,
}
#[derive(Debug, Clone)]
struct King {
white: PieceIndex,
black: PieceIndex,
}
#[derive(Debug)]
pub struct Chess {
pub board: [PieceType; BOARD_SIZE as usize],
turn: u8,
history: Vec<HistoryEntry>,
kings: King,
pub white_captures: Vec<PieceType>,
pub black_captures: Vec<PieceType>,
pub can_white_king_side_castle: bool,
pub can_white_queen_side_castle: bool,
pub can_black_king_side_castle: bool,
pub can_black_queen_side_castle: bool,
unique_positions: HashMap<String, u8>,
half_moves: u8,
full_moves: u8,
lastest_en_passant_square: Option<String>,
last_turn: u8,
pub captures: u64,
pub castles: u64,
pub checks: u64,
pub promotions: u64,
pub moves: HashMap<String, u64>,
}
impl Chess {
pub fn new() -> Self {
Self {
board: [EMPTY; BOARD_SIZE as usize],
turn: WHITE,
history: vec![],
kings: King { white: 1, black: 2 },
white_captures: vec![],
black_captures: vec![],
can_white_king_side_castle: true,
can_white_queen_side_castle: true,
can_black_king_side_castle: true,
can_black_queen_side_castle: true,
unique_positions: HashMap::new(),
moves: HashMap::new(),
half_moves: 0,
full_moves: 0,
lastest_en_passant_square: None,
last_turn: WHITE,
captures: 0,
castles: 0,
checks: 0,
promotions: 0,
}
}
pub fn move_piece(&mut self, move_notation: &str) -> Result<String, MoveError> {
if move_notation.is_empty() {
return Err(MoveError::InvalidPieceToMove);
}
let parts: Vec<char> = move_notation.chars().collect();
let move_regex =
Regex::new(r"^([KQRBN])?([a-h]|[1-8])?(x)?([a-h])([1-8])(=)?([KQRBN])?([+#])?$")
.unwrap();
if move_notation == "O-O" {
if self.turn == WHITE {
if !self.can_white_king_side_castle {
return Err(MoveError::IllegalKingSideCastle);
}
let legal_moves = self.inner_moves(self.kings.white);
if legal_moves.contains(&118) {
self.inner_move_piece(self.kings.white, 118);
} else {
return Err(MoveError::IllegalKingSideCastle);
}
} else {
if !self.can_black_king_side_castle {
return Err(MoveError::IllegalKingSideCastle);
}
let legal_moves = self.inner_moves(self.kings.black);
if legal_moves.contains(&6) {
self.inner_move_piece(self.kings.black, 6);
} else {
return Err(MoveError::IllegalKingSideCastle);
}
}
} else if move_notation == "O-O-O" {
if self.turn == WHITE {
if !self.can_white_queen_side_castle {
return Err(MoveError::IllegalQueenSideCastle);
}
let legal_moves = self.inner_moves(self.kings.white);
if legal_moves.contains(&114) {
self.inner_move_piece(self.kings.white, 114);
} else {
return Err(MoveError::IllegalQueenSideCastle);
}
} else {
if !self.can_black_queen_side_castle {
return Err(MoveError::IllegalQueenSideCastle);
}
let legal_moves = self.inner_moves(self.kings.black);
if legal_moves.contains(&2) {
self.inner_move_piece(self.kings.black, 2);
} else {
return Err(MoveError::IllegalQueenSideCastle);
}
}
} else {
let captures = move_regex
.captures(move_notation)
.expect("Invalid move notation");
let piece_identifer = captures.get(2).map_or("", |m| m.as_str());
let is_capture = captures.get(3).map_or(false, |_| true);
let to_file = captures.get(4).map_or("", |m| m.as_str());
let to_rank = captures.get(5).map_or("", |m| m.as_str());
let is_promotion = captures.get(6).map_or(false, |_| true);
let promotion_piece = captures.get(7).map_or("", |m| m.as_str());
let mut from_idx: Option<PieceIndex> = None;
let idx = self
.convert_algebraic_notation_to_index(format!("{}{}", to_file, to_rank).as_str());
let to_idx = BOARD_MAP[idx as usize];
let target_piece = self.get(to_idx);
if is_capture
&& ((self.is_friendly(target_piece) && target_piece != EN_PASSANT_SQUARE)
|| target_piece == EMPTY)
{
return Err(MoveError::IllegalCapture);
}
let mut target_file = None;
let mut target_rank = None;
if !piece_identifer.is_empty() {
if is_number(piece_identifer) {
target_rank = Some(piece_identifer.parse::<u8>().unwrap());
} else {
target_file =
Some(FILES.iter().position(|f| f.eq(&piece_identifer)).unwrap() as u8);
}
}
if parts[0].is_uppercase() {
let mut count = 0;
let target_type = match parts[0] {
'K' => KING,
'Q' => QUEEN,
'R' => ROOK,
'B' => BISHOP,
'N' => KNIGHT,
_ => {
return Err(MoveError::UnknownPiece);
}
};
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
let piece_type = self.get_type(piece);
if piece_type == target_type && self.is_friendly(piece) {
count += 1;
let _moves = self.inner_moves(idx);
let file = idx & 7;
let rank = 8 - ((idx >> 4) + 1) + 1;
if !_moves.contains(&to_idx) {
continue;
}
if let Some(target_file) = target_file {
if file == target_file {
if count >= 2 && _moves.contains(&to_idx) {
return Err(MoveError::AmbiguousMoveNotation);
}
from_idx = Some(idx);
}
} else if let Some(target_rank) = target_rank {
if rank == target_rank {
if count >= 2 && _moves.contains(&to_idx) {
return Err(MoveError::AmbiguousMoveNotation);
}
from_idx = Some(idx);
}
} else {
if count >= 2 && _moves.contains(&to_idx) && from_idx != None {
return Err(MoveError::AmbiguousMoveNotation);
}
from_idx = Some(idx);
}
}
}
if let Some(from_idx) = from_idx {
self.inner_move_piece(from_idx, to_idx)
} else {
println!("{} {} {:?}", move_notation, self.get_fen(), self.board);
return Err(MoveError::InvalidPieceToMove);
}
} else {
let rank = 8 - ((to_idx >> 4) + 1) + 1;
if is_promotion && (rank > 1 && rank < 8) {
return Err(MoveError::InvalidPromotion);
}
if rank == 0 || rank == 8 {
if !is_promotion {
return Err(MoveError::InvalidPromotion);
}
if is_promotion {
if promotion_piece.is_empty() {
return Err(MoveError::InvalidPromotion);
}
}
}
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
let piece_type = self.get_type(piece);
if piece_type == PAWN && self.is_friendly(piece) {
let _moves = self.inner_moves(idx);
if !_moves.contains(&to_idx) {
continue;
}
from_idx = Some(idx);
}
}
if let Some(from_idx) = from_idx {
self.inner_move_piece(from_idx, to_idx);
if is_promotion {
let piece = match promotion_piece {
"K" => KING,
"Q" => QUEEN,
"R" => ROOK,
"B" => BISHOP,
"N" => KNIGHT,
_ => return Err(MoveError::InvalidPromotion),
};
self.set(piece, to_idx);
}
} else {
return Err(MoveError::InvalidPieceToMove);
}
}
}
if self.turn == BLACK {
self.full_moves += 1;
}
let fen = self.get_fen();
let position = fen.split(" ").collect::<Vec<&str>>()[0];
self.update_castling_rights();
self.change_turn();
let mut new_notation = move_notation.to_string();
Ok(new_notation)
}
pub fn inner_move_piece(&mut self, from_idx: PieceIndex, to_idx: PieceIndex) {
if self.is_on_board(to_idx) {
let mut piece = self.get(from_idx);
if piece == EMPTY || piece == EN_PASSANT_SQUARE {
panic!("can't move an empty square");
}
let to_piece = self.get(to_idx);
let piece_type = self.remove_color(piece);
let mut history_entry = HistoryEntry {
from_idx,
to_idx,
from_piece: self.get(from_idx),
to_piece,
kings: self.kings.clone(),
castle: false,
capture: false,
en_passant_capture: false,
en_passant_move: false,
promotion: false,
turn: self.turn,
last_turn: self.last_turn,
half_moves: self.half_moves,
full_moves: self.full_moves,
en_passant_square: self.lastest_en_passant_square.clone(),
can_white_king_side_castle: self.can_white_king_side_castle,
can_white_queen_side_castle: self.can_white_queen_side_castle,
can_black_king_side_castle: self.can_black_king_side_castle,
can_black_queen_side_castle: self.can_black_queen_side_castle,
};
if to_piece != EN_PASSANT_SQUARE {
self.clear_latest_en_passant_square();
}
if piece_type == PAWN {
piece = piece | MOVED_MASK;
if to_idx.abs_diff(from_idx) == 32 {
let idx = match to_idx {
i if i > from_idx => from_idx + 16,
i if i < from_idx => from_idx - 16,
_ => panic!("could not calculate en passant squares"),
};
self.set(EN_PASSANT_SQUARE, idx);
self.lastest_en_passant_square =
Some(self.convert_index_algebraic_notation(idx));
history_entry.en_passant_move = true;
}
self.reset_half_moves();
} else if piece_type == MOVED_PAWN {
if to_piece == EN_PASSANT_SQUARE {
if self.turn == BLACK {
self.capture(self.get(to_idx - 16));
self.set(Piece::EMPTY, to_idx - 16);
} else {
self.capture(self.get(to_idx + 16));
self.set(Piece::EMPTY, to_idx + 16);
}
self.captures += 1;
history_entry.capture = true;
history_entry.en_passant_capture = true;
self.reset_half_moves();
}
let rank = 8 - ((to_idx >> 4) + 1) + 1;
if rank == 1 || rank == 8 {
history_entry.promotion = true;
}
self.reset_half_moves();
self.lastest_en_passant_square = None;
} else if piece_type == KING {
self.update_kings_position(to_idx);
piece = piece | MOVED_MASK;
let (can_king_side_castle, can_queen_side_castle) = self.get_castling_rights();
if can_king_side_castle || can_queen_side_castle {
if self.is_king_side_castling(from_idx, to_idx) && can_king_side_castle {
self.set(Piece::ROOK | self.turn | MOVED_MASK, to_idx - 1);
self.set(Piece::EMPTY, to_idx + 1);
history_entry.castle = true;
self.castles += 1;
} else if self.is_queen_side_castling(from_idx, to_idx) && can_queen_side_castle
{
self.set(Piece::ROOK | self.turn | MOVED_MASK, to_idx + 1);
self.set(Piece::EMPTY, to_idx - 2);
history_entry.castle = true;
self.castles += 1;
}
}
} else if piece_type == MOVED_KING {
self.update_kings_position(to_idx);
} else if piece_type == ROOK {
piece = piece | MOVED_MASK;
} else {
self.half_moves += 1;
}
if self.is_occupied(to_idx) {
if !self.is_friendly(to_piece) {
self.capture(to_piece);
history_entry.capture = true;
self.reset_half_moves();
self.captures += 1;
}
}
if to_piece == EN_PASSANT_SQUARE {
self.lastest_en_passant_square = None;
}
self.set(piece, to_idx);
self.set(Piece::EMPTY, from_idx);
self.history.push(history_entry);
} else {
}
}
fn get(&self, square_idx: PieceIndex) -> PieceIndex {
if !self.is_on_board(square_idx) || square_idx > 150 {
println!("lmao");
panic!("square out of bound");
}
self.board[square_idx as usize]
}
pub fn set(&mut self, piece: PieceType, square_idx: PieceIndex) {
if !self.is_on_board(square_idx) || square_idx > 150 {
panic!("square out of bound");
}
if piece == KING {
self.kings.white = square_idx;
}
if piece == BLACK_KING {
self.kings.black = square_idx;
}
self.board[square_idx as usize] = piece;
}
pub fn moves(&mut self, square: &str) -> Vec<String> {
let square_idx = BOARD_MAP[self.convert_algebraic_notation_to_index(square) as usize];
if !self.is_on_board(square_idx) {
panic!("invalid square");
}
let moves = self.inner_moves(square_idx);
let piece = self.get(square_idx);
let piece_type = self.get_type(piece);
let prefix = match piece_type {
KING => "K",
QUEEN => "Q",
ROOK => "R",
BISHOP => "B",
KNIGHT => "N",
PAWN => "",
_ => {
panic!("invalid piece")
}
};
let mut a: Vec<String> = moves
.iter()
.map(|m| {
if *m == 118 && piece_type == KING {
return String::from("O-O");
} else if *m == 114 && piece_type == KING {
return String::from("O-O-O");
} else {
let to = self.get(*m);
let rank = 8 - ((*m >> 4) + 1) + 1;
if (rank == 1 || rank == 8) && piece_type == PAWN {
return m.to_string();
}
if to == EN_PASSANT_SQUARE && piece_type == PAWN {
let file = square_idx & 7;
return format!(
"{}x{}",
FILES[file as usize],
self.convert_index_algebraic_notation(*m)
);
}
if self.is_occupied(*m) && !self.is_friendly(to) {
return format!("{}x{}", prefix, self.convert_index_algebraic_notation(*m));
}
return format!("{}{}", prefix, self.convert_index_algebraic_notation(*m));
}
})
.collect();
let mut t_idx: Option<usize> = None;
let mut temp_a = a.clone();
a
}
pub fn inner_moves(&mut self, square_idx: PieceIndex) -> Vec<PieceIndex> {
let piece = self.get(square_idx);
if !self.is_friendly(piece) {
panic!("can't generate inner_moves for enemy piece, set the turn correctly.");
}
if piece == EMPTY || piece == EN_PASSANT_SQUARE {
panic!("can't generate moves for empty squares")
}
let inner_moves = match self.remove_color(piece) {
PAWN => self.generate_pawn_moves(square_idx),
MOVED_PAWN => self.generate_pawn_moves(square_idx),
BISHOP => self.generate_sliding_moves(square_idx, BISHOP_DELTAS.to_vec()),
ROOK => self.generate_sliding_moves(square_idx, ROOK_DELTAS.to_vec()),
QUEEN => self.generate_sliding_moves(square_idx, QUEEN_DELTAS.to_vec()),
KNIGHT => self.generate_knight_moves(square_idx),
KING => self.generate_king_moves(square_idx, KING_DELTAS.to_vec()),
MOVED_KING => self.generate_king_moves(square_idx, MOVED_KING_DELTAS.to_vec()),
MOVED_ROOK => self.generate_sliding_moves(square_idx, ROOK_DELTAS.to_vec()),
_ => vec![],
};
let mut legal_moves: Vec<PieceIndex> = vec![];
for idx in inner_moves {
let to_idx = idx;
self.inner_move_piece(square_idx, to_idx as u8);
if !self.in_check() {
legal_moves.push(to_idx);
}
self.undo();
}
legal_moves
}
pub fn generate_pawn_moves(&mut self, square_idx: PieceIndex) -> Vec<u8> {
let mut inner_moves = vec![];
let pawn = self.get(square_idx);
let deltas = match pawn {
PAWN => WHITE_PAWN_DELTAS,
BLACK_PAWN => BLACK_PAWN_DELTAS,
MOVED_PAWN => MOVED_WHITE_PAWN_DELTAS,
MOVED_BLACK_PAWN => MOVED_BLACK_PAWN_DELTAS,
_ => panic!("piece is not a pawn"),
};
let mut can_move_forward = true;
for delta in deltas {
if delta == 0 {
continue;
}
let destination_idx = square_idx as i16 + delta as i16;
let destination_idx = destination_idx as u8;
if self.is_on_board(destination_idx) {
let piece = self.get(destination_idx);
let rank = 8 - ((destination_idx as u8 >> 4) + 1) + 1;
if rank == 1 || rank == 8 {
}
if delta % 2 != 0 {
if self.is_occupied(destination_idx) {
let is_enemy = !self.is_friendly(piece);
if is_enemy {
inner_moves.push(destination_idx);
}
} else if piece == EN_PASSANT_SQUARE {
if self.turn == WHITE {
let below = self.get(destination_idx + 16);
if !self.is_friendly(below) && self.get_type(below) == PAWN {
inner_moves.push(destination_idx);
}
} else {
let above = self.get(destination_idx - 16);
if !self.is_friendly(above) && self.get_type(above) == PAWN {
inner_moves.push(destination_idx);
}
}
}
} else {
if self.is_occupied(destination_idx) {
can_move_forward = false;
}
if can_move_forward {
inner_moves.push(destination_idx);
}
}
}
}
inner_moves
}
pub fn generate_king_moves(&mut self, square_idx: PieceIndex, deltas: Vec<i8>) -> Vec<u8> {
let mut inner_moves: Vec<PieceIndex> = vec![];
for delta in deltas {
let destination_idx = (square_idx as i16 + delta as i16) as PieceIndex;
if self.is_on_board(destination_idx) {
let piece = self.get(destination_idx);
let is_friendly = self.is_friendly(piece);
if self.is_occupied(destination_idx) {
let is_castling_move = self.is_king_side_castling(square_idx, destination_idx)
|| self.is_queen_side_castling(square_idx, destination_idx);
if is_castling_move {
continue;
}
if !is_friendly || piece == EN_PASSANT_SQUARE {
inner_moves.push(destination_idx);
}
} else {
let is_king_side_castling =
self.is_king_side_castling(square_idx, destination_idx);
let is_queen_side_castling =
self.is_queen_side_castling(square_idx, destination_idx);
let (can_king_side_castle, can_queen_side_castle) = self.get_castling_rights();
if is_king_side_castling && !can_king_side_castle {
continue;
}
if is_queen_side_castling && !can_queen_side_castle {
continue;
}
if is_king_side_castling || is_queen_side_castling {
let rook_idx: PieceIndex = if is_king_side_castling {
destination_idx + 1
} else if is_queen_side_castling {
destination_idx - 2
} else {
panic!("failed to castle")
};
let piece = self.get(rook_idx);
let mut is_checked = false;
let mut is_blocked = false;
let mut idx = square_idx;
let mut idx_2 = square_idx;
let block_range = if is_king_side_castling {
0..3
} else if is_queen_side_castling {
0..4
} else {
0..2
};
let attack_range = if is_king_side_castling {
0..3
} else if is_queen_side_castling {
0..3
} else {
0..2
};
for _ in block_range {
if idx_2 != square_idx && self.get(idx_2) != EMPTY {
is_blocked = true;
}
if is_king_side_castling {
idx_2 += 1;
}
if is_queen_side_castling {
idx_2 -= 1;
}
}
for _ in attack_range {
let is_attacked = self.is_attacked(idx);
if is_attacked {
is_checked = true;
}
if is_king_side_castling {
idx += 1;
}
if is_queen_side_castling {
idx -= 1;
}
}
if piece != EMPTY
&& self.remove_color(piece) == ROOK
&& self.is_friendly(piece)
&& !is_checked
&& !is_blocked
{
inner_moves.push(destination_idx);
}
} else {
inner_moves.push(destination_idx);
}
}
}
}
inner_moves
}
pub fn generate_knight_moves(&mut self, square_idx: PieceIndex) -> Vec<u8> {
let mut inner_moves: Vec<PieceIndex> = vec![];
for delta in KNIGHT_DELTAS {
let destination_idx = square_idx as i16 + delta as i16;
if self.is_on_board(destination_idx as u8) {
let piece = self.get(destination_idx as PieceIndex);
if self.is_occupied(destination_idx as PieceIndex) {
if self.is_friendly(piece) {
continue;
}
}
inner_moves.push(destination_idx as PieceIndex);
}
}
inner_moves
}
pub fn generate_sliding_moves(&mut self, square_idx: PieceIndex, deltas: Vec<i8>) -> Vec<u8> {
let mut inner_moves: Vec<PieceIndex> = vec![];
for delta in deltas {
let mut destination_idx = square_idx as i16 + delta as i16;
while self.is_on_board(destination_idx as PieceIndex) {
let piece = self.get(destination_idx as PieceIndex);
if self.is_occupied(destination_idx as PieceIndex) {
if self.is_friendly(piece) {
break;
} else {
inner_moves.push(destination_idx as PieceIndex);
break;
}
}
inner_moves.push(destination_idx as PieceIndex);
destination_idx += delta as i16;
}
}
inner_moves
}
pub fn undo(&mut self) {
if let Some(old) = self.history.pop() {
if old.castle {
let HistoryEntry {
to_idx, from_idx, ..
} = old;
let king_side = self.is_king_side_castling(from_idx, to_idx);
let queen_side = self.is_queen_side_castling(from_idx, to_idx);
if king_side {
self.set(ROOK | old.turn, to_idx + 1);
self.set(EMPTY, from_idx + 1);
} else if queen_side {
self.set(ROOK | old.turn, to_idx - 2);
self.set(EMPTY, from_idx - 1);
}
}
if old.capture {
if old.turn == WHITE {
self.white_captures.pop();
} else {
self.black_captures.pop();
}
}
if old.en_passant_capture {
let from_idx = old.from_idx;
let idx = match old.to_idx {
i if i < from_idx => old.to_idx + 16,
i if i > from_idx => old.to_idx - 16,
_ => panic!("could not calculate en passant squares"),
};
self.set(EN_PASSANT_SQUARE, idx);
let piece = old.from_piece;
if piece == MOVED_PAWN {
self.set(MOVED_BLACK_PAWN, old.to_idx + 16);
} else if piece == MOVED_BLACK_PAWN {
self.set(MOVED_PAWN, old.to_idx - 16);
}
}
if old.en_passant_move {
let from_idx = old.from_idx;
let idx = match old.to_idx {
i if i < from_idx => old.to_idx + 16,
i if i > from_idx => old.to_idx - 16,
_ => panic!("could not calculate en passant squares"),
};
self.set(EMPTY, idx);
}
self.can_black_king_side_castle = old.can_black_king_side_castle;
self.can_black_queen_side_castle = old.can_black_queen_side_castle;
self.can_white_king_side_castle = old.can_white_king_side_castle;
self.can_white_queen_side_castle = old.can_white_queen_side_castle;
self.half_moves = old.half_moves;
self.full_moves = old.full_moves;
self.turn = old.turn;
self.last_turn = old.last_turn;
self.kings = old.kings.clone();
self.lastest_en_passant_square = old.en_passant_square;
if let Some(ep_square) = &self.lastest_en_passant_square {
let ep_idx = BOARD_MAP
[self.convert_algebraic_notation_to_index(ep_square.as_str()) as usize];
if self.get(ep_idx) == EMPTY {
self.set(EN_PASSANT_SQUARE, ep_idx);
}
}
self.set(old.from_piece, old.from_idx);
self.set(old.to_piece, old.to_idx);
}
}
pub fn get_fen(&self) -> String {
let mut fen = String::from("");
let turn = self.turn();
let mut castling_rights = String::new();
let en_passant_square = if let Some(sq) = self.lastest_en_passant_square.clone() {
sq
} else {
"-".to_string()
};
let half_moves = self.half_moves;
let full_moves = self.full_moves;
if self.can_white_king_side_castle {
castling_rights.push_str("K");
}
if self.can_white_queen_side_castle {
castling_rights.push_str("Q");
}
if self.can_black_king_side_castle {
castling_rights.push_str("k");
}
if self.can_black_queen_side_castle {
castling_rights.push_str("q")
}
if castling_rights.is_empty() {
castling_rights.push_str("-");
}
let mut empty_square: u8 = 0;
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
if self.is_occupied(idx) {
if empty_square != 0 {
fen.push_str(empty_square.to_string().as_str());
empty_square = 0;
}
let piece_type = self.get_type(piece);
if self.get_color(piece) == WHITE {
match piece_type {
PAWN => fen.push_str("P"),
ROOK => fen.push_str("R"),
KNIGHT => fen.push_str("N"),
BISHOP => fen.push_str("B"),
QUEEN => fen.push_str("Q"),
KING => fen.push_str("K"),
_ => panic!("error generating FEN"),
}
} else {
match piece_type {
PAWN => fen.push_str("p"),
ROOK => fen.push_str("r"),
KNIGHT => fen.push_str("n"),
BISHOP => fen.push_str("b"),
QUEEN => fen.push_str("q"),
KING => fen.push_str("k"),
_ => panic!("error generating FEN"),
}
}
} else {
empty_square += 1;
}
if (idx + 1) % 8 == 0 {
if empty_square != 0 {
fen.push_str(empty_square.to_string().as_str());
empty_square = 0;
}
if idx != 119 {
fen.push('/');
}
}
}
vec![
fen,
turn.to_string(),
castling_rights,
en_passant_square,
half_moves.to_string(),
full_moves.to_string(),
]
.join(" ")
}
pub fn load_fen(&mut self, fen: String) {
let fen_parts: Vec<&str> = fen.split(" ").collect();
let ranks: Vec<&str> = fen_parts[0].split("/").collect();
let mut idx: usize = 0;
for rank in ranks {
for piece in rank.chars() {
match piece {
'p' => {
let rank = 8 - ((BOARD_MAP[idx] >> 4) + 1) + 1;
if rank != 7 {
self.set(MOVED_BLACK_PAWN, BOARD_MAP[idx])
} else {
self.set(BLACK_PAWN, BOARD_MAP[idx])
}
}
'r' => self.set(BLACK_ROOK, BOARD_MAP[idx]),
'n' => self.set(BLACK_KNIGHT, BOARD_MAP[idx]),
'b' => self.set(BLACK_BISHOP, BOARD_MAP[idx]),
'q' => self.set(BLACK_QUEEN, BOARD_MAP[idx]),
'k' => self.set(BLACK_KING, BOARD_MAP[idx]),
'P' => {
let rank = 8 - ((BOARD_MAP[idx] >> 4) + 1) + 1;
if rank != 2 {
self.set(MOVED_PAWN, BOARD_MAP[idx])
} else {
self.set(PAWN, BOARD_MAP[idx])
}
}
'R' => self.set(ROOK, BOARD_MAP[idx]),
'N' => self.set(KNIGHT, BOARD_MAP[idx]),
'B' => self.set(BISHOP, BOARD_MAP[idx]),
'Q' => self.set(QUEEN, BOARD_MAP[idx]),
'K' => self.set(KING, BOARD_MAP[idx]),
'1'..='8' => idx += piece.to_digit(10).unwrap() as usize - 1,
_ => panic!("can't load fen pieces"),
}
idx += 1;
}
}
match fen_parts[1] {
"w" => self.set_turn(WHITE),
"b" => self.set_turn(BLACK),
_ => panic!("can't load fen turn"),
}
for castling_right in fen_parts[2].chars() {
match castling_right {
'K' => {
self.can_white_king_side_castle = true;
}
'Q' => {
self.can_white_queen_side_castle = true;
}
'k' => {
self.can_black_king_side_castle = true;
}
'q' => {
self.can_black_queen_side_castle = true;
}
'-' => {
self.can_white_king_side_castle = false;
self.can_white_queen_side_castle = false;
self.can_black_king_side_castle = false;
self.can_black_queen_side_castle = false;
}
_ => panic!("cant load fen castling rights"),
}
}
let square = fen_parts[3];
match square {
"-" => {
self.lastest_en_passant_square = None;
}
_ => {
self.lastest_en_passant_square = Some(square.to_string());
let idx = self.convert_algebraic_notation_to_index(square) as usize;
self.set(EN_PASSANT_SQUARE, BOARD_MAP[idx]);
}
}
self.half_moves = fen_parts[4].parse().unwrap();
self.full_moves = fen_parts[5].parse().unwrap();
*self
.unique_positions
.entry(fen_parts[0].to_string())
.or_insert(0) += 1;
}
pub fn in_check(&self) -> bool {
let king_idx = match self.turn {
WHITE => self.kings.white,
BLACK => self.kings.black,
_ => panic!("Turn cannot be determined when checking if king is attacked"),
};
return self.is_attacked(king_idx);
}
pub fn is_checkmate(&mut self) -> bool {
let mut no_legal_moves = true;
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
if self.is_friendly(piece) {
let inner_moves = self.inner_moves(idx);
if inner_moves.len() > 0 {
no_legal_moves = false;
}
}
}
self.in_check() && no_legal_moves
}
pub fn is_draw(&mut self) -> bool {
self.is_stalemate()
|| self.is_threefold_repetition()
|| self.is_50_moves_rule()
|| self.is_insufficient_materials()
}
pub fn is_stalemate(&mut self) -> bool {
let mut no_legal_moves = true;
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
if (piece != EMPTY || piece != EN_PASSANT_SQUARE) && self.is_friendly(piece) {
let inner_moves = self.inner_moves(idx);
if inner_moves.len() > 0 {
no_legal_moves = false;
}
}
}
!self.in_check() && no_legal_moves
}
pub fn is_threefold_repetition(&mut self) -> bool {
let fen = self.get_fen();
let position = fen.split(" ").collect::<Vec<&str>>()[0];
if let Some(count) = self.unique_positions.get(position) {
*count >= 3
} else {
false
}
}
pub fn is_50_moves_rule(&mut self) -> bool {
self.half_moves >= 100
}
pub fn is_insufficient_materials(&mut self) -> bool {
let mut friendly_knights = 0;
let mut friendly_bishops = 0;
let mut friendly_dark_bishops = 0;
let mut friendly_light_bishops = 0;
let mut enemy_dark_bishops = 0;
let mut enemy_light_bishops = 0;
let mut enemy_knights = 0;
let mut enemy_bishops = 0;
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
let file = (idx & 7) + 1;
let rank = 8 - ((idx >> 4) + 1) + 1;
if piece != EMPTY {
let piece_without_color = self.remove_color(piece);
if piece_without_color == PAWN
|| piece_without_color == ROOK
|| piece_without_color == QUEEN
{
return false;
}
if self.is_friendly(piece) {
if piece_without_color == KNIGHT {
friendly_knights += 1;
}
if piece_without_color == BISHOP {
if (rank % 2 != 0 && file % 2 != 0) || (rank % 2 == 0 && file % 2 == 0) {
friendly_dark_bishops += 1;
if enemy_light_bishops >= 1 {
return false;
}
}
if (rank % 2 == 0 && file % 2 != 0) || (rank % 2 != 0 && file % 2 == 0) {
friendly_light_bishops += 1;
if enemy_dark_bishops >= 1 {
return false;
}
}
friendly_bishops += 1;
}
} else {
if piece_without_color == KNIGHT {
enemy_knights += 1;
}
if piece_without_color == BISHOP {
if (rank % 2 != 0 && file % 2 != 0) || (rank % 2 == 0 && file % 2 == 0) {
enemy_dark_bishops += 1;
if friendly_light_bishops >= 1 {
return false;
}
}
if (rank % 2 == 0 && file % 2 != 0) || (rank % 2 != 0 && file % 2 == 0) {
enemy_light_bishops += 1;
if friendly_dark_bishops >= 1 {
return false;
}
}
enemy_bishops += 1;
}
}
}
}
if friendly_knights == 0
&& friendly_bishops == 0
&& enemy_knights == 0
&& enemy_bishops == 0
{
return true;
}
if friendly_knights == 0 && enemy_knights == 0 {
if friendly_bishops == 2 || enemy_bishops == 2 {
return false;
}
return true;
}
if friendly_bishops == 0 && enemy_bishops == 0 {
if friendly_knights >= 1 && enemy_knights >= 1 {
return false;
}
return true;
}
return false;
}
pub fn is_attacked(&self, square_idx: PieceIndex) -> bool {
let mut is_attacked = false;
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
if is_attacked {
break;
}
let piece = self.get(idx);
let attacker_idx = idx;
let defender_idx = square_idx;
let defender_piece = self.get(defender_idx);
if (self.is_friendly(piece) && piece != EN_PASSANT_SQUARE)
|| piece == EMPTY
|| piece == EN_PASSANT_SQUARE
{
continue;
}
let diff = (defender_idx as i16 - attacker_idx as i16) + 119;
let attack_bits_mask = ATTACKS[diff as usize];
if attack_bits_mask != 0 {
let piece = self.get(attacker_idx);
let piece_type = self.get_type(piece);
if (piece_type & attack_bits_mask) == piece_type {
if piece_type == KNIGHT {
return true;
} else if piece_type == PAWN {
let piece = self.remove_mask(piece, MOVED_MASK);
is_attacked = (piece & attack_bits_mask == piece)
&& self.get_color(attack_bits_mask) == self.get_color(piece);
} else {
let delta = DELTAS[diff as usize];
let mut destination_idx = attacker_idx as i16 + delta as i16;
while self.is_on_board(destination_idx as PieceIndex) {
let piece = self.get(destination_idx as PieceIndex);
if piece == defender_piece
&& destination_idx as PieceIndex == defender_idx
{
is_attacked = true;
break;
} else {
if self.is_occupied(destination_idx as u8) {
break;
}
}
destination_idx += delta as i16;
}
}
}
}
}
return is_attacked;
}
pub fn turn(&self) -> char {
match self.turn {
WHITE => 'w',
BLACK => 'b',
_ => panic!("Unknown turn"),
}
}
pub fn set_turn(&mut self, turn: u8) {
if turn == WHITE {
self.turn = WHITE;
} else {
self.turn = BLACK;
}
}
pub fn convert_algebraic_notation_to_index(&self, notation: &str) -> u8 {
let mut parts = notation.chars();
let file = parts.next().unwrap();
let rank = (parts.next().unwrap().to_digit(10).unwrap() - 1) as u8;
let file = FILES
.iter()
.position(|f| f.eq(&file.to_string().as_str()))
.unwrap() as u8;
(8 * (7 - rank) + file) as u8
}
pub fn convert_index_algebraic_notation(&self, index: u8) -> String {
let file = index & 7;
let rank = 8 - ((index >> 4) + 1) + 1;
let file_letter = FILES[file as usize];
let mut notation = String::new();
notation.push_str(file_letter);
notation.push_str(rank.to_string().as_str());
notation
}
pub fn perft(&mut self, depth: u8, yah: bool) -> u64 {
let mut nodes: u64 = 0;
let mut cnt = 0;
if depth <= 0 {
if self.in_check() {
self.checks += 1;
}
return 1;
}
let moves = self.generate_legal_moves();
for _move in moves {
self.inner_move_piece(_move.from, _move.to);
let from = self.convert_index_algebraic_notation(_move.from);
let to = self.convert_index_algebraic_notation(_move.to);
self.change_turn();
self.update_castling_rights();
cnt = self.perft(depth - 1, false);
nodes += cnt;
if yah {
println!("{} {}", format!("{}{}", from, to), cnt);
*self.moves.entry(format!("{}{}", from, to)).or_insert(0) = cnt;
} else {
}
self.undo()
}
return nodes;
}
pub fn generate_legal_moves(&mut self) -> Vec<Move> {
let mut moves = vec![];
for idx in 0..BOARD_SIZE {
if !self.is_on_board(idx) {
continue;
}
let piece = self.get(idx);
if piece == EN_PASSANT_SQUARE {
continue;
}
if self.is_occupied(idx) && self.is_friendly(piece) {
for _move in self.inner_moves(idx) {
if _move > 150 {
continue;
}
moves.push(Move {
from: idx,
to: _move,
})
}
}
}
moves
}
pub fn change_turn(&mut self) {
if self.turn == WHITE {
self.last_turn = WHITE;
self.set_turn(BLACK);
} else {
self.last_turn = BLACK;
self.set_turn(WHITE);
}
}
pub fn clear(&mut self) {
*self = Self::new();
}
fn reset_half_moves(&mut self) {
self.half_moves = 0;
}
fn is_occupied(&self, idx: PieceIndex) -> bool {
let piece = self.get(idx);
piece != EMPTY && piece != EN_PASSANT_SQUARE
}
fn clear_latest_en_passant_square(&mut self) {
if let Some(sq) = &self.lastest_en_passant_square {
let idx = BOARD_MAP[self.convert_algebraic_notation_to_index(sq.as_str()) as usize];
let piece = self.get(idx);
if piece == EN_PASSANT_SQUARE {
self.set(EMPTY, idx);
self.lastest_en_passant_square = None;
}
}
}
fn get_type(&self, piece: PieceType) -> PieceType {
self.remove_mask(self.remove_color(piece), MOVED_MASK)
}
fn get_color(&self, piece: PieceType) -> PieceType {
piece & COLOR_MASK
}
fn remove_color(&self, piece: PieceType) -> PieceType {
self.remove_mask(piece, COLOR_MASK)
}
pub fn get_castling_rights_tests(&self) -> (bool, bool, bool, bool) {
(
self.can_white_king_side_castle,
self.can_white_queen_side_castle,
self.can_black_king_side_castle,
self.can_black_queen_side_castle,
)
}
pub fn get_castling_rights(&self) -> (bool, bool) {
if self.turn == WHITE {
(
self.can_white_king_side_castle,
self.can_white_queen_side_castle,
)
} else {
(
self.can_black_king_side_castle,
self.can_black_queen_side_castle,
)
}
}
pub fn update_castling_rights(&mut self) {
if self.kings.white != 116 {
self.can_white_king_side_castle = false;
self.can_white_queen_side_castle = false;
}
if self.kings.black != 4 {
self.can_black_king_side_castle = false;
self.can_black_queen_side_castle = false;
}
let right_white_rook_idx = 119;
let left_white_rook_idx = 112;
let right_black_rook_idx = 7;
let left_black_rook_idx = 0;
if self.get(right_white_rook_idx) != ROOK {
self.can_white_king_side_castle = false;
}
if self.get(left_white_rook_idx) != ROOK {
self.can_white_queen_side_castle = false;
}
if self.get(right_black_rook_idx) != BLACK_ROOK {
self.can_black_king_side_castle = false;
}
if self.get(left_black_rook_idx) != BLACK_ROOK {
self.can_black_queen_side_castle = false;
}
}
fn is_queen_side_castling(&self, from: PieceIndex, to: PieceIndex) -> bool {
to as i8 - from as i8 == -2
}
fn is_king_side_castling(&self, from: PieceIndex, to: PieceIndex) -> bool {
to as i8 - from as i8 == 2
}
fn update_kings_position(&mut self, new_idx: PieceIndex) {
if self.turn == WHITE {
self.kings.white = new_idx;
} else {
self.kings.black = new_idx;
}
}
fn capture(&mut self, piece: PieceType) {
if self.turn == WHITE {
self.white_captures.push(piece);
} else {
self.black_captures.push(piece);
}
}
fn remove_mask(&self, piece: PieceType, mask: u8) -> u8 {
(piece | mask) ^ mask
}
fn is_friendly(&self, piece: PieceType) -> bool {
piece & COLOR_MASK == self.turn
}
fn is_on_board(&self, square_idx: PieceIndex) -> bool {
(square_idx) & 0x88 == 0
}
}
#[cfg(test)]
mod bishop {
use super::*;
#[test]
fn bishop_can_move_freely_if_king_is_not_checked() {
let mut chess = Chess::new();
chess.set(BISHOP, 52);
chess.set(KING, 55);
chess.set(BISHOP | BLACK, 86);
let inner_moves = chess.inner_moves(52);
let correct_moves = [69, 86, 67, 82, 97, 112, 35, 18, 1, 37, 22, 7];
assert!(inner_moves.iter().eq(correct_moves.iter()));
}
#[test]
fn bishop_can_not_move_because_king_is_checked() {
let mut chess = Chess::new();
chess.set(BISHOP, 117);
chess.set(KING, 112);
chess.set(BLACK_BISHOP, 97);
let inner_moves = chess.inner_moves(117);
assert_eq!(inner_moves, []);
}
#[test]
fn bishop_can_take_enemy_piece_to_stop_check() {
let mut chess = Chess::new();
chess.set(BISHOP, 2);
chess.set(KING, 0);
chess.set(BISHOP | BLACK, 17);
let inner_moves = chess.inner_moves(2 as u8);
let correct_moves = [17];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set(BISHOP, 99);
chess.set(KING, 37);
chess.set(BISHOP | BLACK, 54);
let inner_moves = chess.inner_moves(99 as u8);
let correct_moves = [54];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set_turn(BLACK);
chess.set(BISHOP, 32);
chess.set(KING | BLACK, 66);
chess.set(BISHOP | BLACK, 17);
let inner_moves = chess.inner_moves(17);
let correct_moves = [32];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set_turn(BLACK);
chess.set(BISHOP, 67);
chess.set(KING | BLACK, 7);
chess.set(BISHOP | BLACK, 118);
let inner_moves = chess.inner_moves(118);
let correct_moves = [67];
assert!(inner_moves.iter().eq(correct_moves.iter()));
}
#[test]
fn bishop_can_move_freely_if_king_is_shielded_from_check() {
let mut chess = Chess::new();
chess.set(BISHOP, 51);
chess.set(KING, 7);
chess.set(PAWN, 37);
chess.set(BISHOP | BLACK, 67);
let inner_moves = chess.inner_moves(51);
let correct_moves = [68, 85, 102, 119, 66, 81, 96, 34, 17, 0, 36, 21, 6];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set(BISHOP, 118);
chess.set(KING, 119);
chess.set(PAWN, 102);
chess.set(BISHOP | BLACK, 85);
let inner_moves = chess.inner_moves(118);
let correct_moves = [101, 84, 67, 50, 33, 16, 103];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set(BISHOP, 66);
chess.set(KING, 17);
chess.set(PAWN, 34);
chess.set(BISHOP | BLACK, 51);
let inner_moves = chess.inner_moves(66);
let correct_moves = [83, 100, 117, 81, 96, 49, 32, 51];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set_turn(BLACK);
chess.set(BISHOP, 64);
chess.set(KING | BLACK, 4);
chess.set(PAWN | BLACK, 19);
chess.set(BISHOP | BLACK, 84);
let inner_moves = chess.inner_moves(84);
let correct_moves = [101, 118, 99, 114, 67, 50, 33, 16, 69, 54, 39];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set_turn(BLACK);
chess.set(BISHOP, 53);
chess.set(KING | BLACK, 87);
chess.set(PAWN | BLACK, 70);
chess.set(BISHOP | BLACK, 98);
let inner_moves = chess.inner_moves(98);
let correct_moves = [115, 113, 81, 64, 83, 68, 53];
assert!(inner_moves.iter().eq(correct_moves.iter()));
}
}
#[cfg(test)]
mod pawn {
use super::*;
#[test]
fn pawn_valid_moves() {
let mut chess = Chess::new();
chess.set_turn(BLACK);
chess.set(Piece::BLACK_PAWN, 98);
let inner_moves = chess.inner_moves(98);
let correct_moves = [114];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set_turn(BLACK);
chess.set(Piece::BLACK_PAWN, 2);
let inner_moves = chess.inner_moves(2);
let correct_moves = [18, 34];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set(Piece::PAWN, 34);
let inner_moves = chess.inner_moves(34);
let correct_moves = [18, 2];
assert!(inner_moves.iter().eq(correct_moves.iter()));
chess.clear();
chess.set(Piece::PAWN, 23);
let inner_moves = chess.inner_moves(23);
let correct_moves = [7];
assert!(inner_moves.iter().eq(correct_moves.iter()));
}
}
#[cfg(test)]
mod tests {
#[test]
fn lol() {}
}