gameboy_worlds.interface.bomberman.actions

  1from typing import Any, Dict, List, Type
  2
  3import numpy as np
  4from gymnasium.spaces import Discrete
  5
  6from gameboy_worlds.emulation import LowLevelActions
  7from gameboy_worlds.emulation.bomberman.parsers import (
  8    BombermanMaxParser,
  9    BombermanPocketParser,
 10    BombermanQuestParser,
 11)
 12from gameboy_worlds.emulation.bomberman.trackers import (
 13    BombermanMaxTracker,
 14    BombermanPocketTracker,
 15    BombermanQuestTracker,
 16)
 17from gameboy_worlds.interface.action import HighLevelAction, SingleHighLevelAction
 18
 19HARD_MAX_STEPS = 10
 20
 21MAX_MENU_METRIC = ("bomberman_max_core", "is_in_menu")
 22MAX_BATTLE_METRIC = ("bomberman_max_core", "is_in_battle")
 23POCKET_MENU_METRIC = ("bomberman_pocket_core", "is_in_menu")
 24QUEST_MENU_METRIC = ("bomberman_quest_core", "is_in_menu")
 25QUEST_BATTLE_METRIC = ("bomberman_quest_core", "is_in_battle")
 26
 27
 28def frame_changed(previous: np.ndarray, current: np.ndarray, epsilon: float = 0.01) -> bool:
 29    return np.abs(previous - current).mean() > epsilon
 30
 31
 32class _MoveAction(HighLevelAction):
 33    _DIRECTION_TO_ACTION = {}
 34    _BLOCKING_METRICS: List[tuple[str, str]] = []
 35
 36    def get_action_space(self):
 37        return Discrete(len(self._DIRECTION_TO_ACTION) * HARD_MAX_STEPS)
 38
 39    def space_to_parameters(self, space_action):
 40        directions = list(self._DIRECTION_TO_ACTION)
 41        if space_action < 0 or space_action >= len(directions) * HARD_MAX_STEPS:
 42            return None
 43        direction = directions[space_action // HARD_MAX_STEPS]
 44        steps = (space_action % HARD_MAX_STEPS) + 1
 45        return {"direction": direction, "steps": steps}
 46
 47    def parameters_to_space(self, direction: str, steps: int):
 48        directions = list(self._DIRECTION_TO_ACTION)
 49        if direction not in directions or steps <= 0 or steps > HARD_MAX_STEPS:
 50            return None
 51        return directions.index(direction) * HARD_MAX_STEPS + steps - 1
 52
 53    def is_valid(self, **kwargs):
 54        direction = kwargs.get("direction")
 55        steps = kwargs.get("steps")
 56        if direction is not None and direction not in self._DIRECTION_TO_ACTION:
 57            return False
 58        if steps is not None and (not isinstance(steps, int) or steps <= 0):
 59            return False
 60        return not any(
 61            self._state_tracker.get_episode_metric(metric_key)
 62            for metric_key in self._BLOCKING_METRICS
 63        )
 64
 65    def _execute(self, direction: str, steps: int):
 66        action = self._DIRECTION_TO_ACTION[direction]
 67        transition_state_dicts: List[Dict[str, Any]] = []
 68        n_steps_taken = 0
 69        previous_frame = self._emulator.get_current_frame()
 70        action_success = -1
 71        for _ in range(steps):
 72            frames, done = self._emulator.step(action)
 73            report = self._state_tracker.report()
 74            transition_state_dicts.append(report)
 75            current_frame = frames[-1]
 76            if not frame_changed(previous_frame, current_frame):
 77                action_success = 1 if n_steps_taken > 0 else -1
 78                break
 79            n_steps_taken += 1
 80            if any(
 81                self._state_tracker.get_episode_metric(metric_key)
 82                for metric_key in self._BLOCKING_METRICS
 83            ):
 84                action_success = 2
 85                break
 86            if done:
 87                action_success = 0
 88                break
 89            previous_frame = current_frame
 90        else:
 91            action_success = 0
 92        if transition_state_dicts:
 93            transition_state_dicts[-1]["core"]["action_return"] = {
 94                "n_steps_taken": n_steps_taken
 95            }
 96        return transition_state_dicts, action_success
 97
 98    @staticmethod
 99    def get_action_name(direction: str, steps: int) -> str:
100        return f"Move {direction} {steps}"
101
102
103class _MetricGatedSingleAction(SingleHighLevelAction):
104    _BUTTON = None
105    _REQUIRED_METRICS_FALSE: List[tuple[str, str]] = []
106    _REQUIRED_METRICS_TRUE: List[tuple[str, str]] = []
107
108    def is_valid(self, **kwargs):
109        return all(
110            not self._state_tracker.get_episode_metric(metric_key)
111            for metric_key in self._REQUIRED_METRICS_FALSE
112        ) and all(
113            self._state_tracker.get_episode_metric(metric_key)
114            for metric_key in self._REQUIRED_METRICS_TRUE
115        )
116
117    def _execute(self):
118        previous = self._emulator.get_current_frame()
119        frames, _ = self._emulator.step(self._BUTTON)
120        report = self._state_tracker.report()
121        return [report], 0 if frame_changed(previous, frames[-1]) else -1
122
123
124class BombermanMaxMoveAction(_MoveAction):
125    REQUIRED_STATE_TRACKER = BombermanMaxTracker
126    REQUIRED_STATE_PARSER = BombermanMaxParser
127    _DIRECTION_TO_ACTION = {
128        "up": LowLevelActions.PRESS_ARROW_UP,
129        "down": LowLevelActions.PRESS_ARROW_DOWN,
130        "left": LowLevelActions.PRESS_ARROW_LEFT,
131        "right": LowLevelActions.PRESS_ARROW_RIGHT,
132    }
133    _BLOCKING_METRICS = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
134
135
136class BombermanMaxPlaceBombAction(_MetricGatedSingleAction):
137    REQUIRED_STATE_TRACKER = BombermanMaxTracker
138    REQUIRED_STATE_PARSER = BombermanMaxParser
139    _BUTTON = LowLevelActions.PRESS_BUTTON_A
140    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
141
142    @staticmethod
143    def get_action_name() -> str:
144        return "PlaceBomb"
145
146
147class BombermanMaxKickBombAction(_MetricGatedSingleAction):
148    REQUIRED_STATE_TRACKER = BombermanMaxTracker
149    REQUIRED_STATE_PARSER = BombermanMaxParser
150    _BUTTON = LowLevelActions.PRESS_BUTTON_B
151    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
152
153    @staticmethod
154    def get_action_name() -> str:
155        return "KickBomb"
156
157
158class BombermanMaxOpenMenuAction(_MetricGatedSingleAction):
159    REQUIRED_STATE_TRACKER = BombermanMaxTracker
160    REQUIRED_STATE_PARSER = BombermanMaxParser
161    _BUTTON = LowLevelActions.PRESS_BUTTON_START
162    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
163
164    def _execute(self):
165        self._emulator.step(self._BUTTON)
166        report = self._state_tracker.report()
167        return [report], 0 if self._state_tracker.get_episode_metric(MAX_MENU_METRIC) else -1
168
169    @staticmethod
170    def get_action_name() -> str:
171        return "OpenMenu"
172
173
174class BombermanMaxCloseMenuAction(_MetricGatedSingleAction):
175    REQUIRED_STATE_TRACKER = BombermanMaxTracker
176    REQUIRED_STATE_PARSER = BombermanMaxParser
177    _BUTTON = LowLevelActions.PRESS_BUTTON_START
178    _REQUIRED_METRICS_TRUE = [MAX_MENU_METRIC]
179
180    def _execute(self):
181        self._emulator.step(self._BUTTON)
182        report = self._state_tracker.report()
183        return [report], 0 if not self._state_tracker.get_episode_metric(MAX_MENU_METRIC) else -1
184
185    @staticmethod
186    def get_action_name() -> str:
187        return "CloseMenu"
188
189
190class BombermanMaxNavigateMenuAction(SingleHighLevelAction):
191    REQUIRED_STATE_TRACKER = BombermanMaxTracker
192    REQUIRED_STATE_PARSER = BombermanMaxParser
193    _ACTION_MAP = {
194        "up": LowLevelActions.PRESS_ARROW_UP,
195        "down": LowLevelActions.PRESS_ARROW_DOWN,
196        "left": LowLevelActions.PRESS_ARROW_LEFT,
197        "right": LowLevelActions.PRESS_ARROW_RIGHT,
198        "confirm": LowLevelActions.PRESS_BUTTON_A,
199        "back": LowLevelActions.PRESS_BUTTON_B,
200    }
201
202    def get_action_space(self):
203        return Discrete(len(self._ACTION_MAP))
204
205    def space_to_parameters(self, space_action):
206        keys = list(self._ACTION_MAP)
207        if space_action < 0 or space_action >= len(keys):
208            return None
209        return {"menu_action": keys[space_action]}
210
211    def parameters_to_space(self, menu_action: str):
212        keys = list(self._ACTION_MAP)
213        return keys.index(menu_action) if menu_action in keys else None
214
215    def is_valid(self, **kwargs):
216        menu_action = kwargs.get("menu_action")
217        return (
218            menu_action in self._ACTION_MAP if menu_action is not None else True
219        ) and self._state_tracker.get_episode_metric(MAX_MENU_METRIC)
220
221    def _execute(self, menu_action: str):
222        previous = self._emulator.get_current_frame()
223        frames, _ = self._emulator.step(self._ACTION_MAP[menu_action])
224        report = self._state_tracker.report()
225        return [report], 0 if frame_changed(previous, frames[-1]) else -1
226
227    @staticmethod
228    def get_action_name(menu_action: str) -> str:
229        return f"NavigateMenu {menu_action}"
230
231
232class BombermanMaxBattleAction(SingleHighLevelAction):
233    REQUIRED_STATE_TRACKER = BombermanMaxTracker
234    REQUIRED_STATE_PARSER = BombermanMaxParser
235    _ACTION_MAP = {
236        "bomb": LowLevelActions.PRESS_BUTTON_A,
237        "up": LowLevelActions.PRESS_ARROW_UP,
238        "down": LowLevelActions.PRESS_ARROW_DOWN,
239        "left": LowLevelActions.PRESS_ARROW_LEFT,
240        "right": LowLevelActions.PRESS_ARROW_RIGHT,
241    }
242
243    def get_action_space(self):
244        return Discrete(len(self._ACTION_MAP))
245
246    def space_to_parameters(self, space_action):
247        keys = list(self._ACTION_MAP)
248        if space_action < 0 or space_action >= len(keys):
249            return None
250        return {"battle_action": keys[space_action]}
251
252    def parameters_to_space(self, battle_action: str):
253        keys = list(self._ACTION_MAP)
254        return keys.index(battle_action) if battle_action in keys else None
255
256    def is_valid(self, **kwargs):
257        battle_action = kwargs.get("battle_action")
258        return (
259            battle_action in self._ACTION_MAP if battle_action is not None else True
260        ) and self._state_tracker.get_episode_metric(MAX_BATTLE_METRIC)
261
262    def _execute(self, battle_action: str):
263        previous = self._emulator.get_current_frame()
264        frames, _ = self._emulator.step(self._ACTION_MAP[battle_action])
265        report = self._state_tracker.report()
266        return [report], 0 if frame_changed(previous, frames[-1]) else -1
267
268    @staticmethod
269    def get_action_name(battle_action: str) -> str:
270        return f"Battle {battle_action}"
271
272
273class BombermanPocketMoveAction(_MoveAction):
274    REQUIRED_STATE_TRACKER = BombermanPocketTracker
275    REQUIRED_STATE_PARSER = BombermanPocketParser
276    _DIRECTION_TO_ACTION = {
277        "left": LowLevelActions.PRESS_ARROW_LEFT,
278        "right": LowLevelActions.PRESS_ARROW_RIGHT,
279    }
280    _BLOCKING_METRICS = [POCKET_MENU_METRIC]
281
282
283class BombermanPocketJumpAction(_MetricGatedSingleAction):
284    REQUIRED_STATE_TRACKER = BombermanPocketTracker
285    REQUIRED_STATE_PARSER = BombermanPocketParser
286    _BUTTON = LowLevelActions.PRESS_BUTTON_B
287    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
288
289    @staticmethod
290    def get_action_name() -> str:
291        return "Jump"
292
293
294class BombermanPocketPlaceBombAction(_MetricGatedSingleAction):
295    REQUIRED_STATE_TRACKER = BombermanPocketTracker
296    REQUIRED_STATE_PARSER = BombermanPocketParser
297    _BUTTON = LowLevelActions.PRESS_BUTTON_A
298    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
299
300    @staticmethod
301    def get_action_name() -> str:
302        return "PlaceBomb"
303
304
305class BombermanPocketOpenPauseMenuAction(_MetricGatedSingleAction):
306    REQUIRED_STATE_TRACKER = BombermanPocketTracker
307    REQUIRED_STATE_PARSER = BombermanPocketParser
308    _BUTTON = LowLevelActions.PRESS_BUTTON_START
309    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
310
311    def _execute(self):
312        self._emulator.step(self._BUTTON)
313        report = self._state_tracker.report()
314        return [report], 0 if self._state_tracker.get_episode_metric(POCKET_MENU_METRIC) else -1
315
316    @staticmethod
317    def get_action_name() -> str:
318        return "OpenPauseMenu"
319
320
321class BombermanPocketClosePauseMenuAction(_MetricGatedSingleAction):
322    REQUIRED_STATE_TRACKER = BombermanPocketTracker
323    REQUIRED_STATE_PARSER = BombermanPocketParser
324    _BUTTON = LowLevelActions.PRESS_BUTTON_START
325    _REQUIRED_METRICS_TRUE = [POCKET_MENU_METRIC]
326
327    def _execute(self):
328        self._emulator.step(self._BUTTON)
329        report = self._state_tracker.report()
330        return [report], 0 if not self._state_tracker.get_episode_metric(POCKET_MENU_METRIC) else -1
331
332    @staticmethod
333    def get_action_name() -> str:
334        return "ClosePauseMenu"
335
336
337class BombermanQuestMoveAction(_MoveAction):
338    REQUIRED_STATE_TRACKER = BombermanQuestTracker
339    REQUIRED_STATE_PARSER = BombermanQuestParser
340    _DIRECTION_TO_ACTION = {
341        "up": LowLevelActions.PRESS_ARROW_UP,
342        "down": LowLevelActions.PRESS_ARROW_DOWN,
343        "left": LowLevelActions.PRESS_ARROW_LEFT,
344        "right": LowLevelActions.PRESS_ARROW_RIGHT,
345    }
346    _BLOCKING_METRICS = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
347
348
349class BombermanQuestPlaceBombAction(_MetricGatedSingleAction):
350    REQUIRED_STATE_TRACKER = BombermanQuestTracker
351    REQUIRED_STATE_PARSER = BombermanQuestParser
352    _BUTTON = LowLevelActions.PRESS_BUTTON_A
353    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
354
355    @staticmethod
356    def get_action_name() -> str:
357        return "PlaceBomb"
358
359
360class BombermanQuestUseBButtonItemAction(_MetricGatedSingleAction):
361    REQUIRED_STATE_TRACKER = BombermanQuestTracker
362    REQUIRED_STATE_PARSER = BombermanQuestParser
363    _BUTTON = LowLevelActions.PRESS_BUTTON_B
364    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
365
366    @staticmethod
367    def get_action_name() -> str:
368        return "UseBButtonItem"
369
370
371class BombermanQuestOpenMenuAction(_MetricGatedSingleAction):
372    REQUIRED_STATE_TRACKER = BombermanQuestTracker
373    REQUIRED_STATE_PARSER = BombermanQuestParser
374    _BUTTON = LowLevelActions.PRESS_BUTTON_START
375    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
376
377    def _execute(self):
378        self._emulator.step(self._BUTTON)
379        report = self._state_tracker.report()
380        return [report], 0 if self._state_tracker.get_episode_metric(QUEST_MENU_METRIC) else -1
381
382    @staticmethod
383    def get_action_name() -> str:
384        return "OpenMenu"
385
386
387class BombermanQuestCloseMenuAction(_MetricGatedSingleAction):
388    REQUIRED_STATE_TRACKER = BombermanQuestTracker
389    REQUIRED_STATE_PARSER = BombermanQuestParser
390    _BUTTON = LowLevelActions.PRESS_BUTTON_START
391    _REQUIRED_METRICS_TRUE = [QUEST_MENU_METRIC]
392
393    def _execute(self):
394        self._emulator.step(self._BUTTON)
395        report = self._state_tracker.report()
396        return [report], 0 if not self._state_tracker.get_episode_metric(QUEST_MENU_METRIC) else -1
397
398    @staticmethod
399    def get_action_name() -> str:
400        return "CloseMenu"
401
402
403class BombermanQuestNavigateMenuAction(BombermanMaxNavigateMenuAction):
404    REQUIRED_STATE_TRACKER = BombermanQuestTracker
405    REQUIRED_STATE_PARSER = BombermanQuestParser
406
407    def is_valid(self, **kwargs):
408        menu_action = kwargs.get("menu_action")
409        return (
410            menu_action in self._ACTION_MAP if menu_action is not None else True
411        ) and self._state_tracker.get_episode_metric(QUEST_MENU_METRIC)
412
413
414class BombermanQuestBattleAction(BombermanMaxBattleAction):
415    REQUIRED_STATE_TRACKER = BombermanQuestTracker
416    REQUIRED_STATE_PARSER = BombermanQuestParser
417    _ACTION_MAP = {
418        "bomb": LowLevelActions.PRESS_BUTTON_A,
419        "item": LowLevelActions.PRESS_BUTTON_B,
420        "up": LowLevelActions.PRESS_ARROW_UP,
421        "down": LowLevelActions.PRESS_ARROW_DOWN,
422        "left": LowLevelActions.PRESS_ARROW_LEFT,
423        "right": LowLevelActions.PRESS_ARROW_RIGHT,
424    }
425
426    def is_valid(self, **kwargs):
427        battle_action = kwargs.get("battle_action")
428        return (
429            battle_action in self._ACTION_MAP if battle_action is not None else True
430        ) and self._state_tracker.get_episode_metric(QUEST_BATTLE_METRIC)
HARD_MAX_STEPS = 10
MAX_MENU_METRIC = ('bomberman_max_core', 'is_in_menu')
MAX_BATTLE_METRIC = ('bomberman_max_core', 'is_in_battle')
POCKET_MENU_METRIC = ('bomberman_pocket_core', 'is_in_menu')
QUEST_MENU_METRIC = ('bomberman_quest_core', 'is_in_menu')
QUEST_BATTLE_METRIC = ('bomberman_quest_core', 'is_in_battle')
def frame_changed( previous: numpy.ndarray, current: numpy.ndarray, epsilon: float = 0.01) -> bool:
29def frame_changed(previous: np.ndarray, current: np.ndarray, epsilon: float = 0.01) -> bool:
30    return np.abs(previous - current).mean() > epsilon
class BombermanMaxMoveAction(_MoveAction):
125class BombermanMaxMoveAction(_MoveAction):
126    REQUIRED_STATE_TRACKER = BombermanMaxTracker
127    REQUIRED_STATE_PARSER = BombermanMaxParser
128    _DIRECTION_TO_ACTION = {
129        "up": LowLevelActions.PRESS_ARROW_UP,
130        "down": LowLevelActions.PRESS_ARROW_DOWN,
131        "left": LowLevelActions.PRESS_ARROW_LEFT,
132        "right": LowLevelActions.PRESS_ARROW_RIGHT,
133    }
134    _BLOCKING_METRICS = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]

