gameboy_worlds.emulation.legend_of_zelda.base_metrics
1from typing import Optional 2 3import numpy as np 4 5from gameboy_worlds.emulation.legend_of_zelda.parsers import BaseLegendOfZeldaParser 6from gameboy_worlds.emulation.tracker import MetricGroup, OCRegionMetric 7 8 9class CoreLegendOfZeldaMetrics(MetricGroup): 10 """ 11 Zelda-specific core metrics. 12 13 Reports: 14 - agent_state: Current parser-derived agent state. 15 16 Final Reports: 17 - None 18 """ 19 20 NAME = "legend_of_zelda_core" 21 REQUIRED_PARSER = BaseLegendOfZeldaParser 22 23 def reset(self, first: bool = False): 24 self.current_state = "in_dialogue" 25 self._previous_state = self.current_state 26 27 def close(self): 28 self.reset() 29 return 30 31 def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]): 32 self._previous_state = self.current_state 33 self.current_state = self.state_parser.get_agent_state(current_frame) 34 35 def report(self) -> dict: 36 return {"agent_state": self.current_state} 37 38 def report_final(self) -> dict: 39 return {} 40 41 42class LegendOfZeldaOCRMetric(OCRegionMetric): 43 REQUIRED_PARSER = BaseLegendOfZeldaParser 44 45 def reset(self, first=False): 46 super().reset(first) 47 48 def start(self): 49 self.kinds = { 50 "dialogue_top": "dialogue_top_ocr", 51 "dialogue_bottom": "dialogue_bottom_ocr", 52 } 53 super().start() 54 55 def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool: 56 if kind in self.kinds: 57 return ( 58 self.state_parser.named_region_matches_target( 59 current_frame, kind 60 ) 61 and self.state_parser.get_agent_state(current_frame) == "in_dialogue" 62 ) 63 return False
10class CoreLegendOfZeldaMetrics(MetricGroup): 11 """ 12 Zelda-specific core metrics. 13 14 Reports: 15 - agent_state: Current parser-derived agent state. 16 17 Final Reports: 18 - None 19 """ 20 21 NAME = "legend_of_zelda_core" 22 REQUIRED_PARSER = BaseLegendOfZeldaParser 23 24 def reset(self, first: bool = False): 25 self.current_state = "in_dialogue" 26 self._previous_state = self.current_state 27 28 def close(self): 29 self.reset() 30 return 31 32 def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]): 33 self._previous_state = self.current_state 34 self.current_state = self.state_parser.get_agent_state(current_frame) 35 36 def report(self) -> dict: 37 return {"agent_state": self.current_state} 38 39 def report_final(self) -> dict: 40 return {}
Zelda-specific core metrics.
Reports:
- agent_state: Current parser-derived agent state.
Final Reports:
- None
The StateParser which implements the minimum required functionality for this MetricGroup to work.
24 def reset(self, first: bool = False): 25 self.current_state = "in_dialogue" 26 self._previous_state = self.current_state
Called when environment resets.
Arguments:
- first (bool): Whether this is the first reset of the environment. If True, might need to aggregate metrics into running final totals.
Called when environment closes. Good for computing summary stats.
Step will not be called after this.
32 def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]): 33 self._previous_state = self.current_state 34 self.current_state = self.state_parser.get_agent_state(current_frame)
Called each environment step to update metrics.
Arguments:
- current_frame (np.ndarray): The current frame rendered by the emulator.
- recent_frames (Optional[np.ndarray]): The stack of frames that were rendered during the last action. Shape is [n_frames, height, width, channels]. Can be None if rendering is disabled.
Return metrics as dictionary for instantaneous variable tracking.
Returns
Dictionary of metrics
43class LegendOfZeldaOCRMetric(OCRegionMetric): 44 REQUIRED_PARSER = BaseLegendOfZeldaParser 45 46 def reset(self, first=False): 47 super().reset(first) 48 49 def start(self): 50 self.kinds = { 51 "dialogue_top": "dialogue_top_ocr", 52 "dialogue_bottom": "dialogue_bottom_ocr", 53 } 54 super().start() 55 56 def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool: 57 if kind in self.kinds: 58 return ( 59 self.state_parser.named_region_matches_target( 60 current_frame, kind 61 ) 62 and self.state_parser.get_agent_state(current_frame) == "in_dialogue" 63 ) 64 return False
Watch particular screen regions and capture subscreens for OCR when possible. Does not actually perform OCR itself, but makes it easy to capture the relevant regions.
Children implementing this must define self.kinds in start() and then call on super().start().
Reports:
ocr_regions: A dictionary mapping kinds to captured regions that had OCR-eligible text detected in them. The keys are kinds of OCR regions, and the values are the stacks of captured screen regions as numpy arrays of shape (num_captures, height, width, channels).step: The current step number. Useful for differentiating when multiple OCR texts were found in the same episode. You can typically safely ignore this.
Final Reports:
ocr_regions: A list of tuples for all steps where OCR was detected. Is in form:List[Tuple[int, Dict[str, np.ndarray]]]where the int is the step number and the Dict maps kinds to a stack of the captured screen region.
The StateParser which implements the minimum required functionality for this MetricGroup to work.
ocr_regions will track a list of the form List[Tuple[int, Dict[str, np.ndarray]]] which is a list of (step_number, {kind: ocr_region}) dictionaries.
49 def start(self): 50 self.kinds = { 51 "dialogue_top": "dialogue_top_ocr", 52 "dialogue_bottom": "dialogue_bottom_ocr", 53 } 54 super().start()
Assumes the child has initialized a dict called self.kinds which tracks the various kinds of OCR that could be done. self.kinds should be in the form: {kind: region_name} where region_name is the name of the region to OCR for that kind. Will track ocr captured region results in form of list of dictionaries where these kinds are keys.
56 def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool: 57 if kind in self.kinds: 58 return ( 59 self.state_parser.named_region_matches_target( 60 current_frame, kind 61 ) 62 and self.state_parser.get_agent_state(current_frame) == "in_dialogue" 63 ) 64 return False
Checks if the frame has text for the given kind.
Arguments:
- frame (np.ndarray): The frame to check.
- kind (str): The kind of text to check for.