Riichi Mahjong Game Engine

This crate implements a game engine of standard Japanese Riichi Mahjong in the form of a library, building upon the foundation of [riichi-elements] and [riichi-decomp].

Table of Contents

Quick Example

See docs on [engine::Engine].

``rust use riichi::prelude::*; // includesEngineandriichi_elements::prelude::*`

let mut engine = Engine::new();

engine.beginround(RoundBegin { ruleset: Default::default(), roundid: RoundId { kyoku: 0, honba: 0 }, // east 1 kyoku, 0 honba (first round in game) wall: wall::makesortedwall([1, 1, 1]), // 1111m2222m3333m4444m0555m... pot: 0, points: [25000, 25000, 25000, 25000], }); asserteq!(engine.state().core.seq, 0); asserteq!(engine.state().core.actor, P0);

engine.register_action(Action::Discard(Discard { tile: t!("1m"), ..Discard::default()}))?;

// use engine.register_reaction for Chii/Pon/Daiminkan/Ron

let step = engine.step(); asserteq!(step.actionresult, ActionResult::Pass);

asserteq!(engine.state().core.seq, 1); asserteq!(engine.state().core.actor, P1); /* ... */

Ok::<(), riichi::engine::ActionError>(())

```

In a more realistic setting:

How We Model the Game

Game Setup

Each game ([Hanchan], [Tonpuu], ...) is played by 4 players and consists of at least 1 round ([Kyoku]). The 3-player variant is currently not supported.

Each round starts with an initial state:

State Machine of a Round

The game flow within a round can be modeled as the following state machine:

asciiart ┌──────┐ │ Deal │ └─┬────┘ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ ▼ ▼ #1 #2 │ ┌────────┐ Draw=Y ┌────────────┐ ┌─────────────┐ Nothing │ │DrawHead├──────────►│ │ │ ├───────────┤ └────────┘ Meld=N │ │ Discard │ │ │ #4 │ ├──────────►│ │ #3 ▼ │ │ Riichi │ │ ┌─────────────────┐ │ In-turn │ │ Resolved │ │ Forced abortion │ │ player's │ │ declaration │ └─────────────────┘ ┌────────┐ Draw=Y │ decision │ │ from │ ▲ ┌─►│DrawTail├──────────►│ │ │ out-of-turn │ │ │ └────────┘ Meld=Y │ (Action) │ │ players │ Daiminkan │ │ #4 │ │ │ ├───────────┤ │ │ │ │ (Reaction) │ │ │ │ │ Kakan │ │ │ │ ┌────────┐ Draw=N │ ├──────────►│ │ Chii │ │ │Chii/Pon├──────────►│ │ Ankan │ ├─────────┐ │ │ └────────┘ Meld=Y └──┬───────┬─┘ └──────┬──────┘ Pon │ │ │ #4 ▲ │ │ │ │ │ │ │ NineKinds│ │Tsumo │Ron │ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌──────────┐ ┌─────┐ ┌─────┐ │ │ │ │ #3│ Abortion │ │ Win │#3 │ Win │#3 │ │ │ │ └──────────┘ └─────┘ └─────┘ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────────┘

There are multiple states within one logical turn of a round:

  1. The player in turn is ready to take an action ([model::Action]), after incoming draw and/or meld. This action might be terminal (abortion by nine kinds, or win by self draw).

  2. Each other player may independently declare an reaction ([model::Reaction]): Chii, Pon, Daiminkan, or Ron. The resolved reaction type determines the next state.

  3. After reaction resolution, we need to check for any involuntary round-ending conditions.

  4. All done, then the next player gains draw and/or meld depending on what has happened so far, marking the beginning of the next turn.

Not all actions are valid at all times; the validity often depends on state variables not illustrated in the state machine diagram.

One-state-per-turn Simplification

It is possible to simplify by only explicitly modeling one state (per turn), namely the one before the in-turn player makes a decision (after taking a draw or a Chii/Pon). This is basically #1 in the state machine diagram, represented by [model::State].

All other states in the diagram can be derived from this:

This key simplification enables a regular representation of the normal game flow of a round as a sequence of triplets: [State] + [Action] + [Reaction] (optional).

Optional features

serde (Default: enabled)

Defines a JSON-centric serialization format for most of the common data structures, including those in the [riichi-elements] crate.

This simplifies interop with external programs (bots, client-server, analysis, data processing), persistence of game states, etc..

See each individual type's docs for the detailed format.

tenhou-log-json (Default: enabled)

Defines an intermediate de/serialization data model for Tenhou's JSON-formatted logs, and reconstruction of each round's preconditions, action-reactions, and end conditions into our own data model.

See [interop::tenhou_log_json] mod-level docs for details.

static-lut (Default: disabled)

Enables the corresponding feature in the [riichi-decomp] crate, which builds the lookup tables required by its hand analysis algorithms statically. If disabled, the lookup tables will be generated upon first instantiation of [riichi_decomp::Decomposer].

References