
Offers tools for analyzing Japanese Mahjong hands, including points calculation, hand improvement analysis, and winning hand evaluation. Provides support for multiple languages via dynamic library bindings.
Japanese Mahjong Utilities
Implemented Features:
App developed based on this library: https://github.com/ssttkkl/mahjong-utils-app
Good news! This component has officially partnered with DeepWiki. Now you can ask AI directly instead of wasting time posting Issues!
Documentation: https://deepwiki.com/ssttkkl/mahjong-utils
Example question: How to calculate all yaku in a winning hand?
This project uses Kotlin/Multiplatform and supports multiple platforms:
For Kotlin/Java: Continue reading below
For Python: We provide Python bindings - see python-lib/README.md
For JavaScript/TypeScript: We provide JS/TS bindings - see js-lib/README.md
For CLI: We provide command-line tools - see skills/mahjong-utils-cli/SKILL.md
Maven:
<dependency>
<groupId>io.github.ssttkkl</groupId>
<artifactId>mahjong-utils</artifactId>
<version>${mahjongUtilsVersion}</version>
</dependency>Gradle:
implementation "io.github.ssttkkl:mahjong-utils:{mahjongUtilsVersion}"Without drawn tile:
val result = shanten(Tile.parseTiles("34568m235p68s"))
val shantenInfo = result.shantenInfo as ShantenWithoutGot
print(shantenInfo.shantenNum) // 2
print(shantenInfo.advance) // [7m, 1p, 4p, 5p, 6s, 8s, 7s, 2p, 3p, 6m, 8m, 3m]With drawn tile:
val result = shanten(Tile.parseTiles("112233p44556s127z"))
val shantenInfo = result.shantenInfo as ShantenWithGot
print(shantenInfo.shantenNum) // 1
print(shantenInfo.discardToAdvance)
/*
{1z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 2z, 7z], advanceNum=13, goodShapeAdvance=[2z, 7z], goodShapeAdvanceNum=6),
2z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 1z, 7z], advanceNum=13, goodShapeAdvance=[1z, 7z], goodShapeAdvanceNum=6),
6s=ShantenWithoutGot(shantenNum=1, advance=[1z, 2z, 7z], advanceNum=9, goodShapeAdvance=[], goodShapeAdvanceNum=0),
7z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 1z, 2z], advanceNum=13, goodShapeAdvance=[1z, 2z], goodShapeAdvanceNum=6),
5s=ShantenWithoutGot(shantenNum=2, advance=[2s, 3s, 4s, 5s, 6s, 1z, 2z, 7z, 7s], advanceNum=28, goodShapeAdvance=null, goodShapeAdvanceNum=null),
3p=ShantenWithoutGot(shantenNum=2, advance=[3p, 3s, 6s, 1z, 2z, 7z], advanceNum=18, goodShapeAdvance=null, goodShapeAdvanceNum=null),
4s=ShantenWithoutGot(shantenNum=2, advance=[3s, 4s, 5s, 6s, 7s, 1z, 2z, 7z], advanceNum=24, goodShapeAdvance=null, goodShapeAdvanceNum=null),
2p=ShantenWithoutGot(shantenNum=2, advance=[2p, 3s, 6s, 1z, 2z, 7z], advanceNum=18, goodShapeAdvance=null, goodShapeAdvanceNum=null),
1p=ShantenWithoutGot(shantenNum=2, advance=[1p, 4p, 3s, 6s, 1z, 2z, 7z], advanceNum=22, goodShapeAdvance=null, goodShapeAdvanceNum=null)}
*/shantenNum excludes a wait that would require a fifth copy of a tile already held in your hand or own melds. structuralShantenNum reports the shape-only count. The analysis does not account for tiles visible in other players' hands, melds, or discards.
val result = shanten(Tile.parseTiles("111z222z333z4444z"))
print(result.shantenInfo.shantenNum) // 1
print(result.shantenInfo.structuralShantenNum) // 0val result = hora(
tiles = Tile.parseTiles("12233466m111z"),
furo = listOf(Furo("789p")),
agari = Tile.get("1z"),
tsumo = true,
dora = 4,
selfWind = Wind.East,
roundWind = Wind.East
)
print(result.yaku) // {SelfWind, RoundWind}
print(result.han) // 6
print(result.hu) // 30
print(result.parentPoint) // ParentPoint(ron=18000, tsumo=6000)
print(result.childPoint) // ChildPoint(ron=12000, tsumoParent=6000, tsumoChild=3000)// Dealer points for 3 han 40 fu
val parentPoint = getParentPointByHanHu(3, 40)
print(parentPoint.ron) // 7700
print(parentPoint.tsumo) // 2600
print(parentPoint.tsumoTotal) // 7800
// Non-dealer points for 3 han 40 fu
val childPoint = getChildPointByHanHu(3, 40)
print(childPoint.ron) // 5200
print(childPoint.tsumoParent) // 2600
print(childPoint.tsumoChild) // 1300
print(childPoint.tsumoTotal) // 5200For cross-language integration, mahjong-utils-entry exposes a unified call method with JSON-formatted parameters and responses.
Ensure JDK11+ is installed. Clone the project and run:
./gradlew :mahjong-utils-entry:linkReleaseSharedNativeOutput files will be in mahjong-utils-entry/build/bin/native/releaseShared.
#include <stdio.h>
#include "libmahjongutils_api.h"
int main(int argc, char** argv) {
libmahjongutils_ExportedSymbols* lib = libmahjongutils_symbols();
const char* params = "{\"tiles\":[\"1s\",\"1s\",\"1s\",\"2s\",\"3s\",\"4s\",\"5s\",\"6s\",\"7s\",\"8s\"]}";
const char* result = lib->kotlin.root.mahjongutils.entry.call("shanten", params);
printf("%s", result);
return 0;
}Output contains three fields: code (status code), msg (error message), and data (result payload).
{"data":{"hand":{"tiles":["1s","1s","1s","2s","3s","4s","5s","6s","7s","8s"],"furo":[],"patterns":[{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","234s","567s"],"furo":[],"tatsu":[],"remaining":["8s"]},{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","234s","678s"],"furo":[],"tatsu":[],"remaining":["5s"]},{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","345s","678s"],"furo":[],"tatsu":[],"remaining":["2s"]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["123s","456s"],"furo":[],"tatsu":["78s"],"remaining":[]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["123s","678s"],"furo":[],"tatsu":["45s"],"remaining":[]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["345s","678s"],"furo":[],"tatsu":["12s"],"remaining":[]}]},"shantenInfo":{"type":"ShantenWithoutGot","shantenNum":0,"advance":["8s","5s","2s","6s","9s","3s"],"advanceNum":19,"goodShapeAdvance":null,"goodShapeAdvanceNum":null,"improvement":{},"improvementNum":0,"goodShapeImprovement":{},"goodShapeImprovementNum":0},"regular":{"hand":{"tiles":["1s","1s","1s","2s","3s","4s","5s","6s","7s","8s"],"furo":[],"patterns":[{"k":3,"jyantou":null,"menzenMentsu":["111s","234s","567s"],"furo":[],"tatsu":[],"remaining":["8s"]},{"k":3,"jyantou":null,"menzenMentsu":["111s","234s","678s"],"furo":[],"tatsu":[],"remaining":["5s"]},{"k":3,"jyantou":null,"menzenMentsu":["111s","345s","678s"],"furo":[],"tatsu":[],"remaining":["2s"]},{"k":3,"jyantou":"1s","menzenMentsu":["123s","456s"],"furo":[],"tatsu":["78s"],"remaining":[]},{"k":3,"jyantou":"1s","menzenMentsu":["123s","678s"],"furo":[],"tatsu":["45s"],"remaining":[]},{"k":3,"jyantou":"1s","menzenMentsu":["345s","678s"],"furo":[],"tatsu":["12s"],"remaining":[]}]},"shantenInfo":{"type":"ShantenWithoutGot","shantenNum":0,"advance":["8s","5s","2s","6s","9s","3s"],"advanceNum":19,"goodShapeAdvance":null,"goodShapeAdvanceNum":null,"improvement":{},"improvementNum":0,"goodShapeImprovement":{},"goodShapeImprovementNum":0}},"chitoi":null,"kokushi":null},"code":200,"msg":""}
For more about Kotlin/Native dynamic libraries: https://kotlinlang.org/docs/native-dynamic-libraries.html#use-generated-headers-from-c
Japanese Mahjong Utilities
Implemented Features:
App developed based on this library: https://github.com/ssttkkl/mahjong-utils-app
Good news! This component has officially partnered with DeepWiki. Now you can ask AI directly instead of wasting time posting Issues!
Documentation: https://deepwiki.com/ssttkkl/mahjong-utils
Example question: How to calculate all yaku in a winning hand?
This project uses Kotlin/Multiplatform and supports multiple platforms:
For Kotlin/Java: Continue reading below
For Python: We provide Python bindings - see python-lib/README.md
For JavaScript/TypeScript: We provide JS/TS bindings - see js-lib/README.md
For CLI: We provide command-line tools - see skills/mahjong-utils-cli/SKILL.md
Maven:
<dependency>
<groupId>io.github.ssttkkl</groupId>
<artifactId>mahjong-utils</artifactId>
<version>${mahjongUtilsVersion}</version>
</dependency>Gradle:
implementation "io.github.ssttkkl:mahjong-utils:{mahjongUtilsVersion}"Without drawn tile:
val result = shanten(Tile.parseTiles("34568m235p68s"))
val shantenInfo = result.shantenInfo as ShantenWithoutGot
print(shantenInfo.shantenNum) // 2
print(shantenInfo.advance) // [7m, 1p, 4p, 5p, 6s, 8s, 7s, 2p, 3p, 6m, 8m, 3m]With drawn tile:
val result = shanten(Tile.parseTiles("112233p44556s127z"))
val shantenInfo = result.shantenInfo as ShantenWithGot
print(shantenInfo.shantenNum) // 1
print(shantenInfo.discardToAdvance)
/*
{1z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 2z, 7z], advanceNum=13, goodShapeAdvance=[2z, 7z], goodShapeAdvanceNum=6),
2z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 1z, 7z], advanceNum=13, goodShapeAdvance=[1z, 7z], goodShapeAdvanceNum=6),
6s=ShantenWithoutGot(shantenNum=1, advance=[1z, 2z, 7z], advanceNum=9, goodShapeAdvance=[], goodShapeAdvanceNum=0),
7z=ShantenWithoutGot(shantenNum=1, advance=[3s, 6s, 1z, 2z], advanceNum=13, goodShapeAdvance=[1z, 2z], goodShapeAdvanceNum=6),
5s=ShantenWithoutGot(shantenNum=2, advance=[2s, 3s, 4s, 5s, 6s, 1z, 2z, 7z, 7s], advanceNum=28, goodShapeAdvance=null, goodShapeAdvanceNum=null),
3p=ShantenWithoutGot(shantenNum=2, advance=[3p, 3s, 6s, 1z, 2z, 7z], advanceNum=18, goodShapeAdvance=null, goodShapeAdvanceNum=null),
4s=ShantenWithoutGot(shantenNum=2, advance=[3s, 4s, 5s, 6s, 7s, 1z, 2z, 7z], advanceNum=24, goodShapeAdvance=null, goodShapeAdvanceNum=null),
2p=ShantenWithoutGot(shantenNum=2, advance=[2p, 3s, 6s, 1z, 2z, 7z], advanceNum=18, goodShapeAdvance=null, goodShapeAdvanceNum=null),
1p=ShantenWithoutGot(shantenNum=2, advance=[1p, 4p, 3s, 6s, 1z, 2z, 7z], advanceNum=22, goodShapeAdvance=null, goodShapeAdvanceNum=null)}
*/shantenNum excludes a wait that would require a fifth copy of a tile already held in your hand or own melds. structuralShantenNum reports the shape-only count. The analysis does not account for tiles visible in other players' hands, melds, or discards.
val result = shanten(Tile.parseTiles("111z222z333z4444z"))
print(result.shantenInfo.shantenNum) // 1
print(result.shantenInfo.structuralShantenNum) // 0val result = hora(
tiles = Tile.parseTiles("12233466m111z"),
furo = listOf(Furo("789p")),
agari = Tile.get("1z"),
tsumo = true,
dora = 4,
selfWind = Wind.East,
roundWind = Wind.East
)
print(result.yaku) // {SelfWind, RoundWind}
print(result.han) // 6
print(result.hu) // 30
print(result.parentPoint) // ParentPoint(ron=18000, tsumo=6000)
print(result.childPoint) // ChildPoint(ron=12000, tsumoParent=6000, tsumoChild=3000)// Dealer points for 3 han 40 fu
val parentPoint = getParentPointByHanHu(3, 40)
print(parentPoint.ron) // 7700
print(parentPoint.tsumo) // 2600
print(parentPoint.tsumoTotal) // 7800
// Non-dealer points for 3 han 40 fu
val childPoint = getChildPointByHanHu(3, 40)
print(childPoint.ron) // 5200
print(childPoint.tsumoParent) // 2600
print(childPoint.tsumoChild) // 1300
print(childPoint.tsumoTotal) // 5200For cross-language integration, mahjong-utils-entry exposes a unified call method with JSON-formatted parameters and responses.
Ensure JDK11+ is installed. Clone the project and run:
./gradlew :mahjong-utils-entry:linkReleaseSharedNativeOutput files will be in mahjong-utils-entry/build/bin/native/releaseShared.
#include <stdio.h>
#include "libmahjongutils_api.h"
int main(int argc, char** argv) {
libmahjongutils_ExportedSymbols* lib = libmahjongutils_symbols();
const char* params = "{\"tiles\":[\"1s\",\"1s\",\"1s\",\"2s\",\"3s\",\"4s\",\"5s\",\"6s\",\"7s\",\"8s\"]}";
const char* result = lib->kotlin.root.mahjongutils.entry.call("shanten", params);
printf("%s", result);
return 0;
}Output contains three fields: code (status code), msg (error message), and data (result payload).
{"data":{"hand":{"tiles":["1s","1s","1s","2s","3s","4s","5s","6s","7s","8s"],"furo":[],"patterns":[{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","234s","567s"],"furo":[],"tatsu":[],"remaining":["8s"]},{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","234s","678s"],"furo":[],"tatsu":[],"remaining":["5s"]},{"type":"RegularHandPattern","k":3,"jyantou":null,"menzenMentsu":["111s","345s","678s"],"furo":[],"tatsu":[],"remaining":["2s"]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["123s","456s"],"furo":[],"tatsu":["78s"],"remaining":[]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["123s","678s"],"furo":[],"tatsu":["45s"],"remaining":[]},{"type":"RegularHandPattern","k":3,"jyantou":"1s","menzenMentsu":["345s","678s"],"furo":[],"tatsu":["12s"],"remaining":[]}]},"shantenInfo":{"type":"ShantenWithoutGot","shantenNum":0,"advance":["8s","5s","2s","6s","9s","3s"],"advanceNum":19,"goodShapeAdvance":null,"goodShapeAdvanceNum":null,"improvement":{},"improvementNum":0,"goodShapeImprovement":{},"goodShapeImprovementNum":0},"regular":{"hand":{"tiles":["1s","1s","1s","2s","3s","4s","5s","6s","7s","8s"],"furo":[],"patterns":[{"k":3,"jyantou":null,"menzenMentsu":["111s","234s","567s"],"furo":[],"tatsu":[],"remaining":["8s"]},{"k":3,"jyantou":null,"menzenMentsu":["111s","234s","678s"],"furo":[],"tatsu":[],"remaining":["5s"]},{"k":3,"jyantou":null,"menzenMentsu":["111s","345s","678s"],"furo":[],"tatsu":[],"remaining":["2s"]},{"k":3,"jyantou":"1s","menzenMentsu":["123s","456s"],"furo":[],"tatsu":["78s"],"remaining":[]},{"k":3,"jyantou":"1s","menzenMentsu":["123s","678s"],"furo":[],"tatsu":["45s"],"remaining":[]},{"k":3,"jyantou":"1s","menzenMentsu":["345s","678s"],"furo":[],"tatsu":["12s"],"remaining":[]}]},"shantenInfo":{"type":"ShantenWithoutGot","shantenNum":0,"advance":["8s","5s","2s","6s","9s","3s"],"advanceNum":19,"goodShapeAdvance":null,"goodShapeAdvanceNum":null,"improvement":{},"improvementNum":0,"goodShapeImprovement":{},"goodShapeImprovementNum":0}},"chitoi":null,"kokushi":null},"code":200,"msg":""}
For more about Kotlin/Native dynamic libraries: https://kotlinlang.org/docs/native-dynamic-libraries.html#use-generated-headers-from-c