A Riichi mahjong library in Rust. (Very WIP.)
For now, it handles most hand scoring scenarios under standard rules. In addition to the tests included in this repo, the logic has also been tested extensively on about a million Tenhou game logs, and seems to be accurate outside of super-edge cases. While this has not yet been heavily optimized, it is already reasonably performant, because Rust.
The plan is to eventually expand this library into a full game engine to be potentially used for reinforcement learning and other computational experiments, and, well, actually playing the game itself.
Here's a demo of a Wasm-based hand calculator that uses this library under the hood.
This project is built against stable Rust.
Run tests:
$ make test
Format code:
$ make fmt
Here's an example that scores a hand with 1 han and 110 fu:
- Closed tiles: 4-man, 5-man, 6-man, ton, ton, nan, nan
- Calls:
- Closed kan on 1-sou
- Closed kan on chun
- Agari: ton (by ron)
- Round wind: nan
- Player wind: nan
use toitoi::score::score;
use toitoi::tile::{tile_from_string, tiles_from_string};
use toitoi::types::{Call, FuReason, HandContext, HanReason, Limit, Points, Yaku};
let tiles = tiles_from_string("456m1122z");
let calls = vec![
Call::ankan(tile_from_string("1s")),
Call::ankan(tile_from_string("7z")),
];
let context = HandContext {
winning_tile: tile_from_string("1z"),
is_tsumo: false,
round_wind: tile_from_string("2z"),
player_wind: tile_from_string("2z"),
..Default::default()
};
let results = score(&tiles, &calls, &context);
assert_eq!(results.len(), 1);
assert_eq!(results[0].fu(), 110);
assert_eq!(results[0].han(), 1);
assert_eq!(results[0].limit(), Limit::NoLimit);
assert_eq!(results[0].points(), Points::Ron(3600));
assert_eq!(results[0].fu_reasons(), vec![
(FuReason::Base, 20),
(FuReason::OpenTripletHonours, 4),
(FuReason::YakuhaiPairRoundWind, 2),
(FuReason::YakuhaiPairPlayerWind, 2),
(FuReason::ClosedQuadTerminals, 32),
(FuReason::ClosedQuadHonours, 32),
(FuReason::ClosedRon, 10),
(FuReason::RoundUp, 8),
]);
assert_eq!(results[0].han_reasons(), vec![(HanReason::Yaku(Yaku::Chun), 1)]);