use crate::game::{Game, State};
use rand::Rng;
use rand_chacha::ChaCha20Rng;
use rand_core::{CryptoRngCore, SeedableRng};
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::marker::PhantomData;
pub trait Strategy<G: Game>: Debug {
type Config<'l>;
fn choose<'l>(
&mut self,
config: Self::Config<'l>,
state: &G::State,
player: &G::Player,
) -> Option<Option<G::Move>>;
}
#[derive(Default)]
pub struct FailureStrategy<G: Game> {
marker: PhantomData<G>,
}
impl<G: Game> Debug for FailureStrategy<G> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FailureStrategy").finish()
}
}
impl<G: Game> FailureStrategy<G> {
pub fn new() -> Self {
Self {
marker: PhantomData,
}
}
}
impl<G: Game> Strategy<G> for FailureStrategy<G> {
type Config<'l> = ();
fn choose<'l>(
&mut self,
_config: Self::Config<'l>,
_state: &G::State,
_player: &G::Player,
) -> Option<Option<G::Move>> {
None
}
}
pub struct ConstantStrategy<G: Game> {
map: HashMap<G::Player, Option<G::Move>>,
}
impl<G: Game> Debug for ConstantStrategy<G> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConstantStrategy")
.field("map", &self.map)
.finish()
}
}
impl<G: Game> ConstantStrategy<G> {
pub fn failure() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn new(player: G::Player, choice: Option<G::Move>) -> Self {
Self {
map: HashMap::from([(player, choice)]),
}
}
pub fn from(map: HashMap<G::Player, Option<G::Move>>) -> Self {
Self { map }
}
}
impl<G: Game> Strategy<G> for ConstantStrategy<G> {
type Config<'l> = ();
fn choose<'l>(
&mut self,
_config: Self::Config<'l>,
state: &G::State,
player: &G::Player,
) -> Option<Option<G::Move>> {
let choice = self.map.get(&player).cloned();
assert!(
{
let moves: Vec<G::Move> = state.moves(player).collect();
choice.is_none()
|| matches!(choice.clone(), Some(None) if moves.is_empty())
|| matches!(choice.clone(), Some(Some(m)) if moves.iter().find(|n|
**n == m).is_some())
},
"ConstantStrategy was constructed with an invalid move choice, {choice:?}, for player \
{player:?}."
);
choice
}
}
pub struct TryStrategy<G: Game, S1: Strategy<G>, S2: Strategy<G>> {
initial: S1,
fallback: S2,
marker: PhantomData<G>,
}
impl<G: Game, S1: Strategy<G>, S2: Strategy<G>> Debug for TryStrategy<G, S1, S2> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TryStrategy")
.field("initial", &self.initial)
.field("fallback", &self.fallback)
.finish()
}
}
impl<G: Game, S1: Strategy<G>, S2: Strategy<G>> TryStrategy<G, S1, S2> {
pub fn new(initial: S1, fallback: S2) -> Self {
Self {
initial,
fallback,
marker: PhantomData,
}
}
}
impl<G: Game, S1: Strategy<G>, S2: Strategy<G>> Strategy<G> for TryStrategy<G, S1, S2> {
type Config<'l> = (S1::Config<'l>, S2::Config<'l>);
fn choose<'l>(
&mut self,
config: Self::Config<'l>,
state: &G::State,
player: &G::Player,
) -> Option<Option<G::Move>> {
self.initial
.choose(config.0, state, player)
.or_else(|| self.fallback.choose(config.1, state, player))
}
}
#[derive(Default)]
pub struct FirstMoveStrategy<G: Game> {
marker: PhantomData<G>,
}
impl<G: Game> FirstMoveStrategy<G> {
pub fn new() -> Self {
Self { marker: PhantomData }
}
}
impl<G: Game> Debug for FirstMoveStrategy<G> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FirstMoveStrategy").finish()
}
}
impl<G: Game> Strategy<G> for FirstMoveStrategy<G> {
type Config<'l> = ();
fn choose<'l>(
&mut self,
_config: Self::Config<'l>,
state: &G::State,
player: &G::Player,
) -> Option<Option<G::Move>> {
Some(state.moves(player).next())
}
}
pub struct RandomStrategy<G: Game> {
seed: u64,
marker: PhantomData<G>,
}
impl<G: Game> Debug for RandomStrategy<G> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RandomStrategy")
.field("seed", &self.seed)
.finish()
}
}
impl<G: Game> RandomStrategy<G> {
pub fn new(seed: u64) -> Self {
RandomStrategy {
seed,
marker: PhantomData,
}
}
pub fn seed(&self) -> u64 {
self.seed
}
}
impl<G: Game> Default for RandomStrategy<G> {
fn default() -> Self {
let mut rng = rand::rng();
RandomStrategy::new(rng.random::<u64>())
}
}
impl<G: Game> Strategy<G> for RandomStrategy<G> {
type Config<'l> = ();
fn choose<'l>(
&mut self,
_config: Self::Config<'l>,
state: &G::State,
player: &G::Player,
) -> Option<Option<G::Move>> {
let mut hasher = DefaultHasher::new();
state.hash(&mut hasher);
let mut rng = ChaCha20Rng::seed_from_u64(self.seed + hasher.finish());
let moves: Vec<G::Move> = state.moves(player).collect();
if moves.is_empty() {
return Some(None);
}
let index = (rng.as_rngcore().next_u64() as usize) % moves.len();
Some(moves.get(index).cloned())
}
}