jansou 0.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.
Files changed (75) hide show
  1. jansou-0.1.0/.editorconfig +9 -0
  2. jansou-0.1.0/.github/workflows/ci.yml +83 -0
  3. jansou-0.1.0/.gitignore +15 -0
  4. jansou-0.1.0/LICENSE +21 -0
  5. jansou-0.1.0/PKG-INFO +161 -0
  6. jansou-0.1.0/README.md +139 -0
  7. jansou-0.1.0/mise.toml +11 -0
  8. jansou-0.1.0/pyproject.toml +125 -0
  9. jansou-0.1.0/src/jansou/__init__.py +11 -0
  10. jansou-0.1.0/src/jansou/analysis/__init__.py +7 -0
  11. jansou-0.1.0/src/jansou/analysis/decompose.py +347 -0
  12. jansou-0.1.0/src/jansou/analysis/efficiency.py +202 -0
  13. jansou-0.1.0/src/jansou/analysis/shanten.py +188 -0
  14. jansou-0.1.0/src/jansou/analysis/waits.py +82 -0
  15. jansou-0.1.0/src/jansou/core/__init__.py +7 -0
  16. jansou-0.1.0/src/jansou/core/hand.py +214 -0
  17. jansou-0.1.0/src/jansou/core/notation.py +236 -0
  18. jansou-0.1.0/src/jansou/core/rules.py +327 -0
  19. jansou-0.1.0/src/jansou/core/tiles.py +360 -0
  20. jansou-0.1.0/src/jansou/game/__init__.py +11 -0
  21. jansou-0.1.0/src/jansou/game/actions.py +532 -0
  22. jansou-0.1.0/src/jansou/game/agents.py +353 -0
  23. jansou-0.1.0/src/jansou/game/environment.py +150 -0
  24. jansou-0.1.0/src/jansou/game/events.py +218 -0
  25. jansou-0.1.0/src/jansou/game/flow.py +886 -0
  26. jansou-0.1.0/src/jansou/game/progression.py +159 -0
  27. jansou-0.1.0/src/jansou/game/state.py +146 -0
  28. jansou-0.1.0/src/jansou/game/wall.py +164 -0
  29. jansou-0.1.0/src/jansou/io/__init__.py +8 -0
  30. jansou-0.1.0/src/jansou/io/from_game.py +200 -0
  31. jansou-0.1.0/src/jansou/io/mjai.py +334 -0
  32. jansou-0.1.0/src/jansou/io/mjlog.py +446 -0
  33. jansou-0.1.0/src/jansou/io/paifu.py +443 -0
  34. jansou-0.1.0/src/jansou/io/tenhou_json.py +514 -0
  35. jansou-0.1.0/src/jansou/io/tiles.py +110 -0
  36. jansou-0.1.0/src/jansou/py.typed +0 -0
  37. jansou-0.1.0/src/jansou/scoring/__init__.py +7 -0
  38. jansou-0.1.0/src/jansou/scoring/context.py +80 -0
  39. jansou-0.1.0/src/jansou/scoring/fu.py +194 -0
  40. jansou-0.1.0/src/jansou/scoring/score.py +278 -0
  41. jansou-0.1.0/src/jansou/scoring/yaku.py +436 -0
  42. jansou-0.1.0/src/jansou/validation/__init__.py +5 -0
  43. jansou-0.1.0/src/jansou/validation/check.py +101 -0
  44. jansou-0.1.0/src/jansou/validation/cli.py +272 -0
  45. jansou-0.1.0/tests/conftest.py +25 -0
  46. jansou-0.1.0/tests/test_actions.py +320 -0
  47. jansou-0.1.0/tests/test_agents.py +283 -0
  48. jansou-0.1.0/tests/test_decompose.py +112 -0
  49. jansou-0.1.0/tests/test_efficiency.py +91 -0
  50. jansou-0.1.0/tests/test_environment.py +145 -0
  51. jansou-0.1.0/tests/test_events.py +76 -0
  52. jansou-0.1.0/tests/test_flow.py +686 -0
  53. jansou-0.1.0/tests/test_fu.py +131 -0
  54. jansou-0.1.0/tests/test_hand.py +191 -0
  55. jansou-0.1.0/tests/test_io_edges.py +61 -0
  56. jansou-0.1.0/tests/test_io_from_game.py +128 -0
  57. jansou-0.1.0/tests/test_io_mjai.py +156 -0
  58. jansou-0.1.0/tests/test_io_mjlog.py +86 -0
  59. jansou-0.1.0/tests/test_io_paifu.py +145 -0
  60. jansou-0.1.0/tests/test_io_tenhou_json.py +255 -0
  61. jansou-0.1.0/tests/test_io_tiles.py +88 -0
  62. jansou-0.1.0/tests/test_io_writers.py +332 -0
  63. jansou-0.1.0/tests/test_notation.py +184 -0
  64. jansou-0.1.0/tests/test_progression.py +146 -0
  65. jansou-0.1.0/tests/test_rules.py +169 -0
  66. jansou-0.1.0/tests/test_scoring.py +174 -0
  67. jansou-0.1.0/tests/test_shanten.py +221 -0
  68. jansou-0.1.0/tests/test_state.py +99 -0
  69. jansou-0.1.0/tests/test_tiles.py +182 -0
  70. jansou-0.1.0/tests/test_validation_check.py +108 -0
  71. jansou-0.1.0/tests/test_validation_cli.py +194 -0
  72. jansou-0.1.0/tests/test_waits.py +66 -0
  73. jansou-0.1.0/tests/test_wall.py +121 -0
  74. jansou-0.1.0/tests/test_yaku.py +268 -0
  75. jansou-0.1.0/uv.lock +427 -0
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 4
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
@@ -0,0 +1,83 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ test:
17
+ name: test (py${{ matrix.python-version }})
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - name: Set up uv and Python ${{ matrix.python-version }}
27
+ uses: astral-sh/setup-uv@v6
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ enable-cache: true
31
+
32
+ - name: Install dependencies
33
+ run: uv sync
34
+
35
+ - name: Run tests
36
+ run: uv run pytest tests/
37
+
38
+ coverage:
39
+ name: coverage
40
+ runs-on: ubuntu-latest
41
+ permissions:
42
+ contents: write
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: Set up uv
47
+ uses: astral-sh/setup-uv@v6
48
+ with:
49
+ python-version: "3.12"
50
+ enable-cache: true
51
+
52
+ - name: Install dependencies
53
+ run: uv sync
54
+
55
+ - name: Run tests with coverage
56
+ run: uv run pytest tests/ --cov --cov-report=term-missing --cov-fail-under=100
57
+
58
+ - name: Update coverage badge
59
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
60
+ uses: py-cov-action/python-coverage-comment-action@v3
61
+ with:
62
+ GITHUB_TOKEN: ${{ github.token }}
63
+
64
+ lint:
65
+ name: lint
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+
70
+ - name: Set up uv
71
+ uses: astral-sh/setup-uv@v6
72
+ with:
73
+ python-version: "3.12"
74
+ enable-cache: true
75
+
76
+ - name: Install dependencies
77
+ run: uv sync
78
+
79
+ - name: Ruff lint
80
+ run: uv run ruff check
81
+
82
+ - name: Ruff format
83
+ run: uv run ruff format --check
@@ -0,0 +1,15 @@
1
+ .DS_Store
2
+ Thumbs.db
3
+
4
+ CLAUDE.local.md
5
+ .claude/settings.local.json
6
+
7
+ __pycache__/
8
+ *.py[co]
9
+ .venv/
10
+ dist/
11
+
12
+ .coverage
13
+ site/
14
+
15
+ dataset/*/data
jansou-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jaemin Noh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
jansou-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: jansou
3
+ Version: 0.1.0
4
+ Summary: Riichi Mahjong Environments and Utilities
5
+ Project-URL: Homepage, https://github.com/Snack-X/jansou
6
+ Project-URL: Repository, https://github.com/Snack-X/jansou
7
+ Project-URL: Issues, https://github.com/Snack-X/jansou/issues
8
+ Author-email: Jaemin Noh <korsnack@korsnack.kr>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: mahjong,riichi
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Games/Entertainment :: Board Games
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Jansou
24
+
25
+ [![Coverage](https://raw.githubusercontent.com/your-org/jansou/python-coverage-comment-action-data/badge.svg)](https://github.com/your-org/jansou/tree/python-coverage-comment-action-data)
26
+
27
+ Python library for Riichi Mahjong environments and ugittilities.
28
+
29
+ - Conversion between multiple tile notations (MPSZ, MJAI, 136)
30
+ - Hand analysis (shanten, wait, efficiency, yaku, fu/han, score)
31
+ - Game environment for 3/4 player game with configurable rules
32
+ - Game replay log in multiple formats (mjlog XML, mjai JSONL, Tenhou JSON)
33
+
34
+ ## Examples
35
+
36
+ ### Notation
37
+
38
+ ```python
39
+ from jansou.core.notation import parse_mpsz, dump_mpsz, parse_mjai, dump_mjai, parse_136, dump_136
40
+
41
+ tiles = parse_mpsz("667s 34668m 2357p 2z")
42
+
43
+ dump_mpsz(tiles) # 34668m2357p667s2z
44
+ dump_mjai(tiles) # 6s 6s 7s 3m 4m 6m 6m 8m 2p 3p 5p 7p S
45
+ dump_136(tiles) # [92, 93, 96, 8, 12, 20, 21, 28, 40, 44, 53, 60, 112]
46
+ ```
47
+
48
+ ### Hand
49
+
50
+ ```python
51
+ from jansou.core.tiles import Tile, TileKind
52
+ from jansou.core.notation import parse_mpsz
53
+ from jansou.core.hand import Hand
54
+ from jansou.analysis.efficiency import discard_evaluation
55
+ from jansou.analysis.shanten import shanten, is_tenpai
56
+ from jansou.analysis.waits import waits
57
+
58
+ # (1) shanten advancement
59
+
60
+ hand = Hand(parse_mpsz("279m 1569p 1168s 35z 2m"))
61
+
62
+ shanten(hand) # 3
63
+ is_tenpai(hand) # False
64
+
65
+ options = discard_evaluation(hand) # sorted by shanten advancement and wider acceptance
66
+ # in this case, 1p 9p 3z 5z are the best, with the same number of acceptance
67
+
68
+ best = options[0]
69
+ best.discard # 1p
70
+ best.shanten # 3
71
+ best.total_acceptance # 20
72
+
73
+ # (2) acceptance
74
+
75
+ hand = Hand(parse_mpsz("567m 34567p 23489s 2z"))
76
+
77
+ options = discard_evaluation(hand) # there are multiple options for 1 shanten
78
+
79
+ best = options[0] # but the best option is 2z with the widest acceptance
80
+ best.discard # 2z
81
+ best.shanten # 1
82
+ best.total_acceptance # 33
83
+
84
+ # (3) waits
85
+
86
+ hand = Hand(parse_mpsz("1112345678999m"))
87
+
88
+ waits(hand) # 1m 2m 3m 4m 5m 6m 7m 8m 9m
89
+ ```
90
+
91
+ ### Score
92
+
93
+ ```python
94
+ from jansou.core.tiles import Tile, TileKind, Wind
95
+ from jansou.core.notation import parse_mpsz
96
+ from jansou.core.rules import preset
97
+ from jansou.core.hand import Hand, Meld, MeldType, CallSource
98
+ from jansou.scoring.context import WinContext
99
+ from jansou.scoring.score import score
100
+
101
+ hand = Hand(
102
+ parse_mpsz("77m 34p 055s 5p"),
103
+ [
104
+ Meld(MeldType.PON, parse_mpsz("111m"), Tile(TileKind.M1), CallSource.TOIMEN),
105
+ Meld(MeldType.PON, parse_mpsz("777z"), Tile(TileKind.CHUN), CallSource.KAMICHA),
106
+ ]
107
+ )
108
+
109
+ context = WinContext(
110
+ rules=preset("m-league"),
111
+ seat_wind=Wind.WEST,
112
+ is_tsumo=False,
113
+ dora_indicators=[Tile(TileKind.CHUN)],
114
+ )
115
+
116
+ result = score(hand, Tile(TileKind.P5), context)
117
+
118
+ result.yaku # YAKUHAI_CHUN
119
+ result.han # 2
120
+ result.fu.total # 40 (20 + 4 + 4 + 4 = 32)
121
+
122
+ result.payment.total # 2600
123
+
124
+ shanten(hand) # 0
125
+ is_tenpai(hand) # True
126
+ waits(hand) # 2p, 5p
127
+ ```
128
+
129
+ ### Game environment, replay log
130
+
131
+ ```python
132
+ from jansou.core.rules import preset
133
+ from jansou.game.environment import Environment
134
+ from jansou.game.agents import SmartEfficiencyAgent
135
+ from jansou.io.from_game import paifu_from_game
136
+ from jansou.io.tenhou_json import dump_tenhou_json_url
137
+
138
+ env = Environment(preset("tenhou"))
139
+ agents = [SmartEfficiencyAgent() for i in range(4)]
140
+
141
+ result = env.run(agents)
142
+
143
+ result.scores
144
+ result.ranking
145
+
146
+ paifu = paifu_from_game(env)
147
+ dump_tenhou_json_url(paifu) # https://tenhou.net/6/#json=...
148
+ # can be viewed at https://mjv.snack.studio
149
+ ```
150
+
151
+ ## Development
152
+
153
+ ```sh
154
+ uv sync
155
+
156
+ uv run pytest
157
+ uv run pytest --cov
158
+
159
+ uv run ruff format
160
+ uv run ruff check
161
+ ```
jansou-0.1.0/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Jansou
2
+
3
+ [![Coverage](https://raw.githubusercontent.com/your-org/jansou/python-coverage-comment-action-data/badge.svg)](https://github.com/your-org/jansou/tree/python-coverage-comment-action-data)
4
+
5
+ Python library for Riichi Mahjong environments and ugittilities.
6
+
7
+ - Conversion between multiple tile notations (MPSZ, MJAI, 136)
8
+ - Hand analysis (shanten, wait, efficiency, yaku, fu/han, score)
9
+ - Game environment for 3/4 player game with configurable rules
10
+ - Game replay log in multiple formats (mjlog XML, mjai JSONL, Tenhou JSON)
11
+
12
+ ## Examples
13
+
14
+ ### Notation
15
+
16
+ ```python
17
+ from jansou.core.notation import parse_mpsz, dump_mpsz, parse_mjai, dump_mjai, parse_136, dump_136
18
+
19
+ tiles = parse_mpsz("667s 34668m 2357p 2z")
20
+
21
+ dump_mpsz(tiles) # 34668m2357p667s2z
22
+ dump_mjai(tiles) # 6s 6s 7s 3m 4m 6m 6m 8m 2p 3p 5p 7p S
23
+ dump_136(tiles) # [92, 93, 96, 8, 12, 20, 21, 28, 40, 44, 53, 60, 112]
24
+ ```
25
+
26
+ ### Hand
27
+
28
+ ```python
29
+ from jansou.core.tiles import Tile, TileKind
30
+ from jansou.core.notation import parse_mpsz
31
+ from jansou.core.hand import Hand
32
+ from jansou.analysis.efficiency import discard_evaluation
33
+ from jansou.analysis.shanten import shanten, is_tenpai
34
+ from jansou.analysis.waits import waits
35
+
36
+ # (1) shanten advancement
37
+
38
+ hand = Hand(parse_mpsz("279m 1569p 1168s 35z 2m"))
39
+
40
+ shanten(hand) # 3
41
+ is_tenpai(hand) # False
42
+
43
+ options = discard_evaluation(hand) # sorted by shanten advancement and wider acceptance
44
+ # in this case, 1p 9p 3z 5z are the best, with the same number of acceptance
45
+
46
+ best = options[0]
47
+ best.discard # 1p
48
+ best.shanten # 3
49
+ best.total_acceptance # 20
50
+
51
+ # (2) acceptance
52
+
53
+ hand = Hand(parse_mpsz("567m 34567p 23489s 2z"))
54
+
55
+ options = discard_evaluation(hand) # there are multiple options for 1 shanten
56
+
57
+ best = options[0] # but the best option is 2z with the widest acceptance
58
+ best.discard # 2z
59
+ best.shanten # 1
60
+ best.total_acceptance # 33
61
+
62
+ # (3) waits
63
+
64
+ hand = Hand(parse_mpsz("1112345678999m"))
65
+
66
+ waits(hand) # 1m 2m 3m 4m 5m 6m 7m 8m 9m
67
+ ```
68
+
69
+ ### Score
70
+
71
+ ```python
72
+ from jansou.core.tiles import Tile, TileKind, Wind
73
+ from jansou.core.notation import parse_mpsz
74
+ from jansou.core.rules import preset
75
+ from jansou.core.hand import Hand, Meld, MeldType, CallSource
76
+ from jansou.scoring.context import WinContext
77
+ from jansou.scoring.score import score
78
+
79
+ hand = Hand(
80
+ parse_mpsz("77m 34p 055s 5p"),
81
+ [
82
+ Meld(MeldType.PON, parse_mpsz("111m"), Tile(TileKind.M1), CallSource.TOIMEN),
83
+ Meld(MeldType.PON, parse_mpsz("777z"), Tile(TileKind.CHUN), CallSource.KAMICHA),
84
+ ]
85
+ )
86
+
87
+ context = WinContext(
88
+ rules=preset("m-league"),
89
+ seat_wind=Wind.WEST,
90
+ is_tsumo=False,
91
+ dora_indicators=[Tile(TileKind.CHUN)],
92
+ )
93
+
94
+ result = score(hand, Tile(TileKind.P5), context)
95
+
96
+ result.yaku # YAKUHAI_CHUN
97
+ result.han # 2
98
+ result.fu.total # 40 (20 + 4 + 4 + 4 = 32)
99
+
100
+ result.payment.total # 2600
101
+
102
+ shanten(hand) # 0
103
+ is_tenpai(hand) # True
104
+ waits(hand) # 2p, 5p
105
+ ```
106
+
107
+ ### Game environment, replay log
108
+
109
+ ```python
110
+ from jansou.core.rules import preset
111
+ from jansou.game.environment import Environment
112
+ from jansou.game.agents import SmartEfficiencyAgent
113
+ from jansou.io.from_game import paifu_from_game
114
+ from jansou.io.tenhou_json import dump_tenhou_json_url
115
+
116
+ env = Environment(preset("tenhou"))
117
+ agents = [SmartEfficiencyAgent() for i in range(4)]
118
+
119
+ result = env.run(agents)
120
+
121
+ result.scores
122
+ result.ranking
123
+
124
+ paifu = paifu_from_game(env)
125
+ dump_tenhou_json_url(paifu) # https://tenhou.net/6/#json=...
126
+ # can be viewed at https://mjv.snack.studio
127
+ ```
128
+
129
+ ## Development
130
+
131
+ ```sh
132
+ uv sync
133
+
134
+ uv run pytest
135
+ uv run pytest --cov
136
+
137
+ uv run ruff format
138
+ uv run ruff check
139
+ ```
jansou-0.1.0/mise.toml ADDED
@@ -0,0 +1,11 @@
1
+ [tools]
2
+ python = "3.12"
3
+ uv = "latest"
4
+
5
+ [tasks.docs]
6
+ description = "Generate the API documentation from docstrings into site/api"
7
+ run = "uv run pdoc -d google jansou -o site/api"
8
+
9
+ [tasks.docs-serve]
10
+ description = "Serve the API documentation with live reload"
11
+ run = "uv run pdoc -d google jansou"
@@ -0,0 +1,125 @@
1
+ [project]
2
+ name = "jansou"
3
+ version = "0.1.0"
4
+ description = "Riichi Mahjong Environments and Utilities"
5
+ authors = [
6
+ { name = "Jaemin Noh", email = "korsnack@korsnack.kr" }
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ keywords = ["mahjong", "riichi"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Games/Entertainment :: Board Games",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/Snack-X/jansou"
27
+ Repository = "https://github.com/Snack-X/jansou"
28
+ Issues = "https://github.com/Snack-X/jansou/issues"
29
+
30
+ [project.scripts]
31
+ jansou-validate = "jansou.validation.cli:main"
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pdoc>=15.0",
36
+ "pytest>=8.0",
37
+ "pytest-cov>=5.0",
38
+ "ruff>=0.15.20",
39
+ ]
40
+
41
+ [build-system]
42
+ requires = ["hatchling"]
43
+ build-backend = "hatchling.build"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/jansou"]
47
+
48
+ [tool.hatch.build.targets.sdist]
49
+ exclude = [
50
+ "/dataset",
51
+ "/docs",
52
+ "/.claude/",
53
+ "CLAUDE.md",
54
+ ]
55
+
56
+ [tool.ruff]
57
+ line-length = 120
58
+
59
+ [tool.ruff.lint]
60
+ select = ["ALL"]
61
+ ignore = [
62
+ # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
63
+ "W191", # tab-indentation
64
+ "E111", # indentation-with-invalid-multiple
65
+ "E114", # indentation-with-invalid-multiple-comment
66
+ "E117", # over-indented
67
+ "D203", # incorrect-blank-line-before-class
68
+ "D206", # docstring-tab-indentation
69
+ "D300", # triple-single-quotes
70
+ "Q000", # bad-quotes-inline-string
71
+ "Q001", # bad-quotes-multiline-string
72
+ "Q002", # bad-quotes-docstring
73
+ "Q003", # avoidable-escaped-quote
74
+ "Q004", # unnecessary-escaped-quote
75
+ "COM812", # missing-trailing-comma
76
+ "COM819", # prohibited-trailing-comma
77
+ "ISC001", # single-line-implicit-string-concatenation
78
+
79
+ "EM", # flake8-errmsg
80
+
81
+ "PLR2004", # magic-value-comparison
82
+ "S311", # suspicious-non-cryptographic-random-usage
83
+ "TRY003", # raise-vanilla-args
84
+ ]
85
+
86
+ [tool.ruff.lint.per-file-ignores]
87
+ "tests/*" = [
88
+ "D1", # undocumented-*
89
+
90
+ "ANN001", # missing-type-function-argument
91
+ "INP001", # implicit-namespace-package
92
+ "PLR2004", # magic-value-comparison
93
+ "S101", # assert
94
+ "S311", # suspicious-non-cryptographic-random-usage
95
+ "SLF001", # private-member-access
96
+ ]
97
+
98
+ [tool.ruff.lint.pydocstyle]
99
+ convention = "google"
100
+
101
+ [tool.ruff.lint.pylint]
102
+ max-args = 10
103
+ max-branches = 15
104
+ max-returns = 10
105
+
106
+ [tool.ruff.lint.mccabe]
107
+ max-complexity = 15
108
+
109
+ [tool.pytest.ini_options]
110
+ testpaths = ["tests"]
111
+
112
+ [tool.coverage.run]
113
+ source = ["jansou"]
114
+ branch = true
115
+ relative_files = true
116
+
117
+ [tool.coverage.report]
118
+ show_missing = true
119
+ skip_covered = false
120
+ exclude_also = [
121
+ "raise NotImplementedError",
122
+ "if TYPE_CHECKING:",
123
+ "if __name__ == .__main__.:",
124
+ "\\.\\.\\.",
125
+ ]
@@ -0,0 +1,11 @@
1
+ """Jansou: a Riichi Mahjong library.
2
+
3
+ Functionality lives in topical subpackages:
4
+
5
+ - jansou.core: tiles, notation, rules, and hands
6
+ - jansou.analysis: decomposition, shanten, waits, and efficiency
7
+ - jansou.scoring: win context, fu, yaku, and points
8
+ - jansou.game: wall, state, events, actions, flow, agents, and environment
9
+ - jansou.io: reading, writing, and replaying external records (mjlog, Tenhou JSON, MJAI), and exporting engine games
10
+ - jansou.validation: checking parsed games against the scores their logs recorded
11
+ """
@@ -0,0 +1,7 @@
1
+ """Hand-shape analysis.
2
+
3
+ - jansou.analysis.decompose: complete-hand decomposition and wait shapes
4
+ - jansou.analysis.shanten: distance to ready
5
+ - jansou.analysis.waits: the tiles that complete a ready hand
6
+ - jansou.analysis.efficiency: acceptance, discard evaluation, improvement tiles
7
+ """