mahjong.utils

Utility functions for tile classification, dora counting, and hand structure analysis.

Tile classification

Functions that identify suit membership and tile properties, all operating on the 34-format tile index:

Dora counting

Functions for calculating dora (bonus tiles) in a hand:

Hand structure analysis

Functions that inspect sets (melds) within a hand decomposition:

mahjong.utils.is_aka_dora(tile_136, aka_enabled)[source]

Check if a tile is an aka dora (red five).

>>> from mahjong.utils import is_aka_dora
>>> is_aka_dora(16, aka_enabled=True)
True
>>> is_aka_dora(16, aka_enabled=False)
False
Parameters:
  • tile_136 (int) – tile index in 136-format

  • aka_enabled (bool) – whether aka dora rules are active

Returns:

True if the tile is a red five and aka dora is enabled

Return type:

bool

mahjong.utils.plus_dora(tile_136, dora_indicators_136, add_aka_dora=False)[source]

Calculate the number of dora for a single tile given dora indicators.

Each dora indicator reveals which tile type is dora (the next tile in sequence). If the tile matches, the count increments once per matching indicator. Optionally includes aka dora (red five) as an additional bonus.

Indicator 0 (1m) points to 2m as dora, so tile 4 (2m) scores one dora:

>>> from mahjong.utils import plus_dora
>>> plus_dora(4, [0])
1

Two indicators each pointing to 2m as dora:

>>> plus_dora(4, [0, 1])
2
Parameters:
  • tile_136 (int) – tile index in 136-format

  • dora_indicators_136 (Collection[int]) – collection of dora indicator tile indices in 136-format

  • add_aka_dora (bool) – include aka dora (red five) bonus in the count

Returns:

total dora count for this tile

Return type:

int

mahjong.utils.build_dora_count_map(dora_indicators_136)[source]

Build a mapping from tile type (34-format) to dora count for the given indicators.

Indicator 0 (first copy of 1m) points to dora 2m (index 1):

>>> from mahjong.utils import build_dora_count_map
>>> build_dora_count_map([0])
{1: 1}

Two indicators each pointing to the same dora:

>>> build_dora_count_map([0, 1])
{1: 2}
Parameters:

dora_indicators_136 (Collection[int]) – collection of dora indicator tile indices in 136-format

Returns:

dictionary mapping 34-format tile index to dora count

Return type:

dict[int, int]

mahjong.utils.count_dora_for_hand(tiles_34, dora_count_map)[source]

Count total dora in a hand using a precomputed dora count map.

Three copies of 2m with one dora indicator pointing to 2m:

>>> from mahjong.utils import count_dora_for_hand, build_dora_count_map
>>> tiles_34 = [0] * 34
>>> tiles_34[1] = 3
>>> dora_map = build_dora_count_map([0])
>>> count_dora_for_hand(tiles_34, dora_map)
3
Parameters:
  • tiles_34 (Sequence[int]) – hand in 34-format tile count array

  • dora_count_map (dict[int, int]) – mapping from 34-format tile index to dora count

Returns:

total dora count for the hand

Return type:

int

mahjong.utils.is_chi(item)[source]

Check if a set of tiles forms a chi (sequence of three consecutive tiles).

The indices must be in ascending order.

>>> from mahjong.utils import is_chi
>>> is_chi([0, 1, 2])
True
>>> is_chi([0, 0, 0])
False
>>> is_chi([2, 1, 0])
False
Parameters:

item (Sequence[int]) – array of tile indices in 34-format

Returns:

True if the tiles form a chi

Return type:

bool

mahjong.utils.is_pon(item)[source]

Check if a set of tiles forms a pon (triplet of identical tiles).

>>> from mahjong.utils import is_pon
>>> is_pon([0, 0, 0])
True
>>> is_pon([0, 1, 2])
False
Parameters:

item (Sequence[int]) – array of tile indices in 34-format

Returns:

True if the tiles form a pon

Return type:

bool

mahjong.utils.is_kan(item)[source]

Check if a set of tiles forms a kan (quad of identical tiles).

