Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from game.ai.defence.yaku_analyzer.chinitsu import ChinitsuAnalyzer 

2from game.ai.defence.yaku_analyzer.honitsu_analyzer_base import HonitsuAnalyzerBase 

3from game.ai.helpers.defence import TileDanger 

4from mahjong.tile import TilesConverter 

5from mahjong.utils import count_tiles_by_suits, is_honor 

6 

7 

8class HonitsuAnalyzer(HonitsuAnalyzerBase): 

9 id = "honitsu" 

10 

11 MIN_DISCARD = 6 

12 MAX_MELDS = 3 

13 EARLY_DISCARD_DIVISOR = 4 

14 LESS_SUIT_PERCENTAGE_BORDER = 20 

15 HONORS_PERCENTAGE_BORDER = 30 

16 

17 def is_yaku_active(self): 

18 # TODO: in some distant future we may want to analyze menhon as well 

19 if not self.enemy.melds: 

20 return False 

21 

22 total_melds = len(self.enemy.melds) 

23 total_discards = len(self.enemy.discards) 

24 

25 # let's check if there is too little info to analyze 

26 if total_discards < HonitsuAnalyzer.MIN_DISCARD and total_melds < HonitsuAnalyzer.MAX_MELDS: 

27 return False 

28 

29 # first of all - check melds, they must be all from one suit or honors 

30 current_suit = None 

31 for meld in self.enemy.melds: 

32 tile = meld.tiles[0] 

33 tile_34 = tile // 4 

34 

35 if is_honor(tile_34): 

36 continue 

37 

38 suit = ChinitsuAnalyzer._get_tile_suit(tile) 

39 if not current_suit: 

40 current_suit = suit 

41 elif suit["name"] != current_suit["name"]: 

42 return False 

43 

44 # let's check discards 

45 discards = [x.value for x in self.enemy.discards] 

46 discards_34 = TilesConverter.to_34_array(discards) 

47 result = count_tiles_by_suits(discards_34) 

48 

49 honors = [x for x in result if x["name"] == "honor"][0] 

50 suits = [x for x in result if x["name"] != "honor"] 

51 suits = sorted(suits, key=lambda x: x["count"], reverse=False) 

52 

53 less_suit = suits[0] 

54 less_suit_tiles = less_suit["count"] 

55 percentage_of_less_suit = (less_suit_tiles / total_discards) * 100 

56 percentage_of_honor_tiles = (honors["count"] / total_discards) * 100 

57 

58 # there is not too much one suit + honor tiles in the discard 

59 # so we can tell that user trying to collect honitsu 

60 if ( 

61 percentage_of_less_suit <= HonitsuAnalyzer.LESS_SUIT_PERCENTAGE_BORDER 

62 and percentage_of_honor_tiles <= HonitsuAnalyzer.HONORS_PERCENTAGE_BORDER 

63 ): 

64 if not current_suit: 

65 current_suit = less_suit 

66 elif current_suit != less_suit: 

67 return False 

68 

69 # still cannot determine the suit - this is probably not honitsu 

70 if not current_suit: 

71 return False 

72 

73 if not self._check_discard_order(current_suit, int(total_discards / HonitsuAnalyzer.EARLY_DISCARD_DIVISOR)): 

74 return False 

75 

76 # all checks have passed - assume this is honitsu 

77 self.chosen_suit = current_suit["function"] 

78 return True 

79 

80 def melds_han(self): 

81 return self.enemy.is_open_hand and 2 or 3 

82 

83 def get_safe_tiles_34(self): 

84 if not self.chosen_suit: 

85 return [] 

86 

87 safe_tiles = [] 

88 for x in range(0, 34): 

89 if not self.chosen_suit(x) and not is_honor(x): 

90 safe_tiles.append(x) 

91 

92 return safe_tiles 

93 

94 def get_bonus_danger(self, tile_136, number_of_revealed_tiles): 

95 tile_34 = tile_136 // 4 

96 

97 if is_honor(tile_34): 

98 if number_of_revealed_tiles == 4: 

99 return [] 

100 elif number_of_revealed_tiles == 3: 

101 return [TileDanger.HONITSU_THIRD_HONOR_BONUS_DANGER] 

102 elif number_of_revealed_tiles == 2: 

103 return [TileDanger.HONITSU_SECOND_HONOR_BONUS_DANGER] 

104 else: 

105 return [TileDanger.HONITSU_SHONPAI_HONOR_BONUS_DANGER] 

106 

107 return [] 

108 

109 def is_absorbed(self, possible_yaku, tile_34=None): 

110 return self._is_absorbed_by(possible_yaku, ChinitsuAnalyzer.id, tile_34)