gameboy_worlds.utils

  1# These are all the utils functions or classes that you may want to import in your project
  2from gameboy_worlds.utils.parameter_handling import load_parameters
  3from gameboy_worlds.utils.log_handling import log_error, log_info, log_warn, log_dict
  4from gameboy_worlds.utils.fundamental import file_makedir, check_optional_installs
  5from pandas import isna
  6from typing import Type, List
  7import numpy as np
  8import os
  9import matplotlib.pyplot as plt
 10import pandas as pd
 11from time import perf_counter_ns
 12from typing import Optional, List, Dict
 13
 14
 15def import_cv2(parameters: dict = None):
 16    """
 17    Import cv2 lazily so headless runs without video writing do not load extra SDL libraries.
 18    """
 19    try:
 20        import cv2
 21    except ImportError:
 22        log_error(
 23            "OpenCV (cv2) is required for emulator video recording utilities.",
 24            parameters,
 25        )
 26    return cv2
 27
 28
 29def import_pygame(parameters: dict):
 30    """
 31    Import pygame lazily so headless runs don't pull in SDL unless rendering is requested.
 32    """
 33    try:
 34        import pygame
 35    except ImportError:
 36        log_error(
 37            "pygame is required for render() / human play display. Install pygame or run without render.",
 38            parameters,
 39        )
 40    return pygame
 41
 42
 43def is_none_str(s) -> bool:
 44    """
 45    Checks if a string is None or represents a null value.
 46
 47    Args:
 48        s (str or None): The string to check.
 49
 50    Returns:
 51        bool: True if the string is None or represents a null value, False otherwise.
 52    """
 53    if s is None:
 54        return True
 55    if isinstance(s, str):
 56        options = ["none", "null", "nan", ""]
 57        for option in options:
 58            if s.lower() == option:
 59                return True
 60    return isna(s)
 61
 62
 63def nested_dict_to_str(
 64    nested_dict: dict, *, indent: int = 0, indent_char: str = "  "
 65) -> str:
 66    """
 67    Converts a nested dictionary to a formatted string representation.
 68    Example Usage:
 69    ```python
 70    nested_dict={2: 4, 3: {4: 5, 6: {7: 8}}}
 71    print(nested_dict_to_str(nested_dict))
 72    2: 4
 73    3: Dict:
 74      4: 5
 75      6: Dict:
 76        7: 8
 77    ```
 78
 79    Args:
 80        nested_dict (dict): The nested dictionary to convert.
 81        indent (int): The current indentation level.
 82        indent_char (str): The character(s) used for indentation.
 83    Returns:
 84        str: A formatted string representation of the nested dictionary.
 85
 86    """
 87    result = ""
 88    for key, value in nested_dict.items():
 89        result += indent_char * indent + str(key) + ": "
 90        if isinstance(value, dict):
 91            result += "Dict: \n" + nested_dict_to_str(
 92                value, indent + 1, indent_char=indent_char
 93            )
 94        else:
 95            result += str(value) + "\n"
 96    return result
 97
 98
 99def verify_parameters(parameters: dict):