Abstract base class for high level actions.

The execute() method performs the high level action and returns a list of state tracker reports after each low level action executed, along with an action success status.

The docstring of each subclass should specify the interpretation of the action success status.

The execute() method may also return some additional information in the final state tracker report under the "core" key, "action_return" subkey (usually as a dict itself)

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

class BombermanMaxPlaceBombAction(_MetricGatedSingleAction):
137class BombermanMaxPlaceBombAction(_MetricGatedSingleAction):
138    REQUIRED_STATE_TRACKER = BombermanMaxTracker
139    REQUIRED_STATE_PARSER = BombermanMaxParser
140    _BUTTON = LowLevelActions.PRESS_BUTTON_A
141    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
142
143    @staticmethod
144    def get_action_name() -> str:
145        return "PlaceBomb"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
143    @staticmethod
144    def get_action_name() -> str:
145        return "PlaceBomb"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanMaxKickBombAction(_MetricGatedSingleAction):
148class BombermanMaxKickBombAction(_MetricGatedSingleAction):
149    REQUIRED_STATE_TRACKER = BombermanMaxTracker
150    REQUIRED_STATE_PARSER = BombermanMaxParser
151    _BUTTON = LowLevelActions.PRESS_BUTTON_B
152    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
153
154    @staticmethod
155    def get_action_name() -> str:
156        return "KickBomb"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
154    @staticmethod
155    def get_action_name() -> str:
156        return "KickBomb"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanMaxOpenMenuAction(_MetricGatedSingleAction):
159class BombermanMaxOpenMenuAction(_MetricGatedSingleAction):
160    REQUIRED_STATE_TRACKER = BombermanMaxTracker
161    REQUIRED_STATE_PARSER = BombermanMaxParser
162    _BUTTON = LowLevelActions.PRESS_BUTTON_START
163    _REQUIRED_METRICS_FALSE = [MAX_MENU_METRIC, MAX_BATTLE_METRIC]
164
165    def _execute(self):
166        self._emulator.step(self._BUTTON)
167        report = self._state_tracker.report()
168        return [report], 0 if self._state_tracker.get_episode_metric(MAX_MENU_METRIC) else -1
169
170    @staticmethod
171    def get_action_name() -> str:
172        return "OpenMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
170    @staticmethod
171    def get_action_name() -> str:
172        return "OpenMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanMaxCloseMenuAction(_MetricGatedSingleAction):
175class BombermanMaxCloseMenuAction(_MetricGatedSingleAction):
176    REQUIRED_STATE_TRACKER = BombermanMaxTracker
177    REQUIRED_STATE_PARSER = BombermanMaxParser
178    _BUTTON = LowLevelActions.PRESS_BUTTON_START
179    _REQUIRED_METRICS_TRUE = [MAX_MENU_METRIC]
180
181    def _execute(self):
182        self._emulator.step(self._BUTTON)
183        report = self._state_tracker.report()
184        return [report], 0 if not self._state_tracker.get_episode_metric(MAX_MENU_METRIC) else -1
185
186    @staticmethod
187    def get_action_name() -> str:
188        return "CloseMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
186    @staticmethod
187    def get_action_name() -> str:
188        return "CloseMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanMaxNavigateMenuAction(gameboy_worlds.interface.action.SingleHighLevelAction):
191class BombermanMaxNavigateMenuAction(SingleHighLevelAction):
192    REQUIRED_STATE_TRACKER = BombermanMaxTracker
193    REQUIRED_STATE_PARSER = BombermanMaxParser
194    _ACTION_MAP = {
195        "up": LowLevelActions.PRESS_ARROW_UP,
196        "down": LowLevelActions.PRESS_ARROW_DOWN,
197        "left": LowLevelActions.PRESS_ARROW_LEFT,
198        "right": LowLevelActions.PRESS_ARROW_RIGHT,
199        "confirm": LowLevelActions.PRESS_BUTTON_A,
200        "back": LowLevelActions.PRESS_BUTTON_B,
201    }
202
203    def get_action_space(self):
204        return Discrete(len(self._ACTION_MAP))
205
206    def space_to_parameters(self, space_action):
207        keys = list(self._ACTION_MAP)
208        if space_action < 0 or space_action >= len(keys):
209            return None
210        return {"menu_action": keys[space_action]}
211
212    def parameters_to_space(self, menu_action: str):
213        keys = list(self._ACTION_MAP)
214        return keys.index(menu_action) if menu_action in keys else None
215
216    def is_valid(self, **kwargs):
217        menu_action = kwargs.get("menu_action")
218        return (
219            menu_action in self._ACTION_MAP if menu_action is not None else True
220        ) and self._state_tracker.get_episode_metric(MAX_MENU_METRIC)
221
222    def _execute(self, menu_action: str):
223        previous = self._emulator.get_current_frame()
224        frames, _ = self._emulator.step(self._ACTION_MAP[menu_action])
225        report = self._state_tracker.report()
226        return [report], 0 if frame_changed(previous, frames[-1]) else -1
227
228    @staticmethod
229    def get_action_name(menu_action: str) -> str:
230        return f"NavigateMenu {menu_action}"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

