gameboy_worlds.emulation.pokemon.parsers
Pokemon specific game state parser implementations for both PokemonRed and PokemonCrystal. While this code base started from: https://github.com/PWhiddy/PokemonRedExperiments/ (v2) and was initially read from memory states https://github.com/thatguy11325/pokemonred_puffer/blob/main/pokemonred_puffer/global_map.py, this is no longer the case as we have moved to visual based state parsing. This decision was primarily made to facilitate easier extension to other games and rom hacks in the future, as well as to avoid reliance on specific memory addresses which may vary between different versions of the game.
However, the code base supports reading from memory addresses to extract game state information, which can be useful for incorporating domain knowledge into reward structures or other aspects of the environment. See the MemoryBasedPokemonRedStateParser class for examples of how to read game state information from memory addresses.
WARNING: The screen capture mechanisms of the parsers rely on a SPECIFIC FRAME being used in the game. This is not a concern with Gen I games, but Gen II games have options for frames. All states and captures in this repo assume a particular choice of frame, and often it is NOT the default Frame 1. Ensure that your agents DO NOT change the frame settings in the game, or the state parsing will fail.
CORE DESIGN PRINCIPLE: Never branch the parser subclasses for a given variant. The inheritance tree for a parser after the game variant parser should always be a tree with only one child per layer. This is to ensure that we don't double effort, any capability added to a parser will always be valid for that game variant. If this principle is followed, any state tracker can always use the STRONGEST (lowest level) parser for a given variant without concern for missing functionality.
1""" 2Pokemon specific game state parser implementations for both PokemonRed and PokemonCrystal. 3While this code base started from: https://github.com/PWhiddy/PokemonRedExperiments/ (v2) and was initially read from memory states https://github.com/thatguy11325/pokemonred_puffer/blob/main/pokemonred_puffer/global_map.py, this is no longer the case as we have moved to visual based state parsing. 4This decision was primarily made to facilitate easier extension to other games and rom hacks in the future, as well as to avoid reliance on specific memory addresses which may vary between different versions of the game. 5 6However, the code base supports reading from memory addresses to extract game state information, which can be useful for incorporating domain knowledge into reward structures or other aspects of the environment. See the MemoryBasedPokemonRedStateParser class for examples of how to read game state information from memory addresses. 7 8WARNING: The screen capture mechanisms of the parsers rely on a SPECIFIC FRAME being used in the game. This is not a concern with Gen I games, but Gen II games have options for frames. All states and captures in this repo assume a particular choice of frame, and often it is NOT the default Frame 1. 9Ensure that your agents DO NOT change the frame settings in the game, or the state parsing will fail. 10 11CORE DESIGN PRINCIPLE: Never branch the parser subclasses for a given variant. The inheritance tree for a parser after the game variant parser should always be a tree with only one child per layer. 12This is to ensure that we don't double effort, any capability added to a parser will always be valid for that game variant. 13If this principle is followed, any state tracker can always use the STRONGEST (lowest level) parser for a given variant without concern for missing functionality. 14""" 15 16from gameboy_worlds.emulation.parser import NamedScreenRegion 17from gameboy_worlds.utils import ( 18 log_warn, 19 log_info, 20 log_error, 21 load_parameters, 22 verify_parameters, 23) 24from gameboy_worlds.emulation.parser import StateParser, _get_proper_regions 25 26from typing import Set, List, Type, Dict, Optional, Tuple 27import os 28from abc import ABC, abstractmethod 29from enum import Enum 30 31from pyboy import PyBoy 32 33import json 34import numpy as np 35from bidict import bidict 36 37 38class AgentState(Enum): 39 """ 40 0. FREE_ROAM: The agent is freely roaming the game world. 41 1. IN_DIALOGUE: The agent is currently in a dialogue state. (including reading signs, talking to NPCs, etc.) 42 2. IN_MENU: The agent is currently in a menu state. (including PC, Name Entry, Pokedex, etc.) 43 3. IN_BATTLE: The agent is currently in a battle state. 44 """ 45 46 FREE_ROAM = 0 47 IN_DIALOGUE = 1 48 IN_MENU = 2 49 IN_BATTLE = 3 50 51 52class PokemonStateParser(StateParser, ABC): 53 """ 54 Base class for Pokemon game state parsers. Uses visual screen regions to parse game state. 55 Defines common named screen regions and methods for determining game states such as being in battle, menu, or dialogue. 56 57 Can be used to determine the exact AgentState 58 """ 59 60 COMMON_REGIONS = [ 61 ("dialogue_bottom_right", 153, 135, 10, 10), 62 ("menu_top_right", 152, 1, 6, 6), 63 ("pc_top_left", 0, 0, 6, 6), 64 ("battle_enemy_hp_text", 15, 17, 10, 5), 65 ("battle_player_hp_text", 80, 73, 10, 5), 66 ("battle_base_menu_top_left", 65, 96, 5, 5), 67 ("battle_fight_options_top_right", 80, 64, 5, 5), 68 ("battle_fight_options_cursor_on_top", 40, 103, 3, 3), 69 ("dialogue_choice_bottom_right", 153, 87, 6, 6), 70 ("name_entity_top_left", 0, 32, 6, 6), 71 ("player_card_middle", 56, 70, 6, 6), 72 ("map_bottom_right", 140, 130, 10, 10), 73 ] 74 """ List of common named screen regions for Pokemon games. 75 - dialogue_bottom_right: Bottom right of dialogue box when interacting with NPCs, signs, etc. Speak to an NPC to capture this. 76 77 - menu_top_right: Top right of the screen when the player start menu is open. Open the start menu to capture this. 78 79 - pc_top_left: Top left of the screen when the PC is open. Open the PC to capture this. 80 81 - battle_enemy_hp_text: Region showing the text 'HP' for the enemy Pokémon in battle. Engage in a battle to capture this. 82 83 - battle_player_hp_text: Region showing the text 'HP' for the player's Pokémon in battle. Engage in a battle to capture this. 84 85 - battle_base_menu_top_left: Top left of the battle base menu. Engage in a battle to capture this. 86 87 - battle_fight_options_top_right: Top right of the fight options menu in battle. Engage in a battle and open the fight options to capture this. 88 89 - battle_fight_options_cursor_on_top: Region showing the cursor on the top attack option in the fight options menu. Engage in a battle, open the fight options and move the cursor to the top option to capture this. 90 91 - dialogue_choice_bottom_right: Bottom right of the choice dialogue box when answering choice questions (e.g. Yes/No prompts). Trigger a choice dialogue to capture this (e.g. confirmation of starter choice) 92 93 - name_entity_top_left: Top left of the screen when naming a character or Pokémon. Catch a pokemon and give it a nickname to capture this. 94 95 - player_card_middle: Middle of the player card screen. Go to this from start menu -> player name 96 97 - map_bottom_right: Bottom right of the map screen when the town map is open. Open the town map to capture this. 98 """ 99 100 COMMON_MULTI_TARGET_REGIONS = [ 101 ("screen", 0, 0, 150, 140), 102 ("dialogue_box_middle", 10, 105, 120, 30), 103 ("dialogue_box_full", 5, 100, 150, 40), 104 ("screen_bottom_half", 5, 70, 150, 70), 105 ("screen_quadrant_1", 85, 0, 60, 60), 106 ("screen_quadrant_2", 0, 0, 60, 60), 107 ("screen_quadrant_3", 0, 70, 60, 70), 108 ("screen_quadrant_4", 85, 70, 60, 70), 109 ("screen_middle", 65, 55, 20, 20), 110 ] 111 """ List of common multi-target named screen regions for Pokemon games. 112 113 - screen: Most of the screen except for the very edges. Useful for general state parsing. 114 - dialogue_box_middle: Middle of the dialogue box, but not on that spot where the blinking arrow cursor appears. Useful for catching particular dialogues. 115 - dialogue_box_full: Full dialogue box area, is useful to capture for OCR purposes. 116 - screen_bottom_half: Bottom half of the screen, useful for OCR of dialogue and other text. 117 - screen_quadrant_1: Top right quadrant of the screen. 118 - screen_quadrant_2: Top left quadrant of the screen. 119 - screen_quadrant_3: Bottom left quadrant of the screen. 120 - screen_quadrant_4: Bottom right quadrant of the screen. 121 - screen_middle: Middle of the screen. 122 """ 123 124 COMMON_MULTI_TARGETS = { 125 "dialogue_box_middle": [ 126 "got_away_safely", 127 "cannot_escape", 128 "cannot_run_from_trainer", 129 "no_pp_for_move", 130 ], 131 "menu_box_strip": ["cursor_on_options", "cursor_on_pokedex"], 132 } 133 """ Common multi-targets for the common multi-target named screen regions. 134 - dialogue_box_middle: 135 - got_away_safely: Run successfully from a wild battle. 136 - cannot_escape: Fail to run from a wild Pokemon 137 - cannot_run_from_trainer: Try to run from a trainer battle and get an error message 138 - no_pp_for_move: Try to use a move with no PP remaining. 139 - menu_box_strip: 140 - cursor_on_options: Cursor is on the options in the start menu. This is vital to prevent agents from changing the text frame option. 141 - cursor_on_pokedex: Cursor is on the Pokedex in the start menu. 142 143 """ 144 145 def __init__( 146 self, 147 variant: str, 148 pyboy: PyBoy, 149 parameters: dict, 150 additional_named_screen_region_details: List[ 151 Tuple[str, int, int, int, int] 152 ] = [], 153 additional_multi_target_named_screen_region_details: List[ 154 Tuple[str, int, int, int, int] 155 ] = [], 156 override_multi_targets: Dict[str, List[str]] = {}, 157 ): 158 """ 159 Initializes the PokemonStateParser. 160 Args: 161 variant (str): The variant of the Pokemon game. 162 pyboy (PyBoy): The PyBoy emulator instance. 163 parameters (dict): Configuration parameters for the emulator. 164 additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include. 165 additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include. 166 override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions. 167 """ 168 verify_parameters(parameters) 169 regions = _get_proper_regions( 170 override_regions=additional_named_screen_region_details, 171 base_regions=self.COMMON_REGIONS, 172 ) 173 self.variant = variant 174 if f"{variant}_rom_data_path" not in parameters: 175 log_error( 176 f"ROM data path not found for variant: {variant}. Add {variant}_rom_data_path to the config files. See configs/pokemon_red_vars.yaml for an example", 177 parameters, 178 ) 179 self.rom_data_path = parameters[f"{variant}_rom_data_path"] 180 """ Path to the ROM data directory for the specific Pokemon variant.""" 181 captures_dir = self.rom_data_path + "/captures/" 182 named_screen_regions = [] 183 for region_name, x, y, w, h in regions: 184 region = NamedScreenRegion( 185 region_name, 186 x, 187 y, 188 w, 189 h, 190 parameters=parameters, 191 target_path=os.path.join(captures_dir, region_name), 192 ) 193 named_screen_regions.append(region) 194 multi_target_regions = _get_proper_regions( 195 override_regions=additional_multi_target_named_screen_region_details, 196 base_regions=self.COMMON_MULTI_TARGET_REGIONS, 197 ) 198 multi_target_region_names = [region[0] for region in multi_target_regions] 199 multi_targets = self.COMMON_MULTI_TARGETS.copy() 200 for key in override_multi_targets: 201 if key in multi_targets: 202 multi_targets[key].extend(override_multi_targets[key]) 203 else: 204 multi_targets[key] = override_multi_targets[key] 205 multi_target_provided_region_names = list(multi_targets.keys()) 206 if not set(multi_target_provided_region_names).issubset( 207 set(multi_target_region_names) 208 ): 209 log_error( 210 f"Multi-target regions provided in multi_targets do not match the defined multi-target regions. Provided: {multi_target_provided_region_names}, Defined: {multi_target_region_names}", 211 parameters, 212 ) 213 for region_name, x, y, w, h in multi_target_regions: 214 region_target_paths = {} 215 subdir = captures_dir + f"/{region_name}/" 216 for target_name in multi_targets.get(region_name, []): 217 region_target_paths[target_name] = os.path.join(subdir, target_name) 218 region = NamedScreenRegion( 219 region_name, 220 x, 221 y, 222 w, 223 h, 224 parameters=parameters, 225 multi_target_paths=region_target_paths, 226 ) 227 named_screen_regions.append(region) 228 super().__init__(pyboy, parameters, named_screen_regions) 229 230 @abstractmethod 231 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 232 """ 233 Determines if the Pokedex is currently open. 234 Args: 235 current_screen (np.ndarray): The current screen frame from the emulator. 236 237 Returns: 238 bool: True if the Pokedex is open, False otherwise. 239 """ 240 raise NotImplementedError 241 242 @staticmethod 243 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 244 """ 245 Determines if the Pokemon menu is currently open. 246 Args: 247 current_screen (np.ndarray): The current screen frame from the emulator. 248 Returns: 249 bool: True if the Pokemon menu is open, False otherwise. 250 """ 251 raise NotImplementedError 252 253 def is_hovering_over_options_in_menu(self, current_screen: np.ndarray) -> bool: 254 """ 255 Determines if the cursor is currently hovering over options in the menu. Typically we force the agent off this state. 256 257 # TODO: This method currently only has one multi_target screen checked, cursor_on_options, which is screen captured AFTER the player gets the pokedex 258 The problem is the menu layout is slightly different before the pokedex is acquired, making the check useless before that point. 259 To fix this, we need to capture another target for the same multi_target region (e.g. cursor_on_options_no_pokedex) and check for both here. 260 But I am lazy, and so will hope this is not needed. 261 262 Args: 263 current_screen (np.ndarray): The current screen frame from the emulator. 264 265 Returns: 266 bool: True if hovering over options, False otherwise. 267 """ 268 return self.named_region_matches_multi_target( 269 current_screen, "menu_box_strip", "cursor_on_options" 270 ) 271 272 def is_in_battle(self, current_screen: np.ndarray) -> bool: 273 """ 274 Determines if the player is currently in a battle by checking for battle HP text regions. 275 276 Args: 277 current_screen (np.ndarray): The current screen frame from the emulator. 278 279 Returns: 280 bool: True if in battle, False otherwise. 281 """ 282 if self.is_in_fight_bag(current_screen): 283 return False # Then, is in menu 284 enemy_hp_match = self.named_region_matches_target( 285 current_screen, "battle_enemy_hp_text" 286 ) 287 player_hp_match = self.named_region_matches_target( 288 current_screen, "battle_player_hp_text" 289 ) 290 return enemy_hp_match or player_hp_match 291 292 def is_in_base_battle_menu(self, current_screen: np.ndarray) -> bool: 293 """ 294 Determines if the player is currently in the base battle menu by checking for the battle base menu top left region. 295 296 Args: 297 current_screen (np.ndarray): The current screen frame from the emulator. 298 Returns: 299 bool: True if in the base battle menu, False otherwise. 300 """ 301 return self.named_region_matches_target( 302 current_screen, "battle_base_menu_top_left" 303 ) 304 305 def is_in_run_screen(self, current_screen: np.ndarray) -> bool: 306 """ 307 Determines if the player is currently in the run screen by checking for the battle base menu top left region. 308 Args: 309 current_screen (np.ndarray): The current screen frame from the emulator. 310 Returns: 311 bool: True if in the run screen, False otherwise. 312 """ 313 got_away_safely = self.named_region_matches_multi_target( 314 current_screen, "dialogue_box_middle", "got_away_safely" 315 ) 316 cannot_escape = self.named_region_matches_multi_target( 317 current_screen, "dialogue_box_middle", "cannot_escape" 318 ) 319 cannot_run_from_trainer = self.named_region_matches_multi_target( 320 current_screen, "dialogue_box_middle", "cannot_run_from_trainer" 321 ) 322 return got_away_safely or cannot_escape or cannot_run_from_trainer 323 324 def is_in_fight_options_menu(self, current_screen: np.ndarray) -> bool: 325 """ 326 Determines if the player is currently in the fight options menu by checking for the battle fight options top right region. 327 328 Args: 329 current_screen (np.ndarray): The current screen frame from the emulator. 330 Returns: 331 bool: True if in the fight options menu, False otherwise. 332 """ 333 return self.named_region_matches_target( 334 current_screen, "battle_fight_options_top_right" 335 ) 336 337 def is_on_top_attack_option(self, current_screen: np.ndarray) -> bool: 338 """ 339 Determines if the cursor is currently on the top attack option in the fight options menu. 340 341 Args: 342 current_screen (np.ndarray): The current screen frame from the emulator. 343 Returns: 344 bool: True if the cursor is on the top attack option, False otherwise. 345 """ 346 return self.named_region_matches_target( 347 current_screen, "battle_fight_options_cursor_on_top" 348 ) 349 350 def tried_no_pp_move(self, current_screen: np.ndarray) -> bool: 351 """ 352 Determines if the player tried to use a move with no PP by checking for the no_pp_for_move target in the dialogue box middle region. 353 354 Args: 355 current_screen (np.ndarray): The current screen frame from the emulator. 356 Returns: 357 bool: True if the player tried to use a move with no PP, False otherwise. 358 """ 359 return self.named_region_matches_multi_target( 360 current_screen, "dialogue_box_middle", "no_pp_for_move" 361 ) 362 363 @abstractmethod 364 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 365 raise NotImplementedError 366 367 def is_on_top_menu_option(self, current_screen: np.ndarray) -> bool: 368 """ 369 Determines if the cursor is currently on the top option in the start menu. 370 371 Args: 372 current_screen (np.ndarray): The current screen frame from the emulator. 373 Returns: 374 bool: True if the cursor is on the top menu option, False otherwise. 375 """ 376 return self.named_region_matches_multi_target( 377 current_screen, "menu_box_strip", "cursor_on_pokedex" 378 ) 379 380 def is_in_menu( 381 self, current_screen: np.ndarray, trust_previous: bool = False 382 ) -> bool: 383 """ 384 Determines if any form of menu (or choice dialogue) is currently open by checking a variety of screen regions. 385 386 Args: 387 current_screen (np.ndarray): The current screen frame from the emulator. 388 trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped. 389 390 Returns: 391 bool: True if the menu is open, False otherwise. 392 """ 393 any_match_regions = [ 394 "menu_top_right", 395 "dialogue_choice_bottom_right", 396 "pc_top_left", 397 "name_entity_top_left", 398 "player_card_middle", 399 "map_bottom_right", 400 "pokemon_list_hp_text", # This one is defined in each subclass as the position varies slightly between games 401 ] 402 if not trust_previous: 403 if self.is_in_battle(current_screen): 404 return False 405 if self.is_in_fight_bag(current_screen): 406 return True 407 if self.is_in_pokedex(current_screen): 408 return True 409 if self.is_in_pokemon_menu(current_screen): 410 return True 411 for region_name in any_match_regions: 412 if self.named_region_matches_target(current_screen, region_name): 413 return True 414 return False 415 416 def dialogue_box_open(self, current_screen: np.ndarray) -> bool: 417 """ 418 Determines if a dialogue box is currently open by checking the dialogue bottom right region. 419 Args: 420 current_screen (np.ndarray): The current screen frame from the emulator. 421 Returns: 422 bool: True if a dialogue box is open, False otherwise. 423 """ 424 return self.named_region_matches_target(current_screen, "dialogue_bottom_right") 425 426 def dialogue_box_empty(self, current_screen: np.ndarray) -> bool: 427 box = self.capture_named_region( 428 current_frame=current_screen, name="dialogue_box_full" 429 ) 430 perc_lt_255 = np.mean(box < 255) 431 if perc_lt_255 < 0.082: # Empirical threshold 432 return True 433 return False 434 435 def is_in_dialogue( 436 self, current_screen: np.ndarray, trust_previous: bool = False 437 ) -> bool: 438 """ 439 Determines if the player is currently in a dialogue state or reading text from a sign, interacting with an object etc. 440 Essentially anything that causes text to appear at the bottom of the screen that isn't a battle, pc or menu. 441 442 Args: 443 current_screen (np.ndarray): The current screen frame from the emulator. 444 trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped. 445 446 Returns: 447 bool: True if in dialogue, False otherwise. 448 """ 449 if trust_previous: 450 return self.named_region_matches_target( 451 current_screen, "dialogue_bottom_right" 452 ) 453 if self.is_in_battle(current_screen): 454 return False 455 elif self.is_in_menu(current_screen): 456 return False 457 else: 458 return self.dialogue_box_open(current_screen) 459 460 def get_agent_state(self, current_screen: np.ndarray) -> AgentState: 461 """ 462 Determines the current agent state based on the screen. 463 464 Uses trust_previous to optimize checks. 465 466 Args: 467 current_screen (np.ndarray): The current screen frame from the emulator. 468 469 Returns: 470 AgentState: The current agent state. 471 """ 472 if self.is_in_battle(current_screen): 473 return AgentState.IN_BATTLE 474 elif self.is_in_menu(current_screen, trust_previous=True): 475 return AgentState.IN_MENU 476 elif self.is_in_dialogue(current_screen, trust_previous=True): 477 return AgentState.IN_DIALOGUE 478 else: 479 return AgentState.FREE_ROAM 480 481 482class BasePokemonRedStateParser(PokemonStateParser, ABC): 483 """ 484 Game state parser for all PokemonRed-based games. 485 """ 486 487 REGIONS = [ 488 ("pokedex_top_left", 7, 6, 12, 6), 489 ("pokedex_info_mid_left", 6, 71, 6, 6), 490 ("pokemon_list_hp_text", 32, 9, 10, 5), 491 ("pokemon_stats_line", 66, 55, 5, 5), 492 ("battle_bag_options_bottom_left", 32, 96, 5, 5), 493 ] 494 """ Additional named screen regions specific to Pokemon Red games. 495 - pokedex_top_left: Top left of the screen when the Pokedex is open. Open the Pokedex to capture this. 496 - pokedex_info_mid_left: Middle left of the screen when viewing a Pokémon's info in the Pokedex. Open a Pokémon's info in the Pokedex to capture this. 497 - pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this. 498 - pokemon_stats_line: A line in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this. 499 - battle_bag_options_bottom_left: The bottom left part of the menu when you open the items option from the battle menu. 500 """ 501 502 MULTI_TARGET_REGIONS = [ 503 ("menu_box_strip", 89, 13, 5, 100), 504 ] 505 """ Additional multi-target named screen regions specific to Pokemon Red games. 506 - menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states. 507 """ 508 509 def __init__( 510 self, 511 pyboy: PyBoy, 512 variant: str, 513 parameters: dict, 514 override_regions: List[Tuple[str, int, int, int, int]] = [], 515 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 516 override_multi_targets: Dict[str, List[str]] = {}, 517 ): 518 self.REGIONS = _get_proper_regions( 519 override_regions=override_regions, base_regions=self.REGIONS 520 ) 521 self.MULTI_TARGET_REGIONS = _get_proper_regions( 522 override_regions=override_multi_target_regions, 523 base_regions=self.MULTI_TARGET_REGIONS, 524 ) 525 super().__init__( 526 variant=variant, 527 pyboy=pyboy, 528 parameters=parameters, 529 additional_named_screen_region_details=self.REGIONS, 530 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 531 override_multi_targets=override_multi_targets, 532 ) 533 534 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 535 return self.named_region_matches_target( 536 current_screen, "pokedex_top_left" 537 ) or self.named_region_matches_target(current_screen, "pokedex_info_mid_left") 538 539 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 540 return self.named_region_matches_target( 541 current_screen, "pokemon_list_hp_text" 542 ) or self.named_region_matches_target(current_screen, "pokemon_stats_line") 543 544 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 545 return self.named_region_matches_target( 546 current_screen, "battle_bag_options_bottom_left" 547 ) 548 549 def __repr__(self): 550 return f"<PokemonRedParser(variant={self.variant})>" 551 552 553class BasePokemonCrystalStateParser(PokemonStateParser, ABC): 554 """ 555 Game state parser for all PokemonCrystal-based games. 556 557 TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. 558 Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match. 559 """ 560 561 REGIONS = [ 562 ("pokemon_list_hp_text", 87, 16, 10, 5), 563 ("pokedex_seen_text", 3, 88, 5, 5), 564 ("pokedex_info_height_text", 69, 57, 5, 5), 565 ("pokegear_top_left", 0, 0, 6, 6), 566 ("pokemon_stats_lvl_text", 113, 0, 5, 5), 567 ("bag_text", 18, 0, 6, 6), 568 ] 569 """ Additional named screen regions specific to Pokemon Crystal games. 570 - pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this. 571 - pokedex_seen_text: Region showing the 'SEEN' text in the Pokedex. Open the Pokedex to capture this. 572 - pokedex_info_height_text: Region showing the 'HEIGHT' text in the Pokedex info screen. Open a Pokémon's info in the Pokedex to capture this. 573 - pokegear_top_left: Top left of the screen when the Pokegear is open. Open the Pokegear to capture this. 574 - pokemon_stats_lvl_text: Region showing the 'LV' text in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this. 575 - bag_text: Top left of the screen when the Bag is open. Open the Bag to capture this. 576 """ 577 578 MULTI_TARGET_REGIONS = [ 579 ("menu_box_strip", 89, 13, 5, 120), 580 ] 581 """ Additional multi-target named screen regions specific to Pokemon Crystal games. 582 - menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states. 583 """ 584 585 def __init__( 586 self, 587 pyboy: PyBoy, 588 variant: str, 589 parameters: dict, 590 override_regions: List[Tuple[str, int, int, int, int]] = [], 591 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 592 override_multi_targets: Dict[str, List[str]] = {}, 593 ): 594 self.REGIONS = _get_proper_regions( 595 override_regions=override_regions, base_regions=self.REGIONS 596 ) 597 self.MULTI_TARGET_REGIONS = _get_proper_regions( 598 override_regions=override_multi_target_regions, 599 base_regions=self.MULTI_TARGET_REGIONS, 600 ) 601 super().__init__( 602 variant=variant, 603 pyboy=pyboy, 604 parameters=parameters, 605 additional_named_screen_region_details=self.REGIONS, 606 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 607 override_multi_targets=override_multi_targets, 608 ) 609 610 def is_in_bag(self, current_screen: np.ndarray) -> bool: 611 """ 612 Determines if the Bag is currently open. 613 """ 614 return self.named_region_matches_target(current_screen, "bag_text") 615 616 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 617 """ 618 For PokemonCrystal, this is just the same as in bag. 619 """ 620 return self.is_in_bag(current_screen) 621 622 def is_in_pokegear(self, current_screen: np.ndarray) -> bool: 623 """ 624 Determines if the Pokegear is currently open. 625 """ 626 return self.named_region_matches_target(current_screen, "pokegear_top_left") 627 628 def is_in_pokedex(self, current_screen): 629 return self.named_region_matches_target( 630 current_screen, "pokedex_seen_text" 631 ) or self.named_region_matches_target( 632 current_screen, "pokedex_info_height_text" 633 ) 634 635 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 636 return self.named_region_matches_target( 637 current_screen, "pokemon_stats_lvl_text" 638 ) or self.named_region_matches_target(current_screen, "pokemon_list_hp_text") 639 640 def is_in_menu( 641 self, current_screen: np.ndarray, trust_previous: bool = False 642 ) -> bool: 643 # This technically mistakenly also flags when someone calls you on the pokegear, but that's probably fine for now. 644 # Could change by adding special region for pokegear_call_top_left and overriding is_in_menu and is_in_dialogue. 645 result = super().is_in_menu(current_screen, trust_previous=trust_previous) 646 if result: 647 return True 648 if self.is_in_bag(current_screen): 649 return True 650 if self.is_in_pokegear(current_screen): 651 return True 652 # Finally, when transitioning to PC screens, maps etc, the screen goes white. Catch that here. 653 # print(f"Checking for white screen... Pixel stats: {np.min(current_screen)}, {np.max(current_screen)}, {np.mean(current_screen)}") # I get 248, 248, 248.0 654 # The following doesn't catch all white screens (e.g town maps), but does catch some important ones like PC screens. 655 if np.mean(current_screen) > 245 and np.min(current_screen) > 245: 656 return True 657 elif np.mean(current_screen) > 210: # screen coming down from full white 658 return True 659 else: 660 return False 661 662 def __repr__(self): 663 return f"<PokemonCrystalParser(variant={self.variant})>" 664 665 666class PokemonRedStateParser(BasePokemonRedStateParser): 667 def __init__(self, pyboy, parameters): 668 override_multi_targets = { 669 "dialogue_box_middle": [ 670 "picked_charmander", 671 "picked_bulbasaur", 672 "picked_squirtle", 673 "talk_bill_complete", 674 "pick_up_pokeball_starting", # is tied to player character name being You 675 "trainers_tips_sign", 676 "cinnabar_gym_aid_complete", 677 "talk_cinnabar_monk", 678 "defeated_brock", 679 "defeated_lass", 680 "caught_pidgey", 681 "caught_pikachu", 682 "used_potion_on_charmander", 683 ], 684 "screen_bottom_half": [ 685 "viridian_pokemon_center_entrance", 686 "mt_moon_entrance", 687 "bought_potion_at_pewter_pokemart", 688 ], 689 "screen_middle": [ 690 "outside_viridian_center_from_left", 691 "outside_viridian_center_from_right", 692 ], 693 } 694 super().__init__( 695 pyboy, 696 variant="pokemon_red", 697 parameters=parameters, 698 override_multi_targets=override_multi_targets, 699 ) 700 701 702class PokemonBrownStateParser(BasePokemonRedStateParser): 703 def __init__(self, pyboy, parameters): 704 super().__init__(pyboy, variant="pokemon_brown", parameters=parameters) 705 706 707class PokemonStarBeastsStateParser(BasePokemonRedStateParser): 708 def __init__(self, pyboy, parameters): 709 override_regions = [ 710 ("pokemon_list_hp_text", 33, 10, 4, 4), 711 ("battle_enemy_hp_text", 6, 15, 5, 5), 712 ("battle_player_hp_text", 88, 72, 5, 5), 713 ] 714 super().__init__( 715 pyboy, 716 variant="pokemon_starbeasts", 717 parameters=parameters, 718 override_regions=override_regions, 719 ) 720 721 722class PokemonStarBeastsCometStateParser(BasePokemonRedStateParser): 723 def __init__(self, pyboy, parameters): 724 #override_regions = [ 725 #("pokemon_list_hp_text", 33, 10, 4, 4), 726 #("battle_enemy_hp_text", 6, 15, 5, 5), 727 #("battle_player_hp_text", 88, 72, 5, 5), 728 #] 729 super().__init__( 730 pyboy, 731 variant="pokemon_starbeasts_comet", 732 parameters=parameters, 733 #override_regions=override_regions, 734 ) 735 736 737class PokemonCrystalStateParser(BasePokemonCrystalStateParser): 738 def __init__(self, pyboy, parameters): 739 super().__init__(pyboy, variant="pokemon_crystal", parameters=parameters) 740 741 742class PokemonPrismStateParser(BasePokemonCrystalStateParser): 743 def __init__(self, pyboy, parameters): 744 override_regions = [("player_card_middle", 25, 58, 5, 5)] 745 super().__init__( 746 pyboy, 747 variant="pokemon_prism", 748 parameters=parameters, 749 override_regions=override_regions, 750 ) 751 752 753class PokemonFoolsGoldStateParser(BasePokemonCrystalStateParser): 754 def __init__(self, pyboy, parameters): 755 override_regions = [("pokedex_info_height_text", 66, 65, 5, 5)] 756 super().__init__( 757 pyboy, 758 variant="pokemon_fools_gold", 759 parameters=parameters, 760 override_regions=override_regions, 761 ) 762 763 764""" 765The below code shows how to add domain information into the game state parser and read from memory addresses to get descriptive state information. 766 767This is not actually used in any of the current environments, but is left here to show that if you want to bake in more domain knowledge and create explicit reward schedules etc., you can read the information required to do so in this class. 768""" 769 770 771class MemoryBasedPokemonRedStateParser(PokemonRedStateParser): 772 """ 773 Game state parser for Pokemon Red. Uses memory addresses to parse game state. 774 Can be used to reproduce https://github.com/PWhiddy/PokemonRedExperiments/ (v2) and facilitates reward engineering based on memory states. 775 """ 776 777 _PAD = 20 778 _GLOBAL_MAP_SHAPE = (444 + _PAD * 2, 436 + _PAD * 2) 779 _MAP_ROW_OFFSET = _PAD 780 _MAP_COL_OFFSET = _PAD 781 782 def __init__(self, pyboy, parameters): 783 """ 784 Initializes the Pokemon Red game state parser. 785 786 Args: 787 pyboy: An instance of the PyBoy emulator. 788 parameters: A dictionary of parameters for configuration. 789 """ 790 super().__init__(pyboy, parameters=parameters) 791 events_location = parameters["pokemon_red_rom_data_path"] + "/events.json" 792 with open(events_location) as f: 793 event_slots = json.load(f) 794 event_slots = event_slots 795 event_names = {v: k for k, v in event_slots.items() if not v[0].isdigit()} 796 beat_opponent_events = bidict() 797 798 def _pop(d, keys): 799 for key in keys: 800 if key in d: 801 d.pop(key, None) 802 803 pop_queue = [] 804 for name, slot in event_names.items(): 805 if name.startswith("Beat "): 806 beat_opponent_events[name.replace("Beat ", "")] = slot 807 pop_queue.append(name) 808 _pop(event_names, pop_queue) 809 self.defeated_opponent_events = beat_opponent_events 810 """Events related to beating specific opponents. E.g. Beat Brock""" 811 tms_obtained_events = bidict() 812 pop_queue = [] 813 for name, slot in event_names.items(): 814 if name.startswith("Got Tm"): 815 tms_obtained_events[name.replace("Got ", "").strip()] = slot 816 pop_queue.append(name) 817 _pop(event_names, pop_queue) 818 self.tms_obtained_events = tms_obtained_events 819 """Events related to obtaining specific TMs. E.g. Got Tm01""" 820 hm_obtained_events = bidict() 821 pop_queue = [] 822 for name, slot in event_names.items(): 823 if name.startswith("Got Hm"): 824 hm_obtained_events[name.replace("Got ", "").strip()] = slot 825 pop_queue.append(name) 826 _pop(event_names, pop_queue) 827 self.hm_obtained_events = hm_obtained_events 828 """Events related to obtaining specific HMs. E.g. Got Hm01""" 829 passed_badge_check_events = bidict() 830 pop_queue = [] 831 for name, slot in event_names.items(): 832 if name.startswith("Passed ") and "badge" in name: 833 passed_badge_check_events[ 834 name.replace("Passed ", "").replace(" Check", "").strip() 835 ] = slot 836 pop_queue.append(name) 837 _pop(event_names, pop_queue) 838 self.passed_badge_check_events = passed_badge_check_events 839 """Events related to passing badge checks. E.g. Passed Boulder badge check. These will only be relevant to enter Victory Road.""" 840 self.key_items_obtained_events = bidict() 841 """Events related to obtaining key items. E.g. Got Bicycle""" 842 pop_queue = [] 843 for name, slot in event_names.items(): 844 if name.startswith("Got "): 845 self.key_items_obtained_events[name.replace("Got ", "").strip()] = slot 846 pop_queue.append(name) 847 _pop(event_names, pop_queue) 848 self.map_events = { 849 "Cinnabar Gym": bidict(), 850 "Victory Road": bidict(), 851 "Silph Co": bidict(), 852 "Seafoam Islands": bidict(), 853 } 854 """Events related to specific map events like unlocking gates or moving boulders.""" 855 for name, slot in event_names.items(): 856 if name.startswith("Cinnabar Gym Gate") and name.endswith("Unlocked"): 857 self.map_events["Cinnabar Gym"][name] = slot 858 pop_queue.append(name) 859 elif name.startswith("Victory Road") and "Boulder On" in name: 860 self.map_events["Victory Road"][name] = slot 861 pop_queue.append(name) 862 elif name.startswith("Silph Co") and "Unlocked" in name: 863 self.map_events["Silph Co"][name] = slot 864 pop_queue.append(name) 865 elif name.startswith("Seafoam"): 866 self.map_events["Seafoam Islands"][name] = slot 867 pop_queue.append(name) 868 _pop(event_names, pop_queue) 869 self.cutscene_events = bidict() 870 """ Flags for cutscene based events (I think, lol). """ 871 872 cutscenes = [ 873 "Event 001", 874 "Daisy Walking", 875 "Pokemon Tower Rival On Left", 876 "Seel Fan Boast", 877 "Pikachu Fan Boast", 878 "Lab Handing Over Fossil Mon", 879 "Route22 Rival Wants Battle", 880 ] # my best guess, need to verify, Silph Co Receptionist At Desk? Autowalks? 881 pop_queue = [] 882 for name, slot in event_names.items(): 883 if name in cutscenes: 884 self.cutscene_events[name] = slot 885 pop_queue.append(name) 886 _pop(event_names, pop_queue) 887 self.special_events = bidict(event_names) 888 """ All other events not categorized elsewhere.""" 889 890 MAP_PATH = parameters["pokemon_red_rom_data_path"] + "/map_data.json" 891 with open(MAP_PATH) as map_data: 892 MAP_DATA = json.load(map_data)["regions"] 893 self._MAP_DATA = {int(e["id"]): e for e in MAP_DATA} 894 895 def get_map_name(self, map_n: int) -> Optional[str]: 896 """ 897 Gets the name of the map given its identifier. 898 Args: 899 map_n (int): Map identifier. 900 Returns: 901 Optional[str]: Name of the map if found, None otherwise. 902 """ 903 try: 904 return self._MAP_DATA[map_n]["name"] 905 except KeyError: 906 return None 907 908 def local_to_global(self, r: int, c: int, map_n: int) -> Tuple[int, int]: 909 """ 910 Converts local map coordinates to global map coordinates. 911 Args: 912 r (int): Local row coordinate. 913 c (int): Local column coordinate. 914 map_n (int): Map identifier. 915 Returns: 916 (int, int): Global (row, column) coordinates. 917 """ 918 try: 919 ( 920 map_x, 921 map_y, 922 ) = self._MAP_DATA[ 923 map_n 924 ]["coordinates"] 925 gy = r + map_y + self._MAP_ROW_OFFSET 926 gx = c + map_x + self._MAP_COL_OFFSET 927 if ( 928 0 <= gy < self._GLOBAL_MAP_SHAPE[0] 929 and 0 <= gx < self._GLOBAL_MAP_SHAPE[1] 930 ): 931 return gy, gx 932 print( 933 f"coord out of bounds! global: ({gx}, {gy}) game: ({r}, {c}, {map_n})" 934 ) 935 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2 936 except KeyError: 937 print(f"Map id {map_n} not found in map_data.json.") 938 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2 939 940 def get_opponents_defeated(self) -> Set[str]: 941 """ 942 Returns a set of all defeated opponents. This function isn't actually used in any current environments, but is left here to show how to read game state information. 943 Similar functions can be created to read obtained TMs, HMs, key items, passed badge checks, etc. 944 945 Returns: 946 Set[str]: A set of names of defeated opponents. 947 """ 948 return self.get_raised_flags(self.defeated_opponent_events) 949 950 def get_facing_direction(self) -> Tuple[int, int]: 951 """ 952 Gets the direction the player is facing. 953 Returns: 954 (int, int): Tuple representing the direction vector (dy, dx). 955 """ 956 direction = self.read_m(0xD52A) 957 if direction == 1: 958 return (0, 1) # Right 959 elif direction == 2: 960 return (0, -1) # Left 961 elif direction == 4: 962 return (1, 0) # Down 963 else: 964 return (-1, 0) # Up 965 966 def get_local_coords(self) -> Tuple[int, int, int]: 967 """ 968 Gets the local game coordinates (x, y, map number). 969 Returns: 970 (int, int, int): Tuple containing (x, y, map number). 971 """ 972 return (self.read_m(0xD362), self.read_m(0xD361), self.read_m(0xD35E)) 973 974 def get_global_coords(self): 975 """ 976 Gets the global coordinates of the player. 977 Returns: 978 (int, int): Tuple containing (global y, global x) coordinates. 979 """ 980 x_pos, y_pos, map_n = self.get_local_coords() 981 return self.local_to_global(y_pos, x_pos, map_n) 982 983 def get_badges(self) -> np.array: 984 """ 985 Gets the player's badges as a binary array. 986 Returns: 987 np.array: Array of 8 binary values representing whether the player has obtained each of the badges. 988 """ 989 # or self.bit_count(self.read_m(0xD356)) 990 return np.array( 991 [int(bit) for bit in f"{self.read_m(0xD356):08b}"], dtype=np.int8 992 )
39class AgentState(Enum): 40 """ 41 0. FREE_ROAM: The agent is freely roaming the game world. 42 1. IN_DIALOGUE: The agent is currently in a dialogue state. (including reading signs, talking to NPCs, etc.) 43 2. IN_MENU: The agent is currently in a menu state. (including PC, Name Entry, Pokedex, etc.) 44 3. IN_BATTLE: The agent is currently in a battle state. 45 """ 46 47 FREE_ROAM = 0 48 IN_DIALOGUE = 1 49 IN_MENU = 2 50 IN_BATTLE = 3
- FREE_ROAM: The agent is freely roaming the game world.
- IN_DIALOGUE: The agent is currently in a dialogue state. (including reading signs, talking to NPCs, etc.)
- IN_MENU: The agent is currently in a menu state. (including PC, Name Entry, Pokedex, etc.)
- IN_BATTLE: The agent is currently in a battle state.
53class PokemonStateParser(StateParser, ABC): 54 """ 55 Base class for Pokemon game state parsers. Uses visual screen regions to parse game state. 56 Defines common named screen regions and methods for determining game states such as being in battle, menu, or dialogue. 57 58 Can be used to determine the exact AgentState 59 """ 60 61 COMMON_REGIONS = [ 62 ("dialogue_bottom_right", 153, 135, 10, 10), 63 ("menu_top_right", 152, 1, 6, 6), 64 ("pc_top_left", 0, 0, 6, 6), 65 ("battle_enemy_hp_text", 15, 17, 10, 5), 66 ("battle_player_hp_text", 80, 73, 10, 5), 67 ("battle_base_menu_top_left", 65, 96, 5, 5), 68 ("battle_fight_options_top_right", 80, 64, 5, 5), 69 ("battle_fight_options_cursor_on_top", 40, 103, 3, 3), 70 ("dialogue_choice_bottom_right", 153, 87, 6, 6), 71 ("name_entity_top_left", 0, 32, 6, 6), 72 ("player_card_middle", 56, 70, 6, 6), 73 ("map_bottom_right", 140, 130, 10, 10), 74 ] 75 """ List of common named screen regions for Pokemon games. 76 - dialogue_bottom_right: Bottom right of dialogue box when interacting with NPCs, signs, etc. Speak to an NPC to capture this. 77 78 - menu_top_right: Top right of the screen when the player start menu is open. Open the start menu to capture this. 79 80 - pc_top_left: Top left of the screen when the PC is open. Open the PC to capture this. 81 82 - battle_enemy_hp_text: Region showing the text 'HP' for the enemy Pokémon in battle. Engage in a battle to capture this. 83 84 - battle_player_hp_text: Region showing the text 'HP' for the player's Pokémon in battle. Engage in a battle to capture this. 85 86 - battle_base_menu_top_left: Top left of the battle base menu. Engage in a battle to capture this. 87 88 - battle_fight_options_top_right: Top right of the fight options menu in battle. Engage in a battle and open the fight options to capture this. 89 90 - battle_fight_options_cursor_on_top: Region showing the cursor on the top attack option in the fight options menu. Engage in a battle, open the fight options and move the cursor to the top option to capture this. 91 92 - dialogue_choice_bottom_right: Bottom right of the choice dialogue box when answering choice questions (e.g. Yes/No prompts). Trigger a choice dialogue to capture this (e.g. confirmation of starter choice) 93 94 - name_entity_top_left: Top left of the screen when naming a character or Pokémon. Catch a pokemon and give it a nickname to capture this. 95 96 - player_card_middle: Middle of the player card screen. Go to this from start menu -> player name 97 98 - map_bottom_right: Bottom right of the map screen when the town map is open. Open the town map to capture this. 99 """ 100 101 COMMON_MULTI_TARGET_REGIONS = [ 102 ("screen", 0, 0, 150, 140), 103 ("dialogue_box_middle", 10, 105, 120, 30), 104 ("dialogue_box_full", 5, 100, 150, 40), 105 ("screen_bottom_half", 5, 70, 150, 70), 106 ("screen_quadrant_1", 85, 0, 60, 60), 107 ("screen_quadrant_2", 0, 0, 60, 60), 108 ("screen_quadrant_3", 0, 70, 60, 70), 109 ("screen_quadrant_4", 85, 70, 60, 70), 110 ("screen_middle", 65, 55, 20, 20), 111 ] 112 """ List of common multi-target named screen regions for Pokemon games. 113 114 - screen: Most of the screen except for the very edges. Useful for general state parsing. 115 - dialogue_box_middle: Middle of the dialogue box, but not on that spot where the blinking arrow cursor appears. Useful for catching particular dialogues. 116 - dialogue_box_full: Full dialogue box area, is useful to capture for OCR purposes. 117 - screen_bottom_half: Bottom half of the screen, useful for OCR of dialogue and other text. 118 - screen_quadrant_1: Top right quadrant of the screen. 119 - screen_quadrant_2: Top left quadrant of the screen. 120 - screen_quadrant_3: Bottom left quadrant of the screen. 121 - screen_quadrant_4: Bottom right quadrant of the screen. 122 - screen_middle: Middle of the screen. 123 """ 124 125 COMMON_MULTI_TARGETS = { 126 "dialogue_box_middle": [ 127 "got_away_safely", 128 "cannot_escape", 129 "cannot_run_from_trainer", 130 "no_pp_for_move", 131 ], 132 "menu_box_strip": ["cursor_on_options", "cursor_on_pokedex"], 133 } 134 """ Common multi-targets for the common multi-target named screen regions. 135 - dialogue_box_middle: 136 - got_away_safely: Run successfully from a wild battle. 137 - cannot_escape: Fail to run from a wild Pokemon 138 - cannot_run_from_trainer: Try to run from a trainer battle and get an error message 139 - no_pp_for_move: Try to use a move with no PP remaining. 140 - menu_box_strip: 141 - cursor_on_options: Cursor is on the options in the start menu. This is vital to prevent agents from changing the text frame option. 142 - cursor_on_pokedex: Cursor is on the Pokedex in the start menu. 143 144 """ 145 146 def __init__( 147 self, 148 variant: str, 149 pyboy: PyBoy, 150 parameters: dict, 151 additional_named_screen_region_details: List[ 152 Tuple[str, int, int, int, int] 153 ] = [], 154 additional_multi_target_named_screen_region_details: List[ 155 Tuple[str, int, int, int, int] 156 ] = [], 157 override_multi_targets: Dict[str, List[str]] = {}, 158 ): 159 """ 160 Initializes the PokemonStateParser. 161 Args: 162 variant (str): The variant of the Pokemon game. 163 pyboy (PyBoy): The PyBoy emulator instance. 164 parameters (dict): Configuration parameters for the emulator. 165 additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include. 166 additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include. 167 override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions. 168 """ 169 verify_parameters(parameters) 170 regions = _get_proper_regions( 171 override_regions=additional_named_screen_region_details, 172 base_regions=self.COMMON_REGIONS, 173 ) 174 self.variant = variant 175 if f"{variant}_rom_data_path" not in parameters: 176 log_error( 177 f"ROM data path not found for variant: {variant}. Add {variant}_rom_data_path to the config files. See configs/pokemon_red_vars.yaml for an example", 178 parameters, 179 ) 180 self.rom_data_path = parameters[f"{variant}_rom_data_path"] 181 """ Path to the ROM data directory for the specific Pokemon variant.""" 182 captures_dir = self.rom_data_path + "/captures/" 183 named_screen_regions = [] 184 for region_name, x, y, w, h in regions: 185 region = NamedScreenRegion( 186 region_name, 187 x, 188 y, 189 w, 190 h, 191 parameters=parameters, 192 target_path=os.path.join(captures_dir, region_name), 193 ) 194 named_screen_regions.append(region) 195 multi_target_regions = _get_proper_regions( 196 override_regions=additional_multi_target_named_screen_region_details, 197 base_regions=self.COMMON_MULTI_TARGET_REGIONS, 198 ) 199 multi_target_region_names = [region[0] for region in multi_target_regions] 200 multi_targets = self.COMMON_MULTI_TARGETS.copy() 201 for key in override_multi_targets: 202 if key in multi_targets: 203 multi_targets[key].extend(override_multi_targets[key]) 204 else: 205 multi_targets[key] = override_multi_targets[key] 206 multi_target_provided_region_names = list(multi_targets.keys()) 207 if not set(multi_target_provided_region_names).issubset( 208 set(multi_target_region_names) 209 ): 210 log_error( 211 f"Multi-target regions provided in multi_targets do not match the defined multi-target regions. Provided: {multi_target_provided_region_names}, Defined: {multi_target_region_names}", 212 parameters, 213 ) 214 for region_name, x, y, w, h in multi_target_regions: 215 region_target_paths = {} 216 subdir = captures_dir + f"/{region_name}/" 217 for target_name in multi_targets.get(region_name, []): 218 region_target_paths[target_name] = os.path.join(subdir, target_name) 219 region = NamedScreenRegion( 220 region_name, 221 x, 222 y, 223 w, 224 h, 225 parameters=parameters, 226 multi_target_paths=region_target_paths, 227 ) 228 named_screen_regions.append(region) 229 super().__init__(pyboy, parameters, named_screen_regions) 230 231 @abstractmethod 232 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 233 """ 234 Determines if the Pokedex is currently open. 235 Args: 236 current_screen (np.ndarray): The current screen frame from the emulator. 237 238 Returns: 239 bool: True if the Pokedex is open, False otherwise. 240 """ 241 raise NotImplementedError 242 243 @staticmethod 244 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 245 """ 246 Determines if the Pokemon menu is currently open. 247 Args: 248 current_screen (np.ndarray): The current screen frame from the emulator. 249 Returns: 250 bool: True if the Pokemon menu is open, False otherwise. 251 """ 252 raise NotImplementedError 253 254 def is_hovering_over_options_in_menu(self, current_screen: np.ndarray) -> bool: 255 """ 256 Determines if the cursor is currently hovering over options in the menu. Typically we force the agent off this state. 257 258 # TODO: This method currently only has one multi_target screen checked, cursor_on_options, which is screen captured AFTER the player gets the pokedex 259 The problem is the menu layout is slightly different before the pokedex is acquired, making the check useless before that point. 260 To fix this, we need to capture another target for the same multi_target region (e.g. cursor_on_options_no_pokedex) and check for both here. 261 But I am lazy, and so will hope this is not needed. 262 263 Args: 264 current_screen (np.ndarray): The current screen frame from the emulator. 265 266 Returns: 267 bool: True if hovering over options, False otherwise. 268 """ 269 return self.named_region_matches_multi_target( 270 current_screen, "menu_box_strip", "cursor_on_options" 271 ) 272 273 def is_in_battle(self, current_screen: np.ndarray) -> bool: 274 """ 275 Determines if the player is currently in a battle by checking for battle HP text regions. 276 277 Args: 278 current_screen (np.ndarray): The current screen frame from the emulator. 279 280 Returns: 281 bool: True if in battle, False otherwise. 282 """ 283 if self.is_in_fight_bag(current_screen): 284 return False # Then, is in menu 285 enemy_hp_match = self.named_region_matches_target( 286 current_screen, "battle_enemy_hp_text" 287 ) 288 player_hp_match = self.named_region_matches_target( 289 current_screen, "battle_player_hp_text" 290 ) 291 return enemy_hp_match or player_hp_match 292 293 def is_in_base_battle_menu(self, current_screen: np.ndarray) -> bool: 294 """ 295 Determines if the player is currently in the base battle menu by checking for the battle base menu top left region. 296 297 Args: 298 current_screen (np.ndarray): The current screen frame from the emulator. 299 Returns: 300 bool: True if in the base battle menu, False otherwise. 301 """ 302 return self.named_region_matches_target( 303 current_screen, "battle_base_menu_top_left" 304 ) 305 306 def is_in_run_screen(self, current_screen: np.ndarray) -> bool: 307 """ 308 Determines if the player is currently in the run screen by checking for the battle base menu top left region. 309 Args: 310 current_screen (np.ndarray): The current screen frame from the emulator. 311 Returns: 312 bool: True if in the run screen, False otherwise. 313 """ 314 got_away_safely = self.named_region_matches_multi_target( 315 current_screen, "dialogue_box_middle", "got_away_safely" 316 ) 317 cannot_escape = self.named_region_matches_multi_target( 318 current_screen, "dialogue_box_middle", "cannot_escape" 319 ) 320 cannot_run_from_trainer = self.named_region_matches_multi_target( 321 current_screen, "dialogue_box_middle", "cannot_run_from_trainer" 322 ) 323 return got_away_safely or cannot_escape or cannot_run_from_trainer 324 325 def is_in_fight_options_menu(self, current_screen: np.ndarray) -> bool: 326 """ 327 Determines if the player is currently in the fight options menu by checking for the battle fight options top right region. 328 329 Args: 330 current_screen (np.ndarray): The current screen frame from the emulator. 331 Returns: 332 bool: True if in the fight options menu, False otherwise. 333 """ 334 return self.named_region_matches_target( 335 current_screen, "battle_fight_options_top_right" 336 ) 337 338 def is_on_top_attack_option(self, current_screen: np.ndarray) -> bool: 339 """ 340 Determines if the cursor is currently on the top attack option in the fight options menu. 341 342 Args: 343 current_screen (np.ndarray): The current screen frame from the emulator. 344 Returns: 345 bool: True if the cursor is on the top attack option, False otherwise. 346 """ 347 return self.named_region_matches_target( 348 current_screen, "battle_fight_options_cursor_on_top" 349 ) 350 351 def tried_no_pp_move(self, current_screen: np.ndarray) -> bool: 352 """ 353 Determines if the player tried to use a move with no PP by checking for the no_pp_for_move target in the dialogue box middle region. 354 355 Args: 356 current_screen (np.ndarray): The current screen frame from the emulator. 357 Returns: 358 bool: True if the player tried to use a move with no PP, False otherwise. 359 """ 360 return self.named_region_matches_multi_target( 361 current_screen, "dialogue_box_middle", "no_pp_for_move" 362 ) 363 364 @abstractmethod 365 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 366 raise NotImplementedError 367 368 def is_on_top_menu_option(self, current_screen: np.ndarray) -> bool: 369 """ 370 Determines if the cursor is currently on the top option in the start menu. 371 372 Args: 373 current_screen (np.ndarray): The current screen frame from the emulator. 374 Returns: 375 bool: True if the cursor is on the top menu option, False otherwise. 376 """ 377 return self.named_region_matches_multi_target( 378 current_screen, "menu_box_strip", "cursor_on_pokedex" 379 ) 380 381 def is_in_menu( 382 self, current_screen: np.ndarray, trust_previous: bool = False 383 ) -> bool: 384 """ 385 Determines if any form of menu (or choice dialogue) is currently open by checking a variety of screen regions. 386 387 Args: 388 current_screen (np.ndarray): The current screen frame from the emulator. 389 trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped. 390 391 Returns: 392 bool: True if the menu is open, False otherwise. 393 """ 394 any_match_regions = [ 395 "menu_top_right", 396 "dialogue_choice_bottom_right", 397 "pc_top_left", 398 "name_entity_top_left", 399 "player_card_middle", 400 "map_bottom_right", 401 "pokemon_list_hp_text", # This one is defined in each subclass as the position varies slightly between games 402 ] 403 if not trust_previous: 404 if self.is_in_battle(current_screen): 405 return False 406 if self.is_in_fight_bag(current_screen): 407 return True 408 if self.is_in_pokedex(current_screen): 409 return True 410 if self.is_in_pokemon_menu(current_screen): 411 return True 412 for region_name in any_match_regions: 413 if self.named_region_matches_target(current_screen, region_name): 414 return True 415 return False 416 417 def dialogue_box_open(self, current_screen: np.ndarray) -> bool: 418 """ 419 Determines if a dialogue box is currently open by checking the dialogue bottom right region. 420 Args: 421 current_screen (np.ndarray): The current screen frame from the emulator. 422 Returns: 423 bool: True if a dialogue box is open, False otherwise. 424 """ 425 return self.named_region_matches_target(current_screen, "dialogue_bottom_right") 426 427 def dialogue_box_empty(self, current_screen: np.ndarray) -> bool: 428 box = self.capture_named_region( 429 current_frame=current_screen, name="dialogue_box_full" 430 ) 431 perc_lt_255 = np.mean(box < 255) 432 if perc_lt_255 < 0.082: # Empirical threshold 433 return True 434 return False 435 436 def is_in_dialogue( 437 self, current_screen: np.ndarray, trust_previous: bool = False 438 ) -> bool: 439 """ 440 Determines if the player is currently in a dialogue state or reading text from a sign, interacting with an object etc. 441 Essentially anything that causes text to appear at the bottom of the screen that isn't a battle, pc or menu. 442 443 Args: 444 current_screen (np.ndarray): The current screen frame from the emulator. 445 trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped. 446 447 Returns: 448 bool: True if in dialogue, False otherwise. 449 """ 450 if trust_previous: 451 return self.named_region_matches_target( 452 current_screen, "dialogue_bottom_right" 453 ) 454 if self.is_in_battle(current_screen): 455 return False 456 elif self.is_in_menu(current_screen): 457 return False 458 else: 459 return self.dialogue_box_open(current_screen) 460 461 def get_agent_state(self, current_screen: np.ndarray) -> AgentState: 462 """ 463 Determines the current agent state based on the screen. 464 465 Uses trust_previous to optimize checks. 466 467 Args: 468 current_screen (np.ndarray): The current screen frame from the emulator. 469 470 Returns: 471 AgentState: The current agent state. 472 """ 473 if self.is_in_battle(current_screen): 474 return AgentState.IN_BATTLE 475 elif self.is_in_menu(current_screen, trust_previous=True): 476 return AgentState.IN_MENU 477 elif self.is_in_dialogue(current_screen, trust_previous=True): 478 return AgentState.IN_DIALOGUE 479 else: 480 return AgentState.FREE_ROAM
Base class for Pokemon game state parsers. Uses visual screen regions to parse game state. Defines common named screen regions and methods for determining game states such as being in battle, menu, or dialogue.
Can be used to determine the exact AgentState
146 def __init__( 147 self, 148 variant: str, 149 pyboy: PyBoy, 150 parameters: dict, 151 additional_named_screen_region_details: List[ 152 Tuple[str, int, int, int, int] 153 ] = [], 154 additional_multi_target_named_screen_region_details: List[ 155 Tuple[str, int, int, int, int] 156 ] = [], 157 override_multi_targets: Dict[str, List[str]] = {}, 158 ): 159 """ 160 Initializes the PokemonStateParser. 161 Args: 162 variant (str): The variant of the Pokemon game. 163 pyboy (PyBoy): The PyBoy emulator instance. 164 parameters (dict): Configuration parameters for the emulator. 165 additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include. 166 additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include. 167 override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions. 168 """ 169 verify_parameters(parameters) 170 regions = _get_proper_regions( 171 override_regions=additional_named_screen_region_details, 172 base_regions=self.COMMON_REGIONS, 173 ) 174 self.variant = variant 175 if f"{variant}_rom_data_path" not in parameters: 176 log_error( 177 f"ROM data path not found for variant: {variant}. Add {variant}_rom_data_path to the config files. See configs/pokemon_red_vars.yaml for an example", 178 parameters, 179 ) 180 self.rom_data_path = parameters[f"{variant}_rom_data_path"] 181 """ Path to the ROM data directory for the specific Pokemon variant.""" 182 captures_dir = self.rom_data_path + "/captures/" 183 named_screen_regions = [] 184 for region_name, x, y, w, h in regions: 185 region = NamedScreenRegion( 186 region_name, 187 x, 188 y, 189 w, 190 h, 191 parameters=parameters, 192 target_path=os.path.join(captures_dir, region_name), 193 ) 194 named_screen_regions.append(region) 195 multi_target_regions = _get_proper_regions( 196 override_regions=additional_multi_target_named_screen_region_details, 197 base_regions=self.COMMON_MULTI_TARGET_REGIONS, 198 ) 199 multi_target_region_names = [region[0] for region in multi_target_regions] 200 multi_targets = self.COMMON_MULTI_TARGETS.copy() 201 for key in override_multi_targets: 202 if key in multi_targets: 203 multi_targets[key].extend(override_multi_targets[key]) 204 else: 205 multi_targets[key] = override_multi_targets[key] 206 multi_target_provided_region_names = list(multi_targets.keys()) 207 if not set(multi_target_provided_region_names).issubset( 208 set(multi_target_region_names) 209 ): 210 log_error( 211 f"Multi-target regions provided in multi_targets do not match the defined multi-target regions. Provided: {multi_target_provided_region_names}, Defined: {multi_target_region_names}", 212 parameters, 213 ) 214 for region_name, x, y, w, h in multi_target_regions: 215 region_target_paths = {} 216 subdir = captures_dir + f"/{region_name}/" 217 for target_name in multi_targets.get(region_name, []): 218 region_target_paths[target_name] = os.path.join(subdir, target_name) 219 region = NamedScreenRegion( 220 region_name, 221 x, 222 y, 223 w, 224 h, 225 parameters=parameters, 226 multi_target_paths=region_target_paths, 227 ) 228 named_screen_regions.append(region) 229 super().__init__(pyboy, parameters, named_screen_regions)
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
List of common named screen regions for Pokemon games.
- dialogue_bottom_right: Bottom right of dialogue box when interacting with NPCs, signs, etc. Speak to an NPC to capture this.
menu_top_right: Top right of the screen when the player start menu is open. Open the start menu to capture this.
pc_top_left: Top left of the screen when the PC is open. Open the PC to capture this.
battle_enemy_hp_text: Region showing the text 'HP' for the enemy Pokémon in battle. Engage in a battle to capture this.
battle_player_hp_text: Region showing the text 'HP' for the player's Pokémon in battle. Engage in a battle to capture this.
battle_base_menu_top_left: Top left of the battle base menu. Engage in a battle to capture this.
battle_fight_options_top_right: Top right of the fight options menu in battle. Engage in a battle and open the fight options to capture this.
battle_fight_options_cursor_on_top: Region showing the cursor on the top attack option in the fight options menu. Engage in a battle, open the fight options and move the cursor to the top option to capture this.
dialogue_choice_bottom_right: Bottom right of the choice dialogue box when answering choice questions (e.g. Yes/No prompts). Trigger a choice dialogue to capture this (e.g. confirmation of starter choice)
name_entity_top_left: Top left of the screen when naming a character or Pokémon. Catch a pokemon and give it a nickname to capture this.
player_card_middle: Middle of the player card screen. Go to this from start menu -> player name
map_bottom_right: Bottom right of the map screen when the town map is open. Open the town map to capture this.
List of common multi-target named screen regions for Pokemon games.
- screen: Most of the screen except for the very edges. Useful for general state parsing.
- dialogue_box_middle: Middle of the dialogue box, but not on that spot where the blinking arrow cursor appears. Useful for catching particular dialogues.
- dialogue_box_full: Full dialogue box area, is useful to capture for OCR purposes.
- screen_bottom_half: Bottom half of the screen, useful for OCR of dialogue and other text.
- screen_quadrant_1: Top right quadrant of the screen.
- screen_quadrant_2: Top left quadrant of the screen.
- screen_quadrant_3: Bottom left quadrant of the screen.
- screen_quadrant_4: Bottom right quadrant of the screen.
- screen_middle: Middle of the screen.
Common multi-targets for the common multi-target named screen regions.
- dialogue_box_middle:
- got_away_safely: Run successfully from a wild battle.
- cannot_escape: Fail to run from a wild Pokemon
- cannot_run_from_trainer: Try to run from a trainer battle and get an error message
- no_pp_for_move: Try to use a move with no PP remaining.
- menu_box_strip:
- cursor_on_options: Cursor is on the options in the start menu. This is vital to prevent agents from changing the text frame option.
- cursor_on_pokedex: Cursor is on the Pokedex in the start menu.
231 @abstractmethod 232 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 233 """ 234 Determines if the Pokedex is currently open. 235 Args: 236 current_screen (np.ndarray): The current screen frame from the emulator. 237 238 Returns: 239 bool: True if the Pokedex is open, False otherwise. 240 """ 241 raise NotImplementedError
Determines if the Pokedex is currently open.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if the Pokedex is open, False otherwise.
273 def is_in_battle(self, current_screen: np.ndarray) -> bool: 274 """ 275 Determines if the player is currently in a battle by checking for battle HP text regions. 276 277 Args: 278 current_screen (np.ndarray): The current screen frame from the emulator. 279 280 Returns: 281 bool: True if in battle, False otherwise. 282 """ 283 if self.is_in_fight_bag(current_screen): 284 return False # Then, is in menu 285 enemy_hp_match = self.named_region_matches_target( 286 current_screen, "battle_enemy_hp_text" 287 ) 288 player_hp_match = self.named_region_matches_target( 289 current_screen, "battle_player_hp_text" 290 ) 291 return enemy_hp_match or player_hp_match
Determines if the player is currently in a battle by checking for battle HP text regions.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if in battle, False otherwise.
306 def is_in_run_screen(self, current_screen: np.ndarray) -> bool: 307 """ 308 Determines if the player is currently in the run screen by checking for the battle base menu top left region. 309 Args: 310 current_screen (np.ndarray): The current screen frame from the emulator. 311 Returns: 312 bool: True if in the run screen, False otherwise. 313 """ 314 got_away_safely = self.named_region_matches_multi_target( 315 current_screen, "dialogue_box_middle", "got_away_safely" 316 ) 317 cannot_escape = self.named_region_matches_multi_target( 318 current_screen, "dialogue_box_middle", "cannot_escape" 319 ) 320 cannot_run_from_trainer = self.named_region_matches_multi_target( 321 current_screen, "dialogue_box_middle", "cannot_run_from_trainer" 322 ) 323 return got_away_safely or cannot_escape or cannot_run_from_trainer
Determines if the player is currently in the run screen by checking for the battle base menu top left region.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if in the run screen, False otherwise.
338 def is_on_top_attack_option(self, current_screen: np.ndarray) -> bool: 339 """ 340 Determines if the cursor is currently on the top attack option in the fight options menu. 341 342 Args: 343 current_screen (np.ndarray): The current screen frame from the emulator. 344 Returns: 345 bool: True if the cursor is on the top attack option, False otherwise. 346 """ 347 return self.named_region_matches_target( 348 current_screen, "battle_fight_options_cursor_on_top" 349 )
Determines if the cursor is currently on the top attack option in the fight options menu.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if the cursor is on the top attack option, False otherwise.
351 def tried_no_pp_move(self, current_screen: np.ndarray) -> bool: 352 """ 353 Determines if the player tried to use a move with no PP by checking for the no_pp_for_move target in the dialogue box middle region. 354 355 Args: 356 current_screen (np.ndarray): The current screen frame from the emulator. 357 Returns: 358 bool: True if the player tried to use a move with no PP, False otherwise. 359 """ 360 return self.named_region_matches_multi_target( 361 current_screen, "dialogue_box_middle", "no_pp_for_move" 362 )
Determines if the player tried to use a move with no PP by checking for the no_pp_for_move target in the dialogue box middle region.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if the player tried to use a move with no PP, False otherwise.
417 def dialogue_box_open(self, current_screen: np.ndarray) -> bool: 418 """ 419 Determines if a dialogue box is currently open by checking the dialogue bottom right region. 420 Args: 421 current_screen (np.ndarray): The current screen frame from the emulator. 422 Returns: 423 bool: True if a dialogue box is open, False otherwise. 424 """ 425 return self.named_region_matches_target(current_screen, "dialogue_bottom_right")
Determines if a dialogue box is currently open by checking the dialogue bottom right region.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if a dialogue box is open, False otherwise.
436 def is_in_dialogue( 437 self, current_screen: np.ndarray, trust_previous: bool = False 438 ) -> bool: 439 """ 440 Determines if the player is currently in a dialogue state or reading text from a sign, interacting with an object etc. 441 Essentially anything that causes text to appear at the bottom of the screen that isn't a battle, pc or menu. 442 443 Args: 444 current_screen (np.ndarray): The current screen frame from the emulator. 445 trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped. 446 447 Returns: 448 bool: True if in dialogue, False otherwise. 449 """ 450 if trust_previous: 451 return self.named_region_matches_target( 452 current_screen, "dialogue_bottom_right" 453 ) 454 if self.is_in_battle(current_screen): 455 return False 456 elif self.is_in_menu(current_screen): 457 return False 458 else: 459 return self.dialogue_box_open(current_screen)
Determines if the player is currently in a dialogue state or reading text from a sign, interacting with an object etc. Essentially anything that causes text to appear at the bottom of the screen that isn't a battle, pc or menu.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
- trust_previous (bool): If True, trusts that checks for other states like is_in_battle have been done and can be skipped.
Returns:
bool: True if in dialogue, False otherwise.
461 def get_agent_state(self, current_screen: np.ndarray) -> AgentState: 462 """ 463 Determines the current agent state based on the screen. 464 465 Uses trust_previous to optimize checks. 466 467 Args: 468 current_screen (np.ndarray): The current screen frame from the emulator. 469 470 Returns: 471 AgentState: The current agent state. 472 """ 473 if self.is_in_battle(current_screen): 474 return AgentState.IN_BATTLE 475 elif self.is_in_menu(current_screen, trust_previous=True): 476 return AgentState.IN_MENU 477 elif self.is_in_dialogue(current_screen, trust_previous=True): 478 return AgentState.IN_DIALOGUE 479 else: 480 return AgentState.FREE_ROAM
Determines the current agent state based on the screen.
Uses trust_previous to optimize checks.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
AgentState: The current agent state.
Inherited Members
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
483class BasePokemonRedStateParser(PokemonStateParser, ABC): 484 """ 485 Game state parser for all PokemonRed-based games. 486 """ 487 488 REGIONS = [ 489 ("pokedex_top_left", 7, 6, 12, 6), 490 ("pokedex_info_mid_left", 6, 71, 6, 6), 491 ("pokemon_list_hp_text", 32, 9, 10, 5), 492 ("pokemon_stats_line", 66, 55, 5, 5), 493 ("battle_bag_options_bottom_left", 32, 96, 5, 5), 494 ] 495 """ Additional named screen regions specific to Pokemon Red games. 496 - pokedex_top_left: Top left of the screen when the Pokedex is open. Open the Pokedex to capture this. 497 - pokedex_info_mid_left: Middle left of the screen when viewing a Pokémon's info in the Pokedex. Open a Pokémon's info in the Pokedex to capture this. 498 - pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this. 499 - pokemon_stats_line: A line in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this. 500 - battle_bag_options_bottom_left: The bottom left part of the menu when you open the items option from the battle menu. 501 """ 502 503 MULTI_TARGET_REGIONS = [ 504 ("menu_box_strip", 89, 13, 5, 100), 505 ] 506 """ Additional multi-target named screen regions specific to Pokemon Red games. 507 - menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states. 508 """ 509 510 def __init__( 511 self, 512 pyboy: PyBoy, 513 variant: str, 514 parameters: dict, 515 override_regions: List[Tuple[str, int, int, int, int]] = [], 516 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 517 override_multi_targets: Dict[str, List[str]] = {}, 518 ): 519 self.REGIONS = _get_proper_regions( 520 override_regions=override_regions, base_regions=self.REGIONS 521 ) 522 self.MULTI_TARGET_REGIONS = _get_proper_regions( 523 override_regions=override_multi_target_regions, 524 base_regions=self.MULTI_TARGET_REGIONS, 525 ) 526 super().__init__( 527 variant=variant, 528 pyboy=pyboy, 529 parameters=parameters, 530 additional_named_screen_region_details=self.REGIONS, 531 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 532 override_multi_targets=override_multi_targets, 533 ) 534 535 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 536 return self.named_region_matches_target( 537 current_screen, "pokedex_top_left" 538 ) or self.named_region_matches_target(current_screen, "pokedex_info_mid_left") 539 540 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 541 return self.named_region_matches_target( 542 current_screen, "pokemon_list_hp_text" 543 ) or self.named_region_matches_target(current_screen, "pokemon_stats_line") 544 545 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 546 return self.named_region_matches_target( 547 current_screen, "battle_bag_options_bottom_left" 548 ) 549 550 def __repr__(self): 551 return f"<PokemonRedParser(variant={self.variant})>"
Game state parser for all PokemonRed-based games.
510 def __init__( 511 self, 512 pyboy: PyBoy, 513 variant: str, 514 parameters: dict, 515 override_regions: List[Tuple[str, int, int, int, int]] = [], 516 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 517 override_multi_targets: Dict[str, List[str]] = {}, 518 ): 519 self.REGIONS = _get_proper_regions( 520 override_regions=override_regions, base_regions=self.REGIONS 521 ) 522 self.MULTI_TARGET_REGIONS = _get_proper_regions( 523 override_regions=override_multi_target_regions, 524 base_regions=self.MULTI_TARGET_REGIONS, 525 ) 526 super().__init__( 527 variant=variant, 528 pyboy=pyboy, 529 parameters=parameters, 530 additional_named_screen_region_details=self.REGIONS, 531 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 532 override_multi_targets=override_multi_targets, 533 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Additional named screen regions specific to Pokemon Red games.
- pokedex_top_left: Top left of the screen when the Pokedex is open. Open the Pokedex to capture this.
- pokedex_info_mid_left: Middle left of the screen when viewing a Pokémon's info in the Pokedex. Open a Pokémon's info in the Pokedex to capture this.
- pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this.
- pokemon_stats_line: A line in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this.
- battle_bag_options_bottom_left: The bottom left part of the menu when you open the items option from the battle menu.
Additional multi-target named screen regions specific to Pokemon Red games.
- menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states.
535 def is_in_pokedex(self, current_screen: np.ndarray) -> bool: 536 return self.named_region_matches_target( 537 current_screen, "pokedex_top_left" 538 ) or self.named_region_matches_target(current_screen, "pokedex_info_mid_left")
Determines if the Pokedex is currently open.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if the Pokedex is open, False otherwise.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
554class BasePokemonCrystalStateParser(PokemonStateParser, ABC): 555 """ 556 Game state parser for all PokemonCrystal-based games. 557 558 TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. 559 Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match. 560 """ 561 562 REGIONS = [ 563 ("pokemon_list_hp_text", 87, 16, 10, 5), 564 ("pokedex_seen_text", 3, 88, 5, 5), 565 ("pokedex_info_height_text", 69, 57, 5, 5), 566 ("pokegear_top_left", 0, 0, 6, 6), 567 ("pokemon_stats_lvl_text", 113, 0, 5, 5), 568 ("bag_text", 18, 0, 6, 6), 569 ] 570 """ Additional named screen regions specific to Pokemon Crystal games. 571 - pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this. 572 - pokedex_seen_text: Region showing the 'SEEN' text in the Pokedex. Open the Pokedex to capture this. 573 - pokedex_info_height_text: Region showing the 'HEIGHT' text in the Pokedex info screen. Open a Pokémon's info in the Pokedex to capture this. 574 - pokegear_top_left: Top left of the screen when the Pokegear is open. Open the Pokegear to capture this. 575 - pokemon_stats_lvl_text: Region showing the 'LV' text in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this. 576 - bag_text: Top left of the screen when the Bag is open. Open the Bag to capture this. 577 """ 578 579 MULTI_TARGET_REGIONS = [ 580 ("menu_box_strip", 89, 13, 5, 120), 581 ] 582 """ Additional multi-target named screen regions specific to Pokemon Crystal games. 583 - menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states. 584 """ 585 586 def __init__( 587 self, 588 pyboy: PyBoy, 589 variant: str, 590 parameters: dict, 591 override_regions: List[Tuple[str, int, int, int, int]] = [], 592 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 593 override_multi_targets: Dict[str, List[str]] = {}, 594 ): 595 self.REGIONS = _get_proper_regions( 596 override_regions=override_regions, base_regions=self.REGIONS 597 ) 598 self.MULTI_TARGET_REGIONS = _get_proper_regions( 599 override_regions=override_multi_target_regions, 600 base_regions=self.MULTI_TARGET_REGIONS, 601 ) 602 super().__init__( 603 variant=variant, 604 pyboy=pyboy, 605 parameters=parameters, 606 additional_named_screen_region_details=self.REGIONS, 607 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 608 override_multi_targets=override_multi_targets, 609 ) 610 611 def is_in_bag(self, current_screen: np.ndarray) -> bool: 612 """ 613 Determines if the Bag is currently open. 614 """ 615 return self.named_region_matches_target(current_screen, "bag_text") 616 617 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 618 """ 619 For PokemonCrystal, this is just the same as in bag. 620 """ 621 return self.is_in_bag(current_screen) 622 623 def is_in_pokegear(self, current_screen: np.ndarray) -> bool: 624 """ 625 Determines if the Pokegear is currently open. 626 """ 627 return self.named_region_matches_target(current_screen, "pokegear_top_left") 628 629 def is_in_pokedex(self, current_screen): 630 return self.named_region_matches_target( 631 current_screen, "pokedex_seen_text" 632 ) or self.named_region_matches_target( 633 current_screen, "pokedex_info_height_text" 634 ) 635 636 def is_in_pokemon_menu(self, current_screen: np.ndarray) -> bool: 637 return self.named_region_matches_target( 638 current_screen, "pokemon_stats_lvl_text" 639 ) or self.named_region_matches_target(current_screen, "pokemon_list_hp_text") 640 641 def is_in_menu( 642 self, current_screen: np.ndarray, trust_previous: bool = False 643 ) -> bool: 644 # This technically mistakenly also flags when someone calls you on the pokegear, but that's probably fine for now. 645 # Could change by adding special region for pokegear_call_top_left and overriding is_in_menu and is_in_dialogue. 646 result = super().is_in_menu(current_screen, trust_previous=trust_previous) 647 if result: 648 return True 649 if self.is_in_bag(current_screen): 650 return True 651 if self.is_in_pokegear(current_screen): 652 return True 653 # Finally, when transitioning to PC screens, maps etc, the screen goes white. Catch that here. 654 # print(f"Checking for white screen... Pixel stats: {np.min(current_screen)}, {np.max(current_screen)}, {np.mean(current_screen)}") # I get 248, 248, 248.0 655 # The following doesn't catch all white screens (e.g town maps), but does catch some important ones like PC screens. 656 if np.mean(current_screen) > 245 and np.min(current_screen) > 245: 657 return True 658 elif np.mean(current_screen) > 210: # screen coming down from full white 659 return True 660 else: 661 return False 662 663 def __repr__(self): 664 return f"<PokemonCrystalParser(variant={self.variant})>"
Game state parser for all PokemonCrystal-based games.
TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match.
586 def __init__( 587 self, 588 pyboy: PyBoy, 589 variant: str, 590 parameters: dict, 591 override_regions: List[Tuple[str, int, int, int, int]] = [], 592 override_multi_target_regions: List[Tuple[str, int, int, int, int]] = [], 593 override_multi_targets: Dict[str, List[str]] = {}, 594 ): 595 self.REGIONS = _get_proper_regions( 596 override_regions=override_regions, base_regions=self.REGIONS 597 ) 598 self.MULTI_TARGET_REGIONS = _get_proper_regions( 599 override_regions=override_multi_target_regions, 600 base_regions=self.MULTI_TARGET_REGIONS, 601 ) 602 super().__init__( 603 variant=variant, 604 pyboy=pyboy, 605 parameters=parameters, 606 additional_named_screen_region_details=self.REGIONS, 607 additional_multi_target_named_screen_region_details=self.MULTI_TARGET_REGIONS, 608 override_multi_targets=override_multi_targets, 609 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Additional named screen regions specific to Pokemon Crystal games.
- pokemon_list_hp_text: Region showing the text 'HP' for the player's Pokémon in the Pokémon list. Open the Pokémon list from the start menu to capture this.
- pokedex_seen_text: Region showing the 'SEEN' text in the Pokedex. Open the Pokedex to capture this.
- pokedex_info_height_text: Region showing the 'HEIGHT' text in the Pokedex info screen. Open a Pokémon's info in the Pokedex to capture this.
- pokegear_top_left: Top left of the screen when the Pokegear is open. Open the Pokegear to capture this.
- pokemon_stats_lvl_text: Region showing the 'LV' text in the Pokémon stats screen. Open a Pokémon's stats from the start menu -> pokemon menu to capture this.
- bag_text: Top left of the screen when the Bag is open. Open the Bag to capture this.
Additional multi-target named screen regions specific to Pokemon Crystal games.
- menu_box_strip: Strip of the menu box when the start menu is open. Open the start menu to capture this. The margins are adjusted to avoiding capturing the player name, as this may change across sav files and states.
611 def is_in_bag(self, current_screen: np.ndarray) -> bool: 612 """ 613 Determines if the Bag is currently open. 614 """ 615 return self.named_region_matches_target(current_screen, "bag_text")
Determines if the Bag is currently open.
617 def is_in_fight_bag(self, current_screen: np.ndarray) -> bool: 618 """ 619 For PokemonCrystal, this is just the same as in bag. 620 """ 621 return self.is_in_bag(current_screen)
For PokemonCrystal, this is just the same as in bag.
623 def is_in_pokegear(self, current_screen: np.ndarray) -> bool: 624 """ 625 Determines if the Pokegear is currently open. 626 """ 627 return self.named_region_matches_target(current_screen, "pokegear_top_left")
Determines if the Pokegear is currently open.
629 def is_in_pokedex(self, current_screen): 630 return self.named_region_matches_target( 631 current_screen, "pokedex_seen_text" 632 ) or self.named_region_matches_target( 633 current_screen, "pokedex_info_height_text" 634 )
Determines if the Pokedex is currently open.
Arguments:
- current_screen (np.ndarray): The current screen frame from the emulator.
Returns:
bool: True if the Pokedex is open, False otherwise.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
667class PokemonRedStateParser(BasePokemonRedStateParser): 668 def __init__(self, pyboy, parameters): 669 override_multi_targets = { 670 "dialogue_box_middle": [ 671 "picked_charmander", 672 "picked_bulbasaur", 673 "picked_squirtle", 674 "talk_bill_complete", 675 "pick_up_pokeball_starting", # is tied to player character name being You 676 "trainers_tips_sign", 677 "cinnabar_gym_aid_complete", 678 "talk_cinnabar_monk", 679 "defeated_brock", 680 "defeated_lass", 681 "caught_pidgey", 682 "caught_pikachu", 683 "used_potion_on_charmander", 684 ], 685 "screen_bottom_half": [ 686 "viridian_pokemon_center_entrance", 687 "mt_moon_entrance", 688 "bought_potion_at_pewter_pokemart", 689 ], 690 "screen_middle": [ 691 "outside_viridian_center_from_left", 692 "outside_viridian_center_from_right", 693 ], 694 } 695 super().__init__( 696 pyboy, 697 variant="pokemon_red", 698 parameters=parameters, 699 override_multi_targets=override_multi_targets, 700 )
Game state parser for all PokemonRed-based games.
668 def __init__(self, pyboy, parameters): 669 override_multi_targets = { 670 "dialogue_box_middle": [ 671 "picked_charmander", 672 "picked_bulbasaur", 673 "picked_squirtle", 674 "talk_bill_complete", 675 "pick_up_pokeball_starting", # is tied to player character name being You 676 "trainers_tips_sign", 677 "cinnabar_gym_aid_complete", 678 "talk_cinnabar_monk", 679 "defeated_brock", 680 "defeated_lass", 681 "caught_pidgey", 682 "caught_pikachu", 683 "used_potion_on_charmander", 684 ], 685 "screen_bottom_half": [ 686 "viridian_pokemon_center_entrance", 687 "mt_moon_entrance", 688 "bought_potion_at_pewter_pokemart", 689 ], 690 "screen_middle": [ 691 "outside_viridian_center_from_left", 692 "outside_viridian_center_from_right", 693 ], 694 } 695 super().__init__( 696 pyboy, 697 variant="pokemon_red", 698 parameters=parameters, 699 override_multi_targets=override_multi_targets, 700 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
703class PokemonBrownStateParser(BasePokemonRedStateParser): 704 def __init__(self, pyboy, parameters): 705 super().__init__(pyboy, variant="pokemon_brown", parameters=parameters)
Game state parser for all PokemonRed-based games.
704 def __init__(self, pyboy, parameters): 705 super().__init__(pyboy, variant="pokemon_brown", parameters=parameters)
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
708class PokemonStarBeastsStateParser(BasePokemonRedStateParser): 709 def __init__(self, pyboy, parameters): 710 override_regions = [ 711 ("pokemon_list_hp_text", 33, 10, 4, 4), 712 ("battle_enemy_hp_text", 6, 15, 5, 5), 713 ("battle_player_hp_text", 88, 72, 5, 5), 714 ] 715 super().__init__( 716 pyboy, 717 variant="pokemon_starbeasts", 718 parameters=parameters, 719 override_regions=override_regions, 720 )
Game state parser for all PokemonRed-based games.
709 def __init__(self, pyboy, parameters): 710 override_regions = [ 711 ("pokemon_list_hp_text", 33, 10, 4, 4), 712 ("battle_enemy_hp_text", 6, 15, 5, 5), 713 ("battle_player_hp_text", 88, 72, 5, 5), 714 ] 715 super().__init__( 716 pyboy, 717 variant="pokemon_starbeasts", 718 parameters=parameters, 719 override_regions=override_regions, 720 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
723class PokemonStarBeastsCometStateParser(BasePokemonRedStateParser): 724 def __init__(self, pyboy, parameters): 725 #override_regions = [ 726 #("pokemon_list_hp_text", 33, 10, 4, 4), 727 #("battle_enemy_hp_text", 6, 15, 5, 5), 728 #("battle_player_hp_text", 88, 72, 5, 5), 729 #] 730 super().__init__( 731 pyboy, 732 variant="pokemon_starbeasts_comet", 733 parameters=parameters, 734 #override_regions=override_regions, 735 )
Game state parser for all PokemonRed-based games.
724 def __init__(self, pyboy, parameters): 725 #override_regions = [ 726 #("pokemon_list_hp_text", 33, 10, 4, 4), 727 #("battle_enemy_hp_text", 6, 15, 5, 5), 728 #("battle_player_hp_text", 88, 72, 5, 5), 729 #] 730 super().__init__( 731 pyboy, 732 variant="pokemon_starbeasts_comet", 733 parameters=parameters, 734 #override_regions=override_regions, 735 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
738class PokemonCrystalStateParser(BasePokemonCrystalStateParser): 739 def __init__(self, pyboy, parameters): 740 super().__init__(pyboy, variant="pokemon_crystal", parameters=parameters)
Game state parser for all PokemonCrystal-based games.
TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match.
739 def __init__(self, pyboy, parameters): 740 super().__init__(pyboy, variant="pokemon_crystal", parameters=parameters)
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- BasePokemonCrystalStateParser
- REGIONS
- MULTI_TARGET_REGIONS
- is_in_bag
- is_in_fight_bag
- is_in_pokegear
- is_in_pokedex
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
743class PokemonPrismStateParser(BasePokemonCrystalStateParser): 744 def __init__(self, pyboy, parameters): 745 override_regions = [("player_card_middle", 25, 58, 5, 5)] 746 super().__init__( 747 pyboy, 748 variant="pokemon_prism", 749 parameters=parameters, 750 override_regions=override_regions, 751 )
Game state parser for all PokemonCrystal-based games.
TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match.
744 def __init__(self, pyboy, parameters): 745 override_regions = [("player_card_middle", 25, 58, 5, 5)] 746 super().__init__( 747 pyboy, 748 variant="pokemon_prism", 749 parameters=parameters, 750 override_regions=override_regions, 751 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- BasePokemonCrystalStateParser
- REGIONS
- MULTI_TARGET_REGIONS
- is_in_bag
- is_in_fight_bag
- is_in_pokegear
- is_in_pokedex
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
754class PokemonFoolsGoldStateParser(BasePokemonCrystalStateParser): 755 def __init__(self, pyboy, parameters): 756 override_regions = [("pokedex_info_height_text", 66, 65, 5, 5)] 757 super().__init__( 758 pyboy, 759 variant="pokemon_fools_gold", 760 parameters=parameters, 761 override_regions=override_regions, 762 )
Game state parser for all PokemonCrystal-based games.
TODO: The map screenshot for crystal assumes a Jhoto map. Must do a similar process for Kanto. To add Kanto we should add another named screen region called map_bottom_right_kanto with same boundary as player_card_middle and then recapture it. Without this fix, the is_in_menu check may fail when in Kanto as the map_bottom_right region will not match.
755 def __init__(self, pyboy, parameters): 756 override_regions = [("pokedex_info_height_text", 66, 65, 5, 5)] 757 super().__init__( 758 pyboy, 759 variant="pokemon_fools_gold", 760 parameters=parameters, 761 override_regions=override_regions, 762 )
Initializes the PokemonStateParser.
Arguments:
- variant (str): The variant of the Pokemon game.
- pyboy (PyBoy): The PyBoy emulator instance.
- parameters (dict): Configuration parameters for the emulator.
- additional_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional named screen regions to include.
- additional_multi_target_named_screen_region_details (List[Tuple[str, int, int, int, int]]): Parameters associated with additional multi-target named screen regions to include.
- override_multi_targets (Dict[str, List[str]]): Dictionary mapping region names to lists of target names for multi-target regions.
Inherited Members
- BasePokemonCrystalStateParser
- REGIONS
- MULTI_TARGET_REGIONS
- is_in_bag
- is_in_fight_bag
- is_in_pokegear
- is_in_pokedex
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference
772class MemoryBasedPokemonRedStateParser(PokemonRedStateParser): 773 """ 774 Game state parser for Pokemon Red. Uses memory addresses to parse game state. 775 Can be used to reproduce https://github.com/PWhiddy/PokemonRedExperiments/ (v2) and facilitates reward engineering based on memory states. 776 """ 777 778 _PAD = 20 779 _GLOBAL_MAP_SHAPE = (444 + _PAD * 2, 436 + _PAD * 2) 780 _MAP_ROW_OFFSET = _PAD 781 _MAP_COL_OFFSET = _PAD 782 783 def __init__(self, pyboy, parameters): 784 """ 785 Initializes the Pokemon Red game state parser. 786 787 Args: 788 pyboy: An instance of the PyBoy emulator. 789 parameters: A dictionary of parameters for configuration. 790 """ 791 super().__init__(pyboy, parameters=parameters) 792 events_location = parameters["pokemon_red_rom_data_path"] + "/events.json" 793 with open(events_location) as f: 794 event_slots = json.load(f) 795 event_slots = event_slots 796 event_names = {v: k for k, v in event_slots.items() if not v[0].isdigit()} 797 beat_opponent_events = bidict() 798 799 def _pop(d, keys): 800 for key in keys: 801 if key in d: 802 d.pop(key, None) 803 804 pop_queue = [] 805 for name, slot in event_names.items(): 806 if name.startswith("Beat "): 807 beat_opponent_events[name.replace("Beat ", "")] = slot 808 pop_queue.append(name) 809 _pop(event_names, pop_queue) 810 self.defeated_opponent_events = beat_opponent_events 811 """Events related to beating specific opponents. E.g. Beat Brock""" 812 tms_obtained_events = bidict() 813 pop_queue = [] 814 for name, slot in event_names.items(): 815 if name.startswith("Got Tm"): 816 tms_obtained_events[name.replace("Got ", "").strip()] = slot 817 pop_queue.append(name) 818 _pop(event_names, pop_queue) 819 self.tms_obtained_events = tms_obtained_events 820 """Events related to obtaining specific TMs. E.g. Got Tm01""" 821 hm_obtained_events = bidict() 822 pop_queue = [] 823 for name, slot in event_names.items(): 824 if name.startswith("Got Hm"): 825 hm_obtained_events[name.replace("Got ", "").strip()] = slot 826 pop_queue.append(name) 827 _pop(event_names, pop_queue) 828 self.hm_obtained_events = hm_obtained_events 829 """Events related to obtaining specific HMs. E.g. Got Hm01""" 830 passed_badge_check_events = bidict() 831 pop_queue = [] 832 for name, slot in event_names.items(): 833 if name.startswith("Passed ") and "badge" in name: 834 passed_badge_check_events[ 835 name.replace("Passed ", "").replace(" Check", "").strip() 836 ] = slot 837 pop_queue.append(name) 838 _pop(event_names, pop_queue) 839 self.passed_badge_check_events = passed_badge_check_events 840 """Events related to passing badge checks. E.g. Passed Boulder badge check. These will only be relevant to enter Victory Road.""" 841 self.key_items_obtained_events = bidict() 842 """Events related to obtaining key items. E.g. Got Bicycle""" 843 pop_queue = [] 844 for name, slot in event_names.items(): 845 if name.startswith("Got "): 846 self.key_items_obtained_events[name.replace("Got ", "").strip()] = slot 847 pop_queue.append(name) 848 _pop(event_names, pop_queue) 849 self.map_events = { 850 "Cinnabar Gym": bidict(), 851 "Victory Road": bidict(), 852 "Silph Co": bidict(), 853 "Seafoam Islands": bidict(), 854 } 855 """Events related to specific map events like unlocking gates or moving boulders.""" 856 for name, slot in event_names.items(): 857 if name.startswith("Cinnabar Gym Gate") and name.endswith("Unlocked"): 858 self.map_events["Cinnabar Gym"][name] = slot 859 pop_queue.append(name) 860 elif name.startswith("Victory Road") and "Boulder On" in name: 861 self.map_events["Victory Road"][name] = slot 862 pop_queue.append(name) 863 elif name.startswith("Silph Co") and "Unlocked" in name: 864 self.map_events["Silph Co"][name] = slot 865 pop_queue.append(name) 866 elif name.startswith("Seafoam"): 867 self.map_events["Seafoam Islands"][name] = slot 868 pop_queue.append(name) 869 _pop(event_names, pop_queue) 870 self.cutscene_events = bidict() 871 """ Flags for cutscene based events (I think, lol). """ 872 873 cutscenes = [ 874 "Event 001", 875 "Daisy Walking", 876 "Pokemon Tower Rival On Left", 877 "Seel Fan Boast", 878 "Pikachu Fan Boast", 879 "Lab Handing Over Fossil Mon", 880 "Route22 Rival Wants Battle", 881 ] # my best guess, need to verify, Silph Co Receptionist At Desk? Autowalks? 882 pop_queue = [] 883 for name, slot in event_names.items(): 884 if name in cutscenes: 885 self.cutscene_events[name] = slot 886 pop_queue.append(name) 887 _pop(event_names, pop_queue) 888 self.special_events = bidict(event_names) 889 """ All other events not categorized elsewhere.""" 890 891 MAP_PATH = parameters["pokemon_red_rom_data_path"] + "/map_data.json" 892 with open(MAP_PATH) as map_data: 893 MAP_DATA = json.load(map_data)["regions"] 894 self._MAP_DATA = {int(e["id"]): e for e in MAP_DATA} 895 896 def get_map_name(self, map_n: int) -> Optional[str]: 897 """ 898 Gets the name of the map given its identifier. 899 Args: 900 map_n (int): Map identifier. 901 Returns: 902 Optional[str]: Name of the map if found, None otherwise. 903 """ 904 try: 905 return self._MAP_DATA[map_n]["name"] 906 except KeyError: 907 return None 908 909 def local_to_global(self, r: int, c: int, map_n: int) -> Tuple[int, int]: 910 """ 911 Converts local map coordinates to global map coordinates. 912 Args: 913 r (int): Local row coordinate. 914 c (int): Local column coordinate. 915 map_n (int): Map identifier. 916 Returns: 917 (int, int): Global (row, column) coordinates. 918 """ 919 try: 920 ( 921 map_x, 922 map_y, 923 ) = self._MAP_DATA[ 924 map_n 925 ]["coordinates"] 926 gy = r + map_y + self._MAP_ROW_OFFSET 927 gx = c + map_x + self._MAP_COL_OFFSET 928 if ( 929 0 <= gy < self._GLOBAL_MAP_SHAPE[0] 930 and 0 <= gx < self._GLOBAL_MAP_SHAPE[1] 931 ): 932 return gy, gx 933 print( 934 f"coord out of bounds! global: ({gx}, {gy}) game: ({r}, {c}, {map_n})" 935 ) 936 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2 937 except KeyError: 938 print(f"Map id {map_n} not found in map_data.json.") 939 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2 940 941 def get_opponents_defeated(self) -> Set[str]: 942 """ 943 Returns a set of all defeated opponents. This function isn't actually used in any current environments, but is left here to show how to read game state information. 944 Similar functions can be created to read obtained TMs, HMs, key items, passed badge checks, etc. 945 946 Returns: 947 Set[str]: A set of names of defeated opponents. 948 """ 949 return self.get_raised_flags(self.defeated_opponent_events) 950 951 def get_facing_direction(self) -> Tuple[int, int]: 952 """ 953 Gets the direction the player is facing. 954 Returns: 955 (int, int): Tuple representing the direction vector (dy, dx). 956 """ 957 direction = self.read_m(0xD52A) 958 if direction == 1: 959 return (0, 1) # Right 960 elif direction == 2: 961 return (0, -1) # Left 962 elif direction == 4: 963 return (1, 0) # Down 964 else: 965 return (-1, 0) # Up 966 967 def get_local_coords(self) -> Tuple[int, int, int]: 968 """ 969 Gets the local game coordinates (x, y, map number). 970 Returns: 971 (int, int, int): Tuple containing (x, y, map number). 972 """ 973 return (self.read_m(0xD362), self.read_m(0xD361), self.read_m(0xD35E)) 974 975 def get_global_coords(self): 976 """ 977 Gets the global coordinates of the player. 978 Returns: 979 (int, int): Tuple containing (global y, global x) coordinates. 980 """ 981 x_pos, y_pos, map_n = self.get_local_coords() 982 return self.local_to_global(y_pos, x_pos, map_n) 983 984 def get_badges(self) -> np.array: 985 """ 986 Gets the player's badges as a binary array. 987 Returns: 988 np.array: Array of 8 binary values representing whether the player has obtained each of the badges. 989 """ 990 # or self.bit_count(self.read_m(0xD356)) 991 return np.array( 992 [int(bit) for bit in f"{self.read_m(0xD356):08b}"], dtype=np.int8 993 )
Game state parser for Pokemon Red. Uses memory addresses to parse game state. Can be used to reproduce https://github.com/PWhiddy/PokemonRedExperiments/ (v2) and facilitates reward engineering based on memory states.
783 def __init__(self, pyboy, parameters): 784 """ 785 Initializes the Pokemon Red game state parser. 786 787 Args: 788 pyboy: An instance of the PyBoy emulator. 789 parameters: A dictionary of parameters for configuration. 790 """ 791 super().__init__(pyboy, parameters=parameters) 792 events_location = parameters["pokemon_red_rom_data_path"] + "/events.json" 793 with open(events_location) as f: 794 event_slots = json.load(f) 795 event_slots = event_slots 796 event_names = {v: k for k, v in event_slots.items() if not v[0].isdigit()} 797 beat_opponent_events = bidict() 798 799 def _pop(d, keys): 800 for key in keys: 801 if key in d: 802 d.pop(key, None) 803 804 pop_queue = [] 805 for name, slot in event_names.items(): 806 if name.startswith("Beat "): 807 beat_opponent_events[name.replace("Beat ", "")] = slot 808 pop_queue.append(name) 809 _pop(event_names, pop_queue) 810 self.defeated_opponent_events = beat_opponent_events 811 """Events related to beating specific opponents. E.g. Beat Brock""" 812 tms_obtained_events = bidict() 813 pop_queue = [] 814 for name, slot in event_names.items(): 815 if name.startswith("Got Tm"): 816 tms_obtained_events[name.replace("Got ", "").strip()] = slot 817 pop_queue.append(name) 818 _pop(event_names, pop_queue) 819 self.tms_obtained_events = tms_obtained_events 820 """Events related to obtaining specific TMs. E.g. Got Tm01""" 821 hm_obtained_events = bidict() 822 pop_queue = [] 823 for name, slot in event_names.items(): 824 if name.startswith("Got Hm"): 825 hm_obtained_events[name.replace("Got ", "").strip()] = slot 826 pop_queue.append(name) 827 _pop(event_names, pop_queue) 828 self.hm_obtained_events = hm_obtained_events 829 """Events related to obtaining specific HMs. E.g. Got Hm01""" 830 passed_badge_check_events = bidict() 831 pop_queue = [] 832 for name, slot in event_names.items(): 833 if name.startswith("Passed ") and "badge" in name: 834 passed_badge_check_events[ 835 name.replace("Passed ", "").replace(" Check", "").strip() 836 ] = slot 837 pop_queue.append(name) 838 _pop(event_names, pop_queue) 839 self.passed_badge_check_events = passed_badge_check_events 840 """Events related to passing badge checks. E.g. Passed Boulder badge check. These will only be relevant to enter Victory Road.""" 841 self.key_items_obtained_events = bidict() 842 """Events related to obtaining key items. E.g. Got Bicycle""" 843 pop_queue = [] 844 for name, slot in event_names.items(): 845 if name.startswith("Got "): 846 self.key_items_obtained_events[name.replace("Got ", "").strip()] = slot 847 pop_queue.append(name) 848 _pop(event_names, pop_queue) 849 self.map_events = { 850 "Cinnabar Gym": bidict(), 851 "Victory Road": bidict(), 852 "Silph Co": bidict(), 853 "Seafoam Islands": bidict(), 854 } 855 """Events related to specific map events like unlocking gates or moving boulders.""" 856 for name, slot in event_names.items(): 857 if name.startswith("Cinnabar Gym Gate") and name.endswith("Unlocked"): 858 self.map_events["Cinnabar Gym"][name] = slot 859 pop_queue.append(name) 860 elif name.startswith("Victory Road") and "Boulder On" in name: 861 self.map_events["Victory Road"][name] = slot 862 pop_queue.append(name) 863 elif name.startswith("Silph Co") and "Unlocked" in name: 864 self.map_events["Silph Co"][name] = slot 865 pop_queue.append(name) 866 elif name.startswith("Seafoam"): 867 self.map_events["Seafoam Islands"][name] = slot 868 pop_queue.append(name) 869 _pop(event_names, pop_queue) 870 self.cutscene_events = bidict() 871 """ Flags for cutscene based events (I think, lol). """ 872 873 cutscenes = [ 874 "Event 001", 875 "Daisy Walking", 876 "Pokemon Tower Rival On Left", 877 "Seel Fan Boast", 878 "Pikachu Fan Boast", 879 "Lab Handing Over Fossil Mon", 880 "Route22 Rival Wants Battle", 881 ] # my best guess, need to verify, Silph Co Receptionist At Desk? Autowalks? 882 pop_queue = [] 883 for name, slot in event_names.items(): 884 if name in cutscenes: 885 self.cutscene_events[name] = slot 886 pop_queue.append(name) 887 _pop(event_names, pop_queue) 888 self.special_events = bidict(event_names) 889 """ All other events not categorized elsewhere.""" 890 891 MAP_PATH = parameters["pokemon_red_rom_data_path"] + "/map_data.json" 892 with open(MAP_PATH) as map_data: 893 MAP_DATA = json.load(map_data)["regions"] 894 self._MAP_DATA = {int(e["id"]): e for e in MAP_DATA}
Initializes the Pokemon Red game state parser.
Arguments:
- pyboy: An instance of the PyBoy emulator.
- parameters: A dictionary of parameters for configuration.
Events related to passing badge checks. E.g. Passed Boulder badge check. These will only be relevant to enter Victory Road.
896 def get_map_name(self, map_n: int) -> Optional[str]: 897 """ 898 Gets the name of the map given its identifier. 899 Args: 900 map_n (int): Map identifier. 901 Returns: 902 Optional[str]: Name of the map if found, None otherwise. 903 """ 904 try: 905 return self._MAP_DATA[map_n]["name"] 906 except KeyError: 907 return None
Gets the name of the map given its identifier.
Arguments:
- map_n (int): Map identifier.
Returns:
Optional[str]: Name of the map if found, None otherwise.
909 def local_to_global(self, r: int, c: int, map_n: int) -> Tuple[int, int]: 910 """ 911 Converts local map coordinates to global map coordinates. 912 Args: 913 r (int): Local row coordinate. 914 c (int): Local column coordinate. 915 map_n (int): Map identifier. 916 Returns: 917 (int, int): Global (row, column) coordinates. 918 """ 919 try: 920 ( 921 map_x, 922 map_y, 923 ) = self._MAP_DATA[ 924 map_n 925 ]["coordinates"] 926 gy = r + map_y + self._MAP_ROW_OFFSET 927 gx = c + map_x + self._MAP_COL_OFFSET 928 if ( 929 0 <= gy < self._GLOBAL_MAP_SHAPE[0] 930 and 0 <= gx < self._GLOBAL_MAP_SHAPE[1] 931 ): 932 return gy, gx 933 print( 934 f"coord out of bounds! global: ({gx}, {gy}) game: ({r}, {c}, {map_n})" 935 ) 936 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2 937 except KeyError: 938 print(f"Map id {map_n} not found in map_data.json.") 939 return self._GLOBAL_MAP_SHAPE[0] // 2, self._GLOBAL_MAP_SHAPE[1] // 2
Converts local map coordinates to global map coordinates.
Arguments:
- r (int): Local row coordinate.
- c (int): Local column coordinate.
- map_n (int): Map identifier.
Returns:
(int, int): Global (row, column) coordinates.
941 def get_opponents_defeated(self) -> Set[str]: 942 """ 943 Returns a set of all defeated opponents. This function isn't actually used in any current environments, but is left here to show how to read game state information. 944 Similar functions can be created to read obtained TMs, HMs, key items, passed badge checks, etc. 945 946 Returns: 947 Set[str]: A set of names of defeated opponents. 948 """ 949 return self.get_raised_flags(self.defeated_opponent_events)
Returns a set of all defeated opponents. This function isn't actually used in any current environments, but is left here to show how to read game state information. Similar functions can be created to read obtained TMs, HMs, key items, passed badge checks, etc.
Returns:
Set[str]: A set of names of defeated opponents.
951 def get_facing_direction(self) -> Tuple[int, int]: 952 """ 953 Gets the direction the player is facing. 954 Returns: 955 (int, int): Tuple representing the direction vector (dy, dx). 956 """ 957 direction = self.read_m(0xD52A) 958 if direction == 1: 959 return (0, 1) # Right 960 elif direction == 2: 961 return (0, -1) # Left 962 elif direction == 4: 963 return (1, 0) # Down 964 else: 965 return (-1, 0) # Up
Gets the direction the player is facing.
Returns:
(int, int): Tuple representing the direction vector (dy, dx).
967 def get_local_coords(self) -> Tuple[int, int, int]: 968 """ 969 Gets the local game coordinates (x, y, map number). 970 Returns: 971 (int, int, int): Tuple containing (x, y, map number). 972 """ 973 return (self.read_m(0xD362), self.read_m(0xD361), self.read_m(0xD35E))
Gets the local game coordinates (x, y, map number).
Returns:
(int, int, int): Tuple containing (x, y, map number).
975 def get_global_coords(self): 976 """ 977 Gets the global coordinates of the player. 978 Returns: 979 (int, int): Tuple containing (global y, global x) coordinates. 980 """ 981 x_pos, y_pos, map_n = self.get_local_coords() 982 return self.local_to_global(y_pos, x_pos, map_n)
Gets the global coordinates of the player.
Returns:
(int, int): Tuple containing (global y, global x) coordinates.
984 def get_badges(self) -> np.array: 985 """ 986 Gets the player's badges as a binary array. 987 Returns: 988 np.array: Array of 8 binary values representing whether the player has obtained each of the badges. 989 """ 990 # or self.bit_count(self.read_m(0xD356)) 991 return np.array( 992 [int(bit) for bit in f"{self.read_m(0xD356):08b}"], dtype=np.int8 993 )
Gets the player's badges as a binary array.
Returns:
np.array: Array of 8 binary values representing whether the player has obtained each of the badges.
Inherited Members
- PokemonStateParser
- COMMON_REGIONS
- COMMON_MULTI_TARGET_REGIONS
- COMMON_MULTI_TARGETS
- variant
- rom_data_path
- is_in_battle
- is_in_run_screen
- is_on_top_attack_option
- tried_no_pp_move
- dialogue_box_open
- dialogue_box_empty
- is_in_dialogue
- get_agent_state
- gameboy_worlds.emulation.parser.StateParser
- named_screen_regions
- image_references
- bit_count
- read_m
- read_bits
- read_bit
- read_m_bit
- get_raised_flags
- get_current_frame
- capture_box
- capture_square_centered
- draw_box
- draw_square_centered
- capture_named_region
- compare_named_region_against_target
- named_region_matches_target
- compare_named_region_against_multi_target
- named_region_matches_multi_target
- draw_named_region
- draw_grid_overlay
- capture_grid_cells
- reform_image
- get_quadrant_frame
- get_image_reference