100    """
101    Does a basic sanity check to ensure parameters is a non-empty dictionary.
102    """
103    if parameters is None:
104        raise ValueError("Parameters cannot be None.")
105    if not isinstance(parameters, dict):
106        raise ValueError("Parameters must be a dictionary.")
107    if len(parameters) == 0:
108        raise ValueError("Parameters dictionary cannot be empty.")
109
110
111def get_lowest_level_subclass(class_list: List[Type]) -> Type:
112    """
113    Given a list of classes, returns the class that is the lowest level subclass in the inheritance hierarchy.
114    """
115    lowest_level_tracker = None
116    for cls in class_list:
117        if lowest_level_tracker is None:
118            lowest_level_tracker = cls
119        elif issubclass(cls, lowest_level_tracker):
120            lowest_level_tracker = cls
121    return lowest_level_tracker
122
123
124def show_frames(
125    frames: np.ndarray, titles: List[str] = None, save=False, parameters: dict = None
126):
127    """
128    Plots each frame as an image in matplotlib. If save is true, will save each frame as title.png in the frame_saves/ directory.
129    titles length must be equal to frame length if specified.
130    """
131    parameters = load_parameters(parameters)
132    if isinstance(frames, list):
133        for i in range(len(frames)):
134            if frames[i].ndim == 2:
135                frames[i] = np.expand_dims(frames[i], axis=-1)
136    else:
137        if not isinstance(frames, np.ndarray):
138            log_error(
139                f"Frames must be a numpy array or list of numpy arrays, but got {type(frames)}",
140                parameters,
141            )
142        if frames.ndim == 2:
143            frames = [np.expand_dims(frames, axis=-1)]
144        elif frames.ndim == 3:
145            # now either we have (num_frames, height, width) or (height, width, channels)
146            if frames.shape[2] == 1:
147                frames = [frames]
148            else:
149                frames = [
150                    np.expand_dims(frames[i], axis=-1) for i in range(frames.shape[0])
151                ]
152    if isinstance(titles, str):
153        titles = [titles]
154    if save:
155        if titles is None:
156            log_error(f"Cannot save frames without titles specified.", parameters)
157    if titles is not None:
158        if len(titles) == 1 and len(frames) > 1:
159            titles = [titles[0] + f"_{i}" for i in range(len(frames))]
160    if titles is not None and len(titles) != len(frames):
161        log_error(
162            f"Length of titles {len(titles)} does not match number of frames {len(frames)}",
163            parameters,
164        )
165    save_dir = "frame_saves/"
166    os.makedirs(save_dir, exist_ok=True)
167
168    for i in range(len(frames)):
169        plt.imshow(frames[i])
170        if titles is not None:
171            plt.title(titles[i])
172        if save:
173            filename = os.path.join(
174                save_dir, titles[i].replace(" ", "_").replace("/", "_") + ".png"
175            )
176            plt.imsave(filename, frames[i][:, :, 0], cmap="gray")
177        else:
178            plt.show()
179
180
181def get_train_games(game: str, parameters: dict = None) -> List[str]:
182    """
183    Returns a list of games that can be used for training the specified game variant.
184
185    Args:
186        game (str): The variant of the game to get training games for.
187        parameters (dict, optional): Additional parameters for error logging.
188    """
189    parameters = load_parameters(parameters)
190    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=True)
191    training_rows = tasks_df[tasks_df["can_train_from_init_state"] == True]
192    if len(training_rows) == 0:
193        log_error(
194            f"No training games specified for game variant '{game}' in benchmark tasks. This is an error.",
195            parameters,
196        )
197    training_games = list(set(training_rows["game"].tolist()))
198    return training_games
199
200
201def get_benchmark_tasks_dfs(parameters: dict = None) -> dict[str, pd.DataFrame]:
202    """
203    Loads the benchmark tasks from the benchmark/tests/tasks.csv file
204
205    Args:
206        parameters (dict, optional): Additional parameters for error logging.
207    Returns:
208        dict[str, pd.DataFrame]: A dictionary mapping game names to their corresponding DataFrames containing the benchmark tasks.
209    """
210    parameters = load_parameters(parameters)
211    project_root = parameters["project_root"]
212    module_paths = os.listdir(project_root + "/benchmark/tests/")
213    benchmark_dfs = {}
214    for module_path in module_paths:
215        if module_path.endswith(".csv"):
216            tasks_filepath = os.path.join(project_root, "benchmark/tests", module_path)
217            benchmark_name = module_path.strip(".csv")
218            benchmark_dfs[benchmark_name] = pd.read_csv(tasks_filepath)
219    return benchmark_dfs
220
221
222def get_benchmark_tasks(
223    game: str, parameters: dict = None, shifted_included: bool = False
224) -> pd.DataFrame:
225    """
226    Loads the benchmark tasks for the specified game from the benchmark/tests/tasks.csv file
227
228    Args:
229        game (str): The variant of the game to get benchmark tasks for.
230        parameters (dict, optional): Additional parameters for error logging.
231        shifted_included (bool, optional): Whether to include task rows from other titles in the same series as part of the benchmark tasks.
232
233    Returns:
234        pd.DataFrame: DataFrame containing the benchmark tasks for the specified game.
235    """
236    parameters = load_parameters(parameters)
237    tasks_dfs = get_benchmark_tasks_dfs(parameters)
238    available_games = set()
239    task_df = None
240    for benchmark_name, df in tasks_dfs.items():
241        available_games.update(set(df["game"].unique()))
242        if game in df["game"].unique():
243            if task_df is not None:
244                log_error(
245                    f"Multiple benchmark modules contain tasks for game variant '{game}'. Please ensure that only one benchmark CSV file contains tasks for this game.",
246                    parameters,
247                )
248            task_df = df
249
250    if game not in available_games:
251        log_error(
252            f"Game variant '{game}' not found in benchmark tasks. Available game variants: {available_games}",
253            parameters=parameters,
254        )
255    if not shifted_included:
256        game_tasks_df = task_df[task_df["game"] == game].reset_index(drop=True)
257    else:
258        game_tasks_df = task_df
259    return game_tasks_df
260
261
262def get_training_states(game: str, parameters: dict = None) -> Optional[List[str]]:
263    """
264    Loads the training states for the specified game from the benchmark/tasks.csv file
265
266    Args:
267        game (str): The variant of the game to get training states for.
268        parameters (dict, optional): Additional parameters for error logging.
269
270    Returns:
271        Optional[List[str]]: A list of training states for the specified game, or None if no training states are specified.
272    """
273    parameters = load_parameters(parameters)
274    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=False)
275    training_rows = tasks_df[tasks_df["can_train_from_init_state"] == True]
276    if len(training_rows) == 0:
277        log_warn(
278            f"No training states specified for game variant '{game}' in benchmark tasks.",
279            parameters,
280        )
281        return None
282    training_states = list(set(training_rows["init_state"].tolist()))
283    return training_states
284
285
286def get_shifted_training_states(
287    game: str, parameters: dict = None
288) -> Dict[str, List[str]]:
289    """
290    Loads the shifted training states for the specified game from the benchmark/tasks.csv file.
291
292    This ends up being all available training states from every other title of the same series. So if you ask for pokemon_red's shifted states, you will get pokemon_crystal's training states.
293
294    Args:
295        game (str): The variant of the game to get shifted training states for.
296        parameters (dict, optional): Additional parameters for error logging.
297    Returns:
298        Dict[str, List[str]]: A dictionary of lists of shifted training states for the specified game. Each entry is in format {game: [training_states]}, where game is a different title in the same series as the input game.
299    """
300    parameters = load_parameters(parameters)
301    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=True)
302    other_task_rows = tasks_df[tasks_df["game"] != game].reset_index(drop=True)
303    training_rows = other_task_rows[
304        other_task_rows["can_train_from_init_state"] == True
305    ]
306    if len(training_rows) == 0:
307        log_error(
308            f"No shifted training states specified for game variant '{game}' in benchmark tasks. This shouldn't happen.",
309            parameters,
310        )
311    shifted_training_states = {}
312    for _, row in training_rows.iterrows():
313        other_game = row["game"]
314        if other_game not in shifted_training_states:
315            shifted_training_states[other_game] = []
316        shifted_training_states[other_game].append(row["init_state"])
317    return shifted_training_states
318
319
320def get_all_training_states(parameters: dict = None) -> Dict[str, List[str]]:
321    """
322    Gets all regular training states for the all games (for which training states are specified).
323
324    Args:
325        parameters (dict, optional): Additional parameters for error logging.
326
327    Returns:
328        Dict[str, List[str]]: A dictionary containing {game: training_states} entries for all games for which training states are specified in the benchmark tasks.
329    """
330    parameters = load_parameters(parameters)
331    tasks_dfs = get_benchmark_tasks_dfs(parameters)
332    all_games = set()
333    for df in tasks_dfs.values():
334        all_games.update(df["game"].unique())
335    all_training_states = {}
336    for game in all_games:
337        training_states = get_training_states(game, parameters)
338        if training_states is not None:
339            all_training_states[game] = training_states
340    return all_training_states
341
342
343def get_all_shifted_training_states(
344    parameters: dict = None,
345) -> Dict[str, Dict[str, List[str]]]:
346    """
347    Gets all shifted training states for all games.
348
349    Args:
350        parameters (dict, optional): Additional parameters for error logging.
351
352    Returns:
353        Dict[str, Dict[str, List[str]]]: A nested dictionary containing {game: {other_game: shifted_training_states}} entries for all games for which shifted training states are specified in the benchmark tasks.
354    """
355    parameters = load_parameters(parameters)
356    tasks_dfs = get_benchmark_tasks_dfs(parameters)
357    all_games = set()
358    for df in tasks_dfs.values():
359        all_games.update(df["game"].unique())
360    all_shifted_training_states = {}
361    for game in all_games:
362        shifted_states = get_shifted_training_states(game, parameters)
363        all_shifted_training_states[game] = shifted_states
364    return all_shifted_training_states
365
366
367class _Profiler:
368    """
369    A simple profiler class to track the time taken by different events in the code. It can also group events together and show the percentage of time taken by each event in the group.
370    """
371
372    LOG_EVENTS = False
373    last_event_time = None
374    last_event = None
375    group = None
376    group_name = None
377
378    @staticmethod
379    def event(event_name):
380        current_time = perf_counter_ns()
381        if _Profiler.last_event_time is not None:
382            elapsed_time = current_time - _Profiler.last_event_time
383            if _Profiler.LOG_EVENTS:
384                log_info(
385                    f"{_Profiler.last_event} -> {event_name}: {elapsed_time / 1e6:.2f} ms",
386                )
387        _Profiler.last_event_time = current_time
388        _Profiler.last_event = event_name
389        if _Profiler.group_name is not None:
390            _Profiler.group.append((event_name, current_time))
391
392    @staticmethod
393    def start_group(group_name):
394        if _Profiler.group_name is not None:
395            fractions = []
396            total_time = 0
397            for i in range(1, len(_Profiler.group)):
398                event_name, event_time = _Profiler.group[i]
399                prev_event_name, prev_event_time = _Profiler.group[i - 1]
400                elapsed_time = event_time - prev_event_time
401                total_time += elapsed_time
402            for i in range(1, len(_Profiler.group)):
403                event_name, event_time = _Profiler.group[i]
404                prev_event_name, prev_event_time = _Profiler.group[i - 1]
405                elapsed_time = event_time - prev_event_time
406                fractions.append(elapsed_time / total_time if total_time > 0 else 0)
407                log_info(
408                    f"{_Profiler.group_name} - {prev_event_name} -> {event_name}: {elapsed_time / 1e6:.2f} ms ({fractions[-1]*100:.2f}%)",
409                )
410
411        _Profiler.group_name = group_name
412        _Profiler.group = []
413
414    @staticmethod
415    def close_group():
416        _Profiler.start_group(None)
def import_cv2(parameters: dict = None):
16def import_cv2(parameters: dict = None):
17    """
18    Import cv2 lazily so headless runs without video writing do not load extra SDL libraries.
19    """
20    try:
21        import cv2
22    except ImportError:
23        log_error(
24            "OpenCV (cv2) is required for emulator video recording utilities.",
25            parameters,
26        )
27    return cv2

