Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion mahjong/hand_calculating/hand.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Collection
from typing import TypedDict

from mahjong.constants import AKA_DORAS, CHUN, HAKU, HATSU
from mahjong.constants import AKA_DORAS, CHUN, HAKU, HATSU, NORTH
from mahjong.hand_calculating.divider import HandDivider
from mahjong.hand_calculating.fu import FuCalculator, FuDetail
from mahjong.hand_calculating.hand_config import HandConfig
Expand Down Expand Up @@ -101,6 +101,7 @@ def estimate_hand_value(
config: HandConfig | None = None,
scores_calculator_factory: type[ScoresCalculator] = ScoresCalculator,
ura_dora_indicators: Collection[int] | None = None,
num_nuki_dora: int = 0,
) -> HandResponse:
"""
Estimate the point value of a winning hand.
Expand Down Expand Up @@ -177,6 +178,7 @@ def estimate_hand_value(
:class:`~mahjong.hand_calculating.scores.Aotenjou` for aotenjou (limitless) scoring
:param ura_dora_indicators: ura dora indicator tile indices in 136-format
(counted only when riichi or double riichi is declared)
:param num_nuki_dora: the number of nuki dora (north wind extraction)
:return: :class:`~mahjong.hand_calculating.hand_response.HandResponse` with scoring
details on success, or with :attr:`~mahjong.hand_calculating.hand_response.HandResponse.error`
set on failure
Expand Down Expand Up @@ -281,6 +283,8 @@ def estimate_hand_value(
# precompute dora counts, invariant across all hand decompositions
dora_count_map = build_dora_count_map(dora_indicators)
precomputed_dora = count_dora_for_hand(tiles_34, dora_count_map)
if num_nuki_dora > 0:
precomputed_dora += dora_count_map.get(NORTH, 0) * num_nuki_dora + num_nuki_dora

precomputed_aka_dora = 0
if config.options.has_aka_dora:
Expand All @@ -290,6 +294,8 @@ def estimate_hand_value(
if config.is_riichi or config.is_daburu_riichi:
ura_count_map = build_dora_count_map(ura_dora_indicators)
precomputed_ura_dora = count_dora_for_hand(tiles_34, ura_count_map)
if num_nuki_dora > 0:
precomputed_ura_dora += ura_count_map.get(NORTH, 0) * num_nuki_dora

yakuhai_seat_wind_yaku = (
config.yaku.seat_wind_east,
Expand Down
74 changes: 74 additions & 0 deletions tests/hand_calculating/tests_yaku_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,80 @@ def test_aka_dora() -> None:
assert hand_calculation.han == 6


@pytest.mark.parametrize(
("num_nuki_dora", "num_dora_indicators", "han"),
[
(0, 0, 1),
(1, 0, 2),
(2, 0, 3),
(3, 0, 4),
(4, 0, 5),
(-1, 0, 1),
(0, 1, 1),
(1, 1, 3),
(2, 1, 5),
(4, 1, 9),
(0, 2, 1),
(1, 2, 4),
(4, 4, 21),
(-1, 4, 1),
],
)
def test_nuki_dora_with_dora_indicators(num_nuki_dora: int, num_dora_indicators: int, han: int) -> None:
tiles = TilesConverter.string_to_136_array(sou="345", pin="456", man="12355599", has_aka_dora=True)
win_tile = _string_to_136_tile(man="9")

hand_config = HandConfig(is_tsumo=True, options=OptionalRules(has_aka_dora=True))
dora_indicators = TilesConverter.string_to_136_array(honors="3" * num_dora_indicators)

result = HandCalculator.estimate_hand_value(
tiles,
win_tile,
config=hand_config,
dora_indicators=dora_indicators,
num_nuki_dora=num_nuki_dora,
)
assert result.error is None
assert result.han == han


@pytest.mark.parametrize(
("num_nuki_dora", "num_ura_dora_indicators", "han"),
[
(0, 0, 1),
(1, 0, 2),
(2, 0, 3),
(3, 0, 4),
(4, 0, 5),
(-1, 0, 1),
(0, 1, 1),
(1, 1, 3),
(2, 1, 5),
(4, 1, 9),
(0, 2, 1),
(1, 2, 4),
(4, 4, 21),
(-1, 4, 1),
],
)
def test_nuki_dora_with_ura_dora_indicators(num_nuki_dora: int, num_ura_dora_indicators: int, han: int) -> None:
tiles = TilesConverter.string_to_136_array(sou="345", pin="456", man="12355599", has_aka_dora=True)
win_tile = _string_to_136_tile(man="9")

hand_config = HandConfig(is_riichi=True, options=OptionalRules(has_aka_dora=True))
ura_dora_indicators = TilesConverter.string_to_136_array(honors="3" * num_ura_dora_indicators)

result = HandCalculator.estimate_hand_value(
tiles,
win_tile,
config=hand_config,
ura_dora_indicators=ura_dora_indicators,
num_nuki_dora=num_nuki_dora,
)
assert result.error is None
assert result.han == han


class TestYakuBaseClass:
"""
Test the abstract Yaku base class methods
Expand Down
Loading