gameboy_worlds.interface.action

  1from abc import ABC, abstractmethod
  2from typing import Any, Dict, Tuple, List, Optional
  3from enum import Enum
  4from gameboy_worlds.emulation.emulator import LowLevelActions, Emulator
  5from gameboy_worlds.utils import (
  6    verify_parameters,
  7    log_info,
  8    log_warn,
  9    log_error,
 10    load_parameters,
 11    get_lowest_level_subclass,
 12)
 13from gameboy_worlds.emulation import StateTracker, StateParser
 14
 15import numpy as np
 16from gymnasium.spaces import Space, Discrete
 17
 18
 19class HighLevelAction(ABC):
 20    """
 21    Abstract base class for high level actions.
 22
 23    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.
 24
 25    The docstring of each subclass should specify the interpretation of the action success status.
 26
 27    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)
 28    """
 29
 30    REQUIRED_STATE_TRACKER = StateTracker
 31    """ The state tracker that tracks the minimal state information required for the action to function. """
 32
 33    REQUIRED_STATE_PARSER = StateParser
 34    """ The state parser that parses the minimal state information required for the action to function. """
 35
 36    def __init__(self, parameters: dict, seed: Optional[int] = None):
 37        verify_parameters(parameters)
 38        self._parameters = parameters
 39        self._rng = np.random.default_rng(seed)
 40        self.unassign_emulator()
 41
 42    def seed(self, seed: Optional[int] = None):
 43        """
 44        Sets the random seed for the high level action.
 45        Args:
 46            seed (int): The random seed to set.
 47        """
 48        self._rng = np.random.default_rng(seed)
 49
 50    def assign_emulator(self, emulator: Emulator):
 51        """
 52        Sets a reference to the emulator instance.
 53        Args:
 54            emulator (Emulator): The emulator instance to be tracked.
 55        """
 56        if not isinstance(emulator, Emulator):
 57            log_error(
 58                f"HighLevelAction requires an Emulator instance, but got {type(emulator)}",
 59                self._parameters,
 60            )
 61        self._emulator = emulator
 62        self._state_tracker = emulator.state_tracker
 63        if not issubclass(type(self._state_tracker), self.REQUIRED_STATE_TRACKER):
 64            log_error(
 65                f"HighLevelAction requires a StateTracker of type {self.REQUIRED_STATE_TRACKER}, but got {type(self._state_tracker)}",
 66                self._parameters,
 67            )
 68        if not issubclass(type(emulator.state_parser), self.REQUIRED_STATE_PARSER):
 69            log_error(
 70                f"HighLevelAction requires a StateParser of type {self.REQUIRED_STATE_PARSER}, but got {type(emulator.state_parser)}",
 71                self._parameters,
 72            )
 73
 74    def unassign_emulator(self):
 75        """
 76        Clears the reference to the emulator instance.
 77        """
 78        self._emulator = None
 79        self._state_tracker = None
 80
 81    @abstractmethod
 82    def get_action_space(self) -> Space:
 83        """
 84        Returns the Gym defined Space that characterizes the high level action's parameter space.
 85
 86        You can use this API to get a Space for sampling high level actions of this type.
 87
 88        Returns:
 89            Space: The Gym space that characterizes the high level action's parameter space.
 90        """
 91        raise NotImplementedError
 92
 93    @abstractmethod
 94    def space_to_parameters(self, space_action: Space) -> Optional[Dict[str, Any]]:
 95        """
 96        Converts a Gym space action into high level action parameters.
 97        If the provided space action is invalid, return None.
 98
 99        :param space_action: The action in the high level action's parameter space.
100        :type space_action: Space
101        :return: The high level action's parameters corresponding to the space action.
102        :rtype: Optional[Dict[str, Any]]
103        """
104        raise NotImplementedError
105
106    @abstractmethod
107    def parameters_to_space(self, **kwargs) -> Optional[Space]:
108        """
109        Converts high level action parameters into a Gym space action.
110        If the provided parameters are invalid, return None.
111
112        :param kwargs: The high level action's parameters.
113        :type kwargs: Dict[str, Any]
114        :return: The action in the high level action's parameter space.
115        :rtype: Optional[Space]
116        """
117        raise NotImplementedError
118
119    @abstractmethod
120    def is_valid(self, **kwargs) -> bool:
121        """
122        Checks if the high level action can be performed in the current state.
123        If kwargs is empty, then must check whether there exists any valid way to perform the action.
124
125        Args:
126            **kwargs: Additional arguments required for the specific high level action.
127        Returns:
128            bool: Whether the action is valid in the current state.
129        """
130        raise NotImplementedError
131
132    @abstractmethod
133    def _execute(self, **kwargs) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
134        """
135        Executes the specified, valid high level action on the emulator.
136        Does not check for validity, assumes the action is valid.
137
138        :param self: Description
139        :param kwargs: Additional arguments required for the specific high level action.
140        :return:
141            - A list of state tracker reports after each low level action executed.
142
143            - Action success status.
144        :rtype: Tuple[List[Dict[str, Dict[str, Any]]], int]
145        """
146        raise NotImplementedError
147
148    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
149        """
150        Returns a list of all valid parameterizations for the high level action in the current state.
151
152        May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)
153
154        Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.
155
156
157        Returns:
158
159            List[Dict[str, Any]]: A list of valid parameterizations for the high level action.
160        """
161        raise ValueError(
162            "This high level action does not implement get_all_valid_parameters(). Most likely, it is not possible to enumerate an exhaustive list of all valid inputs. Use is_valid() instead. See documentation for more details. If you believe this is an error, please implement get_all_valid_parameters() in the high level action subclass."
163        )
164
165    def execute(
166        self, **kwargs
167    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
168        """
169        Executes the specified high level action on the emulator after checking for validity.
170
171        :param kwargs: Additional arguments required for the specific high level action.
172        :return: None, None if the action is not valid. Otherwise:
173
174            - A list of state tracker reports after each low level action executed.
175
176            - Action success status.
177        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
178        """
179        if self._emulator is None:
180            log_error(
181                f"Tried to execute action on HighLevelAction without an emulator",
182                self._parameters,
183            )
184        if not self.is_valid(**kwargs):
185            return None, None
186        return self._execute(**kwargs)
187
188    def execute_space_action(
189        self, space_action: Space
190    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
191        """
192        Executes the specified high level action on the emulator after checking for validity.
193
194        :param space_action: The action in the high level action's parameter space.
195        :type space_action: Space
196        :return: None, None if the action is not valid. Otherwise:
197
198            - A list of state tracker reports after each low level action executed.
199
200            - Action success status.
201        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
202        """
203        parameters = self.space_to_parameters(space_action)
204        if parameters is None:
205            return None, None
206        return self.execute(**parameters)
207
208    @staticmethod
209    @abstractmethod
210    def get_action_name(**kwargs) -> str:
211        """
212        Returns a human readable name for the high level action with the given parameters.
213
214        :param kwargs: The high level action's parameters.
215        :type kwargs: Dict[str, Any]
216        :return: A human readable name for the high level action.
217        :rtype: str
218        """
219        raise NotImplementedError
220
221
222class SingleHighLevelAction(HighLevelAction):
223    """
224    An abstract class for a high level action that has only one possible parameterization.
225    """
226
227    def space_to_parameters(self, space_action):
228        return {}
229
230    def parameters_to_space(self):
231        return 0
232
233    def get_action_space(self):
234        return Discrete(1)
235
236    def get_all_valid_parameters(self):
237        if self.is_valid():
238            return [{}]
239        else:
240            return []
241
242
243class LowLevelAction(HighLevelAction):
244    """A high level action that directly maps to a single low level action."""
245
246    def get_action_space(self):
247        """
248        Returns the Gym defined Space that characterizes the low level action's parameter space.
249        """
250        return Discrete(len(LowLevelActions))
251
252    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
253        action = list(LowLevelActions)[space_action]
254        return {"low_level_action": action}
255
256    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
257        if low_level_action is None or not isinstance(
258            low_level_action, LowLevelActions
259        ):
260            # log_warn(
261            #    "LowLevelAction requires a 'low_level_action' parameter of type LowLevelActions.",
262            #    self._parameters,
263            # )
264            return None
265        return low_level_action.value
266
267    def _execute(
268        self, low_level_action: LowLevelActions
269    ) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
270        self._emulator.step(low_level_action)
271        state_report = self._state_tracker.report()
272        return [
273            state_report
274        ], 0  # Low level actions are always successful in this context.
275
276    def is_valid(self, low_level_action: LowLevelActions) -> bool:
277        """
278        Checks if the low level action can be performed in the current state.
279
280        Args:
281            low_level_action (LowLevelActions): The low level action to check.
282        Returns:
283            bool: Whether the action is valid in the current state.
284        """
285        return True
286
287    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
288        """
289        Returns a list of all valid low level actions in the current state.
290
291        :return: A list of valid low level actions.
292        :rtype: List[Dict[str, Any]]
293        """
294        return [{"low_level_action": action} for action in LowLevelActions]
295
296    @staticmethod
297    def get_action_name(low_level_action: LowLevelActions) -> str:
298        action = (
299            str(low_level_action)
300            .replace("LowLevelActions.PRESS_ARROW_", "")
301            .replace("LowLevelActions.PRESS_BUTTON_", "")
302        )
303        return action
304
305
306class LowLevelPlayAction(HighLevelAction):
307    """A HighLevelAction subclass that directly maps to low level actions, except no menu button presses."""
308
309    def __init__(self, parameters: dict, seed: Optional[int] = None):
310        self.allowed_actions = [
311            LowLevelActions.PRESS_ARROW_UP,
312            LowLevelActions.PRESS_ARROW_DOWN,
313            LowLevelActions.PRESS_ARROW_RIGHT,
314            LowLevelActions.PRESS_ARROW_LEFT,
315            LowLevelActions.PRESS_BUTTON_A,
316            LowLevelActions.PRESS_BUTTON_B,
317        ]
318        super().__init__(parameters, seed=seed)
319
320    def get_action_space(self):
321        """
322        Returns the Gym defined Space that characterizes the low level play action's parameter space.
323        """
324        return Discrete(len(self.allowed_actions))
325
326    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
327        action = self.allowed_actions[space_action]
328        return {"low_level_action": action}
329
330    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
331        if low_level_action is None or low_level_action not in self.allowed_actions:
332            # log_warn(
333            #    "LowLevelPlayAction requires a 'low_level_action' parameter that is not a menu button press.",
334            #    self._parameters,
335            # )
336            return None
337        return self.allowed_actions.index(low_level_action)
338
339    def _execute(
340        self, low_level_action: LowLevelActions
341    ) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
342        self._emulator.step(low_level_action)
343        state_report = self._state_tracker.report()
344        return [
345            state_report
346        ], 0  # Low level actions are always successful in this context.
347
348    def is_valid(self, low_level_action: LowLevelActions) -> bool:
349        return low_level_action in self.allowed_actions
350
351    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
352        return [{"low_level_action": action} for action in self.allowed_actions]
353
354    @staticmethod
355    def get_action_name(low_level_action: LowLevelActions) -> str:
356        action = (
357            str(low_level_action)
358            .replace("LowLevelActions.PRESS_ARROW_", "")
359            .replace("LowLevelActions.PRESS_BUTTON_", "")
360        )
361        return action
362
363
364class RandomPlayAction(HighLevelAction):
365    """Execution either moves or presses A
366
367    Action Success Interpretation:
368
369    - 0: The frame changed after the action
370    - 1: The frame did not change after the action
371
372    """
373
374    def get_action_space(self):
375        """
376        Returns the Gym defined Space that characterizes the random play action's parameter space.
377        """
378        return Discrete(2)  # 0 for move, 1 for press A
379
380    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
381        if space_action == 0:
382            return {"kind": "move"}
383        else:
384            return {"kind": "press"}
385
386    def parameters_to_space(self, kind: str) -> Space:
387        if kind == "move":
388            return 0
389        elif kind == "press":
390            return 1
391        else:
392            # log_warn(
393            #    "RandomPlayAction requires a 'kind' parameter of either 'move' or 'press'.",
394            #    self._parameters,
395            # )
396            return None
397
398    def _execute(self, kind: str):
399        if kind == "move":
400            actions = [
401                LowLevelActions.PRESS_ARROW_DOWN,
402                LowLevelActions.PRESS_ARROW_LEFT,
403                LowLevelActions.PRESS_ARROW_RIGHT,
404                LowLevelActions.PRESS_ARROW_UP,
405            ]
406
407        else:  # kind must be 'press'. Enforced in is_valid
408            actions = [
409                LowLevelActions.PRESS_BUTTON_A,
410            ]
411        action = self._rng.choice(actions)
412        self._emulator.step(action)
413        state_report = self._state_tracker.report()
414        not_success = not state_report["core"][
415            "frame_changed"
416        ]  # Whether the frame changed after the action
417        return [state_report], not_success
418
419    def is_valid(self, kind: str) -> bool:
420        if kind not in ["move", "press"]:
421            return False
422        return True
423
424    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
425        return [{"kind": "move"}, {"kind": "press"}]
426
427    @staticmethod
428    def get_action_name(kind: str) -> str:
429        return kind
class HighLevelAction(abc.ABC):
 20class HighLevelAction(ABC):
 21    """
 22    Abstract base class for high level actions.
 23
 24    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.
 25
 26    The docstring of each subclass should specify the interpretation of the action success status.
 27
 28    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)
 29    """
 30
 31    REQUIRED_STATE_TRACKER = StateTracker
 32    """ The state tracker that tracks the minimal state information required for the action to function. """
 33
 34    REQUIRED_STATE_PARSER = StateParser
 35    """ The state parser that parses the minimal state information required for the action to function. """
 36
 37    def __init__(self, parameters: dict, seed: Optional[int] = None):
 38        verify_parameters(parameters)
 39        self._parameters = parameters
 40        self._rng = np.random.default_rng(seed)
 41        self.unassign_emulator()
 42
 43    def seed(self, seed: Optional[int] = None):
 44        """
 45        Sets the random seed for the high level action.
 46        Args:
 47            seed (int): The random seed to set.
 48        """
 49        self._rng = np.random.default_rng(seed)
 50
 51    def assign_emulator(self, emulator: Emulator):
 52        """
 53        Sets a reference to the emulator instance.
 54        Args:
 55            emulator (Emulator): The emulator instance to be tracked.
 56        """
 57        if not isinstance(emulator, Emulator):
 58            log_error(
 59                f"HighLevelAction requires an Emulator instance, but got {type(emulator)}",
 60                self._parameters,
 61            )
 62        self._emulator = emulator
 63        self._state_tracker = emulator.state_tracker
 64        if not issubclass(type(self._state_tracker), self.REQUIRED_STATE_TRACKER):
 65            log_error(
 66                f"HighLevelAction requires a StateTracker of type {self.REQUIRED_STATE_TRACKER}, but got {type(self._state_tracker)}",
 67                self._parameters,
 68            )
 69        if not issubclass(type(emulator.state_parser), self.REQUIRED_STATE_PARSER):
 70            log_error(
 71                f"HighLevelAction requires a StateParser of type {self.REQUIRED_STATE_PARSER}, but got {type(emulator.state_parser)}",
 72                self._parameters,
 73            )
 74
 75    def unassign_emulator(self):
 76        """
 77        Clears the reference to the emulator instance.
 78        """
 79        self._emulator = None
 80        self._state_tracker = None
 81
 82    @abstractmethod
 83    def get_action_space(self) -> Space:
 84        """
 85        Returns the Gym defined Space that characterizes the high level action's parameter space.
 86
 87        You can use this API to get a Space for sampling high level actions of this type.
 88
 89        Returns:
 90            Space: The Gym space that characterizes the high level action's parameter space.
 91        """
 92        raise NotImplementedError
 93
 94    @abstractmethod
 95    def space_to_parameters(self, space_action: Space) -> Optional[Dict[str, Any]]:
 96        """
 97        Converts a Gym space action into high level action parameters.
 98        If the provided space action is invalid, return None.
 99
100        :param space_action: The action in the high level action's parameter space.
101        :type space_action: Space
102        :return: The high level action's parameters corresponding to the space action.
103        :rtype: Optional[Dict[str, Any]]
104        """
105        raise NotImplementedError
106
107    @abstractmethod
108    def parameters_to_space(self, **kwargs) -> Optional[Space]:
109        """
110        Converts high level action parameters into a Gym space action.
111        If the provided parameters are invalid, return None.
112
113        :param kwargs: The high level action's parameters.
114        :type kwargs: Dict[str, Any]
115        :return: The action in the high level action's parameter space.
116        :rtype: Optional[Space]
117        """
118        raise NotImplementedError
119
120    @abstractmethod
121    def is_valid(self, **kwargs) -> bool:
122        """
123        Checks if the high level action can be performed in the current state.
124        If kwargs is empty, then must check whether there exists any valid way to perform the action.
125
126        Args:
127            **kwargs: Additional arguments required for the specific high level action.
128        Returns:
129            bool: Whether the action is valid in the current state.
130        """
131        raise NotImplementedError
132
133    @abstractmethod
134    def _execute(self, **kwargs) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
135        """
136        Executes the specified, valid high level action on the emulator.
137        Does not check for validity, assumes the action is valid.
138
139        :param self: Description
140        :param kwargs: Additional arguments required for the specific high level action.
141        :return:
142            - A list of state tracker reports after each low level action executed.
143
144            - Action success status.
145        :rtype: Tuple[List[Dict[str, Dict[str, Any]]], int]
146        """
147        raise NotImplementedError
148
149    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
150        """
151        Returns a list of all valid parameterizations for the high level action in the current state.
152
153        May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)
154
155        Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.
156
157
158        Returns:
159
160            List[Dict[str, Any]]: A list of valid parameterizations for the high level action.
161        """
162        raise ValueError(
163            "This high level action does not implement get_all_valid_parameters(). Most likely, it is not possible to enumerate an exhaustive list of all valid inputs. Use is_valid() instead. See documentation for more details. If you believe this is an error, please implement get_all_valid_parameters() in the high level action subclass."
164        )
165
166    def execute(
167        self, **kwargs
168    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
169        """
170        Executes the specified high level action on the emulator after checking for validity.
171
172        :param kwargs: Additional arguments required for the specific high level action.
173        :return: None, None if the action is not valid. Otherwise:
174
175            - A list of state tracker reports after each low level action executed.
176
177            - Action success status.
178        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
179        """
180        if self._emulator is None:
181            log_error(
182                f"Tried to execute action on HighLevelAction without an emulator",
183                self._parameters,
184            )
185        if not self.is_valid(**kwargs):
186            return None, None
187        return self._execute(**kwargs)
188
189    def execute_space_action(
190        self, space_action: Space
191    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
192        """
193        Executes the specified high level action on the emulator after checking for validity.
194
195        :param space_action: The action in the high level action's parameter space.
196        :type space_action: Space
197        :return: None, None if the action is not valid. Otherwise:
198
199            - A list of state tracker reports after each low level action executed.
200
201            - Action success status.
202        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
203        """
204        parameters = self.space_to_parameters(space_action)
205        if parameters is None:
206            return None, None
207        return self.execute(**parameters)
208
209    @staticmethod
210    @abstractmethod
211    def get_action_name(**kwargs) -> str:
212        """
213        Returns a human readable name for the high level action with the given parameters.
214
215        :param kwargs: The high level action's parameters.
216        :type kwargs: Dict[str, Any]
217        :return: A human readable name for the high level action.
218        :rtype: str
219        """
220        raise NotImplementedError

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)

REQUIRED_STATE_TRACKER = <class 'gameboy_worlds.emulation.tracker.StateTracker'>

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

REQUIRED_STATE_PARSER = <class 'gameboy_worlds.emulation.parser.StateParser'>

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

def seed(self, seed: Optional[int] = None):
43    def seed(self, seed: Optional[int] = None):
44        """
45        Sets the random seed for the high level action.
46        Args:
47            seed (int): The random seed to set.
48        """
49        self._rng = np.random.default_rng(seed)

Sets the random seed for the high level action.

Arguments:
  • seed (int): The random seed to set.
def assign_emulator(self, emulator: gameboy_worlds.emulation.emulator.Emulator):
51    def assign_emulator(self, emulator: Emulator):
52        """
53        Sets a reference to the emulator instance.
54        Args:
55            emulator (Emulator): The emulator instance to be tracked.
56        """
57        if not isinstance(emulator, Emulator):
58            log_error(
59                f"HighLevelAction requires an Emulator instance, but got {type(emulator)}",
60                self._parameters,
61            )
62        self._emulator = emulator
63        self._state_tracker = emulator.state_tracker
64        if not issubclass(type(self._state_tracker), self.REQUIRED_STATE_TRACKER):
65            log_error(
66                f"HighLevelAction requires a StateTracker of type {self.REQUIRED_STATE_TRACKER}, but got {type(self._state_tracker)}",
67                self._parameters,
68            )
69        if not issubclass(type(emulator.state_parser), self.REQUIRED_STATE_PARSER):
70            log_error(
71                f"HighLevelAction requires a StateParser of type {self.REQUIRED_STATE_PARSER}, but got {type(emulator.state_parser)}",
72                self._parameters,
73            )

Sets a reference to the emulator instance.

Arguments:
  • emulator (Emulator): The emulator instance to be tracked.
def unassign_emulator(self):
75    def unassign_emulator(self):
76        """
77        Clears the reference to the emulator instance.
78        """
79        self._emulator = None
80        self._state_tracker = None

Clears the reference to the emulator instance.

@abstractmethod
def get_action_space(self) -> gymnasium.spaces.space.Space:
82    @abstractmethod
83    def get_action_space(self) -> Space:
84        """
85        Returns the Gym defined Space that characterizes the high level action's parameter space.
86
87        You can use this API to get a Space for sampling high level actions of this type.
88
89        Returns:
90            Space: The Gym space that characterizes the high level action's parameter space.
91        """
92        raise NotImplementedError

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.

@abstractmethod
def space_to_parameters( self, space_action: gymnasium.spaces.space.Space) -> Optional[Dict[str, Any]]:
 94    @abstractmethod
 95    def space_to_parameters(self, space_action: Space) -> Optional[Dict[str, Any]]:
 96        """
 97        Converts a Gym space action into high level action parameters.
 98        If the provided space action is invalid, return None.
 99
100        :param space_action: The action in the high level action's parameter space.
101        :type space_action: Space
102        :return: The high level action's parameters corresponding to the space action.
103        :rtype: Optional[Dict[str, Any]]
104        """
105        raise NotImplementedError

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.

@abstractmethod
def parameters_to_space(self, **kwargs) -> Optional[gymnasium.spaces.space.Space]:
107    @abstractmethod
108    def parameters_to_space(self, **kwargs) -> Optional[Space]:
109        """
110        Converts high level action parameters into a Gym space action.
111        If the provided parameters are invalid, return None.
112
113        :param kwargs: The high level action's parameters.
114        :type kwargs: Dict[str, Any]
115        :return: The action in the high level action's parameter space.
116        :rtype: Optional[Space]
117        """
118        raise NotImplementedError

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.

@abstractmethod
def is_valid(self, **kwargs) -> bool:
120    @abstractmethod
121    def is_valid(self, **kwargs) -> bool:
122        """
123        Checks if the high level action can be performed in the current state.
124        If kwargs is empty, then must check whether there exists any valid way to perform the action.
125
126        Args:
127            **kwargs: Additional arguments required for the specific high level action.
128        Returns:
129            bool: Whether the action is valid in the current state.
130        """
131        raise NotImplementedError

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.

def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
149    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
150        """
151        Returns a list of all valid parameterizations for the high level action in the current state.
152
153        May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)
154
155        Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.
156
157
158        Returns:
159
160            List[Dict[str, Any]]: A list of valid parameterizations for the high level action.
161        """
162        raise ValueError(
163            "This high level action does not implement get_all_valid_parameters(). Most likely, it is not possible to enumerate an exhaustive list of all valid inputs. Use is_valid() instead. See documentation for more details. If you believe this is an error, please implement get_all_valid_parameters() in the high level action subclass."
164        )

Returns a list of all valid parameterizations for the high level action in the current state.

May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)

Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.

Returns:

List[Dict[str, Any]]: A list of valid parameterizations for the high level action.

def execute( self, **kwargs) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
166    def execute(
167        self, **kwargs
168    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
169        """
170        Executes the specified high level action on the emulator after checking for validity.
171
172        :param kwargs: Additional arguments required for the specific high level action.
173        :return: None, None if the action is not valid. Otherwise:
174
175            - A list of state tracker reports after each low level action executed.
176
177            - Action success status.
178        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
179        """
180        if self._emulator is None:
181            log_error(
182                f"Tried to execute action on HighLevelAction without an emulator",
183                self._parameters,
184            )
185        if not self.is_valid(**kwargs):
186            return None, None
187        return self._execute(**kwargs)

Executes the specified high level action on the emulator after checking for validity.

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

None, None if the action is not valid. Otherwise:

- A list of state tracker reports after each low level action executed.

- Action success status.
def execute_space_action( self, space_action: gymnasium.spaces.space.Space) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
189    def execute_space_action(
190        self, space_action: Space
191    ) -> Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]:
192        """
193        Executes the specified high level action on the emulator after checking for validity.
194
195        :param space_action: The action in the high level action's parameter space.
196        :type space_action: Space
197        :return: None, None if the action is not valid. Otherwise:
198
199            - A list of state tracker reports after each low level action executed.
200
201            - Action success status.
202        :rtype: Tuple[Optional[List[Dict[str, Dict[str, Any]]]], Optional[int]]
203        """
204        parameters = self.space_to_parameters(space_action)
205        if parameters is None:
206            return None, None
207        return self.execute(**parameters)

Executes the specified high level action on the emulator after checking for validity.

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

None, None if the action is not valid. Otherwise:

- A list of state tracker reports after each low level action executed.

- Action success status.
@staticmethod
@abstractmethod
def get_action_name(**kwargs) -> str:
209    @staticmethod
210    @abstractmethod
211    def get_action_name(**kwargs) -> str:
212        """
213        Returns a human readable name for the high level action with the given parameters.
214
215        :param kwargs: The high level action's parameters.
216        :type kwargs: Dict[str, Any]
217        :return: A human readable name for the high level action.
218        :rtype: str
219        """
220        raise NotImplementedError

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 SingleHighLevelAction(HighLevelAction):
223class SingleHighLevelAction(HighLevelAction):
224    """
225    An abstract class for a high level action that has only one possible parameterization.
226    """
227
228    def space_to_parameters(self, space_action):
229        return {}
230
231    def parameters_to_space(self):
232        return 0
233
234    def get_action_space(self):
235        return Discrete(1)
236
237    def get_all_valid_parameters(self):
238        if self.is_valid():
239            return [{}]
240        else:
241            return []

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

def space_to_parameters(self, space_action):
228    def space_to_parameters(self, space_action):
229        return {}

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):
231    def parameters_to_space(self):
232        return 0

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 get_action_space(self):
234    def get_action_space(self):
235        return Discrete(1)

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 get_all_valid_parameters(self):
237    def get_all_valid_parameters(self):
238        if self.is_valid():
239            return [{}]
240        else:
241            return []

Returns a list of all valid parameterizations for the high level action in the current state.

May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)

Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.

