Skip to main content

Crate yahtzee_engine

Crate yahtzee_engine 

Source
Expand description

§Yahtzee Engine

Crates.io Docs.rs Build Status

Complete Yahtzee rules and bots: a multiplayer state machine with the official forced-joker scoring, a fast heuristic player, and an exact expectimax solver that knows the best move — and its expected value — in every position.

The design triangle:

  • State: the compact rules authority. Which categories are legal, what they pay, joker forcing, both bonuses — one implementation shared by the scorecard, the game, and the solver, so they can never disagree.
  • Game: the table-level state machine. Deterministic dice injection is the primitive (replays and front ends stay trivial); illegal moves are rejected without changing anything. A Strategy decides from a View, and drivers run whole turns and games.
  • Solver: exact expected values by backward induction over every scorecard state. Under the official rules the empty card is worth 254.5877 points; the widely cited 254.5896 (Verhoeff, 1999) uses a laxer joker convention that the same engine reproduces when the forcing is lifted.

§Bots

  • HeuristicBot: deterministic rules of thumb, microsecond decisions, no tables. Averages 216 points over 10 000 seeded games.
  • OptimalBot: provably perfect solo play backed by the solver. Solves lazily by default; OptimalBot::presolved() computes the whole table up front (seconds in a release build, spread across cores with the parallel feature).

§Quick start

The rules need no features — inject dice, hold, and score:

use yahtzee_engine::{Category, Game, TurnAction};

let mut game = Game::new(1);
game.roll("13446".parse()?)?;
game.act(TurnAction::Reroll("44".parse()?))?;
game.roll("24464".parse()?)?;
game.act(TurnAction::Reroll("444".parse()?))?;
game.roll("44446".parse()?)?;
let delta = game.act(TurnAction::Score(Category::FourOfAKind))?;
assert_eq!(delta.expect("scored").value, 22);

Neither does the solver — ask it what any position is worth:

use yahtzee_engine::{Category, Solver, State};

let mut solver = Solver::new();
// With only Chance open, optimal play is worth exactly 70/3 points.
let state = State::new(!Category::Chance.bit(), 0, false);
assert!((solver.value(state) - 70.0 / 3.0).abs() < 1e-9);

With the (default) rand feature, roll for real and pit bots against each other:

use yahtzee_engine::{Game, HeuristicBot, play_game};

let mut game = Game::new(2);
let (mut alice, mut bob) = (HeuristicBot::new(), HeuristicBot::new());
let totals = play_game(&mut game, &mut [&mut alice, &mut bob], &mut rand::rng())?;
println!("final scores: {totals:?}");

Writing your own bot is implementing Strategy’s two decisions against a View; the driver handles all bookkeeping.

§Feature flags

  • rand (default): dice-rolling helpers (Game::roll_with, play_turn, play_game) and the play/arena examples. The rules and the solver are deterministic; disable it for a dependency-light build.
  • parallel: solves scorecard tiers across the CPU cores via rayon. The table is bit-identical to the serial build, it just arrives faster. Off by default.

§Examples

  • solve: build the full table and print the optimal expected score — cargo run --release --example solve --features parallel
  • play: play against the heuristic bot in the terminal — cargo run --release --example play
  • arena: bot strength measurements — cargo run --release --example arena -- 10000 --optimal

§Play in the browser

The web/ crate wraps the engine in WebAssembly with the solver as a live advisor: https://jdh8.github.io/yahtzee-engine/.

Structs§

Categories
A set of categories, one bit per Category.
Dice
A roll of exactly five six-sided dice, stored as face counts.
Game
A game of Yahtzee for one or more players.
HeuristicBot
A deterministic rule-of-thumb player.
HeuristicConfig
Tuning knobs for HeuristicBot.
Keep
A sub-multiset of zero to five dice held between rolls.
OptimalBot
A Strategy that plays perfectly, backed by a Solver.
ScoreDelta
The outcome of scoring a category: the box value, any bonuses it triggered, and the resulting state.
Scorecard
One player’s card: every box value plus the Yahtzee-bonus count.
Solver
An exact expectimax solver over the full game.
State
Everything about a scorecard that affects future play.
View
What the acting player looks at while deciding.

Enums§

Category
A scoring category on the card.
EngineError
A strategy misbehaved: it returned an action the rules reject.
GameError
A rules violation, reported without changing the game.
ParseDiceError
Error parsing dice from text.
Phase
Where the game stands, and what input it expects next.
TurnAction
What to do with the dice on the table: hold some and reroll the rest, or write a category now.

Traits§

Strategy
A decision procedure for playing a turn.

Functions§

play_game
Plays the game to completion and returns the final totals in seat order.
play_turn
Plays one complete turn: rolls, consults strategy, and scores.