pub struct Solver { /* private fields */ }Expand description
An exact expectimax solver over the full game.
The value table is keyed by State::index and filled by backward
induction: a state’s expected further score is the mean over the
turn’s roll/keep/roll/keep/roll/score lattice of the best reward plus
the value of the resulting state. From the empty card this comes to
an optimal solo expectation of 254.5877 points under the official
forced-joker rules this crate implements. The widely cited 254.5896
(Verhoeff, 1999) uses a laxer convention — an extra Yahtzee may be
scored in any open box, with joker values gated on the matching
upper box being filled — which this engine reproduces exactly when
the forcing in State::legal_categories is lifted.
Construction is cheap; values are computed on demand. Querying a
mid-game state solves only the states reachable from it, so “what is
best here” costs a fraction of a full solve. All
queries break ties deterministically (first category in card order,
then first keep in canonical order), so replays are stable.
Implementations§
Source§impl Solver
impl Solver
Sourcepub fn is_solved(&self, state: State) -> bool
pub fn is_solved(&self, state: State) -> bool
Whether value for this state is already computed.
Sourcepub fn solve_tier(&mut self, filled: u32)
pub fn solve_tier(&mut self, filled: u32)
Solves every state with exactly filled categories scored.
Tiers must be solved from 13 down to 0; solve
does exactly that. Exposed so incremental front ends can report
progress between tiers. With the parallel feature the tier is
solved across threads, bit-identical to the serial build.
§Panics
Panics if filled > 13, or if the tiers above filled have not
been solved yet — solving out of order would silently poison the
table otherwise.
Sourcepub fn solve(&mut self)
pub fn solve(&mut self)
Solves the whole game, all reachable states, bottom-up.
Takes seconds in a release build (use the parallel feature to
spread it across cores); afterwards every query is a table lookup
plus one turn evaluation.
Sourcepub fn value(&mut self, state: State) -> f64
pub fn value(&mut self, state: State) -> f64
The expected further score from the start of a turn in state,
under optimal play.
Solves lazily: only states reachable from state are computed,
so early queries from a part-filled card are much cheaper than a
full solve.
Sourcepub fn category_ev(
&mut self,
state: State,
dice: Dice,
category: Category,
) -> Option<f64>
pub fn category_ev( &mut self, state: State, dice: Dice, category: Category, ) -> Option<f64>
The expected further score of scoring dice in category now
and playing on optimally: the write, its bonuses, and everything
after — points already on the card are not included.
Returns None if the category is illegal here; see
State::legal_categories.
Sourcepub fn keep_ev(
&mut self,
state: State,
dice: Dice,
keep: Keep,
rolls_left: u8,
) -> Option<f64>
pub fn keep_ev( &mut self, state: State, dice: Dice, keep: Keep, rolls_left: u8, ) -> Option<f64>
The expected further score of holding keep and rerolling the
rest, with rolls_left rolls remaining (clamped to the turn’s
two rerolls).
Returns None if the card is full, no rolls remain, or keep
is not part of dice.
Sourcepub fn best_category(&mut self, state: State, dice: Dice) -> Category
pub fn best_category(&mut self, state: State, dice: Dice) -> Category
Sourcepub fn best_action(
&mut self,
state: State,
dice: Dice,
rolls_left: u8,
) -> TurnAction
pub fn best_action( &mut self, state: State, dice: Dice, rolls_left: u8, ) -> TurnAction
The optimal action for dice with rolls_left rolls remaining
(clamped to the turn’s two rerolls). Scoring wins ties against
rerolling.
§Panics
Panics if the card is already full.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Solver
impl RefUnwindSafe for Solver
impl Send for Solver
impl Sync for Solver
impl Unpin for Solver
impl UnsafeUnpin for Solver
impl UnwindSafe for Solver
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more