Struct Simulator
pub struct Simulator<G>where
G: Game,{ /* private fields */ }Expand description
Coordinates one match: the rules (G), its current position, which agent
controls each seat, and a running log of committed actions.
Pure in-memory bookkeeping: Simulator::step and
Simulator::select_human_action are the only ways the position changes,
and both just forward to Game::apply. There is no terminal, socket, or
timer here, so a simulator runs identically under cargo test and behind
a rendering client.
A chance node (PlayerId::CHANCE active) is not a seat any agent
controls; Simulator::step resolves it automatically by sampling from
chance, a generator seeded from the match seed but offset so it does not
share a stream with the state’s own generator.
Implementations§
§impl<G> Simulator<G>where
G: Game,
impl<G> Simulator<G>where
G: Game,
pub fn new(
game: G,
seed: u64,
agents: HashMap<PlayerId, PlayerAgent<G>>,
) -> Simulator<G>
pub fn new( game: G, seed: u64, agents: HashMap<PlayerId, PlayerAgent<G>>, ) -> Simulator<G>
Starts a match: game.new_initial_state(seed) seeded with seed, with
agents controlling each seat.
pub fn log_history(&self) -> &[String]
pub fn log_history(&self) -> &[String]
Returns every action committed so far, oldest first, formatted for display (a log monitor, a test assertion) rather than for replay.
pub fn awaiting_human(&self) -> Option<PlayerId>
pub fn awaiting_human(&self) -> Option<PlayerId>
Returns the seat a human is expected to act for right now, if any.
The first (lowest seat index) active PlayerAgent::Human seat, since
turnbase::ActivePlayers iterates in ascending order; simultaneous
phases with more than one human seat resolve one at a time, same as
Simulator::step resolves AI seats one at a time.
pub fn is_terminal(&self) -> bool
pub fn is_terminal(&self) -> bool
Returns whether the match has ended.
pub fn primary_human(&self) -> Option<PlayerId>
pub fn primary_human(&self) -> Option<PlayerId>
Returns the lowest-indexed seat controlled by PlayerAgent::Human,
if any.
Iterates a HashMap, so this picks by seat index rather than
insertion order, keeping the result deterministic regardless of hash
order. turnbase-simulator’s dashboard uses this once, at
construction, to decide whose Game::View it renders from for the
whole match: a human should never see another seat’s hidden
information (their cards, say) just because it happened to be an AI’s
turn when the frame was drawn.
§impl<G> Simulator<G>
impl<G> Simulator<G>
pub fn step(&mut self) -> Result<bool, Error>
pub fn step(&mut self) -> Result<bool, Error>
Advances the match by one atomic decision.
Returns Ok(true) if an AI seat or a chance node advanced, Ok(false)
if the match is already over or the active seat is waiting on
Simulator::select_human_action, or Err if the bot chose an action
Game::is_legal rejects.
A chance node (PlayerId::CHANCE active) is resolved here by sampling
a committed outcome, so a driver loop advances past deck deals and dice
without any per-game wiring.
§Errors
Returns Error::IllegalAction if the active bot’s chosen action is
not legal for it in the current state.
pub fn select_human_action(
&mut self,
player: PlayerId,
action: <G as Game>::Action,
) -> Result<(), Error>
pub fn select_human_action( &mut self, player: PlayerId, action: <G as Game>::Action, ) -> Result<(), Error>
Externally forces player’s action into the state machine.
For a PlayerAgent::Human seat’s UI (or a test) to commit a choice
once Simulator::awaiting_human names that seat.
§Errors
Returns Error::NotActive if player is not currently owed a
decision, or Error::IllegalAction if action is not legal for
them.