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
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)
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.
Called when environment closes. Good for computing summary stats.
Step will not be called after this.
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.
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
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)
The StateParser which implements the minimum required functionality for this MetricGroup to work.
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)
The StateParser which implements the minimum required functionality for this MetricGroup to work.
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)
The StateParser which implements the minimum required functionality for this MetricGroup to work.
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.
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.
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.
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.
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.
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.
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.
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.
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.