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
- [
model] --- Data structures for the entire game:- State (including
StateCore), Action, Reaction RoundBegin,RoundEnd, ...AgariResult,Scoring, ...RoundHistory,RoundHistoryLite, ...
- State (including
- [
engine] --- The game Engine. - [
rules] --- Configurable Ruleset for the engine. - [
yaku] --- All known Yaku's and utils. - [
interop] --- Working with data models from other implementations of the Japanese Riichi Mahjong game.
Quick Example
See docs on [engine::Engine].
use *; // includes `Engine` and `riichi_elements::prelude::*`
let mut engine = new;
engine.begin_round;
assert_eq!;
assert_eq!;
engine.register_action?;
// use `engine.register_reaction` for Chii/Pon/Daiminkan/Ron
let step = engine.step;
assert_eq!;
assert_eq!;
assert_eq!;
/* ... */
# Ok::
In a more realistic setting:
round_id,pot, andpointsmay be either their begin-of-game values or derived from the previous round's results.wallshould be shuffled, e.g. using therandcrate.- The State of the engine should be observed by the players at each step.
- Actions and Reactions should be from players' inputs.
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:
- The "Ba-Kyoku-Honba" triplet, i.e. "East 1 Kyoku, 0 Honba", represented as [
model::RoundId]. - How many points each player has at the beginning of the round.
- How many "riichi sticks" currently remains on the table.
- The complete shuffled wall (34 x 4 = 136 tiles) to be used in this round (see [
riichi_elements::wall]).
State Machine of a Round
The game flow within a round can be modeled as the following state machine:
┌──────┐
│ 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:
-
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). -
Each other player may independently declare an reaction ([
model::Reaction]): Chii, Pon, Daiminkan, or Ron. The resolved reaction type determines the next state. -
After reaction resolution, we need to check for any involuntary round-ending conditions.
-
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:
- The state marked
#2is basically the pre-action state (#1) + the action taken. - The states marked
#3are terminal (abortion / win). They can be handled separately outside the normal game flow. - The states marked
#4are internal transitory states skipped over by the engine without any player input.
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].