Crates.io | riichi |
lib.rs | riichi |
version | 0.1.0 |
source | src |
created_at | 2023-01-06 06:53:38.38203 |
updated_at | 2023-01-06 06:53:38.38203 |
description | Japanese Riichi Mahjong game engine |
homepage | |
repository | https://github.com/summivox/riichi-rs |
max_upload_size | |
id | 752115 |
size | 418,319 |
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.
model
] --- Data structures for the entire game:
StateCore
), Action, ReactionRoundBegin
, RoundEnd
, ...AgariResult
, Scoring
, ...RoundHistory
, 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.See docs on [engine::Engine
].
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.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::RoundId
].riichi_elements::wall
]).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.
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:
#2
is basically the pre-action state (#1
) + the action taken.#3
are terminal (abortion / win). They can be handled separately outside the normal game flow.#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).
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
].