Returns:

List[Dict[str, Any]]: A list of valid parameterizations for the high level action.

class LowLevelAction(HighLevelAction):
244class LowLevelAction(HighLevelAction):
245    """A high level action that directly maps to a single low level action."""
246
247    def get_action_space(self):
248        """
249        Returns the Gym defined Space that characterizes the low level action's parameter space.
250        """
251        return Discrete(len(LowLevelActions))
252
253    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
254        action = list(LowLevelActions)[space_action]
255        return {"low_level_action": action}
256
257    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
258        if low_level_action is None or not isinstance(
259            low_level_action, LowLevelActions
260        ):
261            # log_warn(
262            #    "LowLevelAction requires a 'low_level_action' parameter of type LowLevelActions.",
263            #    self._parameters,
264            # )
265            return None
266        return low_level_action.value
267
268    def _execute(
269        self, low_level_action: LowLevelActions
270    ) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
271        self._emulator.step(low_level_action)
272        state_report = self._state_tracker.report()
273        return [
274            state_report
275        ], 0  # Low level actions are always successful in this context.
276
277    def is_valid(self, low_level_action: LowLevelActions) -> bool:
278        """
279        Checks if the low level action can be performed in the current state.
280
281        Args:
282            low_level_action (LowLevelActions): The low level action to check.
283        Returns:
284            bool: Whether the action is valid in the current state.
285        """
286        return True
287
288    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
289        """
290        Returns a list of all valid low level actions in the current state.
291
292        :return: A list of valid low level actions.
293        :rtype: List[Dict[str, Any]]
294        """
295        return [{"low_level_action": action} for action in LowLevelActions]
296
297    @staticmethod
298    def get_action_name(low_level_action: LowLevelActions) -> str:
299        action = (
300            str(low_level_action)
301            .replace("LowLevelActions.PRESS_ARROW_", "")
302            .replace("LowLevelActions.PRESS_BUTTON_", "")
303        )
304        return action

