hanabiapp.game.agent.intentional_agent

  1import pdb
  2import pprint
  3import copy
  4from .agent import Agent
  5from ..action import Action
  6
  7class IntentionalAgent(Agent):
  8  """
  9  意図的な(ルールベースの)エージェントを表現するクラス.
 10  
 11  エージェントは知識とボードの状態に基づいて意図的なアクションを決定する.
 12  """
 13  def __init__(self, name, player_number,is_cui=True):
 14    """
 15    IntentionalAgent のコンストラクタ.
 16    
 17    Args:
 18        name (str): エージェントの名前
 19        player_number (int): プレイヤーの番号
 20        is_cui (bool, optional): CUIモードかどうか(デフォルトはTrue)
 21    """
 22    super().__init__(name, player_number)
 23    self.need_hle_convert= False
 24    self.hints = {}
 25    self.gothint = None
 26    self.last_knowledge = []
 27    self.last_board = []
 28    self.explanation = []
 29  
 30  def format_card(self, card):
 31    """
 32    カードの色とランクを色コード+数字でフォーマットする(表示用).
 33    
 34    Args:
 35        card (tuple): カードの色と数字のタプル
 36    
 37    Returns:
 38        str: フォーマットされたカードの文字列(例: "B3")
 39    """
 40    color_code = self.game_const.COLOR_NAMES[card[0]]  # カードの色コード(例: B)
 41    number = card[1]  # カードの数字
 42    return f"{color_code}{number}" # カードの色コード+数字(例: B3)
 43
 44  def format_intention(self, intention):
 45        """
 46        プレイヤーの意図をフォーマットして文字列に変換する関数.
 47        
 48        Args:
 49            intention (str or None): 意図の種類(PLAY, DISCARD, CANDISCARD など)
 50        
 51        Returns:
 52            str: 整形された意図の文字列
 53        """
 54        # invalidhintなどの文字列の場合はそのまま返す
 55        if isinstance(intention, str):
 56            return intention
 57        # 意図のフォーマット表示
 58        if intention == "PLAY":
 59            return "Play"
 60        elif intention == "DISCARD":
 61            return "Discard"
 62        elif intention == "CANDISCARD":
 63            return "CanDiscard"
 64        return "Keep"
 65  
 66  def format_knowledge(self, k):
 67        """
 68        プレイヤーの知識をフォーマットして文字列に変換する関数.
 69        
 70        Args:
 71            k (dict): カードの知識情報
 72        
 73        Returns:
 74            str: 整形された知識の文字列
 75        """
 76        # プレイヤーの知識をフォーマットして文字列に変換するメソッド
 77        # k: カードに関する知識を表す辞書(各色とランクの情報)
 78        result = ""  # フォーマットされた知識を保持する文字列
 79        for col in self.game_ins.game_const.ALL_COLORS:
 80            for i, cnt in enumerate(k[col]):
 81                if cnt > 0:
 82                    # カウントが0より大きい場合のみ情報を追加
 83                    # 色名とランク、そしてその可能性の数を文字列に追加
 84                    result += self.game_ins.game_const.COLOR_NAMES[col] + " " + str(i + 1) + ": " + str(cnt) + "n"
 85        return result
 86
 87  def print_l_2d(self,name,l_2d):
 88    """
 89    2次元リストのデータを整形して出力する関数(CUIモードのみ).
 90    
 91    Args:
 92        name (str): 出力時のタイトル
 93        l_2d (list): 出力する2次元リスト
 94    """
 95    if self.is_cui:
 96      print(f"{name}:")
 97      pprint.pprint(l_2d,width=100,compact=True)
 98  
 99  def act(self, game_ins):