Import cv2 lazily so headless runs without video writing do not load extra SDL libraries.

def import_pygame(parameters: dict):
30def import_pygame(parameters: dict):
31    """
32    Import pygame lazily so headless runs don't pull in SDL unless rendering is requested.
33    """
34    try:
35        import pygame
36    except ImportError:
37        log_error(
38            "pygame is required for render() / human play display. Install pygame or run without render.",
39            parameters,
40        )
41    return pygame

Import pygame lazily so headless runs don't pull in SDL unless rendering is requested.

def is_none_str(s) -> bool:
44def is_none_str(s) -> bool:
45    """
46    Checks if a string is None or represents a null value.
47
48    Args:
49        s (str or None): The string to check.
50
51    Returns:
52        bool: True if the string is None or represents a null value, False otherwise.
53    """
54    if s is None:
55        return True
56    if isinstance(s, str):
57        options = ["none", "null", "nan", ""]
58        for option in options:
59            if s.lower() == option:
60                return True
61    return isna(s)

Checks if a string is None or represents a null value.

Arguments:
  • s (str or None): The string to check.
Returns:

bool: True if the string is None or represents a null value, False otherwise.

def nested_dict_to_str(nested_dict: dict, *, indent: int = 0, indent_char: str = ' ') -> str:
64def nested_dict_to_str(
65    nested_dict: dict, *, indent: int = 0, indent_char: str = "  "
66) -> str:
67    """
68    Converts a nested dictionary to a formatted string representation.
69    Example Usage:
70    ```python
71    nested_dict={2: 4, 3: {4: 5, 6: {7: 8}}}
72    print(nested_dict_to_str(nested_dict))
73    2: 4
74    3: Dict:
75      4: 5
76      6: Dict:
77        7: 8
78    ```
79
80    Args:
81        nested_dict (dict): The nested dictionary to convert.
82        indent (int): The current indentation level.
83        indent_char (str): The character(s) used for indentation.
84    Returns:
85        str: A formatted string representation of the nested dictionary.
86
87    """
88    result = ""
89    for key, value in nested_dict.items():
90        result += indent_char * indent + str(key) + ": "
91        if isinstance(value, dict):
92            result += "Dict: \n" + nested_dict_to_str(
93                value, indent + 1, indent_char=indent_char
94            )
95        else:
96            result += str(value) + "\n"
97    return result

