use std::cmp::Ordering;
use std::path::Path;
use rand::distr::weighted::WeightedIndex;
use rand::{rng};
use rand::distr::Distribution;
use crate::{Position, Solver};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Difficulty {
Easy,
Medium,
Hard,
Impossible,
}
impl Difficulty {
pub fn temperature(&self) -> f64 {
match self {
Difficulty::Easy => 0.25,
Difficulty::Medium => 0.1,
Difficulty::Hard => 0.025,
Difficulty::Impossible => 0.,
}
}
}
#[derive(Debug)]
pub struct AIPlayer {
solver: Solver,
difficulty: Difficulty,
}
impl AIPlayer {
pub fn new(difficulty: Difficulty) -> AIPlayer {
AIPlayer {
solver: Solver::new(),
difficulty,
}
}
pub fn load_opening_book(&mut self, path: &Path) -> bool {
self.solver.load_opening_book(path)
}
pub fn reset(&mut self) {
self.solver.reset();
}
pub fn solve(&mut self, position: &Position) -> i8 {
self.solver.solve(position)
}
pub fn get_all_move_scores(&mut self, position: &Position) -> [Option<i8>; Position::WIDTH] {
self.solver.get_all_move_scores(position)
}
pub fn get_move(&mut self, position: &Position) -> Option<usize> {
let move_scores = self.solver.get_all_move_scores(position);
self.select_move(position, &move_scores)
}
pub fn select_move(&self, position: &Position, scores: &[Option<i8>; Position::WIDTH]) -> Option<usize> {
let normalised_scores = Self::normalise_scores(position, scores);
let possible_moves: Vec<(usize, f64)> = normalised_scores
.iter()
.enumerate()
.filter_map(|(col_index, score_option)| score_option.map(|score| (col_index, score)))
.collect();
if possible_moves.is_empty() {
return None
}
let temperature = self.difficulty.temperature();
if temperature <= 0.0 {
return possible_moves
.iter()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
.map(|(index, _)| *index);
}
let weights: Vec<f64> = possible_moves
.iter()
.map(|(_, score)| (*score / temperature).exp())
.collect();
let dist = match WeightedIndex::new(&weights) {
Ok(weighted_index) => weighted_index,
Err(_) => return None,
};
let mut rng = rng();
let selected_index = dist.sample(&mut rng);
Some(possible_moves[selected_index].0)
}
fn normalise_scores(position: &Position, scores: &[Option<i8>; Position::WIDTH]) -> [Option<f64>; Position::WIDTH] {
let mut normalised_scores = [None; Position::WIDTH];
let max_possible_score = ((Position::BOARD_SIZE + 1 - position.get_moves()) as i8 / 2) as f64;
if max_possible_score <= 0.0 {
return normalised_scores;
}
for i in 0..Position::WIDTH {
if let Some(score) = scores[i] {
normalised_scores[i] = Some(score as f64 / max_possible_score);
}
}
normalised_scores
}
}