gin-rummy 0.1.1

Cards, melds, deadwood solver, and rules-driven state machine for gin rummy
Documentation

Gin Rummy

Crates.io Docs.rs Build Status

This crate models the mechanics of gin rummy: strongly typed cards and melds, an exact deadwood solver, and a rules-driven state machine for complete two-player rounds and games.

Gin rummy ranks the ace LOW: A-2-3 is a run, Q-K-A is not, and an ace counts one point of deadwood. Rank therefore encodes A = 1 through K = 13, unlike crates for ace-high games such as contract-bridge whose patterns this crate otherwise follows.

Hands are written as four dot-separated suit groups in ascending order, clubs first: "A23.456.789.T" holds ♣A ♣2 ♣3 ♦4 ♦5 ♦6 ♥7 ♥8 ♥9 ♠10.

Modules

Feature flags

  • rand: shuffled dealing (Deck, Round::deal, Game::deal)
  • serde: serialization for every public type, with validated deserialization of mid-game Round snapshots

Quick start

Deadwood analysis needs no features:

use gin_rummy::{best_melds, deadwood};

let hand = "A23.456.789.T".parse::<gin_rummy::Hand>()?;
assert_eq!(deadwood(hand), 10);
println!("{}", best_melds(hand));
# Ok::<(), gin_rummy::hand::ParseHandError>(())

A complete bot-vs-bot game with the rand feature:

# #[cfg(feature = "rand")]
# fn main() {
use gin_rummy::{Game, Player, Rules};

let mut game = Game::new(Rules::default(), Player::One);
while !game.is_over() {
    let mut round = game.deal(&mut rand::rng());
    drive(&mut round); // pass / draw / discard / knock / lay_off …
    game.record(round.result().expect("round finished")).unwrap();
}
println!("{:?}", game.final_score());
# }
# #[cfg(feature = "rand")]
# fn drive(round: &mut gin_rummy::Round) {
#     use gin_rummy::{Phase, best_melds, deadwood};
#     while round.result().is_none() {
#         match round.phase() {
#             Phase::Upcard => round.pass().unwrap(),
#             Phase::Draw => {
#                 round.draw_stock().unwrap();
#             }
#             Phase::Discard => {
#                 let hand = round.hand(round.turn().unwrap());
#                 let (card, rest) = hand
#                     .iter()
#                     .map(|card| (card, deadwood(hand - card.into())))
#                     .min_by_key(|&(_, rest)| rest)
#                     .unwrap();
#                 if rest <= round.knock_limit() {
#                     round.knock(card, best_melds(hand - card.into())).unwrap();
#                 } else {
#                     round.discard(card).unwrap();
#                 }
#             }
#             Phase::Layoff => {
#                 round.finish_layoffs().unwrap();
#             }
#             Phase::Finished => unreachable!(),
#         }
#     }
# }
# #[cfg(not(feature = "rand"))]
# fn main() {}

Examples

  • deadwood: parse hands from the command line and print their best melds — cargo run --example deadwood -- "45.456.567.789"
  • simulate: greedy bots play a full game — cargo run --features rand --example simulate