def get_action_space(self):
203    def get_action_space(self):
204        return Discrete(len(self._ACTION_MAP))

Returns the Gym defined Space that characterizes the high level action's parameter space.

You can use this API to get a Space for sampling high level actions of this type.

Returns:

Space: The Gym space that characterizes the high level action's parameter space.

def space_to_parameters(self, space_action):
206    def space_to_parameters(self, space_action):
207        keys = list(self._ACTION_MAP)
208        if space_action < 0 or space_action >= len(keys):
209            return None
210        return {"menu_action": keys[space_action]}

Converts a Gym space action into high level action parameters. If the provided space action is invalid, return None.

Parameters
  • space_action: The action in the high level action's parameter space.
Returns

The high level action's parameters corresponding to the space action.

def parameters_to_space(self, menu_action: str):
212    def parameters_to_space(self, menu_action: str):
213        keys = list(self._ACTION_MAP)
214        return keys.index(menu_action) if menu_action in keys else None

Converts high level action parameters into a Gym space action. If the provided parameters are invalid, return None.

Parameters
  • kwargs: The high level action's parameters.
Returns

The action in the high level action's parameter space.

def is_valid(self, **kwargs):
216    def is_valid(self, **kwargs):
217        menu_action = kwargs.get("menu_action")
218        return (
219            menu_action in self._ACTION_MAP if menu_action is not None else True
220        ) and self._state_tracker.get_episode_metric(MAX_MENU_METRIC)