Converts a nested dictionary to a formatted string representation. Example Usage:

nested_dict={2: 4, 3: {4: 5, 6: {7: 8}}}
print(nested_dict_to_str(nested_dict))
2: 4
3: Dict:
  4: 5
  6: Dict:
    7: 8
Arguments:
  • nested_dict (dict): The nested dictionary to convert.
  • indent (int): The current indentation level.
  • indent_char (str): The character(s) used for indentation.
Returns:

str: A formatted string representation of the nested dictionary.

def verify_parameters(parameters: dict):
100def verify_parameters(parameters: dict):
101    """
102    Does a basic sanity check to ensure parameters is a non-empty dictionary.
103    """
104    if parameters is None:
105        raise ValueError("Parameters cannot be None.")
106    if not isinstance(parameters, dict):
107        raise ValueError("Parameters must be a dictionary.")
108    if len(parameters) == 0:
109        raise ValueError("Parameters dictionary cannot be empty.")

Does a basic sanity check to ensure parameters is a non-empty dictionary.

def get_lowest_level_subclass(class_list: List[Type]) -> Type:
112def get_lowest_level_subclass(class_list: List[Type]) -> Type:
113    """
114    Given a list of classes, returns the class that is the lowest level subclass in the inheritance hierarchy.
115    """
116    lowest_level_tracker = None
117    for cls in class_list:
118        if lowest_level_tracker is None:
119            lowest_level_tracker = cls
120        elif issubclass(cls, lowest_level_tracker):
121            lowest_level_tracker = cls
122    return lowest_level_tracker

