Skip to main content

mahjong-utils-py

PyPI version

日麻小工具ssttkkl/mahjong-utils的Python绑定

安装

pip install mahjong-utils

使用

获取番符对应和牌点数

from mahjong_utils.point_by_han_hu import get_parent_point_by_han_hu, get_child_point_by_han_hu

# 获取亲家X番Y符的点数,返回(荣和点数, 自摸各家点数)
parent_ron, parent_tsumo = get_parent_point_by_han_hu(3, 40)
# parent_ron == 7700
# parent_tsumo == 2600

# 获取子家X番Y符的点数,返回(荣和点数, 自摸庄家点数, 自摸闲家点数)
child_ron, child_tsumo_parent, child_tsumo_child = get_child_point_by_han_hu(3, 40)
# child_ron == 5200
# child_tsumo_child == 1300
# child_tsumo_parent == 2600

手牌向听数、进张分析

from mahjong_utils.models.tile import parse_tiles
from mahjong_utils.shanten import shanten

result = shanten(parse_tiles("34568m235p68s"))
result.shanten
# 1
result.structural_shanten
# 1
result.advance
# {3m, 6m, 7m, 8m, 1p, 2p, 3p, 4p, 5p, 6s, 7s, 8s}

# 结构听牌但自家已用完全部和牌张时,两个向听数会不同
result = shanten(parse_tiles("111z222z333z4444z"))
result.shanten
# 1
result.structural_shanten
# 0

摸牌何切分析

from mahjong_utils.models.tile import parse_tiles
from mahjong_utils.shanten import shanten

result = shanten(parse_tiles("112233p44556s127z"))
result.shanten
# 1
result.structural_shanten
# 1
result.discard_to_advance
# {1p: ShantenWithoutGot(shanten=2, advance={2z, 7z, 1p, 4p, 3s, 6s, 1z}, advance_num=22, good_shape_advance=None, good_shape_advance_num=None),
# 2p: ShantenWithoutGot(shanten=2, advance={2z, 7z, 2p, 3s, 6s, 1z}, advance_num=18, good_shape_advance=None, good_shape_advance_num=None),
# 3p: ShantenWithoutGot(shanten=2, advance={2z, 7z, 3p, 3s, 6s, 1z}, advance_num=18, good_shape_advance=None, good_shape_advance_num=None),
# 4s: ShantenWithoutGot(shanten=2, advance={2z, 7z, 3s, 4s, 5s, 6s, 7s, 1z}, advance_num=24, good_shape_advance=None, good_shape_advance_num=None),
# 5s: ShantenWithoutGot(shanten=2, advance={2z, 7z, 2s, 3s, 4s, 5s, 6s, 1z}, advance_num=24, good_shape_advance=None, good_shape_advance_num=None),
# 6s: ShantenWithoutGot(shanten=1, advance={2z, 7z, 1z}, advance_num=9, good_shape_advance=set(), good_shape_advance_num=0),
# 1z: ShantenWithoutGot(shanten=1, advance={2z, 6s, 7z, 3s}, advance_num=13, good_shape_advance={2z, 7z}, good_shape_advance_num=6),
# 2z: ShantenWithoutGot(shanten=1, advance={6s, 3s, 7z, 1z}, advance_num=13, good_shape_advance={7z, 1z}, good_shape_advance_num=6),
# 7z: ShantenWithoutGot(shanten=1, advance={2z, 6s, 3s, 1z}, advance_num=13, good_shape_advance={2z, 1z}, good_shape_advance_num=6)}

鸣牌分析

from mahjong_utils.models.tile import parse_tiles, Tile
from mahjong_utils.shanten import furo_chance_shanten

result = furo_chance_shanten(parse_tiles("3456778m123457p"),
                             Tile.by_text("7m"))
