summaryrefslogtreecommitdiff
path: root/ai/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ai/src/lib.rs')
-rw-r--r--ai/src/lib.rs174
1 files changed, 6 insertions, 168 deletions
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() }
-}