MahjongRepository / mahjong

Implementation of riichi mahjong related stuff (hand cost, shanten, agari end, etc.)
MIT License
377 stars 38 forks source link

Add more error condition handlers in HandCalculator #34

Closed Dilant closed 3 years ago

Dilant commented 3 years ago

I added a lot of possible error condition handlers in the HandCalculator class. And:

from mahjong.constants import EAST, SOUTH
from mahjong.hand_calculating.hand import HandCalculator
from mahjong.tile import TilesConverter
from mahjong.hand_calculating.hand_config import HandConfig
from mahjong.meld import Meld
from mahjong.locale.text_reporter import TextReporter

calculator = HandCalculator()
reporter = TextReporter(locale='Chinese') # 'English', 'Japanese', ''(Default)
tiles = TilesConverter.string_to_136_array(sou='12345666678999')
win_tile = TilesConverter.string_to_136_array(sou='1')[0]
melds = [Meld(meld_type=Meld.PON, tiles=TilesConverter.string_to_136_array(sou='9999'))]

configs = [
    HandConfig(is_ippatsu=True),  # 1 IWR
    HandConfig(is_tsumo=True, is_chankan=True),  # 2 CKT
    HandConfig(is_rinshan=True),  # 3 RST
    HandConfig(is_haitei=True),  # 4 HAT
    HandConfig(is_tsumo=True, is_houtei=True),  # 5 HOT
    HandConfig(is_tsumo=True, is_haitei=True, is_rinshan=True),  # 6 HAR
    HandConfig(is_houtei=True, is_chankan=True),  # 7 HOC
    HandConfig(is_tsumo=True, is_tenhou=True),  # 8 (no error when player wind is not specified)
    HandConfig(is_tsumo=True, is_tenhou=True, player_wind=SOUTH),  # 9 TND
    HandConfig(is_tenhou=True),  # 10 TWT
    HandConfig(is_tsumo=True, is_chiihou=True),  # 11 (no error when player wind is not specified)
    HandConfig(is_tsumo=True, is_chiihou=True, player_wind=EAST),  # 12 CND
    HandConfig(is_chiihou=True),  # 13 CWT
    HandConfig(is_renhou=True),  # 14 (no error when player wind is not specified)
    HandConfig(is_renhou=True, player_wind=EAST),  # 15 RND
    HandConfig(is_tsumo=True, is_renhou=True)  # 16 RWT
]

configs_with_meld = [
    HandConfig(is_ippatsu=True),  # 17 IWR (so OHI can be removed)
    HandConfig(is_riichi=True),  # 18 OHR
    HandConfig(is_daburu_riichi=True),  # 19 OHD
    HandConfig(is_tsumo=True, is_tenhou=True),  # 20 TWM
    HandConfig(is_tsumo=True, is_chiihou=True),  # 21 no CWM
    HandConfig(is_renhou=True)  # 22 RWM
]

result = calculator.estimate_hand_value(tiles, win_tile)
report = reporter.report(result)
print(report['yaku'].strip())  # Ittsu(2) + Chinitsu(6)

print()
i = 0

for config in configs:
    i += 1
    result = calculator.estimate_hand_value(tiles, win_tile, config=config)
    report = reporter.report(result)
    print(i, report['error'].strip())

for config in configs_with_meld:
    i += 1
    result = calculator.estimate_hand_value(tiles, win_tile, melds, config=config)
    report = reporter.report(result)
    print(i, report['error'].strip())

'''
【役种详情】
一气通贯: 2番
清一色: 6番
总计: 8番

1 错误: 宣告一发必须立直
2 错误: 枪杠不能自摸
3 错误: 岭上开花必须自摸
4 错误: 海底摸月必须自摸
5 错误: 河底捞鱼不能自摸
6 错误: 海底摸月与岭上开花不能共存
7 错误: 河底捞鱼与枪杠不能共存
8 
9 错误: 闲家不能宣告天和
10 错误: 天和必须自摸
11 
12 错误: 庄家不能宣告地和
13 错误: 地和必须自摸
14 
15 错误: 庄家不能宣告人和
16 错误: 人和必须荣和
17 错误: 宣告一发必须立直
18 错误: 非门前清不能立直
19 错误: 非门前清不能两立直
20 错误: 天和不能副露
21 错误: 地和不能副露
22 错误: 人和不能副露
'''
Nihisil commented 3 years ago

