diff --git a/src/config.rs b/src/config.rs index 81f7de9..12566e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,9 +15,9 @@ impl Info<'_> { pub const INFO: Info = Info { name: "inkwell", - version: "0.0.3-rc", + version: "0.0.666-rc", author: "Bendy (real)", }; // FIXME: this needs to be configurable via UCI -pub const depth: u8 = 3; +pub const depth: u8 = 6; diff --git a/src/eval.rs b/src/eval.rs index 10d168f..25abf67 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,98 +1,156 @@ +use rand::prelude::*; +use std::f64::{INFINITY, NEG_INFINITY}; + use chess::{Board, BoardStatus, ChessMove, Color, MoveGen, Piece}; -pub fn eval_board( +fn static_board_eval( + mov: ChessMove, board: Board, playing_as: Color, - curr_depth: u8, move_history: Vec<(ChessMove, Board)>, -) -> i128 { - let mut current_eval: i128 = 0; +) -> 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; + } + } - let moves_iter = MoveGen::new_legal(&board).into_iter(); + let mut current_eval = 0.0; - moves_iter.for_each(|mov| { - let tmp_board = board.make_move_new(mov); + // 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 */ } + } + }); - // force it to not draw (and cockblock someone trying *to* draw) - if move_history.len() > 6 { - if move_history[move_history.len() - 2].0 == mov - && move_history[move_history.len() - 6].0 == mov - { - current_eval -= 6666666666666666666666666666; - return; - } + 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 */ } } + }); - // basic material counting - tmp_board.color_combined(playing_as).for_each(|square| { - match tmp_board.piece_on(square) { - Some(Piece::Pawn) => current_eval += 1, - Some(Piece::Knight) => current_eval += 3, - Some(Piece::Bishop) => current_eval += 5, - Some(Piece::Rook) => current_eval += 5, - Some(Piece::Queen) => current_eval += 8, - _ => { /* do nothing */ } + // checking heuristic + let mut are_we_in_check = false; + let mut is_opponent_in_check = false; + board + .checkers() + .filter(|sq| board.color_on(*sq).unwrap() == playing_as) + .for_each(|_| { + if !are_we_in_check { + current_eval -= 66.6; + are_we_in_check = true; + } else { + current_eval -= 12.5; } }); - - tmp_board.color_combined(!playing_as).for_each(|square| { - match tmp_board.piece_on(square) { - Some(Piece::Pawn) => current_eval -= 1, - Some(Piece::Knight) => current_eval -= 3, - Some(Piece::Bishop) => current_eval -= 5, - Some(Piece::Rook) => current_eval -= 5, - Some(Piece::Queen) => current_eval -= 8, - _ => { /* do nothing */ } + board + .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 + { + current_eval += 17.5; + is_opponent_in_check = true; + } else { + current_eval += 3.5; } }); - // checking heuristic - let mut are_we_in_check = false; - let mut is_opponent_in_check = false; - tmp_board - .checkers() - .filter(|sq| tmp_board.color_on(*sq).unwrap() == playing_as) - .for_each(|_| { - if !are_we_in_check { - current_eval -= 666; - are_we_in_check = true; - } else { - current_eval -= 15; - } - }); - tmp_board - .checkers() - .filter(|sq| tmp_board.color_on(*sq).unwrap() == !playing_as) - .for_each(|_| { - if !is_opponent_in_check { - current_eval += 15; - is_opponent_in_check = true; - } else { - current_eval += 5; - } - }); + // deprioritize king movement + if board.piece_on(mov.get_source()).unwrap_or(Piece::Pawn) == Piece::King && !are_we_in_check { + current_eval -= 12.5; + } - // die - if tmp_board.status() == BoardStatus::Checkmate && tmp_board.side_to_move() == playing_as { - current_eval -= 666_666; - } + current_eval +} - // go for checkmate - if tmp_board.status() == BoardStatus::Checkmate && tmp_board.side_to_move() == !playing_as { - current_eval += 666_666; - } +pub fn eval_board( + 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(); - // deprioritize king movement - if board.piece_on(mov.get_source()).unwrap() == Piece::King && !are_we_in_check { - current_eval -= 10; - } + if depth == 0 || board.status() == BoardStatus::Checkmate { + return static_board_eval( + last_move.unwrap_or(move_history.last().unwrap().0), + board, + original_playing_as, + move_history, + ); + } else { + if is_maximizing_player { + let mut max_eval = NEG_INFINITY; + for mov in moves_iter { + let mut new_move_history = move_history.clone(); - if curr_depth != 0 { - let mut new_move_history = move_history.clone(); - new_move_history.push((mov, tmp_board)); - current_eval += eval_board(tmp_board, playing_as, curr_depth - 1, new_move_history); - } - }); + let new_board = board.make_move_new(mov); + new_move_history.push((mov, new_board)); - return current_eval; + let eval = eval_board( + new_board, + false, + depth - 1, + new_move_history, + Some(mov), + 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(); + + let new_board = board.make_move_new(mov); + new_move_history.push((mov, new_board)); + + let eval = eval_board( + new_board, + true, + depth - 1, + new_move_history, + Some(mov), + original_playing_as, + alpha, + beta, + ); + min_eval = min_eval.min(eval); + beta = beta.min(eval); + if beta <= alpha { + break; + } + } + return min_eval; + } + } } diff --git a/src/main.rs b/src/main.rs index a35cde9..10d1022 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use eval::eval_board; use itertools::Itertools; use std::collections::HashMap; +use std::f64::{INFINITY, NEG_INFINITY}; use std::io::{self, BufRead}; use std::process; use std::str::FromStr; @@ -101,18 +102,44 @@ fn main() { 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, color, depth, move_history.clone())); + moves_hash_map.insert( + *a, + eval_board( + a.1, + true, + depth, + move_history.clone(), + if move_history.len() > 0 { + Some(move_history.last().unwrap().0) + } else { + None + }, + color, + NEG_INFINITY, + INFINITY, + ), + ); } if !moves_hash_map.contains_key(b) { - moves_hash_map - .insert(*b, eval_board(b.1, color, depth, move_history.clone())); + moves_hash_map.insert( + *b, + eval_board( + b.1, + true, + depth, + move_history.clone(), + None, + color, + NEG_INFINITY, + INFINITY, + ), + ); } let a_eval = moves_hash_map.get(a).unwrap(); let b_eval = moves_hash_map.get(b).unwrap(); - Ord::cmp(&b_eval, &a_eval) + b_eval.total_cmp(a_eval) }); let best_move = sorted_moves.next().unwrap(); @@ -123,12 +150,12 @@ fn main() { eprintln!( "best move: {}, {}", best_move.0, - moves_hash_map.get(&best_move).unwrap_or(&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) + moves_hash_map.get(&worst_move).unwrap_or(&0.0) ); println!("bestmove {}", best_move.0);