# Riichi Mahjong Game Engine
This crate implements a game engine of [standard Japanese Riichi Mahjong][riichi-wiki-home] in the form of a library,
building upon the foundation of [riichi-elements] and [riichi-decomp].
[riichi-elements]: https://crates.io/crates/riichi-elements
[riichi-decomp]: https://crates.io/crates/riichi-decomp
## Table of Contents
- [`model`] --- Data structures for the entire game:
- [State] (including [`StateCore`](model::StateCore)), [Action], [Reaction]
- [`RoundBegin`](model::RoundBegin), [`RoundEnd`](model::RoundEnd), ...
- [`AgariResult`](model::AgariResult), [`Scoring`](model::Scoring), ...
- [`RoundHistory`](model::RoundHistory), [`RoundHistoryLite`](model::RoundHistoryLite), ...
- [`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.
[RoundBegin]: model::RoundBegin
[RoundEnd]: model::RoundEnd
[State]: model::State
[Action]: model::Action
[Reaction]: model::Reaction
[Engine]: engine::Engine
[Ruleset]: rules::Ruleset
[Yaku]: yaku::Yaku
## Quick Example
See docs on [`engine::Engine`].
```rust
use riichi::prelude::*; // includes `Engine` and `riichi_elements::prelude::*`
let mut engine = Engine::new();
engine.begin_round(RoundBegin {
ruleset: Default::default(),
round_id: RoundId { kyoku: 0, honba: 0 }, // east 1 kyoku, 0 honba (first round in game)
wall: wall::make_sorted_wall([1, 1, 1]), // 1111m2222m3333m4444m0555m...
pot: 0,
points: [25000, 25000, 25000, 25000],
});
assert_eq!(engine.state().core.seq, 0);
assert_eq!(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();
assert_eq!(step.action_result, ActionResult::Pass);
assert_eq!(engine.state().core.seq, 1);
assert_eq!(engine.state().core.actor, P1);
/* ... */
# Ok::<(), riichi::engine::ActionError>(())
```
In a more realistic setting:
- `round_id`, `pot`, and `points` may be either their begin-of-game values or derived from the previous round's results.
- `wall` should be shuffled, e.g. using the `rand` crate.
- The [State] of the engine should be observed by the players at each step.
- [Action]s and [Reaction]s 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](model::RoundBegin):
- 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"][Riichi] currently remains on the table.
- The complete [shuffled wall][Setup] (34 x 4 = 136 tiles) to be used in this round (see [`riichi_elements::wall`]).
[Hanchan]: https://riichi.wiki/Hanchan
[Tonpuu]: https://riichi.wiki/Tonpuusen
[Ba]: https://riichi.wiki/Ba
[Kyoku]: https://riichi.wiki/Kyoku
[Honba]: https://riichi.wiki/Honba
[Riichi]: https://riichi.wiki/Riichi
[Setup]: https://riichi.wiki/Japanese_mahjong_setup
### 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:
- The state marked `#2` is basically the pre-action state (`#1`) + the action taken.
- The states marked `#3` are terminal (abortion / win). They can be handled separately outside the normal game flow.
- The states marked `#4` are 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](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
- <https://riichi.wiki/Japanese_mahjong>
- <https://en.wikipedia.org/wiki/Mahjong>
- <https://ja.wikipedia.org/wiki/麻雀>
[riichi-wiki-home]: https://riichi.wiki/Japanese_mahjong