A high level action that directly maps to a single low level action.

def get_action_space(self):
247    def get_action_space(self):
248        """
249        Returns the Gym defined Space that characterizes the low level action's parameter space.
250        """
251        return Discrete(len(LowLevelActions))

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

def space_to_parameters(self, space_action: gymnasium.spaces.space.Space) -> Dict[str, Any]:
253    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
254        action = list(LowLevelActions)[space_action]
255        return {"low_level_action": 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, low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> gymnasium.spaces.space.Space:
257    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
258        if low_level_action is None or not isinstance(
259            low_level_action, LowLevelActions
260        ):
261            # log_warn(
262            #    "LowLevelAction requires a 'low_level_action' parameter of type LowLevelActions.",
263            #    self._parameters,
264            # )
265            return None
266        return low_level_action.value

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, low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> bool:
277    def is_valid(self, low_level_action: LowLevelActions) -> bool:
278        """
279        Checks if the low level action can be performed in the current state.
280
281        Args:
282            low_level_action (LowLevelActions): The low level action to check.
283        Returns:
284            bool: Whether the action is valid in the current state.
285        """
286        return True

Checks if the low level action can be performed in the current state.

Arguments:
  • low_level_action (LowLevelActions): The low level action to check.
Returns:

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

def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
288    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
289        """
290        Returns a list of all valid low level actions in the current state.
291
292        :return: A list of valid low level actions.
293        :rtype: List[Dict[str, Any]]
294        """
295        return [{"low_level_action": action} for action in LowLevelActions]

Returns a list of all valid low level actions in the current state.

Returns

A list of valid low level actions.

@staticmethod
def get_action_name( low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> str:
297    @staticmethod
298    def get_action_name(low_level_action: LowLevelActions) -> str:
299        action = (
300            str(low_level_action)
301            .replace("LowLevelActions.PRESS_ARROW_", "")
302            .replace("LowLevelActions.PRESS_BUTTON_", "")
303        )
304        return 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 LowLevelPlayAction(HighLevelAction):
307class LowLevelPlayAction(HighLevelAction):
308    """A HighLevelAction subclass that directly maps to low level actions, except no menu button presses."""
309
310    def __init__(self, parameters: dict, seed: Optional[int] = None):
311        self.allowed_actions = [
312            LowLevelActions.PRESS_ARROW_UP,
313            LowLevelActions.PRESS_ARROW_DOWN,
314            LowLevelActions.PRESS_ARROW_RIGHT,
315            LowLevelActions.PRESS_ARROW_LEFT,
316            LowLevelActions.PRESS_BUTTON_A,
317            LowLevelActions.PRESS_BUTTON_B,
318        ]
319        super().__init__(parameters, seed=seed)
320
321    def get_action_space(self):
322        """
323        Returns the Gym defined Space that characterizes the low level play action's parameter space.
324        """
325        return Discrete(len(self.allowed_actions))
326
327    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
328        action = self.allowed_actions[space_action]
329        return {"low_level_action": action}
330
331    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
332        if low_level_action is None or low_level_action not in self.allowed_actions:
333            # log_warn(
334            #    "LowLevelPlayAction requires a 'low_level_action' parameter that is not a menu button press.",
335            #    self._parameters,
336            # )
337            return None
338        return self.allowed_actions.index(low_level_action)
339
340    def _execute(
341        self, low_level_action: LowLevelActions
342    ) -> Tuple[List[Dict[str, Dict[str, Any]]], int]:
343        self._emulator.step(low_level_action)
344        state_report = self._state_tracker.report()
345        return [
346            state_report
347        ], 0  # Low level actions are always successful in this context.
348
349    def is_valid(self, low_level_action: LowLevelActions) -> bool:
350        return low_level_action in self.allowed_actions
351
352    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
353        return [{"low_level_action": action} for action in self.allowed_actions]
354
355    @staticmethod
356    def get_action_name(low_level_action: LowLevelActions) -> str:
357        action = (
358            str(low_level_action)
359            .replace("LowLevelActions.PRESS_ARROW_", "")
360            .replace("LowLevelActions.PRESS_BUTTON_", "")
361        )
362        return action

A HighLevelAction subclass that directly maps to low level actions, except no menu button presses.

LowLevelPlayAction(parameters: dict, seed: Optional[int] = None)
310    def __init__(self, parameters: dict, seed: Optional[int] = None):
311        self.allowed_actions = [
312            LowLevelActions.PRESS_ARROW_UP,
313            LowLevelActions.PRESS_ARROW_DOWN,
314            LowLevelActions.PRESS_ARROW_RIGHT,
315            LowLevelActions.PRESS_ARROW_LEFT,
316            LowLevelActions.PRESS_BUTTON_A,
317            LowLevelActions.PRESS_BUTTON_B,
318        ]
319        super().__init__(parameters, seed=seed)
allowed_actions
def get_action_space(self):
321    def get_action_space(self):
322        """
323        Returns the Gym defined Space that characterizes the low level play action's parameter space.
324        """
325        return Discrete(len(self.allowed_actions))

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

def space_to_parameters(self, space_action: gymnasium.spaces.space.Space) -> Dict[str, Any]:
327    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
328        action = self.allowed_actions[space_action]
329        return {"low_level_action": 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, low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> gymnasium.spaces.space.Space:
331    def parameters_to_space(self, low_level_action: LowLevelActions) -> Space:
332        if low_level_action is None or low_level_action not in self.allowed_actions:
333            # log_warn(
334            #    "LowLevelPlayAction requires a 'low_level_action' parameter that is not a menu button press.",
335            #    self._parameters,
336            # )
337            return None
338        return self.allowed_actions.index(low_level_action)

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, low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> bool:
349    def is_valid(self, low_level_action: LowLevelActions) -> bool:
350        return low_level_action in self.allowed_actions

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.

def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
352    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
353        return [{"low_level_action": action} for action in self.allowed_actions]

Returns a list of all valid parameterizations for the high level action in the current state.

May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)

Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.

Returns:

List[Dict[str, Any]]: A list of valid parameterizations for the high level action.

@staticmethod
def get_action_name( low_level_action: gameboy_worlds.emulation.emulator.LowLevelActions) -> str:
355    @staticmethod
356    def get_action_name(low_level_action: LowLevelActions) -> str:
357        action = (
358            str(low_level_action)
359            .replace("LowLevelActions.PRESS_ARROW_", "")
360            .replace("LowLevelActions.PRESS_BUTTON_", "")
361        )
362        return 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 RandomPlayAction(HighLevelAction):
365class RandomPlayAction(HighLevelAction):
366    """Execution either moves or presses A
367
368    Action Success Interpretation:
369
370    - 0: The frame changed after the action
371    - 1: The frame did not change after the action
372
373    """
374
375    def get_action_space(self):
376        """
377        Returns the Gym defined Space that characterizes the random play action's parameter space.
378        """
379        return Discrete(2)  # 0 for move, 1 for press A
380
381    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
382        if space_action == 0:
383            return {"kind": "move"}
384        else:
385            return {"kind": "press"}
386
387    def parameters_to_space(self, kind: str) -> Space:
388        if kind == "move":
389            return 0
390        elif kind == "press":
391            return 1
392        else:
393            # log_warn(
394            #    "RandomPlayAction requires a 'kind' parameter of either 'move' or 'press'.",
395            #    self._parameters,
396            # )
397            return None
398
399    def _execute(self, kind: str):
400        if kind == "move":
401            actions = [
402                LowLevelActions.PRESS_ARROW_DOWN,
403                LowLevelActions.PRESS_ARROW_LEFT,
404                LowLevelActions.PRESS_ARROW_RIGHT,
405                LowLevelActions.PRESS_ARROW_UP,
406            ]
407
408        else:  # kind must be 'press'. Enforced in is_valid
409            actions = [
410                LowLevelActions.PRESS_BUTTON_A,
411            ]
412        action = self._rng.choice(actions)
413        self._emulator.step(action)
414        state_report = self._state_tracker.report()
415        not_success = not state_report["core"][
416            "frame_changed"
417        ]  # Whether the frame changed after the action
418        return [state_report], not_success
419
420    def is_valid(self, kind: str) -> bool:
421        if kind not in ["move", "press"]:
422            return False
423        return True
424
425    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
426        return [{"kind": "move"}, {"kind": "press"}]
427
428    @staticmethod
429    def get_action_name(kind: str) -> str:
430        return kind

Execution either moves or presses A

Action Success Interpretation:

  • 0: The frame changed after the action
  • 1: The frame did not change after the action
def get_action_space(self):
375    def get_action_space(self):
376        """
377        Returns the Gym defined Space that characterizes the random play action's parameter space.
378        """
379        return Discrete(2)  # 0 for move, 1 for press A

Returns the Gym defined Space that characterizes the random play action's parameter space.

def space_to_parameters(self, space_action: gymnasium.spaces.space.Space) -> Dict[str, Any]:
381    def space_to_parameters(self, space_action: Space) -> Dict[str, Any]:
382        if space_action == 0:
383            return {"kind": "move"}
384        else:
385            return {"kind": "press"}

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, kind: str) -> gymnasium.spaces.space.Space:
387    def parameters_to_space(self, kind: str) -> Space:
388        if kind == "move":
389            return 0
390        elif kind == "press":
391            return 1
392        else:
393            # log_warn(
394            #    "RandomPlayAction requires a 'kind' parameter of either 'move' or 'press'.",
395            #    self._parameters,
396            # )
397            return 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, kind: str) -> bool:
420    def is_valid(self, kind: str) -> bool:
421        if kind not in ["move", "press"]:
422            return False
423        return True

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.

def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
425    def get_all_valid_parameters(self) -> List[Dict[str, Any]]:
426        return [{"kind": "move"}, {"kind": "press"}]

Returns a list of all valid parameterizations for the high level action in the current state.

May not well defined for all high level actions, because some high level actions may have infinite parameterizations. (e.g. move to any (x, y) position.)

Use this to enumerate all valid ways to perform the action, and provide a way to sample over all valid parameterizations.

Returns:

List[Dict[str, Any]]: A list of valid parameterizations for the high level action.

@staticmethod
def get_action_name(kind: str) -> str:
428    @staticmethod
429    def get_action_name(kind: str) -> str:
430        return kind

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.