Checks if the high level action can be performed in the current state. If kwargs is empty, then must check whether there exists any valid way to perform the action.

Arguments:
  • **kwargs: Additional arguments required for the specific high level action.
Returns:

bool: Whether the action is valid in the current state.

@staticmethod
def get_action_name(menu_action: str) -> str:
228    @staticmethod
229    def get_action_name(menu_action: str) -> str:
230        return f"NavigateMenu {menu_action}"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanMaxBattleAction(gameboy_worlds.interface.action.SingleHighLevelAction):
233class BombermanMaxBattleAction(SingleHighLevelAction):
234    REQUIRED_STATE_TRACKER = BombermanMaxTracker
235    REQUIRED_STATE_PARSER = BombermanMaxParser
236    _ACTION_MAP = {
237        "bomb": LowLevelActions.PRESS_BUTTON_A,
238        "up": LowLevelActions.PRESS_ARROW_UP,
239        "down": LowLevelActions.PRESS_ARROW_DOWN,
240        "left": LowLevelActions.PRESS_ARROW_LEFT,
241        "right": LowLevelActions.PRESS_ARROW_RIGHT,
242    }
243
244    def get_action_space(self):
245        return Discrete(len(self._ACTION_MAP))
246
247    def space_to_parameters(self, space_action):
248        keys = list(self._ACTION_MAP)
249        if space_action < 0 or space_action >= len(keys):
250            return None
251        return {"battle_action": keys[space_action]}
252
253    def parameters_to_space(self, battle_action: str):
254        keys = list(self._ACTION_MAP)
255        return keys.index(battle_action) if battle_action in keys else None
256
257    def is_valid(self, **kwargs):
258        battle_action = kwargs.get("battle_action")
259        return (
260            battle_action in self._ACTION_MAP if battle_action is not None else True
261        ) and self._state_tracker.get_episode_metric(MAX_BATTLE_METRIC)
262
263    def _execute(self, battle_action: str):
264        previous = self._emulator.get_current_frame()
265        frames, _ = self._emulator.step(self._ACTION_MAP[battle_action])
266        report = self._state_tracker.report()
267        return [report], 0 if frame_changed(previous, frames[-1]) else -1
268
269    @staticmethod
270    def get_action_name(battle_action: str) -> str:
271        return f"Battle {battle_action}"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