result.shanten
# 0
result.shanten_info.chi
# {56m: ShantenWithGot(shanten=1, discard_to_advance={
#   8m: ShantenWithoutGot(shanten=1, advance={6p, 2m, 5m, 3p}, advance_num=15, good_shape_advance={6p, 2m, 5m, 3p}, good_shape_advance_num=15), 
#   7p: ShantenWithoutGot(shanten=1, advance={6p, 2m, 5m, 3p}, advance_num=15, good_shape_advance={6p, 2m, 5m, 3p}, good_shape_advance_num=15), 
#   4p: ShantenWithoutGot(shanten=1, advance={6p, 2m, 5m}, advance_num=12, good_shape_advance={6p}, good_shape_advance_num=4), 
#   1p: ShantenWithoutGot(shanten=1, advance={6p, 2m, 5m}, advance_num=12, good_shape_advance={6p}, good_shape_advance_num=4), 
#   3m: ShantenWithoutGot(shanten=2, advance={2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 1p, 2p, 3p, 4p, 5p, 6p, 7p, 8p, 9p}, advance_num=57, good_shape_advance=None, good_shape_advance_num=None),
#   4m: ShantenWithoutGot(shanten=2, advance={1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 1p, 2p, 3p, 4p, 5p, 6p, 7p, 8p, 9p}, advance_num=61, good_shape_advance=None, good_shape_advance_num=None), 
#   7m: ShantenWithoutGot(shanten=2, advance={2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 1p, 2p, 3p, 4p, 5p, 6p, 7p}, advance_num=49, good_shape_advance=None, good_shape_advance_num=None), 
#   2p: ShantenWithoutGot(shanten=2, advance={2m, 5m, 6m, 7m, 8m, 9m, 1p, 2p, 3p, 5p, 6p, 7p, 8p, 9p}, advance_num=48, good_shape_advance=None, good_shape_advance_num=None),
#   3p: ShantenWithoutGot(shanten=2, advance={6p, 2m, 5m, 3p}, advance_num=15, good_shape_advance=None, good_shape_advance_num=None), 
#   5p: ShantenWithoutGot(shanten=2, advance={2m, 5m, 6m, 7m, 8m, 9m, 1p, 2p, 3p, 4p, 5p, 6p, 7p, 8p, 9p}, advance_num=51, good_shape_advance=None, good_shape_advance_num=None)}, ankan_to_advance={}), 
# 68m: ShantenWithGot(shanten=0, discard_to_advance={
#   7p: ShantenWithoutGot(shanten=0, advance={6p, 3p}, advance_num=7, good_shape_advance=None, good_shape_advance_num=None), 
#   4p: ShantenWithoutGot(shanten=0, advance={6p}, advance_num=4, good_shape_advance=None, good_shape_advance_num=None), 
#   1p: ShantenWithoutGot(shanten=0, advance={6p}, advance_num=4, good_shape_advance=None, good_shape_advance_num=None), 
#   3m: ShantenWithoutGot(shanten=1, advance={6p, 3m, 3p, 6m}, advance_num=14, good_shape_advance={6p, 3m, 3p, 6m}, good_shape_advance_num=14),
#   4m: ShantenWithoutGot(shanten=1, advance={6p, 4m, 3p}, advance_num=10, good_shape_advance={4m}, good_shape_advance_num=3), 
#   5m: ShantenWithoutGot(shanten=1, advance={6p, 2m, 5m, 3p}, advance_num=14, good_shape_advance={6p, 2m, 5m, 3p}, good_shape_advance_num=14), 
#   7m: ShantenWithoutGot(shanten=1, advance={7m, 1p, 3p, 4p, 6p, 7p}, advance_num=18, good_shape_advance={6p, 7p, 7m}, good_shape_advance_num=9), 
#   2p: ShantenWithoutGot(shanten=1, advance={1p, 2p, 3p, 5p, 6p, 7p, 8p, 9p}, advance_num=27, good_shape_advance={6p, 8p, 2p}, good_shape_advance_num=11),
#   3p: ShantenWithoutGot(shanten=1, advance={6p, 3p}, advance_num=7, good_shape_advance={3p}, good_shape_advance_num=3),
#   5p: ShantenWithoutGot(shanten=1, advance={1p, 2p, 3p, 4p, 5p, 6p, 7p, 8p, 9p}, advance_num=30, good_shape_advance={6p, 8p, 3p, 5p}, good_shape_advance_num=14)}, ankan_to_advance={})}

result.shanten_info.pon
# ShantenWithGot(shanten=1, discard_to_advance={
#   7p: ShantenWithoutGot(shanten=1, advance={3m, 6m, 7m, 8m, 1p, 2p, 3p, 4p, 5p, 6p}, advance_num=32, good_shape_advance={3m, 6m, 7m, 8m, 3p, 6p}, good_shape_advance_num=20), 
#   4p: ShantenWithoutGot(shanten=1, advance={3m, 6m, 7m, 8m, 5p, 6p, 7p}, advance_num=23, good_shape_advance={6p}, good_shape_advance_num=4), 
#   5p: ShantenWithoutGot(shanten=1, advance={7p, 1p, 4p, 7m}, advance_num=13, good_shape_advance={7m}, good_shape_advance_num=4), 
#   6m: ShantenWithoutGot(shanten=1, advance={8m, 1p, 3p, 4p, 6p, 7p}, advance_num=19, good_shape_advance={6p, 7p, 8m}, good_shape_advance_num=10), 
#   8m: ShantenWithoutGot(shanten=1, advance={3m, 6m, 1p, 3p, 4p, 6p, 7p}, advance_num=22, good_shape_advance={3m, 6m, 3p, 6p, 7p}, good_shape_advance_num=16), 
#   1p: ShantenWithoutGot(shanten=1, advance={3m, 6m, 7m, 8m, 2p, 5p, 6p, 7p}, advance_num=26, good_shape_advance={6p, 7m}, good_shape_advance_num=8), 
#   2p: ShantenWithoutGot(shanten=1, advance={7p, 1p, 7m}, advance_num=10, good_shape_advance=set(), good_shape_advance_num=0), 
#   3m: ShantenWithoutGot(shanten=1, advance={8m, 1p, 3p, 4p, 6p, 7p}, advance_num=19, good_shape_advance={6p, 8m, 7p}, good_shape_advance_num=10), 
#   4m: ShantenWithoutGot(shanten=2, advance={3m, 4m, 5m, 6m, 7m, 8m, 1p, 2p, 3p, 4p, 5p, 6p, 7p}, advance_num=41, good_shape_advance=None, good_shape_advance_num=None), 
#   5m: ShantenWithoutGot(shanten=2, advance={2m, 3m, 4m, 5m, 6m, 7m, 8m, 1p, 2p, 3p, 4p, 5p, 6p, 7p}, advance_num=45, good_shape_advance=None, good_shape_advance_num=None), 
#   3p: ShantenWithoutGot(shanten=2, advance={3m, 6m, 7m, 8m, 1p, 2p, 3p, 4p, 5p, 6p, 7p}, advance_num=35, good_shape_advance=None, good_shape_advance_num=None)}, ankan_to_advance={})