100    """
101    プレイヤーの次のアクションを決定する関数.
102    
103    Args:
104        game_ins (Game): ゲームのインスタンス
105    
106    Returns:
107        Action: 選択されたアクション
108    """
109    #===============================================================================================
110    # もともと原作では引数で受け取っていた変数.
111    self.game_ins = game_ins
112    player_number = self.player_number
113    hands = self.game_ins.hands
114    knowledge = self.game_ins.knowledge
115    trash = self.game_ins.trash
116    board = self.game_ins.board
117    hints = self.game_ins.hints
118    #===============================================================================================
119
120    # 変数の初期化
121    handsize = len(hands[player_number])
122    possible = []
123    result = None
124    self.explanation = []  # プレイヤーの行動を説明するための初期化
125    self.explanation.append(["oppHand:"] + list(map(self.format_card, hands[1 - player_number])))
126    self.gothint = None
127
128    #===============================================================================================
129    # 己のknowledgeから,持ちうるカード((色,数字)のタプル)をリストアップ(枚数情報除く)
130    for k in knowledge[self.player_number]:
131      possible.append(self.get_possible(k))
132    #===============================================================================================
133    playable_cards= [] # プレイ可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
134    discardable_cards = [] # 廃棄可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
135    for i, card in enumerate(possible):
136      # The above code is using an f-string to print out the value of the variable `card`. The syntax
137      # `{card=}` is a shorthand way to include both the variable name and its value in the output.
138      # print(f"{card=}")
139      # ボード上のその色のカードが次に必要な数字かどうかを確認
140      if self.playable(card,self.game_ins.board):
141        playable_cards.append(i)
142        self.print_log("Intentional:プレイ可能カードを持っていれるので,それをプレイします.")
143        # The code is creating a `result` object with an action of playing a card at a specific
144        # position `i`.
145        result = Action(self.game_ins.game_const.PLAY, card_position=i)
146        # result = Action(self.game_ins.game_const.PLAY, card_position=self.game_ins.random.choice(playable_cards))
147      if self.discardable(card,self.game_ins.board):
148        # possibleの中から,破棄可能なカードが見つかったら,それをdiscardable_cardsにメモしておく
149        discardable_cards.append(i)
150
151    # print(f"{playable_cards=}")
152    # もし自分が破棄可能カードを持っていて,かつヒントトークンを回復させる余地(ヒントトークンが8個未満)があれば,破棄する
153    if discardable_cards and hints < 8 and not result:
154      self.print_log("Intentional:破棄可能カードを持っており,かつヒントトークンを回復できるので,それを捨てます.")
155      result = Action(self.game_ins.game_const.DISCARD, card_position=self.game_ins.random.choice(discardable_cards))
156
157    
158    #===============================================================================================
159    # 相手視点のゴールの設定
160    opponent_playables = [] # 相手の手札のうち,プレイ可能なカード群
161    opponent_useless = [] # 相手の手札のうち,ボード上ですでに出ている無価値なカード群
162    opponent_potentially_discardables = [] # 相手の手札のうち,ボード上で"出ていない"かつ"5未満"のカード群を将来的に捨てれるとみなした,潜在的な捨てカード候補群
163
164    # すでに場にでているカードの情報を取得
165    board_and_trash_cards = []
166    # board辞書を(色, 数字)のタプルのリストに変換して,すでに場に出たカードをメモに加える
167    for color, count in board.items():
168        for number in range(1, count + 1):
169            board_and_trash_cards .append((color, number))
170    # self.print_log(f"board:{board}")
171    board_and_trash_cards  += trash  # 捨てられたカードもリストに追加
172    # self.print_log(f"board_and_trash_cards :{board_and_trash_cards }")
173
174    intentions = [None for _ in range(handsize)]  # 各カード,普通意図するゴールを保持するためのリストを初期化
175
176    # self.print_log(f"hands:{hands}")
177    # 各プレイヤーの手札を確認
178    for i, hand in enumerate(hands.items()):
179      if i != player_number:  # 他プレイヤーの手札をチェック(自分自身ではない)
180        # hand:(1, [(2, 2), (3, 3), (3, 1), (0, 2), (2, 1)])というタプルなので,手札部分だけ取り出す
181        for j, card in enumerate(hand[1]):
182          color, number = card
183          # --- プレイ可能かどうかのチェック ---
184          if board[color] + 1 == number:
185            # 相手の手札のあるカードがプレイ可能な場合、プレイ可能リストに追加
186            opponent_playables.append(card)
187            # ヒントを出したらプレイ可能という意図を込めたアクションをゴールとしてintentionsにセット
188            # 実際にアクションをとるわけではないので,Actionオブジェクトではなく,"PLAY"をセット
189            intentions[j] = "PLAY"
190          
191          # --- 既に無価値なカードのチェック ---
192          if board[color] >= number:
193            # ボード上ですでに出ているカードは無価値とみなし,無価値リストに追加
194            opponent_useless.append(card)
195            # ヒントを出したら無価値カード,という意図を込めたアクションをゴールとしてintentionsにセット
196            # ただし,意図としてはプレイの方が強いので,プレイの意図がない場合のみ無価値カードとして意図をセット
197            if not intentions[j]:
198              intentions[j] = "DISCARD"
199
200          # --- 将来的に捨てても良いカードのチェック ---
201          if number < 5 and (color, number) not in board_and_trash_cards:
202            # ボードにまだないカードで5未満のものは、将来的に捨てても良いと判断
203            opponent_potentially_discardables.append(card)
204            # ただし,意図としてはプレイ&無価値示唆の方が強いので,プレイ&無価値示唆の意図がない場合のみ潜在的な捨てカード候補群として意図をセット
205            if not intentions[j]:
206              intentions[j] = "CANDISCARD"
207
208    # self.print_log(f"opponent_playables:{opponent_playables}")
209    # self.print_log(f"opponent_useless:{opponent_useless}")
210    # self.print_log(f"opponent_potentially_discardables:{opponent_potentially_discardables}")
211
212    self.explanation.append(["intentions:"] + list(map(self.format_intention, intentions)))
213
214    #===============================================================================================
215    # スモールステップ 5: 相手の行動を予測する
216    if hints > 0:
217      valid_hints = []  # 有効なヒントを保存するリスト
218      
219      # 各色に対してヒントを与える可能性をチェック
220      for color in self.game_ins.game_const.ALL_COLORS:
221  
222        assumed_color_hint_action = ("HINT_COLOR", color) # 色ヒントを仮定
223        # self.print_log(f"||||||||||||||pretend :{assumed_color_hint_action[0]},{self.game_ins.game_const.COLOR_NAMES[assumed_color_hint_action[1]]}||||||||||||||")
224        opponent_knowledge = knowledge[1 - player_number]  # 相手の知識を取得
225        opponent_hands = hands[1 - player_number]  # 相手の手札を取得
226
227        is_valid, score, explanation = self.pretend(assumed_color_hint_action, opponent_knowledge, intentions, opponent_hands, board)
228        # self.print_log(f"is_valid:{is_valid},score:{score},explanation:{explanation}")
229
230        # 各仮定結果を説明リストに追加
231        self.explanation.append([f"Prediction for: Hint Color {self.game_ins.game_const.COLOR_NAMES[color]}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
232
233        if is_valid:
234            valid_hints.append((assumed_color_hint_action, score))  # 有効であればヒントを保存
235            
236      # 各ランクに対してヒントを与える可能性をチェック
237      for rank in range(1, 6):
238          action = ("HINT_NUMBER", rank)
239          # self.print_log(f"||||||||||||||pretend :{action[0]},{action[1]}||||||||||||||")
240          # ヒントを仮定して評価
241          is_valid, score, explanation = self.pretend(action, knowledge[1 - player_number], intentions, hands[1 - player_number], board)
242          # 各仮定結果を説明リストに追加
243          self.explanation.append([f"Prediction for: Hint Rank {rank}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
244          if is_valid:
245              valid_hints.append((action, score))  # 有効であればヒントを保存
246      
247
248      
249      # self.print_log(f"valid_hints:{valid_hints}")
250      # ヒントの候補が存在し、まだ行動が決まっていない場合
251      if valid_hints and not result:
252          # スコアが高い順にソートして最良のヒントを選択
253          valid_hints.sort(key=lambda x: -x[1])
254          best_action, _ = valid_hints[0] # 最良のヒントを取得
255          # self.print_log(f"best_action:{best_action}")
256          # 最良のヒントをアクションとして設定
257          if best_action[0] == "HINT_COLOR":
258              self.print_log("Intentional:最良の色ヒントを出します.")
259              result = Action(game_ins.game_const.HINT_COLOR, pnr = 1 - player_number, color = best_action[1])
260          else:
261              self.print_log("Intentional:最良の数字ヒントを出します.")
262              result = Action(game_ins.game_const.HINT_NUMBER, pnr = 1 - player_number, number = best_action[1])
263
264    #===============================================================================================
265
266    # --- スモールステップ 6: 最終的な捨てる行動のスコアリングと選択 ---
267    
268    # self.explanation.append(["My Knowledge"] + list(map(self.format_knowledge, knowledge[player_number]))) # みにくいknowledgeの表示はコメントアウト
269    discard_actions = [Action(game_ins.game_const.DISCARD, card_position=i) for i in range(handsize)]
270    scores = list(map(lambda p: self.pretend_discard(p, knowledge[player_number], board, trash), discard_actions))
271    scores.sort(key=lambda x: -x[1])  # スコアが高い順にソート
272
273
274    # カスタムフォーマットでdiscard_scoresを表示
275    formatted_scores = [
276        (index, action.card_position, score, details)
277        for index, (action, score, details) in enumerate(scores)
278    ]
279
280    # self.print_l_2d("discard_scores",scores)
281
282    # self.print_log(f"result:{result}")
283    # self.print_l_2d("explanation",self.explanation)
284
285    if result:
286        return result
287    else:
288        self.print_log("Intentional:最良の捨てる行動を選択します.")
289        # self.print_l_2d("discard_scores",formatted_scores)
290        return scores[0][0]  # 最もスコアが高い捨てる行動を選択
291
292    # # もし相手がプレイ可能なカードを持っておらず,ヒントトークンがない場合,自分のカードをランダムに選んで捨てる
293    # self.print_log("Intentional:自分のカードをランダムに選んで捨てます.")
294    # #self.print_log(f"random_pick_card_position:{random_pick_card_position}")
295    # return Action(self.game.game_const.DISCARD, card_position=self.game.random.choice(range(self.game.hand_size)))
296
297  def pretend(self, assumed_hint_action, opponent_knowledge, intentions, opponent_hands, board):
298    """
299    指定したヒントアクションを仮定し,その有効性を評価する関数.
300    
301    Args:
302        assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
303        opponent_knowledge (list): 相手の知識状態
304        intentions (list): 各カードに対する意図
305        opponent_hands (list): 相手の手札
306        board (dict): 現在のボードの状態
307    
308    Returns:
309        tuple: (有効性の判定, スコア, 説明)
310    """
311    
312    # アクションを仮定して評価する関数 (詳細版)
313    (assumed_action_type, assumed_action_value) = assumed_hint_action # 仮定したアクションの種類と値を取得
314    is_positive_list_card_matching_hint = []  # 各カードがヒントに合致するかどうか
315    has_positive_card_matching_hint = False  # 少なくとも1枚がヒントに合致するか.事実上のヒントを出したときに指さされたカードかどうか.
316    is_change_opponent_knowledge = False  # ヒントによって新しい情報が得られるか
317
318    # アクションが色のヒントの場合
319    if assumed_action_type == "HINT_COLOR":
320        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
321        # 各カードに対して
322        for i, (col, num) in enumerate(opponent_hands):
323            is_positive_list_card_matching_hint.append(assumed_action_value == col)  # カードがヒントと合致するかどうか
324            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
325            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
326            new_opponent_each_card_knowledge = self.hint_color(opponent_card_knowledge, assumed_action_value, assumed_action_value == col)
327            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
328            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
329
330            # 仮定した色ヒントアクションが相手手札の1枚の色と合致するなら
331            if assumed_action_value == col:
332                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
333                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
334                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
335                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
336            
337            # self.print_log(f"op_hand color:{col},num:{num}")
338            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
339            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
340            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
341            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
342            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
343            # self.print_log("\n")
344
345    else:
346        # アクションがランクのヒントの場合
347        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
348        # 各カードに対して
349        for i, (col, num) in enumerate(opponent_hands):
350            is_positive_list_card_matching_hint.append(assumed_action_value == num)  # カードがヒントと合致するかどうか
351            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
352            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
353            new_opponent_each_card_knowledge = self.hint_rank(opponent_card_knowledge, assumed_action_value, assumed_action_value == num)
354            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
355            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
356
357            if assumed_action_value == num:
358                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
359                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
360                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
361                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
362
363            # self.print_log(f"op_hand color:{col},num:{num}")
364            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
365            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
366            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
367            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
368            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
369            # self.print_log("\n")
370
371    # ヒントがどのカードにも一致しなければ無効なヒント
372    if not has_positive_card_matching_hint:
373        # self.print_log("Invalid hint")
374        return False, 0, ["Invalid hint"]
375    # ヒントが新しい情報をもたらさなければ無効なヒント
376    if not is_change_opponent_knowledge:
377        # self.print_log("No new information")
378        return False, 0, ["No new information"]
379    
380    score = 0  # ヒントのスコアを初期化
381    predictions = []  # 各カードに対する予測結果のリスト
382    has_positive_effect_at_least_one_card = False  # 少なくとも1枚のカードに対して正の影響があるかどうかを示すフラグ
383
384    # self.print_log(f"intentions:{intentions}")
385    # self.print_log(f"opponent_hands:{opponent_hands}")
386    # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
387    # self.print_log(f"is_positive_list_card_matching_hint:{is_positive_list_card_matching_hint}")
388    
389    # 各カードについて意図と一致するか確認
390    for i, card, new_know, is_positive in zip(intentions, opponent_hands, new_opponent_knowledge, is_positive_list_card_matching_hint):
391        # self.print_log(f"i:{i}")
392        #self.print_log(f"new_know:{new_know}")
393        #self.print_log(f"is_positive:{is_positive}")
394
395        # 新しい知識に基づいて,相手がとるだろうアクションを決定
396        opponent_whattodo_action = self.whattodo(new_know, is_positive, board)
397
398        # self.print_log(f"opponent_whattodo_action:{opponent_whattodo_action}")
399        
400        if opponent_whattodo_action == "PLAY" and i != "PLAY":
401            # アクションがプレイだが、意図がプレイでない場合は無効なヒント
402            # self.print_log("op-ac;play,but Invalid hint")
403            return False, 0, predictions + ["PLAY"]
404        
405        if opponent_whattodo_action == "DISCARD" and i not in ["DISCARD", "CANDISCARD"]:
406            # アクションが捨てるだが、意図が捨てるまたは将来的に捨てる可能性のあるカードでない場合は無効なヒント
407            # self.print_log("op-ac;discard,but Invalid hint")
408            return False, 0, predictions + ["DISCARD"]
409        
410        if opponent_whattodo_action == "PLAY" and i == "PLAY":
411            # アクションがプレイであり、意図もプレイの場合はスコアを加算
412            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
413            predictions.append("PLAY")
414            score += 3
415        elif opponent_whattodo_action == "DISCARD" and i in ["DISCARD", "CANDISCARD"]:
416            # アクションが捨てるであり、意図が捨てるもしくは将来的に捨てる可能性がある場合はスコアを加算
417            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
418            predictions.append("DISCARD")
419            if i == "DISCARD":
420                score += 2 # 捨てる意図の場合はスコアを2加算
421            else:
422                score += 1 # 将来的に捨てる可能性がある場合はスコアを1加算
423        else:
424            predictions.append(None)  # それ以外の場合は何も追加しない
425        
426    # self.print_log(f"score:{score}")
427    # self.print_log(f"predictions:{predictions}")
428      
429    # 少なくとも1枚のカードに対して正の影響があるか確認
430    if not has_positive_effect_at_least_one_card:
431        return False, score, predictions
432    return True, score, predictions
433
434  def whattodo(self, knowledge, pointed, board):
435      """
436      プレイヤーの知識に基づいて,次に取るべきアクションを決定する関数.
437      
438      Args:
439          knowledge (list): カードに関する知識のリスト
440          pointed (bool): ヒントで指摘されたかどうか
441          board (dict): 現在のボードの状態
442      
443      Returns:
444          str or None: PLAY, DISCARD, もしくは None
445      """
446      # プレイヤーの知識に基づいて、次に取るべきアクションを決定
447      # knowledge: 各カードに関するプレイヤーの知識を表すリスト
448      # pointed: そのカードがプレイヤーに対して指摘されたかどうかを示すフラグ(ヒントが与えられたかどうか)
449      # board: 現在のボードの状態(各色ごとの進行状況)
450
451      # カードに関する知識から、どの色・ランクのカードである可能性があるかを取得
452      possible = self.get_possible(knowledge)
453
454      # self.print_log(f"possible:{possible}")
455
456      # ボードにプレイできるかどうかを確認
457      play = self.potentially_playable(possible, board)
458      # self.print_log(f"play:{play}")
459
460      # カード捨てても良いかどうかを確認
461      discard = self.potentially_discardable(possible, board)
462      # self.print_log(f"discard:{discard}")
463
464      # カードがプレイ可能で、かつそのカードが指摘されている場合
465      if play and pointed:
466          return "PLAY"  # プレイアクションを返す
467
468      # カードが捨てても良く、かつそのカードが指摘されている場合
469      if discard and pointed:
470          return "DISCARD"  # 捨てるアクションを返す
471
472      # どちらでもない場合は何もしない(None を返す)
473      return None
474
475  def hint_color(self, opponent_card_knowledge, assumed_action_hint_color, truth):
476    """
477    色のヒントを適用して新しい知識を生成する関数.
478    
479    Args:
480        opponent_card_knowledge (list): 相手のカード知識
481        assumed_action_hint_color (int): 与えるヒントの色
482        truth (bool): ヒントが実際の色と一致するかどうか
483    
484    Returns:
485        list: 更新されたカード知識
486    """
487    # 色のヒントを適用して新しい知識を生成
488    # truth は、ヒントが正しいかどうか(与えられた色に合致するか)を示すフラグ
489    result = []  # 新しい知識の結果を保存するリスト
490    for col in self.game_ins.game_const.ALL_COLORS:
491      if truth == (col == assumed_action_hint_color):
492        # truth が True で、col がヒントと一致する場合
493        # または truth が False で、col が一致しない場合
494        # 現在の知識をそのまま新しい知識にコピー
495        result.append(opponent_card_knowledge[col][:])
496      else:
497        # それ以外の場合、情報をリセット(ゼロにする)
498        result.append([0 for _ in opponent_card_knowledge[col]])
499    return result
500  
501  def hint_rank(self, opponent_card_knowledge, assumed_action_hint_rank, truth):
502    """
503    ランクのヒントを適用して新しい知識を生成する関数.
504    
505    Args:
506        opponent_card_knowledge (list): 相手のカード知識
507        assumed_action_hint_rank (int): 与えるヒントのランク
508        truth (bool): ヒントが実際のランクと一致するかどうか
509    
510    Returns:
511        list: 更新されたカード知識
512    """
513    # ランクのヒントを適用して新しい知識を生成
514    # truth は、与えられたランクと一致するかどうかを示すフラグ
515    result = []  # 新しい知識の結果を保存するリスト
516    for col in self.game_ins.game_const.ALL_COLORS:
517        col_knowledge = []  # 各色に対する知識を保存するリスト
518        for i, k in enumerate(opponent_card_knowledge[col]):
519            if truth == (i + 1 == assumed_action_hint_rank):
520                # truth が True で、ランクが一致する場合
521                # または truth が False で、ランクが一致しない場合
522                # 現在の知識をそのまま新しい知識にコピー
523                col_knowledge.append(k)
524            else:
525                # それ以外の場合、情報をリセット(ゼロにする)
526                col_knowledge.append(0)
527        result.append(col_knowledge)
528    return result
529
530
531  def get_possible(self,knowledge):
532    """
533    各カードに対する知識に基づいて,プレイヤーが持っている可能性があるカードのリストを返す.
534    
535    Args:
536        knowledge (list): カードに関する知識
537    
538    Returns:
539        list: 可能性のあるカードのリスト(色と数字のタプル)
540    """
541    possible = []
542    for color in self.game.game_const.ALL_COLORS:
543      for number, count in enumerate(knowledge[color]):
544
545        if count > 0:
546          possible.append((color, number + 1))  # 色と数字のペアを追加
547    #check_type_and_dtype(possible)
548    return possible
549  
550  def playable(self,possible, board):
551    """
552    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
553    
554    Args:
555        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
556        board (dict): 現在のボードの状態
557    
558    Returns:
559        bool: プレイ可能なカードがあればTrue,なければFalse
560    """
561    # ヒントの可能性から,プレイ可能だと確定してるかどうかを確認するメソッド
562    #self.print_log("playable関数")
563    for (color,number) in possible:
564      # self.print_log(f"color:{color},number:{number}")
565      # self.print_log(f"{color}色の最大値:{board[color]}")
566      if board[color] + 1 != number:
567        # self.print_log(f"Playable:{color,number} is False")
568        return False # possibleのリスト内で、少なくとも1つでもplayableでないタプルが含まれていると、そのリスト全体に対してFalseが返されます
569    # self.print_log(f"Playable:{possible} is True")
570    return True 
571  
572  def discardable(self,possible, board):
573    """
574    可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
575    
576    Args:
577        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
578        board (dict): 現在のボードの状態
579    
580    Returns:
581        bool: 廃棄可能なカードがあればTrue,なければFalse
582    """
583    # ヒントの可能性から,廃棄可能だと確定してるかどうかを確認するメソッド
584    for (color,number) in possible:
585      if board[color] < number: 
586        return False
587    #self.print_log("Discardable: ",possible)
588    return True # ボード上のその色のカードよりも小さい数字なら破棄しても問題ない
589
590  def potentially_playable(self, possible, board):
591    """
592    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
593    
594    Args:
595        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
596        board (dict): 現在のボードの状態
597    
598    Returns:
599        bool: プレイ可能なカードがあればTrue,なければFalse
600    """
601    # ヒントの可能性から,「プレイ可能なカードが1枚でもあるか」を確認するメソッド
602    for (color, number) in possible:
603        if board[color] + 1 == number:
604            return True
605    return False
606
607  def potentially_discardable(self, possible, board):
608      """
609      可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
610      
611      Args:
612          possible (list): 可能性のあるカードのリスト(色と数字のタプル)
613          board (dict): 現在のボードの状態
614      
615      Returns:
616          bool: 廃棄可能なカードがあればTrue,なければFalse
617      """
618      # ヒントの可能性から,「廃棄可能なカードが1枚でもあるか」を確認するメソッド
619      for (color, number) in possible:
620          if board[color] >= number:
621              return True
622      return False
623  
624  def pretend_discard(self, act, knowledge, board, trash):
625      """
626      指定したヒントアクションを仮定し,その有効性を評価する関数.
627      
628      Args:
629          assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
630          opponent_knowledge (list): 相手の知識状態
631          intentions (list): 各カードに対する意図
632          opponent_hands (list): 相手の手札
633          board (dict): 現在のボードの状態
634      
635      Returns:
636          tuple: (有効性の判定, スコア, 説明)
637      """
638      # 捨てるアクションを仮定して、可能性やスコアを評価するメソッド
639      # act: 捨てるアクション(Action オブジェクト)
640      # knowledge: 現在のプレイヤーのカードに関する知識
641      # board: 現在のボードの状態(各色ごとの進行状況)
642      # trash: 既に捨てられたカードの情報
643      HINT_VALUE = 0.5
644
645      # --- ステップ 1: 現在のカードの知識をコピー ---
646      # 現在の知識を深いコピーで保存し、元の知識に影響を与えないようにする
647      which = copy.deepcopy(knowledge[act.card_position])
648
649      # self.print_log(f"which:{which}")
650
651      # --- ステップ 2: 捨てられたカードの情報を反映 ---
652      # 既に捨てられているカードの情報を知識に反映させることで、現状のカードの可能性を減らす
653      for (col, num) in trash:
654          if which[col][num - 1] > 0:
655              which[col][num - 1] -= 1
656
657      # self.print_log(f"which without trash:{which}")
658
659      # --- ステップ 3: ボードの情報を反映 ---
660      # 既にボード上でプレイされているカードの情報を知識に反映させることで、プレイ済みのカードの可能性を減らす
661      # self.print_log(f"board:{board}")
662      for col in self.game_ins.game_const.ALL_COLORS:
663          for i in range(board[col]):
664              if which[col][i] > 0:
665                  which[col][i] -= 1
666
667      # self.print_log(f"which without board:{which}")
668
669      # --- ステップ 4: 残りの可能性を計算 ---
670      # カードがどの色・ランクである可能性が残っているかを計算する
671      possibilities = sum(map(sum, which))
672      # self.print_log(f"possibilities:{possibilities}")
673      expected = 0  # 期待値を初期化(このアクションの価値を評価するためのもの)
674      terms = []  # 各計算の詳細を保持するリスト
675
676      # --- ステップ 5: 各カードの価値を評価 ---
677      # 各色とランクに対して、カードの可能性を評価し、期待値を計算
678      for col in self.game_ins.game_const.ALL_COLORS:
679          for i, cnt in enumerate(which[col]):
680              rank = i + 1  # ランクは0から始まるため、1を加えて調整
681              if cnt > 0:  # カードがそのランクである可能性がある場合のみ評価
682                  prob = cnt * 1.0 / possibilities  # このカードがそのランクである確率を計算
683                  if board[col] >= rank:
684                      # 既にボード上にあるカードよりも低いランクの場合、価値を加算
685                      # この場合は捨てても問題ないので、正の価値として考慮
686                      expected += prob * HINT_VALUE
687                      terms.append((col, rank, cnt, prob, prob * HINT_VALUE))
688                  else:
689                      # プレイ可能なカードの場合、距離に応じて価値を減少させる
690                      dist = rank - board[col]  # 現在のボードからの距離を計算
691                      if cnt > 1:
692                          # カードの数が複数ある場合、距離の二乗で割ることで価値を減少させる
693                          value = prob * (6 - rank) / (dist * dist)
694                      else:
695                          # カードが1枚しかない場合の価値
696                          value = (6 - rank)
697                      if rank == 5:
698                          # ランクが5の場合は追加の価値を加算(重要なカードであるため)
699                          value += HINT_VALUE
700                      value *= prob  # 確率に基づいて価値を調整
701                      expected -= value  # 期待値から減算(捨てることによるリスクを考慮)
702                      terms.append((col, rank, cnt, prob, -value))
703      
704      # --- ステップ 6: 結果を返す ---
705      # 捨てるアクション、期待される価値、詳細な評価結果を返す
706      return (act, expected, terms)
class IntentionalAgent(hanabiapp.game.agent.agent.Agent):
  8class IntentionalAgent(Agent):
  9  """
 10  意図的な(ルールベースの)エージェントを表現するクラス.
 11  
 12  エージェントは知識とボードの状態に基づいて意図的なアクションを決定する.
 13  """
 14  def __init__(self, name, player_number,is_cui=True):
 15    """
 16    IntentionalAgent のコンストラクタ.
 17    
 18    Args:
 19        name (str): エージェントの名前
 20        player_number (int): プレイヤーの番号
 21        is_cui (bool, optional): CUIモードかどうか(デフォルトはTrue)
 22    """
 23    super().__init__(name, player_number)
 24    self.need_hle_convert= False
 25    self.hints = {}
 26    self.gothint = None
 27    self.last_knowledge = []
 28    self.last_board = []
 29    self.explanation = []
 30  
 31  def format_card(self, card):
 32    """
 33    カードの色とランクを色コード+数字でフォーマットする(表示用).
 34    
 35    Args:
 36        card (tuple): カードの色と数字のタプル
 37    
 38    Returns:
 39        str: フォーマットされたカードの文字列(例: "B3")
 40    """
 41    color_code = self.game_const.COLOR_NAMES[card[0]]  # カードの色コード(例: B)
 42    number = card[1]  # カードの数字
 43    return f"{color_code}{number}" # カードの色コード+数字(例: B3)
 44
 45  def format_intention(self, intention):
 46        """
 47        プレイヤーの意図をフォーマットして文字列に変換する関数.
 48        
 49        Args:
 50            intention (str or None): 意図の種類(PLAY, DISCARD, CANDISCARD など)
 51        
 52        Returns:
 53            str: 整形された意図の文字列
 54        """
 55        # invalidhintなどの文字列の場合はそのまま返す
 56        if isinstance(intention, str):
 57            return intention
 58        # 意図のフォーマット表示
 59        if intention == "PLAY":
 60            return "Play"
 61        elif intention == "DISCARD":
 62            return "Discard"
 63        elif intention == "CANDISCARD":
 64            return "CanDiscard"
 65        return "Keep"
 66  
 67  def format_knowledge(self, k):
 68        """
 69        プレイヤーの知識をフォーマットして文字列に変換する関数.
 70        
 71        Args:
 72            k (dict): カードの知識情報
 73        
 74        Returns:
 75            str: 整形された知識の文字列
 76        """
 77        # プレイヤーの知識をフォーマットして文字列に変換するメソッド
 78        # k: カードに関する知識を表す辞書(各色とランクの情報)
 79        result = ""  # フォーマットされた知識を保持する文字列
 80        for col in self.game_ins.game_const.ALL_COLORS:
 81            for i, cnt in enumerate(k[col]):
 82                if cnt > 0:
 83                    # カウントが0より大きい場合のみ情報を追加
 84                    # 色名とランク、そしてその可能性の数を文字列に追加
 85                    result += self.game_ins.game_const.COLOR_NAMES[col] + " " + str(i + 1) + ": " + str(cnt) + "n"
 86        return result
 87
 88  def print_l_2d(self,name,l_2d):
 89    """
 90    2次元リストのデータを整形して出力する関数(CUIモードのみ).
 91    
 92    Args:
 93        name (str): 出力時のタイトル
 94        l_2d (list): 出力する2次元リスト
 95    """
 96    if self.is_cui:
 97      print(f"{name}:")
 98      pprint.pprint(l_2d,width=100,compact=True)
 99  
100  def act(self, game_ins):
101    """
102    プレイヤーの次のアクションを決定する関数.
103    
104    Args:
105        game_ins (Game): ゲームのインスタンス
106    
107    Returns:
108        Action: 選択されたアクション
109    """
110    #===============================================================================================
111    # もともと原作では引数で受け取っていた変数.
112    self.game_ins = game_ins
113    player_number = self.player_number
114    hands = self.game_ins.hands
115    knowledge = self.game_ins.knowledge
116    trash = self.game_ins.trash
117    board = self.game_ins.board
118    hints = self.game_ins.hints
119    #===============================================================================================
120
121    # 変数の初期化
122    handsize = len(hands[player_number])
123    possible = []
124    result = None
125    self.explanation = []  # プレイヤーの行動を説明するための初期化
126    self.explanation.append(["oppHand:"] + list(map(self.format_card, hands[1 - player_number])))
127    self.gothint = None
128
129    #===============================================================================================
130    # 己のknowledgeから,持ちうるカード((色,数字)のタプル)をリストアップ(枚数情報除く)
131    for k in knowledge[self.player_number]:
132      possible.append(self.get_possible(k))
133    #===============================================================================================
134    playable_cards= [] # プレイ可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
135    discardable_cards = [] # 廃棄可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
136    for i, card in enumerate(possible):
137      # The above code is using an f-string to print out the value of the variable `card`. The syntax
138      # `{card=}` is a shorthand way to include both the variable name and its value in the output.
139      # print(f"{card=}")
140      # ボード上のその色のカードが次に必要な数字かどうかを確認
141      if self.playable(card,self.game_ins.board):
142        playable_cards.append(i)
143        self.print_log("Intentional:プレイ可能カードを持っていれるので,それをプレイします.")
144        # The code is creating a `result` object with an action of playing a card at a specific
145        # position `i`.
146        result = Action(self.game_ins.game_const.PLAY, card_position=i)
147        # result = Action(self.game_ins.game_const.PLAY, card_position=self.game_ins.random.choice(playable_cards))
148      if self.discardable(card,self.game_ins.board):
149        # possibleの中から,破棄可能なカードが見つかったら,それをdiscardable_cardsにメモしておく
150        discardable_cards.append(i)
151
152    # print(f"{playable_cards=}")
153    # もし自分が破棄可能カードを持っていて,かつヒントトークンを回復させる余地(ヒントトークンが8個未満)があれば,破棄する
154    if discardable_cards and hints < 8 and not result:
155      self.print_log("Intentional:破棄可能カードを持っており,かつヒントトークンを回復できるので,それを捨てます.")
156      result = Action(self.game_ins.game_const.DISCARD, card_position=self.game_ins.random.choice(discardable_cards))
157
158    
159    #===============================================================================================
160    # 相手視点のゴールの設定
161    opponent_playables = [] # 相手の手札のうち,プレイ可能なカード群
162    opponent_useless = [] # 相手の手札のうち,ボード上ですでに出ている無価値なカード群
163    opponent_potentially_discardables = [] # 相手の手札のうち,ボード上で"出ていない"かつ"5未満"のカード群を将来的に捨てれるとみなした,潜在的な捨てカード候補群
164
165    # すでに場にでているカードの情報を取得
166    board_and_trash_cards = []
167    # board辞書を(色, 数字)のタプルのリストに変換して,すでに場に出たカードをメモに加える
168    for color, count in board.items():
169        for number in range(1, count + 1):
170            board_and_trash_cards .append((color, number))
171    # self.print_log(f"board:{board}")
172    board_and_trash_cards  += trash  # 捨てられたカードもリストに追加
173    # self.print_log(f"board_and_trash_cards :{board_and_trash_cards }")
174
175    intentions = [None for _ in range(handsize)]  # 各カード,普通意図するゴールを保持するためのリストを初期化
176
177    # self.print_log(f"hands:{hands}")
178    # 各プレイヤーの手札を確認
179    for i, hand in enumerate(hands.items()):
180      if i != player_number:  # 他プレイヤーの手札をチェック(自分自身ではない)
181        # hand:(1, [(2, 2), (3, 3), (3, 1), (0, 2), (2, 1)])というタプルなので,手札部分だけ取り出す
182        for j, card in enumerate(hand[1]):
183          color, number = card
184          # --- プレイ可能かどうかのチェック ---
185          if board[color] + 1 == number:
186            # 相手の手札のあるカードがプレイ可能な場合、プレイ可能リストに追加
187            opponent_playables.append(card)
188            # ヒントを出したらプレイ可能という意図を込めたアクションをゴールとしてintentionsにセット
189            # 実際にアクションをとるわけではないので,Actionオブジェクトではなく,"PLAY"をセット
190            intentions[j] = "PLAY"
191          
192          # --- 既に無価値なカードのチェック ---
193          if board[color] >= number:
194            # ボード上ですでに出ているカードは無価値とみなし,無価値リストに追加
195            opponent_useless.append(card)
196            # ヒントを出したら無価値カード,という意図を込めたアクションをゴールとしてintentionsにセット
197            # ただし,意図としてはプレイの方が強いので,プレイの意図がない場合のみ無価値カードとして意図をセット
198            if not intentions[j]:
199              intentions[j] = "DISCARD"
200
201          # --- 将来的に捨てても良いカードのチェック ---
202          if number < 5 and (color, number) not in board_and_trash_cards:
203            # ボードにまだないカードで5未満のものは、将来的に捨てても良いと判断
204            opponent_potentially_discardables.append(card)
205            # ただし,意図としてはプレイ&無価値示唆の方が強いので,プレイ&無価値示唆の意図がない場合のみ潜在的な捨てカード候補群として意図をセット
206            if not intentions[j]:
207              intentions[j] = "CANDISCARD"
208
209    # self.print_log(f"opponent_playables:{opponent_playables}")
210    # self.print_log(f"opponent_useless:{opponent_useless}")
211    # self.print_log(f"opponent_potentially_discardables:{opponent_potentially_discardables}")
212
213    self.explanation.append(["intentions:"] + list(map(self.format_intention, intentions)))
214
215    #===============================================================================================
216    # スモールステップ 5: 相手の行動を予測する
217    if hints > 0:
218      valid_hints = []  # 有効なヒントを保存するリスト
219      
220      # 各色に対してヒントを与える可能性をチェック
221      for color in self.game_ins.game_const.ALL_COLORS:
222  
223        assumed_color_hint_action = ("HINT_COLOR", color) # 色ヒントを仮定
224        # self.print_log(f"||||||||||||||pretend :{assumed_color_hint_action[0]},{self.game_ins.game_const.COLOR_NAMES[assumed_color_hint_action[1]]}||||||||||||||")
225        opponent_knowledge = knowledge[1 - player_number]  # 相手の知識を取得
226        opponent_hands = hands[1 - player_number]  # 相手の手札を取得
227
228        is_valid, score, explanation = self.pretend(assumed_color_hint_action, opponent_knowledge, intentions, opponent_hands, board)
229        # self.print_log(f"is_valid:{is_valid},score:{score},explanation:{explanation}")
230
231        # 各仮定結果を説明リストに追加
232        self.explanation.append([f"Prediction for: Hint Color {self.game_ins.game_const.COLOR_NAMES[color]}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
233
234        if is_valid:
235            valid_hints.append((assumed_color_hint_action, score))  # 有効であればヒントを保存
236            
237      # 各ランクに対してヒントを与える可能性をチェック
238      for rank in range(1, 6):
239          action = ("HINT_NUMBER", rank)
240          # self.print_log(f"||||||||||||||pretend :{action[0]},{action[1]}||||||||||||||")
241          # ヒントを仮定して評価
242          is_valid, score, explanation = self.pretend(action, knowledge[1 - player_number], intentions, hands[1 - player_number], board)
243          # 各仮定結果を説明リストに追加
244          self.explanation.append([f"Prediction for: Hint Rank {rank}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
245          if is_valid:
246              valid_hints.append((action, score))  # 有効であればヒントを保存
247      
248
249      
250      # self.print_log(f"valid_hints:{valid_hints}")
251      # ヒントの候補が存在し、まだ行動が決まっていない場合
252      if valid_hints and not result:
253          # スコアが高い順にソートして最良のヒントを選択
254          valid_hints.sort(key=lambda x: -x[1])
255          best_action, _ = valid_hints[0] # 最良のヒントを取得
256          # self.print_log(f"best_action:{best_action}")
257          # 最良のヒントをアクションとして設定
258          if best_action[0] == "HINT_COLOR":
259              self.print_log("Intentional:最良の色ヒントを出します.")
260              result = Action(game_ins.game_const.HINT_COLOR, pnr = 1 - player_number, color = best_action[1])
261          else:
262              self.print_log("Intentional:最良の数字ヒントを出します.")
263              result = Action(game_ins.game_const.HINT_NUMBER, pnr = 1 - player_number, number = best_action[1])
264
265    #===============================================================================================
266
267    # --- スモールステップ 6: 最終的な捨てる行動のスコアリングと選択 ---
268    
269    # self.explanation.append(["My Knowledge"] + list(map(self.format_knowledge, knowledge[player_number]))) # みにくいknowledgeの表示はコメントアウト
270    discard_actions = [Action(game_ins.game_const.DISCARD, card_position=i) for i in range(handsize)]
271    scores = list(map(lambda p: self.pretend_discard(p, knowledge[player_number], board, trash), discard_actions))
272    scores.sort(key=lambda x: -x[1])  # スコアが高い順にソート
273
274
275    # カスタムフォーマットでdiscard_scoresを表示
276    formatted_scores = [
277        (index, action.card_position, score, details)
278        for index, (action, score, details) in enumerate(scores)
279    ]
280
281    # self.print_l_2d("discard_scores",scores)
282
283    # self.print_log(f"result:{result}")
284    # self.print_l_2d("explanation",self.explanation)
285
286    if result:
287        return result
288    else:
289        self.print_log("Intentional:最良の捨てる行動を選択します.")
290        # self.print_l_2d("discard_scores",formatted_scores)
291        return scores[0][0]  # 最もスコアが高い捨てる行動を選択
292
293    # # もし相手がプレイ可能なカードを持っておらず,ヒントトークンがない場合,自分のカードをランダムに選んで捨てる
294    # self.print_log("Intentional:自分のカードをランダムに選んで捨てます.")
295    # #self.print_log(f"random_pick_card_position:{random_pick_card_position}")
296    # return Action(self.game.game_const.DISCARD, card_position=self.game.random.choice(range(self.game.hand_size)))
297
298  def pretend(self, assumed_hint_action, opponent_knowledge, intentions, opponent_hands, board):
299    """
300    指定したヒントアクションを仮定し,その有効性を評価する関数.
301    
302    Args:
303        assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
304        opponent_knowledge (list): 相手の知識状態
305        intentions (list): 各カードに対する意図
306        opponent_hands (list): 相手の手札
307        board (dict): 現在のボードの状態
308    
309    Returns:
310        tuple: (有効性の判定, スコア, 説明)
311    """
312    
313    # アクションを仮定して評価する関数 (詳細版)
314    (assumed_action_type, assumed_action_value) = assumed_hint_action # 仮定したアクションの種類と値を取得
315    is_positive_list_card_matching_hint = []  # 各カードがヒントに合致するかどうか
316    has_positive_card_matching_hint = False  # 少なくとも1枚がヒントに合致するか.事実上のヒントを出したときに指さされたカードかどうか.
317    is_change_opponent_knowledge = False  # ヒントによって新しい情報が得られるか
318
319    # アクションが色のヒントの場合
320    if assumed_action_type == "HINT_COLOR":
321        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
322        # 各カードに対して
323        for i, (col, num) in enumerate(opponent_hands):
324            is_positive_list_card_matching_hint.append(assumed_action_value == col)  # カードがヒントと合致するかどうか
325            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
326            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
327            new_opponent_each_card_knowledge = self.hint_color(opponent_card_knowledge, assumed_action_value, assumed_action_value == col)
328            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
329            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
330
331            # 仮定した色ヒントアクションが相手手札の1枚の色と合致するなら
332            if assumed_action_value == col:
333                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
334                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
335                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
336                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
337            
338            # self.print_log(f"op_hand color:{col},num:{num}")
339            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
340            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
341            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
342            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
343            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
344            # self.print_log("\n")
345
346    else:
347        # アクションがランクのヒントの場合
348        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
349        # 各カードに対して
350        for i, (col, num) in enumerate(opponent_hands):
351            is_positive_list_card_matching_hint.append(assumed_action_value == num)  # カードがヒントと合致するかどうか
352            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
353            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
354            new_opponent_each_card_knowledge = self.hint_rank(opponent_card_knowledge, assumed_action_value, assumed_action_value == num)
355            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
356            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
357
358            if assumed_action_value == num:
359                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
360                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
361                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
362                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
363
364            # self.print_log(f"op_hand color:{col},num:{num}")
365            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
366            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
367            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
368            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
369            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
370            # self.print_log("\n")
371
372    # ヒントがどのカードにも一致しなければ無効なヒント
373    if not has_positive_card_matching_hint:
374        # self.print_log("Invalid hint")
375        return False, 0, ["Invalid hint"]
376    # ヒントが新しい情報をもたらさなければ無効なヒント
377    if not is_change_opponent_knowledge:
378        # self.print_log("No new information")
379        return False, 0, ["No new information"]
380    
381    score = 0  # ヒントのスコアを初期化
382    predictions = []  # 各カードに対する予測結果のリスト
383    has_positive_effect_at_least_one_card = False  # 少なくとも1枚のカードに対して正の影響があるかどうかを示すフラグ
384
385    # self.print_log(f"intentions:{intentions}")
386    # self.print_log(f"opponent_hands:{opponent_hands}")
387    # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
388    # self.print_log(f"is_positive_list_card_matching_hint:{is_positive_list_card_matching_hint}")
389    
390    # 各カードについて意図と一致するか確認
391    for i, card, new_know, is_positive in zip(intentions, opponent_hands, new_opponent_knowledge, is_positive_list_card_matching_hint):
392        # self.print_log(f"i:{i}")
393        #self.print_log(f"new_know:{new_know}")
394        #self.print_log(f"is_positive:{is_positive}")
395
396        # 新しい知識に基づいて,相手がとるだろうアクションを決定
397        opponent_whattodo_action = self.whattodo(new_know, is_positive, board)
398
399        # self.print_log(f"opponent_whattodo_action:{opponent_whattodo_action}")
400        
401        if opponent_whattodo_action == "PLAY" and i != "PLAY":
402            # アクションがプレイだが、意図がプレイでない場合は無効なヒント
403            # self.print_log("op-ac;play,but Invalid hint")
404            return False, 0, predictions + ["PLAY"]
405        
406        if opponent_whattodo_action == "DISCARD" and i not in ["DISCARD", "CANDISCARD"]:
407            # アクションが捨てるだが、意図が捨てるまたは将来的に捨てる可能性のあるカードでない場合は無効なヒント
408            # self.print_log("op-ac;discard,but Invalid hint")
409            return False, 0, predictions + ["DISCARD"]
410        
411        if opponent_whattodo_action == "PLAY" and i == "PLAY":
412            # アクションがプレイであり、意図もプレイの場合はスコアを加算
413            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
414            predictions.append("PLAY")
415            score += 3
416        elif opponent_whattodo_action == "DISCARD" and i in ["DISCARD", "CANDISCARD"]:
417            # アクションが捨てるであり、意図が捨てるもしくは将来的に捨てる可能性がある場合はスコアを加算
418            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
419            predictions.append("DISCARD")
420            if i == "DISCARD":
421                score += 2 # 捨てる意図の場合はスコアを2加算
422            else:
423                score += 1 # 将来的に捨てる可能性がある場合はスコアを1加算
424        else:
425            predictions.append(None)  # それ以外の場合は何も追加しない
426        
427    # self.print_log(f"score:{score}")
428    # self.print_log(f"predictions:{predictions}")
429      
430    # 少なくとも1枚のカードに対して正の影響があるか確認
431    if not has_positive_effect_at_least_one_card:
432        return False, score, predictions
433    return True, score, predictions
434
435  def whattodo(self, knowledge, pointed, board):
436      """
437      プレイヤーの知識に基づいて,次に取るべきアクションを決定する関数.
438      
439      Args:
440          knowledge (list): カードに関する知識のリスト
441          pointed (bool): ヒントで指摘されたかどうか
442          board (dict): 現在のボードの状態
443      
444      Returns:
445          str or None: PLAY, DISCARD, もしくは None
446      """
447      # プレイヤーの知識に基づいて、次に取るべきアクションを決定
448      # knowledge: 各カードに関するプレイヤーの知識を表すリスト
449      # pointed: そのカードがプレイヤーに対して指摘されたかどうかを示すフラグ(ヒントが与えられたかどうか)
450      # board: 現在のボードの状態(各色ごとの進行状況)
451
452      # カードに関する知識から、どの色・ランクのカードである可能性があるかを取得
453      possible = self.get_possible(knowledge)
454
455      # self.print_log(f"possible:{possible}")
456
457      # ボードにプレイできるかどうかを確認
458      play = self.potentially_playable(possible, board)
459      # self.print_log(f"play:{play}")
460
461      # カード捨てても良いかどうかを確認
462      discard = self.potentially_discardable(possible, board)
463      # self.print_log(f"discard:{discard}")
464
465      # カードがプレイ可能で、かつそのカードが指摘されている場合
466      if play and pointed:
467          return "PLAY"  # プレイアクションを返す
468
469      # カードが捨てても良く、かつそのカードが指摘されている場合
470      if discard and pointed:
471          return "DISCARD"  # 捨てるアクションを返す
472
473      # どちらでもない場合は何もしない(None を返す)
474      return None
475
476  def hint_color(self, opponent_card_knowledge, assumed_action_hint_color, truth):
477    """
478    色のヒントを適用して新しい知識を生成する関数.
479    
480    Args:
481        opponent_card_knowledge (list): 相手のカード知識
482        assumed_action_hint_color (int): 与えるヒントの色
483        truth (bool): ヒントが実際の色と一致するかどうか
484    
485    Returns:
486        list: 更新されたカード知識
487    """
488    # 色のヒントを適用して新しい知識を生成
489    # truth は、ヒントが正しいかどうか(与えられた色に合致するか)を示すフラグ
490    result = []  # 新しい知識の結果を保存するリスト
491    for col in self.game_ins.game_const.ALL_COLORS:
492      if truth == (col == assumed_action_hint_color):
493        # truth が True で、col がヒントと一致する場合
494        # または truth が False で、col が一致しない場合
495        # 現在の知識をそのまま新しい知識にコピー
496        result.append(opponent_card_knowledge[col][:])
497      else:
498        # それ以外の場合、情報をリセット(ゼロにする)
499        result.append([0 for _ in opponent_card_knowledge[col]])
500    return result
501  
502  def hint_rank(self, opponent_card_knowledge, assumed_action_hint_rank, truth):
503    """
504    ランクのヒントを適用して新しい知識を生成する関数.
505    
506    Args:
507        opponent_card_knowledge (list): 相手のカード知識
508        assumed_action_hint_rank (int): 与えるヒントのランク
509        truth (bool): ヒントが実際のランクと一致するかどうか
510    
511    Returns:
512        list: 更新されたカード知識
513    """
514    # ランクのヒントを適用して新しい知識を生成
515    # truth は、与えられたランクと一致するかどうかを示すフラグ
516    result = []  # 新しい知識の結果を保存するリスト
517    for col in self.game_ins.game_const.ALL_COLORS:
518        col_knowledge = []  # 各色に対する知識を保存するリスト
519        for i, k in enumerate(opponent_card_knowledge[col]):
520            if truth == (i + 1 == assumed_action_hint_rank):
521                # truth が True で、ランクが一致する場合
522                # または truth が False で、ランクが一致しない場合
523                # 現在の知識をそのまま新しい知識にコピー
524                col_knowledge.append(k)
525            else:
526                # それ以外の場合、情報をリセット(ゼロにする)
527                col_knowledge.append(0)
528        result.append(col_knowledge)
529    return result
530
531
532  def get_possible(self,knowledge):
533    """
534    各カードに対する知識に基づいて,プレイヤーが持っている可能性があるカードのリストを返す.
535    
536    Args:
537        knowledge (list): カードに関する知識
538    
539    Returns:
540        list: 可能性のあるカードのリスト(色と数字のタプル)
541    """
542    possible = []
543    for color in self.game.game_const.ALL_COLORS:
544      for number, count in enumerate(knowledge[color]):
545
546        if count > 0:
547          possible.append((color, number + 1))  # 色と数字のペアを追加
548    #check_type_and_dtype(possible)
549    return possible
550  
551  def playable(self,possible, board):
552    """
553    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
554    
555    Args:
556        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
557        board (dict): 現在のボードの状態
558    
559    Returns:
560        bool: プレイ可能なカードがあればTrue,なければFalse
561    """
562    # ヒントの可能性から,プレイ可能だと確定してるかどうかを確認するメソッド
563    #self.print_log("playable関数")
564    for (color,number) in possible:
565      # self.print_log(f"color:{color},number:{number}")
566      # self.print_log(f"{color}色の最大値:{board[color]}")
567      if board[color] + 1 != number:
568        # self.print_log(f"Playable:{color,number} is False")
569        return False # possibleのリスト内で、少なくとも1つでもplayableでないタプルが含まれていると、そのリスト全体に対してFalseが返されます
570    # self.print_log(f"Playable:{possible} is True")
571    return True 
572  
573  def discardable(self,possible, board):
574    """
575    可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
576    
577    Args:
578        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
579        board (dict): 現在のボードの状態
580    
581    Returns:
582        bool: 廃棄可能なカードがあればTrue,なければFalse
583    """
584    # ヒントの可能性から,廃棄可能だと確定してるかどうかを確認するメソッド
585    for (color,number) in possible:
586      if board[color] < number: 
587        return False
588    #self.print_log("Discardable: ",possible)
589    return True # ボード上のその色のカードよりも小さい数字なら破棄しても問題ない
590
591  def potentially_playable(self, possible, board):
592    """
593    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
594    
595    Args:
596        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
597        board (dict): 現在のボードの状態
598    
599    Returns:
600        bool: プレイ可能なカードがあればTrue,なければFalse
601    """
602    # ヒントの可能性から,「プレイ可能なカードが1枚でもあるか」を確認するメソッド
603    for (color, number) in possible:
604        if board[color] + 1 == number:
605            return True
606    return False
607
608  def potentially_discardable(self, possible, board):
609      """
610      可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
611      
612      Args:
613          possible (list): 可能性のあるカードのリスト(色と数字のタプル)
614          board (dict): 現在のボードの状態
615      
616      Returns:
617          bool: 廃棄可能なカードがあればTrue,なければFalse
618      """
619      # ヒントの可能性から,「廃棄可能なカードが1枚でもあるか」を確認するメソッド
620      for (color, number) in possible:
621          if board[color] >= number:
622              return True
623      return False
624  
625  def pretend_discard(self, act, knowledge, board, trash):
626      """
627      指定したヒントアクションを仮定し,その有効性を評価する関数.
628      
629      Args:
630          assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
631          opponent_knowledge (list): 相手の知識状態
632          intentions (list): 各カードに対する意図
633          opponent_hands (list): 相手の手札
634          board (dict): 現在のボードの状態
635      
636      Returns:
637          tuple: (有効性の判定, スコア, 説明)
638      """
639      # 捨てるアクションを仮定して、可能性やスコアを評価するメソッド
640      # act: 捨てるアクション(Action オブジェクト)
641      # knowledge: 現在のプレイヤーのカードに関する知識
642      # board: 現在のボードの状態(各色ごとの進行状況)
643      # trash: 既に捨てられたカードの情報
644      HINT_VALUE = 0.5
645
646      # --- ステップ 1: 現在のカードの知識をコピー ---
647      # 現在の知識を深いコピーで保存し、元の知識に影響を与えないようにする
648      which = copy.deepcopy(knowledge[act.card_position])
649
650      # self.print_log(f"which:{which}")
651
652      # --- ステップ 2: 捨てられたカードの情報を反映 ---
653      # 既に捨てられているカードの情報を知識に反映させることで、現状のカードの可能性を減らす
654      for (col, num) in trash:
655          if which[col][num - 1] > 0:
656              which[col][num - 1] -= 1
657
658      # self.print_log(f"which without trash:{which}")
659
660      # --- ステップ 3: ボードの情報を反映 ---
661      # 既にボード上でプレイされているカードの情報を知識に反映させることで、プレイ済みのカードの可能性を減らす
662      # self.print_log(f"board:{board}")
663      for col in self.game_ins.game_const.ALL_COLORS:
664          for i in range(board[col]):
665              if which[col][i] > 0:
666                  which[col][i] -= 1
667
668      # self.print_log(f"which without board:{which}")
669
670      # --- ステップ 4: 残りの可能性を計算 ---
671      # カードがどの色・ランクである可能性が残っているかを計算する
672      possibilities = sum(map(sum, which))
673      # self.print_log(f"possibilities:{possibilities}")
674      expected = 0  # 期待値を初期化(このアクションの価値を評価するためのもの)
675      terms = []  # 各計算の詳細を保持するリスト
676
677      # --- ステップ 5: 各カードの価値を評価 ---
678      # 各色とランクに対して、カードの可能性を評価し、期待値を計算
679      for col in self.game_ins.game_const.ALL_COLORS:
680          for i, cnt in enumerate(which[col]):
681              rank = i + 1  # ランクは0から始まるため、1を加えて調整
682              if cnt > 0:  # カードがそのランクである可能性がある場合のみ評価
683                  prob = cnt * 1.0 / possibilities  # このカードがそのランクである確率を計算
684                  if board[col] >= rank:
685                      # 既にボード上にあるカードよりも低いランクの場合、価値を加算
686                      # この場合は捨てても問題ないので、正の価値として考慮
687                      expected += prob * HINT_VALUE
688                      terms.append((col, rank, cnt, prob, prob * HINT_VALUE))
689                  else:
690                      # プレイ可能なカードの場合、距離に応じて価値を減少させる
691                      dist = rank - board[col]  # 現在のボードからの距離を計算
692                      if cnt > 1:
693                          # カードの数が複数ある場合、距離の二乗で割ることで価値を減少させる
694                          value = prob * (6 - rank) / (dist * dist)
695                      else:
696                          # カードが1枚しかない場合の価値
697                          value = (6 - rank)
698                      if rank == 5:
699                          # ランクが5の場合は追加の価値を加算(重要なカードであるため)
700                          value += HINT_VALUE
701                      value *= prob  # 確率に基づいて価値を調整
702                      expected -= value  # 期待値から減算(捨てることによるリスクを考慮)
703                      terms.append((col, rank, cnt, prob, -value))
704      
705      # --- ステップ 6: 結果を返す ---
706      # 捨てるアクション、期待される価値、詳細な評価結果を返す
707      return (act, expected, terms)

意図的な(ルールベースの)エージェントを表現するクラス.

エージェントは知識とボードの状態に基づいて意図的なアクションを決定する.

IntentionalAgent(name, player_number, is_cui=True)
14  def __init__(self, name, player_number,is_cui=True):
15    """
16    IntentionalAgent のコンストラクタ.
17    
18    Args:
19        name (str): エージェントの名前
20        player_number (int): プレイヤーの番号
21        is_cui (bool, optional): CUIモードかどうか(デフォルトはTrue)
22    """
23    super().__init__(name, player_number)
24    self.need_hle_convert= False
25    self.hints = {}
26    self.gothint = None
27    self.last_knowledge = []
28    self.last_board = []
29    self.explanation = []

IntentionalAgent のコンストラクタ.

Arguments:
  • name (str): エージェントの名前
  • player_number (int): プレイヤーの番号
  • is_cui (bool, optional): CUIモードかどうか(デフォルトはTrue)
need_hle_convert
hints
gothint
last_knowledge
last_board
explanation
def format_card(self, card):
31  def format_card(self, card):
32    """
33    カードの色とランクを色コード+数字でフォーマットする(表示用).
34    
35    Args:
36        card (tuple): カードの色と数字のタプル
37    
38    Returns:
39        str: フォーマットされたカードの文字列(例: "B3")
40    """
41    color_code = self.game_const.COLOR_NAMES[card[0]]  # カードの色コード(例: B)
42    number = card[1]  # カードの数字
43    return f"{color_code}{number}" # カードの色コード+数字(例: B3)

カードの色とランクを色コード+数字でフォーマットする(表示用).

Arguments:
  • card (tuple): カードの色と数字のタプル
Returns:

str: フォーマットされたカードの文字列(例: "B3")

def format_intention(self, intention):
45  def format_intention(self, intention):
46        """
47        プレイヤーの意図をフォーマットして文字列に変換する関数.
48        
49        Args:
50            intention (str or None): 意図の種類(PLAY, DISCARD, CANDISCARD など)
51        
52        Returns:
53            str: 整形された意図の文字列
54        """
55        # invalidhintなどの文字列の場合はそのまま返す
56        if isinstance(intention, str):
57            return intention
58        # 意図のフォーマット表示
59        if intention == "PLAY":
60            return "Play"
61        elif intention == "DISCARD":
62            return "Discard"
63        elif intention == "CANDISCARD":
64            return "CanDiscard"
65        return "Keep"

プレイヤーの意図をフォーマットして文字列に変換する関数.

Arguments:
  • intention (str or None): 意図の種類(PLAY, DISCARD, CANDISCARD など)
Returns:

str: 整形された意図の文字列

def format_knowledge(self, k):
67  def format_knowledge(self, k):
68        """
69        プレイヤーの知識をフォーマットして文字列に変換する関数.
70        
71        Args:
72            k (dict): カードの知識情報
73        
74        Returns:
75            str: 整形された知識の文字列
76        """
77        # プレイヤーの知識をフォーマットして文字列に変換するメソッド
78        # k: カードに関する知識を表す辞書(各色とランクの情報)
79        result = ""  # フォーマットされた知識を保持する文字列
80        for col in self.game_ins.game_const.ALL_COLORS:
81            for i, cnt in enumerate(k[col]):
82                if cnt > 0:
83                    # カウントが0より大きい場合のみ情報を追加
84                    # 色名とランク、そしてその可能性の数を文字列に追加
85                    result += self.game_ins.game_const.COLOR_NAMES[col] + " " + str(i + 1) + ": " + str(cnt) + "n"
86        return result

プレイヤーの知識をフォーマットして文字列に変換する関数.

Arguments:
  • k (dict): カードの知識情報
Returns:

str: 整形された知識の文字列

def print_l_2d(self, name, l_2d):
88  def print_l_2d(self,name,l_2d):
89    """
90    2次元リストのデータを整形して出力する関数(CUIモードのみ).
91    
92    Args:
93        name (str): 出力時のタイトル
94        l_2d (list): 出力する2次元リスト
95    """
96    if self.is_cui:
97      print(f"{name}:")
98      pprint.pprint(l_2d,width=100,compact=True)

2次元リストのデータを整形して出力する関数(CUIモードのみ).

Arguments:
  • name (str): 出力時のタイトル
  • l_2d (list): 出力する2次元リスト
def act(self, game_ins):
100  def act(self, game_ins):
101    """
102    プレイヤーの次のアクションを決定する関数.
103    
104    Args:
105        game_ins (Game): ゲームのインスタンス
106    
107    Returns:
108        Action: 選択されたアクション
109    """
110    #===============================================================================================
111    # もともと原作では引数で受け取っていた変数.
112    self.game_ins = game_ins
113    player_number = self.player_number
114    hands = self.game_ins.hands
115    knowledge = self.game_ins.knowledge
116    trash = self.game_ins.trash
117    board = self.game_ins.board
118    hints = self.game_ins.hints
119    #===============================================================================================
120
121    # 変数の初期化
122    handsize = len(hands[player_number])
123    possible = []
124    result = None
125    self.explanation = []  # プレイヤーの行動を説明するための初期化
126    self.explanation.append(["oppHand:"] + list(map(self.format_card, hands[1 - player_number])))
127    self.gothint = None
128
129    #===============================================================================================
130    # 己のknowledgeから,持ちうるカード((色,数字)のタプル)をリストアップ(枚数情報除く)
131    for k in knowledge[self.player_number]:
132      possible.append(self.get_possible(k))
133    #===============================================================================================
134    playable_cards= [] # プレイ可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
135    discardable_cards = [] # 廃棄可能なカードのが手札の何番目にあるかの添え字がappendされるリスト
136    for i, card in enumerate(possible):
137      # The above code is using an f-string to print out the value of the variable `card`. The syntax
138      # `{card=}` is a shorthand way to include both the variable name and its value in the output.
139      # print(f"{card=}")
140      # ボード上のその色のカードが次に必要な数字かどうかを確認
141      if self.playable(card,self.game_ins.board):
142        playable_cards.append(i)
143        self.print_log("Intentional:プレイ可能カードを持っていれるので,それをプレイします.")
144        # The code is creating a `result` object with an action of playing a card at a specific
145        # position `i`.
146        result = Action(self.game_ins.game_const.PLAY, card_position=i)
147        # result = Action(self.game_ins.game_const.PLAY, card_position=self.game_ins.random.choice(playable_cards))
148      if self.discardable(card,self.game_ins.board):
149        # possibleの中から,破棄可能なカードが見つかったら,それをdiscardable_cardsにメモしておく
150        discardable_cards.append(i)
151
152    # print(f"{playable_cards=}")
153    # もし自分が破棄可能カードを持っていて,かつヒントトークンを回復させる余地(ヒントトークンが8個未満)があれば,破棄する
154    if discardable_cards and hints < 8 and not result:
155      self.print_log("Intentional:破棄可能カードを持っており,かつヒントトークンを回復できるので,それを捨てます.")
156      result = Action(self.game_ins.game_const.DISCARD, card_position=self.game_ins.random.choice(discardable_cards))
157
158    
159    #===============================================================================================
160    # 相手視点のゴールの設定
161    opponent_playables = [] # 相手の手札のうち,プレイ可能なカード群
162    opponent_useless = [] # 相手の手札のうち,ボード上ですでに出ている無価値なカード群
163    opponent_potentially_discardables = [] # 相手の手札のうち,ボード上で"出ていない"かつ"5未満"のカード群を将来的に捨てれるとみなした,潜在的な捨てカード候補群
164
165    # すでに場にでているカードの情報を取得
166    board_and_trash_cards = []
167    # board辞書を(色, 数字)のタプルのリストに変換して,すでに場に出たカードをメモに加える
168    for color, count in board.items():
169        for number in range(1, count + 1):
170            board_and_trash_cards .append((color, number))
171    # self.print_log(f"board:{board}")
172    board_and_trash_cards  += trash  # 捨てられたカードもリストに追加
173    # self.print_log(f"board_and_trash_cards :{board_and_trash_cards }")
174
175    intentions = [None for _ in range(handsize)]  # 各カード,普通意図するゴールを保持するためのリストを初期化
176
177    # self.print_log(f"hands:{hands}")
178    # 各プレイヤーの手札を確認
179    for i, hand in enumerate(hands.items()):
180      if i != player_number:  # 他プレイヤーの手札をチェック(自分自身ではない)
181        # hand:(1, [(2, 2), (3, 3), (3, 1), (0, 2), (2, 1)])というタプルなので,手札部分だけ取り出す
182        for j, card in enumerate(hand[1]):
183          color, number = card
184          # --- プレイ可能かどうかのチェック ---
185          if board[color] + 1 == number:
186            # 相手の手札のあるカードがプレイ可能な場合、プレイ可能リストに追加
187            opponent_playables.append(card)
188            # ヒントを出したらプレイ可能という意図を込めたアクションをゴールとしてintentionsにセット
189            # 実際にアクションをとるわけではないので,Actionオブジェクトではなく,"PLAY"をセット
190            intentions[j] = "PLAY"
191          
192          # --- 既に無価値なカードのチェック ---
193          if board[color] >= number:
194            # ボード上ですでに出ているカードは無価値とみなし,無価値リストに追加
195            opponent_useless.append(card)
196            # ヒントを出したら無価値カード,という意図を込めたアクションをゴールとしてintentionsにセット
197            # ただし,意図としてはプレイの方が強いので,プレイの意図がない場合のみ無価値カードとして意図をセット
198            if not intentions[j]:
199              intentions[j] = "DISCARD"
200
201          # --- 将来的に捨てても良いカードのチェック ---
202          if number < 5 and (color, number) not in board_and_trash_cards:
203            # ボードにまだないカードで5未満のものは、将来的に捨てても良いと判断
204            opponent_potentially_discardables.append(card)
205            # ただし,意図としてはプレイ&無価値示唆の方が強いので,プレイ&無価値示唆の意図がない場合のみ潜在的な捨てカード候補群として意図をセット
206            if not intentions[j]:
207              intentions[j] = "CANDISCARD"
208
209    # self.print_log(f"opponent_playables:{opponent_playables}")
210    # self.print_log(f"opponent_useless:{opponent_useless}")
211    # self.print_log(f"opponent_potentially_discardables:{opponent_potentially_discardables}")
212
213    self.explanation.append(["intentions:"] + list(map(self.format_intention, intentions)))
214
215    #===============================================================================================
216    # スモールステップ 5: 相手の行動を予測する
217    if hints > 0:
218      valid_hints = []  # 有効なヒントを保存するリスト
219      
220      # 各色に対してヒントを与える可能性をチェック
221      for color in self.game_ins.game_const.ALL_COLORS:
222  
223        assumed_color_hint_action = ("HINT_COLOR", color) # 色ヒントを仮定
224        # self.print_log(f"||||||||||||||pretend :{assumed_color_hint_action[0]},{self.game_ins.game_const.COLOR_NAMES[assumed_color_hint_action[1]]}||||||||||||||")
225        opponent_knowledge = knowledge[1 - player_number]  # 相手の知識を取得
226        opponent_hands = hands[1 - player_number]  # 相手の手札を取得
227
228        is_valid, score, explanation = self.pretend(assumed_color_hint_action, opponent_knowledge, intentions, opponent_hands, board)
229        # self.print_log(f"is_valid:{is_valid},score:{score},explanation:{explanation}")
230
231        # 各仮定結果を説明リストに追加
232        self.explanation.append([f"Prediction for: Hint Color {self.game_ins.game_const.COLOR_NAMES[color]}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
233
234        if is_valid:
235            valid_hints.append((assumed_color_hint_action, score))  # 有効であればヒントを保存
236            
237      # 各ランクに対してヒントを与える可能性をチェック
238      for rank in range(1, 6):
239          action = ("HINT_NUMBER", rank)
240          # self.print_log(f"||||||||||||||pretend :{action[0]},{action[1]}||||||||||||||")
241          # ヒントを仮定して評価
242          is_valid, score, explanation = self.pretend(action, knowledge[1 - player_number], intentions, hands[1 - player_number], board)
243          # 各仮定結果を説明リストに追加
244          self.explanation.append([f"Prediction for: Hint Rank {rank}"] + list(map(self.format_intention, explanation))+[f"Score: {score}"])
245          if is_valid:
246              valid_hints.append((action, score))  # 有効であればヒントを保存
247      
248
249      
250      # self.print_log(f"valid_hints:{valid_hints}")
251      # ヒントの候補が存在し、まだ行動が決まっていない場合
252      if valid_hints and not result:
253          # スコアが高い順にソートして最良のヒントを選択
254          valid_hints.sort(key=lambda x: -x[1])
255          best_action, _ = valid_hints[0] # 最良のヒントを取得
256          # self.print_log(f"best_action:{best_action}")
257          # 最良のヒントをアクションとして設定
258          if best_action[0] == "HINT_COLOR":
259              self.print_log("Intentional:最良の色ヒントを出します.")
260              result = Action(game_ins.game_const.HINT_COLOR, pnr = 1 - player_number, color = best_action[1])
261          else:
262              self.print_log("Intentional:最良の数字ヒントを出します.")
263              result = Action(game_ins.game_const.HINT_NUMBER, pnr = 1 - player_number, number = best_action[1])
264
265    #===============================================================================================
266
267    # --- スモールステップ 6: 最終的な捨てる行動のスコアリングと選択 ---
268    
269    # self.explanation.append(["My Knowledge"] + list(map(self.format_knowledge, knowledge[player_number]))) # みにくいknowledgeの表示はコメントアウト
270    discard_actions = [Action(game_ins.game_const.DISCARD, card_position=i) for i in range(handsize)]
271    scores = list(map(lambda p: self.pretend_discard(p, knowledge[player_number], board, trash), discard_actions))
272    scores.sort(key=lambda x: -x[1])  # スコアが高い順にソート
273
274
275    # カスタムフォーマットでdiscard_scoresを表示
276    formatted_scores = [
277        (index, action.card_position, score, details)
278        for index, (action, score, details) in enumerate(scores)
279    ]
280
281    # self.print_l_2d("discard_scores",scores)
282
283    # self.print_log(f"result:{result}")
284    # self.print_l_2d("explanation",self.explanation)
285
286    if result:
287        return result
288    else:
289        self.print_log("Intentional:最良の捨てる行動を選択します.")
290        # self.print_l_2d("discard_scores",formatted_scores)
291        return scores[0][0]  # 最もスコアが高い捨てる行動を選択
292
293    # # もし相手がプレイ可能なカードを持っておらず,ヒントトークンがない場合,自分のカードをランダムに選んで捨てる
294    # self.print_log("Intentional:自分のカードをランダムに選んで捨てます.")
295    # #self.print_log(f"random_pick_card_position:{random_pick_card_position}")
296    # return Action(self.game.game_const.DISCARD, card_position=self.game.random.choice(range(self.game.hand_size)))

プレイヤーの次のアクションを決定する関数.

Arguments:
  • game_ins (Game): ゲームのインスタンス
Returns:

Action: 選択されたアクション

def pretend( self, assumed_hint_action, opponent_knowledge, intentions, opponent_hands, board):
298  def pretend(self, assumed_hint_action, opponent_knowledge, intentions, opponent_hands, board):
299    """
300    指定したヒントアクションを仮定し,その有効性を評価する関数.
301    
302    Args:
303        assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
304        opponent_knowledge (list): 相手の知識状態
305        intentions (list): 各カードに対する意図
306        opponent_hands (list): 相手の手札
307        board (dict): 現在のボードの状態
308    
309    Returns:
310        tuple: (有効性の判定, スコア, 説明)
311    """
312    
313    # アクションを仮定して評価する関数 (詳細版)
314    (assumed_action_type, assumed_action_value) = assumed_hint_action # 仮定したアクションの種類と値を取得
315    is_positive_list_card_matching_hint = []  # 各カードがヒントに合致するかどうか
316    has_positive_card_matching_hint = False  # 少なくとも1枚がヒントに合致するか.事実上のヒントを出したときに指さされたカードかどうか.
317    is_change_opponent_knowledge = False  # ヒントによって新しい情報が得られるか
318
319    # アクションが色のヒントの場合
320    if assumed_action_type == "HINT_COLOR":
321        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
322        # 各カードに対して
323        for i, (col, num) in enumerate(opponent_hands):
324            is_positive_list_card_matching_hint.append(assumed_action_value == col)  # カードがヒントと合致するかどうか
325            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
326            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
327            new_opponent_each_card_knowledge = self.hint_color(opponent_card_knowledge, assumed_action_value, assumed_action_value == col)
328            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
329            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
330
331            # 仮定した色ヒントアクションが相手手札の1枚の色と合致するなら
332            if assumed_action_value == col:
333                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
334                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
335                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
336                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
337            
338            # self.print_log(f"op_hand color:{col},num:{num}")
339            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
340            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
341            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
342            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
343            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
344            # self.print_log("\n")
345
346    else:
347        # アクションがランクのヒントの場合
348        new_opponent_knowledge = [] # 仮定した色ヒントアクションで得られる,相手の新しい知識を保存するリスト
349        # 各カードに対して
350        for i, (col, num) in enumerate(opponent_hands):
351            is_positive_list_card_matching_hint.append(assumed_action_value == num)  # カードがヒントと合致するかどうか
352            opponent_card_knowledge = opponent_knowledge[i] # 仮定した色ヒントアクションをとる前「1枚分」のカードの知識
353            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識
354            new_opponent_each_card_knowledge = self.hint_rank(opponent_card_knowledge, assumed_action_value, assumed_action_value == num)
355            # 仮定した色ヒントアクションをとった後のカード「1枚分」の知識を追加
356            new_opponent_knowledge.append(new_opponent_each_card_knowledge)
357
358            if assumed_action_value == num:
359                has_positive_card_matching_hint = True  # 少なくとも1枚がヒントに合致することを確認
360                # 「1枚分」のカードの知識を比較し,新しい情報が得られるかを確認
361                if new_opponent_knowledge[-1] != opponent_knowledge[i]:
362                    is_change_opponent_knowledge = True  # 新しい情報が得られたらTrue
363
364            # self.print_log(f"op_hand color:{col},num:{num}")
365            # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
366            # self.print_log(f"new_opponent_knowledge[-1]:{new_opponent_knowledge[-1]}")
367            # self.print_log(f"opponent_knowledge[i]:{opponent_knowledge[i]}")
368            # self.print_log(f"has_positive_card_matching_hint:{has_positive_card_matching_hint}")
369            # self.print_log(f"is_change_opponent_knowledge:{is_change_opponent_knowledge}")
370            # self.print_log("\n")
371
372    # ヒントがどのカードにも一致しなければ無効なヒント
373    if not has_positive_card_matching_hint:
374        # self.print_log("Invalid hint")
375        return False, 0, ["Invalid hint"]
376    # ヒントが新しい情報をもたらさなければ無効なヒント
377    if not is_change_opponent_knowledge:
378        # self.print_log("No new information")
379        return False, 0, ["No new information"]
380    
381    score = 0  # ヒントのスコアを初期化
382    predictions = []  # 各カードに対する予測結果のリスト
383    has_positive_effect_at_least_one_card = False  # 少なくとも1枚のカードに対して正の影響があるかどうかを示すフラグ
384
385    # self.print_log(f"intentions:{intentions}")
386    # self.print_log(f"opponent_hands:{opponent_hands}")
387    # self.print_l_2d("new_opponent_knowledge",new_opponent_knowledge)
388    # self.print_log(f"is_positive_list_card_matching_hint:{is_positive_list_card_matching_hint}")
389    
390    # 各カードについて意図と一致するか確認
391    for i, card, new_know, is_positive in zip(intentions, opponent_hands, new_opponent_knowledge, is_positive_list_card_matching_hint):
392        # self.print_log(f"i:{i}")
393        #self.print_log(f"new_know:{new_know}")
394        #self.print_log(f"is_positive:{is_positive}")
395
396        # 新しい知識に基づいて,相手がとるだろうアクションを決定
397        opponent_whattodo_action = self.whattodo(new_know, is_positive, board)
398
399        # self.print_log(f"opponent_whattodo_action:{opponent_whattodo_action}")
400        
401        if opponent_whattodo_action == "PLAY" and i != "PLAY":
402            # アクションがプレイだが、意図がプレイでない場合は無効なヒント
403            # self.print_log("op-ac;play,but Invalid hint")
404            return False, 0, predictions + ["PLAY"]
405        
406        if opponent_whattodo_action == "DISCARD" and i not in ["DISCARD", "CANDISCARD"]:
407            # アクションが捨てるだが、意図が捨てるまたは将来的に捨てる可能性のあるカードでない場合は無効なヒント
408            # self.print_log("op-ac;discard,but Invalid hint")
409            return False, 0, predictions + ["DISCARD"]
410        
411        if opponent_whattodo_action == "PLAY" and i == "PLAY":
412            # アクションがプレイであり、意図もプレイの場合はスコアを加算
413            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
414            predictions.append("PLAY")
415            score += 3
416        elif opponent_whattodo_action == "DISCARD" and i in ["DISCARD", "CANDISCARD"]:
417            # アクションが捨てるであり、意図が捨てるもしくは将来的に捨てる可能性がある場合はスコアを加算
418            has_positive_effect_at_least_one_card = True # 少なくとも1枚のカードに対して正の影響がある
419            predictions.append("DISCARD")
420            if i == "DISCARD":
421                score += 2 # 捨てる意図の場合はスコアを2加算
422            else:
423                score += 1 # 将来的に捨てる可能性がある場合はスコアを1加算
424        else:
425            predictions.append(None)  # それ以外の場合は何も追加しない
426        
427    # self.print_log(f"score:{score}")
428    # self.print_log(f"predictions:{predictions}")
429      
430    # 少なくとも1枚のカードに対して正の影響があるか確認
431    if not has_positive_effect_at_least_one_card:
432        return False, score, predictions
433    return True, score, predictions

指定したヒントアクションを仮定し,その有効性を評価する関数.

Arguments:
  • assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
  • opponent_knowledge (list): 相手の知識状態
  • intentions (list): 各カードに対する意図
  • opponent_hands (list): 相手の手札
  • board (dict): 現在のボードの状態
Returns:

tuple: (有効性の判定, スコア, 説明)

def whattodo(self, knowledge, pointed, board):
435  def whattodo(self, knowledge, pointed, board):
436      """
437      プレイヤーの知識に基づいて,次に取るべきアクションを決定する関数.
438      
439      Args:
440          knowledge (list): カードに関する知識のリスト
441          pointed (bool): ヒントで指摘されたかどうか
442          board (dict): 現在のボードの状態
443      
444      Returns:
445          str or None: PLAY, DISCARD, もしくは None
446      """
447      # プレイヤーの知識に基づいて、次に取るべきアクションを決定
448      # knowledge: 各カードに関するプレイヤーの知識を表すリスト
449      # pointed: そのカードがプレイヤーに対して指摘されたかどうかを示すフラグ(ヒントが与えられたかどうか)
450      # board: 現在のボードの状態(各色ごとの進行状況)
451
452      # カードに関する知識から、どの色・ランクのカードである可能性があるかを取得
453      possible = self.get_possible(knowledge)
454
455      # self.print_log(f"possible:{possible}")
456
457      # ボードにプレイできるかどうかを確認
458      play = self.potentially_playable(possible, board)
459      # self.print_log(f"play:{play}")
460
461      # カード捨てても良いかどうかを確認
462      discard = self.potentially_discardable(possible, board)
463      # self.print_log(f"discard:{discard}")
464
465      # カードがプレイ可能で、かつそのカードが指摘されている場合
466      if play and pointed:
467          return "PLAY"  # プレイアクションを返す
468
469      # カードが捨てても良く、かつそのカードが指摘されている場合
470      if discard and pointed:
471          return "DISCARD"  # 捨てるアクションを返す
472
473      # どちらでもない場合は何もしない(None を返す)
474      return None

プレイヤーの知識に基づいて,次に取るべきアクションを決定する関数.

Arguments:
  • knowledge (list): カードに関する知識のリスト
  • pointed (bool): ヒントで指摘されたかどうか
  • board (dict): 現在のボードの状態
Returns:

str or None: PLAY, DISCARD, もしくは None

def hint_color(self, opponent_card_knowledge, assumed_action_hint_color, truth):
476  def hint_color(self, opponent_card_knowledge, assumed_action_hint_color, truth):
477    """
478    色のヒントを適用して新しい知識を生成する関数.
479    
480    Args:
481        opponent_card_knowledge (list): 相手のカード知識
482        assumed_action_hint_color (int): 与えるヒントの色
483        truth (bool): ヒントが実際の色と一致するかどうか
484    
485    Returns:
486        list: 更新されたカード知識
487    """
488    # 色のヒントを適用して新しい知識を生成
489    # truth は、ヒントが正しいかどうか(与えられた色に合致するか)を示すフラグ
490    result = []  # 新しい知識の結果を保存するリスト
491    for col in self.game_ins.game_const.ALL_COLORS:
492      if truth == (col == assumed_action_hint_color):
493        # truth が True で、col がヒントと一致する場合
494        # または truth が False で、col が一致しない場合
495        # 現在の知識をそのまま新しい知識にコピー
496        result.append(opponent_card_knowledge[col][:])
497      else:
498        # それ以外の場合、情報をリセット(ゼロにする)
499        result.append([0 for _ in opponent_card_knowledge[col]])
500    return result

色のヒントを適用して新しい知識を生成する関数.

Arguments:
  • opponent_card_knowledge (list): 相手のカード知識
  • assumed_action_hint_color (int): 与えるヒントの色
  • truth (bool): ヒントが実際の色と一致するかどうか
Returns:

list: 更新されたカード知識

def hint_rank(self, opponent_card_knowledge, assumed_action_hint_rank, truth):
502  def hint_rank(self, opponent_card_knowledge, assumed_action_hint_rank, truth):
503    """
504    ランクのヒントを適用して新しい知識を生成する関数.
505    
506    Args:
507        opponent_card_knowledge (list): 相手のカード知識
508        assumed_action_hint_rank (int): 与えるヒントのランク
509        truth (bool): ヒントが実際のランクと一致するかどうか
510    
511    Returns:
512        list: 更新されたカード知識
513    """
514    # ランクのヒントを適用して新しい知識を生成
515    # truth は、与えられたランクと一致するかどうかを示すフラグ
516    result = []  # 新しい知識の結果を保存するリスト
517    for col in self.game_ins.game_const.ALL_COLORS:
518        col_knowledge = []  # 各色に対する知識を保存するリスト
519        for i, k in enumerate(opponent_card_knowledge[col]):
520            if truth == (i + 1 == assumed_action_hint_rank):
521                # truth が True で、ランクが一致する場合
522                # または truth が False で、ランクが一致しない場合
523                # 現在の知識をそのまま新しい知識にコピー
524                col_knowledge.append(k)
525            else:
526                # それ以外の場合、情報をリセット(ゼロにする)
527                col_knowledge.append(0)
528        result.append(col_knowledge)
529    return result

ランクのヒントを適用して新しい知識を生成する関数.

Arguments:
  • opponent_card_knowledge (list): 相手のカード知識
  • assumed_action_hint_rank (int): 与えるヒントのランク
  • truth (bool): ヒントが実際のランクと一致するかどうか
Returns:

list: 更新されたカード知識

def get_possible(self, knowledge):
532  def get_possible(self,knowledge):
533    """
534    各カードに対する知識に基づいて,プレイヤーが持っている可能性があるカードのリストを返す.
535    
536    Args:
537        knowledge (list): カードに関する知識
538    
539    Returns:
540        list: 可能性のあるカードのリスト(色と数字のタプル)
541    """
542    possible = []
543    for color in self.game.game_const.ALL_COLORS:
544      for number, count in enumerate(knowledge[color]):
545
546        if count > 0:
547          possible.append((color, number + 1))  # 色と数字のペアを追加
548    #check_type_and_dtype(possible)
549    return possible

各カードに対する知識に基づいて,プレイヤーが持っている可能性があるカードのリストを返す.

Arguments:
  • knowledge (list): カードに関する知識
Returns:

list: 可能性のあるカードのリスト(色と数字のタプル)

def playable(self, possible, board):
551  def playable(self,possible, board):
552    """
553    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
554    
555    Args:
556        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
557        board (dict): 現在のボードの状態
558    
559    Returns:
560        bool: プレイ可能なカードがあればTrue,なければFalse
561    """
562    # ヒントの可能性から,プレイ可能だと確定してるかどうかを確認するメソッド
563    #self.print_log("playable関数")
564    for (color,number) in possible:
565      # self.print_log(f"color:{color},number:{number}")
566      # self.print_log(f"{color}色の最大値:{board[color]}")
567      if board[color] + 1 != number:
568        # self.print_log(f"Playable:{color,number} is False")
569        return False # possibleのリスト内で、少なくとも1つでもplayableでないタプルが含まれていると、そのリスト全体に対してFalseが返されます
570    # self.print_log(f"Playable:{possible} is True")
571    return True 

可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.

Arguments:
  • possible (list): 可能性のあるカードのリスト(色と数字のタプル)
  • board (dict): 現在のボードの状態
Returns:

bool: プレイ可能なカードがあればTrue,なければFalse

def discardable(self, possible, board):
573  def discardable(self,possible, board):
574    """
575    可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
576    
577    Args:
578        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
579        board (dict): 現在のボードの状態
580    
581    Returns:
582        bool: 廃棄可能なカードがあればTrue,なければFalse
583    """
584    # ヒントの可能性から,廃棄可能だと確定してるかどうかを確認するメソッド
585    for (color,number) in possible:
586      if board[color] < number: 
587        return False
588    #self.print_log("Discardable: ",possible)
589    return True # ボード上のその色のカードよりも小さい数字なら破棄しても問題ない

可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.

Arguments:
  • possible (list): 可能性のあるカードのリスト(色と数字のタプル)
  • board (dict): 現在のボードの状態
Returns:

bool: 廃棄可能なカードがあればTrue,なければFalse

def potentially_playable(self, possible, board):
591  def potentially_playable(self, possible, board):
592    """
593    可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.
594    
595    Args:
596        possible (list): 可能性のあるカードのリスト(色と数字のタプル)
597        board (dict): 現在のボードの状態
598    
599    Returns:
600        bool: プレイ可能なカードがあればTrue,なければFalse
601    """
602    # ヒントの可能性から,「プレイ可能なカードが1枚でもあるか」を確認するメソッド
603    for (color, number) in possible:
604        if board[color] + 1 == number:
605            return True
606    return False

可能性のあるカードの中でプレイ可能なものがあるかを確認する関数.

Arguments:
  • possible (list): 可能性のあるカードのリスト(色と数字のタプル)
  • board (dict): 現在のボードの状態
Returns:

bool: プレイ可能なカードがあればTrue,なければFalse

def potentially_discardable(self, possible, board):
608  def potentially_discardable(self, possible, board):
609      """
610      可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.
611      
612      Args:
613          possible (list): 可能性のあるカードのリスト(色と数字のタプル)
614          board (dict): 現在のボードの状態
615      
616      Returns:
617          bool: 廃棄可能なカードがあればTrue,なければFalse
618      """
619      # ヒントの可能性から,「廃棄可能なカードが1枚でもあるか」を確認するメソッド
620      for (color, number) in possible:
621          if board[color] >= number:
622              return True
623      return False

可能性のあるカードの中で廃棄可能なものがあるかを確認する関数.

Arguments:
  • possible (list): 可能性のあるカードのリスト(色と数字のタプル)
  • board (dict): 現在のボードの状態
Returns:

bool: 廃棄可能なカードがあればTrue,なければFalse

def pretend_discard(self, act, knowledge, board, trash):
625  def pretend_discard(self, act, knowledge, board, trash):
626      """
627      指定したヒントアクションを仮定し,その有効性を評価する関数.
628      
629      Args:
630          assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
631          opponent_knowledge (list): 相手の知識状態
632          intentions (list): 各カードに対する意図
633          opponent_hands (list): 相手の手札
634          board (dict): 現在のボードの状態
635      
636      Returns:
637          tuple: (有効性の判定, スコア, 説明)
638      """
639      # 捨てるアクションを仮定して、可能性やスコアを評価するメソッド
640      # act: 捨てるアクション(Action オブジェクト)
641      # knowledge: 現在のプレイヤーのカードに関する知識
642      # board: 現在のボードの状態(各色ごとの進行状況)
643      # trash: 既に捨てられたカードの情報
644      HINT_VALUE = 0.5
645
646      # --- ステップ 1: 現在のカードの知識をコピー ---
647      # 現在の知識を深いコピーで保存し、元の知識に影響を与えないようにする
648      which = copy.deepcopy(knowledge[act.card_position])
649
650      # self.print_log(f"which:{which}")
651
652      # --- ステップ 2: 捨てられたカードの情報を反映 ---
653      # 既に捨てられているカードの情報を知識に反映させることで、現状のカードの可能性を減らす
654      for (col, num) in trash:
655          if which[col][num - 1] > 0:
656              which[col][num - 1] -= 1
657
658      # self.print_log(f"which without trash:{which}")
659
660      # --- ステップ 3: ボードの情報を反映 ---
661      # 既にボード上でプレイされているカードの情報を知識に反映させることで、プレイ済みのカードの可能性を減らす
662      # self.print_log(f"board:{board}")
663      for col in self.game_ins.game_const.ALL_COLORS:
664          for i in range(board[col]):
665              if which[col][i] > 0:
666                  which[col][i] -= 1
667
668      # self.print_log(f"which without board:{which}")
669
670      # --- ステップ 4: 残りの可能性を計算 ---
671      # カードがどの色・ランクである可能性が残っているかを計算する
672      possibilities = sum(map(sum, which))
673      # self.print_log(f"possibilities:{possibilities}")
674      expected = 0  # 期待値を初期化(このアクションの価値を評価するためのもの)
675      terms = []  # 各計算の詳細を保持するリスト
676
677      # --- ステップ 5: 各カードの価値を評価 ---
678      # 各色とランクに対して、カードの可能性を評価し、期待値を計算
679      for col in self.game_ins.game_const.ALL_COLORS:
680          for i, cnt in enumerate(which[col]):
681              rank = i + 1  # ランクは0から始まるため、1を加えて調整
682              if cnt > 0:  # カードがそのランクである可能性がある場合のみ評価
683                  prob = cnt * 1.0 / possibilities  # このカードがそのランクである確率を計算
684                  if board[col] >= rank:
685                      # 既にボード上にあるカードよりも低いランクの場合、価値を加算
686                      # この場合は捨てても問題ないので、正の価値として考慮
687                      expected += prob * HINT_VALUE
688                      terms.append((col, rank, cnt, prob, prob * HINT_VALUE))
689                  else:
690                      # プレイ可能なカードの場合、距離に応じて価値を減少させる
691                      dist = rank - board[col]  # 現在のボードからの距離を計算
692                      if cnt > 1:
693                          # カードの数が複数ある場合、距離の二乗で割ることで価値を減少させる
694                          value = prob * (6 - rank) / (dist * dist)
695                      else:
696                          # カードが1枚しかない場合の価値
697                          value = (6 - rank)
698                      if rank == 5:
699                          # ランクが5の場合は追加の価値を加算(重要なカードであるため)
700                          value += HINT_VALUE
701                      value *= prob  # 確率に基づいて価値を調整
702                      expected -= value  # 期待値から減算(捨てることによるリスクを考慮)
703                      terms.append((col, rank, cnt, prob, -value))
704      
705      # --- ステップ 6: 結果を返す ---
706      # 捨てるアクション、期待される価値、詳細な評価結果を返す
707      return (act, expected, terms)

指定したヒントアクションを仮定し,その有効性を評価する関数.

Arguments:
  • assumed_hint_action (tuple): 仮定するアクション(アクションの種類と値)
  • opponent_knowledge (list): 相手の知識状態
  • intentions (list): 各カードに対する意図
  • opponent_hands (list): 相手の手札
  • board (dict): 現在のボードの状態
Returns:

tuple: (有効性の判定, スコア, 説明)