def get_action_space(self):
244    def get_action_space(self):
245        return Discrete(len(self._ACTION_MAP))

Returns the Gym defined Space that characterizes the high level action's parameter space.

You can use this API to get a Space for sampling high level actions of this type.

Returns:

Space: The Gym space that characterizes the high level action's parameter space.

def space_to_parameters(self, space_action):
247    def space_to_parameters(self, space_action):
248        keys = list(self._ACTION_MAP)
249        if space_action < 0 or space_action >= len(keys):
250            return None
251        return {"battle_action": keys[space_action]}

Converts a Gym space action into high level action parameters. If the provided space action is invalid, return None.

Parameters
  • space_action: The action in the high level action's parameter space.
Returns

The high level action's parameters corresponding to the space action.

def parameters_to_space(self, battle_action: str):
253    def parameters_to_space(self, battle_action: str):
254        keys = list(self._ACTION_MAP)
255        return keys.index(battle_action) if battle_action in keys else None

Converts high level action parameters into a Gym space action. If the provided parameters are invalid, return None.

Parameters
  • kwargs: The high level action's parameters.
Returns

The action in the high level action's parameter space.

def is_valid(self, **kwargs):
257    def is_valid(self, **kwargs):
258        battle_action = kwargs.get("battle_action")
259        return (
260            battle_action in self._ACTION_MAP if battle_action is not None else True
261        ) and self._state_tracker.get_episode_metric(MAX_BATTLE_METRIC)

