stub: static exchange evaluation
not yet used, but will be useful hopefully
This commit is contained in:
parent
1d651de4a0
commit
a36f394b99
133
src/eval.rs
133
src/eval.rs
@ -13,7 +13,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
|
|
||||||
//! Position evaluation.
|
//! Position evaluation.
|
||||||
|
|
||||||
use crate::{Board, Color, Piece, Square, N_COLORS, N_PIECES, N_SQUARES};
|
use crate::prelude::*;
|
||||||
use core::cmp::{max, min};
|
use core::cmp::{max, min};
|
||||||
use core::ops::Index;
|
use core::ops::Index;
|
||||||
|
|
||||||
@ -29,6 +29,23 @@ pub trait Eval {
|
|||||||
fn eval(&self) -> EvalInt;
|
fn eval(&self) -> EvalInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait EvalSEE {
|
||||||
|
/// Evaluate the outcome of an exchange at a square (static exchange evaluation).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * dest: Square where the exchange happens.
|
||||||
|
/// * first_move_side: Side to move first in the exchange.
|
||||||
|
///
|
||||||
|
/// This function may panic if a piece already at the destination is the same color as the side
|
||||||
|
/// to move.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Expected gain from this exchange.
|
||||||
|
fn eval_see(&self, dest: Square, first_move_side: Color) -> EvalInt;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) mod eval_score {
|
pub(crate) mod eval_score {
|
||||||
//! Opaque "score" counters to be used in the board.
|
//! Opaque "score" counters to be used in the board.
|
||||||
|
|
||||||
@ -195,7 +212,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
-5, 0, 0, 0, 0, 0, 0, -5, // 2
|
-5, 0, 0, 0, 0, 0, 0, -5, // 2
|
||||||
-5, -3, 0, 0, 0, 2, -3, -5, // 1
|
-5, -3, 0, 0, 0, 2, -3, -5, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 500),
|
], Piece::Rook.value()),
|
||||||
|
|
||||||
// bishop
|
// bishop
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -208,7 +225,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, -10, 0, 0, -10, 0, 0, // 1
|
0, 0, -10, 0, 0, -10, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 300),
|
], Piece::Bishop.value()),
|
||||||
|
|
||||||
// knight
|
// knight
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -221,7 +238,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
-100, 1, 0, 0, 0, 0, 0,-100, // 2
|
-100, 1, 0, 0, 0, 0, 0,-100, // 2
|
||||||
-100,-100,-100,-100,-100,-100,-100,-100, // 1
|
-100,-100,-100,-100,-100,-100,-100,-100, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 300),
|
], Piece::Knight.value()),
|
||||||
|
|
||||||
// king
|
// king
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -234,7 +251,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
-70, -70, -70, -70, -70, -70, -70, -70, // 2
|
-70, -70, -70, -70, -70, -70, -70, -70, // 2
|
||||||
0, 0, 10, 0, 0, 0, 20, 0, // 1
|
0, 0, 10, 0, 0, 0, 20, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 20_000),
|
], Piece::King.value()),
|
||||||
|
|
||||||
// queen
|
// queen
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -247,7 +264,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 900),
|
], Piece::Queen.value()),
|
||||||
|
|
||||||
// pawn
|
// pawn
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -260,7 +277,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 100),
|
], Piece::Pawn.value()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
@ -276,7 +293,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 500),
|
], Piece::Rook.value()),
|
||||||
|
|
||||||
// bishop
|
// bishop
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -289,7 +306,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 300),
|
], Piece::Bishop.value()),
|
||||||
|
|
||||||
// knight
|
// knight
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -302,7 +319,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
-100, 0, 0, 0, 0, 0, 0,-100, // 2
|
-100, 0, 0, 0, 0, 0, 0,-100, // 2
|
||||||
-100,-100,-100,-100,-100,-100,-100,-100, // 1
|
-100,-100,-100,-100,-100,-100,-100,-100, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 300),
|
], Piece::Knight.value()),
|
||||||
|
|
||||||
// king
|
// king
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -315,7 +332,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
-100, -20, -15, -13, -13, -15, -20,-100, // 2
|
-100, -20, -15, -13, -13, -15, -20,-100, // 2
|
||||||
-100,-100, -90, -70, -70, -90,-100,-100, // 1
|
-100,-100, -90, -70, -70, -90,-100,-100, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 20_000),
|
], Piece::King.value()),
|
||||||
|
|
||||||
// queen
|
// queen
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -328,7 +345,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 900),
|
], Piece::Queen.value()),
|
||||||
|
|
||||||
// pawn
|
// pawn
|
||||||
make_pst([
|
make_pst([
|
||||||
@ -341,7 +358,7 @@ pub const PST_ENDGAME: Pst = Pst([
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 100),
|
], Piece::Pawn.value()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Centipawn, signed, eval metrics.
|
/// Centipawn, signed, eval metrics.
|
||||||
@ -399,6 +416,66 @@ impl Eval for Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EvalSEE for Board {
|
||||||
|
fn eval_see(&self, dest: Square, first_mv_side: Color) -> EvalInt {
|
||||||
|
let attackers = self.gen_attackers(dest, false, None);
|
||||||
|
|
||||||
|
// indexed by the Piece enum order
|
||||||
|
let mut atk_qty = [[0u8; N_PIECES]; N_COLORS];
|
||||||
|
|
||||||
|
// counting sort
|
||||||
|
for (attacker, _src) in attackers {
|
||||||
|
atk_qty[attacker.col as usize][attacker.pc as usize] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dest_pc = self.get_piece(dest);
|
||||||
|
|
||||||
|
// it doesn't make sense if the piece already on the square is first to move
|
||||||
|
debug_assert!(!dest_pc.is_some_and(|pc| pc.col == first_mv_side));
|
||||||
|
|
||||||
|
// Simulate the exchange.
|
||||||
|
//
|
||||||
|
// Returns the expected gain for the side in the exchange.
|
||||||
|
//
|
||||||
|
// TODO: promotions aren't accounted for.
|
||||||
|
fn sim_exchange(
|
||||||
|
side: Color,
|
||||||
|
dest_pc: Option<ColPiece>,
|
||||||
|
atk_qty: &mut [[u8; N_PIECES]; N_COLORS],
|
||||||
|
) -> EvalInt {
|
||||||
|
use Piece::*;
|
||||||
|
let val_idxs = [Pawn, Knight, Bishop, Rook, Queen, King];
|
||||||
|
|
||||||
|
let mut ptr = 0;
|
||||||
|
let mut eval = 0;
|
||||||
|
|
||||||
|
// while the count of this piece is zero, move to the next piece
|
||||||
|
while atk_qty[side as usize][val_idxs[ptr] as usize] == 0 {
|
||||||
|
ptr += 1;
|
||||||
|
if ptr == N_PIECES {
|
||||||
|
return eval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cur_pc = val_idxs[ptr];
|
||||||
|
let pc_ptr = cur_pc as usize;
|
||||||
|
|
||||||
|
debug_assert!(atk_qty[side as usize][pc_ptr] > 0);
|
||||||
|
atk_qty[side as usize][pc_ptr] -= 1;
|
||||||
|
|
||||||
|
if let Some(dest_pc) = dest_pc {
|
||||||
|
eval += dest_pc.pc.value();
|
||||||
|
// this player may either give up now, or capture. pick the best (max score).
|
||||||
|
// anything the other player gains is taken from us, hence the minus.
|
||||||
|
eval = max(0, eval - sim_exchange(side.flip(), Some(dest_pc), atk_qty))
|
||||||
|
}
|
||||||
|
|
||||||
|
eval
|
||||||
|
}
|
||||||
|
|
||||||
|
sim_exchange(first_mv_side, dest_pc, &mut atk_qty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -415,4 +492,34 @@ mod tests {
|
|||||||
assert!(eval1 > 0, "got eval {eval1} ({:?})", board1.eval);
|
assert!(eval1 > 0, "got eval {eval1} ({:?})", board1.eval);
|
||||||
assert!(eval2 < 0, "got eval {eval2} ({:?})", board2.eval);
|
assert!(eval2 < 0, "got eval {eval2} ({:?})", board2.eval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Static exchange evaluation tests.
|
||||||
|
#[test]
|
||||||
|
fn test_see_eval() {
|
||||||
|
// set side to move appropriately in the fen
|
||||||
|
//
|
||||||
|
// otherwise the exchange doesn't work
|
||||||
|
let test_cases = [
|
||||||
|
(
|
||||||
|
// fen
|
||||||
|
"8/4n3/8/2qRr3/8/4N3/8/8 b - - 0 1",
|
||||||
|
// square where exchange happens
|
||||||
|
"d5",
|
||||||
|
// expected (signed) value gain of exchange
|
||||||
|
Piece::Rook.value(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"8/8/4b3/2kq4/2PKP3/8/8/8 w - - 0 1",
|
||||||
|
"d5",
|
||||||
|
Piece::Queen.value(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (fen, dest, expected) in test_cases {
|
||||||
|
let board = Board::from_fen(fen).unwrap();
|
||||||
|
let dest: Square = dest.parse().unwrap();
|
||||||
|
let res = board.eval_see(dest, board.turn);
|
||||||
|
assert_eq!(res, expected, "failed {}", fen);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
78
src/lib.rs
78
src/lib.rs
@ -29,6 +29,7 @@ pub mod prelude;
|
|||||||
|
|
||||||
use crate::fen::{FromFen, ToFen, START_POSITION};
|
use crate::fen::{FromFen, ToFen, START_POSITION};
|
||||||
use crate::hash::Zobrist;
|
use crate::hash::Zobrist;
|
||||||
|
use crate::movegen::GenAttackers;
|
||||||
use eval::eval_score::EvalScores;
|
use eval::eval_score::EvalScores;
|
||||||
|
|
||||||
pub const BOARD_WIDTH: usize = 8;
|
pub const BOARD_WIDTH: usize = 8;
|
||||||
@ -45,13 +46,13 @@ pub const N_COLORS: usize = 2;
|
|||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
/// Return opposite color (does not assign).
|
/// Return opposite color (does not assign).
|
||||||
pub fn flip(self) -> Self {
|
pub const fn flip(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Color::White => Color::Black,
|
Color::White => Color::Black,
|
||||||
Color::Black => Color::White,
|
Color::Black => Color::White,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn sign(&self) -> i8 {
|
pub const fn sign(&self) -> i8 {
|
||||||
match self {
|
match self {
|
||||||
Color::White => 1,
|
Color::White => 1,
|
||||||
Color::Black => -1,
|
Color::Black => -1,
|
||||||
@ -79,6 +80,21 @@ pub enum Piece {
|
|||||||
}
|
}
|
||||||
pub const N_PIECES: usize = 6;
|
pub const N_PIECES: usize = 6;
|
||||||
|
|
||||||
|
impl Piece {
|
||||||
|
/// Get a piece's base value.
|
||||||
|
pub const fn value(&self) -> crate::eval::EvalInt {
|
||||||
|
use Piece::*;
|
||||||
|
(match self {
|
||||||
|
Rook => 6,
|
||||||
|
Bishop => 3,
|
||||||
|
Knight => 3,
|
||||||
|
King => 200,
|
||||||
|
Queen => 9,
|
||||||
|
Pawn => 1,
|
||||||
|
}) * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PieceErr;
|
pub struct PieceErr;
|
||||||
|
|
||||||
/// Color and piece.
|
/// Color and piece.
|
||||||
@ -482,6 +498,16 @@ impl Board {
|
|||||||
(0..N_SQUARES).map(Square::try_from).map(|x| x.unwrap())
|
(0..N_SQUARES).map(Square::try_from).map(|x| x.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the 8th rank from a given player's perspective.
|
||||||
|
///
|
||||||
|
/// Useful for promotions.
|
||||||
|
pub fn last_rank(pl: Color) -> usize {
|
||||||
|
match pl {
|
||||||
|
Color::White => usize::from(BOARD_HEIGHT) - 1,
|
||||||
|
Color::Black => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new piece in a location, and pop any existing piece in the destination.
|
/// Create a new piece in a location, and pop any existing piece in the destination.
|
||||||
pub fn set_piece(&mut self, sq: Square, pc: ColPiece) -> Option<ColPiece> {
|
pub fn set_piece(&mut self, sq: Square, pc: ColPiece) -> Option<ColPiece> {
|
||||||
let dest_pc = self.del_piece(sq);
|
let dest_pc = self.del_piece(sq);
|
||||||
@ -563,47 +589,13 @@ impl Board {
|
|||||||
/// Is a given player in check?
|
/// Is a given player in check?
|
||||||
pub fn is_check(&self, pl: Color) -> bool {
|
pub fn is_check(&self, pl: Color) -> bool {
|
||||||
for src in self[pl][Piece::King] {
|
for src in self[pl][Piece::King] {
|
||||||
macro_rules! detect_checker {
|
if self
|
||||||
($dirs: ident, $pc: pat, $keep_going: expr) => {
|
.gen_attackers(src, true, Some(pl.flip()))
|
||||||
for dir in $dirs.into_iter() {
|
.into_iter()
|
||||||
let (mut r, mut c) = src.to_row_col_signed();
|
.next()
|
||||||
loop {
|
.is_some()
|
||||||
let (nr, nc) = (r + dir.0, c + dir.1);
|
{
|
||||||
if let Ok(sq) = Square::from_row_col_signed(nr, nc) {
|
return true;
|
||||||
if let Some(pc) = self.get_piece(sq) {
|
|
||||||
if matches!(pc.pc, $pc) && pc.col != pl {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!($keep_going)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
r = nr;
|
|
||||||
c = nc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let dirs_white_pawn = [(-1, 1), (-1, -1)];
|
|
||||||
let dirs_black_pawn = [(1, 1), (1, -1)];
|
|
||||||
|
|
||||||
use Piece::*;
|
|
||||||
|
|
||||||
use movegen::{DIRS_DIAG, DIRS_KNIGHT, DIRS_STAR, DIRS_STRAIGHT};
|
|
||||||
|
|
||||||
detect_checker!(DIRS_DIAG, Bishop | Queen, true);
|
|
||||||
detect_checker!(DIRS_STRAIGHT, Rook | Queen, true);
|
|
||||||
detect_checker!(DIRS_STAR, King, false);
|
|
||||||
detect_checker!(DIRS_KNIGHT, Knight, false);
|
|
||||||
match pl {
|
|
||||||
Color::White => detect_checker!(dirs_black_pawn, Pawn, false),
|
|
||||||
Color::Black => detect_checker!(dirs_white_pawn, Pawn, false),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
185
src/movegen.rs
185
src/movegen.rs
@ -454,6 +454,134 @@ impl ToUCIAlgebraic for Move {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait GenAttackers {
|
||||||
|
/// Generate attackers/attacks for a given square.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `dest`: Square that is attacked.
|
||||||
|
/// * `single`: Exit early if any attack is found.
|
||||||
|
/// * `filter_color`: Matches only attackers of this color, if given.
|
||||||
|
fn gen_attackers(
|
||||||
|
&self,
|
||||||
|
dest: Square,
|
||||||
|
single: bool,
|
||||||
|
filter_color: Option<Color>,
|
||||||
|
) -> impl IntoIterator<Item = (ColPiece, Move)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenAttackers for Board {
|
||||||
|
fn gen_attackers(
|
||||||
|
&self,
|
||||||
|
dest: Square,
|
||||||
|
single: bool,
|
||||||
|
filter_color: Option<Color>,
|
||||||
|
) -> impl IntoIterator<Item = (ColPiece, Move)> {
|
||||||
|
let mut ret: Vec<(ColPiece, Move)> = Vec::new();
|
||||||
|
|
||||||
|
/// Filter attackers and add them to the return vector.
|
||||||
|
///
|
||||||
|
/// Returns true if attacker was added.
|
||||||
|
fn push_ans(
|
||||||
|
pc: ColPiece,
|
||||||
|
sq: Square,
|
||||||
|
dest: Square,
|
||||||
|
ret: &mut Vec<(ColPiece, Move)>,
|
||||||
|
filter_color: Option<Color>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(filter_color) = filter_color {
|
||||||
|
if filter_color != pc.col {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (r, _c) = dest.to_row_col();
|
||||||
|
let is_promotion = matches!(pc.pc, Piece::Pawn) && r == Board::last_rank(pc.col);
|
||||||
|
|
||||||
|
if is_promotion {
|
||||||
|
use PromotePiece::*;
|
||||||
|
for prom_pc in [Queen, Knight, Rook, Bishop] {
|
||||||
|
ret.push((
|
||||||
|
pc,
|
||||||
|
Move {
|
||||||
|
src: sq,
|
||||||
|
dest,
|
||||||
|
move_type: MoveType::Promotion(prom_pc),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.push((
|
||||||
|
pc,
|
||||||
|
Move {
|
||||||
|
src: sq,
|
||||||
|
dest,
|
||||||
|
move_type: MoveType::Normal,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! detect_checker {
|
||||||
|
($dirs: ident, $pc: pat, $color: pat, $keep_going: expr) => {
|
||||||
|
for dir in $dirs.into_iter() {
|
||||||
|
let (mut r, mut c) = dest.to_row_col_signed();
|
||||||
|
loop {
|
||||||
|
let (nr, nc) = (r + dir.0, c + dir.1);
|
||||||
|
if let Ok(sq) = Square::from_row_col_signed(nr, nc) {
|
||||||
|
if let Some(pc) = self.get_piece(sq) {
|
||||||
|
if matches!(pc.pc, $pc) && matches!(pc.col, $color) {
|
||||||
|
let added = push_ans(pc, sq, dest, &mut ret, filter_color);
|
||||||
|
if single && added {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!($keep_going)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r = nr;
|
||||||
|
c = nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// inverted because our perspective is from the attacked square
|
||||||
|
let dirs_white_pawn = [(-1, 1), (-1, -1)];
|
||||||
|
let dirs_black_pawn = [(1, 1), (1, -1)];
|
||||||
|
|
||||||
|
use Piece::*;
|
||||||
|
|
||||||
|
macro_rules! both {
|
||||||
|
() => {
|
||||||
|
Color::White | Color::Black
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_checker!(DIRS_DIAG, Bishop | Queen, both!(), true);
|
||||||
|
detect_checker!(DIRS_STRAIGHT, Rook | Queen, both!(), true);
|
||||||
|
// this shouldn't happen in legal chess but we're using this function in a pseudo-legal
|
||||||
|
// move gen context
|
||||||
|
detect_checker!(DIRS_STAR, King, both!(), false);
|
||||||
|
detect_checker!(DIRS_KNIGHT, Knight, both!(), false);
|
||||||
|
|
||||||
|
if filter_color.is_none_or(|c| matches!(c, Color::Black)) {
|
||||||
|
detect_checker!(dirs_black_pawn, Pawn, Color::Black, false);
|
||||||
|
}
|
||||||
|
if filter_color.is_none_or(|c| matches!(c, Color::White)) {
|
||||||
|
detect_checker!(dirs_white_pawn, Pawn, Color::White, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum MoveGenType {
|
enum MoveGenType {
|
||||||
/// Legal move generation.
|
/// Legal move generation.
|
||||||
@ -561,6 +689,7 @@ fn move_slider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_legal(board: &mut Board, mv: Move) -> bool {
|
fn is_legal(board: &mut Board, mv: Move) -> bool {
|
||||||
// mut required for check checking
|
// mut required for check checking
|
||||||
// disallow friendly fire
|
// disallow friendly fire
|
||||||
@ -678,10 +807,7 @@ impl MoveGenInternal for Board {
|
|||||||
for src in squares!(Pawn) {
|
for src in squares!(Pawn) {
|
||||||
let (r, c) = src.to_row_col_signed();
|
let (r, c) = src.to_row_col_signed();
|
||||||
|
|
||||||
let last_row = match self.turn {
|
let last_row = isize::try_from(Board::last_rank(self.turn)).unwrap();
|
||||||
Color::White => isize::try_from(BOARD_HEIGHT).unwrap() - 1,
|
|
||||||
Color::Black => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let nr = r + isize::from(self.turn.sign());
|
let nr = r + isize::from(self.turn.sign());
|
||||||
let is_promotion = nr == last_row;
|
let is_promotion = nr == last_row;
|
||||||
@ -763,10 +889,11 @@ impl MoveGenInternal for Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.into_iter().filter(move |mv| match gen_type {
|
ret.retain(move |mv| match gen_type {
|
||||||
MoveGenType::Legal => is_legal(self, *mv),
|
MoveGenType::Legal => is_legal(self, *mv),
|
||||||
MoveGenType::_Pseudo => true,
|
MoveGenType::_Pseudo => true,
|
||||||
})
|
});
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1156,6 +1283,18 @@ mod tests {
|
|||||||
let all_cases = check_cases.iter().chain(¬_check_cases);
|
let all_cases = check_cases.iter().chain(¬_check_cases);
|
||||||
for (fen, expected) in all_cases {
|
for (fen, expected) in all_cases {
|
||||||
let board = Board::from_fen(fen).unwrap();
|
let board = Board::from_fen(fen).unwrap();
|
||||||
|
eprintln!(
|
||||||
|
"got attackers {:?} for {}",
|
||||||
|
board
|
||||||
|
.gen_attackers(
|
||||||
|
board[Color::White][Piece::King].into_iter().next().unwrap(),
|
||||||
|
false,
|
||||||
|
Some(Color::Black)
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
fen
|
||||||
|
);
|
||||||
assert_eq!(board.is_check(Color::White), *expected, "failed on {}", fen);
|
assert_eq!(board.is_check(Color::White), *expected, "failed on {}", fen);
|
||||||
|
|
||||||
let board_anti = board.flip_colors();
|
let board_anti = board.flip_colors();
|
||||||
@ -1431,4 +1570,38 @@ mod tests {
|
|||||||
assert_eq!(mv.to_uci_algebraic(), tc);
|
assert_eq!(mv.to_uci_algebraic(), tc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gen_attackers() {
|
||||||
|
let test_cases = [(
|
||||||
|
// fen
|
||||||
|
"3q4/3rn3/3r2b1/3rb3/rnpkbN2/2qKK2r/1nPPpN2/2rr4 w - - 0 1",
|
||||||
|
// attacked square
|
||||||
|
"d3",
|
||||||
|
// expected results
|
||||||
|
"c3 c2 e3 b4 d4 e4 f2 f4 c4 b2",
|
||||||
|
)];
|
||||||
|
|
||||||
|
for (fen, attacked, expected) in test_cases {
|
||||||
|
let mut expected = expected
|
||||||
|
.split_whitespace()
|
||||||
|
.map(str::parse::<Square>)
|
||||||
|
.map(|x| x.expect("test case has invalid square"))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
expected.sort();
|
||||||
|
|
||||||
|
let attacked = attacked.parse::<Square>().unwrap();
|
||||||
|
|
||||||
|
let board = Board::from_fen(fen).unwrap();
|
||||||
|
|
||||||
|
let mut attackers = board
|
||||||
|
.gen_attackers(attacked, false, None)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_pc, mv)| mv.src)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
attackers.sort();
|
||||||
|
|
||||||
|
assert_eq!(attackers, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,15 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
|
|
||||||
//! Prelude that you can import entirely to use the library conveniently.
|
//! Prelude that you can import entirely to use the library conveniently.
|
||||||
|
|
||||||
pub use crate::eval::{eval_metrics, EvalMetrics, EvalInt, Eval};
|
pub use crate::coordination::{
|
||||||
|
GoMessage, MsgBestmove, MsgToEngine, MsgToMain, UCIMode, UCIModeMachine, UCIModeTransition,
|
||||||
|
};
|
||||||
|
pub use crate::eval::{eval_metrics, Eval, EvalInt, EvalMetrics};
|
||||||
pub use crate::fen::{FromFen, ToFen};
|
pub use crate::fen::{FromFen, ToFen};
|
||||||
pub use crate::movegen::{FromUCIAlgebraic, Move, MoveGen, ToUCIAlgebraic};
|
pub use crate::movegen::{FromUCIAlgebraic, GenAttackers, Move, MoveGen, ToUCIAlgebraic};
|
||||||
pub use crate::search::{best_line, best_move, SearchEval, TranspositionTable, EngineState, SearchConfig, TimeLimits};
|
pub use crate::search::{
|
||||||
pub use crate::{Board, Color, BOARD_HEIGHT, BOARD_WIDTH, N_COLORS, N_PIECES, N_SQUARES, ColPiece, Piece};
|
best_line, best_move, EngineState, SearchConfig, SearchEval, TimeLimits, TranspositionTable,
|
||||||
pub use crate::coordination::{UCIMode, UCIModeTransition, UCIModeMachine, MsgBestmove, MsgToMain, MsgToEngine, GoMessage};
|
};
|
||||||
|
pub use crate::{
|
||||||
|
Board, ColPiece, Color, Piece, BOARD_HEIGHT, BOARD_WIDTH, N_COLORS, N_PIECES, N_SQUARES, Square,
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user