From 1b379403ab971e188483df5d580c39695db7f44a Mon Sep 17 00:00:00 2001 From: Micha White Date: Wed, 27 Sep 2023 09:35:50 -0400 Subject: Big changes --- ai/src/lib.rs | 174 ++-------------------------------------------------------- 1 file changed, 6 insertions(+), 168 deletions(-) (limited to 'ai/src/lib.rs') diff --git a/ai/src/lib.rs b/ai/src/lib.rs index 8c49ba7..06f0818 100644 --- a/ai/src/lib.rs +++ b/ai/src/lib.rs @@ -1,171 +1,9 @@ -use crate::transposition_table::TranspositionTableReference; +#![feature(new_uninit)] +#![feature(get_mut_unchecked)] + +pub use eval::negamax; pub use model::{CheckersBitBoard, Move, PieceColor, PossibleMoves}; -use parking_lot::{Mutex, RwLock}; -use rayon::prelude::*; -use std::mem::MaybeUninit; +pub use transposition_table::{TranspositionTable, TranspositionTableRef}; +mod eval; mod transposition_table; - -const KING_WORTH: u32 = 2; - -fn eval_position(board: CheckersBitBoard) -> f32 { - let light_pieces = board.pieces_bits() & !board.color_bits(); - let dark_pieces = board.pieces_bits() & board.color_bits(); - - let light_peasants = light_pieces & !board.king_bits(); - let dark_peasants = dark_pieces & !board.king_bits(); - - let light_kings = light_pieces & board.king_bits(); - let dark_kings = dark_pieces & board.king_bits(); - - // if we assume the black player doesn't exist, how good is this for white? - let light_eval = - (light_peasants.count_ones() as f32) + ((light_kings.count_ones() * KING_WORTH) as f32); - let dark_eval = - (dark_peasants.count_ones() as f32) + ((dark_kings.count_ones() * KING_WORTH) as f32); - - // avoiding a divide by zero error - if dark_eval + light_eval != 0.0 { - dark_eval / (dark_eval + light_eval) - } else { - 0.5 - } -} - -pub fn eval_singlethreaded( - depth: usize, - mut alpha: f32, - beta: f32, - board: CheckersBitBoard, -) -> f32 { - if depth <= 1 { - if board.turn() == PieceColor::Dark { - eval_position(board) - } else { - 1.0 - eval_position(board) - } - } else { - let table = TranspositionTableReference::new(); - if let Some(eval) = table.get(board, depth as u8) { - return eval; - } - - let turn = board.turn(); - let mut best_eval = f32::NEG_INFINITY; - let moves = PossibleMoves::moves(board); - - if moves.is_empty() { - return 0.0; - } - - for current_move in PossibleMoves::moves(board) { - let board = unsafe { current_move.apply_to(board) }; - let current_eval = if board.turn() != turn { - 1.0 - eval_singlethreaded(depth - 1, 1.0 - beta, 1.0 - alpha, board) - } else { - eval_singlethreaded(depth - 1, alpha, beta, board) - }; - - let table = TranspositionTableReference::new(); - table.insert(board, current_eval, depth as u8); - - if current_eval >= beta { - return beta; - } - - if best_eval < current_eval { - best_eval = current_eval; - } - if alpha < best_eval { - alpha = best_eval; - } - } - - best_eval - } -} - -pub fn eval_multithreaded(depth: usize, alpha: f32, beta: f32, board: CheckersBitBoard) -> f32 { - if depth <= 1 { - if board.turn() == PieceColor::Dark { - eval_position(board) - } else { - 1.0 - eval_position(board) - } - } else { - let turn = board.turn(); - let best_eval = Mutex::new(f32::NEG_INFINITY); - let keep_going = RwLock::new(true); - let alpha = RwLock::new(alpha); - - let is_still_going = || *keep_going.read(); - let get_alpha = || *alpha.read(); - - let moves = PossibleMoves::moves(board); - - if moves.is_empty() { - let pos_eval = if board.turn() == PieceColor::Dark { - eval_position(board) - } else { - 1.0 - eval_position(board) - }; - return if pos_eval < f32::EPSILON || pos_eval > 1.0 - f32::EPSILON { - pos_eval - } else { - 0.5 - }; - } - - moves.into_iter().par_bridge().for_each(|current_move| { - if is_still_going() { - let board = unsafe { current_move.apply_to(board) }; - let current_eval = if board.turn() != turn { - 1.0 - eval_singlethreaded(depth - 1, 1.0 - beta, 1.0 - get_alpha(), board) - } else { - eval_singlethreaded(depth - 1, get_alpha(), beta, board) - }; - - let mut best = best_eval.lock(); - if current_eval >= beta { - *best = beta; - let mut going_val = keep_going.write(); - *going_val = false; - } - - if *best < current_eval { - *best = current_eval; - } - if get_alpha() < *best { - let mut alpha = alpha.write(); - *alpha = *best; - } - } - }); - - best_eval.into_inner() - } -} - -pub fn best_move(depth: usize, board: CheckersBitBoard) -> Move { - let mut best_eval = 0.0; - let mut best_move = MaybeUninit::uninit(); - let moves = PossibleMoves::moves(board); - - if moves.is_empty() { - panic!("No moves are available") - } - - for current_move in PossibleMoves::moves(board) { - let new_board = unsafe { current_move.apply_to(board) }; - let mut current_eval = eval_multithreaded(depth - 1, best_eval, 1.0, new_board); - if new_board.turn() != board.turn() { - current_eval = 1.0 - current_eval; - } - if current_eval > best_eval { - best_eval = current_eval; - best_move.write(current_move); - } - } - - unsafe { best_move.assume_init() } -} -- cgit v1.2.3