go-fish 0.2.0

A core engine for the classic Go Fish card game, providing game logic, deck management, and player interactions.
Documentation

go-fish

A pure Rust library implementing the core engine for the classic Go Fish card game.

Features

  • Complete game logic: dealing, turn resolution, book completion, player elimination, and win detection
  • Serialization-ready — all public types derive serde::Serialize / Deserialize
  • Instrumented with tracing for debug-level turn logging
  • No async, no I/O — plain library crate, suitable for embedding in any host (server, TUI, WASM, etc.)

Quick start

use go_fish::{Deck, Game, Hook, PlayerId, Rank};

let deck = Deck::new().shuffle();
let mut game = Game::new(deck, /* player_count */ 2);

while !game.is_finished {
    let current = game.get_current_player().unwrap();
    // ... choose a target and rank ...
    let hook = Hook { target: PlayerId::new(1), rank: Rank::Seven };
    match game.take_turn(hook) {
        Ok(result) => { /* handle HookResult::Catch or HookResult::GoFish */ }
        Err(e)     => { /* handle TurnError */ }
    }
}

let result = game.get_game_result().unwrap();
println!("Winners: {:?}", result.winners);

API overview

Types

Type Description
Card A single playing card (suit, rank). Copy.
Suit Clubs, Diamonds, Hearts, Spades.
Rank Two through Ace. Implements Ord and Display.
Deck A 52-card deck. Call .shuffle() before passing to Game::new.
Hand A player's in-hand cards, grouped into IncompleteBooks by rank.
IncompleteBook 1–3 cards of the same rank.
CompleteBook All 4 cards of a rank. Contributes to a player's score.
Player An active player: ID, hand, and completed books.
InactivePlayer An eliminated player (empty hand, deck exhausted). Still scored at game end.
PlayerId Newtype wrapper around u8.
Hook A turn action: ask target for all cards of rank.
Game Top-level game state.

Game::new(deck: Deck, player_count: u8) -> Game

Creates a game and deals initial hands (7 cards for 2–3 players, 5 cards otherwise). Completes any initial books before returning.

Game::take_turn(hook: Hook) -> Result<HookResult, TurnError>

The single method that advances game state. Returns:

  • Ok(HookResult::Catch(book)) — the target held cards of the requested rank; they're transferred to the current player.
  • Ok(HookResult::GoFish) — the target had nothing; the current player draws from the deck.
  • Err(TurnError::TargetNotFound(id)) — the target ID doesn't refer to an active player.
  • Err(TurnError::GameIsFinished) — the game is already over.

After each turn the engine automatically handles book completion, player elimination (empty hand → draw or become inactive), and win detection.

Game::get_current_player() -> Option<&Player>

Returns the player whose turn it is, or None if the game is finished.

Game::get_game_result() -> Option<GameResult>

Returns winners and losers by completed-book count once game.is_finished is true.

Bots

The go_fish::bots module provides a Bot trait and a bundled SimpleBot implementation for driving automated players.

Bot trait

pub trait Bot: Send {
    fn observe(&mut self, observation: BotObservation);
    fn generate_hook(&mut self, valid_targets: &[PlayerId]) -> Hook;
}

After each turn the host calls observe with a BotObservation (own hand, opponent hand sizes, completed books, deck size, last hook outcome), then calls generate_hook when it's the bot's turn.

SimpleBot

A probability-table bot. Construct with:

use go_fish::bots::SimpleBot;

let bot = SimpleBot::new(
    my_player_id,
    /* memory_limit */ 5,    // observations to retain (0 = memoryless)
    /* error_margin */ 0.15, // Gaussian noise std-dev (0.0 = deterministic)
    /* seed        */ 42,
);

It builds a (opponent, rank) → probability table from retained observations, updates it using inference rules (Catch / GoFish outcomes), adds configurable noise, then picks the highest-scoring (target, rank) pair from the bot's current hand.

Testing

cargo test --package go-fish                    # unit + integration tests
cargo test --package go-fish complete_games     # full-game simulation (2–6 players)

Integration tests in tests/complete_games.rs simulate up to 10,000 random turns for each player count (2–6) and assert the game terminates correctly.

License

MIT