55
66
77class Meld :
8+ """
9+ Representation of a declared meld (called or concealed tile group).
10+
11+ A meld is a set of tiles that a player has declared, either by calling another
12+ player's discard (open meld) or by declaring a concealed kan. Meld types follow
13+ standard Japanese mahjong terminology:
14+
15+ - **Chi** — a sequence of three consecutive suited tiles, called from the player to the left.
16+ - **Pon** — a triplet of identical tiles, called from any player.
17+ - **Kan** — a quad of identical tiles (open, closed, or extended).
18+ - **Shouminkan** — an added kan (extending an existing pon with the fourth tile).
19+ - **Nuki** — a special declaration used in three-player mahjong (north wind extraction).
20+
21+ Create an open chi meld:
22+
23+ >>> from mahjong.meld import Meld
24+ >>> from mahjong.tile import TilesConverter
25+ >>> tiles = TilesConverter.string_to_136_array(man="123")
26+ >>> meld = Meld(meld_type=Meld.CHI, tiles=tiles)
27+ >>> meld.type
28+ 'chi'
29+ >>> meld.tiles
30+ (0, 4, 8)
31+
32+ Create a closed kan:
33+
34+ >>> tiles = TilesConverter.string_to_136_array(man="1111")
35+ >>> meld = Meld(meld_type=Meld.KAN, tiles=tiles, opened=False)
36+ >>> meld.opened
37+ False
38+
39+ :ivar type: one of the meld type constants (CHI, PON, KAN, SHOUMINKAN, NUKI)
40+ :vartype type: str | None
41+ :ivar tiles: tile indices in 136 format, stored as an immutable tuple
42+ :vartype tiles: tuple[int, ...]
43+ :ivar opened: True for open melds (called from another player), False for closed kan
44+ :vartype opened: bool
45+ :ivar called_tile: the specific tile index (136 format) that was called to form this meld
46+ :vartype called_tile: int | None
47+ :ivar who: seat index (0-3) of the player who declared the meld
48+ :vartype who: int | None
49+ :ivar from_who: seat index (0-3) of the player who discarded the called tile
50+ :vartype from_who: int | None
51+ """
52+
853 CHI = "chi"
54+ """Chi (sequence) meld type — three consecutive suited tiles."""
55+
956 PON = "pon"
57+ """Pon (triplet) meld type — three identical tiles."""
58+
1059 KAN = "kan"
60+ """Kan (quad) meld type — four identical tiles."""
61+
1162 SHOUMINKAN = "shouminkan"
63+ """Shouminkan (added kan) meld type — extending a pon with the fourth tile."""
64+
1265 NUKI = "nuki"
66+ """Nuki (north wind extraction) meld type, at the moment is not used in the library."""
1367
1468 type : str | None
1569 tiles : tuple [int , ...]
16- # we need it to distinguish opened and closed kan
1770 opened : bool
1871 called_tile : int | None
1972 who : int | None
@@ -28,6 +81,25 @@ def __init__(
2881 who : int | None = None ,
2982 from_who : int | None = None ,
3083 ) -> None :
84+ """
85+ Initialize a meld.
86+
87+ >>> from mahjong.meld import Meld
88+ >>> from mahjong.tile import TilesConverter
89+ >>> tiles = TilesConverter.string_to_136_array(man="111")
90+ >>> meld = Meld(meld_type=Meld.PON, tiles=tiles, who=0, from_who=2)
91+ >>> meld.type
92+ 'pon'
93+ >>> meld.who
94+ 0
95+
96+ :param meld_type: one of the meld type constants (CHI, PON, KAN, SHOUMINKAN, NUKI)
97+ :param tiles: tile indices in 136 format
98+ :param opened: True for open melds (called from another player), False for closed kan
99+ :param called_tile: the specific tile index (136 format) that was called to form this meld
100+ :param who: seat index (0-3) of the player who declared the meld
101+ :param from_who: seat index (0-3) of the player who discarded the called tile
102+ """
31103 self .type = meld_type
32104 self .tiles = tuple (tiles ) if tiles else ()
33105 self .opened = opened
@@ -36,17 +108,40 @@ def __init__(
36108 self .from_who = from_who
37109
38110 def __setattr__ (self , name : str , value : object ) -> None :
111+ """Invalidate the tiles_34 cache when tiles are reassigned."""
39112 super ().__setattr__ (name , value )
40113 if name == "tiles" and "tiles_34" in self .__dict__ :
41114 del self .__dict__ ["tiles_34" ]
42115
43116 def __str__ (self ) -> str :
117+ """
118+ Return a human-readable string with meld type and tiles.
119+
120+ >>> from mahjong.meld import Meld
121+ >>> from mahjong.tile import TilesConverter
122+ >>> tiles = TilesConverter.string_to_136_array(man="123")
123+ >>> meld = Meld(meld_type=Meld.CHI, tiles=tiles)
124+ >>> str(meld)
125+ 'Type: chi, Tiles: 123m (0, 4, 8)'
126+ """
44127 return f"Type: { self .type } , Tiles: { TilesConverter .to_one_line_string (self .tiles )} { self .tiles } "
45128
46- # for calls in array
47129 def __repr__ (self ) -> str :
130+ """Return the same representation as __str__."""
48131 return self .__str__ ()
49132
50133 @cached_property
51134 def tiles_34 (self ) -> list [int ]:
135+ """
136+ Convert the meld's 136-format tile indices to 34-format tile indices.
137+
138+ >>> from mahjong.meld import Meld
139+ >>> from mahjong.tile import TilesConverter
140+ >>> tiles = TilesConverter.string_to_136_array(man="111")
141+ >>> meld = Meld(meld_type=Meld.PON, tiles=tiles)
142+ >>> meld.tiles_34
143+ [0, 0, 0]
144+
145+ :return: list of tile indices in 34-format
146+ """
52147 return [x // 4 for x in self .tiles ]
0 commit comments