Given a list of classes, returns the class that is the lowest level subclass in the inheritance hierarchy.

def show_frames( frames: numpy.ndarray, titles: List[str] = None, save=False, parameters: dict = None):
125def show_frames(
126    frames: np.ndarray, titles: List[str] = None, save=False, parameters: dict = None
127):
128    """
129    Plots each frame as an image in matplotlib. If save is true, will save each frame as title.png in the frame_saves/ directory.
130    titles length must be equal to frame length if specified.
131    """
132    parameters = load_parameters(parameters)
133    if isinstance(frames, list):
134        for i in range(len(frames)):
135            if frames[i].ndim == 2:
136                frames[i] = np.expand_dims(frames[i], axis=-1)
137    else:
138        if not isinstance(frames, np.ndarray):
139            log_error(
140                f"Frames must be a numpy array or list of numpy arrays, but got {type(frames)}",
141                parameters,
142            )
143        if frames.ndim == 2:
144            frames = [np.expand_dims(frames, axis=-1)]
145        elif frames.ndim == 3:
146            # now either we have (num_frames, height, width) or (height, width, channels)
147            if frames.shape[2] == 1:
148                frames = [frames]
149            else:
150                frames = [
151                    np.expand_dims(frames[i], axis=-1) for i in range(frames.shape[0])
152                ]
153    if isinstance(titles, str):
154        titles = [titles]
155    if save:
156        if titles is None:
157            log_error(f"Cannot save frames without titles specified.", parameters)
158    if titles is not None:
159        if len(titles) == 1 and len(frames) > 1:
160            titles = [titles[0] + f"_{i}" for i in range(len(frames))]
161    if titles is not None and len(titles) != len(frames):
162        log_error(
163            f"Length of titles {len(titles)} does not match number of frames {len(frames)}",
164            parameters,
165        )
166    save_dir = "frame_saves/"
167    os.makedirs(save_dir, exist_ok=True)
168
169    for i in range(len(frames)):
170        plt.imshow(frames[i])
171        if titles is not None:
172            plt.title(titles[i])
173        if save:
174            filename = os.path.join(
175                save_dir, titles[i].replace(" ", "_").replace("/", "_") + ".png"
176            )
177            plt.imsave(filename, frames[i][:, :, 0], cmap="gray")
178        else:
179            plt.show()

