use std::fmt::{Debug, Display};
#[cfg(doc)]
use crate::game::State;
use crate::game::{Game, GameError};
#[cfg(doc)]
use crate::search;
use crate::search::{SearchChoose, SearchError};
#[derive(Debug, thiserror::Error)]
pub enum CompetitionError<const N: usize, G: Game<N>> {
#[error(transparent)]
GameError(G::Error),
#[error("{0}")]
SearchError(Box<dyn SearchError + Send + Sync>),
#[error("No actions are available to take")]
NoActions,
}
impl<const N: usize, G: Game<N, Error = E>, E: GameError> From<E> for CompetitionError<N, G> {
fn from(value: G::Error) -> Self {
Self::GameError(value)
}
}
pub trait CompetitionStrategy<const N: usize, G: Game<N>>: Debug {
fn take_turn(&self, game: &G, state: G::State) -> Result<G::State, CompetitionError<N, G>>;
}
impl<const N: usize, G: Game<N>, T: SearchChoose<N, G> + Debug> CompetitionStrategy<N, G> for T
where
<T as SearchChoose<N, G>>::Error: Send + Sync + 'static,
G::Action: Debug,
{
fn take_turn(&self, game: &G, state: G::State) -> Result<G::State, CompetitionError<N, G>> {
let action = self
.choose_action(game, &state)
.map_err(|err| CompetitionError::SearchError(Box::new(err)))?
.ok_or(CompetitionError::NoActions)?;
log::debug!("Strategy has determined action {:?}", action);
Ok(game.do_action(&action, state)?)
}
}
pub struct Competition<'a, const N: usize, G: Game<N>> {
game: &'a G,
strategies: [&'a dyn CompetitionStrategy<N, G>; N],
display_state: bool,
}
impl<'a, const N: usize, G: Game<N>> Competition<'a, N, G> {
pub fn new(
game: &'a G,
strategies: [&'a dyn CompetitionStrategy<N, G>; N],
display_state: bool,
) -> Self {
Self {
game,
strategies,
display_state,
}
}
pub fn play(&'a self, state: G::State) -> Result<G::State, CompetitionError<N, G>>
where
G::State: Display,
{
let mut state = state;
log::info!("Starting competition");
while !self.game.is_complete(&state)? {
if self.display_state {
println!("{}\n", state);
}
let current_player = self.game.active_player(&state)?;
let current_player_index = self.game.player_index(current_player)?;
log::trace!(
"Player {:?} has player index {}",
current_player,
current_player_index
);
let strategy = &self.strategies[current_player_index];
log::info!(
"Player {:?} taking turn with strategy {:?}",
current_player,
strategy
);
state = strategy.take_turn(self.game, state)?;
}
log::info!("Competition is complete because game is over");
if self.display_state {
println!("{}\n", state);
}
Ok(state)
}
}