gin_rummy_engine/strategy.rs
1//! The [`Strategy`] trait: a decision procedure for one seat
2
3use crate::{DrawAction, Layoff, TurnAction, UpcardAction, View};
4
5/// A decision procedure for one seat of gin rummy
6///
7/// Every method receives a [`View`] restricted to the information the seat
8/// may legally see. Methods take `&mut self` so strategies can keep state —
9/// an internal random number generator, an opponent model — and the trait is
10/// object-safe, so the driver works with `&mut dyn Strategy`.
11///
12/// A strategy never applies its decisions itself; the [`Table`] driver
13/// validates and applies them, rejecting illegal choices as
14/// [`EngineError::IllegalAction`].
15///
16/// [`Table`]: crate::Table
17/// [`EngineError::IllegalAction`]: crate::EngineError::IllegalAction
18pub trait Strategy {
19 /// Take or pass the initial upcard
20 fn offer_upcard(&mut self, view: &View<'_>) -> UpcardAction;
21
22 /// Draw from the stock or the discard pile
23 ///
24 /// Not consulted on the forced stock draw after both players pass the
25 /// upcard — the driver draws from the stock directly, so implementations
26 /// may treat [`DrawAction::TakeDiscard`] as always available.
27 fn choose_draw(&mut self, view: &View<'_>) -> DrawAction;
28
29 /// Discard, knock, or declare big gin
30 ///
31 /// The drawn card is already in [`View::hand`], which holds 11 cards;
32 /// [`View::taken_discard`] is the card that may not be shed this turn.
33 fn play_turn(&mut self, view: &View<'_>) -> TurnAction;
34
35 /// Lay one card off onto the knocker's spread, or `None` to finish
36 ///
37 /// Called repeatedly with a refreshed view — the spread grows with every
38 /// accepted layoff — until the strategy returns `None`.
39 fn choose_layoff(&mut self, view: &View<'_>) -> Option<Layoff>;
40
41 /// A display name for tournament output
42 fn name(&self) -> &str {
43 "unnamed"
44 }
45}