Plots each frame as an image in matplotlib. If save is true, will save each frame as title.png in the frame_saves/ directory. titles length must be equal to frame length if specified.

def get_train_games(game: str, parameters: dict = None) -> List[str]:
182def get_train_games(game: str, parameters: dict = None) -> List[str]:
183    """
184    Returns a list of games that can be used for training the specified game variant.
185
186    Args:
187        game (str): The variant of the game to get training games for.
188        parameters (dict, optional): Additional parameters for error logging.
189    """
190    parameters = load_parameters(parameters)
191    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=True)
192    training_rows = tasks_df[tasks_df["can_train_from_init_state"] == True]
193    if len(training_rows) == 0:
194        log_error(
195            f"No training games specified for game variant '{game}' in benchmark tasks. This is an error.",
196            parameters,
197        )
198    training_games = list(set(training_rows["game"].tolist()))
199    return training_games

Returns a list of games that can be used for training the specified game variant.

Arguments:
  • game (str): The variant of the game to get training games for.
  • parameters (dict, optional): Additional parameters for error logging.
def get_benchmark_tasks_dfs(parameters: dict = None) -> dict[str, pandas.core.frame.DataFrame]:
202def get_benchmark_tasks_dfs(parameters: dict = None) -> dict[str, pd.DataFrame]:
203    """
204    Loads the benchmark tasks from the benchmark/tests/tasks.csv file
205
206    Args:
207        parameters (dict, optional): Additional parameters for error logging.
208    Returns:
209        dict[str, pd.DataFrame]: A dictionary mapping game names to their corresponding DataFrames containing the benchmark tasks.
210    """
211    parameters = load_parameters(parameters)
212    project_root = parameters["project_root"]
213    module_paths = os.listdir(project_root + "/benchmark/tests/")
214    benchmark_dfs = {}
215    for module_path in module_paths:
216        if module_path.endswith(".csv"):
217            tasks_filepath = os.path.join(project_root, "benchmark/tests", module_path)
218            benchmark_name = module_path.strip(".csv")
219            benchmark_dfs[benchmark_name] = pd.read_csv(tasks_filepath)
220    return benchmark_dfs

Loads the benchmark tasks from the benchmark/tests/tasks.csv file