result.shanten_info.pass_
# ShantenWithoutGot(shanten=1, advance={7m, 1p, 3p, 4p, 6p, 7p}, advance_num=18, good_shape_advance={6p, 7p, 7m}, good_shape_advance_num=9)

result.shanten_info.minkan
# None

和了分析

from mahjong_utils.hora import build_hora
from mahjong_utils.models.tile import parse_tiles, Tile
from mahjong_utils.models.wind import Wind
from mahjong_utils.models.furo import Furo
from mahjong_utils.yaku.common import self_wind, round_wind

# 和了分析
hora = build_hora(
    tiles=parse_tiles("12233466m111z"),
    furo=[Furo.parse("789p")],
    agari=Tile.by_text("1z"),
    tsumo=True,
    dora=4,
    self_wind=Wind.east,
    round_wind=Wind.east
)

# hora.yaku == {self_wind, round_wind}
# hora.han == 6
# hora.hu == 30
# hora.parent_point == (18000, 6000)
# hora.child_point == (12000, 6000, 3000)

指定绑定方式

默认情况下,py版本内部通过py-mini-racer调用mahjong-utils-entry的Kotlin/JS产物,其计算耗时约为JVM产物的2.1倍。

(为什么不是Kotlin/Native产物,因为实测下来Native产物的计算耗时约为JVM产物的7.6倍)

如果希望能够达到JVM产物的计算速度,可以选择切换绑定方式为调用mahjong-utils-webapi提供的HTTP接口,具体步骤如下:

  1. 确保已经安装Java (>= 11)
  2. 从Release页下载与python库版本相对应的mahjong-utils-webapi-all.jar
  3. 运行命令 java -jar mahjong-utils-webapi-all.jar(默认占用8080端口,如果需要指定端口:PORT=10021 java -jar mahjong-utils-webapi-all.jar)
  4. 启动python解释器前设置环境变量ENV_MAHJONG_UTILS_BRIDGE=http(如果需要指定端口与地址:ENV_MAHJONG_UTILS_HTTP_PORT=10021;ENV_MAHJONG_UTILS_HTTP_HOST=127.0.0.1)

Metadata

Release files for mahjong-utils 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mahjong-utils 0.8.0
File Size Uploaded Provenance Actions
mahjong_utils-0.8.0.tar.gz 217.3 kB With provenance Details for mahjong_utils-0.8.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mahjong-utils 0.8.0
File Interpreter ABI Platform Actions
mahjong_utils-0.8.0-py3-none-any.whl Python 3 none any Details for mahjong_utils-0.8.0-py3-none-any.whl

Total release size: 485.7 kB

Release files / mahjong_utils-0.8.0.tar.gz

Download URL mahjong_utils-0.8.0.tar.gz
Size 217.3 kB
Tags Source
SHA-256 checksum
How to use checksums
dda7158d82f9fe854411d74f6cc1b4436b866bc022cdfb14c8256dea97b897e5
BLAKE2b-256 checksum
How to use checksums
570eccfb8bc7b4fa319a2572cf46a103efd1cedda55ba83b95494225803bb682
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mahjong_utils-0.8.0-py3-none-any.whl

Download URL mahjong_utils-0.8.0-py3-none-any.whl
Size 268.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16afe58ee96781f6f22db67af17d683f5ce9bbafcefd19cdfa5518b74d179273
BLAKE2b-256 checksum
How to use checksums
3dd0d6b479e514e28eb322577c4f1968602c219879c22384c63a3d8de8ae4ecf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for mahjong-utils 0.8.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mahjong_utils-0.8.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page