Checks if the high level action can be performed in the current state. If kwargs is empty, then must check whether there exists any valid way to perform the action.

Arguments:
  • **kwargs: Additional arguments required for the specific high level action.
Returns:

bool: Whether the action is valid in the current state.

@staticmethod
def get_action_name(battle_action: str) -> str:
269    @staticmethod
270    def get_action_name(battle_action: str) -> str:
271        return f"Battle {battle_action}"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanPocketMoveAction(_MoveAction):
274class BombermanPocketMoveAction(_MoveAction):
275    REQUIRED_STATE_TRACKER = BombermanPocketTracker
276    REQUIRED_STATE_PARSER = BombermanPocketParser
277    _DIRECTION_TO_ACTION = {
278        "left": LowLevelActions.PRESS_ARROW_LEFT,
279        "right": LowLevelActions.PRESS_ARROW_RIGHT,
280    }
281    _BLOCKING_METRICS = [POCKET_MENU_METRIC]

Abstract base class for high level actions.

The execute() method performs the high level action and returns a list of state tracker reports after each low level action executed, along with an action success status.

The docstring of each subclass should specify the interpretation of the action success status.

The execute() method may also return some additional information in the final state tracker report under the "core" key, "action_return" subkey (usually as a dict itself)

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

class BombermanPocketJumpAction(_MetricGatedSingleAction):
284class BombermanPocketJumpAction(_MetricGatedSingleAction):
285    REQUIRED_STATE_TRACKER = BombermanPocketTracker
286    REQUIRED_STATE_PARSER = BombermanPocketParser
287    _BUTTON = LowLevelActions.PRESS_BUTTON_B
288    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
289
290    @staticmethod
291    def get_action_name() -> str:
292        return "Jump"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
290    @staticmethod
291    def get_action_name() -> str:
292        return "Jump"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanPocketPlaceBombAction(_MetricGatedSingleAction):
295class BombermanPocketPlaceBombAction(_MetricGatedSingleAction):
296    REQUIRED_STATE_TRACKER = BombermanPocketTracker
297    REQUIRED_STATE_PARSER = BombermanPocketParser
298    _BUTTON = LowLevelActions.PRESS_BUTTON_A
299    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
300
301    @staticmethod
302    def get_action_name() -> str:
303        return "PlaceBomb"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
301    @staticmethod
302    def get_action_name() -> str:
303        return "PlaceBomb"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanPocketOpenPauseMenuAction(_MetricGatedSingleAction):
306class BombermanPocketOpenPauseMenuAction(_MetricGatedSingleAction):
307    REQUIRED_STATE_TRACKER = BombermanPocketTracker
308    REQUIRED_STATE_PARSER = BombermanPocketParser
309    _BUTTON = LowLevelActions.PRESS_BUTTON_START
310    _REQUIRED_METRICS_FALSE = [POCKET_MENU_METRIC]
311
312    def _execute(self):
313        self._emulator.step(self._BUTTON)
314        report = self._state_tracker.report()
315        return [report], 0 if self._state_tracker.get_episode_metric(POCKET_MENU_METRIC) else -1
316
317    @staticmethod
318    def get_action_name() -> str:
319        return "OpenPauseMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
317    @staticmethod
318    def get_action_name() -> str:
319        return "OpenPauseMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanPocketClosePauseMenuAction(_MetricGatedSingleAction):
322class BombermanPocketClosePauseMenuAction(_MetricGatedSingleAction):
323    REQUIRED_STATE_TRACKER = BombermanPocketTracker
324    REQUIRED_STATE_PARSER = BombermanPocketParser
325    _BUTTON = LowLevelActions.PRESS_BUTTON_START
326    _REQUIRED_METRICS_TRUE = [POCKET_MENU_METRIC]
327
328    def _execute(self):
329        self._emulator.step(self._BUTTON)
330        report = self._state_tracker.report()
331        return [report], 0 if not self._state_tracker.get_episode_metric(POCKET_MENU_METRIC) else -1
332
333    @staticmethod
334    def get_action_name() -> str:
335        return "ClosePauseMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
333    @staticmethod
334    def get_action_name() -> str:
335        return "ClosePauseMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanQuestMoveAction(_MoveAction):
338class BombermanQuestMoveAction(_MoveAction):
339    REQUIRED_STATE_TRACKER = BombermanQuestTracker
340    REQUIRED_STATE_PARSER = BombermanQuestParser
341    _DIRECTION_TO_ACTION = {
342        "up": LowLevelActions.PRESS_ARROW_UP,
343        "down": LowLevelActions.PRESS_ARROW_DOWN,
344        "left": LowLevelActions.PRESS_ARROW_LEFT,
345        "right": LowLevelActions.PRESS_ARROW_RIGHT,
346    }
347    _BLOCKING_METRICS = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]

