@@ -978,13 +1733,17 @@ Returns
a game.
You can either save it to a file, or in-memory. The following two examples will provide the file handle in each
case. Remember to seek
the in-memory buffer to the beginning before calling PyBoy.load_state()
:
-# Save to file
-file_like_object = open("state_file.state", "wb")
+>>> # Save to file
+>>> with open("state_file.state", "wb") as f:
+... pyboy.save_state(f)
+>>>
+>>> # Save to memory
+>>> import io
+>>> with io.BytesIO() as f:
+... f.seek(0)
+... pyboy.save_state(f)
+0
-# Save to memory
-import io
-file_like_object = io.BytesIO()
-file_like_object.seek(0)
Args
@@ -1003,13 +1762,19 @@ Args
You can either save it to a file, or in-memory. The following two examples will provide the file handle in each
case. Remember to `seek` the in-memory buffer to the beginning before calling `PyBoy.load_state`:
- # Save to file
- file_like_object = open("state_file.state", "wb")
+ ```python
+ >>> # Save to file
+ >>> with open("state_file.state", "wb") as f:
+ ... pyboy.save_state(f)
+ >>>
+ >>> # Save to memory
+ >>> import io
+ >>> with io.BytesIO() as f:
+ ... f.seek(0)
+ ... pyboy.save_state(f)
+ 0
- # Save to memory
- import io
- file_like_object = io.BytesIO()
- file_like_object.seek(0)
+ ```
Args:
file_like_object (io.BufferedIOBase): A file-like object for which to write the emulator state.
@@ -1030,8 +1795,10 @@ Args
You can either load it from a file, or from memory. See PyBoy.save_state()
for how to save the state, before you
can load it here.
To load a file, remember to load it as bytes:
-# Load file
-file_like_object = open("state_file.state", "rb")
+>>> # Load file
+>>> with open("state_file.state", "rb") as f:
+... pyboy.load_state(f)
+>>>
Args
@@ -1051,10 +1818,12 @@ Args
can load it here.
To load a file, remember to load it as bytes:
-
- # Load file
- file_like_object = open("state_file.state", "rb")
-
+ ```python
+ >>> # Load file
+ >>> with open("state_file.state", "rb") as f:
+ ... pyboy.load_state(f)
+ >>>
+ ```
Args:
file_like_object (io.BufferedIOBase): A file-like object for which to read the emulator state.
@@ -1066,40 +1835,266 @@ Args
self.mb.load_state(IntIOWrapper(file_like_object))
-
-def screen_image(self)
+
+def game_area_dimensions(self, x, y, width, height, follow_scrolling=True)
-
-
Shortcut for pyboy.botsupport_manager.screen.screen_image
.
-Generates a PIL Image from the screen buffer.
-Convenient for screen captures, but might be a bottleneck, if you use it to train a neural network. In which
-case, read up on the pyboy.botsupport
features, Pan Docs on tiles/sprites,
-and join our Discord channel for more help.
+If using the generic game wrapper (see PyBoy.game_wrapper
), you can use this to set the section of the
+tilemaps to extract. This will default to the entire tilemap.
+Example:
+>>> pyboy.game_area_dimensions(0, 0, 10, 18, False)
+
+
+Args
+
+x
: int
+- Offset from top-left corner of the screen
+y
: int
+- Offset from top-left corner of the screen
+width
: int
+- Width of game area
+height
: int
+- Height of game area
+follow_scrolling
: bool
+- Whether to follow the scrolling of SCX and SCY
+
+
+
+Expand source code
+
+def game_area_dimensions(self, x, y, width, height, follow_scrolling=True):
+ """
+ If using the generic game wrapper (see `pyboy.PyBoy.game_wrapper`), you can use this to set the section of the
+ tilemaps to extract. This will default to the entire tilemap.
+
+ Example:
+ ```python
+ >>> pyboy.game_area_dimensions(0, 0, 10, 18, False)
+
+ ```
+
+ Args:
+ x (int): Offset from top-left corner of the screen
+ y (int): Offset from top-left corner of the screen
+ width (int): Width of game area
+ height (int): Height of game area
+ follow_scrolling (bool): Whether to follow the scrolling of [SCX and SCY](https://gbdev.io/pandocs/Scrolling.html)
+ """
+ self.game_wrapper.game_area_section = (x, y, width, height)
+ self.game_wrapper.game_area_follow_scxy = follow_scrolling
+
+
+
+def game_area_collision(self)
+
+-
+
Some game wrappers define a collision map. Check if your game wrapper has this feature implemented: pyboy.plugins
.
+The output will be unique for each game wrapper.
+Example:
+>>> # This example show nothing, but a supported game will
+>>> pyboy.game_area_collision()
+array([[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, 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]], dtype=uint32)
+
+
Returns
-PIL.Image:
-- RGB image of (160, 144) pixels
+memoryview:
+- Simplified 2-dimensional memoryview of the collision map
+
+
+
+Expand source code
+
+def game_area_collision(self):
+ """
+ Some game wrappers define a collision map. Check if your game wrapper has this feature implemented: `pyboy.plugins`.
+
+ The output will be unique for each game wrapper.
+
+ Example:
+ ```python
+ >>> # This example show nothing, but a supported game will
+ >>> pyboy.game_area_collision()
+ array([[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, 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]], dtype=uint32)
+
+ ```
+
+ Returns
+ -------
+ memoryview:
+ Simplified 2-dimensional memoryview of the collision map
+ """
+ return self.game_wrapper.game_area_collision()
+
+
+
+def game_area_mapping(self, mapping, sprite_offset=0)
+
+-
+
Define custom mappings for tile identifiers in the game area.
+Example of custom mapping:
+>>> mapping = [x for x in range(384)] # 1:1 mapping
+>>> mapping[0] = 0 # Map tile identifier 0 -> 0
+>>> mapping[1] = 0 # Map tile identifier 1 -> 0
+>>> mapping[2] = 0 # Map tile identifier 2 -> 0
+>>> mapping[3] = 0 # Map tile identifier 3 -> 0
+>>> pyboy.game_area_mapping(mapping, 1000)
+
+
+Some game wrappers will supply mappings as well. See the specific documentation for your game wrapper:
+pyboy.plugins
.
+>>> pyboy.game_area_mapping(pyboy.game_wrapper.mapping_minimal, 0)
+
+
+Args
+
+mapping
: list
or ndarray
+- list of 384 (DMG) or 768 (CGB) tile mappings. Use
None
to reset to a 1:1 mapping.
+sprite_offest
: int
+- Optional offset add to tile id for sprites
Expand source code
-def screen_image(self):
+def game_area_mapping(self, mapping, sprite_offset=0):
+ """
+ Define custom mappings for tile identifiers in the game area.
+
+ Example of custom mapping:
+ ```python
+ >>> mapping = [x for x in range(384)] # 1:1 mapping
+ >>> mapping[0] = 0 # Map tile identifier 0 -> 0
+ >>> mapping[1] = 0 # Map tile identifier 1 -> 0
+ >>> mapping[2] = 0 # Map tile identifier 2 -> 0
+ >>> mapping[3] = 0 # Map tile identifier 3 -> 0
+ >>> pyboy.game_area_mapping(mapping, 1000)
+
+ ```
+
+ Some game wrappers will supply mappings as well. See the specific documentation for your game wrapper:
+ `pyboy.plugins`.
+ ```python
+ >>> pyboy.game_area_mapping(pyboy.game_wrapper.mapping_minimal, 0)
+
+ ```
+
+ Args:
+ mapping (list or ndarray): list of 384 (DMG) or 768 (CGB) tile mappings. Use `None` to reset to a 1:1 mapping.
+ sprite_offest (int): Optional offset add to tile id for sprites
"""
- Shortcut for `pyboy.botsupport_manager.screen.screen_image`.
- Generates a PIL Image from the screen buffer.
+ if mapping is None:
+ mapping = [x for x in range(768)]
+
+ assert isinstance(sprite_offset, int)
+ assert isinstance(mapping, (np.ndarray, list))
+ assert len(mapping) == 384 or len(mapping) == 768
+
+ self.game_wrapper.game_area_mapping(mapping, sprite_offset)
+
+
+
+def game_area(self)
+
+-
+
Use this method to get a matrix of the "game area" of the screen. This view is simplified to be perfect for
+machine learning applications.
+The layout will vary from game to game. Below is an example from Tetris:
+Example:
+>>> pyboy.game_area()
+array([[ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 130, 130, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 130, 130, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47]], dtype=uint32)
- Convenient for screen captures, but might be a bottleneck, if you use it to train a neural network. In which
- case, read up on the `pyboy.botsupport` features, [Pan Docs](http://bgb.bircd.org/pandocs.htm) on tiles/sprites,
- and join our Discord channel for more help.
+
+If you want a "compressed", "minimal" or raw mapping of tiles, you can change the mapping using
+PyBoy.game_area_mapping()
. Either you'll have to supply your own mapping, or you can find one
+that is built-in with the game wrapper plugin for your game. See PyBoy.game_area_mapping()
.
+Returns
+
+memoryview:
+- Simplified 2-dimensional memoryview of the screen
+
+
+
+Expand source code
+
+def game_area(self):
+ """
+ Use this method to get a matrix of the "game area" of the screen. This view is simplified to be perfect for
+ machine learning applications.
+
+ The layout will vary from game to game. Below is an example from Tetris:
+
+ Example:
+ ```python
+ >>> pyboy.game_area()
+ array([[ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 130, 130, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 130, 130, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47],
+ [ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47]], dtype=uint32)
+
+ ```
+
+ If you want a "compressed", "minimal" or raw mapping of tiles, you can change the mapping using
+ `pyboy.PyBoy.game_area_mapping`. Either you'll have to supply your own mapping, or you can find one
+ that is built-in with the game wrapper plugin for your game. See `pyboy.PyBoy.game_area_mapping`.
Returns
-------
- PIL.Image:
- RGB image of (160, 144) pixels
+ memoryview:
+ Simplified 2-dimensional memoryview of the screen
"""
- return self.botsupport_manager().screen().screen_image()
+
+ return self.game_wrapper.game_area()
@@ -1111,6 +2106,10 @@ Returns
The speed is defined as a multiple of real-time. I.e target_speed=2
is double speed.
A target_speed
of 0
means unlimited. I.e. fastest possible execution.
Some window types do not implement a frame-limiter, and will always run at full speed.
+Example:
+>>> pyboy.set_emulation_speed(0) # No limit
+
+
Args
target_speed
: int
@@ -1131,332 +2130,365 @@ Args
Some window types do not implement a frame-limiter, and will always run at full speed.
+ Example:
+ ```python
+ >>> pyboy.set_emulation_speed(0) # No limit
+
+ ```
+
Args:
target_speed (int): Target emulation speed as multiplier of real-time.
"""
+ if self.initialized and self._plugin_manager.window_null_enabled:
+ logger.warning(
+ 'This window type does not support frame-limiting. `pyboy.set_emulation_speed(...)` will have no effect, as it\'s always running at full speed.'
+ )
+
if target_speed > 5:
logger.warning("The emulation speed might not be accurate when speed-target is higher than 5")
self.target_emulationspeed = target_speed
-
-def cartridge_title(self)
+
+def hook_register(self, bank, addr, callback, context)
-Get the title stored on the currently loaded cartridge ROM. The title is all upper-case ASCII and may
-have been truncated to 11 characters.
-Returns
+Adds a hook into a specific bank and memory address.
+When the Game Boy executes this address, the provided callback function will be called.
+By providing an object as context
, you can later get access to information inside and outside of the callback.
+Example:
+>>> context = "Hello from hook"
+>>> def my_callback(context):
+... print(context)
+>>> pyboy.hook_register(0, 0x100, my_callback, context)
+>>> pyboy.tick(70)
+Hello from hook
+True
+
+
+If a symbol file is loaded, this function can also automatically resolve a bank and address from a symbol. To
+enable this, you'll need to place a .sym
file next to your ROM, or provide it using:
+PyBoy(..., symbol_file="game_rom.gb.sym")
.
+Then provide None
for bank
and the symbol for addr
to trigger the automatic lookup.
+Example:
+>>> # Continued example above
+>>> pyboy.hook_register(None, "Main.move", lambda x: print(x), "Hello from hook2")
+>>> pyboy.tick(80)
+Hello from hook2
+True
+
+
+Args
-str :
-- Game title
+bank
: int
or None
+- ROM or RAM bank (None for symbol lookup)
+addr
: int
or str
+- Address in the Game Boy's address space (str for symbol lookup)
+callback
: func
+- A function which takes
context
as argument
+context
: object
+- Argument to pass to callback when hook is called
Expand source code
-def cartridge_title(self):
+def hook_register(self, bank, addr, callback, context):
"""
- Get the title stored on the currently loaded cartridge ROM. The title is all upper-case ASCII and may
- have been truncated to 11 characters.
+ Adds a hook into a specific bank and memory address.
+ When the Game Boy executes this address, the provided callback function will be called.
- Returns
- -------
- str :
- Game title
+ By providing an object as `context`, you can later get access to information inside and outside of the callback.
+
+ Example:
+ ```python
+ >>> context = "Hello from hook"
+ >>> def my_callback(context):
+ ... print(context)
+ >>> pyboy.hook_register(0, 0x100, my_callback, context)
+ >>> pyboy.tick(70)
+ Hello from hook
+ True
+
+ ```
+
+ If a symbol file is loaded, this function can also automatically resolve a bank and address from a symbol. To
+ enable this, you'll need to place a `.sym` file next to your ROM, or provide it using:
+ `PyBoy(..., symbol_file="game_rom.gb.sym")`.
+
+ Then provide `None` for `bank` and the symbol for `addr` to trigger the automatic lookup.
+
+ Example:
+ ```python
+ >>> # Continued example above
+ >>> pyboy.hook_register(None, "Main.move", lambda x: print(x), "Hello from hook2")
+ >>> pyboy.tick(80)
+ Hello from hook2
+ True
+
+ ```
+
+ Args:
+ bank (int or None): ROM or RAM bank (None for symbol lookup)
+ addr (int or str): Address in the Game Boy's address space (str for symbol lookup)
+ callback (func): A function which takes `context` as argument
+ context (object): Argument to pass to callback when hook is called
"""
- return self.mb.cartridge.gamename
+ if bank is None and isinstance(addr, str):
+ bank, addr = self._lookup_symbol(addr)
+
+ opcode = self.memory[bank, addr]
+ if opcode == 0xDB:
+ raise ValueError("Hook already registered for this bank and address.")
+ self.mb.breakpoint_add(bank, addr)
+ bank_addr_opcode = (bank & 0xFF) << 24 | (addr & 0xFFFF) << 8 | (opcode & 0xFF)
+ self._hooks[bank_addr_opcode] = (callback, context)
-