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
class CoreLegendOfZeldaMetrics(gameboy_worlds.emulation.tracker.MetricGroup):
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
NAME = 'legend_of_zelda_core'

Name of the MetricGroup.

The StateParser which implements the minimum required functionality for this MetricGroup to work.

def reset(self, first: bool = False):
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.
def close(self):
28    def close(self):
29        self.reset()
30        return

Called when environment closes. Good for computing summary stats.

Step will not be called after this.

def step( self, current_frame: numpy.ndarray, recent_frames: Optional[numpy.ndarray]):
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.
def report(self) -> dict:
36    def report(self) -> dict:
37        return {"agent_state": self.current_state}

Return metrics as dictionary for instantaneous variable tracking.

Returns

Dictionary of metrics

def report_final(self) -> dict:
39    def report_final(self) -> dict:
40        return {}

Return metrics as dictionary for logging. Called at end of environment (before close). Will never be called before self.close.

Returns

Dictionary of metrics

class LegendOfZeldaOCRMetric(gameboy_worlds.emulation.tracker.OCRegionMetric):
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.

def reset(self, first=False):
46    def reset(self, first=False):
47        super().reset(first)

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.

def start(self):
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.

def can_read_kind(self, current_frame: numpy.ndarray, kind: str) -> bool:
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.