>>> from mahjong.utils import is_kan
>>> is_kan([0, 0, 0, 0])
True
>>> is_kan([0, 0, 0])
False
Parameters:

item (Sequence[int]) – array of tile indices in 34-format

Returns:

True if the tiles form a kan

Return type:

bool

mahjong.utils.is_pon_or_kan(item)[source]

Check if a set of tiles forms a pon (triplet) or a kan (quad).

>>> from mahjong.utils import is_pon_or_kan
>>> is_pon_or_kan([0, 0, 0])
True
>>> is_pon_or_kan([0, 0, 0, 0])
True
>>> is_pon_or_kan([0, 1, 2])
False
Parameters:

item (Sequence[int]) – array of tile indices in 34-format

Returns:

True if the tiles form a pon or kan

Return type:

bool

mahjong.utils.is_pair(item)[source]

Check if a set of tiles forms a pair (two tiles).

>>> from mahjong.utils import is_pair
>>> is_pair([0, 0])
True
>>> is_pair([0, 0, 0])
False
Parameters:

item (Sequence[int]) – array of tile indices in 34-format

Returns:

True if the tiles form a pair

Return type:

bool

mahjong.utils.has_pon_or_kan_of(hand, tile)[source]

Check if hand contains a pon or kan of the specified tile.

>>> from mahjong.utils import has_pon_or_kan_of
>>> has_pon_or_kan_of([[0, 0, 0], [1, 2, 3]], 0)
True
>>> has_pon_or_kan_of([[0, 1, 2], [3, 3, 3]], 0)
False
Parameters:
  • hand (Collection[Sequence[int]]) – collection of tile sets, each a sequence of tile indices in 34-format

  • tile (int) – tile index in 34-format to search for

Returns:

True if the hand contains a pon or kan of the given tile

Return type:

bool

mahjong.utils.classify_hand_suits(hand)[source]

Classify the tile sets in a hand by suit, returning a bitmask and honor count.

The bitmask bits are: 1 for sou, 2 for pin, 4 for man.

>>> from mahjong.utils import classify_hand_suits
>>> classify_hand_suits([[0, 1, 2], [27, 27, 27]])
(4, 1)
Parameters:

hand (Collection[Sequence[int]]) – collection of tile sets, each a sequence of tile indices in 34-format

Returns:

tuple of (suit_mask, honor_count)

Return type:

tuple[int, int]

mahjong.utils.is_man(tile)[source]

Check if a tile belongs to the man (characters) suit.

>>> from mahjong.utils import is_man
>>> is_man(0)
True
>>> is_man(9)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is a man tile (indices 0-8)

Return type:

bool

mahjong.utils.is_pin(tile)[source]

Check if a tile belongs to the pin (circles) suit.

>>> from mahjong.utils import is_pin
>>> is_pin(9)
True
>>> is_pin(0)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is a pin tile (indices 9-17)

Return type:

bool

mahjong.utils.is_sou(tile)[source]

Check if a tile belongs to the sou (bamboo) suit.

>>> from mahjong.utils import is_sou
>>> is_sou(18)
True
>>> is_sou(0)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is a sou tile (indices 18-26)

Return type:

bool

mahjong.utils.is_honor(tile)[source]

Check if a tile is an honor tile (wind or dragon).

>>> from mahjong.utils import is_honor
>>> is_honor(27)
True
>>> is_honor(26)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is an honor tile (indices 27-33)

Return type:

bool

mahjong.utils.is_sangenpai(tile_34)[source]

Check if a tile is a dragon (sangenpai: haku, hatsu, or chun).

>>> from mahjong.utils import is_sangenpai
>>> is_sangenpai(31)
True
>>> is_sangenpai(27)
False
Parameters:

tile_34 (int) – tile index in 34-format

Returns:

True if the tile is a dragon (indices 31-33)

Return type:

bool

mahjong.utils.is_terminal(tile)[source]

Check if a tile is a terminal (1 or 9 of any suited tile).

>>> from mahjong.utils import is_terminal
>>> is_terminal(0)
True
>>> is_terminal(8)
True
>>> is_terminal(1)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is a terminal

Return type:

bool

