use super::representation::Board;
use super::representation::CastlingRights;
use super::*;
use crate::engine;
use crate::evaluation::mobility::EvalAux;
use crate::evaluation::mobility::PieceMobility;
use crate::utils::assert_fast;
use crate::utils::bitflags::BitFlags;
use crate::utils::bithelpers::BitHelpers;
use crate::utils::panic_fast;
use crate::utils::rand;
use crate::Moves;
use std::cmp;
use std::fmt::Display;
use std::fmt::Formatter;
#[allow(non_snake_case)]
pub mod MoveFlags {
pub const SINGLE_PUSH: u8 = 0;
pub const DOUBLE_PUSH: u8 = 1;
pub const SHORT_CASTLING: u8 = 2;
pub const LONG_CASTLING: u8 = 3;
pub const CAPTURE: u8 = 4;
pub const EN_PASSANT: u8 = 5;
pub const UNDEFINED1: u8 = 6;
pub const UNDEFINED2: u8 = 7;
pub const KNIGHT_PROMOTION: u8 = 8;
pub const BISHOP_PROMOTION: u8 = 9;
pub const ROOK_PROMOTION: u8 = 10;
pub const QUEEN_PROMOTION: u8 = 11;
pub const KNIGHT_PROMOTION_CAPTURE: u8 = 12;
pub const BISHOP_PROMOTION_CAPTURE: u8 = 13;
pub const ROOK_PROMOTION_CAPTURE: u8 = 14;
pub const QUEEN_PROMOTION_CAPTURE: u8 = 15;
pub const BIT_SPECIAL_0: u8 = 1;
pub const BIT_SPECIAL_1: u8 = 2;
pub const BIT_CAPTURE: u8 = 4;
pub const BIT_PROMOTION: u8 = 8;
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Move {
pub data: u16,
}
impl Move {
pub fn new(from: usize, to: usize, flags: u8) -> Self {
Self { data: ((flags as u16) << 12) | ((to as u16) << 6) | (from as u16) }
}
pub const fn new_from_raw(data: u16) -> Self {
Self { data }
}
pub fn new_random() -> Self {
let from = rand::usize(ALL_SQUARES);
let to = rand::usize(ALL_SQUARES);
let mut flags = MoveFlags::UNDEFINED1;
loop {
if flags == MoveFlags::UNDEFINED1 || flags == MoveFlags::UNDEFINED2 {
flags = rand::u8(0..16);
} else {
break;
}
}
Move::new(from, to, flags)
}
pub fn get_from(&self) -> usize {
(self.data % 64) as usize
}
pub fn get_to(&self) -> usize {
((self.data >> 6) % 64) as usize
}
pub fn get_flags(&self) -> u8 {
(self.data >> 12) as u8
}
pub fn get_promotion_piece(&self) -> usize {
let flags = self.get_flags();
match self.get_flags() {
MoveFlags::KNIGHT_PROMOTION | MoveFlags::KNIGHT_PROMOTION_CAPTURE => KNIGHT,
MoveFlags::BISHOP_PROMOTION | MoveFlags::BISHOP_PROMOTION_CAPTURE => BISHOP,
MoveFlags::ROOK_PROMOTION | MoveFlags::ROOK_PROMOTION_CAPTURE => ROOK,
MoveFlags::QUEEN_PROMOTION | MoveFlags::QUEEN_PROMOTION_CAPTURE => QUEEN,
_ => panic_fast!("Invalid value: flags={:?}", flags),
}
}
pub fn is_some(&self) -> bool {
self.data != 0
}
pub fn is_empty(&self) -> bool {
self.data == 0
}
pub fn is_single_push(&self) -> bool {
self.get_flags() == MoveFlags::SINGLE_PUSH
}
pub fn is_double_push(&self) -> bool {
self.get_flags() == MoveFlags::DOUBLE_PUSH
}
pub fn is_quiet(&self) -> bool {
self.get_flags() == MoveFlags::SINGLE_PUSH || self.get_flags() == MoveFlags::DOUBLE_PUSH
}
pub fn is_capture(&self) -> bool {
self.get_flags().contains(MoveFlags::CAPTURE)
}
pub fn is_en_passant(&self) -> bool {
self.get_flags() == MoveFlags::EN_PASSANT
}
pub fn is_promotion(&self) -> bool {
self.get_flags().contains(MoveFlags::BIT_PROMOTION)
}
pub fn is_castling(&self) -> bool {
self.get_flags() == MoveFlags::SHORT_CASTLING || self.get_flags() == MoveFlags::LONG_CASTLING
}
pub fn is_legal(&self, board: &Board) -> bool {
let from = self.get_from();
let from_bb = 1u64 << from;
let to = self.get_to();
let to_bb = 1u64 << to;
let piece = board.get_piece(from);
let piece_color = board.get_piece_color(from);
if piece == usize::MAX || piece_color != board.stm {
return false;
}
let target_piece = board.get_piece(to);
let target_piece_color = board.get_piece_color(to);
if self.is_capture() && !self.is_en_passant() && (target_piece == usize::MAX || piece_color == target_piece_color) {
return false;
}
if !self.is_capture() && target_piece != usize::MAX {
return false;
}
let flags = self.get_flags();
let occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
let moves_bb = match piece {
PAWN => match flags {
MoveFlags::DOUBLE_PUSH => match board.stm {
WHITE => from_bb << 16,
BLACK => from_bb >> 16,
_ => panic_fast!("Invalid value: board.stm={}", board.stm),
},
MoveFlags::EN_PASSANT => board.state.en_passant,
_ => match board.stm {
WHITE => ((from_bb & !FILE_H_BB) << 7) | ((from_bb & !RANK_8_BB) << 8) | ((from_bb & !FILE_A_BB) << 9),
BLACK => ((from_bb & !FILE_A_BB) >> 7) | ((from_bb & !RANK_1_BB) >> 8) | ((from_bb & !FILE_H_BB) >> 9),
_ => panic_fast!("Invalid value: board.stm={}", board.stm),
},
},
KNIGHT => movegen::get_knight_moves(from),
BISHOP => movegen::get_bishop_moves(occupancy_bb, from),
ROOK => movegen::get_rook_moves(occupancy_bb, from),
QUEEN => movegen::get_queen_moves(occupancy_bb, from),
KING => match flags {
MoveFlags::SHORT_CASTLING => 1u64 << (from - 2),
MoveFlags::LONG_CASTLING => 1u64 << (from + 2),
_ => movegen::get_king_moves(from),
},
_ => panic_fast!("Invalid value: fen={}, piece={}", board, piece),
};
if (moves_bb & to_bb) == 0 {
return false;
}
if self.is_single_push() {
if piece == PAWN {
if (to_bb & (RANK_1_BB | RANK_8_BB)) != 0 {
return false;
}
if ((from as i8) - (to as i8)).abs() != 8 {
return false;
}
}
true
} else if self.is_double_push() {
if piece != PAWN {
return false;
}
if (board.stm == WHITE && (from_bb & RANK_2_BB) == 0) || (board.stm == BLACK && (from_bb & RANK_7_BB) == 0) {
return false;
}
if board.get_piece((cmp::max(from, to) + cmp::min(from, to)) / 2) != usize::MAX {
return false;
}
true
} else if self.is_en_passant() {
if piece != PAWN {
return false;
}
true
} else if self.is_promotion() {
if piece != PAWN {
return false;
}
if self.is_capture() {
let direction = ((from as i8) - (to as i8)).abs();
if direction != 7 && direction != 9 {
return false;
}
}
true
} else if self.is_capture() {
if piece == PAWN {
let direction = ((from as i8) - (to as i8)).abs();
if direction != 7 && direction != 9 {
return false;
}
}
true
} else if self.is_castling() {
if piece != KING {
return false;
}
if (board.stm == WHITE && from != E1) || (board.stm == BLACK && from != E8) {
return false;
}
let castling_right = match flags {
MoveFlags::SHORT_CASTLING => match piece_color {
WHITE => CastlingRights::WHITE_SHORT_CASTLING,
BLACK => CastlingRights::BLACK_SHORT_CASTLING,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
MoveFlags::LONG_CASTLING => match piece_color {
WHITE => CastlingRights::WHITE_LONG_CASTLING,
BLACK => CastlingRights::BLACK_LONG_CASTLING,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
_ => panic_fast!("Invalid value: fen={}, flags={:?}", board, flags),
};
if !board.state.castling_rights.contains(castling_right) {
return false;
}
let rook_square_bb = match flags {
MoveFlags::SHORT_CASTLING => match piece_color {
WHITE => H1_BB,
BLACK => H8_BB,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
MoveFlags::LONG_CASTLING => match piece_color {
WHITE => A1_BB,
BLACK => A8_BB,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
_ => panic_fast!("Invalid value: fen={}, flags={:?}", board, flags),
};
if (board.pieces[board.stm][ROOK] & rook_square_bb) == 0 {
return false;
}
let castling_area_bb = match flags {
MoveFlags::SHORT_CASTLING => match piece_color {
WHITE => F1_BB | G1_BB,
BLACK => F8_BB | G8_BB,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
MoveFlags::LONG_CASTLING => match piece_color {
WHITE => B1_BB | C1_BB | D1_BB,
BLACK => B8_BB | C8_BB | D8_BB,
_ => panic_fast!("Invalid value: fen={}, piece_color={}", board, piece_color),
},
_ => panic_fast!("Invalid value: fen={}, flags={:?}", board, flags),
};
if (castling_area_bb & occupancy_bb) != 0 {
return false;
}
true
} else {
panic_fast!("Move legality check failed: fen={}, self.data={}", board, self.data);
}
}
}
impl Default for Move {
fn default() -> Self {
Move::new(0, 0, MoveFlags::SINGLE_PUSH)
}
}
impl Display for Move {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_long_notation())
}
}
pub fn scan_piece_moves<const PIECE: usize, const CAPTURES: bool>(board: &Board, moves: &mut Moves, mut index: usize, evasion_mask: u64) -> usize {
assert_fast!(board.stm < 2);
let nstm = board.stm ^ 1;
let mut pieces_bb = board.pieces[board.stm][PIECE];
while pieces_bb != 0 {
let from_bb = pieces_bb.get_lsb();
let from = from_bb.bit_scan();
pieces_bb = pieces_bb.pop_lsb();
let occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
let mut piece_moves_bb = match PIECE {
KNIGHT => movegen::get_knight_moves(from),
BISHOP => movegen::get_bishop_moves(occupancy_bb, from),
ROOK => movegen::get_rook_moves(occupancy_bb, from),
QUEEN => movegen::get_queen_moves(occupancy_bb, from),
KING => movegen::get_king_moves(from),
_ => panic_fast!("Invalid parameter: fen={}, PIECE={}", board, PIECE),
};
piece_moves_bb &= !board.occupancy[board.stm] & evasion_mask;
if CAPTURES {
piece_moves_bb &= board.occupancy[nstm];
} else {
piece_moves_bb &= !board.occupancy[nstm];
}
while piece_moves_bb != 0 {
assert_fast!(index < engine::MAX_MOVES_COUNT);
let to_bb = piece_moves_bb.get_lsb();
let to = to_bb.bit_scan();
piece_moves_bb = piece_moves_bb.pop_lsb();
let capture = (to_bb & board.occupancy[nstm]) != 0;
let flags = if CAPTURES || capture { MoveFlags::CAPTURE } else { MoveFlags::SINGLE_PUSH };
moves[index].write(Move::new(from, to, flags));
index += 1;
}
if PIECE == KING && !CAPTURES {
match board.stm {
WHITE => {
assert_fast!(index < engine::MAX_MOVES_COUNT);
let king_side_castling_rights = board.state.castling_rights.contains(CastlingRights::WHITE_SHORT_CASTLING);
if king_side_castling_rights && (occupancy_bb & (F1_BB | G1_BB)) == 0 {
if !board.are_squares_attacked(board.stm, &[E1, F1, G1]) {
moves[index].write(Move::new(E1, G1, MoveFlags::SHORT_CASTLING));
index += 1;
}
}
assert_fast!(index < engine::MAX_MOVES_COUNT);
let queen_side_castling_rights = board.state.castling_rights.contains(CastlingRights::WHITE_LONG_CASTLING);
if queen_side_castling_rights && (occupancy_bb & (B1_BB | C1_BB | D1_BB)) == 0 {
if !board.are_squares_attacked(board.stm, &[C1, D1, E1]) {
moves[index].write(Move::new(E1, C1, MoveFlags::LONG_CASTLING));
index += 1;
}
}
}
BLACK => {
assert_fast!(index < engine::MAX_MOVES_COUNT);
let king_side_castling_rights = board.state.castling_rights.contains(CastlingRights::BLACK_SHORT_CASTLING);
if king_side_castling_rights && (occupancy_bb & (F8_BB | G8_BB)) == 0 {
if !board.are_squares_attacked(board.stm, &[E8, F8, G8]) {
moves[index].write(Move::new(E8, G8, MoveFlags::SHORT_CASTLING));
index += 1;
}
}
assert_fast!(index < engine::MAX_MOVES_COUNT);
let queen_side_castling_rights = board.state.castling_rights.contains(CastlingRights::BLACK_LONG_CASTLING);
if queen_side_castling_rights && (occupancy_bb & (B8_BB | C8_BB | D8_BB)) == 0 {
if !board.are_squares_attacked(board.stm, &[C8, D8, E8]) {
moves[index].write(Move::new(E8, C8, MoveFlags::LONG_CASTLING));
index += 1;
}
}
}
_ => panic_fast!("Invalid value: board.stm={}", board.stm),
}
}
}
index
}
pub fn get_piece_mobility<const PIECE: usize>(board: &Board, color: usize, aux: &mut EvalAux) -> PieceMobility {
assert_fast!(color < 2);
let mut pieces_bb = board.pieces[color][PIECE];
let mut mobility_inner = 0;
let mut mobility_outer = 0;
let nstm = color ^ 1;
let enemy_king_square = (board.pieces[nstm][KING]).bit_scan();
let enemy_king_box_bb = patterns::get_box(enemy_king_square);
let mut occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
occupancy_bb &= !match PIECE {
BISHOP => board.pieces[color][BISHOP] | board.pieces[color][QUEEN],
ROOK => board.pieces[color][ROOK] | board.pieces[color][QUEEN],
QUEEN => board.pieces[color][BISHOP] | board.pieces[color][ROOK] | board.pieces[color][QUEEN],
_ => 0,
};
while pieces_bb != 0 {
let from_bb = pieces_bb.get_lsb();
let from = from_bb.bit_scan();
pieces_bb = pieces_bb.pop_lsb();
let mut piece_moves_bb = match PIECE {
KNIGHT => movegen::get_knight_moves(from),
BISHOP => movegen::get_bishop_moves(occupancy_bb, from),
ROOK => movegen::get_rook_moves(occupancy_bb, from),
QUEEN => movegen::get_queen_moves(occupancy_bb, from),
KING => movegen::get_king_moves(from),
_ => panic_fast!("Invalid parameter: fen={}, PIECE={}", board, PIECE),
};
aux.king_area_threats += (enemy_king_box_bb & (piece_moves_bb | from_bb)).bit_count() as i8;
piece_moves_bb &= !board.occupancy[color] & !board.pawn_attacks[nstm];
mobility_inner += (piece_moves_bb & CENTER_BB).bit_count() as i8;
mobility_outer += (piece_moves_bb & OUTSIDE_BB).bit_count() as i8;
}
PieceMobility { inner: mobility_inner, outer: mobility_outer }
}
pub fn scan_pawn_moves<const CAPTURES: bool>(board: &Board, moves: &mut Moves, mut index: usize, evasion_mask: u64) -> usize {
if !CAPTURES {
index = scan_pawn_moves_single_push(board, moves, index, evasion_mask);
index = scan_pawn_moves_double_push(board, moves, index, evasion_mask);
} else {
index = scan_pawn_moves_diagonal_attacks::<LEFT>(board, moves, index, evasion_mask);
index = scan_pawn_moves_diagonal_attacks::<RIGHT>(board, moves, index, evasion_mask);
}
index
}
fn scan_pawn_moves_single_push(board: &Board, moves: &mut Moves, mut index: usize, evasion_mask: u64) -> usize {
assert_fast!(board.stm < 2);
let pieces_bb = board.pieces[board.stm][PAWN];
let occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
let shift = 8 - 16 * (board.stm as i8);
let promotion_rank_bb = RANK_8_BB >> (56 * (board.stm as u8));
let mut target_squares_bb = match board.stm {
WHITE => pieces_bb << 8,
BLACK => pieces_bb >> 8,
_ => {
panic_fast!("Invalid value: board.stm={}", board.stm);
}
};
target_squares_bb &= !occupancy_bb & evasion_mask;
while target_squares_bb != 0 {
let to_bb = target_squares_bb.get_lsb();
let to = to_bb.bit_scan();
let from = ((to as i8) - shift) as usize;
target_squares_bb = target_squares_bb.pop_lsb();
if (to_bb & promotion_rank_bb) != 0 {
assert_fast!(index + 3 < engine::MAX_MOVES_COUNT);
moves[index + 0].write(Move::new(from, to, MoveFlags::QUEEN_PROMOTION));
moves[index + 1].write(Move::new(from, to, MoveFlags::ROOK_PROMOTION));
moves[index + 2].write(Move::new(from, to, MoveFlags::BISHOP_PROMOTION));
moves[index + 3].write(Move::new(from, to, MoveFlags::KNIGHT_PROMOTION));
index += 4;
} else {
assert_fast!(index < engine::MAX_MOVES_COUNT);
moves[index].write(Move::new(from, to, MoveFlags::SINGLE_PUSH));
index += 1;
}
}
index
}
fn scan_pawn_moves_double_push(board: &Board, moves: &mut Moves, mut index: usize, evasion_mask: u64) -> usize {
assert_fast!(board.stm < 2);
let pieces_bb = board.pieces[board.stm][PAWN];
let occupancy_bb = board.occupancy[WHITE] | board.occupancy[BLACK];
let shift = 16 - 32 * (board.stm as i8);
let mut target_squares_bb = match board.stm {
WHITE => (((pieces_bb & RANK_2_BB) << 8) & !occupancy_bb) << 8,
BLACK => (((pieces_bb & RANK_7_BB) >> 8) & !occupancy_bb) >> 8,
_ => {
panic_fast!("Invalid value: board.stm={}", board.stm);
}
};
target_squares_bb &= !occupancy_bb & evasion_mask;
while target_squares_bb != 0 {
assert_fast!(index < engine::MAX_MOVES_COUNT);
let to_bb = target_squares_bb.get_lsb();
let to = to_bb.bit_scan();
let from = ((to as i8) - shift) as usize;
target_squares_bb = target_squares_bb.pop_lsb();
moves[index].write(Move::new(from, to, MoveFlags::DOUBLE_PUSH));
index += 1;
}
index
}
fn scan_pawn_moves_diagonal_attacks<const DIR: usize>(board: &Board, moves: &mut Moves, mut index: usize, evasion_mask: u64) -> usize {
assert_fast!(board.stm < 2);
let nstm = board.stm ^ 1;
let pieces_bb = board.pieces[board.stm][PAWN];
let forbidden_file_bb = FILE_A_BB >> (DIR * 7);
let shift = 9 - (board.stm ^ DIR) * 2;
let signed_shift = (shift as i8) - ((board.stm as i8) * 2 * (shift as i8));
let promotion_rank_bb = RANK_8_BB >> (56 * (board.stm as u8));
let mut target_squares_bb = match board.stm {
WHITE => (pieces_bb & !forbidden_file_bb) << shift,
BLACK => (pieces_bb & !forbidden_file_bb) >> shift,
_ => {
panic_fast!("Invalid value: board.stm={}", board.stm);
}
};
target_squares_bb &= (board.occupancy[nstm] | board.state.en_passant) & evasion_mask;
while target_squares_bb != 0 {
let to_bb = target_squares_bb.get_lsb();
let to = to_bb.bit_scan();
let from = ((to as i8) - signed_shift) as usize;
target_squares_bb = target_squares_bb.pop_lsb();
if (to_bb & promotion_rank_bb) != 0 {
assert_fast!(index + 3 < engine::MAX_MOVES_COUNT);
moves[index + 0].write(Move::new(from, to, MoveFlags::QUEEN_PROMOTION_CAPTURE));
moves[index + 1].write(Move::new(from, to, MoveFlags::ROOK_PROMOTION_CAPTURE));
moves[index + 2].write(Move::new(from, to, MoveFlags::BISHOP_PROMOTION_CAPTURE));
moves[index + 3].write(Move::new(from, to, MoveFlags::KNIGHT_PROMOTION_CAPTURE));
index += 4;
} else {
assert_fast!(index < engine::MAX_MOVES_COUNT);
let en_passant = (to_bb & board.state.en_passant) != 0;
let flags = if en_passant { MoveFlags::EN_PASSANT } else { MoveFlags::CAPTURE };
moves[index].write(Move::new(from, to, flags));
index += 1;
}
}
index
}