MahjongRepository/mahjong

IndexError with certain invalid hands (five of the same tile)

jsettlem opened this issue · 2 comments

Background

While playing around with this library, I was randomly generating and evaluating hands and accidentally made some hands with five of the same tile. In some cases, this leads to an IndexError

Code

from mahjong.hand_calculating.hand import *
from mahjong.hand_calculating.hand_config import *
from mahjong.meld import *
import traceback

calculator = HandCalculator()

config = HandConfig(is_tsumo=True, is_riichi=False, player_wind=SOUTH, round_wind=EAST,
                    options=OptionalRules(has_open_tanyao=True, has_aka_dora=True))

draws = [
    [36, 96, 12, 12, 44, 16, 16, 100, 16, 16, 16, 92, 28, 40, 0],
    [40, 40, 32, 0, 24, 28, 64, 40, 40, 0, 12, 8, 16, 40, 80],
    [88, 28, 76, 32, 40, 44, 76, 24, 76, 76, 100, 48, 88, 76, 20],
    [16, 80, 16, 16, 8, 28, 80, 16, 80, 16, 88, 12, 88, 4, 60],
    [84, 84, 76, 76, 4, 4, 28, 4, 4, 20, 4, 16, 76, 24, 128],
    [52, 124, 52, 52, 56, 80, 124, 76, 52, 84, 52, 56, 64, 124, 80],
    [4, 80, 16, 16, 4, 28, 100, 84, 96, 4, 104, 4, 88, 4, 100],
    [12, 20, 116, 120, 4, 16, 120, 4, 28, 120, 4, 116, 4, 4, 12],
    [76, 48, 76, 88, 76, 48, 12, 100, 16, 76, 8, 48, 88, 76, 4]

]
for draw in draws:
    print("hand", TilesConverter.to_one_line_string(draw[:14], print_aka_dora=True))
    print("winning tile", TilesConverter.to_one_line_string([draw[0]], print_aka_dora=True))
    print("dora", TilesConverter.to_one_line_string(draw[-1:], print_aka_dora=True))

    try:
        result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
        print(result)
    except:
        print(traceback.format_exc())

Result

A whole bunch of stack traces
"C:\Program Files\Python39\python.exe" Z:/Dropbox/PyCharm/mahjong-master/mahjong-master/doc/reproduce_the_mahjong.py
hand 44000008m123p678s
winning tile 1p
dora 1m
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 11340789m222228p
winning tile 2p
dora 3s
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 789m234p22222008s
winning tile 0s
dora 6m
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 234000008m33300s
winning tile 0m
dora 7p
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 222220678m22244s
winning tile 4s
dora 6z
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 00000668p234s555z
winning tile 0p
dora 3s
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 22222008m340789s
winning tile 2m
dora 8s
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 222224068m33444z
winning tile 4m
dora 4m
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range

hand 340m444p22222008s
winning tile 2s
dora 2m
Traceback (most recent call last):
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\doc\reproduce_the_mahjong.py", line 29, in <module>
    result = calculator.estimate_hand_value(draw[:14], win_tile=draw[0], dora_indicators=draw[-1:], config=config)
  File "Z:\Dropbox\PyCharm\mahjong-master\mahjong-master\mahjong\hand_calculating\hand.py", line 533, in estimate_hand_value
    calculated_hand = calculated_hands[0]
IndexError: list index out of range


Process finished with exit code 0

Expected

I'm not sure? Obviously this is an exceptional case, but the library gracefully handles many other exceptional cases so I would expect it to handle this one as well.

Environment

Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)] on win32

Reproduced with both 1.1.11 from PyPI and the latest master commit. The stack traces are based off c3e64e5

Thanks for the report. Please check if it still reproducible on latest master version

I can confirm that all the test cases I provided now result in "hand_not_correct." I generated ~9 billion random hands and encountered no further exceptions.

However, this does lead to a slight inconsistency. Some invalid hands result in "hand_not_correct" and some result in "hand_not_winning." Invalid winning hands do seem to score correctly (at least the couple I tested):

[[12, 13, 4, 5, 6, 7, 7, 28, 36, 40, 44, 92, 96, 100, 0],
 [12, 13, 16, 17, 18, 19, 19, 28, 36, 40, 44, 92, 96, 100, 0],
 [12, 13, 20, 21, 22, 23, 23, 28, 36, 40, 44, 92, 96, 100, 0],
 [12, 13, 17, 17, 17, 17, 17, 17, 36, 40, 44, 92, 96, 100, 0],
 [12, 13, 14, 16, 17, 18, 19, 19, 36, 40, 44, 92, 96, 100, 0]]

hand: 22222448m123p678s
winning tile: 4m
dora: 1m
Result: hand_not_correct

hand: 44055558m123p678s
winning tile: 4m
dora: 1m
Result: hand_not_correct

hand: 44666668m123p678s
winning tile: 4m
dora: 1m
Result: hand_not_winning

hand: 44555555m123p678s
winning tile: 4m
dora: 1m
Result: 1 han, 40 fu

hand: 44405555m123p678s
winning tile: 4m
dora: 1m
Result: 2 han, 30 fu

While testing this, I also realized one_line_string_to_136_array() (understandably) breaks these invalid hands:

hand='22222448m123p678s'
TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True)=[4, 5, 6, 7, 8, 12, 13, 28, 36, 40, 44, 92, 96, 100]
TilesConverter.to_one_line_string(TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True), print_aka_dora=True)='22223448m123p678s'

hand='44055558m123p678s'
TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True)=[12, 13, 16, 17, 18, 19, 20, 28, 36, 40, 44, 92, 96, 100]
TilesConverter.to_one_line_string(TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True), print_aka_dora=True)='44055568m123p678s'

hand='44666668m123p678s'
TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True)=[12, 13, 20, 21, 22, 23, 24, 28, 36, 40, 44, 92, 96, 100]
TilesConverter.to_one_line_string(TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True), print_aka_dora=True)='44666678m123p678s'

hand='44555555m123p678s'
TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True)=[12, 13, 17, 18, 19, 20, 21, 22, 36, 40, 44, 92, 96, 100]
TilesConverter.to_one_line_string(TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True), print_aka_dora=True)='44555666m123p678s'

hand='22222244m123p678s'
TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True)=[4, 5, 6, 7, 8, 9, 12, 13, 36, 40, 44, 92, 96, 100]
TilesConverter.to_one_line_string(TilesConverter.one_line_string_to_136_array(hand, has_aka_dora=True), print_aka_dora=True)='22223344m123p678s'

In each case a five of a kind becomes four of a kind plus the next tile up when passed through one_line_string_to_136_array (so 22222m becomes 22223m).

Obviously these are very exceptional cases and I don't actually need support for them for my use case, so I understand if you want to consider them "out of scope," but I did want to at least mention them.