use rayon::prelude::*;
pub trait AlphaBeta<T: Clone + Sync + Send>: Clone + Sync + Send {
fn evaluate(&self) -> Grade;
fn run(&self, depth: usize) -> Option<T> {
self.get_moves()
.into_par_iter()
.map(|t| {
let mut clone = self.clone();
clone.set(&t);
let grade = if depth > 0 {
clone.alpha_beta(Grade::Min, Grade::Max, depth - 1, false)
} else {
clone.evaluate()
};
(grade, t)
})
.max_by(|(g1, _), (g2, _)| g1.cmp(g2))
.map(|(_, t)| t)
}
fn alpha_beta(
&mut self,
mut alpha: Grade,
mut beta: Grade,
depth: usize,
is_max: bool,
) -> Grade {
let moves = self.get_moves();
if depth == 0 || moves.is_empty() {
return self.evaluate();
}
if is_max {
let mut max = Grade::Min;
for m in moves {
self.set(&m);
let eval = self.alpha_beta(alpha, beta, depth - 1, false);
self.unset(&m);
max = eval.max(max);
alpha = alpha.max(eval);
if beta <= alpha {
break;
}
}
max
} else {
let mut min: Grade = Grade::Max;
for m in moves {
self.set(&m);
let eval = self.alpha_beta(alpha, beta, depth - 1, true);
self.unset(&m);
min = eval.min(min);
beta = beta.min(eval);
if alpha >= beta {
break;
}
}
min
}
}
fn get_moves(&self) -> Vec<T>;
fn set(&mut self, m: &T);
fn unset(&mut self, m: &T);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, Hash, PartialOrd)]
pub enum Grade {
Min,
Score(i64),
Max,
}
impl From<&Grade> for i64 {
fn from(value: &Grade) -> Self {
match *value {
Grade::Max => Self::MAX,
Grade::Min => Self::MIN,
Grade::Score(n) => n,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
impl AlphaBeta<i64> for Vec<i64> {
fn evaluate(&self) -> Grade {
let score = self.iter().sum();
Grade::Score(score)
}
fn set(&mut self, m: &i64) {
self.push(*m);
}
fn unset(&mut self, m: &i64) {
let i = self.iter().position(|x| x == m).unwrap();
self.remove(i);
}
fn get_moves(&self) -> Vec<i64> {
vec![-111, -34, -4, 53, 0, 94, -6, 957]
}
}
#[test]
fn test_func() {
let v = vec![9, 8, 5, 7];
let num = v.run(5).unwrap();
assert_eq!(num, 957);
}
#[test]
fn test_vec_depth_zero_returns_some() {
let v = vec![0];
assert!(v.run(0).is_some());
}
#[test]
fn test_vec_depth_one_picks_best() {
let v = vec![0];
assert_eq!(v.run(1).unwrap(), 957);
}
#[test]
fn test_vec_depth_two_also_picks_best() {
let v = vec![0];
assert_eq!(v.run(2).unwrap(), 957);
}
#[derive(Clone, Debug)]
struct ConfigurableGame {
moves: Vec<i64>,
current: Vec<i64>,
}
impl ConfigurableGame {
fn with_moves(moves: Vec<i64>) -> Self {
ConfigurableGame { moves, current: vec![] }
}
}
impl AlphaBeta<i64> for ConfigurableGame {
fn evaluate(&self) -> Grade {
Grade::Score(self.current.iter().sum())
}
fn get_moves(&self) -> Vec<i64> {
self.moves.clone()
}
fn set(&mut self, m: &i64) {
self.current.push(*m);
}
fn unset(&mut self, _m: &i64) {
self.current.pop();
}
}
#[test]
fn test_empty_moves_returns_none() {
let g = ConfigurableGame::with_moves(vec![]);
assert_eq!(g.run(5), None);
}
#[test]
fn test_single_move() {
let g = ConfigurableGame::with_moves(vec![42]);
assert_eq!(g.run(5).unwrap(), 42);
}
#[test]
fn test_max_picks_highest_negative() {
let g = ConfigurableGame::with_moves(vec![-10, -5, -1]);
assert_eq!(g.run(1).unwrap(), -1);
}
#[test]
fn test_mixed_moves_depth_one() {
let g = ConfigurableGame::with_moves(vec![-100, 0, 50]);
assert_eq!(g.run(1).unwrap(), 50);
}
#[derive(Clone, Debug)]
struct TreeGame {
path: Vec<u8>,
leaf_values: Vec<i64>,
max_depth: usize,
}
impl AlphaBeta<u8> for TreeGame {
fn evaluate(&self) -> Grade {
if self.path.len() == self.max_depth {
let mut idx = 0usize;
for (i, &dir) in self.path.iter().enumerate() {
if dir == 1 {
idx |= 1 << (self.max_depth - 1 - i);
}
}
Grade::Score(self.leaf_values[idx])
} else {
Grade::Score(0)
}
}
fn get_moves(&self) -> Vec<u8> {
if self.path.len() < self.max_depth {
vec![0, 1]
} else {
vec![]
}
}
fn set(&mut self, m: &u8) {
self.path.push(*m);
}
fn unset(&mut self, _m: &u8) {
self.path.pop();
}
}
#[test]
fn test_tree_left_branch_better() {
let tree = TreeGame {
path: vec![],
leaf_values: vec![3, 8, 1, 6],
max_depth: 2,
};
assert_eq!(tree.run(2).unwrap(), 0);
}
#[test]
fn test_tree_right_branch_better() {
let tree = TreeGame {
path: vec![],
leaf_values: vec![1, 4, 7, 2],
max_depth: 2,
};
assert_eq!(tree.run(2).unwrap(), 1);
}
#[test]
fn test_tree_empty_moves() {
let tree = TreeGame {
path: vec![],
leaf_values: vec![],
max_depth: 0,
};
assert_eq!(tree.run(1), None);
}
#[test]
fn test_tree_depth_one_returns_some() {
let tree = TreeGame {
path: vec![],
leaf_values: vec![3, 8, 1, 6],
max_depth: 2,
};
assert!(tree.run(1).is_some());
}
#[test]
fn test_tree_deterministic() {
let tree = TreeGame {
path: vec![],
leaf_values: vec![3, 8, 1, 6],
max_depth: 2,
};
assert_eq!(tree.run(2), tree.run(2));
}
}