Arguments:
  • parameters (dict, optional): Additional parameters for error logging.
Returns:

dict[str, pd.DataFrame]: A dictionary mapping game names to their corresponding DataFrames containing the benchmark tasks.

def get_benchmark_tasks( game: str, parameters: dict = None, shifted_included: bool = False) -> pandas.core.frame.DataFrame:
223def get_benchmark_tasks(
224    game: str, parameters: dict = None, shifted_included: bool = False
225) -> pd.DataFrame:
226    """
227    Loads the benchmark tasks for the specified game from the benchmark/tests/tasks.csv file
228
229    Args:
230        game (str): The variant of the game to get benchmark tasks for.
231        parameters (dict, optional): Additional parameters for error logging.
232        shifted_included (bool, optional): Whether to include task rows from other titles in the same series as part of the benchmark tasks.
233
234    Returns:
235        pd.DataFrame: DataFrame containing the benchmark tasks for the specified game.
236    """
237    parameters = load_parameters(parameters)
238    tasks_dfs = get_benchmark_tasks_dfs(parameters)
239    available_games = set()
240    task_df = None
241    for benchmark_name, df in tasks_dfs.items():
242        available_games.update(set(df["game"].unique()))
243        if game in df["game"].unique():
244            if task_df is not None:
245                log_error(
246                    f"Multiple benchmark modules contain tasks for game variant '{game}'. Please ensure that only one benchmark CSV file contains tasks for this game.",
247                    parameters,
248                )
249            task_df = df
250
251    if game not in available_games:
252        log_error(
253            f"Game variant '{game}' not found in benchmark tasks. Available game variants: {available_games}",
254            parameters=parameters,
255        )
256    if not shifted_included:
257        game_tasks_df = task_df[task_df["game"] == game].reset_index(drop=True)
258    else:
259        game_tasks_df = task_df
260    return game_tasks_df

Loads the benchmark tasks for the specified game from the benchmark/tests/tasks.csv file

Arguments:
  • game (str): The variant of the game to get benchmark tasks for.
  • parameters (dict, optional): Additional parameters for error logging.
  • shifted_included (bool, optional): Whether to include task rows from other titles in the same series as part of the benchmark tasks.
Returns:

pd.DataFrame: DataFrame containing the benchmark tasks for the specified game.

def get_training_states(game: str, parameters: dict = None) -> Optional[List[str]]:
263def get_training_states(game: str, parameters: dict = None) -> Optional[List[str]]:
264    """
265    Loads the training states for the specified game from the benchmark/tasks.csv file
266
267    Args:
268        game (str): The variant of the game to get training states for.
269        parameters (dict, optional): Additional parameters for error logging.
270
271    Returns:
272        Optional[List[str]]: A list of training states for the specified game, or None if no training states are specified.
273    """
274    parameters = load_parameters(parameters)
275    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=False)
276    training_rows = tasks_df[tasks_df["can_train_from_init_state"] == True]
277    if len(training_rows) == 0:
278        log_warn(
279            f"No training states specified for game variant '{game}' in benchmark tasks.",
280            parameters,
281        )
282        return None
283    training_states = list(set(training_rows["init_state"].tolist()))
284    return training_states

Loads the training states for the specified game from the benchmark/tasks.csv file

Arguments:
  • game (str): The variant of the game to get training states for.
  • parameters (dict, optional): Additional parameters for error logging.
Returns:

Optional[List[str]]: A list of training states for the specified game, or None if no training states are specified.

