1use crate::{Game, GameError, ScoreDelta, Strategy, TurnAction};
4use rand::Rng;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
11#[non_exhaustive]
12pub enum EngineError {
13 #[error("illegal action from seat {seat}")]
15 IllegalAction {
16 seat: usize,
18 #[source]
20 source: GameError,
21 },
22}
23
24pub fn play_turn<R: Rng + ?Sized>(
31 game: &mut Game,
32 strategy: &mut dyn Strategy,
33 rng: &mut R,
34) -> Result<ScoreDelta, EngineError> {
35 let seat = game.seat();
36 let illegal = |source| EngineError::IllegalAction { seat, source };
37 loop {
38 match game.phase() {
39 crate::Phase::Finished => return Err(illegal(GameError::Finished)),
40 crate::Phase::AwaitingRoll => {
41 game.roll_with(rng).map_err(illegal)?;
42 }
43 crate::Phase::AwaitingAction => {
44 let action = if game.rolls_left() > 0 {
45 strategy.choose_action(&game.view())
46 } else {
47 TurnAction::Score(strategy.choose_category(&game.view()))
48 };
49 if let Some(delta) = game.act(action).map_err(illegal)? {
50 return Ok(delta);
51 }
52 }
53 }
54 }
55}
56
57pub fn play_game<R: Rng + ?Sized>(
69 game: &mut Game,
70 strategies: &mut [&mut dyn Strategy],
71 rng: &mut R,
72) -> Result<Vec<u16>, EngineError> {
73 assert_eq!(
74 strategies.len(),
75 game.scorecards().len(),
76 "one strategy per player"
77 );
78 while !game.is_over() {
79 play_turn(game, strategies[game.seat()], rng)?;
80 }
81 Ok(game.totals())
82}