mahjong.utils.is_dora_indicator_for_terminal(tile)[source]

Check if a tile is a dora indicator that points to a terminal.

The tiles with ranks 8 and 9 in each suit (indices 7, 8, 16, 17, 25, 26) are dora indicators for terminals: a rank-9 indicator wraps around to make rank 1 the dora, and a rank-8 indicator points to rank 9 as the dora.

>>> from mahjong.utils import is_dora_indicator_for_terminal
>>> is_dora_indicator_for_terminal(7)
True
>>> is_dora_indicator_for_terminal(0)
False
Parameters:

tile (int) – tile index in 34-format

Returns:

True if the tile is a dora indicator for a terminal

Return type:

bool

mahjong.utils.contains_terminals(hand_set)[source]

Check if a set of tiles contains any terminal tiles.

>>> from mahjong.utils import contains_terminals
>>> contains_terminals([0, 1, 2])
True
>>> contains_terminals([1, 2, 3])
False
Parameters:

hand_set (Collection[int]) – collection of tile indices in 34-format

Returns:

True if any tile in the set is a terminal

Return type:

bool

mahjong.utils.simplify(tile)[source]

Reduce a tile index to its rank within its suit (0-8).

>>> from mahjong.utils import simplify
>>> simplify(0)
0
>>> simplify(9)
0
>>> simplify(20)
2
Parameters:

tile (int) – tile index in 34-format

Returns:

rank within the suit (0-8)

Return type:

int

mahjong.utils.find_isolated_tile_indices(hand_34)[source]

Find tiles that are isolated (absent from the hand with no adjacent neighbors).

A suited tile is isolated if neither the tile itself, its left neighbor (-1), nor its right neighbor (+1) is present in the hand. Honor tiles are isolated if they are simply not present.

>>> from mahjong.utils import find_isolated_tile_indices
>>> hand_34 = [4, 0, 0, 0, 0, 0, 0, 0, 0] + [0] * 25
>>> 1 not in find_isolated_tile_indices(hand_34)
True
>>> 2 in find_isolated_tile_indices(hand_34)
True
Parameters:

hand_34 (Sequence[int]) – hand in 34-format tile count array

Returns:

list of 34-format tile indices that are isolated

Return type:

list[int]

mahjong.utils.is_tile_strictly_isolated(hand_34, tile_34)[source]

Check if a tile is strictly isolated (no tiles within ±2 distance).

A tile is strictly isolated if no copies (beyond the one being checked) and no neighbors at distances -2, -1, +1, +2 exist in the hand. Honor tiles only need to check for duplicates.

>>> from mahjong.utils import is_tile_strictly_isolated
>>> hand_34 = [1, 0, 0, 0, 0, 0, 0, 0, 0] + [0] * 25
>>> is_tile_strictly_isolated(hand_34, 0)
True
>>> hand_34[2] = 1
>>> is_tile_strictly_isolated(hand_34, 0)
False
Parameters:
  • hand_34 (Sequence[int]) – hand in 34-format tile count array

  • tile_34 (int) – tile index in 34-format to check

Returns:

True if the tile is strictly isolated

Return type:

bool

class mahjong.utils.SuitCount[source]

Bases: TypedDict

Per-suit tile count entry returned by count_tiles_by_suits().

Parameters:
  • count – number of tiles in this suit

  • name – suit name ("sou", "man", "pin", or "honor")

  • function – predicate that tests whether a 34-format tile index belongs to this suit

mahjong.utils.count_tiles_by_suits(tiles_34)[source]

Separate tiles by suit and count them.

>>> from mahjong.utils import count_tiles_by_suits
>>> tiles_34 = [0] * 34
>>> tiles_34[0] = 3
>>> tiles_34[27] = 2
>>> result = count_tiles_by_suits(tiles_34)
>>> [(s["name"], s["count"]) for s in result]
[('sou', 0), ('man', 3), ('pin', 0), ('honor', 2)]
Parameters:

tiles_34 (Sequence[int]) – hand in 34-format tile count array

Returns:

list of SuitCount entries, one per suit

Return type:

list[SuitCount]