From a81bcb46892a3b93a5d8b86eedd8d10f3be60cc8 Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Thu, 23 Mar 2023 19:42:10 -0400 Subject: [PATCH] deez nuts --- Cargo.lock | 137 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/config.rs | 2 +- src/eval.rs | 149 +++++++++++++++++++------------------------------- src/main.rs | 76 +++++-------------------- 5 files changed, 210 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1178b4e..9506fa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,49 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -241,8 +284,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -251,6 +296,15 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -281,10 +335,23 @@ version = "0.1.0" dependencies = [ "chess", "itertools", + "minimax", "rand 0.8.5", "vampirc-uci", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -333,6 +400,28 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimax" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e180da8406d20280b2b52d84d963cc3b89d67c36003ed09c4be7e99f0ba08009" +dependencies = [ + "getrandom", + "instant", + "num_cpus", + "rand 0.8.5", + "rayon", +] + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -367,6 +456,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.30.3" @@ -526,12 +625,40 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "scratch" version = "1.0.3" @@ -726,6 +853,16 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 52a9efa..533bbbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,6 @@ codegen-units = 1 [dependencies] chess = "3.2.0" itertools = "0.10.5" +minimax = "0.5.1" rand = "0.8.5" vampirc-uci = { version = "0.11.1", features = ["chess"] } diff --git a/src/config.rs b/src/config.rs index 3318243..4db824b 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 = 7; diff --git a/src/eval.rs b/src/eval.rs index 9a7c997..e7be6ff 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,6 +1,9 @@ use std::f64::{INFINITY, NEG_INFINITY}; -use chess::{Board, BoardStatus, ChessMove, Color, File, MoveGen, Piece, Rank, Square}; +use chess::{ + Board as ChessBoard, BoardStatus, ChessMove, Color, File, MoveGen, Piece, Rank, Square, +}; +use itertools::Itertools; use crate::piece_tables; use crate::piece_values::get_piece_value; @@ -53,7 +56,7 @@ fn square_to_index(square: Square, playing_as: Color) -> usize { idx } -fn evaluate_piece_square(board: Board, square: Square, playing_as: Color) -> (f64, f64, f64) { +fn evaluate_piece_square(board: ChessBoard, 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; @@ -106,21 +109,7 @@ fn evaluate_piece_square(board: Board, square: Square, playing_as: Color) -> (f6 (mg_value, eg_value, gamephase) } -fn static_board_eval( - mov: ChessMove, - old_board: Board, - board: Board, - playing_as: Color, - move_history: Vec<(ChessMove, Board)>, -) -> f64 { - if move_history.len() > 6 { - if move_history[move_history.len() - 1].0 == mov - && move_history[move_history.len() - 2].0 == mov - { - return -666.0; - } - } - +fn static_board_eval(board: ChessBoard, playing_as: Color, move_history: Vec) -> f64 { // basic material counting + midgame or endgame determination let mut our_mg_value: f64 = 0.0; let mut our_eg_value: f64 = 0.0; @@ -167,14 +156,14 @@ fn static_board_eval( .filter(|sq| board.color_on(*sq).unwrap() == !playing_as) .for_each(|_| { if !is_opponent_in_check && move_history.len() > 10 { - current_eval += 17.5; + current_eval += 5.0; is_opponent_in_check = true; } else { current_eval += 3.5; } }); - // deprioritize king movement + /* // deprioritize king movement if board.piece_on(mov.get_source()).unwrap_or(Piece::Pawn) == Piece::King && !are_we_in_check { current_eval -= 12.5; } @@ -187,87 +176,63 @@ fn static_board_eval( ) { 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)>, - original_playing_as: Color, - mut alpha: f64, - mut beta: f64, -) -> f64 { - 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(), - last_board, - board, - !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 { - let mut new_move_history = move_history.clone(); +#[derive(Clone)] +pub struct Board { + pub current_board: ChessBoard, + pub board_history: Vec, +} - let new_board = board.make_move_new(mov); - new_move_history.push((mov, new_board)); +pub struct Game; - let eval = eval_board( - new_board, - board, - false, - depth - 1, - new_move_history, - original_playing_as, - alpha, - beta, - ); - max_eval = max_eval.max(eval); - alpha = alpha.max(eval); - if beta <= alpha { - break; - } - } - return max_eval; - } else { - let mut min_eval = INFINITY; - for mov in moves_iter { - let mut new_move_history = move_history.clone(); +impl minimax::Game for Game { + type S = Board; + type M = ChessMove; - let new_board = board.make_move_new(mov); - new_move_history.push((mov, new_board)); + fn generate_moves(state: &Self::S, moves: &mut Vec) { + let iter = MoveGen::new_legal(&state.current_board); + for mov in iter { + moves.push(mov) + } + } - let eval = eval_board( - new_board, - board, - true, - depth - 1, - new_move_history, - original_playing_as, - alpha, - beta, - ); - min_eval = min_eval.min(eval); - beta = beta.min(eval); - if beta <= alpha { - break; - } - } - return min_eval; + fn get_winner(b: &Board) -> Option { + match b.current_board.status() { + BoardStatus::Ongoing => None, + BoardStatus::Stalemate => Some(minimax::Winner::Draw), + BoardStatus::Checkmate => Some(minimax::Winner::PlayerJustMoved), } } + + fn apply(b: &mut Board, m: ChessMove) -> Option { + let mut new_board_history = b.board_history.clone(); + new_board_history.push(b.current_board); + Some(Board { + current_board: b.current_board.make_move_new(m), + board_history: new_board_history, + }) + } + + fn zobrist_hash(b: &Board) -> u64 { + b.current_board.get_hash() + } +} + +#[derive(Default)] +pub struct Evaluator; + +impl minimax::Evaluator for Evaluator { + type G = Game; + + fn evaluate(&self, b: &Board) -> minimax::Evaluation { + static_board_eval( + b.current_board, + b.current_board.side_to_move(), + b.board_history.clone(), + ) as i16 + } } diff --git a/src/main.rs b/src/main.rs index 0e3eb99..ca7655e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,9 @@ mod piece_values; use chess::{Board, ChessMove, MoveGen}; use config::INFO; -use eval::eval_board; +use eval::{Board as EvalBoard, Evaluator, Game}; use itertools::Itertools; +use minimax::Strategy; use std::collections::HashMap; use std::f64::{INFINITY, NEG_INFINITY}; @@ -20,7 +21,7 @@ use crate::config::DEPTH; fn main() { let mut board: Option = None; - let mut move_history: Vec<(ChessMove, Board)> = vec![]; + let mut move_history: Vec = vec![]; for line in io::stdin().lock().lines() { let msg: UciMessage = parse_one(&line.unwrap()); @@ -96,67 +97,18 @@ fn main() { } if let Some(board) = board { - let color = board.side_to_move(); - let moves = MoveGen::new_legal(&board); - let iter = moves.map(|mov| (mov, board.make_move_new(mov))); + let opts = minimax::IterativeOptions::new().verbose(); + let mut strat: minimax::IterativeSearch = + minimax::IterativeSearch::new(Evaluator::default(), opts); + strat.set_max_depth(DEPTH); + let mov = strat + .choose_move(&EvalBoard { + current_board: board, + board_history: move_history.clone(), + }) + .unwrap(); - let mut moves_hash_map = HashMap::new(); - - let mut sorted_moves = iter.sorted_by(|a, b| { - if !moves_hash_map.contains_key(a) { - moves_hash_map.insert( - *a, - eval_board( - a.1, - board, - true, - DEPTH, - move_history.clone(), - color, - NEG_INFINITY, - INFINITY, - ), - ); - } - - if !moves_hash_map.contains_key(b) { - moves_hash_map.insert( - *b, - eval_board( - b.1, - board, - true, - DEPTH, - move_history.clone(), - color, - NEG_INFINITY, - INFINITY, - ), - ); - } - - let a_eval = moves_hash_map.get(a).unwrap(); - let b_eval = moves_hash_map.get(b).unwrap(); - b_eval.total_cmp(a_eval) - }); - - let best_move = sorted_moves.next().unwrap(); - let worst_move = sorted_moves.last().unwrap_or(best_move); - - move_history.push(best_move); - - eprintln!( - "best move: {}, {}", - best_move.0, - moves_hash_map.get(&best_move).unwrap_or(&0.0) - ); - eprintln!( - "worst move: {}, {}", - worst_move.0, - moves_hash_map.get(&worst_move).unwrap_or(&0.0) - ); - - println!("bestmove {}", best_move.0); + println!("bestmove {}", mov); } } UciMessage::Stop => {