gameboy_worlds.emulation.bomberman.base_metrics

  1from typing import Optional
  2
  3import numpy as np
  4
  5from gameboy_worlds.emulation.tracker import MetricGroup, OCRegionMetric
  6from gameboy_worlds.emulation.bomberman.parsers import (
  7    BombermanMaxParser,
  8    BombermanPocketParser,
  9    BombermanQuestParser,
 10)
 11
 12
 13class BombermanCoreMetrics(MetricGroup):
 14    METRIC_METHODS = {}
 15
 16    def reset(self, first=False):
 17        for metric_name in self.METRIC_METHODS:
 18            setattr(self, metric_name, False)
 19
 20    def close(self):
 21        self.reset()
 22
 23    def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]):
 24        for metric_name, parser_method in self.METRIC_METHODS.items():
 25            if callable(parser_method):
 26                value = parser_method(self.state_parser, current_frame)
 27            else:
 28                value = getattr(self.state_parser, parser_method)(current_frame)
 29            setattr(self, metric_name, value)
 30
 31    def report(self) -> dict:
 32        return {metric_name: getattr(self, metric_name) for metric_name in self.METRIC_METHODS}
 33
 34    def report_final(self) -> dict:
 35        return {}
 36
 37
 38class BombermanMaxCoreMetrics(BombermanCoreMetrics):
 39    NAME = "bomberman_max_core"
 40    REQUIRED_PARSER = BombermanMaxParser
 41    METRIC_METHODS = {
 42        "is_in_menu": "is_in_menu",
 43        "is_in_battle": "is_in_battle",
 44        "is_in_charabom_select": "is_in_charabom_select",
 45        "is_stage_briefing": "is_stage_briefing_active",
 46    }
 47
 48
 49class BombermanPocketCoreMetrics(BombermanCoreMetrics):
 50    NAME = "bomberman_pocket_core"
 51    REQUIRED_PARSER = BombermanPocketParser
 52    METRIC_METHODS = {
 53        "is_in_menu": "is_in_menu",
 54        "is_paused": "is_paused",
 55    }
 56
 57
 58class BombermanQuestCoreMetrics(BombermanCoreMetrics):
 59    NAME = "bomberman_quest_core"
 60    REQUIRED_PARSER = BombermanQuestParser
 61    METRIC_METHODS = {
 62        "is_in_menu": "is_in_menu",
 63        "is_in_dialogue": "is_in_dialogue",
 64        "is_in_npc_dialogue": "is_in_npc_dialogue",
 65        "is_reading_sign": "is_reading_sign",
 66        "is_in_battle": "is_in_battle",
 67    }
 68
 69
 70class BombermanQuestOCRMetric(OCRegionMetric):
 71    REQUIRED_PARSER = BombermanQuestParser
 72
 73    @property
 74    def parser(self) -> BombermanQuestParser:
 75        return self.state_parser  # type: ignore
 76
 77    def start(self):
 78        self.kinds = {"dialogue": "dialogue_box"}
 79        super().start()
 80
 81    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
 82        if kind == "dialogue":
 83            return self.parser.is_in_dialogue(current_frame)
 84        return False
 85
 86
 87class BombermanPocketOCRMetric(OCRegionMetric):
 88    REQUIRED_PARSER = BombermanPocketParser
 89
 90    @property
 91    def parser(self) -> BombermanPocketParser:
 92        return self.state_parser  # type: ignore
 93
 94    def start(self):
 95        self.kinds = {"area_intro": "area_intro_block"}
 96        super().start()
 97
 98    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
 99        if kind == "area_intro":
