From fe64f445147336d94bff3f84ea39c21aba7714a3 Mon Sep 17 00:00:00 2001 From: Ivo Nikolic Date: Wed, 18 Sep 2024 21:52:34 +0200 Subject: [PATCH 1/2] BombermanGB Wrapper --- pyboy/plugins/game_wrapper_bomberman_gb.pxd | 75 ++ pyboy/plugins/game_wrapper_bomberman_gb.py | 1147 +++++++++++++++++++ pyboy/plugins/manager.pxd | 3 + pyboy/plugins/manager.py | 16 + pyboy/plugins/manager_gen.py | 2 +- 5 files changed, 1242 insertions(+), 1 deletion(-) create mode 100644 pyboy/plugins/game_wrapper_bomberman_gb.pxd create mode 100644 pyboy/plugins/game_wrapper_bomberman_gb.py diff --git a/pyboy/plugins/game_wrapper_bomberman_gb.pxd b/pyboy/plugins/game_wrapper_bomberman_gb.pxd new file mode 100644 index 000000000..866b77967 --- /dev/null +++ b/pyboy/plugins/game_wrapper_bomberman_gb.pxd @@ -0,0 +1,75 @@ +cimport cython +cimport numpy as np + +from pyboy.plugins.base_plugin cimport PyBoyGameWrapper + + +cdef class GameWrapperBombermanGB(PyBoyGameWrapper): + cdef readonly tuple _GAME_AREA_SHAPE + cdef readonly list _ENEMY + + cdef readonly int AGENT_DEAD + cdef readonly int AGENT_STATS_BOMB_PLACED + cdef readonly int AGENT_STATS_BOMB_MAX + cdef readonly int AGENT_STATS_BOMB_RANGE + + cdef readonly dict _COORD_MEM_ADDR[str, (int,int)] + cdef readonly dict _ENEMY_DEAD_MEM_ADDR[str, int] + + cdef readonly int BOMBINFO_START + cdef readonly int BOMBINGO_END + + cdef list _ENEMY_ALIVE + + cdef bint _agent_dead + cdef int _agent_bombs_available + cdef int _agent_bomb_max + cdef int _agent_bomb_range + cdef list _agent_bombs + + cdef bint _score_agent_kill + cdef bint _agent_suicide + + cdef bint _score_agent_placed_bomb + cdef int _bomb_block_hits + + cdef int _min_global_distance + cdef int _score_min_global_dist + cdef int _last_min_enemy_distance + cdef int _score_last_dist + cdef bint _agent_in_bomb_range + + cpdef void start_game(self, timer_div=*, enemies=*, int win=*, int time=*) noexcept + cpdef void reset_game(self, timer_div=*, enemies=*, int win=*, int time=*) noexcept + cpdef bint game_over(self) noexcept + cdef void _reset_settings(self) noexcept + cpdef void post_tick(self) noexcept + + cpdef (int,int,int,int,int,int,int,int, int, int, int, int) score(self) noexcept + + cdef bint _suicide(self) noexcept + cdef np.ndarray[np.uint8_t, ndim=2] _get_explosion_map(self) noexcept + cdef (int, int) _player_game_area_coordinate(self, str player) noexcept + cdef void _update_enemy_alive_cache(self) noexcept + cdef void _check_agent_kill(self, str enemy) noexcept + cdef void _update_agent_bomb_info(self) noexcept + cdef int _agent_placed_bomb(self) noexcept + cdef void _check_bomb_hits(self, int x_coord, int y_coord) noexcept + cdef bint _check_bomb_x_hits(self, list coord_to_check, int y_coord, area_cache) + cdef bint _check_bomb_y_hits(self, list coord_to_check, int x_coord, area_cache) + cdef void _distance_checks(self) noexcept + + cdef bint _agent_bomb_max_increased(self) noexcept + cdef bint _agent_bomb_range_increased(self) noexcept + + + cdef void _navigate_to_password_screen(self) noexcept + cdef void _handle_password_screen(self) noexcept + cdef void _set_enemies(self, int n_enemy=*, bint shuffle=*) noexcept + cdef void _handle_rules_screen(self, int win=*, int time=*) noexcept + cdef void _select_stage_screen(self, int stage=*) noexcept + + cpdef void _save_picture(self, name=*) noexcept + + cdef np.ndarray[np.uint8_t, ndim=1] _minimal_mapping(self) noexcept + diff --git a/pyboy/plugins/game_wrapper_bomberman_gb.py b/pyboy/plugins/game_wrapper_bomberman_gb.py new file mode 100644 index 000000000..024a53cda --- /dev/null +++ b/pyboy/plugins/game_wrapper_bomberman_gb.py @@ -0,0 +1,1147 @@ +from datetime import datetime + +import numpy as np +from numpy.typing import NDArray + +import pyboy + +from .base_plugin import PyBoyGameWrapper + +logger = pyboy.logging.get_logger(__name__) + + +class GameWrapperBombermanGB(PyBoyGameWrapper): + """This class wraps Bomberman GB, and provides basic access for AIs. + + If you call `print` on an instance of this object, it will show an overview of + everything this object provides. + """ + + cartridge_title = 'BOMBER MAN GB' + + def __init__(self, *args, **kwargs): + # Readonly Vars + self._GAME_AREA_SHAPE = (2, 2, 18, 14) + self._ENEMY = ['ENEMY_1', 'ENEMY_2', 'ENEMY_3'] + + self.AGENT_DEAD = 51233 # 0xc821 + self.AGENT_STATS_BOMB_PLACED = 49462 # 0xc136 + self.AGENT_STATS_BOMB_MAX = 49461 # 0xc135 + self.AGENT_STATS_BOMB_RANGE = 49463 # 0xc137 + + self._COORD_MEM_ADDR = { + 'AGENT': (51202, 51206), + 'ENEMY_1': (51458, 51462), + 'ENEMY_2': (51714, 51718), + 'ENEMY_3': (51970, 51974), + } + + self._ENEMY_DEAD_MEM_ADDR = { + 'ENEMY_1': 51489, + 'ENEMY_2': 51745, + 'ENEMY_3': 52001, + } + + self.BOMBINFO_START = 50721 # 0xc621 + self.BOMBINGO_END = 50945 # 0xc701 + + # Game Vars + self._ENEMY_ALIVE = [] + + self._agent_dead = self.pyboy.memory[self.AGENT_DEAD] + self._agent_bombs_available = 1 + self._agent_bomb_max = 1 + self._agent_bomb_range = 1 + self._agent_bombs = [] + + self._score_agent_kill = 0 + self._agent_suicide = 0 + + self._score_agent_placed_bomb = 0 + self._bomb_block_hits = 0 + + self._min_global_distance = 999 + self._score_min_global_dist = 0 + self._last_min_enemy_distance = 999 + self._score_last_dist = 0 + self._agent_in_bomb_range = 0 + + super().__init__(*args, game_area_section=self._GAME_AREA_SHAPE, **kwargs) + super().game_area_mapping(self._minimal_mapping(), 0) + + def start_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: + """This method starts the game into the play state and creates a save state. + + Args: + timer_div (int, optional): "RandomSeed" of PyBoy. Defaults to None. + enemies (int, optional): Amount of enemies. If default random + amount 1-3. Defaults to None. + win (int, optional): Amounts of wins needed. Defaults to 1. + time (int, optional): Max duration of Game (Episode). Defaults to 1. + """ + if not enemies: + enemies = np.random.default_rng().integers(1, 4) + + logger.info('STARTING GAME') + logger.info(f'Enemies: {enemies}, Wins: {win}, Time: {time}') + + PyBoyGameWrapper.start_game(self, timer_div) + + self._navigate_to_password_screen() + + # Enter Password for Arena Mode + self._handle_password_screen() + + # Confirme Player One + self.pyboy.button('start') + self.pyboy.tick(100, render=False) + + # The point to reset is before enemy selection + logger.info('SAVE STATE CREATED') + self.saved_state.seek(0) + self.pyboy.save_state(self.saved_state) + + # Add Enemies. + self._set_enemies(enemies, shuffle=True) + + # Set rules + self._handle_rules_screen(win=win, time=time) + + # Select Stage + self._select_stage_screen() + self._reset_settings() + logger.info('STARTED GAME') + + def reset_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: + """After calling `start_game`, use this method to reset to save state. + + Kwargs: + timer_div (int, optional): Replace timer's DIV register with this value. + Use `None` to randomize. Defaults to None. + enemies (int, optional): Amount of enemies. If default random + amount 1-3. Defaults to 0. + win (int, optional): _description_. Defaults to 1. + time (int, optional): _description_. Defaults to 1. + """ + if not enemies: + enemies = np.random.default_rng().integers(1, 4) + + logger.info('RESETING GAME') + logger.info(f'Enemies: {enemies}, Wins: {win}, Time: {time}') + + PyBoyGameWrapper.reset_game(self, timer_div=timer_div) + + # Add Enemies. + self._set_enemies(enemies, shuffle=True) + + # Set rules + self._handle_rules_screen(win=win, time=time) + + # Select Stage + self._select_stage_screen() + self._reset_settings() + logger.info('RESET GAME') + + def game_over(self) -> bool: + """This function checks if the game is over. + + The game is over, when agent won by last player alive or + lost when agent dead. + + Returns: + bool: True if game over. + """ + logger.info('CHECKING GAME OVER') + + map_changed = self.pyboy.game_area()[2, 2] != 12 + win = (len(self._ENEMY_ALIVE) == 0) and not self._agent_dead + + end = self._agent_dead or map_changed or win # or time_up + + logger.info( + f'\nGAME END: {end}\nDEAD: {self._agent_dead}\n \ + MAP:{map_changed}\nWIN:{win}', + ) + + return end + + def _reset_settings(self) -> None: + """This function resets the players stats.""" + logger.info('RESETING STATS') + self._ENEMY_ALIVE = [] + + self._agent_bombs_available = 1 + self._agent_bomb_max = 1 + self._agent_bomb_range = 1 + self._agent_bombs = [] + + self._score_agent_kill = 0 + self._agent_suicide = 0 + + self._score_agent_placed_bomb = 0 + self._bomb_block_hits = 0 + + self._min_global_distance = 999 + self._score_min_global_dist = 0 + self._last_min_enemy_distance = 999 + self._score_last_dist = 0 + self._agent_in_bomb_range = 0 + + logger.info('STATS RESETED') + + def post_tick(self) -> None: + """This function is called after `.tick()`.""" + # From BaseClass + self._tile_cache_invalid = True + self._sprite_cache_invalid = True + + # Check if dead and if suicide. + self._agent_dead = self.pyboy.memory[self.AGENT_DEAD] + + if self._agent_dead: + logger.info('AGENT DEAD') + self._agent_suicide = self._suicide() + + # This has some timing issues going on + # Update enemy cache + self._update_enemy_alive_cache() + + # Update placed agent placed bombs and coordinate of it + self._update_agent_bomb_info() + self._distance_checks() + + def score(self) -> tuple[int]: + """Returns if condition for score was reached. + + For better abstraction this function returns only if a certain condition was + reached. The amount of reward/points for the condition can be specified + in the `GymWrapper`. + + Returns: + tuple[int]: _description_ + """ + step_penalty = 1 + + max_bomb_up = self._agent_bomb_max_increased() + range_bomb_up = self._agent_bomb_range_increased() + + if self._score_agent_placed_bomb: + placed_bomb = 1 + self._score_agent_placed_bomb = 0 + else: + placed_bomb = 0 + + if self._score_agent_kill: + kill = 1 + self._score_agent_kill = 0 + else: + kill = 0 + + win = 1 if (len(self._ENEMY_ALIVE) == 0) and not self._agent_dead else 0 + lose = 1 if self._agent_dead else 0 + + if self._agent_suicide: + suicide = 1 + self._agent_suicide = 0 + else: + suicide = 0 + + if self._bomb_block_hits: + block_hits = self._bomb_block_hits + self._bomb_block_hits = 0 + else: + block_hits = 0 + + if self._score_min_global_dist: + min_dist = 1 + self._score_min_global_dist = 0 + else: + min_dist = 0 + + if self._score_last_dist > 0: + dist_score = 1 + self._score_last_dist = 0 + elif self._score_last_dist < 0: + dist_score = -1 + self._score_last_dist = 0 + else: + dist_score = 0 + + if self._agent_in_bomb_range: + in_bomb_range = 1 + self._agent_in_bomb_range = 0 + else: + in_bomb_range = 0 + + # logger.info("HELLO") + + return ( + step_penalty * 1, + max_bomb_up * 1, + range_bomb_up * 1, + placed_bomb * 1, + kill * 1, + win * 1, + lose * 1, + suicide * 1, + block_hits * 1, + min_dist * 1, + dist_score * 1, + in_bomb_range * 1, + ) + + def _suicide(self) -> int: + """Checks if Pieets killed himself. + + Returns: + int: True if death by own bomb. + """ + bomb_map = np.empty((7, 9), dtype=np.uint8) + bomb_map = self._get_explosion_map() + + x_coord, y_coord = self._player_game_area_coordinate('AGENT') + + if bomb_map[y_coord // 2, x_coord // 2] in self._agent_bombs: + logger.info('SUICIDE') + return 1 + + return 0 + + def _get_explosion_map(self) -> NDArray[uint8]: # noqa: F821 + """This Function returns a "map" with bombs on it. + + Returns: + np.ndarray: "Map" + """ + bombs = self.pyboy.memory[ + self.BOMBINFO_START : self.BOMBINGO_END + ] # See PlayerInfo + + # Reshape and deselect VRAM + return np.reshape(np.array(bombs, dtype=np.uint8), (7, -1))[:7, :9] + + def _player_game_area_coordinate(self, player: str) -> tuple[int, int]: + """This function returns the coordinates of player. + + Only the upperleft 8x8 square coordinates of 16x16 square player. + + Args: + player (str): Name of player to search for + + Returns: + tuple[int, int]: upperleft square coordinates + """ + x_pixle_coordinate = self.pyboy.memory[self._COORD_MEM_ADDR[player][0]] + y_pixle_coordinate = self.pyboy.memory[self._COORD_MEM_ADDR[player][1]] + + # See global var GAME_AREA_SHAPE + x_coordinate = ( + x_pixle_coordinate // 8 + ) - 3 # //8 for Pixelsize -2 game_area offset. + y_coordinate = (y_pixle_coordinate // 8) - 3 + + # To much logging. + # logger.info(f"UPPERLEFT COORDINATES {player}:") + # logger.info(f"X: {x_coordinate}, Y: {y_coordinate}") + + return (x_coordinate, y_coordinate) # Coordinate of the upperleft quarter. + + def _update_enemy_alive_cache(self) -> None: + """This function creates or updates the enemy cache. + + If a enemy is removed from cache its checked if the player + got the kill. + """ + if self._ENEMY_ALIVE: + for enemy in self._ENEMY_ALIVE: + if self.pyboy.memory[self._ENEMY_DEAD_MEM_ADDR[enemy]]: + self._check_agent_kill(enemy) + self._ENEMY_ALIVE.remove(enemy) + logger.info(f'REMOVE {enemy}') + else: + logger.info('CREATING NEW ENEMY CACHE') + for enemy in self._ENEMY: + if not self.pyboy.memory[self._ENEMY_DEAD_MEM_ADDR[enemy]]: + self._ENEMY_ALIVE.append(enemy) + + def _check_agent_kill(self, enemy) -> None: + bomb_map = np.empty((7, 9), dtype=np.uint8) + bomb_map = self._get_explosion_map() + x_coord, y_coord = self._player_game_area_coordinate(enemy) + + if bomb_map[y_coord // 2, x_coord // 2] in self._agent_bombs: + self._score_agent_kill = 1 + logger.info('PIEETS IS UNSTOPPABLE') + + def _update_agent_bomb_info(self) -> None: + """Updated Pieets bomb information. + + If a bomb placed it gets added to `self._agent_bombs`. + If a bomb exploded it gets removed from `self._agent_bombs`. + + """ + diff_allowed = self._agent_placed_bomb() + # logger.info(f"DIFF: {diff_allowed}") + + if diff_allowed <= 0: + # logger.info(f"BOMB EXPLODE") + # logger.info(f"PIEETS BOMBS BEFORE: {self._agent_bombs}") + + # Bombs are displayed longer in memory then in Pieets bombstats + # so we check every time for removed bombs. + + bomb_map = np.empty((7, 9), dtype=np.uint8) + bomb_map = self._get_explosion_map() + + for bomb in self._agent_bombs: + if not np.any(np.isin(bomb_map, bomb, assume_unique=True)): + self._agent_bombs.remove(bomb) + + # logger.info(f"PIEETS BOMBS AFTER: {self._agent_bombs}") + + if diff_allowed > 0: + bomb_map = np.empty((7, 9), dtype=np.uint8) + bomb_map = self._get_explosion_map() + + x_coord, y_coord = self._player_game_area_coordinate('AGENT') + agent_bomb_number = bomb_map[y_coord // 2, x_coord // 2] + + # logger.info(f"BOMB PLACED") + # logger.info(f"PIEETS BOMBS BEFORE: {self._agent_bombs}") + + self._agent_bombs.append(agent_bomb_number) + self._check_bomb_hits(x_coord, y_coord) + + self._score_agent_placed_bomb = 1 + + # logger.info(f"PIEETS BOMBS AFTER: {self._agent_bombs}") + + def _agent_placed_bomb(self) -> int: + """This function checks if Pieets placed a bomb. + + 1 (True) if bomb placed. + 0 (False) if nothing happened + -1 (False) if bomb exploded. + + Returns: + int: See above. + """ + bomb_amount_current = ( + self.pyboy.memory[self.AGENT_STATS_BOMB_MAX] + - self.pyboy.memory[self.AGENT_STATS_BOMB_PLACED] + ) + + bomb_amount_last = self._agent_bombs_available + + diff_allowed = bomb_amount_last - bomb_amount_current + + self._agent_bombs_available = bomb_amount_current + + return diff_allowed + + def _check_bomb_hits(self, x_coord, y_coord) -> None: + area_cache = self.pyboy.game_area() + + right_check = [ + x_coord + 2 + i if x_coord <= 15 + self._agent_bomb_range else 17 + for i in range(1, self._agent_bomb_range + 1) + ] + left_check = [ + x_coord - 2 - i if x_coord >= 2 - self._agent_bomb_range else 0 + for i in range(1, self._agent_bomb_range + 1) + ] + up_check = [ + y_coord - 2 - i if y_coord >= 2 + self._agent_bomb_range else 0 + for i in range(1, self._agent_bomb_range + 1) + ] + down_check = [ + y_coord + 2 + i if y_coord <= 11 - self._agent_bomb_range else 13 + for i in range(1, self._agent_bomb_range + 1) + ] + + right_hit = self._check_bomb_x_hits(right_check, y_coord, area_cache) + left_hit = self._check_bomb_x_hits(left_check, y_coord, area_cache) + up_hit = self._check_bomb_y_hits(up_check, x_coord, area_cache) + down_hit = self._check_bomb_y_hits(down_check, x_coord, area_cache) + + self._bomb_block_hits = right_hit + left_hit + up_hit + down_hit + + def _check_bomb_x_hits(self, coord_to_check, y_coord, area) -> int: + for coord in coord_to_check: + if coord <= 0 or coord >= 17: + x_block_destroy = 0 + break + x_destroy = area[y_coord, coord] + + if x_destroy == 10: + x_block_destroy = 1 + break + + if x_destroy == 12: + x_block_destroy = 0 + break + return x_block_destroy + + def _check_bomb_y_hits(self, coord_to_check, x_coord, area) -> int: + for coord in coord_to_check: + if coord <= 0 or coord >= 13: + y_block_destroy = 0 + break + + y_destroy = area[coord, x_coord] + + if y_destroy == 10: + y_block_destroy = 1 + break + + if y_destroy == 12: + y_block_destroy = 0 + break + return y_block_destroy + + def _distance_checks(self) -> None: + x_agent, y_agent = self._player_game_area_coordinate('AGENT') + + min_enemy_dist = 99 + enemy_dist_diff = 0 # Because of all enemies dead no assignment + + for enemy in self._ENEMY_ALIVE: + x_enemy, y_enemy = self._player_game_area_coordinate(enemy) + dist = np.abs(x_enemy - x_agent) + np.abs(y_enemy - y_agent) + + # logger.info(f"DISTANCE TO {enemy}: {dist}") + + if min_enemy_dist >= dist: + # logger.info(f"LOWEST DIST TO ENEMY {enemy}") + min_enemy_dist = dist + enemy_dist_diff = self._last_min_enemy_distance - dist + + # logger.info(f"DIST DIFF {enemy_dist_diff}") + + if enemy_dist_diff >= 0: + # logger.info("CLOSER THEN LAST STEP") + self._score_last_dist = 1 + if min_enemy_dist < self._min_global_distance: # MAYBE NOT EQUAL + logger.info('GLOBAL LOWEST DIFF') + self._min_global_distance = min_enemy_dist + self._score_min_global_dist = 1 + if enemy_dist_diff < 0: + # logger.info("FARTHER THEN LAST STEP") + self._score_last_dist = -1 + + self._last_min_enemy_distance = min_enemy_dist + + bomb_map = np.empty((7, 9), dtype=np.uint8) + bomb_map = self._get_explosion_map() + + if bomb_map[y_agent // 2, x_agent // 2] != 0: + self._agent_in_bomb_range = 1 + + def _agent_bomb_max_increased(self) -> int: + current_bomb_max = self._agent_bomb_max + new_bomb_max = self.pyboy.memory[self.AGENT_STATS_BOMB_MAX] + + if current_bomb_max != new_bomb_max: + # print(current_bomb_max, new_bomb_max) + self._agent_bomb_max = new_bomb_max + return 1 + + return 0 + + def _agent_bomb_range_increased(self) -> int: + current_bomb_range = self._agent_bomb_range + new_bomb_range = self.pyboy.memory[self.AGENT_STATS_BOMB_RANGE] + + if current_bomb_range != new_bomb_range: + # print(current_bomb_range, new_bomb_range) + self._agent_bomb_range = new_bomb_range + return 1 + + return 0 + + # ---------------------------------------------------------------------------- + # Dunder methods + + def __repr__(self): + # yapf: disable + + return ( + f"BOMBERMAN GB\nPyboyInstance: {self.pyboy}\n" + f"AGENT (x/y): {self._full_player_coordinate('AGENT')}\n" + f"AGENT (PACKAGE NR): {self._agent_bombs}\n" + f"AGENT (INVENTAR): {self._agent_bombs_available}\n" + f"AGENT (MAX): {self._agent_bomb_max}\n" + f"AGENT (RANGE): {self._agent_bomb_range}\n" + f"AGENT (KILL): {self._score_agent_kill}\n" + f"Enemies Alive: {len(self._ENEMY_ALIVE)}\n" + f"ENEMY1 (x/y): {self._full_player_coordinate('ENEMY_1')}\n" + f"ENEMY2 (x/y): {self._full_player_coordinate('ENEMY_2')}\n" + f"ENEMY3 (x/y): {self._full_player_coordinate('ENEMY_3')}\n" + + super().__repr__() + ) + # yapf: enable + + # ---------------------------------------------------------------------------- + # Not so important methods + + def _minimal_mapping(self) -> NDArray[uint8]: # noqa: F821 + """This function returns the minimal mapping for BombermanGB. + + Returns: + NDArray[uint8]: Mapping of the 384 tiles to a group + """ + created_mapping = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 8, + 8, + 8, + 8, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 0, + 0, + 0, + 0, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 0, + 0, + 0, + 0, + 12, + 12, + 12, + 12, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 11, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] + + return np.array(created_mapping, dtype=np.uint8) + + def _save_picture(self, name=None): # Maybe remove since other calls can be used. + datestring = datetime.now(tz=datetime.timezone.utc).strftime( + '%Y_%m_%d_%H_%M_%S_', + ) + + name = datestring + 'screenshot' if not name else datestring + name + + image = self.pyboy.screen.image + image.save(f'./reports/vis/helper_func/{name}.png') + + def _full_player_coordinate( + self, + player: str, + ) -> tuple[tuple[int, int], tuple[int, int]]: + """This function returns the coordinates the "whole" player is standing. + + This is necessary, since _player_game_area_coordinate only returns the upperleft + quarter of the full player. + + Args: + player (str): Player to return coordinates for + + Returns: + tuple[tuple[int, int], tuple[int, int]]: All coordinates. + """ + x_upperleft_coordinate, y_upperleft_coordinate = ( + self._player_game_area_coordinate(player) + ) + x_coord = [x_upperleft_coordinate, x_upperleft_coordinate + 1] + y_coord = [y_upperleft_coordinate, y_upperleft_coordinate + 1] + + return x_coord, y_coord + + # ----------------------------------------------------------------------------- + # Functions handling navigation and game settings and not providing any more + # use. + + def _navigate_to_password_screen(self) -> None: + # logger.info("START: NAVIGATE TO PASSWORD SCREEN") + # Skip to main menu + self.pyboy.tick(200, render=False) + self.pyboy.button('start') + self.pyboy.tick(69, render=False) + + # Select password in main menu + for _ in range(3): + self.pyboy.button('down') + self.pyboy.tick(10, render=False) + self.pyboy.button('a') + self.pyboy.tick(99, render=False) + + # logger.info("END: NAVIGATE TO PASSWORD SCREEN") + + def _handle_password_screen(self) -> None: + # logger.info("START: HANDLE PASSWORD SCREEN") + for _ in range(5): + self.pyboy.button('up') + self.pyboy.tick(10, render=False) + + self.pyboy.button('right') + self.pyboy.tick(10, render=False) + + for _ in range(6): + self.pyboy.button('up') + self.pyboy.tick(10, render=False) + + self.pyboy.button('right') + self.pyboy.tick(10, render=False) + + for _ in range(5): + self.pyboy.button('up') + self.pyboy.tick(10, render=False) + + self.pyboy.button('right') + self.pyboy.tick(10, render=False) + + for _ in range(6): + self.pyboy.button('up') + self.pyboy.tick(10, render=False) + + self.pyboy.button('start') + self.pyboy.tick(100, render=False) + # logger.info("END: HANDLE PASSWORD SCREEN") + + def _set_enemies(self, n_enemy=0, shuffle=False) -> None: + """Selects n enemies. + + Always selects the furthest enemy from player. Agent used to wait for + enemies to free him. + + Args: + n_enemy (int): Number of enemies + shuffle (bool, optional): Shuffle enemy selection. Defaults to None. + """ + enemy_list = np.arange(2, -1, -1) # Reverse so at 1v1 most away enemy + + logger.info(f'START: SET ENEMIES {n_enemy}') + + if shuffle: + logger.info(f'SHUFFLE: SET ENEMIES {n_enemy}') + np.random.default_rng().shuffle(enemy_list) + + selected_enemies = enemy_list[:n_enemy] + logger.info(f'SELECTED: ENEMIES {selected_enemies+2}') + + def select_enemie(enemy): + if enemy != 0: + for _ in range(enemy): + self.pyboy.button('right') + self.pyboy.tick(2, render=False) + + self.pyboy.button('down') + self.pyboy.tick(2, render=False) + + if enemy != 0: + for _ in range(enemy): + self.pyboy.button('left') + self.pyboy.tick(2, render=False) + + for player in selected_enemies: + select_enemie(player) + + self.pyboy.button('start') + self.pyboy.tick(69, render=False) + logger.info(f'END:SET ENMIES {n_enemy}') + + def _handle_rules_screen(self, win=1, time=1) -> None: + # logger.info(f"START: SETTING RULES Win:{win}, Time:{time}") + + time_diff = 3 - time + win_diff = 4 - win + + if time_diff == 0 and win_diff == 0: + self.pyboy.button('start') + self.pyboy.tick(46, render=False) + + if time_diff < 0: + for _ in range(time - 3): + self.pyboy.button('right') + self.pyboy.tick(5, render=False) + if time_diff > 0: + for _ in range(3 - time): + self.pyboy.button('left') + self.pyboy.tick(5, render=False) + + self.pyboy.button('down') + self.pyboy.tick(5, render=False) + + if win_diff < 0: + for _ in range(win - 4): + self.pyboy.button('right') + self.pyboy.tick(5, render=False) + if win_diff > 0: + for _ in range(4 - win): + self.pyboy.button('left') + self.pyboy.tick(5, render=False) + + self.pyboy.button('start') + self.pyboy.tick(69, render=False) + # logger.info(f"END: SETTING RULES Win:{win}, Time:{time}") + + def _select_stage_screen(self, stage=1): + # logger.info(f"START: SELECT STAGE") + self.pyboy.button('start') + self.pyboy.tick(46, render=True) + # logger.info(f"END: SELECT STAGE") diff --git a/pyboy/plugins/manager.pxd b/pyboy/plugins/manager.pxd index a770eedf7..295336983 100644 --- a/pyboy/plugins/manager.pxd +++ b/pyboy/plugins/manager.pxd @@ -23,6 +23,7 @@ from pyboy.plugins.game_wrapper_tetris cimport GameWrapperTetris from pyboy.plugins.game_wrapper_kirby_dream_land cimport GameWrapperKirbyDreamLand from pyboy.plugins.game_wrapper_pokemon_gen1 cimport GameWrapperPokemonGen1 from pyboy.plugins.game_wrapper_pokemon_pinball cimport GameWrapperPokemonPinball +from pyboy.plugins.game_wrapper_bomberman_gb cimport GameWrapperBombermanGB # imports end @@ -48,6 +49,7 @@ cdef class PluginManager: cdef public GameWrapperKirbyDreamLand game_wrapper_kirby_dream_land cdef public GameWrapperPokemonGen1 game_wrapper_pokemon_gen1 cdef public GameWrapperPokemonPinball game_wrapper_pokemon_pinball + cdef public GameWrapperBombermanGB game_wrapper_bomberman_gb cdef bint window_sdl2_enabled cdef bint window_open_gl_enabled cdef bint window_null_enabled @@ -64,6 +66,7 @@ cdef class PluginManager: cdef bint game_wrapper_kirby_dream_land_enabled cdef bint game_wrapper_pokemon_gen1_enabled cdef bint game_wrapper_pokemon_pinball_enabled + cdef bint game_wrapper_bomberman_gb_enabled # plugin_cdef end cdef list handle_events(self, list) noexcept diff --git a/pyboy/plugins/manager.py b/pyboy/plugins/manager.py index df279c8da..c36ca14fa 100644 --- a/pyboy/plugins/manager.py +++ b/pyboy/plugins/manager.py @@ -22,6 +22,7 @@ from pyboy.plugins.game_wrapper_kirby_dream_land import GameWrapperKirbyDreamLand # isort:skip from pyboy.plugins.game_wrapper_pokemon_gen1 import GameWrapperPokemonGen1 # isort:skip from pyboy.plugins.game_wrapper_pokemon_pinball import GameWrapperPokemonPinball # isort:skip +from pyboy.plugins.game_wrapper_bomberman_gb import GameWrapperBombermanGB # isort:skip # imports end @@ -43,12 +44,14 @@ def parser_arguments(): yield GameWrapperKirbyDreamLand.argv yield GameWrapperPokemonGen1.argv yield GameWrapperPokemonPinball.argv + yield GameWrapperBombermanGB.argv # yield_plugins end pass class PluginManager: def __init__(self, pyboy, mb, pyboy_argv): + self.pyboy = pyboy self.generic_game_wrapper = PyBoyGameWrapper(pyboy, mb, pyboy_argv) @@ -86,6 +89,9 @@ def __init__(self, pyboy, mb, pyboy_argv): self.game_wrapper_pokemon_gen1_enabled = self.game_wrapper_pokemon_gen1.enabled() self.game_wrapper_pokemon_pinball = GameWrapperPokemonPinball(pyboy, mb, pyboy_argv) self.game_wrapper_pokemon_pinball_enabled = self.game_wrapper_pokemon_pinball.enabled() + # BOMBERMAN GB + self.game_wrapper_bomberman_gb = GameWrapperBombermanGB(pyboy, mb, pyboy_argv) + self.game_wrapper_bomberman_gb_enabled = self.game_wrapper_bomberman_gb.enabled() # plugins_enabled end def gamewrapper(self): @@ -95,6 +101,7 @@ def gamewrapper(self): if self.game_wrapper_kirby_dream_land_enabled: return self.game_wrapper_kirby_dream_land if self.game_wrapper_pokemon_gen1_enabled: return self.game_wrapper_pokemon_gen1 if self.game_wrapper_pokemon_pinball_enabled: return self.game_wrapper_pokemon_pinball + if self.game_wrapper_bomberman_gb_enabled: return self.game_wrapper_bomberman_gb # gamewrapper end self.generic_game_wrapper_enabled = True return self.generic_game_wrapper @@ -135,6 +142,8 @@ def handle_events(self, events): events = self.game_wrapper_pokemon_gen1.handle_events(events) if self.game_wrapper_pokemon_pinball_enabled: events = self.game_wrapper_pokemon_pinball.handle_events(events) + if self.game_wrapper_bomberman_gb_enabled: + events = self.game_wrapper_bomberman_gb.handle_events(events) # foreach end if self.generic_game_wrapper_enabled: events = self.generic_game_wrapper.handle_events(events) @@ -166,6 +175,9 @@ def post_tick(self): self.game_wrapper_pokemon_gen1.post_tick() if self.game_wrapper_pokemon_pinball_enabled: self.game_wrapper_pokemon_pinball.post_tick() + if self.game_wrapper_bomberman_gb_enabled: + self.game_wrapper_bomberman_gb.post_tick() + # foreach end if self.generic_game_wrapper_enabled: self.generic_game_wrapper.post_tick() @@ -253,6 +265,8 @@ def window_title(self): title += self.game_wrapper_pokemon_gen1.window_title() if self.game_wrapper_pokemon_pinball_enabled: title += self.game_wrapper_pokemon_pinball.window_title() + if self.game_wrapper_bomberman_gb_enabled: + title += self.game_wrapper_bomberman_gb.window_title() # foreach end return title @@ -292,6 +306,8 @@ def stop(self): self.game_wrapper_pokemon_gen1.stop() if self.game_wrapper_pokemon_pinball_enabled: self.game_wrapper_pokemon_pinball.stop() + if self.game_wrapper_bomberman_gb_enabled: + self.game_wrapper_bomberman_gb.stop() # foreach end if self.generic_game_wrapper_enabled: self.generic_game_wrapper.stop() diff --git a/pyboy/plugins/manager_gen.py b/pyboy/plugins/manager_gen.py index 93fef23f6..0a4594b11 100644 --- a/pyboy/plugins/manager_gen.py +++ b/pyboy/plugins/manager_gen.py @@ -9,7 +9,7 @@ windows = ["WindowSDL2", "WindowOpenGL", "WindowNull", "Debug"] game_wrappers = [ "GameWrapperSuperMarioLand", "GameWrapperTetris", "GameWrapperKirbyDreamLand", "GameWrapperPokemonGen1", - "GameWrapperPokemonPinball" + "GameWrapperPokemonPinball", "GameWrapperBombermanGB" ] plugins = [ "DisableInput", "AutoPause", "RecordReplay", "Rewind", "ScreenRecorder", "ScreenshotRecorder", "DebugPrompt" From 077ee8fbe5424dea3bb8d6762507130e4f3a6e5a Mon Sep 17 00:00:00 2001 From: Ivo Nikolic Date: Wed, 18 Sep 2024 21:57:22 +0200 Subject: [PATCH 2/2] Double Quotes Really? --- pyboy/plugins/game_wrapper_bomberman_gb.py | 173 ++++++++++----------- 1 file changed, 81 insertions(+), 92 deletions(-) diff --git a/pyboy/plugins/game_wrapper_bomberman_gb.py b/pyboy/plugins/game_wrapper_bomberman_gb.py index 024a53cda..88eca4240 100644 --- a/pyboy/plugins/game_wrapper_bomberman_gb.py +++ b/pyboy/plugins/game_wrapper_bomberman_gb.py @@ -17,33 +17,33 @@ class GameWrapperBombermanGB(PyBoyGameWrapper): everything this object provides. """ - cartridge_title = 'BOMBER MAN GB' + cartridge_title = "BOMBER MAN GB" def __init__(self, *args, **kwargs): # Readonly Vars self._GAME_AREA_SHAPE = (2, 2, 18, 14) - self._ENEMY = ['ENEMY_1', 'ENEMY_2', 'ENEMY_3'] + self._ENEMY = ["ENEMY_1", "ENEMY_2", "ENEMY_3"] - self.AGENT_DEAD = 51233 # 0xc821 - self.AGENT_STATS_BOMB_PLACED = 49462 # 0xc136 - self.AGENT_STATS_BOMB_MAX = 49461 # 0xc135 - self.AGENT_STATS_BOMB_RANGE = 49463 # 0xc137 + self.AGENT_DEAD = 51233 # 0xc821 + self.AGENT_STATS_BOMB_PLACED = 49462 # 0xc136 + self.AGENT_STATS_BOMB_MAX = 49461 # 0xc135 + self.AGENT_STATS_BOMB_RANGE = 49463 # 0xc137 self._COORD_MEM_ADDR = { - 'AGENT': (51202, 51206), - 'ENEMY_1': (51458, 51462), - 'ENEMY_2': (51714, 51718), - 'ENEMY_3': (51970, 51974), + "AGENT": (51202, 51206), + "ENEMY_1": (51458, 51462), + "ENEMY_2": (51714, 51718), + "ENEMY_3": (51970, 51974), } self._ENEMY_DEAD_MEM_ADDR = { - 'ENEMY_1': 51489, - 'ENEMY_2': 51745, - 'ENEMY_3': 52001, + "ENEMY_1": 51489, + "ENEMY_2": 51745, + "ENEMY_3": 52001, } - self.BOMBINFO_START = 50721 # 0xc621 - self.BOMBINGO_END = 50945 # 0xc701 + self.BOMBINFO_START = 50721 # 0xc621 + self.BOMBINGO_END = 50945 # 0xc701 # Game Vars self._ENEMY_ALIVE = [] @@ -81,9 +81,9 @@ def start_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: """ if not enemies: enemies = np.random.default_rng().integers(1, 4) - - logger.info('STARTING GAME') - logger.info(f'Enemies: {enemies}, Wins: {win}, Time: {time}') + + logger.info("STARTING GAME") + logger.info(f"Enemies: {enemies}, Wins: {win}, Time: {time}") PyBoyGameWrapper.start_game(self, timer_div) @@ -93,11 +93,11 @@ def start_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: self._handle_password_screen() # Confirme Player One - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(100, render=False) # The point to reset is before enemy selection - logger.info('SAVE STATE CREATED') + logger.info("SAVE STATE CREATED") self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) @@ -110,7 +110,7 @@ def start_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: # Select Stage self._select_stage_screen() self._reset_settings() - logger.info('STARTED GAME') + logger.info("STARTED GAME") def reset_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: """After calling `start_game`, use this method to reset to save state. @@ -126,8 +126,8 @@ def reset_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: if not enemies: enemies = np.random.default_rng().integers(1, 4) - logger.info('RESETING GAME') - logger.info(f'Enemies: {enemies}, Wins: {win}, Time: {time}') + logger.info("RESETING GAME") + logger.info(f"Enemies: {enemies}, Wins: {win}, Time: {time}") PyBoyGameWrapper.reset_game(self, timer_div=timer_div) @@ -140,7 +140,7 @@ def reset_game(self, timer_div=None, enemies=None, win=1, time=1) -> None: # Select Stage self._select_stage_screen() self._reset_settings() - logger.info('RESET GAME') + logger.info("RESET GAME") def game_over(self) -> bool: """This function checks if the game is over. @@ -151,23 +151,21 @@ def game_over(self) -> bool: Returns: bool: True if game over. """ - logger.info('CHECKING GAME OVER') + logger.info("CHECKING GAME OVER") map_changed = self.pyboy.game_area()[2, 2] != 12 win = (len(self._ENEMY_ALIVE) == 0) and not self._agent_dead - end = self._agent_dead or map_changed or win # or time_up + end = self._agent_dead or map_changed or win # or time_up - logger.info( - f'\nGAME END: {end}\nDEAD: {self._agent_dead}\n \ - MAP:{map_changed}\nWIN:{win}', - ) + logger.info(f"\nGAME END: {end}\nDEAD: {self._agent_dead}\n \ + MAP:{map_changed}\nWIN:{win}", ) return end def _reset_settings(self) -> None: """This function resets the players stats.""" - logger.info('RESETING STATS') + logger.info("RESETING STATS") self._ENEMY_ALIVE = [] self._agent_bombs_available = 1 @@ -187,7 +185,7 @@ def _reset_settings(self) -> None: self._score_last_dist = 0 self._agent_in_bomb_range = 0 - logger.info('STATS RESETED') + logger.info("STATS RESETED") def post_tick(self) -> None: """This function is called after `.tick()`.""" @@ -199,7 +197,7 @@ def post_tick(self) -> None: self._agent_dead = self.pyboy.memory[self.AGENT_DEAD] if self._agent_dead: - logger.info('AGENT DEAD') + logger.info("AGENT DEAD") self._agent_suicide = self._suicide() # This has some timing issues going on @@ -299,23 +297,21 @@ def _suicide(self) -> int: bomb_map = np.empty((7, 9), dtype=np.uint8) bomb_map = self._get_explosion_map() - x_coord, y_coord = self._player_game_area_coordinate('AGENT') + x_coord, y_coord = self._player_game_area_coordinate("AGENT") if bomb_map[y_coord // 2, x_coord // 2] in self._agent_bombs: - logger.info('SUICIDE') + logger.info("SUICIDE") return 1 return 0 - def _get_explosion_map(self) -> NDArray[uint8]: # noqa: F821 + def _get_explosion_map(self) -> NDArray[uint8]: # noqa: F821 """This Function returns a "map" with bombs on it. Returns: np.ndarray: "Map" """ - bombs = self.pyboy.memory[ - self.BOMBINFO_START : self.BOMBINGO_END - ] # See PlayerInfo + bombs = self.pyboy.memory[self.BOMBINFO_START:self.BOMBINGO_END] # See PlayerInfo # Reshape and deselect VRAM return np.reshape(np.array(bombs, dtype=np.uint8), (7, -1))[:7, :9] @@ -335,16 +331,14 @@ def _player_game_area_coordinate(self, player: str) -> tuple[int, int]: y_pixle_coordinate = self.pyboy.memory[self._COORD_MEM_ADDR[player][1]] # See global var GAME_AREA_SHAPE - x_coordinate = ( - x_pixle_coordinate // 8 - ) - 3 # //8 for Pixelsize -2 game_area offset. - y_coordinate = (y_pixle_coordinate // 8) - 3 + x_coordinate = (x_pixle_coordinate//8) - 3 # //8 for Pixelsize -2 game_area offset. + y_coordinate = (y_pixle_coordinate//8) - 3 # To much logging. # logger.info(f"UPPERLEFT COORDINATES {player}:") # logger.info(f"X: {x_coordinate}, Y: {y_coordinate}") - return (x_coordinate, y_coordinate) # Coordinate of the upperleft quarter. + return (x_coordinate, y_coordinate) # Coordinate of the upperleft quarter. def _update_enemy_alive_cache(self) -> None: """This function creates or updates the enemy cache. @@ -357,9 +351,9 @@ def _update_enemy_alive_cache(self) -> None: if self.pyboy.memory[self._ENEMY_DEAD_MEM_ADDR[enemy]]: self._check_agent_kill(enemy) self._ENEMY_ALIVE.remove(enemy) - logger.info(f'REMOVE {enemy}') + logger.info(f"REMOVE {enemy}") else: - logger.info('CREATING NEW ENEMY CACHE') + logger.info("CREATING NEW ENEMY CACHE") for enemy in self._ENEMY: if not self.pyboy.memory[self._ENEMY_DEAD_MEM_ADDR[enemy]]: self._ENEMY_ALIVE.append(enemy) @@ -371,7 +365,7 @@ def _check_agent_kill(self, enemy) -> None: if bomb_map[y_coord // 2, x_coord // 2] in self._agent_bombs: self._score_agent_kill = 1 - logger.info('PIEETS IS UNSTOPPABLE') + logger.info("PIEETS IS UNSTOPPABLE") def _update_agent_bomb_info(self) -> None: """Updated Pieets bomb information. @@ -403,7 +397,7 @@ def _update_agent_bomb_info(self) -> None: bomb_map = np.empty((7, 9), dtype=np.uint8) bomb_map = self._get_explosion_map() - x_coord, y_coord = self._player_game_area_coordinate('AGENT') + x_coord, y_coord = self._player_game_area_coordinate("AGENT") agent_bomb_number = bomb_map[y_coord // 2, x_coord // 2] # logger.info(f"BOMB PLACED") @@ -427,8 +421,7 @@ def _agent_placed_bomb(self) -> int: int: See above. """ bomb_amount_current = ( - self.pyboy.memory[self.AGENT_STATS_BOMB_MAX] - - self.pyboy.memory[self.AGENT_STATS_BOMB_PLACED] + self.pyboy.memory[self.AGENT_STATS_BOMB_MAX] - self.pyboy.memory[self.AGENT_STATS_BOMB_PLACED] ) bomb_amount_last = self._agent_bombs_available @@ -500,10 +493,10 @@ def _check_bomb_y_hits(self, coord_to_check, x_coord, area) -> int: return y_block_destroy def _distance_checks(self) -> None: - x_agent, y_agent = self._player_game_area_coordinate('AGENT') + x_agent, y_agent = self._player_game_area_coordinate("AGENT") min_enemy_dist = 99 - enemy_dist_diff = 0 # Because of all enemies dead no assignment + enemy_dist_diff = 0 # Because of all enemies dead no assignment for enemy in self._ENEMY_ALIVE: x_enemy, y_enemy = self._player_game_area_coordinate(enemy) @@ -521,8 +514,8 @@ def _distance_checks(self) -> None: if enemy_dist_diff >= 0: # logger.info("CLOSER THEN LAST STEP") self._score_last_dist = 1 - if min_enemy_dist < self._min_global_distance: # MAYBE NOT EQUAL - logger.info('GLOBAL LOWEST DIFF') + if min_enemy_dist < self._min_global_distance: # MAYBE NOT EQUAL + logger.info("GLOBAL LOWEST DIFF") self._min_global_distance = min_enemy_dist self._score_min_global_dist = 1 if enemy_dist_diff < 0: @@ -584,7 +577,7 @@ def __repr__(self): # ---------------------------------------------------------------------------- # Not so important methods - def _minimal_mapping(self) -> NDArray[uint8]: # noqa: F821 + def _minimal_mapping(self) -> NDArray[uint8]: # noqa: F821 """This function returns the minimal mapping for BombermanGB. Returns: @@ -979,15 +972,13 @@ def _minimal_mapping(self) -> NDArray[uint8]: # noqa: F821 return np.array(created_mapping, dtype=np.uint8) - def _save_picture(self, name=None): # Maybe remove since other calls can be used. - datestring = datetime.now(tz=datetime.timezone.utc).strftime( - '%Y_%m_%d_%H_%M_%S_', - ) + def _save_picture(self, name=None): # Maybe remove since other calls can be used. + datestring = datetime.now(tz=datetime.timezone.utc).strftime("%Y_%m_%d_%H_%M_%S_", ) - name = datestring + 'screenshot' if not name else datestring + name + name = datestring + "screenshot" if not name else datestring + name image = self.pyboy.screen.image - image.save(f'./reports/vis/helper_func/{name}.png') + image.save(f"./reports/vis/helper_func/{name}.png") def _full_player_coordinate( self, @@ -1004,9 +995,7 @@ def _full_player_coordinate( Returns: tuple[tuple[int, int], tuple[int, int]]: All coordinates. """ - x_upperleft_coordinate, y_upperleft_coordinate = ( - self._player_game_area_coordinate(player) - ) + x_upperleft_coordinate, y_upperleft_coordinate = (self._player_game_area_coordinate(player)) x_coord = [x_upperleft_coordinate, x_upperleft_coordinate + 1] y_coord = [y_upperleft_coordinate, y_upperleft_coordinate + 1] @@ -1020,14 +1009,14 @@ def _navigate_to_password_screen(self) -> None: # logger.info("START: NAVIGATE TO PASSWORD SCREEN") # Skip to main menu self.pyboy.tick(200, render=False) - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(69, render=False) # Select password in main menu for _ in range(3): - self.pyboy.button('down') + self.pyboy.button("down") self.pyboy.tick(10, render=False) - self.pyboy.button('a') + self.pyboy.button("a") self.pyboy.tick(99, render=False) # logger.info("END: NAVIGATE TO PASSWORD SCREEN") @@ -1035,31 +1024,31 @@ def _navigate_to_password_screen(self) -> None: def _handle_password_screen(self) -> None: # logger.info("START: HANDLE PASSWORD SCREEN") for _ in range(5): - self.pyboy.button('up') + self.pyboy.button("up") self.pyboy.tick(10, render=False) - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(10, render=False) for _ in range(6): - self.pyboy.button('up') + self.pyboy.button("up") self.pyboy.tick(10, render=False) - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(10, render=False) for _ in range(5): - self.pyboy.button('up') + self.pyboy.button("up") self.pyboy.tick(10, render=False) - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(10, render=False) for _ in range(6): - self.pyboy.button('up') + self.pyboy.button("up") self.pyboy.tick(10, render=False) - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(100, render=False) # logger.info("END: HANDLE PASSWORD SCREEN") @@ -1073,37 +1062,37 @@ def _set_enemies(self, n_enemy=0, shuffle=False) -> None: n_enemy (int): Number of enemies shuffle (bool, optional): Shuffle enemy selection. Defaults to None. """ - enemy_list = np.arange(2, -1, -1) # Reverse so at 1v1 most away enemy + enemy_list = np.arange(2, -1, -1) # Reverse so at 1v1 most away enemy - logger.info(f'START: SET ENEMIES {n_enemy}') + logger.info(f"START: SET ENEMIES {n_enemy}") if shuffle: - logger.info(f'SHUFFLE: SET ENEMIES {n_enemy}') + logger.info(f"SHUFFLE: SET ENEMIES {n_enemy}") np.random.default_rng().shuffle(enemy_list) selected_enemies = enemy_list[:n_enemy] - logger.info(f'SELECTED: ENEMIES {selected_enemies+2}') + logger.info(f"SELECTED: ENEMIES {selected_enemies+2}") def select_enemie(enemy): if enemy != 0: for _ in range(enemy): - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(2, render=False) - self.pyboy.button('down') + self.pyboy.button("down") self.pyboy.tick(2, render=False) if enemy != 0: for _ in range(enemy): - self.pyboy.button('left') + self.pyboy.button("left") self.pyboy.tick(2, render=False) for player in selected_enemies: select_enemie(player) - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(69, render=False) - logger.info(f'END:SET ENMIES {n_enemy}') + logger.info(f"END:SET ENMIES {n_enemy}") def _handle_rules_screen(self, win=1, time=1) -> None: # logger.info(f"START: SETTING RULES Win:{win}, Time:{time}") @@ -1112,36 +1101,36 @@ def _handle_rules_screen(self, win=1, time=1) -> None: win_diff = 4 - win if time_diff == 0 and win_diff == 0: - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(46, render=False) if time_diff < 0: for _ in range(time - 3): - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(5, render=False) if time_diff > 0: for _ in range(3 - time): - self.pyboy.button('left') + self.pyboy.button("left") self.pyboy.tick(5, render=False) - self.pyboy.button('down') + self.pyboy.button("down") self.pyboy.tick(5, render=False) if win_diff < 0: for _ in range(win - 4): - self.pyboy.button('right') + self.pyboy.button("right") self.pyboy.tick(5, render=False) if win_diff > 0: for _ in range(4 - win): - self.pyboy.button('left') + self.pyboy.button("left") self.pyboy.tick(5, render=False) - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(69, render=False) # logger.info(f"END: SETTING RULES Win:{win}, Time:{time}") def _select_stage_screen(self, stage=1): # logger.info(f"START: SELECT STAGE") - self.pyboy.button('start') + self.pyboy.button("start") self.pyboy.tick(46, render=True) # logger.info(f"END: SELECT STAGE")