diff --git a/pyboy/plugins/__init__.py b/pyboy/plugins/__init__.py index 0f8bf8565..bb88661a2 100644 --- a/pyboy/plugins/__init__.py +++ b/pyboy/plugins/__init__.py @@ -8,18 +8,18 @@ __pdoc__ = { # docs exclude - "record_replay": False, "window_headless": False, - "manager_gen": False, "window_open_gl": False, - "debug": False, - "screenshot_recorder": False, - "manager": False, - "window_sdl2": False, - "auto_pause": False, "screen_recorder": False, - "window_dummy": False, "rewind": False, + "window_dummy": False, "disable_input": False, + "manager_gen": False, + "auto_pause": False, + "manager": False, + "record_replay": False, + "screenshot_recorder": False, + "debug": False, + "window_sdl2": False, # docs exclude end } diff --git a/pyboy/plugins/base_plugin.pxd b/pyboy/plugins/base_plugin.pxd index fdcd6eca1..99fe0d83d 100644 --- a/pyboy/plugins/base_plugin.pxd +++ b/pyboy/plugins/base_plugin.pxd @@ -4,7 +4,7 @@ # from cpython.array cimport array -from libc.stdint cimport uint8_t, uint32_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t cimport cython from pyboy.botsupport.tilemap cimport TileMap from pyboy.core.mb cimport Motherboard @@ -42,6 +42,9 @@ cdef class PyBoyGameWrapper(PyBoyPlugin): cdef public shape cdef bint game_has_started cdef TileMap tilemap_background + cdef TileMap tilemap_window + cdef bint tilemap_use_background + cdef uint16_t sprite_offset cdef bint _tile_cache_invalid cdef array _cached_game_area_tiles_raw diff --git a/pyboy/plugins/base_plugin.py b/pyboy/plugins/base_plugin.py index 8c372426c..1e134b965 100644 --- a/pyboy/plugins/base_plugin.py +++ b/pyboy/plugins/base_plugin.py @@ -99,6 +99,9 @@ class PyBoyGameWrapper(PyBoyPlugin): def __init__(self, *args, game_area_section=(0, 0, 32, 32), game_area_wrap_around=False, **kwargs): super().__init__(*args, **kwargs) self.tilemap_background = self.pyboy.botsupport_manager().tilemap_background() + self.tilemap_window = self.pyboy.botsupport_manager().tilemap_window() + self.tilemap_use_background = True + self.sprite_offset = 0 self.game_has_started = False self._tile_cache_invalid = True self._sprite_cache_invalid = True @@ -167,12 +170,6 @@ def game_over(self): """ raise NotImplementedError("game_over not implemented in game wrapper") - def game_over(self): - """ - After calling `start_game`, you can call this method at any time to know if the game is over. - """ - raise NotImplementedError("game_over not implemented in game wrapper") - def _sprites_on_screen(self): if self._sprite_cache_invalid: self._cached_sprites_on_screen = [] @@ -199,14 +196,25 @@ def _game_area_tiles(self): for x in range(width): _x = (xx+x+SCX) % 32 _y = (yy+y+SCY) % 32 - self._cached_game_area_tiles[y][x] = self.tilemap_background.tile_identifier(_x, _y) + if self.tilemap_use_background: + self._cached_game_area_tiles[y][x] = self.tilemap_background.tile_identifier(_x, _y) + else: + self._cached_game_area_tiles[y][x] = self.tilemap_window.tile_identifier(_x, _y) else: - self._cached_game_area_tiles = np.asarray( - self.tilemap_background[xx:xx + width, yy:yy + height], dtype=np.uint32 - ) + if self.tilemap_use_background: + self._cached_game_area_tiles = np.asarray( + self.tilemap_background[xx:xx + width, yy:yy + height], dtype=np.uint32 + ) + else: + self._cached_game_area_tiles = np.asarray( + self.tilemap_window[xx:xx + width, yy:yy + height], dtype=np.uint32 + ) self._tile_cache_invalid = False return self._cached_game_area_tiles + def use_background(self, value): + self.tilemap_use_background = value + def game_area(self): """ This method returns a cut-out of the screen as a simplified matrix for use in machine learning applications. @@ -226,7 +234,8 @@ def game_area(self): _x = (s.x // 8) - xx _y = (s.y // 8) - yy if 0 <= _y < height and 0 <= _x < width: - tiles_matrix[_y][_x] = s.tile_identifier + tiles_matrix[_y][ + _x] = s.tile_identifier + self.sprite_offset # Adding offset to try to seperate sprites from tiles return tiles_matrix def _game_area_np(self, observation_type="tiles"): diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1.pxd b/pyboy/plugins/game_wrapper_pokemon_gen1.pxd new file mode 100644 index 000000000..38e041e95 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1.pxd @@ -0,0 +1,10 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# +from libc.stdint cimport uint8_t +from pyboy.plugins.base_plugin cimport PyBoyGameWrapper +cimport cython + +cdef class GameWrapperPokemonGen1(PyBoyGameWrapper): + pass diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1.py b/pyboy/plugins/game_wrapper_pokemon_gen1.py new file mode 100644 index 000000000..b76de1be3 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1.py @@ -0,0 +1,70 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# +__pdoc__ = { + "GameWrapperPokemonGen1.cartridge_title": False, + "GameWrapperPokemonGen1.post_tick": False, +} + +import logging + +from pyboy.utils import WindowEvent + +from .base_plugin import PyBoyGameWrapper + +logger = logging.getLogger(__name__) + +try: + from cython import compiled + cythonmode = compiled +except ImportError: + cythonmode = False + + +class GameWrapperPokemonGen1(PyBoyGameWrapper): + """ + This class wraps Pokemon Red/Blue, 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 = None + + def __init__(self, *args, **kwargs): + self.shape = (20, 18) + super().__init__(*args, game_area_section=(0, 0) + self.shape, game_area_wrap_around=True, **kwargs) + self.sprite_offset = 0x1000 + + def enabled(self): + return self.pyboy_argv.get("game_wrapper") and ((self.pyboy.cartridge_title() == "POKEMON RED") or + (self.pyboy.cartridge_title() == "POKEMON BLUE")) + + def post_tick(self): + self._tile_cache_invalid = True + self._sprite_cache_invalid = True + + scanline_parameters = self.pyboy.botsupport_manager().screen().tilemap_position_list() + WX = scanline_parameters[0][2] + WY = scanline_parameters[0][3] + self.use_background(WY != 0) + + def __repr__(self): + adjust = 4 + # yapf: disable + return ( + f"Pokemon Gen 1:\n" + + "Sprites on screen:\n" + + "\n".join([str(s) for s in self._sprites_on_screen()]) + + "\n" + + "Tiles on screen:\n" + + " "*5 + "".join([f"{i: <4}" for i in range(10)]) + "\n" + + "_"*(adjust*20+4) + + "\n" + + "\n".join( + [ + f"{i: <3}| " + "".join([str(tile).ljust(adjust) for tile in line]) + for i, line in enumerate(self.game_area()) + ] + ) + ) + # yapf: enable diff --git a/pyboy/plugins/manager.pxd b/pyboy/plugins/manager.pxd index 8ad523071..e6f24a896 100644 --- a/pyboy/plugins/manager.pxd +++ b/pyboy/plugins/manager.pxd @@ -19,6 +19,7 @@ from pyboy.plugins.screenshot_recorder cimport ScreenshotRecorder from pyboy.plugins.game_wrapper_super_mario_land cimport GameWrapperSuperMarioLand 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 # imports end from pyboy.plugins.base_plugin cimport PyBoyPlugin, PyBoyWindowPlugin @@ -42,6 +43,7 @@ cdef class PluginManager: cdef public GameWrapperSuperMarioLand game_wrapper_super_mario_land cdef public GameWrapperTetris game_wrapper_tetris cdef public GameWrapperKirbyDreamLand game_wrapper_kirby_dream_land + cdef public GameWrapperPokemonGen1 game_wrapper_pokemon_gen1 cdef bint window_sdl2_enabled cdef bint window_open_gl_enabled cdef bint window_headless_enabled @@ -56,6 +58,7 @@ cdef class PluginManager: cdef bint game_wrapper_super_mario_land_enabled cdef bint game_wrapper_tetris_enabled cdef bint game_wrapper_kirby_dream_land_enabled + cdef bint game_wrapper_pokemon_gen1_enabled # plugin_cdef end cdef list handle_events(self, list) diff --git a/pyboy/plugins/manager.py b/pyboy/plugins/manager.py index f5e9662ea..ce37b8983 100644 --- a/pyboy/plugins/manager.py +++ b/pyboy/plugins/manager.py @@ -18,7 +18,9 @@ from pyboy.plugins.game_wrapper_super_mario_land import GameWrapperSuperMarioLand # isort:skip from pyboy.plugins.game_wrapper_tetris import GameWrapperTetris # isort:skip from pyboy.plugins.game_wrapper_kirby_dream_land import GameWrapperKirbyDreamLand # isort:skip +from pyboy.plugins.game_wrapper_pokemon_gen1 import GameWrapperPokemonGen1 # isort:skip # imports end +from pyboy.plugins.base_plugin import PyBoyGameWrapper def parser_arguments(): @@ -37,6 +39,7 @@ def parser_arguments(): yield GameWrapperSuperMarioLand.argv yield GameWrapperTetris.argv yield GameWrapperKirbyDreamLand.argv + yield GameWrapperPokemonGen1.argv # yield_plugins end pass @@ -74,6 +77,8 @@ def __init__(self, pyboy, mb, pyboy_argv): self.game_wrapper_tetris_enabled = self.game_wrapper_tetris.enabled() self.game_wrapper_kirby_dream_land = GameWrapperKirbyDreamLand(pyboy, mb, pyboy_argv) self.game_wrapper_kirby_dream_land_enabled = self.game_wrapper_kirby_dream_land.enabled() + self.game_wrapper_pokemon_gen1 = GameWrapperPokemonGen1(pyboy, mb, pyboy_argv) + self.game_wrapper_pokemon_gen1_enabled = self.game_wrapper_pokemon_gen1.enabled() # plugins_enabled end def gamewrapper(self): @@ -84,6 +89,8 @@ def gamewrapper(self): return self.game_wrapper_tetris 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 # gamewrapper end return None @@ -119,6 +126,8 @@ def handle_events(self, events): events = self.game_wrapper_tetris.handle_events(events) if self.game_wrapper_kirby_dream_land_enabled: events = self.game_wrapper_kirby_dream_land.handle_events(events) + if self.game_wrapper_pokemon_gen1_enabled: + events = self.game_wrapper_pokemon_gen1.handle_events(events) # foreach end return events @@ -142,6 +151,8 @@ def post_tick(self): self.game_wrapper_tetris.post_tick() if self.game_wrapper_kirby_dream_land_enabled: self.game_wrapper_kirby_dream_land.post_tick() + if self.game_wrapper_pokemon_gen1_enabled: + self.game_wrapper_pokemon_gen1.post_tick() # foreach end self._post_tick_windows() @@ -235,6 +246,8 @@ def window_title(self): title += self.game_wrapper_tetris.window_title() if self.game_wrapper_kirby_dream_land_enabled: title += self.game_wrapper_kirby_dream_land.window_title() + if self.game_wrapper_pokemon_gen1_enabled: + title += self.game_wrapper_pokemon_gen1.window_title() # foreach end return title @@ -270,6 +283,8 @@ def stop(self): self.game_wrapper_tetris.stop() if self.game_wrapper_kirby_dream_land_enabled: self.game_wrapper_kirby_dream_land.stop() + if self.game_wrapper_pokemon_gen1_enabled: + self.game_wrapper_pokemon_gen1.stop() # foreach end pass diff --git a/pyboy/plugins/manager_gen.py b/pyboy/plugins/manager_gen.py index 4710abdf4..a6660d549 100644 --- a/pyboy/plugins/manager_gen.py +++ b/pyboy/plugins/manager_gen.py @@ -7,7 +7,9 @@ # Plugins and priority! # E.g. DisableInput first windows = ["WindowSDL2", "WindowOpenGL", "WindowHeadless", "WindowDummy", "Debug"] -game_wrappers = ["GameWrapperSuperMarioLand", "GameWrapperTetris", "GameWrapperKirbyDreamLand"] +game_wrappers = [ + "GameWrapperSuperMarioLand", "GameWrapperTetris", "GameWrapperKirbyDreamLand", "GameWrapperPokemonGen1" +] plugins = [ "DisableInput", "AutoPause", "RecordReplay", "Rewind", "ScreenRecorder", "ScreenshotRecorder" ] + game_wrappers