100            return self.parser.is_in_any_area_intro(current_frame)
101        return False
102
103
104class BombermanMaxOCRMetric(OCRegionMetric):
105    REQUIRED_PARSER = BombermanMaxParser
106
107    @property
108    def parser(self) -> BombermanMaxParser:
109        return self.state_parser  # type: ignore
110
111    def start(self):
112        self.kinds = {"stage_briefing": "stage_briefing_box"}
113        super().start()
114
115    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
116        if kind == "stage_briefing":
117            return self.parser.is_stage_briefing_active(current_frame)
118        return False
class BombermanCoreMetrics(gameboy_worlds.emulation.tracker.MetricGroup):
14class BombermanCoreMetrics(MetricGroup):
15    METRIC_METHODS = {}
16
17    def reset(self, first=False):
18        for metric_name in self.METRIC_METHODS:
19            setattr(self, metric_name, False)
20
21    def close(self):
22        self.reset()
23
24    def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]):
25        for metric_name, parser_method in self.METRIC_METHODS.items():
26            if callable(parser_method):
27                value = parser_method(self.state_parser, current_frame)
28            else:
29                value = getattr(self.state_parser, parser_method)(current_frame)
30            setattr(self, metric_name, value)
31
32    def report(self) -> dict:
33        return {metric_name: getattr(self, metric_name) for metric_name in self.METRIC_METHODS}
34
35    def report_final(self) -> dict:
36        return {}

Abstract Base class for organizing related metrics.

Documentation Guidlines:

Every subchild should document the following in their class docstrings:

  • Reports (List of keys that are present in the return dict of report)
  • Final Reports (List of keys that are present in the return dict of report_final)
METRIC_METHODS = {}
def reset(self, first=False):
17    def reset(self, first=False):
18        for metric_name in self.METRIC_METHODS:
19            setattr(self, metric_name, False)

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):
21    def close(self):
22        self.reset()

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]):
24    def step(self, current_frame: np.ndarray, recent_frames: Optional[np.ndarray]):
25        for metric_name, parser_method in self.METRIC_METHODS.items():
26            if callable(parser_method):
27                value = parser_method(self.state_parser, current_frame)
28            else:
29                value = getattr(self.state_parser, parser_method)(current_frame)
30            setattr(self, metric_name, value)

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:
32    def report(self) -> dict:
33        return {metric_name: getattr(self, metric_name) for metric_name in self.METRIC_METHODS}

Return metrics as dictionary for instantaneous variable tracking.

Returns

Dictionary of metrics

def report_final(self) -> dict:
35    def report_final(self) -> dict:
36        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 BombermanMaxCoreMetrics(BombermanCoreMetrics):
39class BombermanMaxCoreMetrics(BombermanCoreMetrics):
40    NAME = "bomberman_max_core"
41    REQUIRED_PARSER = BombermanMaxParser
42    METRIC_METHODS = {
43        "is_in_menu": "is_in_menu",
44        "is_in_battle": "is_in_battle",
45        "is_in_charabom_select": "is_in_charabom_select",
46        "is_stage_briefing": "is_stage_briefing_active",
47    }

Abstract Base class for organizing related metrics.

Documentation Guidlines:

Every subchild should document the following in their class docstrings:

  • Reports (List of keys that are present in the return dict of report)
  • Final Reports (List of keys that are present in the return dict of report_final)
NAME = 'bomberman_max_core'

Name of the MetricGroup.

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

METRIC_METHODS = {'is_in_menu': 'is_in_menu', 'is_in_battle': 'is_in_battle', 'is_in_charabom_select': 'is_in_charabom_select', 'is_stage_briefing': 'is_stage_briefing_active'}
class BombermanPocketCoreMetrics(BombermanCoreMetrics):
50class BombermanPocketCoreMetrics(BombermanCoreMetrics):
51    NAME = "bomberman_pocket_core"
52    REQUIRED_PARSER = BombermanPocketParser
53    METRIC_METHODS = {
54        "is_in_menu": "is_in_menu",
55        "is_paused": "is_paused",
56    }

Abstract Base class for organizing related metrics.

Documentation Guidlines:

Every subchild should document the following in their class docstrings:

  • Reports (List of keys that are present in the return dict of report)
  • Final Reports (List of keys that are present in the return dict of report_final)
NAME = 'bomberman_pocket_core'

Name of the MetricGroup.

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

