1+ """
2+ Configuration classes for the hand calculator.
3+
4+ .. rubric:: Classes
5+
6+ * :class:`HandConstants` - constants for kazoe (counted) yakuman limit modes
7+ * :class:`OptionalRules` - toggle optional rule variants (open tanyao, aka dora, etc.)
8+ * :class:`HandConfig` - full hand configuration combining win conditions, wind context,
9+ bonuses, and optional rules
10+ """
11+
112from mahjong .constants import EAST
213from mahjong .hand_calculating .yaku_config import YakuConfig
314
415
516class HandConstants :
6- # Hands over 26+ han don't count as double yakuman
17+ """Constants for kazoe (counted) yakuman limit modes."""
18+
719 KAZOE_LIMITED = 0
8- # Hands over 13+ is a sanbaiman
20+ """Kazoe hands (13+ han) are capped at single yakuman."""
21+
922 KAZOE_SANBAIMAN = 1
10- # 26+ han as double yakuman, 39+ han as triple yakuman, etc.
23+ """Kazoe hands (13+ han) are capped at sanbaiman."""
24+
1125 KAZOE_NO_LIMIT = 2
26+ """No cap on kazoe hands; 13+ han as yakuman, 26+ as double yakuman, and so on."""
1227
1328
1429class OptionalRules :
1530 """
16- All the supported optional rules
31+ Toggle optional rule variants for hand evaluation.
32+
33+ Japanese mahjong has many rule variations across different parlors and online platforms.
34+ This class controls which optional rules are active during hand calculation.
35+
36+ :ivar has_open_tanyao: allow tanyao on open hands (kuitan)
37+ :vartype has_open_tanyao: bool
38+ :ivar has_aka_dora: enable red five tiles as bonus dora
39+ :vartype has_aka_dora: bool
40+ :ivar has_double_yakuman: allow double yakuman scoring for applicable hands
41+ :vartype has_double_yakuman: bool
42+ :ivar kazoe_limit: kazoe yakuman limit mode; one of
43+ :attr:`HandConstants.KAZOE_LIMITED`, :attr:`HandConstants.KAZOE_SANBAIMAN`,
44+ or :attr:`HandConstants.KAZOE_NO_LIMIT`
45+ :vartype kazoe_limit: int
46+ :ivar kiriage: round up to mangan when han/fu are at the boundary (4 han 30 fu or 3 han 60 fu)
47+ :vartype kiriage: bool
48+ :ivar fu_for_open_pinfu: add 2 fu for open pinfu-form ron (totaling 30 fu);
49+ when False, 1 han 20 fu hands become possible
50+ :vartype fu_for_open_pinfu: bool
51+ :ivar fu_for_pinfu_tsumo: award 2 fu for tsumo on pinfu hands (disabling the 0-fu
52+ tsumo pinfu exception)
53+ :vartype fu_for_pinfu_tsumo: bool
54+ :ivar renhou_as_yakuman: treat renhou as yakuman instead of mangan
55+ :vartype renhou_as_yakuman: bool
56+ :ivar has_daisharin: enable daisharin 22334455667788p as yakuman;
57+ automatically set to True when ``has_daisharin_other_suits`` is True
58+ :vartype has_daisharin: bool
59+ :ivar has_daisharin_other_suits: allow daisharin in man and sou suits (not only pin)
60+ :vartype has_daisharin_other_suits: bool
61+ :ivar has_daichisei: enable daichisei (tsuuiisou / all honors as seven pairs) as yakuman
62+ :vartype has_daichisei: bool
63+ :ivar has_sashikomi_yakuman: enable sashikomi (intentional deal-in for open riichi)
64+ as yakuman
65+ :vartype has_sashikomi_yakuman: bool
66+ :ivar limit_to_sextuple_yakuman: cap yakuman multiplier at 6x
67+ :vartype limit_to_sextuple_yakuman: bool
68+ :ivar paarenchan_needs_yaku: require at least one yaku for paarenchan to count
69+ :vartype paarenchan_needs_yaku: bool
1770 """
1871
1972 has_open_tanyao : bool
2073 has_aka_dora : bool
2174 has_double_yakuman : bool
22- # not implemented! tenhou does not support double yakuman for a single yaku
2375 kazoe_limit : int
2476 kiriage : bool
25- # if false, 1-20 hand will be possible
2677 fu_for_open_pinfu : bool
27- # if true, pinfu tsumo will be disabled
2878 fu_for_pinfu_tsumo : bool
2979 renhou_as_yakuman : bool
3080 has_daisharin : bool
@@ -51,6 +101,31 @@ def __init__(
51101 paarenchan_needs_yaku : bool = True ,
52102 has_daichisei : bool = False ,
53103 ) -> None :
104+ """
105+ Initialize optional rules.
106+
107+ >>> from mahjong.hand_calculating.hand_config import OptionalRules
108+ >>> options = OptionalRules(has_open_tanyao=True, kiriage=True)
109+ >>> options.has_open_tanyao
110+ True
111+ >>> options.kiriage
112+ True
113+
114+ :param has_open_tanyao: allow tanyao on open hands (kuitan)
115+ :param has_aka_dora: enable red five tiles as bonus dora
116+ :param has_double_yakuman: allow double yakuman scoring
117+ :param kazoe_limit: kazoe yakuman limit mode
118+ :param kiriage: round up to mangan at boundary han/fu combinations
119+ :param fu_for_open_pinfu: award 2 fu for open hands with no other fu sources
120+ :param fu_for_pinfu_tsumo: award tsumo fu even for pinfu hands
121+ :param renhou_as_yakuman: treat renhou as yakuman
122+ :param has_daisharin: enable daisharin yakuman
123+ :param has_daisharin_other_suits: allow daisharin in all suits
124+ :param has_sashikomi_yakuman: enable sashikomi yakuman
125+ :param limit_to_sextuple_yakuman: cap yakuman multiplier at 6x
126+ :param paarenchan_needs_yaku: require yaku for paarenchan
127+ :param has_daichisei: enable daichisei yakuman
128+ """
54129 self .has_open_tanyao = has_open_tanyao
55130 self .has_aka_dora = has_aka_dora
56131 self .has_double_yakuman = has_double_yakuman
@@ -69,7 +144,82 @@ def __init__(
69144
70145class HandConfig (HandConstants ):
71146 """
72- Special class to pass various settings to the hand calculator object
147+ Configuration for the hand calculator combining win conditions, wind context, and optional rules.
148+
149+ Pass a ``HandConfig`` instance to
150+ :meth:`~mahjong.hand_calculating.hand.HandCalculator.estimate_hand_value` to describe
151+ how the hand was won and which rules apply.
152+
153+ A default config represents a closed ron with no special conditions:
154+
155+ >>> from mahjong.hand_calculating.hand_config import HandConfig
156+ >>> config = HandConfig()
157+ >>> config.is_tsumo
158+ False
159+ >>> config.is_dealer
160+ False
161+
162+ Configure a dealer tsumo win with riichi and ippatsu:
163+
164+ >>> from mahjong.constants import EAST
165+ >>> config = HandConfig(is_tsumo=True, is_riichi=True, is_ippatsu=True, player_wind=EAST)
166+ >>> config.is_tsumo
167+ True
168+ >>> config.is_dealer
169+ True
170+
171+ Include score bonuses and optional rules:
172+
173+ >>> from mahjong.hand_calculating.hand_config import OptionalRules
174+ >>> options = OptionalRules(has_open_tanyao=True, has_aka_dora=True)
175+ >>> config = HandConfig(tsumi_number=2, kyoutaku_number=3, options=options)
176+ >>> config.tsumi_number
177+ 2
178+ >>> config.options.has_open_tanyao
179+ True
180+
181+ :ivar yaku: yaku definition objects used during hand evaluation
182+ :vartype yaku: ~mahjong.hand_calculating.yaku_config.YakuConfig
183+ :ivar options: optional rule settings
184+ :vartype options: OptionalRules
185+ :ivar is_tsumo: hand won by self-draw
186+ :vartype is_tsumo: bool
187+ :ivar is_riichi: player declared riichi
188+ :vartype is_riichi: bool
189+ :ivar is_ippatsu: win within one turn of declaring riichi
190+ :vartype is_ippatsu: bool
191+ :ivar is_rinshan: win on a replacement tile after calling kan
192+ :vartype is_rinshan: bool
193+ :ivar is_chankan: win by robbing another player's kan declaration
194+ :vartype is_chankan: bool
195+ :ivar is_haitei: win on the last drawable tile (tsumo) of the round
196+ :vartype is_haitei: bool
197+ :ivar is_houtei: win on the discard following the last drawable tile (ron)
198+ :vartype is_houtei: bool
199+ :ivar is_daburu_riichi: player declared double riichi (riichi on first turn)
200+ :vartype is_daburu_riichi: bool
201+ :ivar is_nagashi_mangan: all discards are terminals and honors with no calls against them
202+ :vartype is_nagashi_mangan: bool
203+ :ivar is_tenhou: dealer wins on the initial draw
204+ :vartype is_tenhou: bool
205+ :ivar is_renhou: non-dealer wins on the first go-around before any calls
206+ :vartype is_renhou: bool
207+ :ivar is_chiihou: non-dealer wins on the initial draw
208+ :vartype is_chiihou: bool
209+ :ivar is_open_riichi: player declared open riichi (revealed hand)
210+ :vartype is_open_riichi: bool
211+ :ivar is_dealer: True when ``player_wind`` is East
212+ :vartype is_dealer: bool
213+ :ivar player_wind: tile index in 34-format of the player's seat wind, or None
214+ :vartype player_wind: int | None
215+ :ivar round_wind: tile index in 34-format of the round wind, or None
216+ :vartype round_wind: int | None
217+ :ivar paarenchan: consecutive dealer wins count; above 0 enables paarenchan yakuman check
218+ :vartype paarenchan: int
219+ :ivar kyoutaku_number: number of riichi deposits on the table (1000 points each)
220+ :vartype kyoutaku_number: int
221+ :ivar tsumi_number: number of honba counters (100 points each per counter)
222+ :vartype tsumi_number: int
73223 """
74224
75225 yaku : YakuConfig
@@ -92,11 +242,10 @@ class HandConfig(HandConstants):
92242 is_dealer : bool
93243 player_wind : int | None
94244 round_wind : int | None
95- # for optional yakuman paarenchan above 0 means that dealer has paarenchan possibility
96245 paarenchan : int
97246
98- kyoutaku_number : int # 1000-point
99- tsumi_number : int # 100-point
247+ kyoutaku_number : int
248+ tsumi_number : int
100249
101250 def __init__ (
102251 self ,
@@ -120,6 +269,44 @@ def __init__(
120269 paarenchan : int = 0 ,
121270 options : OptionalRules | None = None ,
122271 ) -> None :
272+ """
273+ Initialize hand configuration.
274+
275+ >>> from mahjong.hand_calculating.hand_config import HandConfig, OptionalRules
276+ >>> from mahjong.constants import EAST, SOUTH
277+ >>> config = HandConfig(
278+ ... is_tsumo=True,
279+ ... is_riichi=True,
280+ ... player_wind=EAST,
281+ ... round_wind=SOUTH,
282+ ... options=OptionalRules(has_open_tanyao=True),
283+ ... )
284+ >>> config.is_dealer
285+ True
286+ >>> config.round_wind == SOUTH
287+ True
288+
289+ :param is_tsumo: hand won by self-draw
290+ :param is_riichi: player declared riichi
291+ :param is_ippatsu: win within one turn of declaring riichi
292+ :param is_rinshan: win on a replacement tile after calling kan
293+ :param is_chankan: win by robbing another player's kan
294+ :param is_haitei: win on the last drawable tile
295+ :param is_houtei: win on the discard following the last drawable tile
296+ :param is_daburu_riichi: player declared double riichi
297+ :param is_nagashi_mangan: all discards are terminals and honors
298+ :param is_tenhou: dealer wins on the initial draw
299+ :param is_renhou: non-dealer wins on the first go-around
300+ :param is_chiihou: non-dealer wins on the initial draw
301+ :param is_open_riichi: player declared open riichi
302+ :param player_wind: tile index in 34-format of the player's seat wind
303+ :param round_wind: tile index in 34-format of the round wind
304+ :param kyoutaku_number: riichi deposits on the table (1000 points each)
305+ :param tsumi_number: honba counters (100 points each per counter)
306+ :param paarenchan: consecutive dealer wins count for paarenchan check
307+ :param options: optional rule settings; defaults to :class:`OptionalRules` with
308+ all defaults when None
309+ """
123310 self .yaku = YakuConfig ()
124311 self .options = options or OptionalRules ()
125312
0 commit comments