Abstract base class for high level actions.

The execute() method performs the high level action and returns a list of state tracker reports after each low level action executed, along with an action success status.

The docstring of each subclass should specify the interpretation of the action success status.

The execute() method may also return some additional information in the final state tracker report under the "core" key, "action_return" subkey (usually as a dict itself)

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

class BombermanQuestPlaceBombAction(_MetricGatedSingleAction):
350class BombermanQuestPlaceBombAction(_MetricGatedSingleAction):
351    REQUIRED_STATE_TRACKER = BombermanQuestTracker
352    REQUIRED_STATE_PARSER = BombermanQuestParser
353    _BUTTON = LowLevelActions.PRESS_BUTTON_A
354    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
355
356    @staticmethod
357    def get_action_name() -> str:
358        return "PlaceBomb"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
356    @staticmethod
357    def get_action_name() -> str:
358        return "PlaceBomb"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanQuestUseBButtonItemAction(_MetricGatedSingleAction):
361class BombermanQuestUseBButtonItemAction(_MetricGatedSingleAction):
362    REQUIRED_STATE_TRACKER = BombermanQuestTracker
363    REQUIRED_STATE_PARSER = BombermanQuestParser
364    _BUTTON = LowLevelActions.PRESS_BUTTON_B
365    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
366
367    @staticmethod
368    def get_action_name() -> str:
369        return "UseBButtonItem"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
367    @staticmethod
368    def get_action_name() -> str:
369        return "UseBButtonItem"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanQuestOpenMenuAction(_MetricGatedSingleAction):
372class BombermanQuestOpenMenuAction(_MetricGatedSingleAction):
373    REQUIRED_STATE_TRACKER = BombermanQuestTracker
374    REQUIRED_STATE_PARSER = BombermanQuestParser
375    _BUTTON = LowLevelActions.PRESS_BUTTON_START
376    _REQUIRED_METRICS_FALSE = [QUEST_MENU_METRIC, QUEST_BATTLE_METRIC]
377
378    def _execute(self):
379        self._emulator.step(self._BUTTON)
380        report = self._state_tracker.report()
381        return [report], 0 if self._state_tracker.get_episode_metric(QUEST_MENU_METRIC) else -1
382
383    @staticmethod
384    def get_action_name() -> str:
385        return "OpenMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
383    @staticmethod
384    def get_action_name() -> str:
385        return "OpenMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanQuestCloseMenuAction(_MetricGatedSingleAction):
388class BombermanQuestCloseMenuAction(_MetricGatedSingleAction):
389    REQUIRED_STATE_TRACKER = BombermanQuestTracker
390    REQUIRED_STATE_PARSER = BombermanQuestParser
391    _BUTTON = LowLevelActions.PRESS_BUTTON_START
392    _REQUIRED_METRICS_TRUE = [QUEST_MENU_METRIC]
393
394    def _execute(self):
395        self._emulator.step(self._BUTTON)
396        report = self._state_tracker.report()
397        return [report], 0 if not self._state_tracker.get_episode_metric(QUEST_MENU_METRIC) else -1
398
399    @staticmethod
400    def get_action_name() -> str:
401        return "CloseMenu"

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

