From 0790e688efed7aba6e6185a9a16cb2f8038a2405 Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Fri, 10 Feb 2023 18:03:25 -0500 Subject: [PATCH] Rewrite the entirety of board eval or something idk Co-authored-by: Tymon --- .gitignore | 1 + TODO.md | 4 +- src/config.rs | 2 +- src/eval.rs | 181 ++++++++++++++++++++++++++++++++++++-------- src/main.rs | 18 ++--- src/piece_tables.rs | 147 +++++++++++++++++++++++++++++++++++ src/piece_values.rs | 22 ++++++ 7 files changed, 330 insertions(+), 45 deletions(-) create mode 100644 src/piece_tables.rs create mode 100644 src/piece_values.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..ecb727e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +inkwell-regression-testing \ No newline at end of file diff --git a/TODO.md b/TODO.md index ddd8405..54b38d9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,5 @@ # 1.666.0-rc -- [ ] Ability to evaluate game state (whether it's early/mid/end-game) -- [ ] Piece tables +- [x] Ability to evaluate game state (whether it's early/mid/end-game) +- [x] Piece tables - [ ] Draw prevention (via `chess`'s Game struct?) - [ ] Basic king safety (ie castling when appropriate, not always playing the double triple decker bongcloud) \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 620ff4a..3318243 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,4 +20,4 @@ pub const INFO: Info = Info { }; // FIXME: this needs to be configurable via UCI -pub const depth: u8 = 6; +pub const DEPTH: u8 = 6; diff --git a/src/eval.rs b/src/eval.rs index 25abf67..9a7c997 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,10 +1,114 @@ -use rand::prelude::*; use std::f64::{INFINITY, NEG_INFINITY}; -use chess::{Board, BoardStatus, ChessMove, Color, MoveGen, Piece}; +use chess::{Board, BoardStatus, ChessMove, Color, File, MoveGen, Piece, Rank, Square}; + +use crate::piece_tables; +use crate::piece_values::get_piece_value; + +fn square_to_index(square: Square, playing_as: Color) -> usize { + let mut idx = 0; + if playing_as == Color::White { + match square.get_rank() { + Rank::First => idx += 0, + Rank::Second => idx += 8, + Rank::Third => idx += 16, + Rank::Fourth => idx += 24, + Rank::Fifth => idx += 32, + Rank::Sixth => idx += 40, + Rank::Seventh => idx += 48, + Rank::Eighth => idx += 56, + }; + match square.get_file() { + File::A => idx += 0, + File::B => idx += 1, + File::C => idx += 2, + File::D => idx += 3, + File::E => idx += 4, + File::F => idx += 5, + File::G => idx += 6, + File::H => idx += 7, + }; + } else { + match square.get_rank() { + Rank::Eighth => idx += 0, + Rank::Seventh => idx += 8, + Rank::Sixth => idx += 16, + Rank::Fifth => idx += 24, + Rank::Fourth => idx += 32, + Rank::Third => idx += 40, + Rank::Second => idx += 48, + Rank::First => idx += 56, + }; + match square.get_file() { + File::H => idx += 0, + File::G => idx += 1, + File::F => idx += 2, + File::E => idx += 3, + File::D => idx += 4, + File::C => idx += 5, + File::B => idx += 6, + File::A => idx += 7, + } + } + idx +} + +fn evaluate_piece_square(board: Board, square: Square, playing_as: Color) -> (f64, f64, f64) { + let mut mg_value: f64 = 0.0; + let mut eg_value: f64 = 0.0; + let mut gamephase: f64 = 0.0; + + let piece = board.piece_on(square); + + match piece { + Some(Piece::Pawn) => { + mg_value += get_piece_value(piece) + + (piece_tables::MIDGAME_PAWNS[square_to_index(square, playing_as)] / 10.0); + eg_value += get_piece_value(piece) + + (piece_tables::ENDGAME_PAWNS[square_to_index(square, playing_as)] / 10.0); + gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize]; + } + Some(Piece::Knight) => { + mg_value += get_piece_value(piece) + + (piece_tables::MIDGAME_HORSEYS[square_to_index(square, playing_as)] / 10.0); + eg_value += get_piece_value(piece) + + (piece_tables::ENDGAME_HORSEYS[square_to_index(square, playing_as)] / 10.0); + gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize]; + } + Some(Piece::Bishop) => { + mg_value += get_piece_value(piece) + + (piece_tables::MIDGAME_BISHOPS[square_to_index(square, playing_as)] / 10.0); + eg_value += get_piece_value(piece) + + (piece_tables::ENDGAME_BISHOPS[square_to_index(square, playing_as)] / 10.0); + gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize]; + } + Some(Piece::Rook) => { + mg_value += get_piece_value(piece) + + (piece_tables::MIDGAME_ROOKS[square_to_index(square, playing_as)] / 10.0); + eg_value += get_piece_value(piece) + + (piece_tables::ENDGAME_ROOKS[square_to_index(square, playing_as)] / 10.0); + gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize]; + } + Some(Piece::Queen) => { + mg_value += get_piece_value(piece) + + (piece_tables::MIDGAME_QUEENS[square_to_index(square, playing_as)] / 10.0); + eg_value += get_piece_value(piece) + + (piece_tables::ENDGAME_QUEENS[square_to_index(square, playing_as)] / 10.0); + gamephase += piece_tables::GAMEPHASE[get_piece_value(piece).floor() as usize]; + } + Some(Piece::King) => { + mg_value += piece_tables::MIDGAME_KINGS[square_to_index(square, playing_as)] / 10.0; + eg_value += piece_tables::ENDGAME_KINGS[square_to_index(square, playing_as)] / 10.0; + } + None => {} + } + + (mg_value, eg_value, gamephase) +} fn static_board_eval( mov: ChessMove, + old_board: Board, board: Board, playing_as: Color, move_history: Vec<(ChessMove, Board)>, @@ -17,31 +121,33 @@ fn static_board_eval( } } - let mut current_eval = 0.0; + // basic material counting + midgame or endgame determination + let mut our_mg_value: f64 = 0.0; + let mut our_eg_value: f64 = 0.0; + let mut opponent_mg_value: f64 = 0.0; + let mut opponent_eg_value: f64 = 0.0; + let mut gamephase: f64 = 0.0; - // basic material counting board.color_combined(playing_as).for_each(|square| { - match board.piece_on(square) { - Some(Piece::Pawn) => current_eval += 1.0, - Some(Piece::Knight) => current_eval += 3.0, - Some(Piece::Bishop) => current_eval += 5.0, - Some(Piece::Rook) => current_eval += 5.0, - Some(Piece::Queen) => current_eval += 8.0, - _ => { /* do nothing */ } - } + let tmp_gamephase; + (our_mg_value, our_eg_value, tmp_gamephase) = + evaluate_piece_square(board, square, playing_as); + gamephase += tmp_gamephase; }); board.color_combined(!playing_as).for_each(|square| { - match board.piece_on(square) { - Some(Piece::Pawn) => current_eval -= 1.0, - Some(Piece::Knight) => current_eval -= 3.0, - Some(Piece::Bishop) => current_eval -= 5.0, - Some(Piece::Rook) => current_eval -= 5.0, - Some(Piece::Queen) => current_eval -= 8.0, - _ => { /* do nothing */ } - } + let tmp_gamephase; + (opponent_mg_value, opponent_eg_value, tmp_gamephase) = + evaluate_piece_square(board, square, !playing_as); + gamephase += tmp_gamephase; }); + let mg_score = our_mg_value - opponent_mg_value; + let eg_score = our_eg_value - opponent_eg_value; + let mg_phase = gamephase.min(24.0); + let eg_phase = 24.0 - mg_phase; + let mut current_eval = (mg_score * mg_phase + eg_score * eg_phase) / 24.0; + // checking heuristic let mut are_we_in_check = false; let mut is_opponent_in_check = false; @@ -60,12 +166,7 @@ fn static_board_eval( .checkers() .filter(|sq| board.color_on(*sq).unwrap() == !playing_as) .for_each(|_| { - if !is_opponent_in_check - && move_history.len() - > *vec![10, 11, 12, 13, 14, 15] - .choose(&mut rand::thread_rng()) - .unwrap() as usize - { + if !is_opponent_in_check && move_history.len() > 10 { current_eval += 17.5; is_opponent_in_check = true; } else { @@ -78,29 +179,45 @@ fn static_board_eval( current_eval -= 12.5; } + // incentivize capturing if material value of capturer is lower than material value of captured + // FIXME: this should take into account whether a piece is protected at all (and if so let pieces be captured by ones of equal and above importance) + if let (Some(captured_piece), Some(capturing_piece)) = ( + old_board.piece_on(mov.get_dest()), + old_board.piece_on(mov.get_source()), + ) { + current_eval += + (get_piece_value(Some(captured_piece)) - get_piece_value(Some(capturing_piece))) + } + current_eval } pub fn eval_board( board: Board, + last_board: Board, is_maximizing_player: bool, depth: u8, move_history: Vec<(ChessMove, Board)>, - last_move: Option, original_playing_as: Color, mut alpha: f64, mut beta: f64, ) -> f64 { - let moves_iter = MoveGen::new_legal(&board).into_iter(); + let last_move = if move_history.len() > 0 { + Some(move_history.last().unwrap().0) + } else { + None + }; if depth == 0 || board.status() == BoardStatus::Checkmate { return static_board_eval( - last_move.unwrap_or(move_history.last().unwrap().0), + last_move.unwrap(), + last_board, board, - original_playing_as, + !board.side_to_move(), move_history, ); } else { + let moves_iter = MoveGen::new_legal(&board).into_iter(); if is_maximizing_player { let mut max_eval = NEG_INFINITY; for mov in moves_iter { @@ -111,10 +228,10 @@ pub fn eval_board( let eval = eval_board( new_board, + board, false, depth - 1, new_move_history, - Some(mov), original_playing_as, alpha, beta, @@ -136,10 +253,10 @@ pub fn eval_board( let eval = eval_board( new_board, + board, true, depth - 1, new_move_history, - Some(mov), original_playing_as, alpha, beta, diff --git a/src/main.rs b/src/main.rs index 10d1022..0e3eb99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ mod config; mod eval; +mod piece_tables; +mod piece_values; use chess::{Board, ChessMove, MoveGen}; use config::INFO; @@ -14,12 +16,12 @@ use std::str::FromStr; use vampirc_uci::parse_one; use vampirc_uci::{UciMessage, UciOptionConfig, UciTimeControl}; -use crate::config::depth; +use crate::config::DEPTH; fn main() { - let _rng = rand::thread_rng(); let mut board: Option = None; let mut move_history: Vec<(ChessMove, Board)> = vec![]; + for line in io::stdin().lock().lines() { let msg: UciMessage = parse_one(&line.unwrap()); match msg { @@ -106,14 +108,10 @@ fn main() { *a, eval_board( a.1, + board, true, - depth, + DEPTH, move_history.clone(), - if move_history.len() > 0 { - Some(move_history.last().unwrap().0) - } else { - None - }, color, NEG_INFINITY, INFINITY, @@ -126,10 +124,10 @@ fn main() { *b, eval_board( b.1, + board, true, - depth, + DEPTH, move_history.clone(), - None, color, NEG_INFINITY, INFINITY, diff --git a/src/piece_tables.rs b/src/piece_tables.rs new file mode 100644 index 0000000..77fed85 --- /dev/null +++ b/src/piece_tables.rs @@ -0,0 +1,147 @@ +// piece tables originally sourced from https://www.talkchess.com/forum3/viewtopic.php?f=2&t=68311&start=19 via https://www.chessprogramming.org/PeSTO%27s_Evaluation_Function + +pub const GAMEPHASE: [f64; 12] = [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 4.0, 4.0, 0.0, 0.0]; + +#[rustfmt::skip] +pub const MIDGAME_PAWNS: [f64; 64] = [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 98.0, 134.0, 61.0, 95.0, 68.0, 126.0, 34.0, -11.0, + -6.0, 7.0, 26.0, 31.0, 65.0, 56.0, 25.0, -20.0, + -14.0, 13.0, 6.0, 21.0, 23.0, 12.0, 17.0, -23.0, + -27.0, -2.0, -5.0, 12.0, 17.0, 6.0, 10.0, -25.0, + -26.0, -4.0, -4.0, -10.0, 3.0, 3.0, 33.0, -12.0, + -35.0, -1.0, -20.0, -23.0, -15.0, 24.0, 38.0, -22.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_PAWNS: [f64; 64] = [ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 178.0, 173.0, 158.0, 134.0, 147.0, 132.0, 165.0, 187.0, + 94.0, 100.0, 85.0, 67.0, 56.0, 53.0, 82.0, 84.0, + 32.0, 24.0, 13.0, 5.0, -2.0, 4.0, 17.0, 17.0, + 13.0, 9.0, -3.0, -7.0, -7.0, -8.0, 3.0, -1.0, + 4.0, 7.0, -6.0, 1.0, 0.0, -5.0, -1.0, -8.0, + 13.0, 8.0, 8.0, 10.0, 13.0, 0.0, 2.0, -7.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, +]; + +#[rustfmt::skip] +pub const MIDGAME_HORSEYS: [f64; 64] = [ + -167.0, -89.0, -34.0, -49.0, 61.0, -97.0, -15.0, -107.0, + -73.0, -41.0, 72.0, 36.0, 23.0, 62.0, 7.0, -17.0, + -47.0, 60.0, 37.0, 65.0, 84.0, 129.0, 73.0, 44.0, + -9.0, 17.0, 19.0, 53.0, 37.0, 69.0, 18.0, 22.0, + -13.0, 4.0, 16.0, 13.0, 28.0, 19.0, 21.0, -8.0, + -23.0, -9.0, 12.0, 10.0, 19.0, 17.0, 25.0, -16.0, + -29.0, -53.0, -12.0, -3.0, -1.0, 18.0, -14.0, -19.0, + -105.0, -21.0, -58.0, -33.0, -17.0, -28.0, -19.0, -23.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_HORSEYS: [f64; 64] = [ + -58.0, -38.0, -13.0, -28.0, -31.0, -27.0, -63.0, -99.0, + -25.0, -8.0, -25.0, -2.0, -9.0, -25.0, -24.0, -52.0, + -24.0, -20.0, 10.0, 9.0, -1.0, -9.0, -19.0, -41.0, + -17.0, 3.0, 22.0, 22.0, 22.0, 11.0, 8.0, -18.0, + -18.0, -6.0, 16.0, 25.0, 16.0, 17.0, 4.0, -18.0, + -23.0, -3.0, -1.0, 15.0, 10.0, -3.0, -20.0, -22.0, + -42.0, -20.0, -10.0, -5.0, -2.0, -20.0, -23.0, -44.0, + -29.0, -51.0, -23.0, -15.0, -22.0, -18.0, -50.0, -64.0, +]; + +#[rustfmt::skip] +pub const MIDGAME_BISHOPS: [f64; 64] = [ + -29.0, 4.0, -82.0, -37.0, -25.0, -42.0, 7.0, -8.0, + -26.0, 16.0, -18.0, -13.0, 30.0, 59.0, 18.0, -47.0, + -16.0, 37.0, 43.0, 40.0, 35.0, 50.0, 37.0, -2.0, + -4.0, 5.0, 19.0, 50.0, 37.0, 37.0, 7.0, -2.0, + -6.0, 13.0, 13.0, 26.0, 34.0, 12.0, 10.0, 4.0, + 0.0, 15.0, 15.0, 15.0, 14.0, 27.0, 18.0, 10.0, + 4.0, 15.0, 16.0, 0.0, 7.0, 21.0, 33.0, 1.0, + -33.0, -3.0, -14.0, -21.0, -13.0, -12.0, -39.0, -21.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_BISHOPS: [f64; 64] = [ + -14.0, -21.0, -11.0, -8.0, -7.0, -9.0, -17.0, -24.0, + -8.0, -4.0, 7.0, -12.0, -3.0, -13.0, -4.0, -14.0, + 2.0, -8.0, 0.0, -1.0, -2.0, 6.0, 0.0, 4.0, + -3.0, 9.0, 12.0, 9.0, 14.0, 10.0, 3.0, 2.0, + -6.0, 3.0, 13.0, 19.0, 7.0, 10.0, -3.0, -9.0, + -12.0, -3.0, 8.0, 10.0, 13.0, 3.0, -7.0, -15.0, + -14.0, -18.0, -7.0, -1.0, 4.0, -9.0, -15.0, -27.0, + -23.0, -9.0, -23.0, -5.0, -9.0, -16.0, -5.0, -17.0, +]; + +#[rustfmt::skip] +pub const MIDGAME_ROOKS: [f64; 64] = [ + 32.0, 42.0, 32.0, 51.0, 63.0, 9.0, 31.0, 43.0, + 27.0, 32.0, 58.0, 62.0, 80.0, 67.0, 26.0, 44.0, + -5.0, 19.0, 26.0, 36.0, 17.0, 45.0, 61.0, 16.0, + -24.0, -11.0, 7.0, 26.0, 24.0, 35.0, -8.0, -20.0, + -36.0, -26.0, -12.0, -1.0, 9.0, -7.0, 6.0, -23.0, + -45.0, -25.0, -16.0, -17.0, 3.0, 0.0, -5.0, -33.0, + -44.0, -16.0, -20.0, -9.0, -1.0, 11.0, -6.0, -71.0, + -19.0, -13.0, 1.0, 17.0, 16.0, 7.0, -37.0, -26.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_ROOKS: [f64; 64] = [ + 13.0, 10.0, 18.0, 15.0, 12.0, 12.0, 8.0, 5.0, + 11.0, 13.0, 13.0, 11.0, -3.0, 3.0, 8.0, 3.0, + 7.0, 7.0, 7.0, 5.0, 4.0, -3.0, -5.0, -3.0, + 4.0, 3.0, 13.0, 1.0, 2.0, 1.0, -1.0, 2.0, + 3.0, 5.0, 8.0, 4.0, -5.0, -6.0, -8.0, -11.0, + -4.0, 0.0, -5.0, -1.0, -7.0, -12.0, -8.0, -16.0, + -6.0, -6.0, 0.0, 2.0, -9.0, -9.0, -11.0, -3.0, + -9.0, 2.0, 3.0, -1.0, -5.0, -13.0, 4.0, -20.0, +]; + +#[rustfmt::skip] +pub const MIDGAME_QUEENS: [f64; 64] = [ + -28.0, 0.0, 29.0, 12.0, 59.0, 44.0, 43.0, 45.0, + -24.0, -39.0, -5.0, 1.0, -16.0, 57.0, 28.0, 54.0, + -13.0, -17.0, 7.0, 8.0, 29.0, 56.0, 47.0, 57.0, + -27.0, -27.0, -16.0, -16.0, -1.0, 17.0, -2.0, 1.0, + -9.0, -26.0, -9.0, -10.0, -2.0, -4.0, 3.0, -3.0, + -14.0, 2.0, -11.0, -2.0, -5.0, 2.0, 14.0, 5.0, + -35.0, -8.0, 11.0, 2.0, 8.0, 15.0, -3.0, 1.0, + -1.0, -18.0, -9.0, 10.0, -15.0, -25.0, -31.0, -50.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_QUEENS: [f64; 64] = [ + -9.0, 22.0, 22.0, 27.0, 27.0, 19.0, 10.0, 20.0, + -17.0, 20.0, 32.0, 41.0, 58.0, 25.0, 30.0, 0.0, + -20.0, 6.0, 9.0, 49.0, 47.0, 35.0, 19.0, 9.0, + 3.0, 22.0, 24.0, 45.0, 57.0, 40.0, 57.0, 36.0, + -18.0, 28.0, 19.0, 47.0, 31.0, 34.0, 39.0, 23.0, + -16.0, -27.0, 15.0, 6.0, 9.0, 17.0, 10.0, 5.0, + -22.0, -23.0, -30.0, -16.0, -16.0, -23.0, -36.0, -32.0, + -33.0, -28.0, -22.0, -43.0, -5.0, -32.0, -20.0, -41.0, +]; + +#[rustfmt::skip] +pub const MIDGAME_KINGS: [f64; 64] = [ + -65.0, 23.0, 16.0, -15.0, -56.0, -34.0, 2.0, 13.0, + 29.0, -1.0, -20.0, -7.0, -8.0, -4.0, -38.0, -29.0, + -9.0, 24.0, 2.0, -16.0, -20.0, 6.0, 22.0, -22.0, + -17.0, -20.0, -12.0, -27.0, -30.0, -25.0, -14.0, -36.0, + -49.0, -1.0, -27.0, -39.0, -46.0, -44.0, -33.0, -51.0, + -14.0, -14.0, -22.0, -46.0, -44.0, -30.0, -15.0, -27.0, + 1.0, 7.0, -8.0, -64.0, -43.0, -16.0, 9.0, 8.0, + -15.0, 36.0, 12.0, -54.0, 8.0, -28.0, 24.0, 14.0, +]; + +#[rustfmt::skip] +pub const ENDGAME_KINGS: [f64; 64] = [ + -74.0, -35.0, -18.0, -18.0, -11.0, 15.0, 4.0, -17.0, + -12.0, 17.0, 14.0, 17.0, 17.0, 38.0, 23.0, 11.0, + 10.0, 17.0, 23.0, 15.0, 20.0, 45.0, 44.0, 13.0, + -8.0, 22.0, 24.0, 27.0, 26.0, 33.0, 26.0, 3.0, + -18.0, -4.0, 21.0, 24.0, 27.0, 23.0, 9.0, -11.0, + -19.0, -3.0, 11.0, 21.0, 23.0, 16.0, 7.0, -9.0, + -27.0, -11.0, 4.0, 13.0, 14.0, 4.0, -5.0, -17.0, + -53.0, -34.0, -21.0, -11.0, -28.0, -14.0, -24.0, -43.0, +]; diff --git a/src/piece_values.rs b/src/piece_values.rs new file mode 100644 index 0000000..0be4257 --- /dev/null +++ b/src/piece_values.rs @@ -0,0 +1,22 @@ +use chess::Piece; + +pub const PAWN: f64 = 1.0; + +pub const HORSEY: f64 = 3.0; + +pub const BISHOP: f64 = 5.0; + +pub const ROOK: f64 = 5.5; + +pub const QUEEN: f64 = 10.0; + +pub fn get_piece_value(piece: Option) -> f64 { + match piece { + Some(Piece::Pawn) => PAWN, + Some(Piece::Knight) => HORSEY, + Some(Piece::Bishop) => BISHOP, + Some(Piece::Rook) => ROOK, + Some(Piece::Queen) => QUEEN, + _ => 0.0, + } +}