use crate::{Game, GameError, ScoreDelta, Strategy, TurnAction};
use rand::Rng;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum EngineError {
#[error("illegal action from seat {seat}")]
IllegalAction {
seat: usize,
#[source]
source: GameError,
},
}
pub fn play_turn<R: Rng + ?Sized>(
game: &mut Game,
strategy: &mut dyn Strategy,
rng: &mut R,
) -> Result<ScoreDelta, EngineError> {
let seat = game.seat();
let illegal = |source| EngineError::IllegalAction { seat, source };
loop {
match game.phase() {
crate::Phase::Finished => return Err(illegal(GameError::Finished)),
crate::Phase::AwaitingRoll => {
game.roll_with(rng).map_err(illegal)?;
}
crate::Phase::AwaitingAction => {
let action = if game.rolls_left() > 0 {
strategy.choose_action(&game.view())
} else {
TurnAction::Score(strategy.choose_category(&game.view()))
};
if let Some(delta) = game.act(action).map_err(illegal)? {
return Ok(delta);
}
}
}
}
}
pub fn play_game<R: Rng + ?Sized>(
game: &mut Game,
strategies: &mut [&mut dyn Strategy],
rng: &mut R,
) -> Result<Vec<u16>, EngineError> {
assert_eq!(
strategies.len(),
game.scorecards().len(),
"one strategy per player"
);
while !game.is_over() {
play_turn(game, strategies[game.seat()], rng)?;
}
Ok(game.totals())
}