use crate::bitboard::{self, Bitboard};
use crate::coretypes::{Color, Cp, CpKind, PieceKind, SquareIndexable, NUM_RANKS, NUM_SQUARES};
use crate::coretypes::{Color::*, PieceKind::*};
use crate::movegen as mg;
use crate::position::Position;
impl PieceKind {
pub const fn centipawns(&self) -> Cp {
Cp(match self {
Pawn => 100, Knight => 305, Bishop => 310, Rook => 510,
Queen => 950,
King => 10_000,
})
}
}
const MOBILITY_CP: Cp = Cp(1);
pub fn terminal(position: &Position) -> Cp {
if position.is_checkmate() {
-Cp::CHECKMATE
} else {
Cp::STALEMATE
}
}
pub fn draw(is_engine: bool, contempt: Cp) -> Cp {
Cp::STALEMATE
+ match is_engine {
true => -contempt,
false => contempt,
}
}
pub fn evaluate(position: &Position) -> Cp {
evaluate_abs(position) * position.player.sign()
}
pub fn terminal_abs(position: &Position) -> Cp {
if position.is_checkmate() {
match position.player {
White => -Cp::CHECKMATE,
Black => Cp::CHECKMATE,
}
} else {
Cp::STALEMATE
}
}
pub fn evaluate_abs(position: &Position) -> Cp {
let cp_material = material(position);
let cp_piece_sq = piece_square_lookup(position);
let cp_pass_pawns = pass_pawns(position);
let cp_xray_king = xray_king_attacks(position);
let cp_mobility = mobility(position);
let cp_king_safety = king_safety(position);
let cp_total =
cp_material + cp_piece_sq + cp_pass_pawns + cp_xray_king + cp_mobility + cp_king_safety;
cp_total
}
pub fn material(position: &Position) -> Cp {
let w_piece_cp: Cp = PieceKind::iter()
.map(|pk| pk.centipawns() * position.pieces[(White, pk)].count_squares())
.fold(Cp::default(), |acc, value| acc + value);
let b_piece_cp: Cp = PieceKind::iter()
.map(|pk| pk.centipawns() * position.pieces[(Black, pk)].count_squares())
.fold(Cp::default(), |acc, value| acc + value);
w_piece_cp - b_piece_cp
}
pub fn king_safety(position: &Position) -> Cp {
let mut cp = Cp(0);
let occupied = position.pieces.occupied();
let w_sliding = position.pieces[(White, Queen)]
| position.pieces[(White, Rook)]
| position.pieces[(White, Bishop)];
let b_sliding = position.pieces[(Black, Queen)]
| position.pieces[(Black, Rook)]
| position.pieces[(Black, Bishop)];
let w_num_sliding = w_sliding.count_squares();
let b_num_sliding = b_sliding.count_squares();
let w_king = position.pieces[(White, King)];
let b_king = position.pieces[(Black, King)];
let w_king_open_squares = mg::queen_attacks(w_king, occupied).count_squares();
let b_king_open_squares = mg::queen_attacks(b_king, occupied).count_squares();
let w_value = b_king_open_squares * w_num_sliding / 2;
let b_value = w_king_open_squares * b_num_sliding / 2;
let value_diff = Cp(w_value as CpKind - b_value as CpKind);
cp += value_diff;
cp
}
pub fn mobility(position: &Position) -> Cp {
let w_attacks = position.attacks(White, position.pieces().occupied());
let b_attacks = position.attacks(Black, position.pieces().occupied());
let attack_surface_area_diff =
w_attacks.count_squares() as CpKind - b_attacks.count_squares() as CpKind;
Cp(attack_surface_area_diff) * MOBILITY_CP
}
pub fn pass_pawns(position: &Position) -> Cp {
const SCALAR: Cp = Cp(20);
const RANK_CP: [CpKind; NUM_RANKS] = [0, 0, 1, 2, 10, 50, 250, 900];
let w_passed: Bitboard = pass_pawns_bb(position, White);
let b_passed: Bitboard = pass_pawns_bb(position, Black);
let w_num_passed = w_passed.count_squares() as CpKind;
let b_num_passed = b_passed.count_squares() as CpKind;
let w_rank_bonus = w_passed
.into_iter()
.map(|sq| sq.rank())
.fold(Cp(0), |acc, rank| acc + Cp(RANK_CP[rank as usize]));
let b_rank_bonus = b_passed
.into_iter()
.map(|sq| sq.rank().flip())
.fold(Cp(0), |acc, rank| acc + Cp(RANK_CP[rank as usize]));
Cp(w_num_passed - b_num_passed) * SCALAR + w_rank_bonus - b_rank_bonus
}
pub fn xray_king_attacks(position: &Position) -> Cp {
const SCALAR: Cp = Cp(8);
let w_king = position.pieces[(White, King)].get_lowest_square().unwrap();
let b_king = position.pieces[(Black, King)].get_lowest_square().unwrap();
let w_king_ortho = Bitboard::from(w_king.file()) | Bitboard::from(w_king.rank());
let b_king_ortho = Bitboard::from(b_king.file()) | Bitboard::from(b_king.rank());
let w_king_diags = mg::bishop_pattern(w_king);
let b_king_diags = mg::bishop_pattern(b_king);
let w_diags = position.pieces[(White, Queen)] | position.pieces[(White, Bishop)];
let b_diags = position.pieces[(Black, Queen)] | position.pieces[(Black, Bishop)];
let w_ortho = position.pieces[(White, Queen)] | position.pieces[(White, Rook)];
let b_ortho = position.pieces[(Black, Queen)] | position.pieces[(Black, Rook)];
let w_xray_attackers_bb = (b_king_diags & w_diags) | (b_king_ortho & w_ortho);
let b_xray_attackers_bb = (w_king_diags & b_diags) | (w_king_ortho & b_ortho);
let w_xray_attackers: CpKind = w_xray_attackers_bb.count_squares() as CpKind;
let b_xray_attackers: CpKind = b_xray_attackers_bb.count_squares() as CpKind;
Cp(w_xray_attackers - b_xray_attackers) * SCALAR
}
pub fn piece_square_lookup(position: &Position) -> Cp {
let mut w_values = Cp(0);
position.pieces[(White, Pawn)]
.into_iter()
.for_each(|sq| w_values += Cp(MG_PAWN_TABLE[sq.idx()]));
position.pieces[(White, Knight)]
.into_iter()
.for_each(|sq| w_values += Cp(MG_KNIGHT_TABLE[sq.idx()]));
position.pieces[(White, Bishop)]
.into_iter()
.for_each(|sq| w_values += Cp(MG_BISHOP_TABLE[sq.idx()]));
position.pieces[(White, King)]
.into_iter()
.for_each(|sq| w_values += Cp(MG_KING_TABLE[sq.idx()]));
let mut b_values = Cp(0);
position.pieces[(Black, Pawn)]
.into_iter()
.for_each(|sq| b_values += Cp(MG_PAWN_TABLE[sq.flip_rank().idx()]));
position.pieces[(Black, Knight)]
.into_iter()
.for_each(|sq| b_values += Cp(MG_KNIGHT_TABLE[sq.flip_rank().idx()]));
position.pieces[(Black, Bishop)]
.into_iter()
.for_each(|sq| b_values += Cp(MG_BISHOP_TABLE[sq.flip_rank().idx()]));
position.pieces[(Black, King)]
.into_iter()
.for_each(|sq| b_values += Cp(MG_KING_TABLE[sq.flip_rank().idx()]));
w_values - b_values
}
#[inline]
fn pass_pawns_bb(position: &Position, player: Color) -> Bitboard {
use Bitboard as Bb;
let opponent_pawns = position.pieces[(!player, Pawn)];
let spans = opponent_pawns
.into_iter()
.map(|sq| {
let file = sq.file();
let mut span = Bb::from(file);
match player {
Color::White => span.clear_square_and_above(sq),
Color::Black => span.clear_square_and_below(sq),
};
span | span.to_east() | span.to_west()
})
.fold(Bitboard::EMPTY, |acc, bb| acc | bb);
position.pieces[(player, Pawn)] & !spans
}
#[rustfmt::skip]
const MG_PAWN_TABLE: [CpKind; NUM_SQUARES] = [
0, 0, 0, 0, 0, 0, 0, 0,
5, 1, 0, -20, -20, 0, 1, 5,
5, -2, 0, 0, 0, 0, -2, 5,
0, 0, 0, 20, 20, 0, 0, 0,
2, 2, 2, 21, 21, 2, 2, 2,
3, 3, 3, 22, 22, 3, 3, 3,
4, 4, 4, 23, 23, 4, 4, 4,
0, 0, 0, 0, 0, 0, 0, 0,
];
#[rustfmt::skip]
const MG_KNIGHT_TABLE: [CpKind; NUM_SQUARES] = [
-50, -30, -20, -20, -20, -20, -30, -50,
-20, 0, 0, 5, 5, 0, 0, -20,
-10, 0, 10, 15, 15, 10, 0, -10,
-10, 0, 15, 20, 20, 15, 0, -10,
-10, 0, 15, 20, 20, 15, 0, -10,
-10, 0, 10, 15, 15, 10, 0, -10,
-20, 0, 0, 0, 0, 0, 0, -20,
-50, -10, -10, -10, -10, -10, -10, -50,
];
#[rustfmt::skip]
const MG_BISHOP_TABLE: [CpKind; NUM_SQUARES] = [
-20, -8, -10, -8, -8, -10, -8, -20,
-8, 5, 0, 0, 0, 0, 5, -8,
-8, 10, 10, 10, 10, 10, 10, -8,
-8, 0, 10, 10, 10, 10, 0, -8,
-8, 0, 10, 10, 10, 10, 0, -8,
-8, 0, 10, 10, 10, 10, 0, -8,
-8, 0, 0, 0, 0, 0, 0, -8,
-20, -8, -8, -8, -8, -8, -8, -20,
];
#[rustfmt::skip]
const MG_KING_TABLE: [CpKind; NUM_SQUARES] = [
20, 30, 10, 0, 0, 10, 30, 20,
20, 20, 0, 0, 0, 0, 20, 20,
-10, -10, -15, -15, -15, -15, -10, -10,
-10, -10, -10, -10, -10, -10, -10, -10,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
pub const PASS_PAWN_SIZE: usize = (NUM_SQUARES - 24) * 2;
pub const PASS_PAWN_PATTERN: [Bitboard; PASS_PAWN_SIZE] = generate_pass_pawn_pattern();
macro_rules! w_repeat_for_each {
($array:ident, $func:ident, $($numbers:literal),+) => {
{
$($array[$numbers - 8] = $func($numbers);)*
}
};
}
const fn generate_pass_pawn_pattern() -> [Bitboard; PASS_PAWN_SIZE] {
let mut array = [Bitboard::EMPTY; PASS_PAWN_SIZE];
#[rustfmt::skip]
w_repeat_for_each!(
array,
w_pass_pawn_pattern_idx,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47
);
array
}
const fn w_pass_pawn_pattern_idx(square: usize) -> Bitboard {
use Bitboard as Bb;
let square_bb: bitboard::BitboardKind = 1u64 << square;
if square_bb & Bitboard::FILE_A.0 > 0 {
let mut pass_pawn_pat = Bitboard::FILE_A.0 | Bitboard::FILE_B.0;
pass_pawn_pat &= !square_bb; pass_pawn_pat &= !(square_bb << 1); if square != 0 {
pass_pawn_pat &= !(square_bb - 1);
}
Bitboard(pass_pawn_pat)
} else if square_bb & Bitboard::FILE_H.0 > 0 {
let mut pass_pawn_pat = Bitboard::FILE_G.0 | Bitboard::FILE_H.0;
pass_pawn_pat &= !(square_bb ^ (square_bb - 1)); Bitboard(pass_pawn_pat)
} else {
let mut pass_pawn_pat = match square_bb {
bb if bb & Bb::FILE_B.0 > 0 => Bb::FILE_A.0 | Bb::FILE_B.0 | Bb::FILE_C.0,
bb if bb & Bb::FILE_C.0 > 0 => Bb::FILE_B.0 | Bb::FILE_C.0 | Bb::FILE_D.0,
bb if bb & Bb::FILE_D.0 > 0 => Bb::FILE_C.0 | Bb::FILE_D.0 | Bb::FILE_E.0,
bb if bb & Bb::FILE_E.0 > 0 => Bb::FILE_D.0 | Bb::FILE_E.0 | Bb::FILE_F.0,
bb if bb & Bb::FILE_F.0 > 0 => Bb::FILE_E.0 | Bb::FILE_F.0 | Bb::FILE_G.0,
bb if bb & Bb::FILE_G.0 > 0 => Bb::FILE_F.0 | Bb::FILE_G.0 | Bb::FILE_H.0,
_ => 0,
};
pass_pawn_pat &= !(square_bb ^ (square_bb - 1)); pass_pawn_pat &= !(square_bb << 1);
Bitboard(pass_pawn_pat)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Fen;
#[test]
fn start_pos_equal_eval() {
let mut start = Position::start_position();
let w_eval = evaluate(&start);
start.player = Black;
let b_eval = evaluate(&start);
assert_eq!(w_eval, b_eval);
assert_eq!(w_eval, evaluate(&start.color_flip()));
}
#[test]
fn cp_min_and_max() {
let min = Cp::MIN;
let max = Cp::MAX;
assert_eq!(min.signum(), -1);
assert_eq!(max.signum(), 1);
assert_eq!((-min).signum(), 1);
assert_eq!((-max).signum(), -1);
}
#[test]
fn large_eval_in_score_range() {
let pos = Position::parse_fen("4k3/8/8/8/8/8/QQQQ1QQQ/QQQQKQQQ w - - 0 1").unwrap();
let score = evaluate(&pos);
assert!(score.is_score());
assert!(score.is_legal());
assert!(!score.is_mate());
println!("MAX POSSIBLE SCORE: {}", score);
}
}