gameboy_worlds.emulation.survival_kids.parsers
Survival Kids game state parsers.
1""" 2Survival Kids game state parsers. 3""" 4 5import os 6from enum import Enum 7from typing import Dict, List, Optional, Tuple 8 9import numpy as np 10 11from gameboy_worlds.emulation.parser import ( 12 NamedScreenRegion, 13 StateParser, 14 _get_proper_regions, 15) 16from gameboy_worlds.utils import log_error, verify_parameters 17 18 19def _merge_multi_targets( 20 *multi_targets: Dict[str, List[str]], 21) -> Dict[str, List[str]]: 22 merged: Dict[str, List[str]] = {} 23 for targets in multi_targets: 24 for region_name, target_names in targets.items(): 25 region_targets = merged.setdefault(region_name, []) 26 for target_name in target_names: 27 if target_name not in region_targets: 28 region_targets.append(target_name) 29 return merged 30 31 32class AgentState(Enum): 33 FREE_ROAM = 0 34 IN_DIALOGUE = 1 35 IN_MENU = 2 36 37 38class SurvivalKidsParser(StateParser): 39 """Game state parser for Survival Kids 1 (GBC).""" 40 41 VARIANT = "survival_kids_1" 42 LOAD_ONLY_EXISTING_MULTI_TARGETS = False 43 FORCE_LOAD_MISSING_MULTI_TARGETS: Dict[str, List[str]] = {} 44 45 MULTI_TARGET_REGIONS: List[Tuple[str, int, int, int, int]] = [ 46 ("screen", 0, 0, 160, 144), 47 ("screen_bottom", 0, 108, 160, 36), 48 ("game_viewport", 0, 0, 160, 128), 49 ("status_bar", 0, 128, 160, 16), 50 ("hp_area", 0, 128, 40, 16), 51 ("hunger_area", 0, 136, 42, 8), 52 ("thirst_area", 42, 136, 42, 8), 53 ("stamina_area", 84, 136, 42, 8), 54 ("equipped_items_area", 20, 128, 24, 8), 55 ("equipped_item_area", 0, 105, 160, 39), 56 ("pack_icon_area", 144, 128, 16, 16), 57 ("bag_icon_area", 64, 32, 48, 48), 58 ("object_area", 64, 32, 48, 48), 59 ("choose_item_area", 0, 20, 160, 80), 60 ("dialogue_area", 8, 112, 144, 28), 61 ("inventory_select_area", 0, 0, 88, 72), 62 ("inventory_description_area", 8, 98, 144, 30), 63 ("item_action_menu", 0, 0, 64, 56), 64 ("item_action_menu_two_options", 0, 0, 64, 40), 65 ("item_action_menu_three_options", 0, 0, 64, 57), 66 ("inventory_item_action_menu", 8, 98, 144, 38), 67 ("item_use_menu_area", 0, 84, 160, 56), 68 ("merge_menu_area", 0, 24, 160, 76), 69 ("item_action_cursor", 6, 8, 12, 8), 70 ("item_action_options", 18, 8, 42, 28), 71 ("menu_area", 0, 0, 160, 144), 72 ("merge_confirm_area", 0, 101, 160, 31), 73 ] 74 75 MULTI_TARGETS: Dict[str, List[str]] = { 76 "screen": [ 77 "after_drinking_water", 78 "after_filling_water", 79 "day_reference", 80 "entered_shelter", 81 "found_river", 82 "got_the_brdfeather", 83 "got_the_stick", 84 "got_the_tree_bark", 85 "got_the_water", 86 "inventory_select_item", 87 "night_reference", 88 "water_menu_open", 89 ], 90 "menu_area": [ 91 "inventory_after_fire_lit", 92 "inventory_open", 93 ], 94 "merge_menu_area": [ 95 "merge_menu", 96 "select_stick", 97 ], 98 "merge_confirm_area": [ 99 "merge_confirm", 100 ], 101 "item_action_menu_two_options": [ 102 "select_take", 103 "take_leave_menu", 104 "feather_take_leave_menu", 105 ], 106 "dialogue_area": [ 107 "pickup_item_dialogue", 108 "canteen_pickup_dialogue", 109 "cooked_meat_eaten_dialogue", 110 "cooked_meat_stored", 111 "got_the_clam", 112 "meat_cooked_dialogue", 113 ], 114 "bag_icon_area": [ 115 "bag_icon", 116 ], 117 "equipped_item_area": [ 118 "knife_equipped", 119 "use_kindling", 120 ], 121 "choose_item_area": [ 122 "canteen_chosen", 123 "knife_chosen", 124 "select_kindling", 125 ], 126 "inventory_select_area": [ 127 "kindling_merged", 128 ], 129 "inventory_description_area": [ 130 "select_meat", 131 ], 132 "item_use_menu_area": [ 133 "canteen_action_menu", 134 "canteen_drink_selected", 135 "canteen_use_selected", 136 ], 137 "item_action_menu": [ 138 "canteen_take_leave_menu", 139 "cooked_meat_action_menu", 140 "cooked_meat_eat_selected", 141 "meat_take_eat_leave_menu", 142 "select_take", 143 ], 144 "inventory_item_action_menu": [ 145 "burn_confirm", 146 "meat_burn_selected", 147 "select_drop", 148 ], 149 "game_viewport": [ 150 "animal_killed", 151 "chapter1_path_cleared", 152 "fire_lit", 153 "grass_cut", 154 "in_the_shelter", 155 "path_after_blocking_grass", 156 ], 157 } 158 159 _DARK_PIXEL_THRESHOLD = 24 160 _BRIGHT_PIXEL_THRESHOLD = 220 161 _DIALOGUE_BRIGHT_RATIO_THRESHOLD = 0.55 162 _MENU_DARK_RATIO_THRESHOLD = 0.08 163 _MENU_BRIGHT_RATIO_THRESHOLD = 0.18 164 _FULL_MENU_BRIGHT_RATIO_THRESHOLD = 0.75 165 _BOTTOM_MENU_DARK_RATIO_MAX = 0.025 166 167 def __init__( 168 self, 169 pyboy, 170 parameters: dict, 171 additional_multi_target_regions: Optional[ 172 List[Tuple[str, int, int, int, int]] 173 ] = None, 174 override_multi_targets: Optional[Dict[str, List[str]]] = None, 175 ): 176 verify_parameters(parameters) 177 additional_multi_target_regions = additional_multi_target_regions or [] 178 override_multi_targets = override_multi_targets or {} 179 if f"{self.VARIANT}_rom_data_path" not in parameters: 180 log_error( 181 f"ROM data path not found for variant: {self.VARIANT}. " 182 f"Add {self.VARIANT}_rom_data_path to the config files.", 183 parameters, 184 ) 185 self.rom_data_path = parameters[f"{self.VARIANT}_rom_data_path"] 186 captures_dir = os.path.join(self.rom_data_path, "captures") 187 188 multi_target_regions = _get_proper_regions( 189 override_regions=additional_multi_target_regions, 190 base_regions=self.MULTI_TARGET_REGIONS, 191 ) 192 193 multi_targets: Dict[str, List[str]] = {} 194 for key, val in self.MULTI_TARGETS.items(): 195 multi_targets[key] = list(val) 196 for key, val in override_multi_targets.items(): 197 if key in multi_targets: 198 multi_targets[key].extend(val) 199 else: 200 multi_targets[key] = list(val) 201 202 named_screen_regions: List[NamedScreenRegion] = [] 203 for region_name, x, y, w, h in multi_target_regions: 204 target_paths: Dict[str, str] = {} 205 subdir = os.path.join(captures_dir, region_name) 206 for target_name in multi_targets.get(region_name, []): 207 target_path = os.path.join(subdir, target_name) 208 root_target_path = os.path.join(captures_dir, target_name) 209 if ( 210 not os.path.exists(f"{target_path}.npy") 211 and os.path.exists(f"{root_target_path}.npy") 212 ): 213 target_path = root_target_path 214 force_load_missing = target_name in self.FORCE_LOAD_MISSING_MULTI_TARGETS.get( 215 region_name, [] 216 ) 217 if ( 218 self.LOAD_ONLY_EXISTING_MULTI_TARGETS 219 and not force_load_missing 220 and not os.path.exists(f"{target_path}.npy") 221 ): 222 continue 223 target_paths[target_name] = target_path 224 region = NamedScreenRegion( 225 region_name, 226 x, 227 y, 228 w, 229 h, 230 parameters=parameters, 231 multi_target_paths=target_paths, 232 ) 233 named_screen_regions.append(region) 234 235 super().__init__(pyboy, parameters, named_screen_regions) 236 237 @staticmethod 238 def _as_gray(current_screen: np.ndarray) -> np.ndarray: 239 if current_screen.ndim == 3: 240 return current_screen[:, :, 0] 241 return current_screen 242 243 def _region_gray(self, current_screen: np.ndarray, region_name: str) -> np.ndarray: 244 return self.capture_named_region(current_screen, region_name)[:, :, 0] 245 246 def _has_text_box_like_region(self, region: np.ndarray) -> bool: 247 dark_ratio = np.mean(region <= self._DARK_PIXEL_THRESHOLD) 248 bright_ratio = np.mean(region >= self._BRIGHT_PIXEL_THRESHOLD) 249 return ( 250 bright_ratio >= self._DIALOGUE_BRIGHT_RATIO_THRESHOLD 251 and dark_ratio >= 0.02 252 ) 253 254 def is_in_dialogue(self, current_screen: np.ndarray) -> bool: 255 dialogue_area = self._region_gray(current_screen, "dialogue_area") 256 return self._has_text_box_like_region(dialogue_area) 257 258 def is_in_menu(self, current_screen: np.ndarray) -> bool: 259 screen = self._as_gray(current_screen) 260 top_area = screen[:96, :] 261 bottom_dialogue = self._region_gray(current_screen, "dialogue_area") 262 top_dark_ratio = np.mean(top_area <= self._DARK_PIXEL_THRESHOLD) 263 top_bright_ratio = np.mean(top_area >= self._BRIGHT_PIXEL_THRESHOLD) 264 bottom_dark_ratio = np.mean(bottom_dialogue <= self._DARK_PIXEL_THRESHOLD) 265 if top_bright_ratio >= self._FULL_MENU_BRIGHT_RATIO_THRESHOLD: 266 return True 267 return ( 268 top_dark_ratio >= self._MENU_DARK_RATIO_THRESHOLD 269 and top_bright_ratio >= self._MENU_BRIGHT_RATIO_THRESHOLD 270 and bottom_dark_ratio <= self._BOTTOM_MENU_DARK_RATIO_MAX 271 ) 272 273 def get_agent_state(self, current_screen: np.ndarray) -> AgentState: 274 if self.is_in_menu(current_screen): 275 return AgentState.IN_MENU 276 if self.is_in_dialogue(current_screen): 277 return AgentState.IN_DIALOGUE 278 return AgentState.FREE_ROAM 279 280 def read_memory_byte(self, address: int) -> int: 281 return self._pyboy.memory[address] 282 283 def __repr__(self) -> str: 284 return f"<SurvivalKidsParser(variant={self.VARIANT})>" 285 286 287class SurvivalKids2Parser(SurvivalKidsParser): 288 """Game state parser for Survival Kids 2 (GBC).""" 289 290 VARIANT = "survival_kids_2" 291 LOAD_ONLY_EXISTING_MULTI_TARGETS = True 292 FORCE_LOAD_MISSING_MULTI_TARGETS = { 293 "dialogue_area": [ 294 "got_the_log", 295 "log_found_dialogue", 296 "water_available_dialogue", 297 ], 298 "screen": ["knife_equipped"], 299 "game_viewport": [ 300 "grass_cut", 301 "grass_cut_before_pickup_log", 302 "grass_cut_before_sharp_stone", 303 "helmet_found", 304 ], 305 } 306 MULTI_TARGET_REGIONS = _get_proper_regions( 307 override_regions=[ 308 ("dialogue_area", 0, 104, 160, 40), 309 ("merge_confirm_area", 0, 104, 160, 40), 310 ], 311 base_regions=SurvivalKidsParser.MULTI_TARGET_REGIONS, 312 ) 313 MULTI_TARGETS: Dict[str, List[str]] = _merge_multi_targets( 314 SurvivalKidsParser.MULTI_TARGETS, 315 { 316 "dialogue_area": [ 317 "fruit_found_dialogue", 318 "fruit_taken_dialogue", 319 "got_the_log", 320 "log_found_dialogue", 321 "pry_stone_dialogue", 322 "tree_bark_pickup_dialogue", 323 "water_available_dialogue", 324 ], 325 "game_viewport": [ 326 "fruit_eaten", 327 "grass_cut", 328 "grass_cut_before_pickup_log", 329 "grass_cut_before_push_stone_2", 330 "grass_cut_before_sharp_stone", 331 "helmet_found", 332 "near_pry_stone", 333 "new_path_1_found", 334 "new_path_2_found", 335 "path_after_pried_stone", 336 "path_after_pushed_stone_2", 337 "path_after_blocking_grass_c1", 338 "sharp_stone_found", 339 "stone_pushed_open_2", 340 ], 341 "hunger_area": [ 342 "fruit_eaten", 343 ], 344 "item_action_menu": [ 345 "fruit_action_menu", 346 "fruit_eat_selected", 347 "log_action_menu", 348 "log_select_take", 349 ], 350 "item_action_menu_two_options": [ 351 "fruit_action_menu", 352 "fruit_eat_selected", 353 ], 354 "object_area": [ 355 "bag_icon", 356 ], 357 "equipped_item_area": [ 358 "knife_equipped", 359 ], 360 "screen": [ 361 "afternoon_reference", 362 "club_equipped", 363 "day_reference", 364 "fruit_eaten", 365 "got_the_sharp_stone", 366 "got_the_stone", 367 "got_the_vine", 368 "knife_equipped", 369 "near_push_stone_2", 370 "night_reference", 371 "push_stone_dialogue_2", 372 "select_club_near_pry_stone", 373 ], 374 "menu_area": [ 375 "inventory_open_with_club", 376 "inventory_open_with_club_near_pry_stone", 377 "inventory_open_with_club_near_push_stone_2", 378 "inventory_select_item", 379 ], 380 "inventory_description_area": [ 381 "select_club", 382 "select_log", 383 ], 384 "inventory_item_action_menu": [ 385 "log_action_menu", 386 "log_select_take", 387 ], 388 }, 389 ) 390 391 def __repr__(self) -> str: 392 return f"<SurvivalKids2Parser(variant={self.VARIANT})>"
class
AgentState(enum.Enum):
FREE_ROAM =
<AgentState.FREE_ROAM: 0>
IN_DIALOGUE =
<AgentState.IN_DIALOGUE: 1>
IN_MENU =
<AgentState.IN_MENU: 2>
39class SurvivalKidsParser(StateParser): 40 """Game state parser for Survival Kids 1 (GBC).""" 41 42 VARIANT = "survival_kids_1" 43 LOAD_ONLY_EXISTING_MULTI_TARGETS = False 44 FORCE_LOAD_MISSING_MULTI_TARGETS: Dict[str, List[str]] = {} 45 46 MULTI_TARGET_REGIONS: List[Tuple[str, int, int, int, int]] = [ 47 ("screen", 0, 0, 160, 144), 48 ("screen_bottom", 0, 108, 160, 36), 49 ("game_viewport", 0, 0, 160, 128), 50 ("status_bar", 0, 128, 160, 16), 51 ("hp_area", 0, 128, 40, 16), 52 ("hunger_area", 0, 136, 42, 8), 53 ("thirst_area", 42, 136, 42, 8), 54 ("stamina_area", 84, 136, 42, 8), 55 ("equipped_items_area", 20, 128, 24, 8), 56 ("equipped_item_area", 0, 105, 160, 39), 57 ("pack_icon_area", 144, 128, 16, 16), 58 ("bag_icon_area", 64, 32, 48, 48), 59 ("object_area", 64, 32, 48, 48), 60 ("choose_item_area", 0, 20, 160, 80), 61 ("dialogue_area", 8, 112, 144, 28), 62 ("inventory_select_area", 0, 0, 88, 72), 63 ("inventory_description_area", 8, 98, 144, 30), 64 ("item_action_menu", 0, 0, 64, 56), 65 ("item_action_menu_two_options", 0, 0, 64, 40), 66 ("item_action_menu_three_options", 0, 0, 64, 57), 67 ("inventory_item_action_menu", 8, 98, 144, 38), 68 ("item_use_menu_area", 0, 84, 160, 56), 69 ("merge_menu_area", 0, 24, 160, 76), 70 ("item_action_cursor", 6, 8, 12, 8), 71 ("item_action_options", 18, 8, 42, 28), 72 ("menu_area", 0, 0, 160, 144), 73 ("merge_confirm_area", 0, 101, 160, 31), 74 ] 75 76 MULTI_TARGETS: Dict[str, List[str]] = { 77 "screen": [ 78 "after_drinking_water", 79 "after_filling_water", 80 "day_reference", 81 "entered_shelter", 82 "found_river", 83 "got_the_brdfeather", 84 "got_the_stick", 85 "got_the_tree_bark", 86 "got_the_water", 87 "inventory_select_item", 88 "night_reference", 89 "water_menu_open", 90 ], 91 "menu_area": [ 92 "inventory_after_fire_lit", 93 "inventory_open", 94 ], 95 "merge_menu_area": [ 96 "merge_menu", 97 "select_stick", 98 ], 99 "merge_confirm_area": [ 100 "merge_confirm", 101 ], 102 "item_action_menu_two_options": [ 103 "select_take", 104 "take_leave_menu", 105 "feather_take_leave_menu", 106 ], 107 "dialogue_area": [ 108 "pickup_item_dialogue", 109 "canteen_pickup_dialogue", 110 "cooked_meat_eaten_dialogue", 111 "cooked_meat_stored", 112 "got_the_clam", 113 "meat_cooked_dialogue", 114 ], 115 "bag_icon_area": [ 116 "bag_icon", 117 ], 118 "equipped_item_area": [ 119 "knife_equipped", 120 "use_kindling", 121 ], 122 "choose_item_area": [ 123 "canteen_chosen", 124 "knife_chosen", 125 "select_kindling", 126 ], 127 "inventory_select_area": [ 128 "kindling_merged", 129 ], 130 "inventory_description_area": [ 131 "select_meat", 132 ], 133 "item_use_menu_area": [ 134 "canteen_action_menu", 135 "canteen_drink_selected", 136 "canteen_use_selected", 137 ], 138 "item_action_menu": [ 139 "canteen_take_leave_menu", 140 "cooked_meat_action_menu", 141 "cooked_meat_eat_selected", 142 "meat_take_eat_leave_menu", 143 "select_take", 144 ], 145 "inventory_item_action_menu": [ 146 "burn_confirm", 147 "meat_burn_selected", 148 "select_drop", 149 ], 150 "game_viewport": [ 151 "animal_killed", 152 "chapter1_path_cleared", 153 "fire_lit", 154 "grass_cut", 155 "in_the_shelter", 156 "path_after_blocking_grass", 157 ], 158 } 159 160 _DARK_PIXEL_THRESHOLD = 24 161 _BRIGHT_PIXEL_THRESHOLD = 220 162 _DIALOGUE_BRIGHT_RATIO_THRESHOLD = 0.55 163 _MENU_DARK_RATIO_THRESHOLD = 0.08 164 _MENU_BRIGHT_RATIO_THRESHOLD = 0.18 165 _FULL_MENU_BRIGHT_RATIO_THRESHOLD = 0.75 166 _BOTTOM_MENU_DARK_RATIO_MAX = 0.025 167 168 def __init__( 169 self, 170 pyboy, 171 parameters: dict, 172 additional_multi_target_regions: Optional[ 173 List[Tuple[str, int, int, int, int]] 174 ] = None, 175 override_multi_targets: Optional[Dict[str, List[str]]] = None, 176 ): 177 verify_parameters(parameters) 178 additional_multi_target_regions = additional_multi_target_regions or [] 179 override_multi_targets = override_multi_targets or {} 180 if f"{self.VARIANT}_rom_data_path" not in parameters: 181 log_error( 182 f"ROM data path not found for variant: {self.VARIANT}. " 183 f"Add {self.VARIANT}_rom_data_path to the config files.", 184 parameters, 185 ) 186 self.rom_data_path = parameters[f"{self.VARIANT}_rom_data_path"] 187 captures_dir = os.path.join(self.rom_data_path, "captures") 188 189 multi_target_regions = _get_proper_regions( 190 override_regions=additional_multi_target_regions, 191 base_regions=self.MULTI_TARGET_REGIONS, 192 ) 193 194 multi_targets: Dict[str, List[str]] = {} 195 for key, val in self.MULTI_TARGETS.items(): 196 multi_targets[key] = list(val) 197 for key, val in override_multi_targets.items(): 198 if key in multi_targets: 199 multi_targets[key].extend(val) 200 else: 201 multi_targets[key] = list(val) 202 203 named_screen_regions: List[NamedScreenRegion] = [] 204 for region_name, x, y, w, h in multi_target_regions: 205 target_paths: Dict[str, str] = {} 206 subdir = os.path.join(captures_dir, region_name) 207 for target_name in multi_targets.get(region_name, []): 208 target_path = os.path.join(subdir, target_name) 209 root_target_path = os.path.join(captures_dir, target_name) 210 if ( 211 not os.path.exists(f"{target_path}.npy") 212 and os.path.exists(f"{root_target_path}.npy") 213 ): 214 target_path = root_target_path 215 force_load_missing = target_name in self.FORCE_LOAD_MISSING_MULTI_TARGETS.get( 216 region_name, [] 217 ) 218 if ( 219 self.LOAD_ONLY_EXISTING_MULTI_TARGETS 220 and not force_load_missing 221 and not os.path.exists(f"{target_path}.npy") 222 ): 223 continue 224 target_paths[target_name] = target_path 225 region = NamedScreenRegion( 226 region_name, 227 x, 228 y, 229 w, 230 h, 231 parameters=parameters, 232 multi_target_paths=target_paths, 233 ) 234 named_screen_regions.append(region) 235 236 super().__init__(pyboy, parameters, named_screen_regions) 237 238 @staticmethod 239 def _as_gray(current_screen: np.ndarray) -> np.ndarray: 240 if current_screen.ndim == 3: 241 return current_screen[:, :, 0] 242 return current_screen 243 244 def _region_gray(self, current_screen: np.ndarray, region_name: str) -> np.ndarray: 245 return self.capture_named_region(current_screen, region_name)[:, :, 0] 246 247 def _has_text_box_like_region(self, region: np.ndarray) -> bool: 248 dark_ratio = np.mean(region <= self._DARK_PIXEL_THRESHOLD) 249 bright_ratio = np.mean(region >= self._BRIGHT_PIXEL_THRESHOLD) 250 return ( 251 bright_ratio >= self._DIALOGUE_BRIGHT_RATIO_THRESHOLD 252 and dark_ratio >= 0.02 253 ) 254 255 def is_in_dialogue(self, current_screen: np.ndarray) -> bool: 256 dialogue_area = self._region_gray(current_screen, "dialogue_area") 257 return self._has_text_box_like_region(dialogue_area) 258 259 def is_in_menu(self, current_screen: np.ndarray) -> bool: 260 screen = self._as_gray(current_screen) 261 top_area = screen[:96, :] 262 bottom_dialogue = self._region_gray(current_screen, "dialogue_area") 263 top_dark_ratio = np.mean(top_area <= self._DARK_PIXEL_THRESHOLD) 264 top_bright_ratio = np.mean(top_area >= self._BRIGHT_PIXEL_THRESHOLD) 265 bottom_dark_ratio = np.mean(bottom_dialogue <= self._DARK_PIXEL_THRESHOLD) 266 if top_bright_ratio >= self._FULL_MENU_BRIGHT_RATIO_THRESHOLD: 267 return True 268 return ( 269 top_dark_ratio >= self._MENU_DARK_RATIO_THRESHOLD 270 and top_bright_ratio >= self._MENU_BRIGHT_RATIO_THRESHOLD 271 and bottom_dark_ratio <= self._BOTTOM_MENU_DARK_RATIO_MAX 272 ) 273 274 def get_agent_state(self, current_screen: np.ndarray) -> AgentState: 275 if self.is_in_menu(current_screen): 276 return AgentState.IN_MENU 277 if self.is_in_dialogue(current_screen): 278 return AgentState.IN_DIALOGUE 279 return AgentState.FREE_ROAM 280 281 def read_memory_byte(self, address: int) -> int: 282 return self._pyboy.memory[address] 283 284 def __repr__(self) -> str: 285 return f"<SurvivalKidsParser(variant={self.VARIANT})>"
Game state parser for Survival Kids 1 (GBC).
SurvivalKidsParser( pyboy, parameters: dict, additional_multi_target_regions: Optional[List[Tuple[str, int, int, int, int]]] = None, override_multi_targets: Optional[Dict[str, List[str]]] = None)
168 def __init__( 169 self, 170 pyboy, 171 parameters: dict, 172 additional_multi_target_regions: Optional[ 173 List[Tuple[str, int, int, int, int]] 174 ] = None, 175 override_multi_targets: Optional[Dict[str, List[str]]] = None, 176 ): 177 verify_parameters(parameters) 178 additional_multi_target_regions = additional_multi_target_regions or [] 179 override_multi_targets = override_multi_targets or {} 180 if f"{self.VARIANT}_rom_data_path" not in parameters: 181 log_error( 182 f"ROM data path not found for variant: {self.VARIANT}. " 183 f"Add {self.VARIANT}_rom_data_path to the config files.", 184 parameters, 185 ) 186 self.rom_data_path = parameters[f"{self.VARIANT}_rom_data_path"] 187 captures_dir = os.path.join(self.rom_data_path, "captures") 188 189 multi_target_regions = _get_proper_regions( 190 override_regions=additional_multi_target_regions, 191 base_regions=self.MULTI_TARGET_REGIONS, 192 ) 193 194 multi_targets: Dict[str, List[str]] = {} 195 for key, val in self.MULTI_TARGETS.items(): 196 multi_targets[key] = list(val) 197 for key, val in override_multi_targets.items(): 198 if key in multi_targets: 199 multi_targets[key].extend(val) 200 else: 201 multi_targets[key] = list(val) 202 203 named_screen_regions: List[NamedScreenRegion] = [] 204 for region_name, x, y, w, h in multi_target_regions: 205 target_paths: Dict[str, str] = {} 206 subdir = os.path.join(captures_dir, region_name) 207 for target_name in multi_targets.get(region_name, []): 208 target_path = os.path.join(subdir, target_name) 209 root_target_path = os.path.join(captures_dir, target_name) 210 if ( 211 not os.path.exists(f"{target_path}.npy") 212 and os.path.exists(f"{root_target_path}.npy") 213 ): 214 target_path = root_target_path 215 force_load_missing = target_name in self.FORCE_LOAD_MISSING_MULTI_TARGETS.get( 216 region_name, [] 217 ) 218 if ( 219 self.LOAD_ONLY_EXISTING_MULTI_TARGETS 220 and not force_load_missing 221 and not os.path.exists(f"{target_path}.npy") 222 ): 223 continue 224 target_paths[target_name] = target_path 225 region = NamedScreenRegion( 226 region_name, 227 x, 228 y, 229 w, 230 h, 231 parameters=parameters, 232 multi_target_paths=target_paths, 233 ) 234 named_screen_regions.append(region) 235 236 super().__init__(pyboy, parameters, named_screen_regions)
Initializes the StateParser. Child implementations should call super().__init__() after running their code. All children must create a self.rom_data_path variable
Arguments:
- pyboy: An instance of the PyBoy emulator.
- parameters: A dictionary of parameters for configuration.
- named_screen_regions (Optional[list[NamedScreenRegion]]): A list of NamedScreenRegion objects for easy access to specific screen regions.
MULTI_TARGET_REGIONS: List[Tuple[str, int, int, int, int]] =
[('screen', 0, 0, 160, 144), ('screen_bottom', 0, 108, 160, 36), ('game_viewport', 0, 0, 160, 128), ('status_bar', 0, 128, 160, 16), ('hp_area', 0, 128, 40, 16), ('hunger_area', 0, 136, 42, 8), ('thirst_area', 42, 136, 42, 8), ('stamina_area', 84, 136, 42, 8), ('equipped_items_area', 20, 128, 24, 8), ('equipped_item_area', 0, 105, 160, 39), ('pack_icon_area', 144, 128, 16, 16), ('bag_icon_area', 64, 32, 48, 48), ('object_area', 64, 32, 48, 48), ('choose_item_area', 0, 20, 160, 80), ('dialogue_area', 8, 112, 144, 28), ('inventory_select_area', 0, 0, 88, 72), ('inventory_description_area', 8, 98, 144, 30), ('item_action_menu', 0, 0, 64, 56), ('item_action_menu_two_options', 0, 0, 64, 40), ('item_action_menu_three_options', 0, 0, 64, 57), ('inventory_item_action_menu', 8, 98, 144, 38), ('item_use_menu_area', 0, 84, 160, 56), ('merge_menu_area', 0, 24, 160, 76), ('item_action_cursor', 6, 8, 12, 8), ('item_action_options', 18, 8, 42, 28), ('menu_area', 0, 0, 160, 144), ('merge_confirm_area', 0, 101, 160, 31)]
MULTI_TARGETS: Dict[str, List[str]] =
{'screen': ['after_drinking_water', 'after_filling_water', 'day_reference', 'entered_shelter', 'found_river', 'got_the_brdfeather', 'got_the_stick', 'got_the_tree_bark', 'got_the_water', 'inventory_select_item', 'night_reference', 'water_menu_open'], 'menu_area': ['inventory_after_fire_lit', 'inventory_open'], 'merge_menu_area': ['merge_menu', 'select_stick'], 'merge_confirm_area': ['merge_confirm'], 'item_action_menu_two_options': ['select_take', 'take_leave_menu', 'feather_take_leave_menu'], 'dialogue_area': ['pickup_item_dialogue', 'canteen_pickup_dialogue', 'cooked_meat_eaten_dialogue', 'cooked_meat_stored', 'got_the_clam', 'meat_cooked_dialogue'], 'bag_icon_area': ['bag_icon'], 'equipped_item_area': ['knife_equipped', 'use_kindling'], 'choose_item_area': ['canteen_chosen', 'knife_chosen', 'select_kindling'], 'inventory_select_area': ['kindling_merged'], 'inventory_description_area': ['select_meat'], 'item_use_menu_area': ['canteen_action_menu', 'canteen_drink_selected', 'canteen_use_selected'], 'item_action_menu': ['canteen_take_leave_menu', 'cooked_meat_action_menu', 'cooked_meat_eat_selected', 'meat_take_eat_leave_menu', 'select_take'], 'inventory_item_action_menu': ['burn_confirm', 'meat_burn_selected', 'select_drop'], 'game_viewport': ['animal_killed', 'chapter1_path_cleared', 'fire_lit', 'grass_cut', 'in_the_shelter', 'path_after_blocking_grass']}
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
288class SurvivalKids2Parser(SurvivalKidsParser): 289 """Game state parser for Survival Kids 2 (GBC).""" 290 291 VARIANT = "survival_kids_2" 292 LOAD_ONLY_EXISTING_MULTI_TARGETS = True 293 FORCE_LOAD_MISSING_MULTI_TARGETS = { 294 "dialogue_area": [ 295 "got_the_log", 296 "log_found_dialogue", 297 "water_available_dialogue", 298 ], 299 "screen": ["knife_equipped"], 300 "game_viewport": [ 301 "grass_cut", 302 "grass_cut_before_pickup_log", 303 "grass_cut_before_sharp_stone", 304 "helmet_found", 305 ], 306 } 307 MULTI_TARGET_REGIONS = _get_proper_regions( 308 override_regions=[ 309 ("dialogue_area", 0, 104, 160, 40), 310 ("merge_confirm_area", 0, 104, 160, 40), 311 ], 312 base_regions=SurvivalKidsParser.MULTI_TARGET_REGIONS, 313 ) 314 MULTI_TARGETS: Dict[str, List[str]] = _merge_multi_targets( 315 SurvivalKidsParser.MULTI_TARGETS, 316 { 317 "dialogue_area": [ 318 "fruit_found_dialogue", 319 "fruit_taken_dialogue", 320 "got_the_log", 321 "log_found_dialogue", 322 "pry_stone_dialogue", 323 "tree_bark_pickup_dialogue", 324 "water_available_dialogue", 325 ], 326 "game_viewport": [ 327 "fruit_eaten", 328 "grass_cut", 329 "grass_cut_before_pickup_log", 330 "grass_cut_before_push_stone_2", 331 "grass_cut_before_sharp_stone", 332 "helmet_found", 333 "near_pry_stone", 334 "new_path_1_found", 335 "new_path_2_found", 336 "path_after_pried_stone", 337 "path_after_pushed_stone_2", 338 "path_after_blocking_grass_c1", 339 "sharp_stone_found", 340 "stone_pushed_open_2", 341 ], 342 "hunger_area": [ 343 "fruit_eaten", 344 ], 345 "item_action_menu": [ 346 "fruit_action_menu", 347 "fruit_eat_selected", 348 "log_action_menu", 349 "log_select_take", 350 ], 351 "item_action_menu_two_options": [ 352 "fruit_action_menu", 353 "fruit_eat_selected", 354 ], 355 "object_area": [ 356 "bag_icon", 357 ], 358 "equipped_item_area": [ 359 "knife_equipped", 360 ], 361 "screen": [ 362 "afternoon_reference", 363 "club_equipped", 364 "day_reference", 365 "fruit_eaten", 366 "got_the_sharp_stone", 367 "got_the_stone", 368 "got_the_vine", 369 "knife_equipped", 370 "near_push_stone_2", 371 "night_reference", 372 "push_stone_dialogue_2", 373 "select_club_near_pry_stone", 374 ], 375 "menu_area": [ 376 "inventory_open_with_club", 377 "inventory_open_with_club_near_pry_stone", 378 "inventory_open_with_club_near_push_stone_2", 379 "inventory_select_item", 380 ], 381 "inventory_description_area": [ 382 "select_club", 383 "select_log", 384 ], 385 "inventory_item_action_menu": [ 386 "log_action_menu", 387 "log_select_take", 388 ], 389 }, 390 ) 391 392 def __repr__(self) -> str: 393 return f"<SurvivalKids2Parser(variant={self.VARIANT})>"
Game state parser for Survival Kids 2 (GBC).
FORCE_LOAD_MISSING_MULTI_TARGETS =
{'dialogue_area': ['got_the_log', 'log_found_dialogue', 'water_available_dialogue'], 'screen': ['knife_equipped'], 'game_viewport': ['grass_cut', 'grass_cut_before_pickup_log', 'grass_cut_before_sharp_stone', 'helmet_found']}
MULTI_TARGET_REGIONS =
[('dialogue_area', 0, 104, 160, 40), ('merge_confirm_area', 0, 104, 160, 40), ('screen', 0, 0, 160, 144), ('screen_bottom', 0, 108, 160, 36), ('game_viewport', 0, 0, 160, 128), ('status_bar', 0, 128, 160, 16), ('hp_area', 0, 128, 40, 16), ('hunger_area', 0, 136, 42, 8), ('thirst_area', 42, 136, 42, 8), ('stamina_area', 84, 136, 42, 8), ('equipped_items_area', 20, 128, 24, 8), ('equipped_item_area', 0, 105, 160, 39), ('pack_icon_area', 144, 128, 16, 16), ('bag_icon_area', 64, 32, 48, 48), ('object_area', 64, 32, 48, 48), ('choose_item_area', 0, 20, 160, 80), ('inventory_select_area', 0, 0, 88, 72), ('inventory_description_area', 8, 98, 144, 30), ('item_action_menu', 0, 0, 64, 56), ('item_action_menu_two_options', 0, 0, 64, 40), ('item_action_menu_three_options', 0, 0, 64, 57), ('inventory_item_action_menu', 8, 98, 144, 38), ('item_use_menu_area', 0, 84, 160, 56), ('merge_menu_area', 0, 24, 160, 76), ('item_action_cursor', 6, 8, 12, 8), ('item_action_options', 18, 8, 42, 28), ('menu_area', 0, 0, 160, 144)]
MULTI_TARGETS: Dict[str, List[str]] =
{'screen': ['after_drinking_water', 'after_filling_water', 'day_reference', 'entered_shelter', 'found_river', 'got_the_brdfeather', 'got_the_stick', 'got_the_tree_bark', 'got_the_water', 'inventory_select_item', 'night_reference', 'water_menu_open', 'afternoon_reference', 'club_equipped', 'fruit_eaten', 'got_the_sharp_stone', 'got_the_stone', 'got_the_vine', 'knife_equipped', 'near_push_stone_2', 'push_stone_dialogue_2', 'select_club_near_pry_stone'], 'menu_area': ['inventory_after_fire_lit', 'inventory_open', 'inventory_open_with_club', 'inventory_open_with_club_near_pry_stone', 'inventory_open_with_club_near_push_stone_2', 'inventory_select_item'], 'merge_menu_area': ['merge_menu', 'select_stick'], 'merge_confirm_area': ['merge_confirm'], 'item_action_menu_two_options': ['select_take', 'take_leave_menu', 'feather_take_leave_menu', 'fruit_action_menu', 'fruit_eat_selected'], 'dialogue_area': ['pickup_item_dialogue', 'canteen_pickup_dialogue', 'cooked_meat_eaten_dialogue', 'cooked_meat_stored', 'got_the_clam', 'meat_cooked_dialogue', 'fruit_found_dialogue', 'fruit_taken_dialogue', 'got_the_log', 'log_found_dialogue', 'pry_stone_dialogue', 'tree_bark_pickup_dialogue', 'water_available_dialogue'], 'bag_icon_area': ['bag_icon'], 'equipped_item_area': ['knife_equipped', 'use_kindling'], 'choose_item_area': ['canteen_chosen', 'knife_chosen', 'select_kindling'], 'inventory_select_area': ['kindling_merged'], 'inventory_description_area': ['select_meat', 'select_club', 'select_log'], 'item_use_menu_area': ['canteen_action_menu', 'canteen_drink_selected', 'canteen_use_selected'], 'item_action_menu': ['canteen_take_leave_menu', 'cooked_meat_action_menu', 'cooked_meat_eat_selected', 'meat_take_eat_leave_menu', 'select_take', 'fruit_action_menu', 'fruit_eat_selected', 'log_action_menu', 'log_select_take'], 'inventory_item_action_menu': ['burn_confirm', 'meat_burn_selected', 'select_drop', 'log_action_menu', 'log_select_take'], 'game_viewport': ['animal_killed', 'chapter1_path_cleared', 'fire_lit', 'grass_cut', 'in_the_shelter', 'path_after_blocking_grass', 'fruit_eaten', 'grass_cut_before_pickup_log', 'grass_cut_before_push_stone_2', 'grass_cut_before_sharp_stone', 'helmet_found', 'near_pry_stone', 'new_path_1_found', 'new_path_2_found', 'path_after_pried_stone', 'path_after_pushed_stone_2', 'path_after_blocking_grass_c1', 'sharp_stone_found', 'stone_pushed_open_2'], 'hunger_area': ['fruit_eaten'], 'object_area': ['bag_icon']}
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