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)
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)
意図的な(ルールベースの)エージェントを表現するクラス.
エージェントは知識とボードの状態に基づいて意図的なアクションを決定する.
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)
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")
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: 整形された意図の文字列
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: 整形された知識の文字列
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次元リスト
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: 選択されたアクション
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: (有効性の判定, スコア, 説明)
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
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: 更新されたカード知識
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: 更新されたカード知識
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: 可能性のあるカードのリスト(色と数字のタプル)
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
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
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
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
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: (有効性の判定, スコア, 説明)