mahjong 1.4.0__tar.gz → 2.0.0.dev2__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-1.4.0 → mahjong-2.0.0.dev2}/LICENSE.txt +1 -1
- mahjong-2.0.0.dev2/MANIFEST.in +2 -0
- {mahjong-1.4.0/mahjong.egg-info → mahjong-2.0.0.dev2}/PKG-INFO +16 -8
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/README.md +11 -5
- mahjong-2.0.0.dev2/mahjong/__init__.py +87 -0
- mahjong-2.0.0.dev2/mahjong/agari.py +226 -0
- mahjong-2.0.0.dev2/mahjong/constants.py +50 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/divider.py +302 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/fu.py +314 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/hand.py +601 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/hand_config.py +40 -42
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/hand_response.py +18 -18
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/scores.py +56 -46
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku.py +27 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_config.py +210 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/__init__.py +55 -6
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/aka_dora.py +20 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/chankan.py +18 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/chantai.py +7 -22
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/chiitoitsu.py +16 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/chinitsu.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/chun.py +19 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/daburu_open_riichi.py +3 -11
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/daburu_riichi.py +17 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/dora.py +16 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/haitei.py +18 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/haku.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/hatsu.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/honitsu.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/honroto.py +20 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/houtei.py +18 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/iipeiko.py +25 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/ippatsu.py +17 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/ittsu.py +47 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/junchan.py +6 -20
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/nagashi_mangan.py +4 -11
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/open_riichi.py +12 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/pinfu.py +16 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/renhou.py +17 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/riichi.py +12 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/rinshan.py +18 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/round_wind_east.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/round_wind_north.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/round_wind_south.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/round_wind_west.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/ryanpeiko.py +27 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/sanankou.py +51 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/sankantsu.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/sanshoku.py +39 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/sanshoku_douko.py +44 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/seat_wind_east.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/seat_wind_north.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/seat_wind_south.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/seat_wind_west.py +21 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/shosangen.py +27 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/tanyao.py +20 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/toitoi.py +19 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/tsumo.py +17 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/ura_dora.py +15 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/__init__.py +23 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/chiihou.py +4 -8
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/chinroto.py +5 -13
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/chuuren_poutou.py +51 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/daburu_chuuren_poutou.py +14 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/daburu_kokushi.py +14 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/daichisei.py +4 -11
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/daisangen.py +9 -15
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/daisharin.py +55 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/daisuushi.py +29 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/kokushi.py +7 -17
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/paarenchan.py +7 -15
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/renhou_yakuman.py +4 -11
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/ryuisou.py +22 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/sashikomi.py +4 -11
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/shosuushi.py +32 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/suuankou.py +5 -14
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/suuankou_tanki.py +13 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/suukantsu.py +20 -0
- mahjong-2.0.0.dev2/mahjong/hand_calculating/yaku_list/yakuman/tenhou.py +18 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/hand_calculating/yaku_list/yakuman/tsuisou.py +5 -13
- mahjong-2.0.0.dev2/mahjong/meld.py +147 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/shanten.py +143 -190
- mahjong-2.0.0.dev2/mahjong/tile.py +294 -0
- mahjong-2.0.0.dev2/mahjong/utils.py +653 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2/mahjong.egg-info}/PKG-INFO +16 -8
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong.egg-info/SOURCES.txt +9 -6
- mahjong-2.0.0.dev2/pyproject.toml +149 -0
- mahjong-1.4.0/MANIFEST.in +0 -2
- mahjong-1.4.0/mahjong/agari.py +0 -211
- mahjong-1.4.0/mahjong/constants.py +0 -22
- mahjong-1.4.0/mahjong/hand_calculating/__init__.py +0 -1
- mahjong-1.4.0/mahjong/hand_calculating/divider.py +0 -258
- mahjong-1.4.0/mahjong/hand_calculating/fu.py +0 -162
- mahjong-1.4.0/mahjong/hand_calculating/hand.py +0 -578
- mahjong-1.4.0/mahjong/hand_calculating/yaku.py +0 -50
- mahjong-1.4.0/mahjong/hand_calculating/yaku_config.py +0 -150
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/aka_dora.py +0 -29
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/chankan.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/chiitoitsu.py +0 -26
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/chinitsu.py +0 -46
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/chun.py +0 -28
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/daburu_riichi.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/dora.py +0 -25
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/east.py +0 -34
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/haitei.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/haku.py +0 -28
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/hatsu.py +0 -28
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/honitsu.py +0 -45
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/honroto.py +0 -30
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/houtei.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/iipeiko.py +0 -38
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/ippatsu.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/ittsu.py +0 -57
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/north.py +0 -34
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/open_riichi.py +0 -20
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/pinfu.py +0 -26
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/renhou.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/riichi.py +0 -22
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/rinshan.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/ryanpeiko.py +0 -36
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/sanankou.py +0 -61
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/sankantsu.py +0 -28
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/sanshoku.py +0 -51
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/sanshoku_douko.py +0 -51
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/shosangen.py +0 -35
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/south.py +0 -34
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/tanyao.py +0 -30
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/toitoi.py +0 -27
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/tsumo.py +0 -26
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/west.py +0 -34
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuhai_place.py +0 -22
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuhai_round.py +0 -22
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/chuuren_poutou.py +0 -71
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/daburu_chuuren_poutou.py +0 -23
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/daburu_kokushi.py +0 -23
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/daisharin.py +0 -80
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/daisuushi.py +0 -43
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/ryuisou.py +0 -30
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/shosuushi.py +0 -42
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/suuankou_tanki.py +0 -22
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/suukantsu.py +0 -28
- mahjong-1.4.0/mahjong/hand_calculating/yaku_list/yakuman/tenhou.py +0 -27
- mahjong-1.4.0/mahjong/meld.py +0 -53
- mahjong-1.4.0/mahjong/tile.py +0 -221
- mahjong-1.4.0/mahjong/utils.py +0 -264
- mahjong-1.4.0/pyproject.toml +0 -74
- {mahjong-1.4.0/mahjong → mahjong-2.0.0.dev2/mahjong/hand_calculating}/__init__.py +0 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong/py.typed +0 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong.egg-info/dependency_links.txt +0 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/mahjong.egg-info/top_level.txt +0 -0
- {mahjong-1.4.0 → mahjong-2.0.0.dev2}/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,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mahjong
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0.dev2
|
|
4
4
|
Summary: Mahjong hands calculation
|
|
5
5
|
Author-email: Alexey Lisikhin <alexey@nihisil.com>
|
|
6
|
+
Maintainer: Apricot S.
|
|
6
7
|
License-Expression: MIT
|
|
7
8
|
Project-URL: Homepage, https://github.com/MahjongRepository/mahjong
|
|
9
|
+
Project-URL: Documentation, https://mahjongrepository.github.io/mahjong
|
|
10
|
+
Project-URL: Wiki, https://github.com/MahjongRepository/mahjong/wiki
|
|
8
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
9
12
|
Classifier: Environment :: Console
|
|
10
13
|
Classifier: Intended Audience :: Developers
|
|
11
14
|
Classifier: Programming Language :: Python
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE.txt
|
|
21
23
|
Dynamic: license-file
|
|
@@ -27,25 +29,27 @@ Dynamic: license-file
|
|
|
27
29
|
[](https://pypi.python.org/pypi/mahjong)
|
|
28
30
|
[](https://pypi.org/project/mahjong/)
|
|
29
31
|
[](https://github.com/MahjongRepository/mahjong/actions/workflows/lint_and_test.yml)
|
|
32
|
+
[](https://mahjongrepository.github.io/mahjong)
|
|
30
33
|
|
|
31
34
|
This library can calculate hand cost (han, fu with details, yaku, and scores) for riichi mahjong (Japanese version).
|
|
32
35
|
|
|
33
36
|
Also calculating of shanten is supported.
|
|
34
37
|
|
|
35
|
-
The code was validated on tenhou.net phoenix replays in total on **
|
|
38
|
+
The code was validated on tenhou.net phoenix replays in total on **26,148,038 hands**.
|
|
36
39
|
|
|
37
40
|
So, we can say that our hand calculator works the same way that tenhou.net hand calculation.
|
|
38
41
|
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
- [API reference](https://mahjongrepository.github.io/mahjong)
|
|
45
|
+
- [Wiki](https://github.com/MahjongRepository/mahjong/wiki) — Supported rules and usage examples
|
|
46
|
+
|
|
39
47
|
## How to install
|
|
40
48
|
|
|
41
49
|
```bash
|
|
42
50
|
pip install mahjong
|
|
43
51
|
```
|
|
44
52
|
|
|
45
|
-
## Supported rules and usage examples
|
|
46
|
-
|
|
47
|
-
You can find usage examples and information about all supported rules variations in the [wiki](https://github.com/MahjongRepository/mahjong/wiki)
|
|
48
|
-
|
|
49
53
|
## Local development setup
|
|
50
54
|
|
|
51
55
|
To set up the project locally for development:
|
|
@@ -76,3 +80,7 @@ To set up the project locally for development:
|
|
|
76
80
|
```bash
|
|
77
81
|
make check # Runs format, lint, and tests
|
|
78
82
|
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
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
|
This library can calculate hand cost (han, fu with details, yaku, and scores) for riichi mahjong (Japanese version).
|
|
10
11
|
|
|
11
12
|
Also calculating of shanten is supported.
|
|
12
13
|
|
|
13
|
-
The code was validated on tenhou.net phoenix replays in total on **
|
|
14
|
+
The code was validated on tenhou.net phoenix replays in total on **26,148,038 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,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
_summary_.
|
|
3
|
+
|
|
4
|
+
Tile representation
|
|
5
|
+
###################
|
|
6
|
+
|
|
7
|
+
mpsz-notation
|
|
8
|
+
-------------
|
|
9
|
+
|
|
10
|
+
The *mpsz-notation* is the same format used by the Tenhou (see https://tenhou.net/2/), where each tile is
|
|
11
|
+
represented by a two-character string: a digit for the rank followed by a letter for the suit.
|
|
12
|
+
|
|
13
|
+
The suit letters correspond to:
|
|
14
|
+
|
|
15
|
+
* `m` - manzu (萬子, characters)
|
|
16
|
+
* `p` - pinzu (筒子, circles)
|
|
17
|
+
* `s` - souzu (索子, bamboo)
|
|
18
|
+
* `z` - jihai (字牌, honors)
|
|
19
|
+
|
|
20
|
+
Within the numbered suits (`m`, `p`, `s`), ranks run from `1` through `9`; the red fives are
|
|
21
|
+
written with rank `0` (e.g., `0m`, `0p`, `0s`).
|
|
22
|
+
|
|
23
|
+
Honors (`z`) use ranks to distinguish individual tiles:
|
|
24
|
+
|
|
25
|
+
* `1` - East (東)
|
|
26
|
+
* `2` - South (南)
|
|
27
|
+
* `3` - West (西)
|
|
28
|
+
* `4` - North (北)
|
|
29
|
+
* `5` - White dragon (白, haku)
|
|
30
|
+
* `6` - Green dragon (發, hatsu)
|
|
31
|
+
* `7` - Red dragon (中, chun)
|
|
32
|
+
|
|
33
|
+
Multiple tiles may be concatenated without separators to form a string that represents a collection. For example:
|
|
34
|
+
|
|
35
|
+
``123m406p789s11555z``
|
|
36
|
+
|
|
37
|
+
is equivalent to ``1m2m3m4p0p6p7s8s9s1z1z5z5z5z``.
|
|
38
|
+
|
|
39
|
+
34-format
|
|
40
|
+
---------
|
|
41
|
+
|
|
42
|
+
In the *34-format*, an entry is assigned to each of the 34 tile types.
|
|
43
|
+
It does **not** distinguish a red five from a normal five - both map to the same entry.
|
|
44
|
+
The format is commonly used in two ways:
|
|
45
|
+
|
|
46
|
+
* A sequence of length 34 where each entry represents the count of the corresponding tile type.
|
|
47
|
+
* A list of tile-type indices, containing one element per tile rather than counts.
|
|
48
|
+
For example, a pon of 1m is represented as ``[0, 0, 0]``.
|
|
49
|
+
|
|
50
|
+
The correspondence between the index and the tile is shown in the table below.
|
|
51
|
+
|
|
52
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
53
|
+
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
|
54
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
55
|
+
| Tile | 1m | 2m | 3m | 4m | 5m | 6m | 7m | 8m | 9m |
|
|
56
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
57
|
+
|
|
58
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
59
|
+
| Index | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|
|
60
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
61
|
+
| Tile | 1p | 2p | 3p | 4p | 5p | 6p | 7p | 8p | 9p |
|
|
62
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
63
|
+
|
|
64
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
65
|
+
| Index | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
|
|
66
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
67
|
+
| Tile | 1s | 2s | 3s | 4s | 5s | 6s | 7s | 8s | 9s |
|
|
68
|
+
+-------+----+----+----+----+----+----+----+----+----+
|
|
69
|
+
|
|
70
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
71
|
+
| Index | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
|
|
72
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
73
|
+
| Tile | East (1z) | South (2z) | West (3z) | North (4z) | White (5z) | Green (6z) | Red (7z) |
|
|
74
|
+
+-------+-----------+------------+-----------+------------+------------+------------+----------+
|
|
75
|
+
|
|
76
|
+
136-format
|
|
77
|
+
----------
|
|
78
|
+
|
|
79
|
+
In the *136-format*, each physical tile (four copies of each of the 34 tile types) is assigned a unique integer.
|
|
80
|
+
It is primarily used when treating tiles as individual objects rather than counts, such as melds.
|
|
81
|
+
|
|
82
|
+
The integers range from 0 to 135.
|
|
83
|
+
IDs are assigned in groups of four: the four copies of 1m receive `0`, `1`, `2`, and `3`;
|
|
84
|
+
the four copies of 2m receive `4`, `5`, `6`, and `7`;
|
|
85
|
+
and so on, up to the four copies of red dragon (7z), which receive `132`, `133`, `134`, and `135`.
|
|
86
|
+
Red fives are assigned the indices `16` (`0m`), `52` (`0p`), and `88` (`0s`).
|
|
87
|
+
"""
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from collections.abc import Collection, Sequence
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Agari:
|
|
5
|
+
"""Agari (winning hand) detection."""
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def is_agari(tiles_34: Sequence[int], open_sets_34: Collection[Sequence[int]] | None = None) -> bool:
|
|
9
|
+
"""
|
|
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
|
|
36
|
+
"""
|
|
37
|
+
if open_sets_34:
|
|
38
|
+
# subtract declared meld tiles so the algorithm checks only the closed portion
|
|
39
|
+
tiles = list(tiles_34)
|
|
40
|
+
for meld in open_sets_34:
|
|
41
|
+
tiles[meld[0]] -= 1
|
|
42
|
+
tiles[meld[1]] -= 1
|
|
43
|
+
tiles[meld[2]] -= 1
|
|
44
|
+
if len(meld) > 3:
|
|
45
|
+
tiles[meld[3]] -= 1
|
|
46
|
+
else:
|
|
47
|
+
tiles = tiles_34
|
|
48
|
+
|
|
49
|
+
j = (
|
|
50
|
+
(1 << tiles[27])
|
|
51
|
+
| (1 << tiles[28])
|
|
52
|
+
| (1 << tiles[29])
|
|
53
|
+
| (1 << tiles[30])
|
|
54
|
+
| (1 << tiles[31])
|
|
55
|
+
| (1 << tiles[32])
|
|
56
|
+
| (1 << tiles[33])
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if j >= 0x10:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# 13 orphans
|
|
63
|
+
if ((j & 3) == 2) and (
|
|
64
|
+
tiles[0]
|
|
65
|
+
* tiles[8]
|
|
66
|
+
* tiles[9]
|
|
67
|
+
* tiles[17]
|
|
68
|
+
* tiles[18]
|
|
69
|
+
* tiles[26]
|
|
70
|
+
* tiles[27]
|
|
71
|
+
* tiles[28]
|
|
72
|
+
* tiles[29]
|
|
73
|
+
* tiles[30]
|
|
74
|
+
* tiles[31]
|
|
75
|
+
* tiles[32]
|
|
76
|
+
* tiles[33]
|
|
77
|
+
== 2
|
|
78
|
+
):
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
# seven pairs
|
|
82
|
+
if not (j & 10) and sum([tiles[i] == 2 for i in range(34)]) == 7:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
if j & 2:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
n00 = tiles[0] + tiles[3] + tiles[6]
|
|
89
|
+
n01 = tiles[1] + tiles[4] + tiles[7]
|
|
90
|
+
n02 = tiles[2] + tiles[5] + tiles[8]
|
|
91
|
+
|
|
92
|
+
n10 = tiles[9] + tiles[12] + tiles[15]
|
|
93
|
+
n11 = tiles[10] + tiles[13] + tiles[16]
|
|
94
|
+
n12 = tiles[11] + tiles[14] + tiles[17]
|
|
95
|
+
|
|
96
|
+
n20 = tiles[18] + tiles[21] + tiles[24]
|
|
97
|
+
n21 = tiles[19] + tiles[22] + tiles[25]
|
|
98
|
+
n22 = tiles[20] + tiles[23] + tiles[26]
|
|
99
|
+
|
|
100
|
+
n0 = (n00 + n01 + n02) % 3
|
|
101
|
+
if n0 == 1:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
n1 = (n10 + n11 + n12) % 3
|
|
105
|
+
if n1 == 1:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
n2 = (n20 + n21 + n22) % 3
|
|
109
|
+
if n2 == 1:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
if (n0 == 2) + (n1 == 2) + (n2 == 2) + (tiles[27] == 2) + (tiles[28] == 2) + (tiles[29] == 2) + (
|
|
113
|
+
tiles[30] == 2
|
|
114
|
+
) + (tiles[31] == 2) + (tiles[32] == 2) + (tiles[33] == 2) != 1:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
nn0 = (n00 * 1 + n01 * 2) % 3
|
|
118
|
+
m0 = Agari._to_meld(tiles, 0)
|
|
119
|
+
nn1 = (n10 * 1 + n11 * 2) % 3
|
|
120
|
+
m1 = Agari._to_meld(tiles, 9)
|
|
121
|
+
nn2 = (n20 * 1 + n21 * 2) % 3
|
|
122
|
+
m2 = Agari._to_meld(tiles, 18)
|
|
123
|
+
|
|
124
|
+
if j & 4:
|
|
125
|
+
return (
|
|
126
|
+
not (n0 | nn0 | n1 | nn1 | n2 | nn2)
|
|
127
|
+
and Agari._is_mentsu(m0)
|
|
128
|
+
and Agari._is_mentsu(m1)
|
|
129
|
+
and Agari._is_mentsu(m2)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if n0 == 2:
|
|
133
|
+
return (
|
|
134
|
+
not (n1 | nn1 | n2 | nn2)
|
|
135
|
+
and Agari._is_mentsu(m1)
|
|
136
|
+
and Agari._is_mentsu(m2)
|
|
137
|
+
and Agari._is_atama_mentsu(nn0, m0)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if n1 == 2:
|
|
141
|
+
return (
|
|
142
|
+
not (n2 | nn2 | n0 | nn0)
|
|
143
|
+
and Agari._is_mentsu(m2)
|
|
144
|
+
and Agari._is_mentsu(m0)
|
|
145
|
+
and Agari._is_atama_mentsu(nn1, m1)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
not (n0 | nn0 | n1 | nn1)
|
|
150
|
+
and Agari._is_mentsu(m0)
|
|
151
|
+
and Agari._is_mentsu(m1)
|
|
152
|
+
and Agari._is_atama_mentsu(nn2, m2)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _is_mentsu(m: int) -> bool:
|
|
157
|
+
a = m & 7
|
|
158
|
+
b = 0
|
|
159
|
+
c = 0
|
|
160
|
+
if a in {1, 4}:
|
|
161
|
+
b = c = 1
|
|
162
|
+
elif a == 2:
|
|
163
|
+
b = c = 2
|
|
164
|
+
m >>= 3
|
|
165
|
+
a = (m & 7) - b
|
|
166
|
+
|
|
167
|
+
if a < 0:
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
for _ in range(6):
|
|
171
|
+
b = c
|
|
172
|
+
c = 0
|
|
173
|
+
if a in {1, 4}:
|
|
174
|
+
b += 1
|
|
175
|
+
c += 1
|
|
176
|
+
elif a == 2:
|
|
177
|
+
b += 2
|
|
178
|
+
c += 2
|
|
179
|
+
m >>= 3
|
|
180
|
+
a = (m & 7) - b
|
|
181
|
+
if a < 0:
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
m >>= 3
|
|
185
|
+
a = (m & 7) - c
|
|
186
|
+
|
|
187
|
+
return a in {0, 3}
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def _is_atama_mentsu(nn: int, m: int) -> bool:
|
|
191
|
+
if nn == 0:
|
|
192
|
+
if (m & (7 << 6)) >= (2 << 6) and Agari._is_mentsu(m - (2 << 6)):
|
|
193
|
+
return True
|
|
194
|
+
if (m & (7 << 15)) >= (2 << 15) and Agari._is_mentsu(m - (2 << 15)):
|
|
195
|
+
return True
|
|
196
|
+
if (m & (7 << 24)) >= (2 << 24) and Agari._is_mentsu(m - (2 << 24)):
|
|
197
|
+
return True
|
|
198
|
+
elif nn == 1:
|
|
199
|
+
if (m & (7 << 3)) >= (2 << 3) and Agari._is_mentsu(m - (2 << 3)):
|
|
200
|
+
return True
|
|
201
|
+
if (m & (7 << 12)) >= (2 << 12) and Agari._is_mentsu(m - (2 << 12)):
|
|
202
|
+
return True
|
|
203
|
+
if (m & (7 << 21)) >= (2 << 21) and Agari._is_mentsu(m - (2 << 21)):
|
|
204
|
+
return True
|
|
205
|
+
elif nn == 2:
|
|
206
|
+
if (m & 7) >= 2 and Agari._is_mentsu(m - 2):
|
|
207
|
+
return True
|
|
208
|
+
if (m & (7 << 9)) >= (2 << 9) and Agari._is_mentsu(m - (2 << 9)):
|
|
209
|
+
return True
|
|
210
|
+
if (m & (7 << 18)) >= (2 << 18) and Agari._is_mentsu(m - (2 << 18)):
|
|
211
|
+
return True
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _to_meld(tiles: Sequence[int], d: int) -> int:
|
|
216
|
+
return (
|
|
217
|
+
tiles[d]
|
|
218
|
+
| (tiles[d + 1] << 3)
|
|
219
|
+
| (tiles[d + 2] << 6)
|
|
220
|
+
| (tiles[d + 3] << 9)
|
|
221
|
+
| (tiles[d + 4] << 12)
|
|
222
|
+
| (tiles[d + 5] << 15)
|
|
223
|
+
| (tiles[d + 6] << 18)
|
|
224
|
+
| (tiles[d + 7] << 21)
|
|
225
|
+
| (tiles[d + 8] << 24)
|
|
226
|
+
)
|
|
@@ -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."""
|