Thank you for the fix. While you are here, can you please change error keys to something readable by human? I wanted to do that before.

Like NWT -> no_winning_tile_in_hand.

Also, before submitting PR you can check all linter errors with commands:

Dilant commented 3 years ago

Thank you for the fix. While you are here, can you please change error keys to something readable by human? I wanted to do that before. Like NWT -> no_winning_tile_in_hand

Sure, I'll do that 😃

Also, before submitting PR you can check all linter errors with commands

I forgot it and sorry about that... Checked just now, and test cases related to these (maybe breaking) changes will be fixed, e.g.:

234456m66p12344s 4s [Tsumo, Haitei]

Yaku Details
Menzen Tsumo: 1 Han
Haitei Raoyue: 1 Han
Total: 2 Han

Fu Details
Base Fu: 20 Fu
Simple Closed Triplet: 4 Fu
Self Draw: 2 Fu
Total: 30 Fu
def test_is_haitei(self):
    hand = HandCalculator()

    tiles = self._string_to_136_array(sou="123444", man="234456", pin="66")
    win_tile = self._string_to_136_tile(sou="4")
-   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_haitei=True))
+   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_tsumo=True, is_haitei=True))
    self.assertEqual(result.error, None)
-   self.assertEqual(result.han, 1)
-   self.assertEqual(result.fu, 40)
-   self.assertEqual(len(result.yaku), 1)
+   self.assertEqual(result.han, 2)
+   self.assertEqual(result.fu, 30)
+   self.assertEqual(len(result.yaku), 2)

Also, if possible, I'll try to add a unit test for these new error keys.

Thanks a lot!

Dilant commented 3 years ago

change error keys to something readable by human check all linter errors test cases related to these (maybe breaking) changes will be fixed add a unit test for these new error keys

All done! 😆

Nihisil commented 3 years ago

Thank you for these changes.

I didn't get yet why old tests were changed, since these new fixes shouldn't affect them.

In next 1-2 days I will check it detailed and will merge this PR.

Dilant commented 3 years ago

I didn't get yet why old tests were changed, since these new fixes shouldn't affect them.

Here's the thing:

# haitei must be tsumo
-   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_haitei=True))
+   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_tsumo=True, is_haitei=True))

# houtei must not be tsumo
-   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_houtei=True))
+   result = hand.estimate_hand_value(tiles, win_tile, config=self._make_hand_config(is_tsumo=False, is_houtei=True))
# chankan: how can others make a shouminkan of "4s"? 
    tiles = self._string_to_136_array(sou="123444", man="234456", pin="66")
-   win_tile = self._string_to_136_tile(sou="4")
+   win_tile = self._string_to_136_tile(sou="1")

# rinshan: you should call a kan (and then tsumo)
-   tiles = self._string_to_136_array(sou="123444", man="234456", pin="66")
-   win_tile = self._string_to_136_tile(sou="4")
+   tiles = self._string_to_136_array(sou="1234444", man="234456", pin="66")
+   win_tile = self._string_to_136_tile(sou="1")
+   melds = [self._make_meld(Meld.KAN, is_open=False, sou="4444")]
# 1112223334445z 5z tenhou & tsuisou & daisushi & suuankou tanki
# must be tsumo because of tenhou
    self.assertEqual(result.cost["main"], 96000)
    self.assertEqual(result.cost["additional"], 48000)

# 5z -11-z -22-z -33-z -44-z 5z suukantsu & tsuisou & daisushi & suuankou tanki
# can be tsumo or ron (here's ron)
    self.assertEqual(result.cost["main"], 192000)
Nihisil commented 3 years ago

Yep, figured it out. Thank you for the contribution! Do you need new version with these fixes released in pip?

Dilant commented 3 years ago

Do you need new version with these fixes released in pip?

Maybe not now. I think it's better to wait for Japanese translations, and then everything is ready.