@staticmethod
def get_action_name() -> str:
399    @staticmethod
400    def get_action_name() -> str:
401        return "CloseMenu"

Returns a human readable name for the high level action with the given parameters.

Parameters
  • kwargs: The high level action's parameters.
Returns

A human readable name for the high level action.

class BombermanQuestNavigateMenuAction(BombermanMaxNavigateMenuAction):
404class BombermanQuestNavigateMenuAction(BombermanMaxNavigateMenuAction):
405    REQUIRED_STATE_TRACKER = BombermanQuestTracker
406    REQUIRED_STATE_PARSER = BombermanQuestParser
407
408    def is_valid(self, **kwargs):
409        menu_action = kwargs.get("menu_action")
410        return (
411            menu_action in self._ACTION_MAP if menu_action is not None else True
412        ) and self._state_tracker.get_episode_metric(QUEST_MENU_METRIC)

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

def is_valid(self, **kwargs):
408    def is_valid(self, **kwargs):
409        menu_action = kwargs.get("menu_action")
410        return (
411            menu_action in self._ACTION_MAP if menu_action is not None else True
412        ) and self._state_tracker.get_episode_metric(QUEST_MENU_METRIC)

Checks if the high level action can be performed in the current state. If kwargs is empty, then must check whether there exists any valid way to perform the action.

Arguments:
  • **kwargs: Additional arguments required for the specific high level action.
Returns:

bool: Whether the action is valid in the current state.

class BombermanQuestBattleAction(BombermanMaxBattleAction):
415class BombermanQuestBattleAction(BombermanMaxBattleAction):
416    REQUIRED_STATE_TRACKER = BombermanQuestTracker
417    REQUIRED_STATE_PARSER = BombermanQuestParser
418    _ACTION_MAP = {
419        "bomb": LowLevelActions.PRESS_BUTTON_A,
420        "item": LowLevelActions.PRESS_BUTTON_B,
421        "up": LowLevelActions.PRESS_ARROW_UP,
422        "down": LowLevelActions.PRESS_ARROW_DOWN,
423        "left": LowLevelActions.PRESS_ARROW_LEFT,
424        "right": LowLevelActions.PRESS_ARROW_RIGHT,
425    }
426
427    def is_valid(self, **kwargs):
428        battle_action = kwargs.get("battle_action")
429        return (
430            battle_action in self._ACTION_MAP if battle_action is not None else True
431        ) and self._state_tracker.get_episode_metric(QUEST_BATTLE_METRIC)

An abstract class for a high level action that has only one possible parameterization.

The state tracker that tracks the minimal state information required for the action to function.

The state parser that parses the minimal state information required for the action to function.

def is_valid(self, **kwargs):
427    def is_valid(self, **kwargs):
428        battle_action = kwargs.get("battle_action")
429        return (
430            battle_action in self._ACTION_MAP if battle_action is not None else True
431        ) and self._state_tracker.get_episode_metric(QUEST_BATTLE_METRIC)

Checks if the high level action can be performed in the current state. If kwargs is empty, then must check whether there exists any valid way to perform the action.

Arguments:
  • **kwargs: Additional arguments required for the specific high level action.
Returns:

bool: Whether the action is valid in the current state.