use crate::{MoveSorter, OpeningBook, Position, TTFlag, TranspositionTable};
use std::path::Path;
const OPENING_BOOK_BYTES: &[u8] = include_bytes!("books/default-book.bin");
#[derive(Debug)]
pub struct Solver {
pub explored_positions: usize,
pub transposition_table: TranspositionTable,
pub opening_book: Option<OpeningBook>,
}
impl Solver {
pub const COLUMNS: [usize; Position::WIDTH] = const {
let mut columns = [0; Position::WIDTH];
let mut i = 0;
while i < Position::WIDTH {
columns[i] = (Position::WIDTH as i32 / 2 + (1 - 2 * (i as i32 % 2)) * (i as i32 + 1) / 2) as usize;
i += 1;
}
columns
};
pub fn new() -> Solver {
Self::default()
}
pub fn empty() -> Solver {
Solver {
explored_positions: 0,
transposition_table: TranspositionTable::new(),
opening_book: None
}
}
pub fn load_opening_book(&mut self, path: &Path) -> bool {
self.opening_book = OpeningBook::load(path).ok();
self.opening_book.is_some()
}
pub fn reset(&mut self) {
self.explored_positions = 0;
self.transposition_table.reset();
}
pub fn solve(&mut self, position: &Position) -> i8 {
self.explored_positions = 0;
if let Some(score) = self.opening_book.as_ref().and_then(|book| book.get(position)) {
return score;
}
if position.can_win_next() {
return (Position::BOARD_SIZE + 1 - position.get_moves()) as i8 / 2
}
let mut min = -((Position::BOARD_SIZE - position.get_moves()) as i8) / 2;
let mut max = (Position::BOARD_SIZE + 1 - position.get_moves()) as i8 / 2;
while min < max {
let mut mid = min + (max - min) / 2;
if mid <= 0 && min / 2 < mid {
mid = min / 2
} else if mid >= 0 && max / 2 > mid {
mid = max / 2
}
let score = self.negamax(position, (Position::BOARD_SIZE - position.get_moves()) as u8, mid, mid + 1);
if score <= mid {
max = score
} else {
min = score
}
}
min
}
pub fn get_all_move_scores(&mut self, position: &Position) -> [Option<i8>; Position::WIDTH] {
let mut scores = [None; Position::WIDTH];
let depth = (Position::BOARD_SIZE - position.get_moves()) as u8;
if position.is_won_position() || depth == 0 {
return scores;
}
for &column in Self::COLUMNS.iter() {
if !position.is_playable(column) {
continue;
}
if position.is_winning_move(column) {
scores[column] = Some((Position::BOARD_SIZE - position.get_moves() + 1) as i8 / 2);
} else {
let mut new_position = *position;
new_position.play(column);
scores[column] = Some(-self.solve(&new_position));
}
}
scores
}
fn negamax(&mut self, position: &Position, depth: u8, mut alpha: i8, mut beta: i8) -> i8 {
self.explored_positions += 1;
if depth == 0 {
return 0;
}
let original_alpha = alpha;
let key = position.get_key();
if let Some(entry) = self.transposition_table.get(key) {
if entry.depth >= depth {
match entry.flag {
TTFlag::Exact => return entry.value,
TTFlag::LowerBound if entry.value >= beta => return entry.value,
TTFlag::UpperBound if entry.value <= alpha => return entry.value,
_ => {} }
}
}
let possible_moves = position.possible_non_losing_moves();
if possible_moves == 0 {
return -((Position::BOARD_SIZE - position.get_moves()) as i8) / 2;
}
let min = -((Position::BOARD_SIZE - position.get_moves()) as i8 - 2) / 2;
if alpha < min {
if min >= beta { return min }
alpha = min;
}
let max = ((Position::BOARD_SIZE - position.get_moves()) as i8 - 1) / 2;
if beta > max {
if alpha >= max { return max }
beta = max;
}
let mut moves = MoveSorter::new();
for &column in Self::COLUMNS.iter().rev() {
let move_bit = possible_moves & Position::column_mask(column);
if move_bit > 0 {
moves.add(column, position.score_move(move_bit))
}
}
for column in moves {
let mut new_position = *position;
new_position.play(column);
let score = -self.negamax(&new_position, depth - 1, -beta, -alpha);
if score > alpha {
alpha = score;
}
if alpha >= beta {
break;
}
}
let flag = if alpha <= original_alpha {
TTFlag::UpperBound
} else if alpha >= beta {
TTFlag::LowerBound
} else {
TTFlag::Exact
};
self.transposition_table.put(key, alpha, flag, depth);
alpha
}
}
impl Default for Solver {
fn default() -> Solver {
Solver {
explored_positions: 0,
transposition_table: TranspositionTable::new(),
opening_book: OpeningBook::from_static_bytes(OPENING_BOOK_BYTES).ok()
}
}
}