aboutsummaryrefslogtreecommitdiff
path: root/src/grossmeister/evaluation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/grossmeister/evaluation.rs')
-rw-r--r--src/grossmeister/evaluation.rs276
1 files changed, 173 insertions, 103 deletions
diff --git a/src/grossmeister/evaluation.rs b/src/grossmeister/evaluation.rs
index 1b57f5a..8deb929 100644
--- a/src/grossmeister/evaluation.rs
+++ b/src/grossmeister/evaluation.rs
@@ -1,12 +1,17 @@
use std::f32::INFINITY;
-use crate::{board::{piece::Piece, color::Color, CastlingSide}, bitboard::{Bitboard, BitboardFns}, square::Square};
+use crate::{
+ bitboard::{Bitboard, BitboardFns},
+ board::{color::Color, piece::Piece, CastlingSide},
+ square::Square,
+};
use super::Grossmeister;
/// Score in pawns (not centipawns)
pub type Score = f32;
+#[rustfmt::skip]
const PAWN_BONUS: [Score; 64] = [
// A B C D E F G H
0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
@@ -19,6 +24,7 @@ const PAWN_BONUS: [Score; 64] = [
0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
];
+#[rustfmt::skip]
const PAWN_BONUS_PASSER_ENDGAME: [Score; 64] = [
// A B C D E F G H
0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
@@ -31,6 +37,7 @@ const PAWN_BONUS_PASSER_ENDGAME: [Score; 64] = [
0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
];
+#[rustfmt::skip]
const KNIGHT_BONUS: [Score; 64] = [
// A B C D E F G H
-0.50, -0.40, -0.30, -0.30, -0.30, -0.30, -0.40, -0.50,
@@ -43,6 +50,7 @@ const KNIGHT_BONUS: [Score; 64] = [
-0.50, -0.40, -0.30, -0.30, -0.30, -0.30, -0.40, -0.50,
];
+#[rustfmt::skip]
const BISHOP_BONUS: [Score; 64] = [
// A B C D E F G H
-0.20, -0.10, -0.10, -0.10, -0.10, -0.10, -0.10, -0.20,
@@ -55,6 +63,7 @@ const BISHOP_BONUS: [Score; 64] = [
-0.20, -0.10, -0.10, -0.10, -0.10, -0.10, -0.10, -0.20,
];
+#[rustfmt::skip]
const ROOK_BONUS: [Score; 64] = [
// A B C D E F G H
0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
@@ -67,6 +76,7 @@ const ROOK_BONUS: [Score; 64] = [
0.00, 0.00, 0.00, 0.05, 0.05, 0.00, 0.00, 0.00
];
+#[rustfmt::skip]
const QUEEN_BONUS: [Score; 64] = [
// A B C D E F G H
-0.20, -0.10, -0.10, -0.05, -0.05, -0.10, -0.10, -0.20,
@@ -79,6 +89,7 @@ const QUEEN_BONUS: [Score; 64] = [
-0.20, -0.10, -0.10, -0.05, -0.05, -0.10, -0.10, -0.20
];
+#[rustfmt::skip]
const KING_BONUS: [Score; 64] = [
// A B C D E F G H
-0.30, -0.40, -0.40, -0.50, -0.50, -0.40, -0.40, -0.30,
@@ -91,6 +102,7 @@ const KING_BONUS: [Score; 64] = [
0.20, 0.10, 0.30, -0.20, 0.00, 0.10, 0.30, 0.20
];
+#[rustfmt::skip]
const KING_BONUS_ENGAME: [Score; 64] = [
// A B C D E F G H
-0.50, -0.40, -0.30, -0.20, -0.20, -0.30, -0.40, -0.50,
@@ -149,39 +161,50 @@ impl Grossmeister {
/// Compute total bonus for well-positioned pieces
/// according to Piece-Square Tables
pub fn pst_bonus(&self, color: Color, is_endgame: bool) -> f32 {
- self.board.pieces_by_color(color).iter().enumerate().fold(0., |total, (piece_index, bitboard)| {
- let pst = match Piece::from(piece_index).without_color() {
- Piece::Pawn => if is_endgame && (bitboard & self.passer_mask(color) > 0) {
- PAWN_BONUS_PASSER_ENDGAME
- } else {
- PAWN_BONUS
- }
- Piece::Knight => KNIGHT_BONUS,
- Piece::Bishop => BISHOP_BONUS,
- Piece::Rook => ROOK_BONUS,
- Piece::Queen => QUEEN_BONUS,
- Piece::King => if is_endgame {
- KING_BONUS_ENGAME
- } else {
- KING_BONUS
- }
- _ => panic!("Unreachable")
- };
- total + bitboard.serialize().fold(0., |acc, square| {
- let pst_index = match color {
- Color::White => square.mirror(),
- Color::Black => square,
- } as usize;
- acc + pst[pst_index]
- })
- })
+ self.board.pieces_by_color(color).iter().enumerate().fold(
+ 0.,
+ |total, (piece_index, bitboard)| {
+ let pst = match Piece::from(piece_index).without_color() {
+ Piece::Pawn => {
+ if is_endgame && (bitboard & self.passer_mask(color) > 0) {
+ PAWN_BONUS_PASSER_ENDGAME
+ } else {
+ PAWN_BONUS
+ }
+ }
+ Piece::Knight => KNIGHT_BONUS,
+ Piece::Bishop => BISHOP_BONUS,
+ Piece::Rook => ROOK_BONUS,
+ Piece::Queen => QUEEN_BONUS,
+ Piece::King => {
+ if is_endgame {
+ KING_BONUS_ENGAME
+ } else {
+ KING_BONUS
+ }
+ }
+ _ => panic!("Unreachable"),
+ };
+ total
+ + bitboard.serialize().fold(0., |acc, square| {
+ let pst_index = match color {
+ Color::White => square.mirror(),
+ Color::Black => square,
+ } as usize;
+ acc + pst[pst_index]
+ })
+ },
+ )
}
/// Count raw material of the given color
pub fn material(&self, color: Color) -> f32 {
- self.board.pieces_by_color(color).iter().enumerate().fold(0., |acc, (piece_index, bitboard)| {
- acc + Piece::from(piece_index).static_eval() * bitboard.pop_count() as f32
- })
+ self.board.pieces_by_color(color).iter().enumerate().fold(
+ 0.,
+ |acc, (piece_index, bitboard)| {
+ acc + Piece::from(piece_index).static_eval() * bitboard.pop_count() as f32
+ },
+ )
}
// Returns a value in [0, 240] representing how
@@ -193,24 +216,27 @@ impl Grossmeister {
let rook_phase = 20;
let queen_phase = 40;
- let total_phase = knight_phase*4 + bishop_phase*4 + rook_phase*4 + queen_phase*2;
- // If you change it, make sure to update denominator in interpolation
+ let total_phase = knight_phase * 4 + bishop_phase * 4 + rook_phase * 4 + queen_phase * 2;
+ // If you change it, make sure to update denominator in interpolation
debug_assert_eq!(total_phase, 240);
- self.board.piece_sets.iter().enumerate().fold(0, |acc, (piece_index, &bitboard)| {
- acc + match Piece::from(piece_index).without_color() {
- Piece::King => 0,
- Piece::Pawn => 0,
- Piece::Knight => knight_phase,
- Piece::Bishop => bishop_phase,
- Piece::Rook => rook_phase,
- Piece::Queen => queen_phase,
- _ => panic!("Unreachable")
- } * bitboard.pop_count()
- })
+ self.board
+ .piece_sets
+ .iter()
+ .enumerate()
+ .fold(0, |acc, (piece_index, &bitboard)| {
+ acc + match Piece::from(piece_index).without_color() {
+ Piece::King => 0,
+ Piece::Pawn => 0,
+ Piece::Knight => knight_phase,
+ Piece::Bishop => bishop_phase,
+ Piece::Rook => rook_phase,
+ Piece::Queen => queen_phase,
+ _ => panic!("Unreachable"),
+ } * bitboard.pop_count()
+ })
}
-
/// Evaluate a position relative to the current player
pub fn evaluate(&self) -> Score {
let color = self.board.color();
@@ -220,7 +246,7 @@ impl Grossmeister {
let loseable = self.board.is_theoretically_winnable(opponent_color);
if !winnable && !loseable {
- return 0.0
+ return 0.0;
}
let material_advantage = self.material(color) - self.material(opponent_color);
@@ -229,22 +255,21 @@ impl Grossmeister {
let pawn_islands_advantage = self.pawn_islands(opponent_color) - self.pawn_islands(color);
// Middlegame eval, assumming all pieces are on the Board
- let middlegame_eval =
- mobility_advantage * 0.05 +
- pawn_shield_advantage * 0.20 +
- pawn_islands_advantage * 0.10 +
- (self.pst_bonus(color, false) - self.pst_bonus(opponent_color, false));
+ let middlegame_eval = mobility_advantage * 0.05
+ + pawn_shield_advantage * 0.20
+ + pawn_islands_advantage * 0.10
+ + (self.pst_bonus(color, false) - self.pst_bonus(opponent_color, false));
// Engame eval, assuming no pieces are left on the Board (only kings and pawns)
- let endgame_eval =
- mobility_advantage * 0.03 +
- pawn_shield_advantage * 0.05 +
- pawn_islands_advantage * 0.15 +
- (self.pst_bonus(color, true) - self.pst_bonus(opponent_color, true));
+ let endgame_eval = mobility_advantage * 0.03
+ + pawn_shield_advantage * 0.05
+ + pawn_islands_advantage * 0.15
+ + (self.pst_bonus(color, true) - self.pst_bonus(opponent_color, true));
// Interpolate evalutaion based on amount of pieces to remove discontinuity
let phase = self.phase();
- let tapered_eval = (middlegame_eval * phase as f32 + endgame_eval * (240 - phase) as f32) / 240.;
+ let tapered_eval =
+ (middlegame_eval * phase as f32 + endgame_eval * (240 - phase) as f32) / 240.;
let eval = (material_advantage + tapered_eval)
.min(if winnable { INFINITY } else { 0.0 }) // Can not score > 0 if not winnable
@@ -279,7 +304,9 @@ impl Grossmeister {
let ep_bitboard = match self.board.ep_target {
Some(square) => {
let rank = square.rank();
- if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) {
+ if (rank == 2 && color == Color::Black)
+ || (rank == 5 && color == Color::White)
+ {
square.to_bitboard()
} else {
0
@@ -287,20 +314,30 @@ impl Grossmeister {
}
None => 0,
};
- mobility += (self.board.attacks.pawn[color as usize][source as usize] & (opponent_occupancy | ep_bitboard)).pop_count() as f32;
- mobility += (self.board.attacks.pawn_pushes[color as usize][source as usize] & empty).pop_count() as f32;
+ mobility += (self.board.attacks.pawn[color as usize][source as usize]
+ & (opponent_occupancy | ep_bitboard))
+ .pop_count() as f32;
+ mobility += (self.board.attacks.pawn_pushes[color as usize]
+ [source as usize]
+ & empty)
+ .pop_count() as f32;
}
let able_to_double_push_mask = match color {
Color::White => empty >> 8,
Color::Black => empty << 8,
};
for source in (*piece & able_to_double_push_mask).serialize() {
- mobility += (self.board.attacks.pawn_double_pushes[color as usize][source as usize] & empty).pop_count() as f32;
+ mobility += (self.board.attacks.pawn_double_pushes[color as usize]
+ [source as usize]
+ & empty)
+ .pop_count() as f32;
}
}
Piece::King => {
for source in piece.serialize() {
- mobility += (self.board.attacks.king[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
+ mobility += (self.board.attacks.king[source as usize]
+ & (empty | opponent_occupancy))
+ .pop_count() as f32;
// Castling
let king_home_position = match color {
@@ -308,56 +345,79 @@ impl Grossmeister {
Color::Black => Square::E8,
};
if *piece == king_home_position.to_bitboard() {
- for rook_square in player_pieces[Piece::Rook as usize]
- .serialize()
- .filter(|rook_square| rook_square.rank() == king_home_position.rank())
- {
- match rook_square.file() {
- 0 => {
- let castle_line = [
- king_home_position.west_one(),
- king_home_position.west_one().west_one(),
- ];
-
- let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
- let any_checks = castle_line.iter().any(|square| self.board.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.board.castling_rights[color as usize][CastlingSide::Queen as usize] {
- mobility += 1.;
- }
- },
- 7 => {
- let castle_line = [
- king_home_position.east_one(),
- king_home_position.east_one().east_one(),
- ];
-
- let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
- let any_checks = castle_line.iter().any(|square| self.board.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.board.castling_rights[color as usize][CastlingSide::King as usize] {
- mobility += 1.;
- }
- },
- _ => {},
+ for rook_square in
+ player_pieces[Piece::Rook as usize].serialize().filter(
+ |rook_square| rook_square.rank() == king_home_position.rank(),
+ )
+ {
+ match rook_square.file() {
+ 0 => {
+ let castle_line = [
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ ];
+
+ let all_empty = castle_line
+ .iter()
+ .all(|square| empty & square.to_bitboard() > 0);
+ let any_checks = castle_line.iter().any(|square| {
+ self.board.is_square_attacked(*square, color.flip())
+ });
+
+ if all_empty
+ && !any_checks
+ && self.board.castling_rights[color as usize]
+ [CastlingSide::Queen as usize]
+ {
+ mobility += 1.;
+ }
+ }
+ 7 => {
+ let castle_line = [
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+ ];
+
+ let all_empty = castle_line
+ .iter()
+ .all(|square| empty & square.to_bitboard() > 0);
+ let any_checks = castle_line.iter().any(|square| {
+ self.board.is_square_attacked(*square, color.flip())
+ });
+
+ if all_empty
+ && !any_checks
+ && self.board.castling_rights[color as usize]
+ [CastlingSide::King as usize]
+ {
+ mobility += 1.;
+ }
}
+ _ => {}
}
+ }
}
}
}
Piece::Knight => {
for source in piece.serialize() {
- mobility += (self.board.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
+ mobility += (self.board.attacks.knight[source as usize]
+ & (empty | opponent_occupancy))
+ .pop_count() as f32;
}
}
Piece::Bishop => {
for source in piece.serialize() {
- mobility += (self.board.attacks.bishop(self.board.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
+ mobility += (self.board.attacks.bishop(self.board.occupancy, source)
+ & (empty | opponent_occupancy))
+ .pop_count() as f32;
}
}
Piece::Rook => {
for source in piece.serialize() {
- mobility += (self.board.attacks.rook(self.board.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
+ mobility += (self.board.attacks.rook(self.board.occupancy, source)
+ & (empty | opponent_occupancy))
+ .pop_count() as f32;
}
}
Piece::Queen => {
@@ -372,7 +432,10 @@ impl Grossmeister {
#[cfg(test)]
mod tests {
- use crate::{board::{Board, io::IO}, moves::{Move, MoveKind}};
+ use crate::{
+ board::{io::IO, Board},
+ moves::{Move, MoveKind},
+ };
use super::*;
@@ -407,7 +470,6 @@ mod tests {
let board = Board::new();
let gm = Grossmeister::new(board);
assert_eq!(gm.material(Color::Black), gm.material(Color::White));
-
}
#[test]
@@ -421,7 +483,11 @@ mod tests {
fn king_tropism() {
let board = Board::new();
let mut gm = Grossmeister::new(board);
- gm.board.make_move(Move { source: Square::D1, target: Square::F5, kind: MoveKind::Quiet });
+ gm.board.make_move(Move {
+ source: Square::D1,
+ target: Square::F5,
+ kind: MoveKind::Quiet,
+ });
let score = gm.evaluate();
gm.board.print();
println!("Score {}", score);
@@ -482,7 +548,8 @@ mod tests {
#[test]
fn discourage_edge_knights() {
let score1 = {
- let fen = String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
+ let fen =
+ String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
let board = Board::from_FEN(fen);
let gm = Grossmeister::new(board);
let score = gm.evaluate();
@@ -492,7 +559,8 @@ mod tests {
};
let score2 = {
- let fen = String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/7N/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
+ let fen =
+ String::from("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/7N/PPPP1PPP/RNBQKB1R w KQkq - 2 3");
let board = Board::from_FEN(fen);
let gm = Grossmeister::new(board);
let score = gm.evaluate();
@@ -507,7 +575,8 @@ mod tests {
#[test]
fn mirrored_evaluation() {
let score1 = {
- let fen = String::from("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1");
+ let fen =
+ String::from("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1");
let board = Board::from_FEN(fen);
let gm = Grossmeister::new(board);
let score = gm.evaluate();
@@ -517,7 +586,8 @@ mod tests {
};
let score2 = {
- let fen = String::from("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1 ");
+ let fen =
+ String::from("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1 ");
let board = Board::from_FEN(fen);
let gm = Grossmeister::new(board);
let score = gm.evaluate();