def get_shifted_training_states(game: str, parameters: dict = None) -> Dict[str, List[str]]:
287def get_shifted_training_states(
288    game: str, parameters: dict = None
289) -> Dict[str, List[str]]:
290    """
291    Loads the shifted training states for the specified game from the benchmark/tasks.csv file.
292
293    This ends up being all available training states from every other title of the same series. So if you ask for pokemon_red's shifted states, you will get pokemon_crystal's training states.
294
295    Args:
296        game (str): The variant of the game to get shifted training states for.
297        parameters (dict, optional): Additional parameters for error logging.
298    Returns:
299        Dict[str, List[str]]: A dictionary of lists of shifted training states for the specified game. Each entry is in format {game: [training_states]}, where game is a different title in the same series as the input game.
300    """
301    parameters = load_parameters(parameters)
302    tasks_df = get_benchmark_tasks(game, parameters, shifted_included=True)
303    other_task_rows = tasks_df[tasks_df["game"] != game].reset_index(drop=True)
304    training_rows = other_task_rows[
305        other_task_rows["can_train_from_init_state"] == True
306    ]
307    if len(training_rows) == 0:
308        log_error(
309            f"No shifted training states specified for game variant '{game}' in benchmark tasks. This shouldn't happen.",
310            parameters,
311        )
312    shifted_training_states = {}
313    for _, row in training_rows.iterrows():
314        other_game = row["game"]
315        if other_game not in shifted_training_states:
316            shifted_training_states[other_game] = []
317        shifted_training_states[other_game].append(row["init_state"])
318    return shifted_training_states

Loads the shifted training states for the specified game from the benchmark/tasks.csv file.

This ends up being all available training states from every other title of the same series. So if you ask for pokemon_red's shifted states, you will get pokemon_crystal's training states.

Arguments:
  • game (str): The variant of the game to get shifted training states for.
  • parameters (dict, optional): Additional parameters for error logging.
Returns:

Dict[str, List[str]]: A dictionary of lists of shifted training states for the specified game. Each entry is in format {game: [training_states]}, where game is a different title in the same series as the input game.

def get_all_training_states(parameters: dict = None) -> Dict[str, List[str]]:
321def get_all_training_states(parameters: dict = None) -> Dict[str, List[str]]:
322    """
323    Gets all regular training states for the all games (for which training states are specified).
324
325    Args:
326        parameters (dict, optional): Additional parameters for error logging.
327
328    Returns:
329        Dict[str, List[str]]: A dictionary containing {game: training_states} entries for all games for which training states are specified in the benchmark tasks.
330    """
331    parameters = load_parameters(parameters)
332    tasks_dfs = get_benchmark_tasks_dfs(parameters)
333    all_games = set()
334    for df in tasks_dfs.values():
335        all_games.update(df["game"].unique())
336    all_training_states = {}
337    for game in all_games:
338        training_states = get_training_states(game, parameters)
339        if training_states is not None:
340            all_training_states[game] = training_states
341    return all_training_states

Gets all regular training states for the all games (for which training states are specified).

Arguments:
  • parameters (dict, optional): Additional parameters for error logging.
Returns:

Dict[str, List[str]]: A dictionary containing {game: training_states} entries for all games for which training states are specified in the benchmark tasks.

def get_all_shifted_training_states(parameters: dict = None) -> Dict[str, Dict[str, List[str]]]:
344def get_all_shifted_training_states(
345    parameters: dict = None,
346) -> Dict[str, Dict[str, List[str]]]:
347    """
348    Gets all shifted training states for all games.
349
350    Args:
351        parameters (dict, optional): Additional parameters for error logging.
352
353    Returns:
354        Dict[str, Dict[str, List[str]]]: A nested dictionary containing {game: {other_game: shifted_training_states}} entries for all games for which shifted training states are specified in the benchmark tasks.
355    """
356    parameters = load_parameters(parameters)
357    tasks_dfs = get_benchmark_tasks_dfs(parameters)
358    all_games = set()
359    for df in tasks_dfs.values():
360        all_games.update(df["game"].unique())
361    all_shifted_training_states = {}
362    for game in all_games:
363        shifted_states = get_shifted_training_states(game, parameters)
364        all_shifted_training_states[game] = shifted_states
365    return all_shifted_training_states

Gets all shifted training states for all games.

Arguments:
  • parameters (dict, optional): Additional parameters for error logging.
Returns:

Dict[str, Dict[str, List[str]]]: A nested dictionary containing {game: {other_game: shifted_training_states}} entries for all games for which shifted training states are specified in the benchmark tasks.