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.honitsu_analyzer_base import HonitsuAnalyzerBase 

2from mahjong.tile import TilesConverter 

3from mahjong.utils import count_tiles_by_suits, is_honor 

4 

5 

6class ChinitsuAnalyzer(HonitsuAnalyzerBase): 

7 id = "chinitsu" 

8 

9 MIN_DISCARD = 5 

10 MIN_DISCARD_FOR_LESS_SUIT = 10 

11 MAX_MELDS = 3 

12 EARLY_DISCARD_DIVISOR = 3 

13 LESS_SUIT_PERCENTAGE_BORDER = 30 

14 

15 def is_yaku_active(self): 

16 # TODO: in some distant future we may want to analyze menchin as well 

17 if not self.enemy.melds: 

18 return False 

19 

20 total_melds = len(self.enemy.melds) 

21 total_discards = len(self.enemy.discards) 

22 

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

24 if total_discards < ChinitsuAnalyzer.MIN_DISCARD and total_melds < ChinitsuAnalyzer.MAX_MELDS: 

25 return False 

26 

27 # first of all - check melds, they must be all from one suit 

28 current_suit = None 

29 for meld in self.enemy.melds: 

30 tile = meld.tiles[0] 

31 tile_34 = tile // 4 

32 

33 if is_honor(tile_34): 

34 return False 

35 

36 suit = self._get_tile_suit(tile) 

37 if not current_suit: 

38 current_suit = suit 

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

40 return False 

41 

42 assert current_suit 

43 

44 if not self._check_discard_order(current_suit, int(total_discards / ChinitsuAnalyzer.EARLY_DISCARD_DIVISOR)): 

45 return False 

46 

47 # finally let's check if discard is not too full of chosen suit 

48 

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

50 discards_34 = TilesConverter.to_34_array(discards) 

51 result = count_tiles_by_suits(discards_34) 

52 

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

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

55 

56 less_suits = [x for x in suits if x["count"] == suits[0]["count"]] 

57 assert len(less_suits) != 0 

58 

59 current_suit_is_less_suit = False 

60 for less_suit in less_suits: 

61 if less_suit["name"] == current_suit["name"]: 

62 current_suit_is_less_suit = True 

63 

64 if not current_suit_is_less_suit: 

65 return False 

66 

67 less_suit = suits[0] 

68 less_suit_tiles = less_suit["count"] 

69 

70 if total_discards >= ChinitsuAnalyzer.MIN_DISCARD_FOR_LESS_SUIT: 

71 percentage_of_less_suit = (less_suit_tiles / total_discards) * 100 

72 if percentage_of_less_suit > ChinitsuAnalyzer.LESS_SUIT_PERCENTAGE_BORDER: 

73 return False 

74 else: 

75 if len(self.enemy.melds) < 2: 

76 return False 

77 

78 if less_suit_tiles > 1: 

79 return False 

80 

81 self.chosen_suit = current_suit["function"] 

82 return True 

83 

84 def melds_han(self): 

85 return self.enemy.is_open_hand and 5 or 6 

86 

87 def get_safe_tiles_34(self): 

88 if not self.chosen_suit: 

89 return [] 

90 

91 safe_tiles = [] 

92 for x in range(0, 34): 

93 if not self.chosen_suit(x): 

94 safe_tiles.append(x) 

95 

96 return safe_tiles 

97 

98 @staticmethod 

99 # FIXME: remove this method and use proper one from mahjong lib 

100 def _get_tile_suit(tile_136): 

101 suits = sorted( 

102 count_tiles_by_suits(TilesConverter.to_34_array([tile_136])), key=lambda x: x["count"], reverse=True 

103 ) 

104 suit = suits[0] 

105 assert suit["count"] == 1 

106 return suit