METRIC_METHODS = {'is_in_menu': 'is_in_menu', 'is_paused': 'is_paused'}
class BombermanQuestCoreMetrics(BombermanCoreMetrics):
59class BombermanQuestCoreMetrics(BombermanCoreMetrics):
60    NAME = "bomberman_quest_core"
61    REQUIRED_PARSER = BombermanQuestParser
62    METRIC_METHODS = {
63        "is_in_menu": "is_in_menu",
64        "is_in_dialogue": "is_in_dialogue",
65        "is_in_npc_dialogue": "is_in_npc_dialogue",
66        "is_reading_sign": "is_reading_sign",
67        "is_in_battle": "is_in_battle",
68    }

Abstract Base class for organizing related metrics.

Documentation Guidlines:

Every subchild should document the following in their class docstrings:

  • Reports (List of keys that are present in the return dict of report)
  • Final Reports (List of keys that are present in the return dict of report_final)
NAME = 'bomberman_quest_core'

Name of the MetricGroup.

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

METRIC_METHODS = {'is_in_menu': 'is_in_menu', 'is_in_dialogue': 'is_in_dialogue', 'is_in_npc_dialogue': 'is_in_npc_dialogue', 'is_reading_sign': 'is_reading_sign', 'is_in_battle': 'is_in_battle'}
class BombermanQuestOCRMetric(gameboy_worlds.emulation.tracker.OCRegionMetric):
71class BombermanQuestOCRMetric(OCRegionMetric):
72    REQUIRED_PARSER = BombermanQuestParser
73
74    @property
75    def parser(self) -> BombermanQuestParser:
76        return self.state_parser  # type: ignore
77
78    def start(self):
79        self.kinds = {"dialogue": "dialogue_box"}
80        super().start()
81
82    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
83        if kind == "dialogue":
84            return self.parser.is_in_dialogue(current_frame)
85        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.

74    @property
75    def parser(self) -> BombermanQuestParser:
76        return self.state_parser  # type: ignore
def start(self):
78    def start(self):
79        self.kinds = {"dialogue": "dialogue_box"}
80        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:
82    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
83        if kind == "dialogue":
84            return self.parser.is_in_dialogue(current_frame)
85        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.
class BombermanPocketOCRMetric(gameboy_worlds.emulation.tracker.OCRegionMetric):
 88class BombermanPocketOCRMetric(OCRegionMetric):
 89    REQUIRED_PARSER = BombermanPocketParser
 90
 91    @property
 92    def parser(self) -> BombermanPocketParser:
 93        return self.state_parser  # type: ignore
 94
 95    def start(self):
 96        self.kinds = {"area_intro": "area_intro_block"}
 97        super().start()
 98
 99    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
100        if kind == "area_intro":
101            return self.parser.is_in_any_area_intro(current_frame)
102        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.

91    @property
92    def parser(self) -> BombermanPocketParser:
93        return self.state_parser  # type: ignore
def start(self):
95    def start(self):
96        self.kinds = {"area_intro": "area_intro_block"}
97        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:
 99    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
100        if kind == "area_intro":
101            return self.parser.is_in_any_area_intro(current_frame)
102        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.
class BombermanMaxOCRMetric(gameboy_worlds.emulation.tracker.OCRegionMetric):
105class BombermanMaxOCRMetric(OCRegionMetric):
106    REQUIRED_PARSER = BombermanMaxParser
107
108    @property
109    def parser(self) -> BombermanMaxParser:
110        return self.state_parser  # type: ignore
111
112    def start(self):
113        self.kinds = {"stage_briefing": "stage_briefing_box"}
114        super().start()
115
116    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
117        if kind == "stage_briefing":
118            return self.parser.is_stage_briefing_active(current_frame)
119        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.

108    @property
109    def parser(self) -> BombermanMaxParser:
110        return self.state_parser  # type: ignore
def start(self):
112    def start(self):
113        self.kinds = {"stage_briefing": "stage_briefing_box"}
114        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:
116    def can_read_kind(self, current_frame: np.ndarray, kind: str) -> bool:
117        if kind == "stage_briefing":
118            return self.parser.is_stage_briefing_active(current_frame)
119        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.