mahjong 2.0.0.dev1__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/LICENSE.txt +1 -1
- mahjong-2.1.0/MANIFEST.in +2 -0
- {mahjong-2.0.0.dev1/mahjong.egg-info → mahjong-2.1.0}/PKG-INFO +21 -9
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/README.md +13 -7
- mahjong-2.1.0/mahjong/__init__.py +106 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/agari.py +28 -4
- mahjong-2.1.0/mahjong/constants.py +50 -0
- mahjong-2.1.0/mahjong/hand_calculating/__init__.py +23 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/divider.py +69 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/fu.py +109 -8
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/hand.py +149 -29
- mahjong-2.1.0/mahjong/hand_calculating/hand_config.py +345 -0
- mahjong-2.1.0/mahjong/hand_calculating/hand_response.py +125 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/scores.py +133 -41
- mahjong-2.1.0/mahjong/hand_calculating/yaku.py +87 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_config.py +110 -55
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/__init__.py +58 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/aka_dora.py +3 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/chankan.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/chantai.py +4 -2
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/chiitoitsu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/chinitsu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/chun.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/daburu_open_riichi.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/daburu_riichi.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/dora.py +4 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/haitei.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/haku.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/hatsu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/honitsu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/honroto.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/houtei.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/iipeiko.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/ippatsu.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/ittsu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/junchan.py +4 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/nagashi_mangan.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/open_riichi.py +3 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/pinfu.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/renhou.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/riichi.py +3 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/rinshan.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/round_wind_east.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/round_wind_north.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/round_wind_south.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/round_wind_west.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/ryanpeiko.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/sanankou.py +10 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/sankantsu.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/sanshoku.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/sanshoku_douko.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/seat_wind_east.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/seat_wind_north.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/seat_wind_south.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/seat_wind_west.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/shosangen.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/tanyao.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/toitoi.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/tsumo.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/ura_dora.py +4 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/__init__.py +32 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/chiihou.py +3 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/chinroto.py +3 -5
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/chuuren_poutou.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daburu_chuuren_poutou.py +3 -1
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daburu_kokushi.py +3 -1
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daichisei.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daisangen.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daisharin.py +18 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/daisuushi.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/kokushi.py +10 -2
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/paarenchan.py +4 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/renhou_yakuman.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/ryuisou.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/sashikomi.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/shosuushi.py +2 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/suuankou.py +9 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/suuankou_tanki.py +3 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/suukantsu.py +8 -3
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/tenhou.py +2 -4
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/hand_calculating/yaku_list/yakuman/tsuisou.py +2 -8
- mahjong-2.1.0/mahjong/meld.py +147 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/shanten.py +157 -24
- mahjong-2.1.0/mahjong/tile.py +296 -0
- mahjong-2.1.0/mahjong/utils.py +677 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0/mahjong.egg-info}/PKG-INFO +21 -9
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/pyproject.toml +47 -8
- mahjong-2.0.0.dev1/MANIFEST.in +0 -2
- mahjong-2.0.0.dev1/mahjong/__init__.py +0 -0
- mahjong-2.0.0.dev1/mahjong/constants.py +0 -24
- mahjong-2.0.0.dev1/mahjong/hand_calculating/__init__.py +0 -0
- mahjong-2.0.0.dev1/mahjong/hand_calculating/hand_config.py +0 -146
- mahjong-2.0.0.dev1/mahjong/hand_calculating/hand_response.py +0 -54
- mahjong-2.0.0.dev1/mahjong/hand_calculating/yaku.py +0 -27
- mahjong-2.0.0.dev1/mahjong/meld.py +0 -52
- mahjong-2.0.0.dev1/mahjong/tile.py +0 -195
- mahjong-2.0.0.dev1/mahjong/utils.py +0 -341
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong/py.typed +0 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong.egg-info/SOURCES.txt +0 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong.egg-info/dependency_links.txt +0 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/mahjong.egg-info/top_level.txt +0 -0
- {mahjong-2.0.0.dev1 → mahjong-2.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2017 mahjong Python library contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mahjong
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Mahjong hands calculation
|
|
5
5
|
Author-email: Alexey Lisikhin <alexey@nihisil.com>
|
|
6
6
|
Maintainer: Apricot S.
|
|
7
7
|
License-Expression: MIT
|
|
8
8
|
Project-URL: Homepage, https://github.com/MahjongRepository/mahjong
|
|
9
|
+
Project-URL: Documentation, https://mahjongrepository.github.io/mahjong
|
|
9
10
|
Project-URL: Wiki, https://github.com/MahjongRepository/mahjong/wiki
|
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
22
|
+
Classifier: Topic :: Games/Entertainment :: Board Games
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
19
25
|
Requires-Python: >=3.10
|
|
20
26
|
Description-Content-Type: text/markdown
|
|
21
27
|
License-File: LICENSE.txt
|
|
@@ -28,25 +34,27 @@ Dynamic: license-file
|
|
|
28
34
|
[](https://pypi.python.org/pypi/mahjong)
|
|
29
35
|
[](https://pypi.org/project/mahjong/)
|
|
30
36
|
[](https://github.com/MahjongRepository/mahjong/actions/workflows/lint_and_test.yml)
|
|
37
|
+
[](https://mahjongrepository.github.io/mahjong)
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Japanese riichi mahjong hand calculator library.
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
Compute hand cost (han, fu, yaku, and scores), validate winning hands, and calculate shanten (minimum tiles to tenpai).
|
|
35
42
|
|
|
36
|
-
The code was validated on tenhou.net phoenix replays
|
|
43
|
+
The code was validated on tenhou.net phoenix replays: **26,148,050** four-player and **8,406,421** three-player winning hands.
|
|
37
44
|
|
|
38
45
|
So, we can say that our hand calculator works the same way that tenhou.net hand calculation.
|
|
39
46
|
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
- [API reference](https://mahjongrepository.github.io/mahjong)
|
|
50
|
+
- [Wiki](https://github.com/MahjongRepository/mahjong/wiki) — Supported rules and usage examples
|
|
51
|
+
|
|
40
52
|
## How to install
|
|
41
53
|
|
|
42
54
|
```bash
|
|
43
55
|
pip install mahjong
|
|
44
56
|
```
|
|
45
57
|
|
|
46
|
-
## Supported rules and usage examples
|
|
47
|
-
|
|
48
|
-
You can find usage examples and information about all supported rules variations in the [wiki](https://github.com/MahjongRepository/mahjong/wiki)
|
|
49
|
-
|
|
50
58
|
## Local development setup
|
|
51
59
|
|
|
52
60
|
To set up the project locally for development:
|
|
@@ -77,3 +85,7 @@ To set up the project locally for development:
|
|
|
77
85
|
```bash
|
|
78
86
|
make check # Runs format, lint, and tests
|
|
79
87
|
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
This library is licensed under the [MIT license](LICENSE.txt).
|
|
@@ -5,25 +5,27 @@
|
|
|
5
5
|
[](https://pypi.python.org/pypi/mahjong)
|
|
6
6
|
[](https://pypi.org/project/mahjong/)
|
|
7
7
|
[](https://github.com/MahjongRepository/mahjong/actions/workflows/lint_and_test.yml)
|
|
8
|
+
[](https://mahjongrepository.github.io/mahjong)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Japanese riichi mahjong hand calculator library.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Compute hand cost (han, fu, yaku, and scores), validate winning hands, and calculate shanten (minimum tiles to tenpai).
|
|
12
13
|
|
|
13
|
-
The code was validated on tenhou.net phoenix replays
|
|
14
|
+
The code was validated on tenhou.net phoenix replays: **26,148,050** four-player and **8,406,421** three-player winning hands.
|
|
14
15
|
|
|
15
16
|
So, we can say that our hand calculator works the same way that tenhou.net hand calculation.
|
|
16
17
|
|
|
18
|
+
## Documentation
|
|
19
|
+
|
|
20
|
+
- [API reference](https://mahjongrepository.github.io/mahjong)
|
|
21
|
+
- [Wiki](https://github.com/MahjongRepository/mahjong/wiki) — Supported rules and usage examples
|
|
22
|
+
|
|
17
23
|
## How to install
|
|
18
24
|
|
|
19
25
|
```bash
|
|
20
26
|
pip install mahjong
|
|
21
27
|
```
|
|
22
28
|
|
|
23
|
-
## Supported rules and usage examples
|
|
24
|
-
|
|
25
|
-
You can find usage examples and information about all supported rules variations in the [wiki](https://github.com/MahjongRepository/mahjong/wiki)
|
|
26
|
-
|
|
27
29
|
## Local development setup
|
|
28
30
|
|
|
29
31
|
To set up the project locally for development:
|
|
@@ -54,3 +56,7 @@ To set up the project locally for development:
|
|
|
54
56
|
```bash
|
|
55
57
|
make check # Runs format, lint, and tests
|
|
56
58
|
```
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
This library is licensed under the [MIT license](LICENSE.txt).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Japanese riichi mahjong hand calculator library.
|
|
3
|
+
|
|
4
|
+
Compute hand cost (han, fu, yaku, and scores), validate winning hands, and calculate shanten (minimum tiles to tenpai).
|
|
5
|
+
|
|
6
|
+
.. rubric:: Main entry points
|
|
7
|
+
|
|
8
|
+
* :class:`~mahjong.hand_calculating.hand.HandCalculator` — estimate hand value (han, fu, cost,
|
|
9
|
+
and yaku list) given tiles, win tile, melds, and dora indicators.
|
|
10
|
+
* :class:`~mahjong.agari.Agari` — check whether tiles form a complete (winning) hand structure.
|
|
11
|
+
* :class:`~mahjong.shanten.Shanten` — calculate how many tiles away a hand is from winning.
|
|
12
|
+
|
|
13
|
+
.. rubric:: Tile and meld utilities
|
|
14
|
+
|
|
15
|
+
* :class:`~mahjong.tile.TilesConverter` — convert between mpsz-notation strings, 34-format count
|
|
16
|
+
arrays, and 136-format tile index lists.
|
|
17
|
+
* :class:`~mahjong.meld.Meld` — represent declared melds (chi, pon, kan).
|
|
18
|
+
|
|
19
|
+
.. rubric:: Configuration and results
|
|
20
|
+
|
|
21
|
+
* :class:`~mahjong.hand_calculating.hand_config.HandConfig` — configure win conditions (tsumo/ron),
|
|
22
|
+
wind context, and optional rule variants (open tanyao, aka dora, double yakuman, etc.).
|
|
23
|
+
* :class:`~mahjong.hand_calculating.hand_response.HandResponse` — structured result containing
|
|
24
|
+
han, fu, fu details, cost breakdown, and yaku list.
|
|
25
|
+
|
|
26
|
+
Tile representation
|
|
27
|
+
###################
|
|
28
|
+
|
|
29
|
+
mpsz-notation
|
|
30
|
+
-------------
|
|
31
|
+
|
|
32
|
+
The *mpsz-notation* is the same format used by the Tenhou (see https://tenhou.net/2/), where each tile is
|
|
33
|
+
represented by a two-character string: a digit for the rank followed by a letter for the suit.
|
|
34
|
+
|
|
35
|
+
The suit letters correspond to:
|
|
36
|
+
|
|
37
|
+
* `m` - manzu (萬子, characters)
|
|
38
|
+
* `p` - pinzu (筒子, circles)
|
|
39
|
+
* `s` - souzu (索子, bamboo)
|
|
40
|
+
* `z` - jihai (字牌, honors)
|
|
41
|
+
|
|
42
|
+
Within the numbered suits (`m`, `p`, `s`), ranks run from `1` through `9`; the red fives are
|
|
43
|
+
written with rank `0` (e.g., `0m`, `0p`, `0s`).
|
|
44
|
+
|
|
45
|
+
Honors (`z`) use ranks to distinguish individual tiles:
|
|
46
|
+
|
|
47
|
+
* `1` - East (東)
|
|
48
|
+
* `2` - South (南)
|
|
49
|
+
* `3` - West (西)
|
|
50
|
+
* `4` - North (北)
|
|
51
|
+
* `5` - White dragon (白, haku)
|
|
52
|
+
* `6` - Green dragon (發, hatsu)
|
|
53
|
+
* `7` - Red dragon (中, chun)
|
|
54
|
+
|
|
55
|
+
Multiple tiles may be concatenated without separators to form a string that represents a collection
|
|
56
|
+
(e.g., ``123m406p789s11555z`` is equivalent to ``1m2m3m4p0p6p7s8s9s1z1z5z5z5z``).
|
|
57
|
+
|
|
58
|
+
34-format
|
|
59
|
+
---------
|
|
60
|
+
|
|
61
|
+
In the *34-format*, an entry is assigned to each of the 34 tile types.
|
|
62
|
+
It does **not** distinguish a red five from a normal five - both map to the same entry.
|
|
63
|
+
The format is commonly used in two ways:
|
|
64
|
+
|
|
65
|
+
* A sequence of length 34 where each entry represents the count of the corresponding tile type.
|
|
66
|
+
* A list of tile-type indices, containing one element per tile rather than counts.
|
|
67
|
+
For example, a pon of 1m is represented as ``[0, 0, 0]``.
|
|
68
|
+
|
|
69
|
+
The correspondence between the index and the tile is shown in the table below.
|
|
70
|
+
|
|
71
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
72
|
+
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
|
73
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
74
|
+
| Tile | 1m | 2m | 3m | 4m | 5m | 6m | 7m | 8m | 9m |
|
|
75
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
76
|
+
|
|
77
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
78
|
+
| Index | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|
|
79
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
80
|
+
| Tile | 1p | 2p | 3p | 4p | 5p | 6p | 7p | 8p | 9p |
|
|
81
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
82
|
+
|
|
83
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
84
|
+
| Index | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
|
|
85
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
86
|
+
| Tile | 1s | 2s | 3s | 4s | 5s | 6s | 7s | 8s | 9s |
|
|
87
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
88
|
+
|
|
89
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
90
|
+
| Index | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
|
|
91
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
92
|
+
| Tile | East (1z) | South (2z) | West (3z) | North (4z) | White (5z) | Green (6z) | Red (7z) |
|
|
93
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
94
|
+
|
|
95
|
+
136-format
|
|
96
|
+
----------
|
|
97
|
+
|
|
98
|
+
In the *136-format*, each physical tile (four copies of each of the 34 tile types) is assigned a unique integer.
|
|
99
|
+
It is primarily used when treating tiles as individual objects rather than counts, such as melds.
|
|
100
|
+
|
|
101
|
+
The integers range from 0 to 135.
|
|
102
|
+
IDs are assigned in groups of four: the four copies of 1m receive `0`, `1`, `2`, and `3`;
|
|
103
|
+
the four copies of 2m receive `4`, `5`, `6`, and `7`;
|
|
104
|
+
and so on, up to the four copies of red dragon (7z), which receive `132`, `133`, `134`, and `135`.
|
|
105
|
+
Red fives are assigned the indices `16` (`0m`), `52` (`0p`), and `88` (`0s`).
|
|
106
|
+
"""
|
|
@@ -2,13 +2,37 @@ from collections.abc import Collection, Sequence
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Agari:
|
|
5
|
+
"""Agari (winning hand) detection."""
|
|
6
|
+
|
|
5
7
|
@staticmethod
|
|
6
8
|
def is_agari(tiles_34: Sequence[int], open_sets_34: Collection[Sequence[int]] | None = None) -> bool:
|
|
7
9
|
"""
|
|
8
|
-
Determine whether the
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Determine whether the given tiles form a complete hand structure.
|
|
11
|
+
|
|
12
|
+
Validate the tile arrangement only (4 melds + 1 pair, seven pairs, or thirteen orphans),
|
|
13
|
+
not yaku or scoring. When open sets are provided, the declared meld tiles are subtracted
|
|
14
|
+
before checking.
|
|
15
|
+
|
|
16
|
+
The concealed portion can be passed alone without melds, which is valid
|
|
17
|
+
for any number of melds (0-4) plus 1 pair:
|
|
18
|
+
|
|
19
|
+
0 melds + 1 pair (valid hand):
|
|
20
|
+
|
|
21
|
+
>>> tiles_34 = [0] * 34
|
|
22
|
+
>>> tiles_34[0] = 2
|
|
23
|
+
>>> Agari.is_agari(tiles_34)
|
|
24
|
+
True
|
|
25
|
+
|
|
26
|
+
1 meld (triplet) without a pair is not a valid hand:
|
|
27
|
+
|
|
28
|
+
>>> tiles_34 = [0] * 34
|
|
29
|
+
>>> tiles_34[0] = 3
|
|
30
|
+
>>> Agari.is_agari(tiles_34)
|
|
31
|
+
False
|
|
32
|
+
|
|
33
|
+
:param tiles_34: hand in 34-format count array (length 34)
|
|
34
|
+
:param open_sets_34: declared melds as arrays of tile indices in 34-format
|
|
35
|
+
:return: True if the hand is complete
|
|
12
36
|
"""
|
|
13
37
|
if open_sets_34:
|
|
14
38
|
# subtract declared meld tiles so the algorithm checks only the closed portion
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
TERMINAL_INDICES = frozenset([0, 8, 9, 17, 18, 26])
|
|
2
|
+
"""Tile indices (34-format) of terminal tiles — 1 and 9 of each suit (man, pin, sou)."""
|
|
3
|
+
|
|
4
|
+
EAST = 27
|
|
5
|
+
"""Tile index (34-format) for East wind."""
|
|
6
|
+
|
|
7
|
+
SOUTH = 28
|
|
8
|
+
"""Tile index (34-format) for South wind."""
|
|
9
|
+
|
|
10
|
+
WEST = 29
|
|
11
|
+
"""Tile index (34-format) for West wind."""
|
|
12
|
+
|
|
13
|
+
NORTH = 30
|
|
14
|
+
"""Tile index (34-format) for North wind."""
|
|
15
|
+
|
|
16
|
+
HAKU = 31
|
|
17
|
+
"""Tile index (34-format) for Haku (white dragon)."""
|
|
18
|
+
|
|
19
|
+
HATSU = 32
|
|
20
|
+
"""Tile index (34-format) for Hatsu (green dragon)."""
|
|
21
|
+
|
|
22
|
+
CHUN = 33
|
|
23
|
+
"""Tile index (34-format) for Chun (red dragon)."""
|
|
24
|
+
|
|
25
|
+
WINDS = frozenset([EAST, SOUTH, WEST, NORTH])
|
|
26
|
+
"""Tile indices (34-format) of all four wind tiles."""
|
|
27
|
+
|
|
28
|
+
DRAGONS = frozenset([HAKU, HATSU, CHUN])
|
|
29
|
+
"""Tile indices (34-format) of all three dragon tiles."""
|
|
30
|
+
|
|
31
|
+
HONOR_INDICES = WINDS | DRAGONS
|
|
32
|
+
"""Tile indices (34-format) of all honor tiles — winds and dragons."""
|
|
33
|
+
|
|
34
|
+
TERMINAL_AND_HONOR_INDICES = TERMINAL_INDICES | HONOR_INDICES
|
|
35
|
+
"""Tile indices (34-format) of all terminal and honor tiles."""
|
|
36
|
+
|
|
37
|
+
FIVE_RED_MAN = 16
|
|
38
|
+
"""Tile index (136-format) for the red five of man (characters)."""
|
|
39
|
+
|
|
40
|
+
FIVE_RED_PIN = 52
|
|
41
|
+
"""Tile index (136-format) for the red five of pin (circles)."""
|
|
42
|
+
|
|
43
|
+
FIVE_RED_SOU = 88
|
|
44
|
+
"""Tile index (136-format) for the red five of sou (bamboo)."""
|
|
45
|
+
|
|
46
|
+
AKA_DORAS = frozenset([FIVE_RED_MAN, FIVE_RED_PIN, FIVE_RED_SOU])
|
|
47
|
+
"""Tile indices (136-format) of all three red five (aka dora) tiles."""
|
|
48
|
+
|
|
49
|
+
DISPLAY_WINDS = {EAST: "East", SOUTH: "South", WEST: "West", NORTH: "North"}
|
|
50
|
+
"""Mapping from wind tile indices (34-format) to their English display names."""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hand value calculation pipeline.
|
|
3
|
+
|
|
4
|
+
This subpackage evaluates a winning hand and produces its han, fu, yaku list,
|
|
5
|
+
and payment amounts. The calculation flows through several stages:
|
|
6
|
+
|
|
7
|
+
1. :class:`~mahjong.hand_calculating.divider.HandDivider` decomposes the hand into all
|
|
8
|
+
valid block combinations (4 melds + 1 pair, or 7 pairs for chiitoitsu).
|
|
9
|
+
2. :class:`~mahjong.hand_calculating.yaku.Yaku` subclasses test each decomposition for
|
|
10
|
+
matching yaku patterns (e.g., tanyao, pinfu, honitsu).
|
|
11
|
+
3. :class:`~mahjong.hand_calculating.fu.FuCalculator` computes minipoints from the meld
|
|
12
|
+
structure, wait type, pair, and winning method.
|
|
13
|
+
4. :class:`~mahjong.hand_calculating.scores.ScoresCalculator` converts han and fu into
|
|
14
|
+
payment amounts, accounting for honba and kyoutaku bonuses.
|
|
15
|
+
|
|
16
|
+
:class:`~mahjong.hand_calculating.hand.HandCalculator` orchestrates the full pipeline and
|
|
17
|
+
returns a :class:`~mahjong.hand_calculating.hand_response.HandResponse` with the
|
|
18
|
+
highest-scoring result.
|
|
19
|
+
|
|
20
|
+
Behavior is controlled by :class:`~mahjong.hand_calculating.hand_config.HandConfig`, which
|
|
21
|
+
specifies win conditions (tsumo/ron), wind context, and optional rule variants such as
|
|
22
|
+
open tanyao, aka dora, and double yakuman.
|
|
23
|
+
"""
|
|
@@ -50,16 +50,81 @@ _Blocks = tuple[_Block, ...]
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class HandDivider:
|
|
53
|
+
"""
|
|
54
|
+
Decompose a winning hand into all possible block combinations.
|
|
55
|
+
|
|
56
|
+
A valid combination consists of 4 melds (sequences, triplets, or quads) plus 1 pair.
|
|
57
|
+
Chiitoitsu (seven pairs) is also recognized when no melds are declared.
|
|
58
|
+
Multiple valid decompositions may exist for the same hand.
|
|
59
|
+
|
|
60
|
+
Each decomposition is a list of blocks, where each block is a list of tile indices
|
|
61
|
+
in 34-format:
|
|
62
|
+
|
|
63
|
+
- pair: ``[tile, tile]`` (length 2, identical tiles)
|
|
64
|
+
- triplet: ``[tile, tile, tile]`` (length 3, identical tiles)
|
|
65
|
+
- sequence: ``[tile, tile+1, tile+2]`` (length 3, consecutive tiles)
|
|
66
|
+
- quad: ``[tile, tile, tile, tile]`` (length 4, identical tiles)
|
|
67
|
+
"""
|
|
68
|
+
|
|
53
69
|
@staticmethod
|
|
54
70
|
def divide_hand(
|
|
55
71
|
tiles_34: Sequence[int],
|
|
56
72
|
melds: Collection[Meld] | None = None,
|
|
57
73
|
) -> list[list[list[int]]]:
|
|
58
74
|
"""
|
|
59
|
-
Return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
Return all valid decompositions of the hand into blocks.
|
|
76
|
+
|
|
77
|
+
Decompose the full hand (including any declared melds) into every possible
|
|
78
|
+
combination of 4 melds + 1 pair, or 7 pairs (chiitoitsu, only when no melds
|
|
79
|
+
are declared).
|
|
80
|
+
|
|
81
|
+
Single decomposition (one triplet, three sequences, and a pair):
|
|
82
|
+
|
|
83
|
+
>>> from mahjong.hand_calculating.divider import HandDivider
|
|
84
|
+
>>> from mahjong.tile import TilesConverter
|
|
85
|
+
>>> tiles_34 = TilesConverter.one_line_string_to_34_array("234567m23455s777z")
|
|
86
|
+
>>> result = HandDivider.divide_hand(tiles_34)
|
|
87
|
+
>>> result
|
|
88
|
+
[[[1, 2, 3], [4, 5, 6], [19, 20, 21], [22, 22], [33, 33, 33]]]
|
|
89
|
+
|
|
90
|
+
Multiple decompositions (triplets vs. sequences):
|
|
91
|
+
|
|
92
|
+
>>> tiles_34 = TilesConverter.one_line_string_to_34_array("11122233388899m")
|
|
93
|
+
>>> result = HandDivider.divide_hand(tiles_34)
|
|
94
|
+
>>> result
|
|
95
|
+
[[[0, 0, 0], [1, 1, 1], [2, 2, 2], [7, 7, 7], [8, 8]], [[0, 1, 2], [0, 1, 2], [0, 1, 2], [7, 7, 7], [8, 8]]]
|
|
96
|
+
|
|
97
|
+
Ryanpeikou form (decomposes into both 4 melds + 1 pair and chiitoitsu):
|
|
98
|
+
|
|
99
|
+
>>> tiles_34 = TilesConverter.one_line_string_to_34_array("11223344556677m")
|
|
100
|
+
>>> result = HandDivider.divide_hand(tiles_34)
|
|
101
|
+
>>> len(result)
|
|
102
|
+
4
|
|
103
|
+
>>> [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6]] in result
|
|
104
|
+
True
|
|
105
|
+
>>> [[0, 1, 2], [0, 1, 2], [3, 4, 5], [3, 4, 5], [6, 6]] in result
|
|
106
|
+
True
|
|
107
|
+
|
|
108
|
+
Hands with declared melds:
|
|
109
|
+
|
|
110
|
+
>>> from mahjong.meld import Meld
|
|
111
|
+
>>> tiles_34 = TilesConverter.string_to_34_array(pin="234777888999", honors="22")
|
|
112
|
+
>>> chi_1 = Meld(meld_type=Meld.CHI, tiles=TilesConverter.string_to_136_array(pin="789"))
|
|
113
|
+
>>> chi_2 = Meld(meld_type=Meld.CHI, tiles=TilesConverter.string_to_136_array(pin="234"))
|
|
114
|
+
>>> result = HandDivider.divide_hand(tiles_34, [chi_1, chi_2])
|
|
115
|
+
>>> result
|
|
116
|
+
[[[10, 11, 12], [15, 16, 17], [15, 16, 17], [15, 16, 17], [28, 28]]]
|
|
117
|
+
|
|
118
|
+
Invalid hands return an empty list:
|
|
119
|
+
|
|
120
|
+
>>> HandDivider.divide_hand([5] * 34)
|
|
121
|
+
[]
|
|
122
|
+
|
|
123
|
+
:param tiles_34: hand in 34-format count array (length 34), including tiles from melds
|
|
124
|
+
:param melds: declared melds (:class:`~mahjong.meld.Meld` objects); their tiles are
|
|
125
|
+
subtracted from ``tiles_34`` before decomposing the concealed portion
|
|
126
|
+
:return: list of decompositions, where each decomposition is a list of blocks
|
|
127
|
+
(each block is a list of tile indices in 34-format)
|
|
63
128
|
"""
|
|
64
129
|
if not HandDivider._validate_tiles(tiles_34):
|
|
65
130
|
return []
|
|
@@ -7,6 +7,16 @@ from mahjong.meld import Meld
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class FuDetail(TypedDict):
|
|
10
|
+
"""
|
|
11
|
+
A single fu component with its point value and reason.
|
|
12
|
+
|
|
13
|
+
Each entry in the fu breakdown returned by :meth:`FuCalculator.calculate_fu`
|
|
14
|
+
describes one source of fu (e.g., base fu, a closed pon, or a wait type).
|
|
15
|
+
|
|
16
|
+
:param fu: fu points awarded for this component
|
|
17
|
+
:param reason: identifier string matching one of the :class:`FuCalculator` reason constants
|
|
18
|
+
"""
|
|
19
|
+
|
|
10
20
|
fu: int
|
|
11
21
|
reason: str
|
|
12
22
|
|
|
@@ -20,26 +30,61 @@ _IS_KAN = 2
|
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
class FuCalculator:
|
|
33
|
+
"""
|
|
34
|
+
Calculate fu (minipoints) for a winning hand decomposition.
|
|
35
|
+
|
|
36
|
+
Fu are computed from the hand's meld structure, wait type, pair, and winning method.
|
|
37
|
+
The result is a detailed breakdown of fu components and the total fu rounded up to
|
|
38
|
+
the nearest 10.
|
|
39
|
+
"""
|
|
40
|
+
|
|
23
41
|
BASE = "base"
|
|
42
|
+
"""Base fu: 20 for open/tsumo, 30 for closed ron, 25 for chiitoitsu."""
|
|
43
|
+
|
|
24
44
|
PENCHAN = "penchan"
|
|
45
|
+
"""Penchan (edge wait): 2 fu for completing 1-2-3 with the 3, or 7-8-9 with the 7."""
|
|
46
|
+
|
|
25
47
|
KANCHAN = "kanchan"
|
|
48
|
+
"""Kanchan (closed wait): 2 fu for waiting on the middle tile of a sequence."""
|
|
49
|
+
|
|
26
50
|
VALUED_PAIR = "valued_pair"
|
|
51
|
+
"""Valued pair: 2 fu for a pair of dragon, seat wind, or round wind tiles."""
|
|
52
|
+
|
|
27
53
|
DOUBLE_VALUED_PAIR = "double_valued_pair"
|
|
54
|
+
"""Double valued pair: 4 fu when the pair tile is both seat wind and round wind."""
|
|
55
|
+
|
|
28
56
|
PAIR_WAIT = "pair_wait"
|
|
57
|
+
"""Pair wait: 2 fu for winning on the pair tile."""
|
|
58
|
+
|
|
29
59
|
TSUMO = "tsumo"
|
|
60
|
+
"""Tsumo: 2 fu for winning by self-draw."""
|
|
61
|
+
|
|
30
62
|
HAND_WITHOUT_FU = "hand_without_fu"
|
|
63
|
+
"""Open pinfu: 2 fu for an open hand with no other fu sources."""
|
|
31
64
|
|
|
32
65
|
CLOSED_PON = "closed_pon"
|
|
66
|
+
"""Closed pon of simple tiles: 4 fu."""
|
|
67
|
+
|
|
33
68
|
OPEN_PON = "open_pon"
|
|
69
|
+
"""Open pon of simple tiles: 2 fu."""
|
|
34
70
|
|
|
35
71
|
CLOSED_TERMINAL_PON = "closed_terminal_pon"
|
|
72
|
+
"""Closed pon of terminal or honor tiles: 8 fu."""
|
|
73
|
+
|
|
36
74
|
OPEN_TERMINAL_PON = "open_terminal_pon"
|
|
75
|
+
"""Open pon of terminal or honor tiles: 4 fu."""
|
|
37
76
|
|
|
38
77
|
CLOSED_KAN = "closed_kan"
|
|
78
|
+
"""Closed kan of simple tiles: 16 fu."""
|
|
79
|
+
|
|
39
80
|
OPEN_KAN = "open_kan"
|
|
81
|
+
"""Open kan of simple tiles: 8 fu."""
|
|
40
82
|
|
|
41
83
|
CLOSED_TERMINAL_KAN = "closed_terminal_kan"
|
|
84
|
+
"""Closed kan of terminal or honor tiles: 32 fu."""
|
|
85
|
+
|
|
42
86
|
OPEN_TERMINAL_KAN = "open_terminal_kan"
|
|
87
|
+
"""Open kan of terminal or honor tiles: 16 fu."""
|
|
43
88
|
|
|
44
89
|
# lookup table indexed by [is_terminal_or_honor][is_kan][is_open] -> (fu, reason)
|
|
45
90
|
_PON_KAN_FU_TABLE = (
|
|
@@ -63,14 +108,70 @@ class FuCalculator:
|
|
|
63
108
|
melds: Collection[Meld] | None = None,
|
|
64
109
|
) -> tuple[list[FuDetail], int]:
|
|
65
110
|
"""
|
|
66
|
-
Calculate
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
Calculate fu for a winning hand decomposition.
|
|
112
|
+
|
|
113
|
+
Analyze the hand's meld structure, wait type, pair, and winning method to produce
|
|
114
|
+
a detailed breakdown of fu components and the total fu rounded up to the nearest 10.
|
|
115
|
+
|
|
116
|
+
Chiitoitsu (seven pairs) always receives 25 fu:
|
|
117
|
+
|
|
118
|
+
>>> from mahjong.hand_calculating.fu import FuCalculator
|
|
119
|
+
>>> from mahjong.hand_calculating.hand_config import HandConfig
|
|
120
|
+
>>> hand = [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6]]
|
|
121
|
+
>>> win_tile = 24
|
|
122
|
+
>>> win_group = [6, 6]
|
|
123
|
+
>>> fu_details, fu = FuCalculator.calculate_fu(hand, win_tile, win_group, HandConfig())
|
|
124
|
+
>>> fu
|
|
125
|
+
25
|
|
126
|
+
|
|
127
|
+
A closed ron hand with only chi melds and a non-valued pair (pinfu) receives 30 fu:
|
|
128
|
+
|
|
129
|
+
>>> hand = [[0, 1, 2], [3, 4, 5], [9, 10, 11], [18, 19, 20], [27, 27]]
|
|
130
|
+
>>> win_tile = 20
|
|
131
|
+
>>> win_group = [3, 4, 5]
|
|
132
|
+
>>> fu_details, fu = FuCalculator.calculate_fu(hand, win_tile, win_group, HandConfig())
|
|
133
|
+
>>> fu
|
|
134
|
+
30
|
|
135
|
+
|
|
136
|
+
A closed pon of simple tiles adds 4 fu:
|
|
137
|
+
|
|
138
|
+
>>> hand = [[2, 2, 2], [3, 4, 5], [9, 10, 11], [18, 19, 20], [27, 27]]
|
|
139
|
+
>>> win_tile = 20
|
|
140
|
+
>>> win_group = [3, 4, 5]
|
|
141
|
+
>>> fu_details, fu = FuCalculator.calculate_fu(hand, win_tile, win_group, HandConfig())
|
|
142
|
+
>>> fu
|
|
143
|
+
40
|
|
144
|
+
>>> {"fu": 4, "reason": FuCalculator.CLOSED_PON} in fu_details
|
|
145
|
+
True
|
|
146
|
+
|
|
147
|
+
A double valued pair with an open terminal pon:
|
|
148
|
+
|
|
149
|
+
>>> from mahjong.meld import Meld
|
|
150
|
+
>>> hand = [[2, 3, 4], [9, 10, 11], [18, 19, 20], [33, 33, 33], [27, 27]]
|
|
151
|
+
>>> win_tile = 8
|
|
152
|
+
>>> win_group = [2, 3, 4]
|
|
153
|
+
>>> valued_tiles = [27, 27]
|
|
154
|
+
>>> melds = [Meld(meld_type=Meld.PON, tiles=[132, 133, 134], opened=True)]
|
|
155
|
+
>>> fu_details, fu = FuCalculator.calculate_fu(hand, win_tile, win_group, HandConfig(), valued_tiles, melds)
|
|
156
|
+
>>> fu
|
|
157
|
+
30
|
|
158
|
+
>>> {"fu": 4, "reason": FuCalculator.DOUBLE_VALUED_PAIR} in fu_details
|
|
159
|
+
True
|
|
160
|
+
>>> {"fu": 4, "reason": FuCalculator.OPEN_TERMINAL_PON} in fu_details
|
|
161
|
+
True
|
|
162
|
+
|
|
163
|
+
:param hand: decomposed hand as a collection of tile sets, each a sequence of tile
|
|
164
|
+
indices in 34-format: a pair (length 2), chi (length 3 with consecutive tiles),
|
|
165
|
+
pon (length 3 with identical tiles), or kan (length 4);
|
|
166
|
+
chiitoitsu hands have 7 pairs
|
|
167
|
+
:param win_tile: the winning tile index in 136-format
|
|
168
|
+
:param win_group: the tile set (from ``hand``) that contains the winning tile,
|
|
169
|
+
as tile indices in 34-format
|
|
170
|
+
:param config: hand configuration with win method and optional rule settings
|
|
171
|
+
:param valued_tiles: tile indices in 34-format for tiles that grant pair fu
|
|
172
|
+
(dragons, seat wind, round wind); pass the same index twice for double-valued
|
|
173
|
+
:param melds: declared melds (chi, pon, kan)
|
|
174
|
+
:return: tuple of (fu component list, total fu rounded up to nearest 10)
|
|
74
175
|
"""
|
|
75
176
|
# chiitoitsu: always 25 fu
|
|
76
177
|
if len(hand) == 7:
|