use super::{Depth, Heuristic, Value};
use crate::game::{Bowl, Position};
use crate::strategy::Strategy;
use std::cmp::max;
pub struct AlphaBetaBuilder<H>
where
H: Heuristic + Sized,
{
search_depth: Depth,
heuristic: H,
}
impl<H> AlphaBetaBuilder<H>
where
H: Heuristic + Sized,
{
pub fn build(self) -> AlphaBeta<H> {
AlphaBeta {
search_depth: self.search_depth,
heuristic: self.heuristic,
}
}
pub fn limited_to(mut self, search_depth: Depth) -> Self {
self.search_depth = search_depth;
self
}
pub fn with_heuristic<H_>(self, heuristic: H_) -> AlphaBetaBuilder<H_>
where
H_: Heuristic + Sized,
{
AlphaBetaBuilder {
search_depth: self.search_depth,
heuristic,
}
}
}
pub struct AlphaBeta<H>
where
H: Heuristic + Sized,
{
search_depth: Depth,
heuristic: H,
}
impl AlphaBeta<Delta> {
pub fn strategy() -> AlphaBetaBuilder<Delta> {
AlphaBetaBuilder {
search_depth: Depth::Infinite,
heuristic: delta(),
}
}
}
impl<H> Strategy for AlphaBeta<H>
where
H: Heuristic + Sized,
{
fn play(&mut self, position: &Position) -> Option<Bowl> {
let (bowl, _) = alpha_beta(
position,
Value::NegativeInfinity,
Value::PositiveInfinity,
&self.search_depth,
&self.heuristic,
);
bowl
}
}
fn alpha_beta(
position: &Position,
alpha_prime: Value,
beta: Value,
search_depth: &Depth,
heuristic: &dyn Heuristic,
) -> (Option<Bowl>, Value) {
let mut alpha = alpha_prime;
if position.finished() || search_depth.is_zero() {
if position.finished() {
(
None,
Value::Actual(position.score().expect("finished game to have a score")),
)
} else {
(None, heuristic.evaluate(position))
}
} else {
let mut best_bowl = None;
let mut best_value = Value::NegativeInfinity;
for bowl in position.options() {
let candidate_position = position.play(bowl).expect("option to be playable");
let value;
if candidate_position.turn() == position.turn() {
let tuple = alpha_beta(
&candidate_position,
alpha,
beta,
&search_depth.decrement(),
heuristic,
);
value = tuple.1;
} else {
let tuple = alpha_beta(
&candidate_position,
beta.opposite(),
alpha.opposite(),
&search_depth.decrement(),
heuristic,
);
value = tuple.1.opposite()
}
if value > best_value {
best_bowl = Some(bowl);
best_value = value;
}
alpha = max(alpha, value);
if alpha >= beta {
break;
}
}
(best_bowl, best_value)
}
}
pub struct Delta {}
pub fn delta() -> Delta {
Delta {}
}
impl Heuristic for Delta {
fn evaluate(&self, position: &Position) -> Value {
Value::Actual(position.delta())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::game::Position;
#[test]
fn finished_games_are_scored() {
let position = Position::from((5, 0, [0, 0, 2, 2]));
let heuristic = Delta {};
let (bowl, value) = alpha_beta(
&position,
Value::NegativeInfinity,
Value::PositiveInfinity,
&Depth::Infinite,
&heuristic,
);
assert_eq!(value, Value::Actual(1));
assert_eq!(bowl, None);
}
#[test]
fn only_bowl_is_selected() {
let position = Position::from([1, 0, 1, 0]);
let heuristic = Delta {};
let result = alpha_beta(
&position,
Value::NegativeInfinity,
Value::PositiveInfinity,
&Depth::Infinite,
&heuristic,
);
assert_eq!(result, (Some(0), Value::Actual(2)));
}
#[test]
fn best_bowl_is_selected() {
let position = Position::from([1, 2, 1, 0, 2, 1]);
let heuristic = Delta {};
let (_, value) = alpha_beta(
&position,
Value::NegativeInfinity,
Value::PositiveInfinity,
&Depth::Infinite,
&heuristic,
);
assert_eq!(value, Value::Actual(5));
}
}