use crate::engine::board::Position;
use crate::engine::types::{Color, PieceType, Square};
pub const INF: i32 = 100_000;
pub const MATE: i32 = 90_000;
#[inline]
pub fn is_mate_score(score: i32) -> bool {
score.abs() >= MATE - 500
}
const PIECE_VALUE: [i32; 6] = [
100, 320, 330, 500, 900, 0, ];
#[rustfmt::skip]
const PAWN_PST: [i32; 64] = [
0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 10,-20,-20, 10, 10, 5, 5, -5,-10, 0, 0,-10, -5, 5, 0, 0, 0, 20, 20, 0, 0, 0, 5, 5, 10, 25, 25, 10, 5, 5, 10, 10, 20, 30, 30, 20, 10, 10, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0, ];
#[rustfmt::skip]
const KNIGHT_PST: [i32; 64] = [
-50,-40,-30,-30,-30,-30,-40,-50,
-40,-20, 0, 5, 5, 0,-20,-40,
-30, 5, 10, 15, 15, 10, 5,-30,
-30, 0, 15, 20, 20, 15, 0,-30,
-30, 5, 15, 20, 20, 15, 5,-30,
-30, 0, 10, 15, 15, 10, 0,-30,
-40,-20, 0, 0, 0, 0,-20,-40,
-50,-40,-30,-30,-30,-30,-40,-50,
];
#[rustfmt::skip]
const BISHOP_PST: [i32; 64] = [
-20,-10,-10,-10,-10,-10,-10,-20,
-10, 5, 0, 0, 0, 0, 5,-10,
-10, 10, 10, 10, 10, 10, 10,-10,
-10, 0, 10, 10, 10, 10, 0,-10,
-10, 5, 5, 10, 10, 5, 5,-10,
-10, 0, 5, 10, 10, 5, 0,-10,
-10, 0, 0, 0, 0, 0, 0,-10,
-20,-10,-10,-10,-10,-10,-10,-20,
];
#[rustfmt::skip]
const ROOK_PST: [i32; 64] = [
0, 0, 0, 5, 5, 0, 0, 0,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
5, 10, 10, 10, 10, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const QUEEN_PST: [i32; 64] = [
-20,-10,-10, -5, -5,-10,-10,-20,
-10, 0, 5, 0, 0, 0, 0,-10,
-10, 5, 5, 5, 5, 5, 0,-10,
0, 0, 5, 5, 5, 5, 0, -5,
-5, 0, 5, 5, 5, 5, 0, -5,
-10, 0, 5, 5, 5, 5, 0,-10,
-10, 0, 0, 0, 0, 0, 0,-10,
-20,-10,-10, -5, -5,-10,-10,-20,
];
#[rustfmt::skip]
const KING_MG_PST: [i32; 64] = [
20, 30, 10, 0, 0, 10, 30, 20,
20, 20, 0, 0, 0, 0, 20, 20,
-10,-20,-20,-20,-20,-20,-20,-10,
-20,-30,-30,-40,-40,-30,-30,-20,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,
];
const PST: [[i32; 64]; 6] = [
PAWN_PST,
KNIGHT_PST,
BISHOP_PST,
ROOK_PST,
QUEEN_PST,
KING_MG_PST,
];
pub fn evaluate(pos: &Position) -> i32 {
let mut score = 0i32;
for pt_idx in 0..6 {
for sq in pos.pieces[Color::White.index()][pt_idx].iter() {
score += PIECE_VALUE[pt_idx];
score += PST[pt_idx][sq.0 as usize];
}
for sq in pos.pieces[Color::Black.index()][pt_idx].iter() {
score -= PIECE_VALUE[pt_idx];
score -= PST[pt_idx][mirror_square(sq) as usize];
}
}
if pos.pieces[Color::White.index()][PieceType::Bishop.index()].pop_count() >= 2 {
score += 30;
}
if pos.pieces[Color::Black.index()][PieceType::Bishop.index()].pop_count() >= 2 {
score -= 30;
}
score
}
#[inline]
pub fn evaluate_relative(pos: &Position) -> i32 {
let score = evaluate(pos);
match pos.side_to_move {
Color::White => score,
Color::Black => -score,
}
}
#[inline]
fn mirror_square(sq: Square) -> u8 {
sq.0 ^ 56 }
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::board::Position;
#[test]
fn starting_position_roughly_equal() {
let pos = Position::starting();
let score = evaluate(&pos);
assert!(
score.abs() < 50,
"starting position eval too skewed: {score}"
);
}
#[test]
fn white_extra_queen_is_positive() {
let pos = Position::from_fen("4k3/8/8/8/8/8/8/3QK3 w - - 0 1").unwrap();
let score = evaluate(&pos);
assert!(
score > 800,
"extra queen should give large advantage: {score}"
);
}
#[test]
fn black_extra_queen_is_negative() {
let pos = Position::from_fen("3qk3/8/8/8/8/8/8/4K3 w - - 0 1").unwrap();
let score = evaluate(&pos);
assert!(
score < -800,
"opponent extra queen should be negative: {score}"
);
}
#[test]
fn symmetric_position_near_zero() {
let pos =
Position::from_fen("r1bqkb1r/pppppppp/2n2n2/8/8/2N2N2/PPPPPPPP/R1BQKB1R w KQkq - 0 1")
.unwrap();
let score = evaluate(&pos);
assert!(
score.abs() < 30,
"symmetric position should be near zero: {score}"
);
}
#[test]
fn evaluate_relative_flips_for_black() {
let pos = Position::from_fen("3qk3/8/8/8/8/8/8/4K3 b - - 0 1").unwrap();
let rel = evaluate_relative(&pos);
assert!(
rel > 800,
"relative eval for Black with extra queen should be positive: {rel}"
);
}
#[test]
fn bishop_pair_bonus() {
let w2b = Position::from_fen("4k3/8/8/8/8/8/8/2B1KB2 w - - 0 1").unwrap();
let w1b = Position::from_fen("4k3/8/8/8/8/8/8/4KB2 w - - 0 1").unwrap();
let diff = evaluate(&w2b) - evaluate(&w1b);
assert!(diff > 300, "adding second bishop should add >300cp: {diff}");
}
#[test]
fn mate_score_detection() {
assert!(is_mate_score(MATE));
assert!(is_mate_score(MATE - 10));
assert!(is_mate_score(-(MATE - 10)));
assert!(!is_mate_score(500));
assert!(!is_mate_score(0));
}
#[test]
fn mirror_square_works() {
assert_eq!(mirror_square(Square(0)), 56); assert_eq!(mirror_square(Square(63)), 7); assert_eq!(mirror_square(Square(4)), 60); }
}