From e68099fa5657293f0213b272a699aff941fc1d00 Mon Sep 17 00:00:00 2001 From: Mads Ynddal Date: Sun, 11 Feb 2024 09:46:52 +0100 Subject: [PATCH] Update docs --- docs/api/constants.html | 192 ++ docs/api/index.html | 117 + docs/api/memory_scanner.html | 757 +++++ docs/{botsupport => api}/screen.html | 531 ++-- docs/{botsupport => api}/sprite.html | 153 +- docs/{botsupport => api}/tile.html | 295 +- docs/{botsupport => api}/tilemap.html | 344 ++- docs/botsupport/index.html | 480 --- docs/index.html | 2652 +++++++++++------ docs/openai_gym.html | 596 ---- docs/plugins/base_plugin.html | 190 +- .../game_wrapper_kirby_dream_land.html | 128 +- docs/plugins/game_wrapper_pokemon_gen1.html | 58 +- .../game_wrapper_super_mario_land.html | 215 +- docs/plugins/game_wrapper_tetris.html | 202 +- docs/plugins/index.html | 20 +- docs/utils.html | 889 ++++++ 17 files changed, 4775 insertions(+), 3044 deletions(-) create mode 100644 docs/api/constants.html create mode 100644 docs/api/index.html create mode 100644 docs/api/memory_scanner.html rename docs/{botsupport => api}/screen.html (55%) rename docs/{botsupport => api}/sprite.html (77%) rename docs/{botsupport => api}/tile.html (62%) rename docs/{botsupport => api}/tilemap.html (74%) delete mode 100644 docs/botsupport/index.html delete mode 100644 docs/openai_gym.html create mode 100644 docs/utils.html diff --git a/docs/api/constants.html b/docs/api/constants.html new file mode 100644 index 000000000..d4b592a0f --- /dev/null +++ b/docs/api/constants.html @@ -0,0 +1,192 @@ + + + + + + +pyboy.api.constants API documentation + + + + + + + + + + +
+
+
+

Module pyboy.api.constants

+
+
+

Memory constants used internally to calculate tile and tile map addresses.

+
+ +Expand source code + +
#
+# License: See LICENSE.md file
+# GitHub: https://github.com/Baekalfen/PyBoy
+#
+"""
+Memory constants used internally to calculate tile and tile map addresses.
+"""
+
+VRAM_OFFSET = 0x8000
+"""
+Start address of VRAM
+"""
+LCDC_OFFSET = 0xFF40
+"""
+LCDC Register
+"""
+OAM_OFFSET = 0xFE00
+"""
+Start address of Object-Attribute-Memory (OAM)
+"""
+LOW_TILEMAP = 0x1800 + VRAM_OFFSET
+"""
+Start address of lower tilemap
+"""
+HIGH_TILEMAP = 0x1C00 + VRAM_OFFSET
+"""
+Start address of high tilemap
+"""
+LOW_TILEDATA = VRAM_OFFSET
+"""
+Start address of lower tile data
+"""
+LOW_TILEDATA_NTILES = 0x100
+"""
+Number of tiles in lower tile data
+"""
+HIGH_TILEDATA = 0x800 + VRAM_OFFSET
+"""
+Start address of high tile data
+"""
+TILES = 384
+"""
+Number of tiles supported on Game Boy DMG (non-color)
+"""
+TILES_CGB = 768
+"""
+Number of tiles supported on Game Boy Color
+"""
+SPRITES = 40
+"""
+Number of sprites supported
+"""
+ROWS = 144
+"""
+Rows (horizontal lines) on the screen
+"""
+COLS = 160
+"""
+Columns (vertical lines) on the screen
+"""
+
+
+
+
+
+

Global variables

+
+
var VRAM_OFFSET
+
+

Start address of VRAM

+
+
var LCDC_OFFSET
+
+

LCDC Register

+
+
var OAM_OFFSET
+
+

Start address of Object-Attribute-Memory (OAM)

+
+
var LOW_TILEMAP
+
+

Start address of lower tilemap

+
+
var HIGH_TILEMAP
+
+

Start address of high tilemap

+
+
var LOW_TILEDATA
+
+

Start address of lower tile data

+
+
var LOW_TILEDATA_NTILES
+
+

Number of tiles in lower tile data

+
+
var HIGH_TILEDATA
+
+

Start address of high tile data

+
+
var TILES
+
+

Number of tiles supported on Game Boy DMG (non-color)

+
+
var TILES_CGB
+
+

Number of tiles supported on Game Boy Color

+
+
var SPRITES
+
+

Number of sprites supported

+
+
var ROWS
+
+

Rows (horizontal lines) on the screen

+
+
var COLS
+
+

Columns (vertical lines) on the screen

+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/api/index.html b/docs/api/index.html new file mode 100644 index 000000000..69d4af7ea --- /dev/null +++ b/docs/api/index.html @@ -0,0 +1,117 @@ + + + + + + +pyboy.api API documentation + + + + + + + + + + +
+
+
+

Module pyboy.api

+
+
+

Tools to help interfacing with the Game Boy hardware

+
+ +Expand source code + +
#
+# License: See LICENSE.md file
+# GitHub: https://github.com/Baekalfen/PyBoy
+#
+"""
+Tools to help interfacing with the Game Boy hardware
+"""
+
+from . import constants
+from .screen import Screen
+from .sprite import Sprite
+from .tile import Tile
+from .tilemap import TileMap
+
+# __pdoc__ = {
+#     "constants": False,
+#     "manager": False,
+# }
+# __all__ = ["API"]
+
+
+
+

Sub-modules

+
+
pyboy.api.constants
+
+

Memory constants used internally to calculate tile and tile map addresses.

+
+
pyboy.api.memory_scanner
+
+
+
+
pyboy.api.screen
+
+

This class gives access to the frame buffer and other screen parameters of PyBoy.

+
+
pyboy.api.sprite
+
+

This class presents an interface to the sprites held in the OAM data on the Game Boy.

+
+
pyboy.api.tile
+
+

The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used both for +Sprite and …

+
+
pyboy.api.tilemap
+
+

The Game Boy has two tile maps, which defines what is rendered on the screen.

+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/api/memory_scanner.html b/docs/api/memory_scanner.html new file mode 100644 index 000000000..76cc41655 --- /dev/null +++ b/docs/api/memory_scanner.html @@ -0,0 +1,757 @@ + + + + + + +pyboy.api.memory_scanner API documentation + + + + + + + + + + +
+
+
+

Module pyboy.api.memory_scanner

+
+
+
+ +Expand source code + +
from enum import Enum
+
+from pyboy.utils import bcd_to_dec
+
+
+class StandardComparisonType(Enum):
+    """Enumeration for defining types of comparisons that do not require a previous value."""
+    EXACT = 1
+    LESS_THAN = 2
+    GREATER_THAN = 3
+    LESS_THAN_OR_EQUAL = 4
+    GREATER_THAN_OR_EQUAL = 5
+
+
+class DynamicComparisonType(Enum):
+    """Enumeration for defining types of comparisons that require a previous value."""
+    UNCHANGED = 1
+    CHANGED = 2
+    INCREASED = 3
+    DECREASED = 4
+    MATCH = 5
+
+
+class ScanMode(Enum):
+    """Enumeration for defining scanning modes."""
+    INT = 1
+    BCD = 2
+
+
+class MemoryScanner():
+    """A class for scanning memory within a given range."""
+    def __init__(self, pyboy):
+        """
+        Initializes the MemoryScanner with a PyBoy instance.
+
+        Args:
+            pyboy (PyBoy): The PyBoy emulator instance.
+        """
+        self.pyboy = pyboy
+        self._memory_cache = {}
+        self._memory_cache_byte_width = 1
+
+    def scan_memory(
+        self,
+        target_value=None,
+        start_addr=0x0000,
+        end_addr=0xFFFF,
+        standard_comparison_type=StandardComparisonType.EXACT,
+        value_type=ScanMode.INT,
+        byte_width=1,
+        byteorder="little"
+    ):
+        """
+        This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included).
+
+        Example:
+        ```python
+        >>> current_score = 4 # You write current score in game
+        >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+        []
+
+        ```
+
+        Args:
+            start_addr (int): The starting address for the scan.
+            end_addr (int): The ending address for the scan.
+            target_value (int or None): The value to search for. If None, any value is considered a match.
+            standard_comparison_type (StandardComparisonType): The type of comparison to use.
+            value_type (ValueType): The type of value (INT or BCD) to consider.
+            byte_width (int): The number of bytes to consider for each value.
+            byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
+
+        Returns:
+            list of int: A list of addresses where the target value is found.
+        """
+        self._memory_cache = {}
+        self._memory_cache_byte_width = byte_width
+        for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr
+            # Read multiple bytes based on byte_width and byteorder
+            value_bytes = self.pyboy.memory[addr:addr + byte_width]
+            value = int.from_bytes(value_bytes, byteorder)
+
+            if value_type == ScanMode.BCD:
+                value = bcd_to_dec(value, byte_width, byteorder)
+
+            if target_value is None or self._check_value(value, target_value, standard_comparison_type.value):
+                self._memory_cache[addr] = value
+
+        return list(self._memory_cache.keys())
+
+    def rescan_memory(
+        self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little"
+    ):
+        """
+        Rescans the memory and updates the memory cache based on a dynamic comparison type.
+
+        Example:
+        ```python
+        >>> current_score = 4 # You write current score in game
+        >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+        []
+        >>> for _ in range(175):
+        ...     pyboy.tick(1, True) # Progress the game to change score
+        True...
+        >>> current_score = 8 # You write the new score in game
+        >>> from pyboy.api.memory_scanner import DynamicComparisonType
+        >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
+        >>> print(addresses) # If repeated enough, only one address will remain
+        []
+
+        ```
+
+        Args:
+            new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used.
+            dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED.
+
+        Returns:
+            list of int: A list of addresses remaining in the memory cache after the rescan.
+        """
+        for addr, value in self._memory_cache.copy().items():
+            current_value = int.from_bytes(
+                self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder
+            )
+            if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED):
+                if value != current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.CHANGED):
+                if value == current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.INCREASED):
+                if value >= current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.DECREASED):
+                if value <= current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.MATCH):
+                if new_value == None:
+                    raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH")
+                if current_value != new_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            else:
+                raise ValueError("Invalid comparison type")
+        return list(self._memory_cache.keys())
+
+    def _check_value(self, value, target_value, standard_comparison_type):
+        """
+        Compares a value with the target value based on the specified compare type.
+
+        Args:
+            value (int): The value to compare.
+            target_value (int or None): The target value to compare against.
+            standard_comparison_type (StandardComparisonType): The type of comparison to use.
+
+        Returns:
+            bool: True if the comparison condition is met, False otherwise.
+        """
+        if standard_comparison_type == StandardComparisonType.EXACT.value:
+            return value == target_value
+        elif standard_comparison_type == StandardComparisonType.LESS_THAN.value:
+            return value < target_value
+        elif standard_comparison_type == StandardComparisonType.GREATER_THAN.value:
+            return value > target_value
+        elif standard_comparison_type == StandardComparisonType.LESS_THAN_OR_EQUAL.value:
+            return value <= target_value
+        elif standard_comparison_type == StandardComparisonType.GREATER_THAN_OR_EQUAL.value:
+            return value >= target_value
+        else:
+            raise ValueError("Invalid comparison type")
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class StandardComparisonType +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+
+

Enumeration for defining types of comparisons that do not require a previous value.

+
+ +Expand source code + +
class StandardComparisonType(Enum):
+    """Enumeration for defining types of comparisons that do not require a previous value."""
+    EXACT = 1
+    LESS_THAN = 2
+    GREATER_THAN = 3
+    LESS_THAN_OR_EQUAL = 4
+    GREATER_THAN_OR_EQUAL = 5
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var EXACT
+
+
+
+
var LESS_THAN
+
+
+
+
var GREATER_THAN
+
+
+
+
var LESS_THAN_OR_EQUAL
+
+
+
+
var GREATER_THAN_OR_EQUAL
+
+
+
+
+
+
+class DynamicComparisonType +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+
+

Enumeration for defining types of comparisons that require a previous value.

+
+ +Expand source code + +
class DynamicComparisonType(Enum):
+    """Enumeration for defining types of comparisons that require a previous value."""
+    UNCHANGED = 1
+    CHANGED = 2
+    INCREASED = 3
+    DECREASED = 4
+    MATCH = 5
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var UNCHANGED
+
+
+
+
var CHANGED
+
+
+
+
var INCREASED
+
+
+
+
var DECREASED
+
+
+
+
var MATCH
+
+
+
+
+
+
+class ScanMode +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+
+

Enumeration for defining scanning modes.

+
+ +Expand source code + +
class ScanMode(Enum):
+    """Enumeration for defining scanning modes."""
+    INT = 1
+    BCD = 2
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var INT
+
+
+
+
var BCD
+
+
+
+
+
+
+class MemoryScanner +(pyboy) +
+
+

A class for scanning memory within a given range.

+

Initializes the MemoryScanner with a PyBoy instance.

+

Args

+
+
pyboy : PyBoy
+
The PyBoy emulator instance.
+
+
+ +Expand source code + +
class MemoryScanner():
+    """A class for scanning memory within a given range."""
+    def __init__(self, pyboy):
+        """
+        Initializes the MemoryScanner with a PyBoy instance.
+
+        Args:
+            pyboy (PyBoy): The PyBoy emulator instance.
+        """
+        self.pyboy = pyboy
+        self._memory_cache = {}
+        self._memory_cache_byte_width = 1
+
+    def scan_memory(
+        self,
+        target_value=None,
+        start_addr=0x0000,
+        end_addr=0xFFFF,
+        standard_comparison_type=StandardComparisonType.EXACT,
+        value_type=ScanMode.INT,
+        byte_width=1,
+        byteorder="little"
+    ):
+        """
+        This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included).
+
+        Example:
+        ```python
+        >>> current_score = 4 # You write current score in game
+        >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+        []
+
+        ```
+
+        Args:
+            start_addr (int): The starting address for the scan.
+            end_addr (int): The ending address for the scan.
+            target_value (int or None): The value to search for. If None, any value is considered a match.
+            standard_comparison_type (StandardComparisonType): The type of comparison to use.
+            value_type (ValueType): The type of value (INT or BCD) to consider.
+            byte_width (int): The number of bytes to consider for each value.
+            byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
+
+        Returns:
+            list of int: A list of addresses where the target value is found.
+        """
+        self._memory_cache = {}
+        self._memory_cache_byte_width = byte_width
+        for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr
+            # Read multiple bytes based on byte_width and byteorder
+            value_bytes = self.pyboy.memory[addr:addr + byte_width]
+            value = int.from_bytes(value_bytes, byteorder)
+
+            if value_type == ScanMode.BCD:
+                value = bcd_to_dec(value, byte_width, byteorder)
+
+            if target_value is None or self._check_value(value, target_value, standard_comparison_type.value):
+                self._memory_cache[addr] = value
+
+        return list(self._memory_cache.keys())
+
+    def rescan_memory(
+        self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little"
+    ):
+        """
+        Rescans the memory and updates the memory cache based on a dynamic comparison type.
+
+        Example:
+        ```python
+        >>> current_score = 4 # You write current score in game
+        >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+        []
+        >>> for _ in range(175):
+        ...     pyboy.tick(1, True) # Progress the game to change score
+        True...
+        >>> current_score = 8 # You write the new score in game
+        >>> from pyboy.api.memory_scanner import DynamicComparisonType
+        >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
+        >>> print(addresses) # If repeated enough, only one address will remain
+        []
+
+        ```
+
+        Args:
+            new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used.
+            dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED.
+
+        Returns:
+            list of int: A list of addresses remaining in the memory cache after the rescan.
+        """
+        for addr, value in self._memory_cache.copy().items():
+            current_value = int.from_bytes(
+                self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder
+            )
+            if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED):
+                if value != current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.CHANGED):
+                if value == current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.INCREASED):
+                if value >= current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.DECREASED):
+                if value <= current_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            elif (dynamic_comparison_type == DynamicComparisonType.MATCH):
+                if new_value == None:
+                    raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH")
+                if current_value != new_value:
+                    self._memory_cache.pop(addr)
+                else:
+                    self._memory_cache[addr] = current_value
+            else:
+                raise ValueError("Invalid comparison type")
+        return list(self._memory_cache.keys())
+
+    def _check_value(self, value, target_value, standard_comparison_type):
+        """
+        Compares a value with the target value based on the specified compare type.
+
+        Args:
+            value (int): The value to compare.
+            target_value (int or None): The target value to compare against.
+            standard_comparison_type (StandardComparisonType): The type of comparison to use.
+
+        Returns:
+            bool: True if the comparison condition is met, False otherwise.
+        """
+        if standard_comparison_type == StandardComparisonType.EXACT.value:
+            return value == target_value
+        elif standard_comparison_type == StandardComparisonType.LESS_THAN.value:
+            return value < target_value
+        elif standard_comparison_type == StandardComparisonType.GREATER_THAN.value:
+            return value > target_value
+        elif standard_comparison_type == StandardComparisonType.LESS_THAN_OR_EQUAL.value:
+            return value <= target_value
+        elif standard_comparison_type == StandardComparisonType.GREATER_THAN_OR_EQUAL.value:
+            return value >= target_value
+        else:
+            raise ValueError("Invalid comparison type")
+
+

Methods

+
+
+def scan_memory(self, target_value=None, start_addr=0, end_addr=65535, standard_comparison_type=StandardComparisonType.EXACT, value_type=ScanMode.INT, byte_width=1, byteorder='little') +
+
+

This function scans a specified range of memory for a target value from the start_addr to the end_addr (both included).

+

Example:

+
>>> current_score = 4 # You write current score in game
+>>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+[]
+
+
+

Args

+
+
start_addr : int
+
The starting address for the scan.
+
end_addr : int
+
The ending address for the scan.
+
target_value : int or None
+
The value to search for. If None, any value is considered a match.
+
standard_comparison_type : StandardComparisonType
+
The type of comparison to use.
+
value_type : ValueType
+
The type of value (INT or BCD) to consider.
+
byte_width : int
+
The number of bytes to consider for each value.
+
byteorder : str
+
The endian type to use. This is only used for 16-bit values and higher. See int.from_bytes for more details.
+
+

Returns

+
+
list of int
+
A list of addresses where the target value is found.
+
+
+ +Expand source code + +
def scan_memory(
+    self,
+    target_value=None,
+    start_addr=0x0000,
+    end_addr=0xFFFF,
+    standard_comparison_type=StandardComparisonType.EXACT,
+    value_type=ScanMode.INT,
+    byte_width=1,
+    byteorder="little"
+):
+    """
+    This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included).
+
+    Example:
+    ```python
+    >>> current_score = 4 # You write current score in game
+    >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+    []
+
+    ```
+
+    Args:
+        start_addr (int): The starting address for the scan.
+        end_addr (int): The ending address for the scan.
+        target_value (int or None): The value to search for. If None, any value is considered a match.
+        standard_comparison_type (StandardComparisonType): The type of comparison to use.
+        value_type (ValueType): The type of value (INT or BCD) to consider.
+        byte_width (int): The number of bytes to consider for each value.
+        byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
+
+    Returns:
+        list of int: A list of addresses where the target value is found.
+    """
+    self._memory_cache = {}
+    self._memory_cache_byte_width = byte_width
+    for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr
+        # Read multiple bytes based on byte_width and byteorder
+        value_bytes = self.pyboy.memory[addr:addr + byte_width]
+        value = int.from_bytes(value_bytes, byteorder)
+
+        if value_type == ScanMode.BCD:
+            value = bcd_to_dec(value, byte_width, byteorder)
+
+        if target_value is None or self._check_value(value, target_value, standard_comparison_type.value):
+            self._memory_cache[addr] = value
+
+    return list(self._memory_cache.keys())
+
+
+
+def rescan_memory(self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder='little') +
+
+

Rescans the memory and updates the memory cache based on a dynamic comparison type.

+

Example:

+
>>> current_score = 4 # You write current score in game
+>>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+[]
+>>> for _ in range(175):
+...     pyboy.tick(1, True) # Progress the game to change score
+True...
+>>> current_score = 8 # You write the new score in game
+>>> from pyboy.api.memory_scanner import DynamicComparisonType
+>>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
+>>> print(addresses) # If repeated enough, only one address will remain
+[]
+
+
+

Args

+
+
new_value : int, optional
+
The new value for comparison. If not provided, the current value in memory is used.
+
dynamic_comparison_type : DynamicComparisonType
+
The type of comparison to use. Defaults to UNCHANGED.
+
+

Returns

+
+
list of int
+
A list of addresses remaining in the memory cache after the rescan.
+
+
+ +Expand source code + +
def rescan_memory(
+    self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little"
+):
+    """
+    Rescans the memory and updates the memory cache based on a dynamic comparison type.
+
+    Example:
+    ```python
+    >>> current_score = 4 # You write current score in game
+    >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
+    []
+    >>> for _ in range(175):
+    ...     pyboy.tick(1, True) # Progress the game to change score
+    True...
+    >>> current_score = 8 # You write the new score in game
+    >>> from pyboy.api.memory_scanner import DynamicComparisonType
+    >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
+    >>> print(addresses) # If repeated enough, only one address will remain
+    []
+
+    ```
+
+    Args:
+        new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used.
+        dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED.
+
+    Returns:
+        list of int: A list of addresses remaining in the memory cache after the rescan.
+    """
+    for addr, value in self._memory_cache.copy().items():
+        current_value = int.from_bytes(
+            self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder
+        )
+        if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED):
+            if value != current_value:
+                self._memory_cache.pop(addr)
+            else:
+                self._memory_cache[addr] = current_value
+        elif (dynamic_comparison_type == DynamicComparisonType.CHANGED):
+            if value == current_value:
+                self._memory_cache.pop(addr)
+            else:
+                self._memory_cache[addr] = current_value
+        elif (dynamic_comparison_type == DynamicComparisonType.INCREASED):
+            if value >= current_value:
+                self._memory_cache.pop(addr)
+            else:
+                self._memory_cache[addr] = current_value
+        elif (dynamic_comparison_type == DynamicComparisonType.DECREASED):
+            if value <= current_value:
+                self._memory_cache.pop(addr)
+            else:
+                self._memory_cache[addr] = current_value
+        elif (dynamic_comparison_type == DynamicComparisonType.MATCH):
+            if new_value == None:
+                raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH")
+            if current_value != new_value:
+                self._memory_cache.pop(addr)
+            else:
+                self._memory_cache[addr] = current_value
+        else:
+            raise ValueError("Invalid comparison type")
+    return list(self._memory_cache.keys())
+
+
+
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/botsupport/screen.html b/docs/api/screen.html similarity index 55% rename from docs/botsupport/screen.html rename to docs/api/screen.html index 27f9864d0..ebb8ccd15 100644 --- a/docs/botsupport/screen.html +++ b/docs/api/screen.html @@ -4,7 +4,7 @@ -pyboy.botsupport.screen API documentation +pyboy.api.screen API documentation @@ -18,7 +18,7 @@
-

Module pyboy.botsupport.screen

+

Module pyboy.api.screen

This class gives access to the frame buffer and other screen parameters of PyBoy.

@@ -34,13 +34,14 @@

Module pyboy.botsupport.screen

This class gives access to the frame buffer and other screen parameters of PyBoy. """ -import logging - import numpy as np +from pyboy import utils +from pyboy.logging import get_logger + from .constants import COLS, ROWS -logger = logging.getLogger(__name__) +logger = get_logger(__name__) try: from PIL import Image @@ -54,117 +55,122 @@

Module pyboy.botsupport.screen

to make it possible to read this buffer out. If you're making an AI or bot, it's highly recommended to _not_ use this class for detecting objects on the screen. - It's much more efficient to use `pyboy.botsupport.BotSupportManager.tilemap_background`, `pyboy.botsupport.BotSupportManager.tilemap_window`, and - `pyboy.botsupport.BotSupportManager.sprite` instead. + It's much more efficient to use `pyboy.tilemap_background`, `pyboy.tilemap_window`, and `pyboy.sprite` instead. """ def __init__(self, mb): self.mb = mb - def tilemap_position(self): + self.raw_buffer = self.mb.lcd.renderer._screenbuffer """ - These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note - that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer - to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site - of the tile map. + Provides a raw, unfiltered `bytes` object with the data from the screen. Check + `Screen.raw_buffer_format` to see which dataformat is used. **The returned type and dataformat are + subject to change.** The screen buffer is row-major. - For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf), - or the Pan Docs under [LCD Position and Scrolling](http://bgb.bircd.org/pandocs.htm#lcdpositionandscrolling). + Use this, only if you need to bypass the overhead of `Screen.image` or `Screen.ndarray`. Returns ------- - tuple: - Returns the tuple of registers ((SCX, SCY), (WX - 7, WY)) + bytes: + 92160 bytes of screen data in a `bytes` object. """ - return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos()) - - def tilemap_position_list(self): + self.raw_buffer_dims = self.mb.lcd.renderer.buffer_dims """ - This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the - screen buffer. These parameters are often used for visual effects, and some games will reset the registers at - the end of each call to `pyboy.PyBoy.tick()`. For such games, `Screen.tilemap_position` becomes useless. - - See `Screen.tilemap_position` for more information. + Returns the dimensions of the raw screen buffer. The screen buffer is row-major. Returns ------- - list: - Nested list of SCX, SCY, WX and WY for each scanline (144x4). Returns (0, 0, 0, 0) when LCD is off. + tuple: + A two-tuple of the buffer dimensions. E.g. (144, 160). """ - if self.mb.lcd._LCDC.lcd_enable: - return [[line[0], line[1], line[2], line[3]] for line in self.mb.lcd.renderer._scanlineparameters] - else: - return [[0, 0, 0, 0] for line in range(144)] - - def raw_screen_buffer(self): + self.raw_buffer_format = self.mb.lcd.renderer.color_format """ - Provides a raw, unfiltered `bytes` object with the data from the screen. Check - `Screen.raw_screen_buffer_format` to see which dataformat is used. The returned type and dataformat are - subject to change. - - Use this, only if you need to bypass the overhead of `Screen.screen_image` or `Screen.screen_ndarray`. + Returns the color format of the raw screen buffer. **This format is subject to change.** Returns ------- - bytes: - 92160 bytes of screen data in a `bytes` object. + str: + Color format of the raw screen buffer. E.g. 'RGBA'. """ - return self.mb.lcd.renderer._screenbuffer_raw.tobytes() - - def raw_screen_buffer_dims(self): + self.image = None """ - Returns the dimensions of the raw screen buffer. + Reference to a PIL Image from the screen buffer. **Remember to copy, resize or convert this object** if you + intend to store it. The backing buffer will update, but it will be the same `PIL.Image` object. + + The screen buffer is internally row-major, but PIL hides this. + + 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.api` features, [Pan Docs](https://gbdev.io/pandocs/) on tiles/sprites, + and join our Discord channel for more help. Returns ------- - tuple: - A two-tuple of the buffer dimensions. E.g. (160, 144). + PIL.Image: + RGB image of (160, 144) pixels """ - return self.mb.lcd.renderer.buffer_dims + if not Image: + logger.warning("Cannot generate screen image. Missing dependency \"Pillow\".") - def raw_screen_buffer_format(self): + else: + self.image = Image.frombuffer( + self.mb.lcd.renderer.color_format, self.mb.lcd.renderer.buffer_dims[::-1], + self.mb.lcd.renderer._screenbuffer_raw + ) + + self.ndarray = np.frombuffer( + self.mb.lcd.renderer._screenbuffer_raw, + dtype=np.uint8, + ).reshape(ROWS, COLS, 4) """ - Returns the color format of the raw screen buffer. + References the screen data in NumPy format. **Remember to copy this object** if you intend to store it. + The backing buffer will update, but it will be the same `ndarray` object. + + The format is given by `pyboy.api.screen.Screen.raw_buffer_format`. The screen buffer is row-major. Returns ------- - str: - Color format of the raw screen buffer. E.g. 'RGB'. + numpy.ndarray: + Screendata in `ndarray` of bytes with shape (144, 160, 3) """ - return self.mb.lcd.renderer.color_format - def screen_ndarray(self): + @property + def tilemap_position_list(self): """ - Provides the screen data in NumPy format. The dataformat is always RGB. + This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the + screen buffer. These parameters are often used for visual effects, and some games will reset the registers at + the end of each call to `pyboy.PyBoy.tick()`. + + See `Screen.get_tilemap_position` for more information. Returns ------- - numpy.ndarray: - Screendata in `ndarray` of bytes with shape (160, 144, 3) + list: + Nested list of SCX, SCY, WX and WY for each scanline (144x4). Returns (0, 0, 0, 0) when LCD is off. """ - return np.frombuffer(self.mb.lcd.renderer._screenbuffer_raw, dtype=np.uint8).reshape(ROWS, COLS, 4)[:, :, 1:] + # self.tilemap_position_list = np.asarray(self.mb.lcd.renderer._scanlineparameters, dtype=np.uint8).reshape(144, 5)[:, :4] + # self.tilemap_position_list = self.mb.lcd.renderer._scanlineparameters - def screen_image(self): + # # return self.mb.lcd.renderer._scanlineparameters + if self.mb.lcd._LCDC.lcd_enable: + return [[line[0], line[1], line[2], line[3]] for line in self.mb.lcd.renderer._scanlineparameters] + else: + return [[0, 0, 0, 0] for line in range(144)] + + def get_tilemap_position(self): """ - Generates a PIL Image from the screen buffer. + These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note + that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer + to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site + of the tile map. - 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. + For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf), + or the Pan Docs under [LCD Position and Scrolling](https://gbdev.io/pandocs/Scrolling.html). Returns ------- - PIL.Image: - RGB image of (160, 144) pixels + tuple: + Returns the tuple of registers ((SCX, SCY), (WX - 7, WY)) """ - if not Image: - logger.error("Cannot generate screen image. Missing dependency \"Pillow\".") - return None - - # NOTE: Might have room for performance improvement - # It's not possible to use the following, as the byte-order (endianess) isn't supported in Pillow - # Image.frombytes('RGBA', self.buffer_dims, self.screen_buffer()).show() - # FIXME: FORMAT IS BGR NOT RGB!!! - return Image.fromarray(self.screen_ndarray()[:, :, [2, 1, 0]], "RGB") + return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos())
@@ -176,7 +182,7 @@

Module pyboy.botsupport.screen

Classes

-
+
class Screen (mb)
@@ -184,8 +190,7 @@

Classes

As part of the emulation, we generate a screen buffer in 32-bit RGBA format. This class has several helper methods to make it possible to read this buffer out.

If you're making an AI or bot, it's highly recommended to not use this class for detecting objects on the screen. -It's much more efficient to use BotSupportManager.tilemap_background(), BotSupportManager.tilemap_window(), and -BotSupportManager.sprite() instead.

+It's much more efficient to use pyboy.tilemap_background, pyboy.tilemap_window, and pyboy.sprite instead.

Expand source code @@ -196,165 +201,131 @@

Classes

to make it possible to read this buffer out. If you're making an AI or bot, it's highly recommended to _not_ use this class for detecting objects on the screen. - It's much more efficient to use `pyboy.botsupport.BotSupportManager.tilemap_background`, `pyboy.botsupport.BotSupportManager.tilemap_window`, and - `pyboy.botsupport.BotSupportManager.sprite` instead. + It's much more efficient to use `pyboy.tilemap_background`, `pyboy.tilemap_window`, and `pyboy.sprite` instead. """ def __init__(self, mb): self.mb = mb - def tilemap_position(self): + self.raw_buffer = self.mb.lcd.renderer._screenbuffer """ - These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note - that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer - to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site - of the tile map. + Provides a raw, unfiltered `bytes` object with the data from the screen. Check + `Screen.raw_buffer_format` to see which dataformat is used. **The returned type and dataformat are + subject to change.** The screen buffer is row-major. - For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf), - or the Pan Docs under [LCD Position and Scrolling](http://bgb.bircd.org/pandocs.htm#lcdpositionandscrolling). + Use this, only if you need to bypass the overhead of `Screen.image` or `Screen.ndarray`. Returns ------- - tuple: - Returns the tuple of registers ((SCX, SCY), (WX - 7, WY)) + bytes: + 92160 bytes of screen data in a `bytes` object. """ - return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos()) - - def tilemap_position_list(self): + self.raw_buffer_dims = self.mb.lcd.renderer.buffer_dims """ - This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the - screen buffer. These parameters are often used for visual effects, and some games will reset the registers at - the end of each call to `pyboy.PyBoy.tick()`. For such games, `Screen.tilemap_position` becomes useless. - - See `Screen.tilemap_position` for more information. + Returns the dimensions of the raw screen buffer. The screen buffer is row-major. Returns ------- - list: - Nested list of SCX, SCY, WX and WY for each scanline (144x4). Returns (0, 0, 0, 0) when LCD is off. + tuple: + A two-tuple of the buffer dimensions. E.g. (144, 160). """ - if self.mb.lcd._LCDC.lcd_enable: - return [[line[0], line[1], line[2], line[3]] for line in self.mb.lcd.renderer._scanlineparameters] - else: - return [[0, 0, 0, 0] for line in range(144)] - - def raw_screen_buffer(self): + self.raw_buffer_format = self.mb.lcd.renderer.color_format """ - Provides a raw, unfiltered `bytes` object with the data from the screen. Check - `Screen.raw_screen_buffer_format` to see which dataformat is used. The returned type and dataformat are - subject to change. - - Use this, only if you need to bypass the overhead of `Screen.screen_image` or `Screen.screen_ndarray`. + Returns the color format of the raw screen buffer. **This format is subject to change.** Returns ------- - bytes: - 92160 bytes of screen data in a `bytes` object. + str: + Color format of the raw screen buffer. E.g. 'RGBA'. """ - return self.mb.lcd.renderer._screenbuffer_raw.tobytes() - - def raw_screen_buffer_dims(self): + self.image = None """ - Returns the dimensions of the raw screen buffer. + Reference to a PIL Image from the screen buffer. **Remember to copy, resize or convert this object** if you + intend to store it. The backing buffer will update, but it will be the same `PIL.Image` object. + + The screen buffer is internally row-major, but PIL hides this. + + 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.api` features, [Pan Docs](https://gbdev.io/pandocs/) on tiles/sprites, + and join our Discord channel for more help. Returns ------- - tuple: - A two-tuple of the buffer dimensions. E.g. (160, 144). + PIL.Image: + RGB image of (160, 144) pixels """ - return self.mb.lcd.renderer.buffer_dims + if not Image: + logger.warning("Cannot generate screen image. Missing dependency \"Pillow\".") - def raw_screen_buffer_format(self): + else: + self.image = Image.frombuffer( + self.mb.lcd.renderer.color_format, self.mb.lcd.renderer.buffer_dims[::-1], + self.mb.lcd.renderer._screenbuffer_raw + ) + + self.ndarray = np.frombuffer( + self.mb.lcd.renderer._screenbuffer_raw, + dtype=np.uint8, + ).reshape(ROWS, COLS, 4) """ - Returns the color format of the raw screen buffer. + References the screen data in NumPy format. **Remember to copy this object** if you intend to store it. + The backing buffer will update, but it will be the same `ndarray` object. + + The format is given by `pyboy.api.screen.Screen.raw_buffer_format`. The screen buffer is row-major. Returns ------- - str: - Color format of the raw screen buffer. E.g. 'RGB'. + numpy.ndarray: + Screendata in `ndarray` of bytes with shape (144, 160, 3) """ - return self.mb.lcd.renderer.color_format - def screen_ndarray(self): + @property + def tilemap_position_list(self): """ - Provides the screen data in NumPy format. The dataformat is always RGB. + This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the + screen buffer. These parameters are often used for visual effects, and some games will reset the registers at + the end of each call to `pyboy.PyBoy.tick()`. + + See `Screen.get_tilemap_position` for more information. Returns ------- - numpy.ndarray: - Screendata in `ndarray` of bytes with shape (160, 144, 3) + list: + Nested list of SCX, SCY, WX and WY for each scanline (144x4). Returns (0, 0, 0, 0) when LCD is off. """ - return np.frombuffer(self.mb.lcd.renderer._screenbuffer_raw, dtype=np.uint8).reshape(ROWS, COLS, 4)[:, :, 1:] + # self.tilemap_position_list = np.asarray(self.mb.lcd.renderer._scanlineparameters, dtype=np.uint8).reshape(144, 5)[:, :4] + # self.tilemap_position_list = self.mb.lcd.renderer._scanlineparameters - def screen_image(self): + # # return self.mb.lcd.renderer._scanlineparameters + if self.mb.lcd._LCDC.lcd_enable: + return [[line[0], line[1], line[2], line[3]] for line in self.mb.lcd.renderer._scanlineparameters] + else: + return [[0, 0, 0, 0] for line in range(144)] + + def get_tilemap_position(self): """ - Generates a PIL Image from the screen buffer. + These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note + that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer + to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site + of the tile map. - 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. + For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf), + or the Pan Docs under [LCD Position and Scrolling](https://gbdev.io/pandocs/Scrolling.html). Returns ------- - PIL.Image: - RGB image of (160, 144) pixels + tuple: + Returns the tuple of registers ((SCX, SCY), (WX - 7, WY)) """ - if not Image: - logger.error("Cannot generate screen image. Missing dependency \"Pillow\".") - return None - - # NOTE: Might have room for performance improvement - # It's not possible to use the following, as the byte-order (endianess) isn't supported in Pillow - # Image.frombytes('RGBA', self.buffer_dims, self.screen_buffer()).show() - # FIXME: FORMAT IS BGR NOT RGB!!! - return Image.fromarray(self.screen_ndarray()[:, :, [2, 1, 0]], "RGB")
+ return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos())
-

Methods

+

Instance variables

-
-def tilemap_position(self) -
-
-

These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note -that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer -to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site -of the tile map.

-

For more details, see "7.4 Viewport" in the report, -or the Pan Docs under LCD Position and Scrolling.

-

Returns

-
-
tuple:
-
Returns the tuple of registers ((SCX, SCY), (WX - 7, WY))
-
-
- -Expand source code - -
def tilemap_position(self):
-    """
-    These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note
-    that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer
-    to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site
-    of the tile map.
-
-    For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf),
-    or the Pan Docs under [LCD Position and Scrolling](http://bgb.bircd.org/pandocs.htm#lcdpositionandscrolling).
-
-    Returns
-    -------
-    tuple:
-        Returns the tuple of registers ((SCX, SCY), (WX - 7, WY))
-    """
-    return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos())
-
-
-
-def tilemap_position_list(self) -
+
var tilemap_position_list

This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the screen buffer. These parameters are often used for visual effects, and some games will reset the registers at -the end of each call to PyBoy.tick(). For such games, Screen.tilemap_position() becomes useless.

-

See Screen.tilemap_position() for more information.

+the end of each call to PyBoy.tick().

+

See Screen.get_tilemap_position() for more information.

Returns

list:
@@ -364,175 +335,123 @@

Returns

Expand source code -
def tilemap_position_list(self):
+
@property
+def tilemap_position_list(self):
     """
     This function provides the screen (SCX, SCY) and window (WX. WY) position for each horizontal line in the
     screen buffer. These parameters are often used for visual effects, and some games will reset the registers at
-    the end of each call to `pyboy.PyBoy.tick()`. For such games, `Screen.tilemap_position` becomes useless.
+    the end of each call to `pyboy.PyBoy.tick()`.
 
-    See `Screen.tilemap_position` for more information.
+    See `Screen.get_tilemap_position` for more information.
 
     Returns
     -------
     list:
         Nested list of SCX, SCY, WX and WY for each scanline (144x4). Returns (0, 0, 0, 0) when LCD is off.
     """
+    # self.tilemap_position_list = np.asarray(self.mb.lcd.renderer._scanlineparameters, dtype=np.uint8).reshape(144, 5)[:, :4]
+    # self.tilemap_position_list = self.mb.lcd.renderer._scanlineparameters
+
+    # # return self.mb.lcd.renderer._scanlineparameters
     if self.mb.lcd._LCDC.lcd_enable:
         return [[line[0], line[1], line[2], line[3]] for line in self.mb.lcd.renderer._scanlineparameters]
     else:
         return [[0, 0, 0, 0] for line in range(144)]
-
-def raw_screen_buffer(self) -
+
var raw_buffer

Provides a raw, unfiltered bytes object with the data from the screen. Check -Screen.raw_screen_buffer_format() to see which dataformat is used. The returned type and dataformat are -subject to change.

-

Use this, only if you need to bypass the overhead of Screen.screen_image() or Screen.screen_ndarray().

+Screen.raw_buffer_format to see which dataformat is used. The returned type and dataformat are +subject to change. The screen buffer is row-major.

+

Use this, only if you need to bypass the overhead of Screen.image or Screen.ndarray.

Returns

bytes:
92160 bytes of screen data in a bytes object.
-
- -Expand source code - -
def raw_screen_buffer(self):
-    """
-    Provides a raw, unfiltered `bytes` object with the data from the screen. Check
-    `Screen.raw_screen_buffer_format` to see which dataformat is used. The returned type and dataformat are
-    subject to change.
-
-    Use this, only if you need to bypass the overhead of `Screen.screen_image` or `Screen.screen_ndarray`.
-
-    Returns
-    -------
-    bytes:
-        92160 bytes of screen data in a `bytes` object.
-    """
-    return self.mb.lcd.renderer._screenbuffer_raw.tobytes()
-
-
-def raw_screen_buffer_dims(self) -
+
var raw_buffer_dims
-

Returns the dimensions of the raw screen buffer.

+

Returns the dimensions of the raw screen buffer. The screen buffer is row-major.

Returns

tuple:
-
A two-tuple of the buffer dimensions. E.g. (160, 144).
+
A two-tuple of the buffer dimensions. E.g. (144, 160).
-
- -Expand source code - -
def raw_screen_buffer_dims(self):
-    """
-    Returns the dimensions of the raw screen buffer.
-
-    Returns
-    -------
-    tuple:
-        A two-tuple of the buffer dimensions. E.g. (160, 144).
-    """
-    return self.mb.lcd.renderer.buffer_dims
-
-
-def raw_screen_buffer_format(self) -
+
var raw_buffer_format
-

Returns the color format of the raw screen buffer.

+

Returns the color format of the raw screen buffer. This format is subject to change.

Returns

str:
-
Color format of the raw screen buffer. E.g. 'RGB'.
+
Color format of the raw screen buffer. E.g. 'RGBA'.
-
- -Expand source code - -
def raw_screen_buffer_format(self):
-    """
-    Returns the color format of the raw screen buffer.
-
-    Returns
-    -------
-    str:
-        Color format of the raw screen buffer. E.g. 'RGB'.
-    """
-    return self.mb.lcd.renderer.color_format
-
-
-def screen_ndarray(self) -
+
var image
-

Provides the screen data in NumPy format. The dataformat is always RGB.

+

Reference to a PIL Image from the screen buffer. Remember to copy, resize or convert this object if you +intend to store it. The backing buffer will update, but it will be the same PIL.Image object.

+

The screen buffer is internally row-major, but PIL hides this.

+

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.api features, Pan Docs on tiles/sprites, +and join our Discord channel for more help.

+

Returns

+
+
PIL.Image:
+
RGB image of (160, 144) pixels
+
+
+
var ndarray
+
+

References the screen data in NumPy format. Remember to copy this object if you intend to store it. +The backing buffer will update, but it will be the same ndarray object.

+

The format is given by Screen.raw_buffer_format. The screen buffer is row-major.

Returns

numpy.ndarray:
-
Screendata in ndarray of bytes with shape (160, 144, 3)
+
Screendata in ndarray of bytes with shape (144, 160, 3)
-
- -Expand source code - -
def screen_ndarray(self):
-    """
-    Provides the screen data in NumPy format. The dataformat is always RGB.
-
-    Returns
-    -------
-    numpy.ndarray:
-        Screendata in `ndarray` of bytes with shape (160, 144, 3)
-    """
-    return np.frombuffer(self.mb.lcd.renderer._screenbuffer_raw, dtype=np.uint8).reshape(ROWS, COLS, 4)[:, :, 1:]
-
-
-def screen_image(self) +
+

Methods

+
+
+def get_tilemap_position(self)
-

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.

+

These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note +that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer +to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site +of the tile map.

+

For more details, see "7.4 Viewport" in the report, +or the Pan Docs under LCD Position and Scrolling.

Returns

-
PIL.Image:
-
RGB image of (160, 144) pixels
+
tuple:
+
Returns the tuple of registers ((SCX, SCY), (WX - 7, WY))
Expand source code -
def screen_image(self):
+
def get_tilemap_position(self):
     """
-    Generates a PIL Image from the screen buffer.
+    These coordinates define the offset in the tile map from where the top-left corner of the screen is place. Note
+    that the tile map defines 256x256 pixels, but the screen can only show 160x144 pixels. When the offset is closer
+    to the right or bottom edge than 160x144 pixels, the screen will wrap around and render from the opposite site
+    of the tile map.
 
-    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.
+    For more details, see "7.4 Viewport" in the [report](https://github.com/Baekalfen/PyBoy/raw/master/extras/PyBoy.pdf),
+    or the Pan Docs under [LCD Position and Scrolling](https://gbdev.io/pandocs/Scrolling.html).
 
     Returns
     -------
-    PIL.Image:
-        RGB image of (160, 144) pixels
+    tuple:
+        Returns the tuple of registers ((SCX, SCY), (WX - 7, WY))
     """
-    if not Image:
-        logger.error("Cannot generate screen image. Missing dependency \"Pillow\".")
-        return None
-
-    # NOTE: Might have room for performance improvement
-    # It's not possible to use the following, as the byte-order (endianess) isn't supported in Pillow
-    # Image.frombytes('RGBA', self.buffer_dims, self.screen_buffer()).show()
-    # FIXME: FORMAT IS BGR NOT RGB!!!
-    return Image.fromarray(self.screen_ndarray()[:, :, [2, 1, 0]], "RGB")
+ return (self.mb.lcd.getviewport(), self.mb.lcd.getwindowpos())
@@ -548,21 +467,21 @@

Index

  • Super-module

  • Classes

    diff --git a/docs/botsupport/sprite.html b/docs/api/sprite.html similarity index 77% rename from docs/botsupport/sprite.html rename to docs/api/sprite.html index 7c6c40099..7c119dff7 100644 --- a/docs/botsupport/sprite.html +++ b/docs/api/sprite.html @@ -4,7 +4,7 @@ -pyboy.botsupport.sprite API documentation +pyboy.api.sprite API documentation @@ -18,7 +18,7 @@
    -

    Module pyboy.botsupport.sprite

    +

    Module pyboy.api.sprite

    This class presents an interface to the sprites held in the OAM data on the Game Boy.

    @@ -56,7 +56,7 @@

    Module pyboy.botsupport.sprite

    call to `pyboy.PyBoy.tick`, so make sure to verify the `Sprite.tile_identifier` hasn't changed. By knowing the tile identifiers of players, enemies, power-ups and so on, you'll be able to search for them - using `pyboy.botsupport.BotSupportManager.sprite_by_tile_identifier` and feed it to your bot or AI. + using `pyboy.sprite_by_tile_identifier` and feed it to your bot or AI. """ assert 0 <= sprite_index < SPRITES, f"Sprite index of {sprite_index} is out of range (0-{SPRITES})" self.mb = mb @@ -99,7 +99,7 @@

    Module pyboy.botsupport.sprite

    self.tile_identifier = self.mb.getitem(OAM_OFFSET + self._offset + 2) """ The identifier of the tile the sprite uses. To get a better representation, see the method - `pyboy.botsupport.sprite.Sprite.tiles`. + `pyboy.api.sprite.Sprite.tiles`. For double-height sprites, this will only give the identifier of the first tile. The second tile will always be the one immediately following the first (`tile_identifier + 1`). @@ -114,7 +114,7 @@

    Module pyboy.botsupport.sprite

    self.attr_obj_bg_priority = _bit(attr, 7) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -125,7 +125,7 @@

    Module pyboy.botsupport.sprite

    self.attr_y_flip = _bit(attr, 6) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -136,7 +136,7 @@

    Module pyboy.botsupport.sprite

    self.attr_x_flip = _bit(attr, 5) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -144,10 +144,21 @@

    Module pyboy.botsupport.sprite

    The state of the bit in the attributes lookup. """ - self.attr_palette_number = _bit(attr, 4) + self.attr_palette_number = 0 """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). + + Returns + ------- + int: + The state of the bit(s) in the attributes lookup. + """ + + self.attr_cgb_bank_number = 0 + """ + To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -155,8 +166,14 @@

    Module pyboy.botsupport.sprite

    The state of the bit in the attributes lookup. """ + if self.mb.cgb: + self.attr_palette_number = attr & 0b111 + self.attr_cgb_bank_number = _bit(attr, 3) + else: + self.attr_palette_number = _bit(attr, 4) + LCDC = LCDCRegister(self.mb.getitem(LCDC_OFFSET)) - sprite_height = 16 if LCDC.sprite_height else 8 + sprite_height = 16 if LCDC._get_sprite_height() else 8 self.shape = (8, sprite_height) """ Sprites can be set to be 8x8 or 8x16 pixels (16 pixels tall). This is defined globally for the rendering @@ -176,12 +193,12 @@

    Module pyboy.botsupport.sprite

    immediately following the identifier given, and render it below the first. More information can be found in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam) + (OAM)](https://gbdev.io/pandocs/OAM.html) Returns ------- list: - A list of `pyboy.botsupport.tile.Tile` object(s) representing the graphics data for the sprite + A list of `pyboy.api.tile.Tile` object(s) representing the graphics data for the sprite """ if sprite_height == 16: self.tiles += [Tile(self.mb, self.tile_identifier + 1)] @@ -222,7 +239,7 @@

    Module pyboy.botsupport.sprite

    Classes

    -
    +
    class Sprite (mb, sprite_index)
    @@ -234,9 +251,9 @@

    Classes

    grid-size of 8x8 pixels precision, and can have no transparency.

    Sprites on the Game Boy are tightly associated with tiles. The sprites can be seen as "upgraded" tiles, as the image data still refers back to one (or two) tiles. The tile that a sprite will show, can change between each -call to PyBoy.tick(), so make sure to verify the Sprite.tile_identifier hasn't changed.

    +call to PyBoy.tick(), so make sure to verify the Sprite.tile_identifier hasn't changed.

    By knowing the tile identifiers of players, enemies, power-ups and so on, you'll be able to search for them -using BotSupportManager.sprite_by_tile_identifier() and feed it to your bot or AI.

    +using pyboy.sprite_by_tile_identifier and feed it to your bot or AI.

    Expand source code @@ -257,7 +274,7 @@

    Classes

    call to `pyboy.PyBoy.tick`, so make sure to verify the `Sprite.tile_identifier` hasn't changed. By knowing the tile identifiers of players, enemies, power-ups and so on, you'll be able to search for them - using `pyboy.botsupport.BotSupportManager.sprite_by_tile_identifier` and feed it to your bot or AI. + using `pyboy.sprite_by_tile_identifier` and feed it to your bot or AI. """ assert 0 <= sprite_index < SPRITES, f"Sprite index of {sprite_index} is out of range (0-{SPRITES})" self.mb = mb @@ -300,7 +317,7 @@

    Classes

    self.tile_identifier = self.mb.getitem(OAM_OFFSET + self._offset + 2) """ The identifier of the tile the sprite uses. To get a better representation, see the method - `pyboy.botsupport.sprite.Sprite.tiles`. + `pyboy.api.sprite.Sprite.tiles`. For double-height sprites, this will only give the identifier of the first tile. The second tile will always be the one immediately following the first (`tile_identifier + 1`). @@ -315,7 +332,7 @@

    Classes

    self.attr_obj_bg_priority = _bit(attr, 7) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -326,7 +343,7 @@

    Classes

    self.attr_y_flip = _bit(attr, 6) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -337,7 +354,7 @@

    Classes

    self.attr_x_flip = _bit(attr, 5) """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -345,10 +362,21 @@

    Classes

    The state of the bit in the attributes lookup. """ - self.attr_palette_number = _bit(attr, 4) + self.attr_palette_number = 0 """ To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam). + (OAM)](https://gbdev.io/pandocs/OAM.html). + + Returns + ------- + int: + The state of the bit(s) in the attributes lookup. + """ + + self.attr_cgb_bank_number = 0 + """ + To better understand this values, look in the [Pan Docs: VRAM Sprite Attribute Table + (OAM)](https://gbdev.io/pandocs/OAM.html). Returns ------- @@ -356,8 +384,14 @@

    Classes

    The state of the bit in the attributes lookup. """ + if self.mb.cgb: + self.attr_palette_number = attr & 0b111 + self.attr_cgb_bank_number = _bit(attr, 3) + else: + self.attr_palette_number = _bit(attr, 4) + LCDC = LCDCRegister(self.mb.getitem(LCDC_OFFSET)) - sprite_height = 16 if LCDC.sprite_height else 8 + sprite_height = 16 if LCDC._get_sprite_height() else 8 self.shape = (8, sprite_height) """ Sprites can be set to be 8x8 or 8x16 pixels (16 pixels tall). This is defined globally for the rendering @@ -377,12 +411,12 @@

    Classes

    immediately following the identifier given, and render it below the first. More information can be found in the [Pan Docs: VRAM Sprite Attribute Table - (OAM)](http://bgb.bircd.org/pandocs.htm#vramspriteattributetableoam) + (OAM)](https://gbdev.io/pandocs/OAM.html) Returns ------- list: - A list of `pyboy.botsupport.tile.Tile` object(s) representing the graphics data for the sprite + A list of `pyboy.api.tile.Tile` object(s) representing the graphics data for the sprite """ if sprite_height == 16: self.tiles += [Tile(self.mb, self.tile_identifier + 1)] @@ -410,7 +444,7 @@

    Classes

    Instance variables

    -
    var y
    +
    var y

    The Y-coordinate on the screen to show the Sprite. The (x,y) coordinate points to the top-left corner of the sprite.

    Returns

    @@ -419,7 +453,7 @@

    Returns

    Y-coordinate
-
var x
+
var x

The X-coordinate on the screen to show the Sprite. The (x,y) coordinate points to the top-left corner of the sprite.

Returns

@@ -428,10 +462,10 @@

Returns

X-coordinate
-
var tile_identifier
+
var tile_identifier

The identifier of the tile the sprite uses. To get a better representation, see the method -Sprite.tiles.

+Sprite.tiles.

For double-height sprites, this will only give the identifier of the first tile. The second tile will always be the one immediately following the first (tile_identifier + 1).

Returns

@@ -440,9 +474,9 @@

Returns

unsigned tile index
-
var attr_obj_bg_priority
+
var attr_obj_bg_priority
-

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table +

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table (OAM).

Returns

@@ -450,9 +484,9 @@

Returns

The state of the bit in the attributes lookup.
-
var attr_y_flip
+
var attr_y_flip
-

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table +

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table (OAM).

Returns

@@ -460,9 +494,9 @@

Returns

The state of the bit in the attributes lookup.
-
var attr_x_flip
+
var attr_x_flip
-

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table +

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table (OAM).

Returns

@@ -470,9 +504,19 @@

Returns

The state of the bit in the attributes lookup.
-
var attr_palette_number
+
var attr_palette_number
+
+

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table +(OAM).

+

Returns

+
+
int:
+
The state of the bit(s) in the attributes lookup.
+
+
+
var attr_cgb_bank_number
-

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table +

To better understand this values, look in the Pan Docs: VRAM Sprite Attribute Table (OAM).

Returns

@@ -480,7 +524,7 @@

Returns

The state of the bit in the attributes lookup.
-
var shape
+
var shape

Sprites can be set to be 8x8 or 8x16 pixels (16 pixels tall). This is defined globally for the rendering hardware, so it's either all sprites using 8x16 pixels, or all sprites using 8x8 pixels.

@@ -488,20 +532,20 @@

Returns

(int, int): The width and height of the sprite.

-
var tiles
+
var tiles

The Game Boy support sprites of single-height (8x8 pixels) and double-height (8x16 pixels).

In the single-height format, one tile is used. For double-height sprites, the Game Boy will also use the tile immediately following the identifier given, and render it below the first.

-

More information can be found in the Pan Docs: VRAM Sprite Attribute Table +

More information can be found in the Pan Docs: VRAM Sprite Attribute Table (OAM)

Returns

list:
-
A list of Tile object(s) representing the graphics data for the sprite
+
A list of Tile object(s) representing the graphics data for the sprite
-
var on_screen
+
var on_screen

To disable sprites from being rendered on screen, developers will place the sprite outside the area of the screen. This is often a good way to determine if the sprite is inactive.

@@ -526,24 +570,25 @@

Index

  • Super-module

  • Classes

    diff --git a/docs/botsupport/tile.html b/docs/api/tile.html similarity index 62% rename from docs/botsupport/tile.html rename to docs/api/tile.html index 54fdfe878..e18332394 100644 --- a/docs/botsupport/tile.html +++ b/docs/api/tile.html @@ -4,9 +4,9 @@ -pyboy.botsupport.tile API documentation +pyboy.api.tile API documentation +`pyboy.api.sprite.Sprite` and …" /> @@ -19,11 +19,11 @@
    -

    Module pyboy.botsupport.tile

    +

    Module pyboy.api.tile

    The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used both for -Sprite and TileMap, when refering to graphics.

    +Sprite and TileMap, when refering to graphics.

    Expand source code @@ -34,50 +34,58 @@

    Module pyboy.botsupport.tile

    # """ The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used both for -`pyboy.botsupport.sprite.Sprite` and `pyboy.botsupport.tilemap.TileMap`, when refering to graphics. +`pyboy.api.sprite.Sprite` and `pyboy.api.tilemap.TileMap`, when refering to graphics. """ -import logging - import numpy as np + +import pyboy from pyboy import utils -from .constants import LOW_TILEDATA, VRAM_OFFSET +from .constants import LOW_TILEDATA, TILES, TILES_CGB, VRAM_OFFSET -logger = logging.getLogger(__name__) +logger = pyboy.logging.get_logger(__name__) try: from PIL import Image except ImportError: Image = None +try: + from cython import compiled + cythonmode = compiled +except ImportError: + cythonmode = False + class Tile: def __init__(self, mb, identifier): """ The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used for - `pyboy.botsupport.BotSupportManager.tile`, `pyboy.botsupport.sprite.Sprite` and `pyboy.botsupport.tilemap.TileMap`, when - refering to graphics. + `pyboy.tile`, `pyboy.api.sprite.Sprite` and `pyboy.api.tilemap.TileMap`, when refering to graphics. This class is not meant to be instantiated by developers reading this documentation, but it will be created - internally and returned by `pyboy.botsupport.sprite.Sprite.tiles` and - `pyboy.botsupport.tilemap.TileMap.tile`. + internally and returned by `pyboy.api.sprite.Sprite.tiles` and + `pyboy.api.tilemap.TileMap.tile`. The data of this class is static, apart from the image data, which is loaded from the Game Boy's memory when needed. Beware that the graphics for the tile can change between each call to `pyboy.PyBoy.tick`. """ self.mb = mb - assert 0 <= identifier < 384, "Identifier out of range" + if self.mb.cgb: + assert 0 <= identifier < TILES_CGB, "Identifier out of range" + else: + assert 0 <= identifier < TILES, "Identifier out of range" - self.data_address = LOW_TILEDATA + (16*identifier) + self.data_address = LOW_TILEDATA + (16 * (identifier%TILES)) """ The tile data is defined in a specific area of the Game Boy. This function returns the address of the tile data - corresponding to the tile identifier. It is advised to use `pyboy.botsupport.tile.Tile.image` or one of the + corresponding to the tile identifier. It is advised to use `pyboy.api.tile.Tile.image` or one of the other `image`-functions if you want to view the tile. You can read how the data is read in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Returns ------- @@ -85,10 +93,16 @@

    Module pyboy.botsupport.tile

    address in VRAM where tile data starts """ - self.tile_identifier = (self.data_address - LOW_TILEDATA) // 16 + if identifier < TILES: + self.vram_bank = 0 + else: + self.vram_bank = 1 + + self.tile_identifier = identifier """ The Game Boy has a slightly complicated indexing system for tiles. This identifier unifies the otherwise - complicated indexing system on the Game Boy into a single range of 0-383 (both included). + complicated indexing system on the Game Boy into a single range of 0-383 (both included) or 0-767 for Game Boy + Color. Returns ------- @@ -106,9 +120,19 @@

    Module pyboy.botsupport.tile

    The width and height of the tile. """ + self.raw_buffer_format = self.mb.lcd.renderer.color_format + """ + Returns the color format of the raw screen buffer. + + Returns + ------- + str: + Color format of the raw screen buffer. E.g. 'RGBA'. + """ + def image(self): """ - Use this function to get an easy-to-use `PIL.Image` object of the tile. The image is 8x8 pixels in RGBA colors. + Use this function to get an `PIL.Image` object of the tile. The image is 8x8 pixels. The format or "mode" might change at any time. Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`. @@ -120,13 +144,17 @@

    Module pyboy.botsupport.tile

    if Image is None: logger.error(f"{__name__}: Missing dependency \"Pillow\".") return None - return Image.frombytes("RGBA", (8, 8), bytes(self.image_data())) + + if cythonmode: + return Image.fromarray(self._image_data().base, mode=self.raw_buffer_format) + else: + return Image.frombytes(self.raw_buffer_format, (8, 8), self._image_data()) def image_ndarray(self): """ - Use this function to get an easy-to-use `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4) - and each value is of `numpy.uint8`. The values corresponds to and RGBA image of 8x8 pixels with each sub-color - in a separate cell. + Use this function to get an `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4) + and each value is of `numpy.uint8`. The values corresponds to an image of 8x8 pixels with each sub-color + in a separate cell. The format is given by `pyboy.api.tile.Tile.raw_buffer_format`. Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`. @@ -135,9 +163,11 @@

    Module pyboy.botsupport.tile

    numpy.ndarray : Array of shape (8, 8, 4) with data type of `numpy.uint8`. """ - return np.asarray(self.image_data()).view(dtype=np.uint8).reshape(8, 8, 4) + # The data is laid out as (X, red, green, blue), where X is currently always zero, but this is not guarenteed + # across versions of PyBoy. + return np.asarray(self._image_data()).view(dtype=np.uint8).reshape(8, 8, 4) - def image_data(self): + def _image_data(self): """ Use this function to get the raw tile data. The data is a `memoryview` corresponding to 8x8 pixels in RGBA colors. @@ -147,23 +177,25 @@

    Module pyboy.botsupport.tile

    Returns ------- memoryview : - Image data of tile in 8x8 pixels and RGBA colors. + Image data of tile in 8x8 pixels and RGB colors. """ self.data = np.zeros((8, 8), dtype=np.uint32) for k in range(0, 16, 2): # 2 bytes for each line - byte1 = self.mb.lcd.VRAM0[self.data_address + k - VRAM_OFFSET] - byte2 = self.mb.lcd.VRAM0[self.data_address + k + 1 - VRAM_OFFSET] + if self.vram_bank == 0: + byte1 = self.mb.lcd.VRAM0[self.data_address + k - VRAM_OFFSET] + byte2 = self.mb.lcd.VRAM0[self.data_address + k + 1 - VRAM_OFFSET] + else: + byte1 = self.mb.lcd.VRAM1[self.data_address + k - VRAM_OFFSET] + byte2 = self.mb.lcd.VRAM1[self.data_address + k + 1 - VRAM_OFFSET] for x in range(8): colorcode = utils.color_code(byte1, byte2, 7 - x) - # NOTE: ">> 8 | 0xFF000000" to keep compatibility with earlier code - old_A_format = 0xFF000000 - self.data[k // 2][x] = self.mb.lcd.BGP.getcolor(colorcode) >> 8 | old_A_format - + alpha_mask = 0x00FFFFFF + self.data[k // 2][x] = self.mb.lcd.BGP.getcolor(colorcode) & alpha_mask return self.data def __eq__(self, other): - return self.data_address == other.data_address + return self.data_address == other.data_address and self.vram_bank == other.vram_bank def __repr__(self): return f"Tile: {self.tile_identifier}" @@ -178,17 +210,16 @@

    Module pyboy.botsupport.tile

    Classes

    -
    +
    class Tile (mb, identifier)

    The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used for -BotSupportManager.tile(), Sprite and TileMap, when -refering to graphics.

    +pyboy.tile, Sprite and TileMap, when refering to graphics.

    This class is not meant to be instantiated by developers reading this documentation, but it will be created -internally and returned by Sprite.tiles and -TileMap.tile().

    +internally and returned by Sprite.tiles and +TileMap.tile().

    The data of this class is static, apart from the image data, which is loaded from the Game Boy's memory when needed. Beware that the graphics for the tile can change between each call to PyBoy.tick().

    @@ -199,28 +230,30 @@

    Classes

    def __init__(self, mb, identifier): """ The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used for - `pyboy.botsupport.BotSupportManager.tile`, `pyboy.botsupport.sprite.Sprite` and `pyboy.botsupport.tilemap.TileMap`, when - refering to graphics. + `pyboy.tile`, `pyboy.api.sprite.Sprite` and `pyboy.api.tilemap.TileMap`, when refering to graphics. This class is not meant to be instantiated by developers reading this documentation, but it will be created - internally and returned by `pyboy.botsupport.sprite.Sprite.tiles` and - `pyboy.botsupport.tilemap.TileMap.tile`. + internally and returned by `pyboy.api.sprite.Sprite.tiles` and + `pyboy.api.tilemap.TileMap.tile`. The data of this class is static, apart from the image data, which is loaded from the Game Boy's memory when needed. Beware that the graphics for the tile can change between each call to `pyboy.PyBoy.tick`. """ self.mb = mb - assert 0 <= identifier < 384, "Identifier out of range" + if self.mb.cgb: + assert 0 <= identifier < TILES_CGB, "Identifier out of range" + else: + assert 0 <= identifier < TILES, "Identifier out of range" - self.data_address = LOW_TILEDATA + (16*identifier) + self.data_address = LOW_TILEDATA + (16 * (identifier%TILES)) """ The tile data is defined in a specific area of the Game Boy. This function returns the address of the tile data - corresponding to the tile identifier. It is advised to use `pyboy.botsupport.tile.Tile.image` or one of the + corresponding to the tile identifier. It is advised to use `pyboy.api.tile.Tile.image` or one of the other `image`-functions if you want to view the tile. You can read how the data is read in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Returns ------- @@ -228,10 +261,16 @@

    Classes

    address in VRAM where tile data starts """ - self.tile_identifier = (self.data_address - LOW_TILEDATA) // 16 + if identifier < TILES: + self.vram_bank = 0 + else: + self.vram_bank = 1 + + self.tile_identifier = identifier """ The Game Boy has a slightly complicated indexing system for tiles. This identifier unifies the otherwise - complicated indexing system on the Game Boy into a single range of 0-383 (both included). + complicated indexing system on the Game Boy into a single range of 0-383 (both included) or 0-767 for Game Boy + Color. Returns ------- @@ -249,9 +288,19 @@

    Classes

    The width and height of the tile. """ + self.raw_buffer_format = self.mb.lcd.renderer.color_format + """ + Returns the color format of the raw screen buffer. + + Returns + ------- + str: + Color format of the raw screen buffer. E.g. 'RGBA'. + """ + def image(self): """ - Use this function to get an easy-to-use `PIL.Image` object of the tile. The image is 8x8 pixels in RGBA colors. + Use this function to get an `PIL.Image` object of the tile. The image is 8x8 pixels. The format or "mode" might change at any time. Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`. @@ -263,13 +312,17 @@

    Classes

    if Image is None: logger.error(f"{__name__}: Missing dependency \"Pillow\".") return None - return Image.frombytes("RGBA", (8, 8), bytes(self.image_data())) + + if cythonmode: + return Image.fromarray(self._image_data().base, mode=self.raw_buffer_format) + else: + return Image.frombytes(self.raw_buffer_format, (8, 8), self._image_data()) def image_ndarray(self): """ - Use this function to get an easy-to-use `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4) - and each value is of `numpy.uint8`. The values corresponds to and RGBA image of 8x8 pixels with each sub-color - in a separate cell. + Use this function to get an `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4) + and each value is of `numpy.uint8`. The values corresponds to an image of 8x8 pixels with each sub-color + in a separate cell. The format is given by `pyboy.api.tile.Tile.raw_buffer_format`. Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`. @@ -278,9 +331,11 @@

    Classes

    numpy.ndarray : Array of shape (8, 8, 4) with data type of `numpy.uint8`. """ - return np.asarray(self.image_data()).view(dtype=np.uint8).reshape(8, 8, 4) + # The data is laid out as (X, red, green, blue), where X is currently always zero, but this is not guarenteed + # across versions of PyBoy. + return np.asarray(self._image_data()).view(dtype=np.uint8).reshape(8, 8, 4) - def image_data(self): + def _image_data(self): """ Use this function to get the raw tile data. The data is a `memoryview` corresponding to 8x8 pixels in RGBA colors. @@ -290,67 +345,79 @@

    Classes

    Returns ------- memoryview : - Image data of tile in 8x8 pixels and RGBA colors. + Image data of tile in 8x8 pixels and RGB colors. """ self.data = np.zeros((8, 8), dtype=np.uint32) for k in range(0, 16, 2): # 2 bytes for each line - byte1 = self.mb.lcd.VRAM0[self.data_address + k - VRAM_OFFSET] - byte2 = self.mb.lcd.VRAM0[self.data_address + k + 1 - VRAM_OFFSET] + if self.vram_bank == 0: + byte1 = self.mb.lcd.VRAM0[self.data_address + k - VRAM_OFFSET] + byte2 = self.mb.lcd.VRAM0[self.data_address + k + 1 - VRAM_OFFSET] + else: + byte1 = self.mb.lcd.VRAM1[self.data_address + k - VRAM_OFFSET] + byte2 = self.mb.lcd.VRAM1[self.data_address + k + 1 - VRAM_OFFSET] for x in range(8): colorcode = utils.color_code(byte1, byte2, 7 - x) - # NOTE: ">> 8 | 0xFF000000" to keep compatibility with earlier code - old_A_format = 0xFF000000 - self.data[k // 2][x] = self.mb.lcd.BGP.getcolor(colorcode) >> 8 | old_A_format - + alpha_mask = 0x00FFFFFF + self.data[k // 2][x] = self.mb.lcd.BGP.getcolor(colorcode) & alpha_mask return self.data def __eq__(self, other): - return self.data_address == other.data_address + return self.data_address == other.data_address and self.vram_bank == other.vram_bank def __repr__(self): return f"Tile: {self.tile_identifier}"

    Instance variables

    -
    var data_address
    +
    var data_address

    The tile data is defined in a specific area of the Game Boy. This function returns the address of the tile data -corresponding to the tile identifier. It is advised to use Tile.image() or one of the +corresponding to the tile identifier. It is advised to use Tile.image() or one of the other image-functions if you want to view the tile.

    You can read how the data is read in the -Pan Docs: VRAM Tile Data.

    +Pan Docs: VRAM Tile Data.

    Returns

    int:
    address in VRAM where tile data starts
    -
    var tile_identifier
    +
    var tile_identifier

    The Game Boy has a slightly complicated indexing system for tiles. This identifier unifies the otherwise -complicated indexing system on the Game Boy into a single range of 0-383 (both included).

    +complicated indexing system on the Game Boy into a single range of 0-383 (both included) or 0-767 for Game Boy +Color.

    Returns

    int:
    Unique identifier for the tile
    -
    var shape
    +
    var shape

    Tiles are always 8x8 pixels.

    Returns

    (int, int): The width and height of the tile.

    +
    var raw_buffer_format
    +
    +

    Returns the color format of the raw screen buffer.

    +

    Returns

    +
    +
    str:
    +
    Color format of the raw screen buffer. E.g. 'RGBA'.
    +
    +

    Methods

    -
    +
    def image(self)
    -

    Use this function to get an easy-to-use PIL.Image object of the tile. The image is 8x8 pixels in RGBA colors.

    +

    Use this function to get an PIL.Image object of the tile. The image is 8x8 pixels. The format or "mode" might change at any time.

    Be aware, that the graphics for this tile can change between each call to PyBoy.tick().

    Returns

    @@ -363,7 +430,7 @@

    Returns

    def image(self):
         """
    -    Use this function to get an easy-to-use `PIL.Image` object of the tile. The image is 8x8 pixels in RGBA colors.
    +    Use this function to get an `PIL.Image` object of the tile. The image is 8x8 pixels. The format or "mode" might change at any time.
     
         Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`.
     
    @@ -375,16 +442,20 @@ 

    Returns

    if Image is None: logger.error(f"{__name__}: Missing dependency \"Pillow\".") return None - return Image.frombytes("RGBA", (8, 8), bytes(self.image_data()))
    + + if cythonmode: + return Image.fromarray(self._image_data().base, mode=self.raw_buffer_format) + else: + return Image.frombytes(self.raw_buffer_format, (8, 8), self._image_data())
-
+
def image_ndarray(self)
-

Use this function to get an easy-to-use numpy.ndarray object of the tile. The array has a shape of (8, 8, 4) -and each value is of numpy.uint8. The values corresponds to and RGBA image of 8x8 pixels with each sub-color -in a separate cell.

+

Use this function to get an numpy.ndarray object of the tile. The array has a shape of (8, 8, 4) +and each value is of numpy.uint8. The values corresponds to an image of 8x8 pixels with each sub-color +in a separate cell. The format is given by Tile.raw_buffer_format.

Be aware, that the graphics for this tile can change between each call to PyBoy.tick().

Returns

@@ -397,9 +468,9 @@

Returns

def image_ndarray(self):
     """
-    Use this function to get an easy-to-use `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4)
-    and each value is of `numpy.uint8`. The values corresponds to and RGBA image of 8x8 pixels with each sub-color
-    in a separate cell.
+    Use this function to get an `numpy.ndarray` object of the tile. The array has a shape of (8, 8, 4)
+    and each value is of `numpy.uint8`. The values corresponds to an image of 8x8 pixels with each sub-color
+    in a separate cell. The format is given by `pyboy.api.tile.Tile.raw_buffer_format`.
 
     Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`.
 
@@ -408,49 +479,9 @@ 

Returns

numpy.ndarray : Array of shape (8, 8, 4) with data type of `numpy.uint8`. """ - return np.asarray(self.image_data()).view(dtype=np.uint8).reshape(8, 8, 4)
- -
-
-def image_data(self) -
-
-

Use this function to get the raw tile data. The data is a memoryview corresponding to 8x8 pixels in RGBA -colors.

-

Be aware, that the graphics for this tile can change between each call to PyBoy.tick().

-

Returns

-
-
memoryview :
-
Image data of tile in 8x8 pixels and RGBA colors.
-
-
- -Expand source code - -
def image_data(self):
-    """
-    Use this function to get the raw tile data. The data is a `memoryview` corresponding to 8x8 pixels in RGBA
-    colors.
-
-    Be aware, that the graphics for this tile can change between each call to `pyboy.PyBoy.tick`.
-
-    Returns
-    -------
-    memoryview :
-        Image data of tile in 8x8 pixels and RGBA colors.
-    """
-    self.data = np.zeros((8, 8), dtype=np.uint32)
-    for k in range(0, 16, 2): # 2 bytes for each line
-        byte1 = self.mb.lcd.VRAM0[self.data_address + k - VRAM_OFFSET]
-        byte2 = self.mb.lcd.VRAM0[self.data_address + k + 1 - VRAM_OFFSET]
-
-        for x in range(8):
-            colorcode = utils.color_code(byte1, byte2, 7 - x)
-            # NOTE: ">> 8 | 0xFF000000" to keep compatibility with earlier code
-            old_A_format = 0xFF000000
-            self.data[k // 2][x] = self.mb.lcd.BGP.getcolor(colorcode) >> 8 | old_A_format
-
-    return self.data
+ # The data is laid out as (X, red, green, blue), where X is currently always zero, but this is not guarenteed + # across versions of PyBoy. + return np.asarray(self._image_data()).view(dtype=np.uint8).reshape(8, 8, 4)
@@ -466,20 +497,20 @@

Index

  • Super-module

  • Classes

    diff --git a/docs/botsupport/tilemap.html b/docs/api/tilemap.html similarity index 74% rename from docs/botsupport/tilemap.html rename to docs/api/tilemap.html index 1e20d84ff..e42b640b3 100644 --- a/docs/botsupport/tilemap.html +++ b/docs/api/tilemap.html @@ -4,7 +4,7 @@ -pyboy.botsupport.tilemap API documentation +pyboy.api.tilemap API documentation @@ -18,7 +18,7 @@
    -

    Module pyboy.botsupport.tilemap

    +

    Module pyboy.api.tilemap

    The Game Boy has two tile maps, which defines what is rendered on the screen.

    @@ -35,6 +35,7 @@

    Module pyboy.botsupport.tilemap

    """ import numpy as np + from pyboy.core.lcd import LCDCRegister from .constants import HIGH_TILEMAP, LCDC_OFFSET, LOW_TILEDATA_NTILES, LOW_TILEMAP @@ -42,39 +43,37 @@

    Module pyboy.botsupport.tilemap

    class TileMap: - def __init__(self, mb, select): + def __init__(self, pyboy, mb, select): """ The Game Boy has two tile maps, which defines what is rendered on the screen. These are also referred to as "background" and "window". - Use `pyboy.botsupport.BotSupportManager.tilemap_background` and - `pyboy.botsupport.BotSupportManager.tilemap_window` to instantiate this object. + Use `pyboy.tilemap_background` and + `pyboy.tilemap_window` to instantiate this object. This object defines `__getitem__`, which means it can be accessed with the square brackets to get a tile identifier at a given coordinate. Example: ``` - >>> tilemap = pyboy.tilemap_window - >>> tile = tilemap[10,10] - >>> print(tile) - 34 - >>> print(tilemap[0:10,10]) - [43, 54, 23, 23, 23, 54, 12, 54, 54, 23] - >>> print(tilemap[0:10,0:4]) - [[43, 54, 23, 23, 23, 54, 12, 54, 54, 23], - [43, 54, 43, 23, 23, 43, 12, 39, 54, 23], - [43, 54, 23, 12, 87, 54, 12, 54, 21, 23], - [43, 54, 23, 43, 23, 87, 12, 50, 54, 72]] + >>> pyboy.tilemap_window[8,8] + 1 + >>> pyboy.tilemap_window[7:12,8] + [0, 1, 0, 1, 0] + >>> pyboy.tilemap_window[7:12,8:11] + [[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]] + ``` Each element in the matrix, is the tile identifier of the tile to be shown on screen for each position. If you need the entire 32x32 tile map, you can use the shortcut: `tilemap[:,:]`. """ + self.pyboy = pyboy self.mb = mb self._select = select self._use_tile_objects = False - self.refresh_lcdc() + self.frame_count_update = 0 + self.__refresh_lcdc() self.shape = (32, 32) """ @@ -86,7 +85,12 @@

    Module pyboy.botsupport.tilemap

    The width and height of the tile map. """ - def refresh_lcdc(self): + def _refresh_lcdc(self): + if self.frame_count_update == self.pyboy.frame_count: + return 0 + self.__refresh_lcdc() + + def __refresh_lcdc(self): """ The tile data and view that is showed on the background and window respectively can change dynamically. If you believe it has changed, you can use this method to update the tilemap from the LCDC register. @@ -108,9 +112,9 @@

    Module pyboy.botsupport.tilemap

    Example: ``` - >>> tilemap = pyboy.tilemap_window - >>> print(tilemap.search_for_identifiers([43, 123])) - [[[0,0], [2,4], [8,7]], []] + >>> pyboy.tilemap_window.search_for_identifiers([5,3]) + [[[9, 11]], [[9, 9], [9, 12]]] + ``` Meaning, that tile identifier `43` is found at the positions: (0,0), (2,4), and (8,7), while tile identifier @@ -135,7 +139,7 @@

    Module pyboy.botsupport.tilemap

    """ Returns the memory address in the tilemap for the tile at the given coordinate. The address contains the index of tile which will be shown at this position. This should not be confused with the actual tile data of - `pyboy.botsupport.tile.Tile.data_address`. + `pyboy.api.tile.Tile.data_address`. This can be used as an global identifier for the specific location in a tile map. @@ -144,9 +148,9 @@

    Module pyboy.botsupport.tilemap

    on the screen. The index might also be a signed number. Depending on if it is signed or not, will change where the tile data - is read from. Use `pyboy.botsupport.tilemap.TileMap.signed_tile_index` to test if the indexes are signed for + is read from. Use `pyboy.api.tilemap.TileMap.signed_tile_index` to test if the indexes are signed for this tile view. You can read how the indexes work in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Args: column (int): Column in this tile map. @@ -157,7 +161,6 @@

    Module pyboy.botsupport.tilemap

    int: Address in the tile map to read a tile index. """ - if not 0 <= column < 32: raise IndexError("column is out of bounds. Value of 0 to 31 is allowed") if not 0 <= row < 32: @@ -166,8 +169,8 @@

    Module pyboy.botsupport.tilemap

    def tile(self, column, row): """ - Provides a `pyboy.botsupport.tile.Tile`-object which allows for easy interpretation of the tile data. The - object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.botsupport.tile.Tile`-objects might + Provides a `pyboy.api.tile.Tile`-object which allows for easy interpretation of the tile data. The + object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.api.tile.Tile`-objects might be returned from two different coordinates in the tile map if they are shown different places on the screen. Args: @@ -176,7 +179,7 @@

    Module pyboy.botsupport.tilemap

    Returns ------- - `pyboy.botsupport.tile.Tile`: + `pyboy.api.tile.Tile`: Tile object corresponding to the tile index at the given coordinate in the tile map. """ @@ -191,7 +194,7 @@

    Module pyboy.botsupport.tilemap

    0-383 (both included). You can read how the indexes work in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Args: column (int): Column in this tile map. @@ -202,7 +205,7 @@

    Module pyboy.botsupport.tilemap

    int: Tile identifier. """ - + self._refresh_lcdc() tile = self.mb.getitem(self._tile_address(column, row)) if self.signed_tile_data: return ((tile ^ 0x80) - 128) + LOW_TILEDATA_NTILES @@ -210,6 +213,7 @@

    Module pyboy.botsupport.tilemap

    return tile def __repr__(self): + self._refresh_lcdc() adjust = 4 _use_tile_objects = self._use_tile_objects self.use_tile_objects(False) @@ -235,24 +239,51 @@

    Module pyboy.botsupport.tilemap

    Used to change which object is returned when using the ``__getitem__`` method (i.e. `tilemap[0,0]`). Args: - switch (bool): If True, accesses will return `pyboy.botsupport.tile.Tile`-object. If False, accesses will + switch (bool): If True, accesses will return `pyboy.api.tile.Tile`-object. If False, accesses will return an `int`. """ self._use_tile_objects = switch - def __getitem__(self, xy): - x, y = xy + def _fix_slice(self, addr): + if addr.step is None: + step = 1 + else: + step = addr.step + + if addr.start is None: + start = 0 + else: + start = addr.start + + if addr.stop is None: + stop = 32 + else: + stop = addr.stop - if x == slice(None): - x = slice(0, 32, 1) + if step < 0: + raise ValueError("Reversed ranges are unsupported") + elif start > stop: + raise ValueError("Invalid range") + return start, stop, step - if y == slice(None): - y = slice(0, 32, 1) + def __getitem__(self, xy): + if isinstance(xy, (int, slice)): + x = xy + y = slice(None) + else: + x, y = xy + + x_slice = isinstance(x, slice) + y_slice = isinstance(y, slice) + if x_slice: + x = self._fix_slice(x) + else: + assert isinstance(x, int) - x_slice = isinstance(x, slice) # Assume slice, otherwise int - y_slice = isinstance(y, slice) # Assume slice, otherwise int - assert x_slice or isinstance(x, int) - assert y_slice or isinstance(y, int) + if y_slice: + y = self._fix_slice(y) + else: + assert isinstance(y, int) if self._use_tile_objects: tile_fun = self.tile @@ -260,11 +291,11 @@

    Module pyboy.botsupport.tilemap

    tile_fun = lambda x, y: self.tile_identifier(x, y) if x_slice and y_slice: - return [[tile_fun(_x, _y) for _x in range(x.stop)[x]] for _y in range(y.stop)[y]] + return [[tile_fun(_x, _y) for _x in range(*x)] for _y in range(*y)] elif x_slice: - return [tile_fun(_x, y) for _x in range(x.stop)[x]] + return [tile_fun(_x, y) for _x in range(*x)] elif y_slice: - return [tile_fun(x, _y) for _y in range(y.stop)[y]] + return [tile_fun(x, _y) for _y in range(*y)] else: return tile_fun(x, y)
    @@ -278,29 +309,25 @@

    Module pyboy.botsupport.tilemap

    Classes

    -
    +
    class TileMap -(mb, select) +(pyboy, mb, select)

    The Game Boy has two tile maps, which defines what is rendered on the screen. These are also referred to as "background" and "window".

    -

    Use BotSupportManager.tilemap_background() and -BotSupportManager.tilemap_window() to instantiate this object.

    +

    Use pyboy.tilemap_background and +pyboy.tilemap_window to instantiate this object.

    This object defines __getitem__, which means it can be accessed with the square brackets to get a tile identifier at a given coordinate.

    Example:

    -
    >>> tilemap = pyboy.tilemap_window
    ->>> tile = tilemap[10,10]
    ->>> print(tile)
    -34
    ->>> print(tilemap[0:10,10])
    -[43, 54, 23, 23, 23, 54, 12, 54, 54, 23]
    ->>> print(tilemap[0:10,0:4])
    -[[43, 54, 23, 23, 23, 54, 12, 54, 54, 23],
    - [43, 54, 43, 23, 23, 43, 12, 39, 54, 23],
    - [43, 54, 23, 12, 87, 54, 12, 54, 21, 23],
    - [43, 54, 23, 43, 23, 87, 12, 50, 54, 72]]
    +
    >>> pyboy.tilemap_window[8,8]
    +1
    +>>> pyboy.tilemap_window[7:12,8]
    +[0, 1, 0, 1, 0]
    +>>> pyboy.tilemap_window[7:12,8:11]
    +[[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]]
    +
     

    Each element in the matrix, is the tile identifier of the tile to be shown on screen for each position. If you need the entire 32x32 tile map, you can use the shortcut: tilemap[:,:].

    @@ -309,39 +336,37 @@

    Classes

    Expand source code
    class TileMap:
    -    def __init__(self, mb, select):
    +    def __init__(self, pyboy, mb, select):
             """
             The Game Boy has two tile maps, which defines what is rendered on the screen. These are also referred to as
             "background" and "window".
     
    -        Use `pyboy.botsupport.BotSupportManager.tilemap_background` and
    -        `pyboy.botsupport.BotSupportManager.tilemap_window` to instantiate this object.
    +        Use `pyboy.tilemap_background` and
    +        `pyboy.tilemap_window` to instantiate this object.
     
             This object defines `__getitem__`, which means it can be accessed with the square brackets to get a tile
             identifier at a given coordinate.
     
             Example:
             ```
    -        >>> tilemap = pyboy.tilemap_window
    -        >>> tile = tilemap[10,10]
    -        >>> print(tile)
    -        34
    -        >>> print(tilemap[0:10,10])
    -        [43, 54, 23, 23, 23, 54, 12, 54, 54, 23]
    -        >>> print(tilemap[0:10,0:4])
    -        [[43, 54, 23, 23, 23, 54, 12, 54, 54, 23],
    -         [43, 54, 43, 23, 23, 43, 12, 39, 54, 23],
    -         [43, 54, 23, 12, 87, 54, 12, 54, 21, 23],
    -         [43, 54, 23, 43, 23, 87, 12, 50, 54, 72]]
    +        >>> pyboy.tilemap_window[8,8]
    +        1
    +        >>> pyboy.tilemap_window[7:12,8]
    +        [0, 1, 0, 1, 0]
    +        >>> pyboy.tilemap_window[7:12,8:11]
    +        [[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]]
    +
             ```
     
             Each element in the matrix, is the tile identifier of the tile to be shown on screen for each position. If you
             need the entire 32x32 tile map, you can use the shortcut: `tilemap[:,:]`.
             """
    +        self.pyboy = pyboy
             self.mb = mb
             self._select = select
             self._use_tile_objects = False
    -        self.refresh_lcdc()
    +        self.frame_count_update = 0
    +        self.__refresh_lcdc()
     
             self.shape = (32, 32)
             """
    @@ -353,7 +378,12 @@ 

    Classes

    The width and height of the tile map. """ - def refresh_lcdc(self): + def _refresh_lcdc(self): + if self.frame_count_update == self.pyboy.frame_count: + return 0 + self.__refresh_lcdc() + + def __refresh_lcdc(self): """ The tile data and view that is showed on the background and window respectively can change dynamically. If you believe it has changed, you can use this method to update the tilemap from the LCDC register. @@ -375,9 +405,9 @@

    Classes

    Example: ``` - >>> tilemap = pyboy.tilemap_window - >>> print(tilemap.search_for_identifiers([43, 123])) - [[[0,0], [2,4], [8,7]], []] + >>> pyboy.tilemap_window.search_for_identifiers([5,3]) + [[[9, 11]], [[9, 9], [9, 12]]] + ``` Meaning, that tile identifier `43` is found at the positions: (0,0), (2,4), and (8,7), while tile identifier @@ -402,7 +432,7 @@

    Classes

    """ Returns the memory address in the tilemap for the tile at the given coordinate. The address contains the index of tile which will be shown at this position. This should not be confused with the actual tile data of - `pyboy.botsupport.tile.Tile.data_address`. + `pyboy.api.tile.Tile.data_address`. This can be used as an global identifier for the specific location in a tile map. @@ -411,9 +441,9 @@

    Classes

    on the screen. The index might also be a signed number. Depending on if it is signed or not, will change where the tile data - is read from. Use `pyboy.botsupport.tilemap.TileMap.signed_tile_index` to test if the indexes are signed for + is read from. Use `pyboy.api.tilemap.TileMap.signed_tile_index` to test if the indexes are signed for this tile view. You can read how the indexes work in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Args: column (int): Column in this tile map. @@ -424,7 +454,6 @@

    Classes

    int: Address in the tile map to read a tile index. """ - if not 0 <= column < 32: raise IndexError("column is out of bounds. Value of 0 to 31 is allowed") if not 0 <= row < 32: @@ -433,8 +462,8 @@

    Classes

    def tile(self, column, row): """ - Provides a `pyboy.botsupport.tile.Tile`-object which allows for easy interpretation of the tile data. The - object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.botsupport.tile.Tile`-objects might + Provides a `pyboy.api.tile.Tile`-object which allows for easy interpretation of the tile data. The + object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.api.tile.Tile`-objects might be returned from two different coordinates in the tile map if they are shown different places on the screen. Args: @@ -443,7 +472,7 @@

    Classes

    Returns ------- - `pyboy.botsupport.tile.Tile`: + `pyboy.api.tile.Tile`: Tile object corresponding to the tile index at the given coordinate in the tile map. """ @@ -458,7 +487,7 @@

    Classes

    0-383 (both included). You can read how the indexes work in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Args: column (int): Column in this tile map. @@ -469,7 +498,7 @@

    Classes

    int: Tile identifier. """ - + self._refresh_lcdc() tile = self.mb.getitem(self._tile_address(column, row)) if self.signed_tile_data: return ((tile ^ 0x80) - 128) + LOW_TILEDATA_NTILES @@ -477,6 +506,7 @@

    Classes

    return tile def __repr__(self): + self._refresh_lcdc() adjust = 4 _use_tile_objects = self._use_tile_objects self.use_tile_objects(False) @@ -502,24 +532,51 @@

    Classes

    Used to change which object is returned when using the ``__getitem__`` method (i.e. `tilemap[0,0]`). Args: - switch (bool): If True, accesses will return `pyboy.botsupport.tile.Tile`-object. If False, accesses will + switch (bool): If True, accesses will return `pyboy.api.tile.Tile`-object. If False, accesses will return an `int`. """ self._use_tile_objects = switch - def __getitem__(self, xy): - x, y = xy + def _fix_slice(self, addr): + if addr.step is None: + step = 1 + else: + step = addr.step + + if addr.start is None: + start = 0 + else: + start = addr.start + + if addr.stop is None: + stop = 32 + else: + stop = addr.stop + + if step < 0: + raise ValueError("Reversed ranges are unsupported") + elif start > stop: + raise ValueError("Invalid range") + return start, stop, step - if x == slice(None): - x = slice(0, 32, 1) + def __getitem__(self, xy): + if isinstance(xy, (int, slice)): + x = xy + y = slice(None) + else: + x, y = xy - if y == slice(None): - y = slice(0, 32, 1) + x_slice = isinstance(x, slice) + y_slice = isinstance(y, slice) + if x_slice: + x = self._fix_slice(x) + else: + assert isinstance(x, int) - x_slice = isinstance(x, slice) # Assume slice, otherwise int - y_slice = isinstance(y, slice) # Assume slice, otherwise int - assert x_slice or isinstance(x, int) - assert y_slice or isinstance(y, int) + if y_slice: + y = self._fix_slice(y) + else: + assert isinstance(y, int) if self._use_tile_objects: tile_fun = self.tile @@ -527,17 +584,17 @@

    Classes

    tile_fun = lambda x, y: self.tile_identifier(x, y) if x_slice and y_slice: - return [[tile_fun(_x, _y) for _x in range(x.stop)[x]] for _y in range(y.stop)[y]] + return [[tile_fun(_x, _y) for _x in range(*x)] for _y in range(*y)] elif x_slice: - return [tile_fun(_x, y) for _x in range(x.stop)[x]] + return [tile_fun(_x, y) for _x in range(*x)] elif y_slice: - return [tile_fun(x, _y) for _y in range(y.stop)[y]] + return [tile_fun(x, _y) for _y in range(*y)] else: return tile_fun(x, y)

    Instance variables

    -
    var shape
    +
    var shape

    Tile maps are always 32x32 tiles.

    Returns

    @@ -547,42 +604,16 @@

    Returns

    Methods

    -
    -def refresh_lcdc(self) -
    -
    -

    The tile data and view that is showed on the background and window respectively can change dynamically. If you -believe it has changed, you can use this method to update the tilemap from the LCDC register.

    -
    - -Expand source code - -
    def refresh_lcdc(self):
    -    """
    -    The tile data and view that is showed on the background and window respectively can change dynamically. If you
    -    believe it has changed, you can use this method to update the tilemap from the LCDC register.
    -    """
    -    LCDC = LCDCRegister(self.mb.getitem(LCDC_OFFSET))
    -    if self._select == "WINDOW":
    -        self.map_offset = HIGH_TILEMAP if LCDC.windowmap_select else LOW_TILEMAP
    -        self.signed_tile_data = not bool(LCDC.tiledata_select)
    -    elif self._select == "BACKGROUND":
    -        self.map_offset = HIGH_TILEMAP if LCDC.backgroundmap_select else LOW_TILEMAP
    -        self.signed_tile_data = not bool(LCDC.tiledata_select)
    -    else:
    -        raise KeyError(f"Invalid tilemap selected: {self._select}")
    -
    -
    -
    +
    def search_for_identifiers(self, identifiers)

    Provided a list of tile identifiers, this function will find all occurrences of these in the tilemap and return the coordinates where each identifier is found.

    Example:

    -
    >>> tilemap = pyboy.tilemap_window
    ->>> print(tilemap.search_for_identifiers([43, 123]))
    -[[[0,0], [2,4], [8,7]], []]
    +
    >>> pyboy.tilemap_window.search_for_identifiers([5,3])
    +[[[9, 11]], [[9, 9], [9, 12]]]
    +
     

    Meaning, that tile identifier 43 is found at the positions: (0,0), (2,4), and (8,7), while tile identifier 123was not found anywhere.

    @@ -607,9 +638,9 @@

    Returns

    Example: ``` - >>> tilemap = pyboy.tilemap_window - >>> print(tilemap.search_for_identifiers([43, 123])) - [[[0,0], [2,4], [8,7]], []] + >>> pyboy.tilemap_window.search_for_identifiers([5,3]) + [[[9, 11]], [[9, 9], [9, 12]]] + ``` Meaning, that tile identifier `43` is found at the positions: (0,0), (2,4), and (8,7), while tile identifier @@ -631,12 +662,12 @@

    Returns

    return matches
    -
    +
    def tile(self, column, row)
    -

    Provides a Tile-object which allows for easy interpretation of the tile data. The -object is agnostic to where it was found in the tilemap. I.e. equal Tile-objects might +

    Provides a Tile-object which allows for easy interpretation of the tile data. The +object is agnostic to where it was found in the tilemap. I.e. equal Tile-objects might be returned from two different coordinates in the tile map if they are shown different places on the screen.

    Args

    @@ -646,7 +677,7 @@

    Args

    Row in this tile map.

    Returns

    -

    Tile: +

    Tile: Tile object corresponding to the tile index at the given coordinate in the tile map.

    @@ -655,8 +686,8 @@

    Returns

    def tile(self, column, row):
         """
    -    Provides a `pyboy.botsupport.tile.Tile`-object which allows for easy interpretation of the tile data. The
    -    object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.botsupport.tile.Tile`-objects might
    +    Provides a `pyboy.api.tile.Tile`-object which allows for easy interpretation of the tile data. The
    +    object is agnostic to where it was found in the tilemap. I.e. equal `pyboy.api.tile.Tile`-objects might
         be returned from two different coordinates in the tile map if they are shown different places on the screen.
     
         Args:
    @@ -665,14 +696,14 @@ 

    Returns

    Returns ------- - `pyboy.botsupport.tile.Tile`: + `pyboy.api.tile.Tile`: Tile object corresponding to the tile index at the given coordinate in the tile map. """ return Tile(self.mb, self.tile_identifier(column, row))
    -
    +
    def tile_identifier(self, column, row)
    @@ -681,7 +712,7 @@

    Returns

    This identifier unifies the otherwise complicated indexing system on the Game Boy into a single range of 0-383 (both included).

    You can read how the indexes work in the -Pan Docs: VRAM Tile Data.

    +Pan Docs: VRAM Tile Data.

    Args

    column : int
    @@ -707,7 +738,7 @@

    Returns

    0-383 (both included). You can read how the indexes work in the - [Pan Docs: VRAM Tile Data](http://bgb.bircd.org/pandocs.htm#vramtiledata). + [Pan Docs: VRAM Tile Data](https://gbdev.io/pandocs/Tile_Data.html). Args: column (int): Column in this tile map. @@ -718,7 +749,7 @@

    Returns

    int: Tile identifier. """ - + self._refresh_lcdc() tile = self.mb.getitem(self._tile_address(column, row)) if self.signed_tile_data: return ((tile ^ 0x80) - 128) + LOW_TILEDATA_NTILES @@ -726,7 +757,7 @@

    Returns

    return tile
    -
    +
    def use_tile_objects(self, switch)
    @@ -734,7 +765,7 @@

    Returns

    Args

    switch : bool
    -
    If True, accesses will return Tile-object. If False, accesses will +
    If True, accesses will return Tile-object. If False, accesses will return an int.
    @@ -746,7 +777,7 @@

    Args

    Used to change which object is returned when using the ``__getitem__`` method (i.e. `tilemap[0,0]`). Args: - switch (bool): If True, accesses will return `pyboy.botsupport.tile.Tile`-object. If False, accesses will + switch (bool): If True, accesses will return `pyboy.api.tile.Tile`-object. If False, accesses will return an `int`. """ self._use_tile_objects = switch
    @@ -765,20 +796,19 @@

    Index

    • Super-module

    • Classes

      diff --git a/docs/botsupport/index.html b/docs/botsupport/index.html deleted file mode 100644 index 61c1c66dc..000000000 --- a/docs/botsupport/index.html +++ /dev/null @@ -1,480 +0,0 @@ - - - - - - -pyboy.botsupport API documentation - - - - - - - - - - -
      -
      -
      -

      Module pyboy.botsupport

      -
      -
      -

      Tools to help interfacing with the Game Boy hardware

      -
      - -Expand source code - -
      #
      -# License: See LICENSE.md file
      -# GitHub: https://github.com/Baekalfen/PyBoy
      -#
      -"""
      -Tools to help interfacing with the Game Boy hardware
      -"""
      -
      -__pdoc__ = {
      -    "constants": False,
      -    "manager": False,
      -}
      -__all__ = ["BotSupportManager"]
      -
      -from .manager import BotSupportManager
      -
      -
      -
      -

      Sub-modules

      -
      -
      pyboy.botsupport.screen
      -
      -

      This class gives access to the frame buffer and other screen parameters of PyBoy.

      -
      -
      pyboy.botsupport.sprite
      -
      -

      This class presents an interface to the sprites held in the OAM data on the Game Boy.

      -
      -
      pyboy.botsupport.tile
      -
      -

      The Game Boy uses tiles as the building block for all graphics on the screen. This base-class is used both for -Sprite and …

      -
      -
      pyboy.botsupport.tilemap
      -
      -

      The Game Boy has two tile maps, which defines what is rendered on the screen.

      -
      -
      -
      -
      -
      -
      -
      -
      -

      Classes

      -
      -
      -class BotSupportManager -(pyboy, mb) -
      -
      -
      -
      - -Expand source code - -
      class BotSupportManager:
      -    def __init__(self, pyboy, mb):
      -        if not cythonmode:
      -            self.pyboy = pyboy
      -            self.mb = mb
      -
      -    def __cinit__(self, pyboy, mb):
      -        self.pyboy = pyboy
      -        self.mb = mb
      -
      -    def screen(self):
      -        """
      -        Use this method to get a `pyboy.botsupport.screen.Screen` object. This can be used to get the screen buffer in
      -        a variety of formats.
      -
      -        It's also here you can find the screen position (SCX, SCY, WX, WY) for each scan line in the screen buffer. See
      -        `pyboy.botsupport.screen.Screen.tilemap_position` for more information.
      -
      -        Returns
      -        -------
      -        `pyboy.botsupport.screen.Screen`:
      -            A Screen object with helper functions for reading the screen buffer.
      -        """
      -        return _screen.Screen(self.mb)
      -
      -    def sprite(self, sprite_index):
      -        """
      -        Provides a `pyboy.botsupport.sprite.Sprite` object, which makes the OAM data more presentable. The given index
      -        corresponds to index of the sprite in the "Object Attribute Memory" (OAM).
      -
      -        The Game Boy supports 40 sprites in total. Read more details about it, in the [Pan
      -        Docs](http://bgb.bircd.org/pandocs.htm).
      -
      -        Args:
      -            index (int): Sprite index from 0 to 39.
      -        Returns
      -        -------
      -        `pyboy.botsupport.sprite.Sprite`:
      -            Sprite corresponding to the given index.
      -        """
      -        return _sprite.Sprite(self.mb, sprite_index)
      -
      -    def sprite_by_tile_identifier(self, tile_identifiers, on_screen=True):
      -        """
      -        Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile
      -        identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the
      -        `pyboy.botsupport.BotSupportManager.sprite` function to get a `pyboy.botsupport.sprite.Sprite` object.
      -
      -        Example:
      -        ```
      -        >>> print(pyboy.botsupport_manager().sprite_by_tile_identifier([43, 123]))
      -        [[0, 2, 4], []]
      -        ```
      -
      -        Meaning, that tile identifier `43` is found at the sprite indexes: 0, 2, and 4, while tile identifier
      -        `123` was not found anywhere.
      -
      -        Args:
      -            identifiers (list): List of tile identifiers (int)
      -            on_screen (bool): Require that the matched sprite is on screen
      -
      -        Returns
      -        -------
      -        list:
      -            list of sprite matches for every tile identifier in the input
      -        """
      -
      -        matches = []
      -        for i in tile_identifiers:
      -            match = []
      -            for s in range(_constants.SPRITES):
      -                sprite = _sprite.Sprite(self.mb, s)
      -                for t in sprite.tiles:
      -                    if t.tile_identifier == i and (not on_screen or (on_screen and sprite.on_screen)):
      -                        match.append(s)
      -            matches.append(match)
      -        return matches
      -
      -    def tile(self, identifier):
      -        """
      -        The Game Boy can have 384 tiles loaded in memory at once. Use this method to get a
      -        `pyboy.botsupport.tile.Tile`-object for given identifier.
      -
      -        The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See
      -        the `pyboy.botsupport.tile.Tile` object for more information.
      -
      -        Returns
      -        -------
      -        `pyboy.botsupport.tile.Tile`:
      -            A Tile object for the given identifier.
      -        """
      -        return _tile.Tile(self.mb, identifier=identifier)
      -
      -    def tilemap_background(self):
      -        """
      -        The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one
      -        for the _background_ tiles. The game chooses whether it wants to use the low or the high tilemap.
      -
      -        Read more details about it, in the [Pan Docs](http://bgb.bircd.org/pandocs.htm#vrambackgroundmaps).
      -
      -        Returns
      -        -------
      -        `pyboy.botsupport.tilemap.TileMap`:
      -            A TileMap object for the tile map.
      -        """
      -        return _tilemap.TileMap(self.mb, "BACKGROUND")
      -
      -    def tilemap_window(self):
      -        """
      -        The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one
      -        for the _window_ tiles. The game chooses whether it wants to use the low or the high tilemap.
      -
      -        Read more details about it, in the [Pan Docs](http://bgb.bircd.org/pandocs.htm#vrambackgroundmaps).
      -
      -        Returns
      -        -------
      -        `pyboy.botsupport.tilemap.TileMap`:
      -            A TileMap object for the tile map.
      -        """
      -        return _tilemap.TileMap(self.mb, "WINDOW")
      -
      -

      Methods

      -
      -
      -def screen(self) -
      -
      -

      Use this method to get a Screen object. This can be used to get the screen buffer in -a variety of formats.

      -

      It's also here you can find the screen position (SCX, SCY, WX, WY) for each scan line in the screen buffer. See -Screen.tilemap_position() for more information.

      -

      Returns

      -

      Screen: -A Screen object with helper functions for reading the screen buffer.

      -
      - -Expand source code - -
      def screen(self):
      -    """
      -    Use this method to get a `pyboy.botsupport.screen.Screen` object. This can be used to get the screen buffer in
      -    a variety of formats.
      -
      -    It's also here you can find the screen position (SCX, SCY, WX, WY) for each scan line in the screen buffer. See
      -    `pyboy.botsupport.screen.Screen.tilemap_position` for more information.
      -
      -    Returns
      -    -------
      -    `pyboy.botsupport.screen.Screen`:
      -        A Screen object with helper functions for reading the screen buffer.
      -    """
      -    return _screen.Screen(self.mb)
      -
      -
      -
      -def sprite(self, sprite_index) -
      -
      -

      Provides a Sprite object, which makes the OAM data more presentable. The given index -corresponds to index of the sprite in the "Object Attribute Memory" (OAM).

      -

      The Game Boy supports 40 sprites in total. Read more details about it, in the Pan -Docs.

      -

      Args

      -
      -
      index : int
      -
      Sprite index from 0 to 39.
      -
      -

      Returns

      -

      Sprite: -Sprite corresponding to the given index.

      -
      - -Expand source code - -
      def sprite(self, sprite_index):
      -    """
      -    Provides a `pyboy.botsupport.sprite.Sprite` object, which makes the OAM data more presentable. The given index
      -    corresponds to index of the sprite in the "Object Attribute Memory" (OAM).
      -
      -    The Game Boy supports 40 sprites in total. Read more details about it, in the [Pan
      -    Docs](http://bgb.bircd.org/pandocs.htm).
      -
      -    Args:
      -        index (int): Sprite index from 0 to 39.
      -    Returns
      -    -------
      -    `pyboy.botsupport.sprite.Sprite`:
      -        Sprite corresponding to the given index.
      -    """
      -    return _sprite.Sprite(self.mb, sprite_index)
      -
      -
      -
      -def sprite_by_tile_identifier(self, tile_identifiers, on_screen=True) -
      -
      -

      Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile -identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the -BotSupportManager.sprite() function to get a Sprite object.

      -

      Example:

      -
      >>> print(pyboy.botsupport_manager().sprite_by_tile_identifier([43, 123]))
      -[[0, 2, 4], []]
      -
      -

      Meaning, that tile identifier 43 is found at the sprite indexes: 0, 2, and 4, while tile identifier -123 was not found anywhere.

      -

      Args

      -
      -
      identifiers : list
      -
      List of tile identifiers (int)
      -
      on_screen : bool
      -
      Require that the matched sprite is on screen
      -
      -

      Returns

      -
      -
      list:
      -
      list of sprite matches for every tile identifier in the input
      -
      -
      - -Expand source code - -
      def sprite_by_tile_identifier(self, tile_identifiers, on_screen=True):
      -    """
      -    Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile
      -    identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the
      -    `pyboy.botsupport.BotSupportManager.sprite` function to get a `pyboy.botsupport.sprite.Sprite` object.
      -
      -    Example:
      -    ```
      -    >>> print(pyboy.botsupport_manager().sprite_by_tile_identifier([43, 123]))
      -    [[0, 2, 4], []]
      -    ```
      -
      -    Meaning, that tile identifier `43` is found at the sprite indexes: 0, 2, and 4, while tile identifier
      -    `123` was not found anywhere.
      -
      -    Args:
      -        identifiers (list): List of tile identifiers (int)
      -        on_screen (bool): Require that the matched sprite is on screen
      -
      -    Returns
      -    -------
      -    list:
      -        list of sprite matches for every tile identifier in the input
      -    """
      -
      -    matches = []
      -    for i in tile_identifiers:
      -        match = []
      -        for s in range(_constants.SPRITES):
      -            sprite = _sprite.Sprite(self.mb, s)
      -            for t in sprite.tiles:
      -                if t.tile_identifier == i and (not on_screen or (on_screen and sprite.on_screen)):
      -                    match.append(s)
      -        matches.append(match)
      -    return matches
      -
      -
      -
      -def tile(self, identifier) -
      -
      -

      The Game Boy can have 384 tiles loaded in memory at once. Use this method to get a -Tile-object for given identifier.

      -

      The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See -the Tile object for more information.

      -

      Returns

      -

      Tile: -A Tile object for the given identifier.

      -
      - -Expand source code - -
      def tile(self, identifier):
      -    """
      -    The Game Boy can have 384 tiles loaded in memory at once. Use this method to get a
      -    `pyboy.botsupport.tile.Tile`-object for given identifier.
      -
      -    The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See
      -    the `pyboy.botsupport.tile.Tile` object for more information.
      -
      -    Returns
      -    -------
      -    `pyboy.botsupport.tile.Tile`:
      -        A Tile object for the given identifier.
      -    """
      -    return _tile.Tile(self.mb, identifier=identifier)
      -
      -
      -
      -def tilemap_background(self) -
      -
      -

      The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one -for the background tiles. The game chooses whether it wants to use the low or the high tilemap.

      -

      Read more details about it, in the Pan Docs.

      -

      Returns

      -

      TileMap: -A TileMap object for the tile map.

      -
      - -Expand source code - -
      def tilemap_background(self):
      -    """
      -    The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one
      -    for the _background_ tiles. The game chooses whether it wants to use the low or the high tilemap.
      -
      -    Read more details about it, in the [Pan Docs](http://bgb.bircd.org/pandocs.htm#vrambackgroundmaps).
      -
      -    Returns
      -    -------
      -    `pyboy.botsupport.tilemap.TileMap`:
      -        A TileMap object for the tile map.
      -    """
      -    return _tilemap.TileMap(self.mb, "BACKGROUND")
      -
      -
      -
      -def tilemap_window(self) -
      -
      -

      The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one -for the window tiles. The game chooses whether it wants to use the low or the high tilemap.

      -

      Read more details about it, in the Pan Docs.

      -

      Returns

      -

      TileMap: -A TileMap object for the tile map.

      -
      - -Expand source code - -
      def tilemap_window(self):
      -    """
      -    The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one
      -    for the _window_ tiles. The game chooses whether it wants to use the low or the high tilemap.
      -
      -    Read more details about it, in the [Pan Docs](http://bgb.bircd.org/pandocs.htm#vrambackgroundmaps).
      -
      -    Returns
      -    -------
      -    `pyboy.botsupport.tilemap.TileMap`:
      -        A TileMap object for the tile map.
      -    """
      -    return _tilemap.TileMap(self.mb, "WINDOW")
      -
      -
      -
      -
      -
      -
      -
      - -
      - - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index b7c45692f..cb444dc42 100644 --- a/docs/index.html +++ b/docs/index.html @@ -32,31 +32,30 @@

      Module pyboy

      __pdoc__ = { "core": False, - "logger": False, + "logging": False, "pyboy": False, - "utils": False, + "conftest": False, } -__all__ = ["PyBoy", "WindowEvent"] +__all__ = ["PyBoy"] -from .pyboy import PyBoy -from .utils import WindowEvent
      +from .pyboy import PyBoy

    Sub-modules

    -
    pyboy.botsupport
    +
    pyboy.api

    Tools to help interfacing with the Game Boy hardware

    -
    pyboy.openai_gym
    -
    -
    -
    pyboy.plugins

    Plugins that extend PyBoy's functionality. The only publicly exposed, are the game wrappers.

    +
    pyboy.utils
    +
    +
    +
    @@ -68,13 +67,13 @@

    Classes

    class PyBoy -(gamerom_file, *, bootrom_file=None, disable_renderer=False, sound=False, sound_emulated=False, cgb=None, randomize=False, **kwargs) +(gamerom_file, *, symbols_file=None, bootrom_file=None, disable_renderer=False, sound=False, sound_emulated=False, cgb=None, randomize=False, **kwargs)

    PyBoy is loadable as an object in Python. This means, it can be initialized from another script, and be controlled and probed by the script. It is supported to spawn multiple emulators, just instantiate the class multiple times.

    -

    This object, WindowEvent, and the pyboy.botsupport module, are the only official user-facing +

    This object, WindowEvent, and the pyboy.api module, are the only official user-facing interfaces. All other parts of the emulator, are subject to change.

    A range of methods are exposed, which should allow for complete control of the emulator. Please open an issue on GitHub, if other methods are needed for your projects. Take a look at the files in examples/ for a crude @@ -101,6 +100,7 @@

    Kwargs

    self, gamerom_file, *, + symbols_file=None, bootrom_file=None, disable_renderer=False, sound=False, @@ -114,7 +114,7 @@

    Kwargs

    controlled and probed by the script. It is supported to spawn multiple emulators, just instantiate the class multiple times. - This object, `pyboy.WindowEvent`, and the `pyboy.botsupport` module, are the only official user-facing + This object, `pyboy.utils.WindowEvent`, and the `pyboy.api` module, are the only official user-facing interfaces. All other parts of the emulator, are subject to change. A range of methods are exposed, which should allow for complete control of the emulator. Please open an issue on @@ -146,6 +146,13 @@

    Kwargs

    raise FileNotFoundError(f"ROM file {gamerom_file} was not found!") self.gamerom_file = gamerom_file + self.rom_symbols = {} + if symbols_file is not None: + if not os.path.isfile(symbols_file): + raise FileNotFoundError(f"Symbols file {symbols_file} was not found!") + self.symbols_file = symbols_file + self._load_symbols() + self.mb = Motherboard( gamerom_file, bootrom_file or kwargs.get("bootrom"), # Our current way to provide cli arguments is broken @@ -169,39 +176,204 @@

    Kwargs

    self.set_emulation_speed(1) self.paused = False self.events = [] + self.queued_input = [] self.old_events = [] self.quitting = False self.stopped = False self.window_title = "PyBoy" ################### - # Plugins + # API attributes + self.screen = Screen(self.mb) + """ + Use this method to get a `pyboy.api.screen.Screen` object. This can be used to get the screen buffer in + a variety of formats. - self.plugin_manager = PluginManager(self, self.mb, kwargs) - self.initialized = True + It's also here you can find the screen position (SCX, SCY, WX, WY) for each scan line in the screen buffer. See + `pyboy.api.screen.Screen.tilemap_position_list` for more information. + + Example: + ```python + >>> pyboy.screen.image.show() + >>> pyboy.screen.ndarray.shape + (144, 160, 4) + >>> pyboy.screen.raw_buffer_format + 'RGBA' - def tick(self): + ``` + + Returns + ------- + `pyboy.api.screen.Screen`: + A Screen object with helper functions for reading the screen buffer. + """ + self.memory = PyBoyMemoryView(self.mb) """ - Progresses the emulator ahead by one frame. + Provides a `pyboy.PyBoyMemoryView` object for reading and writing the memory space of the Game Boy. - To run the emulator in real-time, this will need to be called 60 times a second (for example in a while-loop). - This function will block for roughly 16,67ms at a time, to not run faster than real-time, unless you specify - otherwise with the `PyBoy.set_emulation_speed` method. + Example: + ```python + >>> values = pyboy.memory[0x0000:0x10000] + >>> pyboy.memory[0xC000:0xC0010] = 0 + + ``` + + """ + + self.memory_scanner = MemoryScanner(self) + """ + Provides a `pyboy.api.memory_scanner.MemoryScanner` object for locating addresses of interest in the memory space + of the Game Boy. This might require some trial and error. Values can be represented in memory in surprising ways. _Open an issue on GitHub if you need finer control, and we will take a look at it._ + + Example: + ```python + >>> current_score = 4 # You write current score in game + >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) + [] + >>> for _ in range(175): + ... pyboy.tick(1, True) # Progress the game to change score + True... + >>> current_score = 8 # You write the new score in game + >>> from pyboy.api.memory_scanner import DynamicComparisonType + >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH) + >>> print(addresses) # If repeated enough, only one address will remain + [] + + ``` + """ + + self.tilemap_background = TileMap(self, self.mb, "BACKGROUND") + """ + The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one + for the _background_ tiles. The game chooses whether it wants to use the low or the high tilemap. + + Read more details about it, in the [Pan Docs](https://gbdev.io/pandocs/Tile_Maps.html). + + Example: + ``` + >>> pyboy.tilemap_background[8,8] + 1 + >>> pyboy.tilemap_background[7:12,8] + [0, 1, 0, 1, 0] + >>> pyboy.tilemap_background[7:12,8:11] + [[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]] + + ``` + + Returns + ------- + `pyboy.api.tilemap.TileMap`: + A TileMap object for the tile map. + """ + + self.tilemap_window = TileMap(self, self.mb, "WINDOW") + """ + The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one + for the _window_ tiles. The game chooses whether it wants to use the low or the high tilemap. + + Read more details about it, in the [Pan Docs](https://gbdev.io/pandocs/Tile_Maps.html). + + Example: + ``` + >>> pyboy.tilemap_window[8,8] + 1 + >>> pyboy.tilemap_window[7:12,8] + [0, 1, 0, 1, 0] + >>> pyboy.tilemap_window[7:12,8:11] + [[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]] + + ``` + + Returns + ------- + `pyboy.api.tilemap.TileMap`: + A TileMap object for the tile map. + """ + + self.cartridge_title = self.mb.cartridge.gamename + """ + The title stored on the currently loaded cartridge ROM. The title is all upper-case ASCII and may + have been truncated to 11 characters. + + Example: + ```python + >>> pyboy.cartridge_title # Title of PyBoy's default ROM + 'DEFAULT-ROM' + + ``` + + Returns + ------- + str : + Game title + """ + + self._plugin_manager = PluginManager(self, self.mb, kwargs) + """ + Returns + ------- + `pyboy.plugins.manager.PluginManager`: + Object for handling plugins in PyBoy + """ + + self.game_wrapper = self._plugin_manager.gamewrapper() + """ + Provides an instance of a game-specific or generic wrapper. The game is detected by the cartridge's hard-coded + game title (see `pyboy.PyBoy.cartridge_title`). + + If the a game-specific wrapper is not found, a generic wrapper will be returned. + + To get more information, find the wrapper for your game in `pyboy.plugins`. + + Example: + ```python + >>> pyboy.game_wrapper.start_game() + >>> pyboy.game_wrapper.reset_game() + + ``` + + Returns + ------- + `pyboy.plugins.base_plugin.PyBoyGameWrapper`: + A game-specific wrapper object. """ + + self._hooks = {} + self.initialized = True + + def _tick(self, render): if self.stopped: - return True + return False t_start = time.perf_counter_ns() self._handle_events(self.events) t_pre = time.perf_counter_ns() if not self.paused: - if self.mb.tick(): - # breakpoint reached - self.plugin_manager.handle_breakpoint() - else: - self.frame_count += 1 + self.__rendering(render) + # Reenter mb.tick until we eventually get a clean exit without breakpoints + while self.mb.tick(): + # Breakpoint reached + # NOTE: Potentially reinject breakpoint that we have now stepped passed + self.mb.breakpoint_reinject() + + # NOTE: PC has not been incremented when hitting breakpoint! + breakpoint_index = self.mb.breakpoint_reached() + if breakpoint_index != -1: + self.mb.breakpoint_remove(breakpoint_index) + self.mb.breakpoint_singlestep_latch = 0 + + if not self._handle_hooks(): + self._plugin_manager.handle_breakpoint() + else: + if self.mb.breakpoint_singlestep_latch: + if not self._handle_hooks(): + self._plugin_manager.handle_breakpoint() + # Keep singlestepping on, if that's what we're doing + self.mb.breakpoint_singlestep = self.mb.breakpoint_singlestep_latch + + self.frame_count += 1 t_tick = time.perf_counter_ns() self._post_tick() t_post = time.perf_counter_ns() @@ -215,25 +387,85 @@

    Kwargs

    nsecs = t_post - t_tick self.avg_post = 0.9 * self.avg_post + (0.1*nsecs/1_000_000_000) - return self.quitting + return not self.quitting + + def tick(self, count=1, render=True): + """ + Progresses the emulator ahead by `count` frame(s). + + To run the emulator in real-time, it will need to process 60 frames a second (for example in a while-loop). + This function will block for roughly 16,67ms per frame, to not run faster than real-time, unless you specify + otherwise with the `PyBoy.set_emulation_speed` method. + + If you need finer control than 1 frame, have a look at `PyBoy.hook_register` to inject code at a specific point + in the game. + + Setting `render` to `True` will make PyBoy render the screen for *the last frame* of this tick. This can be seen + as a type of "frameskipping" optimization. + + For AI training, it's adviced to use as high a count as practical, as it will otherwise reduce performance + substantially. While setting `render` to `False`, you can still access the `PyBoy.game_area` to get a simpler + representation of the game. + + If `render` was enabled, use `pyboy.api.screen.Screen` to get a NumPy buffer or raw memory buffer. + + + Example: + ```python + >>> values = pyboy.memory[0x0000:0x10000] + >>> pyboy.memory[0xC000:0xC0010] = 0 + + ``` + + Example: + ```python + >>> pyboy.tick() # Progress 1 frame with rendering + True + >>> pyboy.tick(1) # Progress 1 frame with rendering + True + >>> pyboy.tick(60, False) # Progress 60 frame *without* rendering + True + >>> pyboy.tick(60, True) # Progress 60 frame and render *only the last frame* + True + >>> for _ in range(60): # Progress 60 frames and render every frame + ... if not pyboy.tick(1, True): + ... break + >>> + ``` + + Args: + count (int): Number of ticks to process. -1 is infinite. + render (bool): Whether to render an image for this tick + Returns + ------- + (True or False): + False if emulation has ended otherwise True + """ + + running = False + while count != 0: + _render = render and count == 1 # Only render on last tick to improve performance + running = self._tick(_render) + count -= 1 + return running def _handle_events(self, events): # This feeds events into the tick-loop from the window. There might already be events in the list from the API. - events = self.plugin_manager.handle_events(events) + events = self._plugin_manager.handle_events(events) for event in events: if event == WindowEvent.QUIT: self.quitting = True elif event == WindowEvent.RELEASE_SPEED_UP: # Switch between unlimited and 1x real-time emulation speed self.target_emulationspeed = int(bool(self.target_emulationspeed) ^ True) - logger.debug("Speed limit: %s" % self.target_emulationspeed) + logger.debug("Speed limit: %d", self.target_emulationspeed) elif event == WindowEvent.STATE_SAVE: with open(self.gamerom_file + ".state", "wb") as f: self.mb.save_state(IntIOWrapper(f)) elif event == WindowEvent.STATE_LOAD: state_path = self.gamerom_file + ".state" if not os.path.isfile(state_path): - logger.error(f"State file not found: {state_path}") + logger.error("State file not found: %s", state_path) continue with open(state_path, "rb") as f: self.mb.load_state(IntIOWrapper(f)) @@ -249,7 +481,7 @@

    Kwargs

    elif event == WindowEvent.UNPAUSE: self._unpause() elif event == WindowEvent._INTERNAL_RENDERER_FLUSH: - self.plugin_manager._post_tick_windows() + self._plugin_manager._post_tick_windows() else: self.mb.buttonevent(event) @@ -273,21 +505,22 @@

    Kwargs

    def _post_tick(self): if self.frame_count % 60 == 0: self._update_window_title() - self.plugin_manager.post_tick() - self.plugin_manager.frame_limiter(self.target_emulationspeed) + self._plugin_manager.post_tick() + self._plugin_manager.frame_limiter(self.target_emulationspeed) # Prepare an empty list, as the API might be used to send in events between ticks self.old_events = self.events - self.events = [] + self.events = self.queued_input + self.queued_input = [] def _update_window_title(self): avg_emu = self.avg_pre + self.avg_tick + self.avg_post - self.window_title = "CPU/frame: %0.2f%%" % ((self.avg_pre + self.avg_tick) / SPF * 100) - self.window_title += " Emulation: x%s" % (round(SPF / avg_emu) if avg_emu > 0 else "INF") + self.window_title = f"CPU/frame: {(self.avg_pre + self.avg_tick) / SPF * 100:0.2f}%" + self.window_title += f' Emulation: x{(round(SPF / avg_emu) if avg_emu > 0 else "INF")}' if self.paused: self.window_title += "[PAUSED]" - self.window_title += self.plugin_manager.window_title() - self.plugin_manager._set_title() + self.window_title += self._plugin_manager.window_title() + self._plugin_manager._set_title() def __del__(self): self.stop(save=False) @@ -302,6 +535,13 @@

    Kwargs

    """ Gently stops the emulator and all sub-modules. + Example: + ```python + >>> pyboy.stop() # Stop emulator and save game progress (cartridge RAM) + >>> pyboy.stop(False) # Stop emulator and discard game progress (cartridge RAM) + + ``` + Args: save (bool): Specify whether to save the game upon stopping. It will always be saved in a file next to the provided game-ROM. @@ -310,7 +550,7 @@

    Kwargs

    logger.info("###########################") logger.info("# Emulator is turning off #") logger.info("###########################") - self.plugin_manager.stop() + self._plugin_manager.stop() self.mb.stop(save) self.stopped = True @@ -318,147 +558,163 @@

    Kwargs

    # Scripts and bot methods # - def botsupport_manager(self): - """ - - Returns - ------- - `pyboy.botsupport.BotSupportManager`: - The manager, which gives easier access to the emulated game through the classes in `pyboy.botsupport`. - """ - return botsupport.BotSupportManager(self, self.mb) - - def openai_gym(self, observation_type="tiles", action_type="press", simultaneous_actions=False, **kwargs): + def button(self, input): """ - For Reinforcement learning, it is often easier to use the standard gym environment. This method will provide one. - This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins. - Additional kwargs are passed to the start_game method of the game_wrapper. + Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down". - Args: - observation_type (str): Define what the agent will be able to see: - * `"raw"`: Gives the raw pixels color - * `"tiles"`: Gives the id of the sprites in 8x8 pixel zones of the game_area defined by the game_wrapper. - * `"compressed"`: Gives a more detailled but heavier representation than `"minimal"`. - * `"minimal"`: Gives a minimal representation defined by the game_wrapper (recommended). + The button will automatically be released at the following call to `PyBoy.tick`. - action_type (str): Define how the agent will interact with button inputs - * `"press"`: The agent will only press inputs for 1 frame an then release it. - * `"toggle"`: The agent will toggle inputs, first time it press and second time it release. - * `"all"`: The agent have access to all inputs, press and release are separated. + Example: + ```python + >>> pyboy.button('a') # Press button 'a' and release after `pyboy.tick()` + >>> pyboy.tick() # Button 'a' pressed + True + >>> pyboy.tick() # Button 'a' released + True - simultaneous_actions (bool): Allow to inject multiple input at once. This dramatically increases the action_space: \\(n \\rightarrow 2^n\\) + ``` - Returns - ------- - `pyboy.openai_gym.PyBoyGymEnv`: - A Gym environment based on the `Pyboy` object. + Args: + input (str): button to press """ - if gym_enabled: - return PyBoyGymEnv(self, observation_type, action_type, simultaneous_actions, **kwargs) + input = input.lower() + if input == "left": + self.send_input(WindowEvent.PRESS_ARROW_LEFT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_LEFT)) + elif input == "right": + self.send_input(WindowEvent.PRESS_ARROW_RIGHT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_RIGHT)) + elif input == "up": + self.send_input(WindowEvent.PRESS_ARROW_UP) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_UP)) + elif input == "down": + self.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_DOWN)) + elif input == "a": + self.send_input(WindowEvent.PRESS_BUTTON_A) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_A)) + elif input == "b": + self.send_input(WindowEvent.PRESS_BUTTON_B) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_B)) + elif input == "start": + self.send_input(WindowEvent.PRESS_BUTTON_START) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_START)) + elif input == "select": + self.send_input(WindowEvent.PRESS_BUTTON_SELECT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_SELECT)) else: - logger.error(f"{__name__}: Missing dependency \"gym\". ") - return None - - def game_wrapper(self): - """ - Provides an instance of a game-specific wrapper. The game is detected by the cartridge's hard-coded game title - (see `pyboy.PyBoy.cartridge_title`). - - If the game isn't supported, None will be returned. - - To get more information, find the wrapper for your game in `pyboy.plugins`. + raise Exception("Unrecognized input:", input) - Returns - ------- - `pyboy.plugins.base_plugin.PyBoyGameWrapper`: - A game-specific wrapper object. - """ - return self.plugin_manager.gamewrapper() - - def get_memory_value(self, addr): - """ - Reads a given memory address of the Game Boy's current memory state. This will not directly give you access to - all switchable memory banks. Open an issue on GitHub if that is needed, or use `PyBoy.set_memory_value` to send - MBC commands to the virtual cartridge. - - Returns - ------- - int: - An integer with the value of the memory address - """ - return self.mb.getitem(addr) - - def set_memory_value(self, addr, value): + def button_press(self, input): """ - Write one byte to a given memory address of the Game Boy's current memory state. + Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down". - This will not directly give you access to all switchable memory banks. + The button will remain press until explicitly released with `PyBoy.button_release` or `PyBoy.send_input`. - __NOTE:__ This function will not let you change ROM addresses (0x0000 to 0x8000). If you write to these - addresses, it will send commands to the "Memory Bank Controller" (MBC) of the virtual cartridge. You can read - about the MBC at [Pan Docs](http://bgb.bircd.org/pandocs.htm). + Example: + ```python + >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' pressed + True + >>> pyboy.tick() # Button 'a' still pressed + True + >>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' released + True - If you need to change ROM values, see `pyboy.PyBoy.override_memory_value`. + ``` Args: - addr (int): Address to write the byte - value (int): A byte of data + input (str): button to press """ - self.mb.setitem(addr, value) + input = input.lower() + + if input == "left": + self.send_input(WindowEvent.PRESS_ARROW_LEFT) + elif input == "right": + self.send_input(WindowEvent.PRESS_ARROW_RIGHT) + elif input == "up": + self.send_input(WindowEvent.PRESS_ARROW_UP) + elif input == "down": + self.send_input(WindowEvent.PRESS_ARROW_DOWN) + elif input == "a": + self.send_input(WindowEvent.PRESS_BUTTON_A) + elif input == "b": + self.send_input(WindowEvent.PRESS_BUTTON_B) + elif input == "start": + self.send_input(WindowEvent.PRESS_BUTTON_START) + elif input == "select": + self.send_input(WindowEvent.PRESS_BUTTON_SELECT) + else: + raise Exception("Unrecognized input") - def override_memory_value(self, rom_bank, addr, value): + def button_release(self, input): """ - Override one byte at a given memory address of the Game Boy's ROM. + Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down". - This will let you override data in the ROM at any given bank. This is the memory allocated at 0x0000 to 0x8000, where 0x4000 to 0x8000 can be changed from the MBC. + This will release a button after a call to `PyBoy.button_press` or `PyBoy.send_input`. - __NOTE__: Any changes here are not saved or loaded to game states! Use this function with caution and reapply - any overrides when reloading the ROM. + Example: + ```python + >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' pressed + True + >>> pyboy.tick() # Button 'a' still pressed + True + >>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' released + True - If you need to change a RAM address, see `pyboy.PyBoy.set_memory_value`. + ``` Args: - rom_bank (int): ROM bank to do the overwrite in - addr (int): Address to write the byte inside the ROM bank - value (int): A byte of data + input (str): button to release """ - # TODO: If you change a RAM value outside of the ROM banks above, the memory value will stay the same no matter - # what the game writes to the address. This can be used so freeze the value for health, cash etc. - self.mb.cartridge.overrideitem(rom_bank, addr, value) + input = input.lower() + if input == "left": + self.send_input(WindowEvent.RELEASE_ARROW_LEFT) + elif input == "right": + self.send_input(WindowEvent.RELEASE_ARROW_RIGHT) + elif input == "up": + self.send_input(WindowEvent.RELEASE_ARROW_UP) + elif input == "down": + self.send_input(WindowEvent.RELEASE_ARROW_DOWN) + elif input == "a": + self.send_input(WindowEvent.RELEASE_BUTTON_A) + elif input == "b": + self.send_input(WindowEvent.RELEASE_BUTTON_B) + elif input == "start": + self.send_input(WindowEvent.RELEASE_BUTTON_START) + elif input == "select": + self.send_input(WindowEvent.RELEASE_BUTTON_SELECT) + else: + raise Exception("Unrecognized input") def send_input(self, event): """ - Send a single input to control the emulator. This is both Game Boy buttons and emulator controls. + Send a single input to control the emulator. This is both Game Boy buttons and emulator controls. See + `pyboy.utils.WindowEvent` for which events to send. - See `pyboy.WindowEvent` for which events to send. - - Args: - event (pyboy.WindowEvent): The event to send - """ - self.events.append(WindowEvent(event)) + Consider using `PyBoy.button` instead for easier access. - def get_input( - self, - ignore=( - WindowEvent.PASS, WindowEvent._INTERNAL_TOGGLE_DEBUG, WindowEvent._INTERNAL_RENDERER_FLUSH, - WindowEvent._INTERNAL_MOUSE, WindowEvent._INTERNAL_MARK_TILE - ) - ): - """ - Get current inputs except the events specified in "ignore" tuple. - This is both Game Boy buttons and emulator controls. + Example: + ```python + >>> from pyboy.utils import WindowEvent + >>> pyboy.send_input(WindowEvent.PRESS_BUTTON_A) # Press button 'a' and keep pressed after `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' pressed + True + >>> pyboy.tick() # Button 'a' still pressed + True + >>> pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) # Release button 'a' on next call to `PyBoy.tick()` + >>> pyboy.tick() # Button 'a' released + True - See `pyboy.WindowEvent` for which events to get. + ``` Args: - ignore (tuple): Events this function should ignore - - Returns - ------- - list: - List of the `pyboy.utils.WindowEvent`s processed for the last call to `pyboy.PyBoy.tick` + event (pyboy.WindowEvent): The event to send """ - return [x for x in self.old_events if x not in ignore] + self.events.append(WindowEvent(event)) def save_state(self, file_like_object): """ @@ -468,13 +724,19 @@

    Kwargs

    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. @@ -494,10 +756,12 @@

    Kwargs

    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. @@ -508,22 +772,135 @@

    Kwargs

    self.mb.load_state(IntIOWrapper(file_like_object)) - def screen_image(self): + 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: + ```python + >>> # This example show nothing, but a support 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: + ```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 - 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. + 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: + ```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() def _serial(self): """ @@ -547,427 +924,792 @@

    Kwargs

    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 __rendering(self, value): """ - 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. + Disable or enable rendering + """ + self.mb.lcd.disable_renderer = not value + + def _is_cpu_stuck(self): + return self.mb.cpu.is_stuck + + def _load_symbols(self): + gamerom_file_no_ext, rom_ext = os.path.splitext(self.gamerom_file) + for sym_path in [self.symbols_file, gamerom_file_no_ext + ".sym", gamerom_file_no_ext + rom_ext + ".sym"]: + if sym_path and os.path.isfile(sym_path): + logger.info("Loading symbol file: %s", sym_path) + with open(sym_path) as f: + for _line in f.readlines(): + line = _line.strip() + if line == "": + continue + elif line.startswith(";"): + continue + elif line.startswith("["): + # Start of key group + # [labels] + # [definitions] + continue + + try: + bank, addr, sym_label = re.split(":| ", line.strip()) + bank = int(bank, 16) + addr = int(addr, 16) + if not bank in self.rom_symbols: + self.rom_symbols[bank] = {} + + self.rom_symbols[bank][addr] = sym_label + except ValueError as ex: + logger.warning("Skipping .sym line: %s", line.strip()) + return self.rom_symbols + + def _lookup_symbol(self, symbol): + for bank, addresses in self.rom_symbols.items(): + for addr, label in addresses.items(): + if label == symbol: + return bank, addr + raise ValueError("Symbol not found: %s" % symbol) + + def hook_register(self, bank, addr, callback, context): + """ + 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: + ```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="file.rom.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 + """ + 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) + + def hook_deregister(self, bank, addr): + """ + Remove a previously registered hook from a specific bank and memory address. + + Example: + ```python + >>> context = "Hello from hook" + >>> def my_callback(context): + ... print(context) + >>> pyboy.hook_register(0, 0x2000, my_callback, context) + >>> pyboy.hook_deregister(0, 0x2000) + + ``` + This function can also deregister a hook based on a symbol. See `PyBoy.hook_register` for details. + + Example: + ```python + >>> pyboy.hook_register(None, "Main", lambda x: print(x), "Hello from hook") + >>> pyboy.hook_deregister(None, "Main") + + ``` + + 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) + """ + if bank is None and isinstance(addr, str): + bank, addr = self._lookup_symbol(addr) + + index = self.mb.breakpoint_find(bank, addr) + if index == -1: + raise ValueError("Breakpoint not found for bank and addr") + + _, _, opcode = self.mb.breakpoints_list[index] + self.mb.breakpoint_remove(index) + bank_addr_opcode = (bank & 0xFF) << 24 | (addr & 0xFFFF) << 8 | (opcode & 0xFF) + self._hooks.pop(bank_addr_opcode) + + def _handle_hooks(self): + if _handler := self._hooks.get(self.mb.breakpoint_waiting): + (callback, context) = _handler + callback(context) + return True + return False + + def get_sprite(self, sprite_index): + """ + Provides a `pyboy.api.sprite.Sprite` object, which makes the OAM data more presentable. The given index + corresponds to index of the sprite in the "Object Attribute Memory" (OAM). + + The Game Boy supports 40 sprites in total. Read more details about it, in the [Pan + Docs](http://bgb.bircd.org/pandocs.htm). + + ```python + >>> s = pyboy.get_sprite(12) + >>> s + Sprite [12]: Position: (-8, -16), Shape: (8, 8), Tiles: (Tile: 0), On screen: False + >>> s.on_screen + False + >>> s.tiles + [Tile: 0] + + ``` + + Args: + index (int): Sprite index from 0 to 39. Returns ------- - str : - Game title + `pyboy.api.sprite.Sprite`: + Sprite corresponding to the given index. """ - return self.mb.cartridge.gamename + return Sprite(self.mb, sprite_index) - def _rendering(self, value): - """ - Disable or enable rendering + def get_sprite_by_tile_identifier(self, tile_identifiers, on_screen=True): """ - self.mb.lcd.disable_renderer = not value + Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile + identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the + `pyboy.sprite` function to get a `pyboy.api.sprite.Sprite` object. - def _is_cpu_stuck(self): - return self.mb.cpu.is_stuck
    - -

    Methods

    -
    -
    -def tick(self) -
    -
    -

    Progresses the emulator ahead by one frame.

    -

    To run the emulator in real-time, this will need to be called 60 times a second (for example in a while-loop). -This function will block for roughly 16,67ms at a time, to not run faster than real-time, unless you specify -otherwise with the PyBoy.set_emulation_speed() method.

    -

    Open an issue on GitHub if you need finer control, and we will take a look at it.

    -
    - -Expand source code - -
    def tick(self):
    -    """
    -    Progresses the emulator ahead by one frame.
    +        Example:
    +        ```python
    +        >>> print(pyboy.get_sprite_by_tile_identifier([43, 123]))
    +        [[0, 2, 4], []]
     
    -    To run the emulator in real-time, this will need to be called 60 times a second (for example in a while-loop).
    -    This function will block for roughly 16,67ms at a time, to not run faster than real-time, unless you specify
    -    otherwise with the `PyBoy.set_emulation_speed` method.
    +        ```
     
    -    _Open an issue on GitHub if you need finer control, and we will take a look at it._
    -    """
    -    if self.stopped:
    -        return True
    -
    -    t_start = time.perf_counter_ns()
    -    self._handle_events(self.events)
    -    t_pre = time.perf_counter_ns()
    -    if not self.paused:
    -        if self.mb.tick():
    -            # breakpoint reached
    -            self.plugin_manager.handle_breakpoint()
    -        else:
    -            self.frame_count += 1
    -    t_tick = time.perf_counter_ns()
    -    self._post_tick()
    -    t_post = time.perf_counter_ns()
    +        Meaning, that tile identifier `43` is found at the sprite indexes: 0, 2, and 4, while tile identifier
    +        `123` was not found anywhere.
    +
    +        Args:
    +            identifiers (list): List of tile identifiers (int)
    +            on_screen (bool): Require that the matched sprite is on screen
    +
    +        Returns
    +        -------
    +        list:
    +            list of sprite matches for every tile identifier in the input
    +        """
    +
    +        matches = []
    +        for i in tile_identifiers:
    +            match = []
    +            for s in range(constants.SPRITES):
    +                sprite = Sprite(self.mb, s)
    +                for t in sprite.tiles:
    +                    if t.tile_identifier == i and (not on_screen or (on_screen and sprite.on_screen)):
    +                        match.append(s)
    +            matches.append(match)
    +        return matches
    +
    +    def get_tile(self, identifier):
    +        """
    +        The Game Boy can have 384 tiles loaded in memory at once (768 for Game Boy Color). Use this method to get a
    +        `pyboy.api.tile.Tile`-object for given identifier.
     
    -    nsecs = t_pre - t_start
    -    self.avg_pre = 0.9 * self.avg_pre + (0.1*nsecs/1_000_000_000)
    +        The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See
    +        the `pyboy.api.tile.Tile` object for more information.
     
    -    nsecs = t_tick - t_pre
    -    self.avg_tick = 0.9 * self.avg_tick + (0.1*nsecs/1_000_000_000)
    +        Example:
    +        ```python
    +        >>> t = pyboy.get_tile(2)
    +        >>> t
    +        Tile: 2
    +        >>> t.shape
    +        (8, 8)
     
    -    nsecs = t_post - t_tick
    -    self.avg_post = 0.9 * self.avg_post + (0.1*nsecs/1_000_000_000)
    +        ```
     
    -    return self.quitting
    + Returns + ------- + `pyboy.api.tile.Tile`: + A Tile object for the given identifier. + """ + return Tile(self.mb, identifier=identifier)
    +

    Instance variables

    +
    +
    var screen
    +
    +

    Use this method to get a Screen object. This can be used to get the screen buffer in +a variety of formats.

    +

    It's also here you can find the screen position (SCX, SCY, WX, WY) for each scan line in the screen buffer. See +Screen.tilemap_position_list for more information.

    +

    Example:

    +
    >>> pyboy.screen.image.show()
    +>>> pyboy.screen.ndarray.shape
    +(144, 160, 4)
    +>>> pyboy.screen.raw_buffer_format
    +'RGBA'
    +
    +
    +

    Returns

    +

    Screen: +A Screen object with helper functions for reading the screen buffer.

    -
    -def stop(self, save=True) -
    +
    var memory
    -

    Gently stops the emulator and all sub-modules.

    -

    Args

    -
    -
    save : bool
    -
    Specify whether to save the game upon stopping. It will always be saved in a file next to the -provided game-ROM.
    -
    -
    - -Expand source code - -
    def stop(self, save=True):
    -    """
    -    Gently stops the emulator and all sub-modules.
    +

    Provides a pyboy.PyBoyMemoryView object for reading and writing the memory space of the Game Boy.

    +

    Example:

    +
    >>> values = pyboy.memory[0x0000:0x10000]
    +>>> pyboy.memory[0xC000:0xC0010] = 0
     
    -    Args:
    -        save (bool): Specify whether to save the game upon stopping. It will always be saved in a file next to the
    -            provided game-ROM.
    -    """
    -    if self.initialized and not self.stopped:
    -        logger.info("###########################")
    -        logger.info("# Emulator is turning off #")
    -        logger.info("###########################")
    -        self.plugin_manager.stop()
    -        self.mb.stop(save)
    -        self.stopped = True
    -
    +
    -
    -def botsupport_manager(self) -
    +
    var memory_scanner
    -

    Returns

    -

    BotSupportManager: -The manager, which gives easier access to the emulated game through the classes in pyboy.botsupport.

    -
    - -Expand source code - -
    def botsupport_manager(self):
    -    """
    +

    Provides a MemoryScanner object for locating addresses of interest in the memory space +of the Game Boy. This might require some trial and error. Values can be represented in memory in surprising ways.

    +

    Open an issue on GitHub if you need finer control, and we will take a look at it.

    +

    Example:

    +
    >>> current_score = 4 # You write current score in game
    +>>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
    +[]
    +>>> for _ in range(175):
    +...     pyboy.tick(1, True) # Progress the game to change score
    +True...
    +>>> current_score = 8 # You write the new score in game
    +>>> from pyboy.api.memory_scanner import DynamicComparisonType
    +>>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
    +>>> print(addresses) # If repeated enough, only one address will remain
    +[]
     
    -    Returns
    -    -------
    -    `pyboy.botsupport.BotSupportManager`:
    -        The manager, which gives easier access to the emulated game through the classes in `pyboy.botsupport`.
    -    """
    -    return botsupport.BotSupportManager(self, self.mb)
    -
    +
    -
    -def openai_gym(self, observation_type='tiles', action_type='press', simultaneous_actions=False, **kwargs) -
    +
    var tilemap_background
    -

    For Reinforcement learning, it is often easier to use the standard gym environment. This method will provide one. -This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins. -Additional kwargs are passed to the start_game method of the game_wrapper.

    -

    Args

    +

    The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one +for the background tiles. The game chooses whether it wants to use the low or the high tilemap.

    +

    Read more details about it, in the Pan Docs.

    +

    Example:

    +
    >>> pyboy.tilemap_background[8,8]
    +1
    +>>> pyboy.tilemap_background[7:12,8]
    +[0, 1, 0, 1, 0]
    +>>> pyboy.tilemap_background[7:12,8:11]
    +[[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]]
    +
    +
    +

    Returns

    +

    TileMap: +A TileMap object for the tile map.

    +
    +
    var tilemap_window
    +
    +

    The Game Boy uses two tile maps at the same time to draw graphics on the screen. This method will provide one +for the window tiles. The game chooses whether it wants to use the low or the high tilemap.

    +

    Read more details about it, in the Pan Docs.

    +

    Example:

    +
    >>> pyboy.tilemap_window[8,8]
    +1
    +>>> pyboy.tilemap_window[7:12,8]
    +[0, 1, 0, 1, 0]
    +>>> pyboy.tilemap_window[7:12,8:11]
    +[[0, 1, 0, 1, 0], [0, 2, 3, 4, 5], [0, 0, 6, 0, 0]]
    +
    +
    +

    Returns

    +

    TileMap: +A TileMap object for the tile map.

    +
    +
    var cartridge_title
    +
    +

    The title stored on the currently loaded cartridge ROM. The title is all upper-case ASCII and may +have been truncated to 11 characters.

    +

    Example:

    +
    >>> pyboy.cartridge_title # Title of PyBoy's default ROM
    +'DEFAULT-ROM'
    +
    +
    +

    Returns

    -
    observation_type : str
    -
    Define what the agent will be able to see:
    +
    str :
    +
    Game title
    +
    +
    +
    var game_wrapper
    +
    +

    Provides an instance of a game-specific or generic wrapper. The game is detected by the cartridge's hard-coded +game title (see PyBoy.cartridge_title).

    +

    If the a game-specific wrapper is not found, a generic wrapper will be returned.

    +

    To get more information, find the wrapper for your game in pyboy.plugins.

    +

    Example:

    +
    >>> pyboy.game_wrapper.start_game()
    +>>> pyboy.game_wrapper.reset_game()
    +
    +
    +

    Returns

    +

    PyBoyGameWrapper: +A game-specific wrapper object.

    +
    -
      -
    • "raw": Gives the raw pixels color
    • -
    • "tiles": -Gives the id of the sprites in 8x8 pixel zones of the game_area defined by the game_wrapper.
    • -
    • "compressed": Gives a more detailled but heavier representation than "minimal".
    • -
    • "minimal": Gives a minimal representation defined by the game_wrapper (recommended).
    • -
    +

    Methods

    -
    action_type : str
    -
    Define how the agent will interact with button inputs
    -
    -
      -
    • "press": The agent will only press inputs for 1 frame an then release it.
    • -
    • "toggle": The agent will toggle inputs, first time it press and second time it release.
    • -
    • "all": The agent have access to all inputs, press and release are separated.
    • -
    +
    +def tick(self, count=1, render=True) +
    +
    +

    Progresses the emulator ahead by count frame(s).

    +

    To run the emulator in real-time, it will need to process 60 frames a second (for example in a while-loop). +This function will block for roughly 16,67ms per frame, to not run faster than real-time, unless you specify +otherwise with the PyBoy.set_emulation_speed() method.

    +

    If you need finer control than 1 frame, have a look at PyBoy.hook_register() to inject code at a specific point +in the game.

    +

    Setting render to True will make PyBoy render the screen for the last frame of this tick. This can be seen +as a type of "frameskipping" optimization.

    +

    For AI training, it's adviced to use as high a count as practical, as it will otherwise reduce performance +substantially. While setting render to False, you can still access the PyBoy.game_area() to get a simpler +representation of the game.

    +

    If render was enabled, use Screen to get a NumPy buffer or raw memory buffer.

    +

    Example:

    +
    >>> values = pyboy.memory[0x0000:0x10000]
    +>>> pyboy.memory[0xC000:0xC0010] = 0
    +
    +
    +

    Example:

    +
    >>> pyboy.tick() # Progress 1 frame with rendering
    +True
    +>>> pyboy.tick(1) # Progress 1 frame with rendering
    +True
    +>>> pyboy.tick(60, False) # Progress 60 frame *without* rendering
    +True
    +>>> pyboy.tick(60, True) # Progress 60 frame and render *only the last frame*
    +True
    +>>> for _ in range(60): # Progress 60 frames and render every frame
    +...     if not pyboy.tick(1, True):
    +...         break
    +>>>
    +
    +

    Args

    -
    simultaneous_actions : bool
    -
    Allow to inject multiple input at once. This dramatically increases the action_space: n \rightarrow 2^n
    +
    count : int
    +
    Number of ticks to process. -1 is infinite.
    +
    render : bool
    +
    Whether to render an image for this tick

    Returns

    -

    PyBoyGymEnv: -A Gym environment based on the Pyboy object.

    +

    (True or False): +False if emulation has ended otherwise True

    Expand source code -
    def openai_gym(self, observation_type="tiles", action_type="press", simultaneous_actions=False, **kwargs):
    +
    def tick(self, count=1, render=True):
         """
    -    For Reinforcement learning, it is often easier to use the standard gym environment. This method will provide one.
    -    This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins.
    -    Additional kwargs are passed to the start_game method of the game_wrapper.
    +    Progresses the emulator ahead by `count` frame(s).
     
    -    Args:
    -        observation_type (str): Define what the agent will be able to see:
    -        * `"raw"`: Gives the raw pixels color
    -        * `"tiles"`:  Gives the id of the sprites in 8x8 pixel zones of the game_area defined by the game_wrapper.
    -        * `"compressed"`: Gives a more detailled but heavier representation than `"minimal"`.
    -        * `"minimal"`: Gives a minimal representation defined by the game_wrapper (recommended).
    +    To run the emulator in real-time, it will need to process 60 frames a second (for example in a while-loop).
    +    This function will block for roughly 16,67ms per frame, to not run faster than real-time, unless you specify
    +    otherwise with the `PyBoy.set_emulation_speed` method.
     
    -        action_type (str): Define how the agent will interact with button inputs
    -        * `"press"`: The agent will only press inputs for 1 frame an then release it.
    -        * `"toggle"`: The agent will toggle inputs, first time it press and second time it release.
    -        * `"all"`: The agent have access to all inputs, press and release are separated.
    +    If you need finer control than 1 frame, have a look at `PyBoy.hook_register` to inject code at a specific point
    +    in the game.
     
    -        simultaneous_actions (bool): Allow to inject multiple input at once. This dramatically increases the action_space: \\(n \\rightarrow 2^n\\)
    +    Setting `render` to `True` will make PyBoy render the screen for *the last frame* of this tick. This can be seen
    +    as a type of "frameskipping" optimization.
    +
    +    For AI training, it's adviced to use as high a count as practical, as it will otherwise reduce performance
    +    substantially. While setting `render` to `False`, you can still access the `PyBoy.game_area` to get a simpler
    +    representation of the game.
    +
    +    If `render` was enabled, use `pyboy.api.screen.Screen` to get a NumPy buffer or raw memory buffer.
     
    -    Returns
    -    -------
    -    `pyboy.openai_gym.PyBoyGymEnv`:
    -        A Gym environment based on the `Pyboy` object.
    -    """
    -    if gym_enabled:
    -        return PyBoyGymEnv(self, observation_type, action_type, simultaneous_actions, **kwargs)
    -    else:
    -        logger.error(f"{__name__}: Missing dependency \"gym\". ")
    -        return None
    -
    -
    -
    -def game_wrapper(self) -
    -
    -

    Provides an instance of a game-specific wrapper. The game is detected by the cartridge's hard-coded game title -(see PyBoy.cartridge_title()).

    -

    If the game isn't supported, None will be returned.

    -

    To get more information, find the wrapper for your game in pyboy.plugins.

    -

    Returns

    -

    PyBoyGameWrapper: -A game-specific wrapper object.

    -
    - -Expand source code - -
    def game_wrapper(self):
    -    """
    -    Provides an instance of a game-specific wrapper. The game is detected by the cartridge's hard-coded game title
    -    (see `pyboy.PyBoy.cartridge_title`).
     
    -    If the game isn't supported, None will be returned.
    +    Example:
    +    ```python
    +    >>> values = pyboy.memory[0x0000:0x10000]
    +    >>> pyboy.memory[0xC000:0xC0010] = 0
     
    -    To get more information, find the wrapper for your game in `pyboy.plugins`.
    +    ```
     
    +    Example:
    +    ```python
    +    >>> pyboy.tick() # Progress 1 frame with rendering
    +    True
    +    >>> pyboy.tick(1) # Progress 1 frame with rendering
    +    True
    +    >>> pyboy.tick(60, False) # Progress 60 frame *without* rendering
    +    True
    +    >>> pyboy.tick(60, True) # Progress 60 frame and render *only the last frame*
    +    True
    +    >>> for _ in range(60): # Progress 60 frames and render every frame
    +    ...     if not pyboy.tick(1, True):
    +    ...         break
    +    >>>
    +    ```
    +
    +    Args:
    +        count (int): Number of ticks to process. -1 is infinite.
    +        render (bool): Whether to render an image for this tick
         Returns
         -------
    -    `pyboy.plugins.base_plugin.PyBoyGameWrapper`:
    -        A game-specific wrapper object.
    +    (True or False):
    +        False if emulation has ended otherwise True
         """
    -    return self.plugin_manager.gamewrapper()
    + + running = False + while count != 0: + _render = render and count == 1 # Only render on last tick to improve performance + running = self._tick(_render) + count -= 1 + return running
    -
    -def get_memory_value(self, addr) +
    +def stop(self, save=True)
    -

    Reads a given memory address of the Game Boy's current memory state. This will not directly give you access to -all switchable memory banks. Open an issue on GitHub if that is needed, or use PyBoy.set_memory_value() to send -MBC commands to the virtual cartridge.

    -

    Returns

    +

    Gently stops the emulator and all sub-modules.

    +

    Example:

    +
    >>> pyboy.stop() # Stop emulator and save game progress (cartridge RAM)
    +>>> pyboy.stop(False) # Stop emulator and discard game progress (cartridge RAM)
    +
    +
    +

    Args

    -
    int:
    -
    An integer with the value of the memory address
    +
    save : bool
    +
    Specify whether to save the game upon stopping. It will always be saved in a file next to the +provided game-ROM.
    Expand source code -
    def get_memory_value(self, addr):
    +
    def stop(self, save=True):
         """
    -    Reads a given memory address of the Game Boy's current memory state. This will not directly give you access to
    -    all switchable memory banks. Open an issue on GitHub if that is needed, or use `PyBoy.set_memory_value` to send
    -    MBC commands to the virtual cartridge.
    +    Gently stops the emulator and all sub-modules.
     
    -    Returns
    -    -------
    -    int:
    -        An integer with the value of the memory address
    +    Example:
    +    ```python
    +    >>> pyboy.stop() # Stop emulator and save game progress (cartridge RAM)
    +    >>> pyboy.stop(False) # Stop emulator and discard game progress (cartridge RAM)
    +
    +    ```
    +
    +    Args:
    +        save (bool): Specify whether to save the game upon stopping. It will always be saved in a file next to the
    +            provided game-ROM.
         """
    -    return self.mb.getitem(addr)
    + if self.initialized and not self.stopped: + logger.info("###########################") + logger.info("# Emulator is turning off #") + logger.info("###########################") + self._plugin_manager.stop() + self.mb.stop(save) + self.stopped = True
    -
    -def set_memory_value(self, addr, value) +
    +def button(self, input)
    -

    Write one byte to a given memory address of the Game Boy's current memory state.

    -

    This will not directly give you access to all switchable memory banks.

    -

    NOTE: This function will not let you change ROM addresses (0x0000 to 0x8000). If you write to these -addresses, it will send commands to the "Memory Bank Controller" (MBC) of the virtual cartridge. You can read -about the MBC at Pan Docs.

    -

    If you need to change ROM values, see PyBoy.override_memory_value().

    +

    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".

    +

    The button will automatically be released at the following call to PyBoy.tick().

    +

    Example:

    +
    >>> pyboy.button('a') # Press button 'a' and release after `pyboy.tick()`
    +>>> pyboy.tick() # Button 'a' pressed
    +True
    +>>> pyboy.tick() # Button 'a' released
    +True
    +
    +

    Args

    -
    addr : int
    -
    Address to write the byte
    -
    value : int
    -
    A byte of data
    +
    input : str
    +
    button to press
    Expand source code -
    def set_memory_value(self, addr, value):
    +
    def button(self, input):
         """
    -    Write one byte to a given memory address of the Game Boy's current memory state.
    +    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".
     
    -    This will not directly give you access to all switchable memory banks.
    +    The button will automatically be released at the following call to `PyBoy.tick`.
     
    -    __NOTE:__ This function will not let you change ROM addresses (0x0000 to 0x8000). If you write to these
    -    addresses, it will send commands to the "Memory Bank Controller" (MBC) of the virtual cartridge. You can read
    -    about the MBC at [Pan Docs](http://bgb.bircd.org/pandocs.htm).
    +    Example:
    +    ```python
    +    >>> pyboy.button('a') # Press button 'a' and release after `pyboy.tick()`
    +    >>> pyboy.tick() # Button 'a' pressed
    +    True
    +    >>> pyboy.tick() # Button 'a' released
    +    True
     
    -    If you need to change ROM values, see `pyboy.PyBoy.override_memory_value`.
    +    ```
     
         Args:
    -        addr (int): Address to write the byte
    -        value (int): A byte of data
    +        input (str): button to press
         """
    -    self.mb.setitem(addr, value)
    + input = input.lower() + if input == "left": + self.send_input(WindowEvent.PRESS_ARROW_LEFT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_LEFT)) + elif input == "right": + self.send_input(WindowEvent.PRESS_ARROW_RIGHT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_RIGHT)) + elif input == "up": + self.send_input(WindowEvent.PRESS_ARROW_UP) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_UP)) + elif input == "down": + self.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_ARROW_DOWN)) + elif input == "a": + self.send_input(WindowEvent.PRESS_BUTTON_A) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_A)) + elif input == "b": + self.send_input(WindowEvent.PRESS_BUTTON_B) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_B)) + elif input == "start": + self.send_input(WindowEvent.PRESS_BUTTON_START) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_START)) + elif input == "select": + self.send_input(WindowEvent.PRESS_BUTTON_SELECT) + self.queued_input.append(WindowEvent(WindowEvent.RELEASE_BUTTON_SELECT)) + else: + raise Exception("Unrecognized input:", input)
    -
    -def override_memory_value(self, rom_bank, addr, value) +
    +def button_press(self, input)
    -

    Override one byte at a given memory address of the Game Boy's ROM.

    -

    This will let you override data in the ROM at any given bank. This is the memory allocated at 0x0000 to 0x8000, where 0x4000 to 0x8000 can be changed from the MBC.

    -

    NOTE: Any changes here are not saved or loaded to game states! Use this function with caution and reapply -any overrides when reloading the ROM.

    -

    If you need to change a RAM address, see PyBoy.set_memory_value().

    +

    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".

    +

    The button will remain press until explicitly released with PyBoy.button_release() or PyBoy.send_input().

    +

    Example:

    +
    >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' pressed
    +True
    +>>> pyboy.tick() # Button 'a' still pressed
    +True
    +>>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' released
    +True
    +
    +

    Args

    -
    rom_bank : int
    -
    ROM bank to do the overwrite in
    -
    addr : int
    -
    Address to write the byte inside the ROM bank
    -
    value : int
    -
    A byte of data
    +
    input : str
    +
    button to press
    Expand source code -
    def override_memory_value(self, rom_bank, addr, value):
    +
    def button_press(self, input):
         """
    -    Override one byte at a given memory address of the Game Boy's ROM.
    +    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".
     
    -    This will let you override data in the ROM at any given bank. This is the memory allocated at 0x0000 to 0x8000, where 0x4000 to 0x8000 can be changed from the MBC.
    +    The button will remain press until explicitly released with `PyBoy.button_release` or `PyBoy.send_input`.
     
    -    __NOTE__: Any changes here are not saved or loaded to game states! Use this function with caution and reapply
    -    any overrides when reloading the ROM.
    +    Example:
    +    ```python
    +    >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' pressed
    +    True
    +    >>> pyboy.tick() # Button 'a' still pressed
    +    True
    +    >>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' released
    +    True
     
    -    If you need to change a RAM address, see `pyboy.PyBoy.set_memory_value`.
    +    ```
     
         Args:
    -        rom_bank (int): ROM bank to do the overwrite in
    -        addr (int): Address to write the byte inside the ROM bank
    -        value (int): A byte of data
    +        input (str): button to press
         """
    -    # TODO: If you change a RAM value outside of the ROM banks above, the memory value will stay the same no matter
    -    # what the game writes to the address. This can be used so freeze the value for health, cash etc.
    -    self.mb.cartridge.overrideitem(rom_bank, addr, value)
    + input = input.lower() + + if input == "left": + self.send_input(WindowEvent.PRESS_ARROW_LEFT) + elif input == "right": + self.send_input(WindowEvent.PRESS_ARROW_RIGHT) + elif input == "up": + self.send_input(WindowEvent.PRESS_ARROW_UP) + elif input == "down": + self.send_input(WindowEvent.PRESS_ARROW_DOWN) + elif input == "a": + self.send_input(WindowEvent.PRESS_BUTTON_A) + elif input == "b": + self.send_input(WindowEvent.PRESS_BUTTON_B) + elif input == "start": + self.send_input(WindowEvent.PRESS_BUTTON_START) + elif input == "select": + self.send_input(WindowEvent.PRESS_BUTTON_SELECT) + else: + raise Exception("Unrecognized input")
    -
    -def send_input(self, event) +
    +def button_release(self, input)
    -

    Send a single input to control the emulator. This is both Game Boy buttons and emulator controls.

    -

    See WindowEvent for which events to send.

    +

    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".

    +

    This will release a button after a call to PyBoy.button_press() or PyBoy.send_input().

    +

    Example:

    +
    >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' pressed
    +True
    +>>> pyboy.tick() # Button 'a' still pressed
    +True
    +>>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' released
    +True
    +
    +

    Args

    -
    event : WindowEvent
    -
    The event to send
    +
    input : str
    +
    button to release
    Expand source code -
    def send_input(self, event):
    +
    def button_release(self, input):
         """
    -    Send a single input to control the emulator. This is both Game Boy buttons and emulator controls.
    +    Send input to PyBoy in the form of "a", "b", "start", "select", "left", "right", "up" and "down".
    +
    +    This will release a button after a call to `PyBoy.button_press` or `PyBoy.send_input`.
     
    -    See `pyboy.WindowEvent` for which events to send.
    +    Example:
    +    ```python
    +    >>> pyboy.button_press('a') # Press button 'a' and keep pressed after `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' pressed
    +    True
    +    >>> pyboy.tick() # Button 'a' still pressed
    +    True
    +    >>> pyboy.button_release('a') # Release button 'a' on next call to `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' released
    +    True
    +
    +    ```
     
         Args:
    -        event (pyboy.WindowEvent): The event to send
    +        input (str): button to release
         """
    -    self.events.append(WindowEvent(event))
    + input = input.lower() + if input == "left": + self.send_input(WindowEvent.RELEASE_ARROW_LEFT) + elif input == "right": + self.send_input(WindowEvent.RELEASE_ARROW_RIGHT) + elif input == "up": + self.send_input(WindowEvent.RELEASE_ARROW_UP) + elif input == "down": + self.send_input(WindowEvent.RELEASE_ARROW_DOWN) + elif input == "a": + self.send_input(WindowEvent.RELEASE_BUTTON_A) + elif input == "b": + self.send_input(WindowEvent.RELEASE_BUTTON_B) + elif input == "start": + self.send_input(WindowEvent.RELEASE_BUTTON_START) + elif input == "select": + self.send_input(WindowEvent.RELEASE_BUTTON_SELECT) + else: + raise Exception("Unrecognized input")
    -
    -def get_input(self, ignore=(22, 17, 33, 34, 35)) +
    +def send_input(self, event)
    -

    Get current inputs except the events specified in "ignore" tuple. -This is both Game Boy buttons and emulator controls.

    -

    See WindowEvent for which events to get.

    +

    Send a single input to control the emulator. This is both Game Boy buttons and emulator controls. See +WindowEvent for which events to send.

    +

    Consider using PyBoy.button() instead for easier access.

    +

    Example:

    +
    >>> from pyboy.utils import WindowEvent
    +>>> pyboy.send_input(WindowEvent.PRESS_BUTTON_A) # Press button 'a' and keep pressed after `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' pressed
    +True
    +>>> pyboy.tick() # Button 'a' still pressed
    +True
    +>>> pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) # Release button 'a' on next call to `PyBoy.tick()`
    +>>> pyboy.tick() # Button 'a' released
    +True
    +
    +

    Args

    -
    ignore : tuple
    -
    Events this function should ignore
    -
    -

    Returns

    -
    -
    list:
    -
    List of the pyboy.utils.WindowEvents processed for the last call to PyBoy.tick()
    +
    event : pyboy.WindowEvent
    +
    The event to send
    Expand source code -
    def get_input(
    -    self,
    -    ignore=(
    -        WindowEvent.PASS, WindowEvent._INTERNAL_TOGGLE_DEBUG, WindowEvent._INTERNAL_RENDERER_FLUSH,
    -        WindowEvent._INTERNAL_MOUSE, WindowEvent._INTERNAL_MARK_TILE
    -    )
    -):
    +
    def send_input(self, event):
         """
    -    Get current inputs except the events specified in "ignore" tuple.
    -    This is both Game Boy buttons and emulator controls.
    +    Send a single input to control the emulator. This is both Game Boy buttons and emulator controls. See
    +    `pyboy.utils.WindowEvent` for which events to send.
     
    -    See `pyboy.WindowEvent` for which events to get.
    +    Consider using `PyBoy.button` instead for easier access.
     
    -    Args:
    -        ignore (tuple): Events this function should ignore
    +    Example:
    +    ```python
    +    >>> from pyboy.utils import WindowEvent
    +    >>> pyboy.send_input(WindowEvent.PRESS_BUTTON_A) # Press button 'a' and keep pressed after `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' pressed
    +    True
    +    >>> pyboy.tick() # Button 'a' still pressed
    +    True
    +    >>> pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) # Release button 'a' on next call to `PyBoy.tick()`
    +    >>> pyboy.tick() # Button 'a' released
    +    True
     
    -    Returns
    -    -------
    -    list:
    -        List of the `pyboy.utils.WindowEvent`s processed for the last call to `pyboy.PyBoy.tick`
    +    ```
    +
    +    Args:
    +        event (pyboy.WindowEvent): The event to send
         """
    -    return [x for x in self.old_events if x not in ignore]
    + self.events.append(WindowEvent(event))
    @@ -978,13 +1720,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 +1749,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 +1782,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 +1805,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 +1822,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) +
    +
    +

    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)
    -

    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.

    +

    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 support 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 screen_image(self):
    +
    def game_area_collision(self):
         """
    -    Shortcut for `pyboy.botsupport_manager.screen.screen_image`.
    +    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 support 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)
    +
    +    ```
     
    -    Generates a PIL Image from the screen buffer.
    +    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)
     
    -    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.
    +
    +

    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 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
    +    """
    +
    +    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)
    +
    +
    +

    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 +2093,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 +2117,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="file.rom.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="file.rom.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)
    -
    - -
    -class WindowEvent -(event) +
    +def hook_deregister(self, bank, addr)
    -

    All supported events can be found in the class description below.

    -

    It can be used as follows:

    -
    >>> from pyboy import PyBoy, WindowEvent
    ->>> pyboy = PyBoy('file.rom')
    ->>> pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT)
    -
    +

    Remove a previously registered hook from a specific bank and memory address.

    +

    Example:

    +
    >>> context = "Hello from hook"
    +>>> def my_callback(context):
    +...     print(context)
    +>>> pyboy.hook_register(0, 0x2000, my_callback, context)
    +>>> pyboy.hook_deregister(0, 0x2000)
    +
    +
    +

    This function can also deregister a hook based on a symbol. See PyBoy.hook_register() for details.

    +

    Example:

    +
    >>> pyboy.hook_register(None, "Main", lambda x: print(x), "Hello from hook")
    +>>> pyboy.hook_deregister(None, "Main")
    +
    +
    +

    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)
    +
    Expand source code -
    class WindowEvent:
    +
    def hook_deregister(self, bank, addr):
         """
    -    All supported events can be found in the class description below.
    +    Remove a previously registered hook from a specific bank and memory address.
    +
    +    Example:
    +    ```python
    +    >>> context = "Hello from hook"
    +    >>> def my_callback(context):
    +    ...     print(context)
    +    >>> pyboy.hook_register(0, 0x2000, my_callback, context)
    +    >>> pyboy.hook_deregister(0, 0x2000)
     
    -    It can be used as follows:
    +    ```
     
    -    >>> from pyboy import PyBoy, WindowEvent
    -    >>> pyboy = PyBoy('file.rom')
    -    >>> pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT)
    +    This function can also deregister a hook based on a symbol. See `PyBoy.hook_register` for details.
    +
    +    Example:
    +    ```python
    +    >>> pyboy.hook_register(None, "Main", lambda x: print(x), "Hello from hook")
    +    >>> pyboy.hook_deregister(None, "Main")
    +
    +    ```
    +
    +    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)
         """
    +    if bank is None and isinstance(addr, str):
    +        bank, addr = self._lookup_symbol(addr)
     
    -    # ONLY ADD NEW EVENTS AT THE END OF THE LIST!
    -    # Otherwise, it will break replays, which depend on the id of the event
    -    (
    -        QUIT,
    -        PRESS_ARROW_UP,
    -        PRESS_ARROW_DOWN,
    -        PRESS_ARROW_RIGHT,
    -        PRESS_ARROW_LEFT,
    -        PRESS_BUTTON_A,
    -        PRESS_BUTTON_B,
    -        PRESS_BUTTON_SELECT,
    -        PRESS_BUTTON_START,
    -        RELEASE_ARROW_UP,
    -        RELEASE_ARROW_DOWN,
    -        RELEASE_ARROW_RIGHT,
    -        RELEASE_ARROW_LEFT,
    -        RELEASE_BUTTON_A,
    -        RELEASE_BUTTON_B,
    -        RELEASE_BUTTON_SELECT,
    -        RELEASE_BUTTON_START,
    -        _INTERNAL_TOGGLE_DEBUG,
    -        PRESS_SPEED_UP,
    -        RELEASE_SPEED_UP,
    -        STATE_SAVE,
    -        STATE_LOAD,
    -        PASS,
    -        SCREEN_RECORDING_TOGGLE,
    -        PAUSE,
    -        UNPAUSE,
    -        PAUSE_TOGGLE,
    -        PRESS_REWIND_BACK,
    -        PRESS_REWIND_FORWARD,
    -        RELEASE_REWIND_BACK,
    -        RELEASE_REWIND_FORWARD,
    -        WINDOW_FOCUS,
    -        WINDOW_UNFOCUS,
    -        _INTERNAL_RENDERER_FLUSH,
    -        _INTERNAL_MOUSE,
    -        _INTERNAL_MARK_TILE,
    -        SCREENSHOT_RECORD,
    -        DEBUG_MEMORY_SCROLL_DOWN,
    -        DEBUG_MEMORY_SCROLL_UP,
    -        MOD_SHIFT_ON,
    -        MOD_SHIFT_OFF,
    -        FULL_SCREEN_TOGGLE,
    -    ) = range(42)
    -
    -    def __init__(self, event):
    -        self.event = event
    -
    -    def __eq__(self, x):
    -        if isinstance(x, int):
    -            return self.event == x
    -        else:
    -            return self.event == x.event
    -
    -    def __int__(self):
    -        return self.event
    -
    -    def __str__(self):
    -        return (
    -            "QUIT",
    -            "PRESS_ARROW_UP",
    -            "PRESS_ARROW_DOWN",
    -            "PRESS_ARROW_RIGHT",
    -            "PRESS_ARROW_LEFT",
    -            "PRESS_BUTTON_A",
    -            "PRESS_BUTTON_B",
    -            "PRESS_BUTTON_SELECT",
    -            "PRESS_BUTTON_START",
    -            "RELEASE_ARROW_UP",
    -            "RELEASE_ARROW_DOWN",
    -            "RELEASE_ARROW_RIGHT",
    -            "RELEASE_ARROW_LEFT",
    -            "RELEASE_BUTTON_A",
    -            "RELEASE_BUTTON_B",
    -            "RELEASE_BUTTON_SELECT",
    -            "RELEASE_BUTTON_START",
    -            "_INTERNAL_TOGGLE_DEBUG",
    -            "PRESS_SPEED_UP",
    -            "RELEASE_SPEED_UP",
    -            "STATE_SAVE",
    -            "STATE_LOAD",
    -            "PASS",
    -            "SCREEN_RECORDING_TOGGLE",
    -            "PAUSE",
    -            "UNPAUSE",
    -            "PAUSE_TOGGLE",
    -            "PRESS_REWIND_BACK",
    -            "PRESS_REWIND_FORWARD",
    -            "RELEASE_REWIND_BACK",
    -            "RELEASE_REWIND_FORWARD",
    -            "WINDOW_FOCUS",
    -            "WINDOW_UNFOCUS",
    -            "_INTERNAL_RENDERER_FLUSH",
    -            "_INTERNAL_MOUSE",
    -            "_INTERNAL_MARK_TILE",
    -            "SCREENSHOT_RECORD",
    -            "DEBUG_MEMORY_SCROLL_DOWN",
    -            "DEBUG_MEMORY_SCROLL_UP",
    -            "MOD_SHIFT_ON",
    -            "MOD_SHIFT_OFF",
    -            "FULL_SCREEN_TOGGLE",
    -        )[self.event]
    + index = self.mb.breakpoint_find(bank, addr) + if index == -1: + raise ValueError("Breakpoint not found for bank and addr") + + _, _, opcode = self.mb.breakpoints_list[index] + self.mb.breakpoint_remove(index) + bank_addr_opcode = (bank & 0xFF) << 24 | (addr & 0xFFFF) << 8 | (opcode & 0xFF) + self._hooks.pop(bank_addr_opcode)
    -

    Subclasses

    -
      -
    • pyboy.utils.WindowEventMouse
    • -
    -

    Class variables

    -
    -
    var QUIT
    -
    -
    -
    -
    var PRESS_ARROW_UP
    -
    -
    -
    -
    var PRESS_ARROW_DOWN
    -
    -
    -
    -
    var PRESS_ARROW_RIGHT
    -
    -
    -
    -
    var PRESS_ARROW_LEFT
    -
    -
    -
    -
    var PRESS_BUTTON_A
    -
    -
    -
    -
    var PRESS_BUTTON_B
    -
    -
    -
    -
    var PRESS_BUTTON_SELECT
    -
    -
    -
    -
    var PRESS_BUTTON_START
    -
    -
    -
    -
    var RELEASE_ARROW_UP
    -
    -
    -
    -
    var RELEASE_ARROW_DOWN
    -
    -
    -
    -
    var RELEASE_ARROW_RIGHT
    -
    -
    -
    -
    var RELEASE_ARROW_LEFT
    -
    -
    -
    -
    var RELEASE_BUTTON_A
    -
    -
    -
    -
    var RELEASE_BUTTON_B
    -
    -
    -
    -
    var RELEASE_BUTTON_SELECT
    -
    -
    -
    -
    var RELEASE_BUTTON_START
    -
    -
    -
    -
    var PRESS_SPEED_UP
    -
    -
    -
    -
    var RELEASE_SPEED_UP
    -
    -
    -
    -
    var STATE_SAVE
    -
    -
    -
    -
    var STATE_LOAD
    -
    -
    -
    -
    var PASS
    -
    -
    -
    -
    var SCREEN_RECORDING_TOGGLE
    -
    -
    -
    var PAUSE
    -
    -
    -
    -
    var UNPAUSE
    -
    -
    -
    -
    var PAUSE_TOGGLE
    -
    -
    -
    -
    var PRESS_REWIND_BACK
    -
    -
    -
    -
    var PRESS_REWIND_FORWARD
    -
    -
    -
    -
    var RELEASE_REWIND_BACK
    -
    -
    -
    -
    var RELEASE_REWIND_FORWARD
    -
    -
    -
    -
    var WINDOW_FOCUS
    -
    -
    -
    -
    var WINDOW_UNFOCUS
    -
    -
    -
    -
    var SCREENSHOT_RECORD
    -
    -
    -
    -
    var DEBUG_MEMORY_SCROLL_DOWN
    -
    -
    -
    -
    var DEBUG_MEMORY_SCROLL_UP
    -
    -
    -
    -
    var MOD_SHIFT_ON
    +
    +def get_sprite(self, sprite_index) +
    -
    +

    Provides a Sprite object, which makes the OAM data more presentable. The given index +corresponds to index of the sprite in the "Object Attribute Memory" (OAM).

    +

    The Game Boy supports 40 sprites in total. Read more details about it, in the Pan +Docs.

    +
    >>> s = pyboy.get_sprite(12)
    +>>> s
    +Sprite [12]: Position: (-8, -16), Shape: (8, 8), Tiles: (Tile: 0), On screen: False
    +>>> s.on_screen
    +False
    +>>> s.tiles
    +[Tile: 0]
    +
    +
    +

    Args

    +
    +
    index : int
    +
    Sprite index from 0 to 39.
    +
    +

    Returns

    +

    Sprite: +Sprite corresponding to the given index.

    +
    + +Expand source code + +
    def get_sprite(self, sprite_index):
    +    """
    +    Provides a `pyboy.api.sprite.Sprite` object, which makes the OAM data more presentable. The given index
    +    corresponds to index of the sprite in the "Object Attribute Memory" (OAM).
    +
    +    The Game Boy supports 40 sprites in total. Read more details about it, in the [Pan
    +    Docs](http://bgb.bircd.org/pandocs.htm).
    +
    +    ```python
    +    >>> s = pyboy.get_sprite(12)
    +    >>> s
    +    Sprite [12]: Position: (-8, -16), Shape: (8, 8), Tiles: (Tile: 0), On screen: False
    +    >>> s.on_screen
    +    False
    +    >>> s.tiles
    +    [Tile: 0]
    +
    +    ```
    +
    +    Args:
    +        index (int): Sprite index from 0 to 39.
    +    Returns
    +    -------
    +    `pyboy.api.sprite.Sprite`:
    +        Sprite corresponding to the given index.
    +    """
    +    return Sprite(self.mb, sprite_index)
    +
    -
    var MOD_SHIFT_OFF
    +
    +def get_sprite_by_tile_identifier(self, tile_identifiers, on_screen=True) +
    -
    +

    Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile +identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the +pyboy.sprite function to get a Sprite object.

    +

    Example:

    +
    >>> print(pyboy.get_sprite_by_tile_identifier([43, 123]))
    +[[0, 2, 4], []]
    +
    +
    +

    Meaning, that tile identifier 43 is found at the sprite indexes: 0, 2, and 4, while tile identifier +123 was not found anywhere.

    +

    Args

    +
    +
    identifiers : list
    +
    List of tile identifiers (int)
    +
    on_screen : bool
    +
    Require that the matched sprite is on screen
    +
    +

    Returns

    +
    +
    list:
    +
    list of sprite matches for every tile identifier in the input
    +
    +
    + +Expand source code + +
    def get_sprite_by_tile_identifier(self, tile_identifiers, on_screen=True):
    +    """
    +    Provided a list of tile identifiers, this function will find all occurrences of sprites using the tile
    +    identifiers and return the sprite indexes where each identifier is found. Use the sprite indexes in the
    +    `pyboy.sprite` function to get a `pyboy.api.sprite.Sprite` object.
    +
    +    Example:
    +    ```python
    +    >>> print(pyboy.get_sprite_by_tile_identifier([43, 123]))
    +    [[0, 2, 4], []]
    +
    +    ```
    +
    +    Meaning, that tile identifier `43` is found at the sprite indexes: 0, 2, and 4, while tile identifier
    +    `123` was not found anywhere.
    +
    +    Args:
    +        identifiers (list): List of tile identifiers (int)
    +        on_screen (bool): Require that the matched sprite is on screen
    +
    +    Returns
    +    -------
    +    list:
    +        list of sprite matches for every tile identifier in the input
    +    """
    +
    +    matches = []
    +    for i in tile_identifiers:
    +        match = []
    +        for s in range(constants.SPRITES):
    +            sprite = Sprite(self.mb, s)
    +            for t in sprite.tiles:
    +                if t.tile_identifier == i and (not on_screen or (on_screen and sprite.on_screen)):
    +                    match.append(s)
    +        matches.append(match)
    +    return matches
    +
    -
    var FULL_SCREEN_TOGGLE
    +
    +def get_tile(self, identifier) +
    -
    +

    The Game Boy can have 384 tiles loaded in memory at once (768 for Game Boy Color). Use this method to get a +Tile-object for given identifier.

    +

    The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See +the Tile object for more information.

    +

    Example:

    +
    >>> t = pyboy.get_tile(2)
    +>>> t
    +Tile: 2
    +>>> t.shape
    +(8, 8)
    +
    +
    +

    Returns

    +

    Tile: +A Tile object for the given identifier.

    +
    + +Expand source code + +
    def get_tile(self, identifier):
    +    """
    +    The Game Boy can have 384 tiles loaded in memory at once (768 for Game Boy Color). Use this method to get a
    +    `pyboy.api.tile.Tile`-object for given identifier.
    +
    +    The identifier is a PyBoy construct, which unifies two different scopes of indexes in the Game Boy hardware. See
    +    the `pyboy.api.tile.Tile` object for more information.
    +
    +    Example:
    +    ```python
    +    >>> t = pyboy.get_tile(2)
    +    >>> t
    +    Tile: 2
    +    >>> t.shape
    +    (8, 8)
    +
    +    ```
    +
    +    Returns
    +    -------
    +    `pyboy.api.tile.Tile`:
    +        A Tile object for the given identifier.
    +    """
    +    return Tile(self.mb, identifier=identifier)
    +
    @@ -1471,9 +2490,9 @@

    Index

    diff --git a/docs/openai_gym.html b/docs/openai_gym.html deleted file mode 100644 index 0af456a43..000000000 --- a/docs/openai_gym.html +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - -pyboy.openai_gym API documentation - - - - - - - - - - -
    -
    -
    -

    Module pyboy.openai_gym

    -
    -
    -
    - -Expand source code - -
    #
    -# License: See LICENSE.md file
    -# GitHub: https://github.com/Baekalfen/PyBoy
    -#
    -
    -import numpy as np
    -
    -from .botsupport.constants import TILES
    -from .utils import WindowEvent
    -
    -try:
    -    from gym import Env
    -    from gym.spaces import Discrete, MultiDiscrete, Box
    -    enabled = True
    -except ImportError:
    -
    -    class Env:
    -        pass
    -
    -    enabled = False
    -
    -
    -class PyBoyGymEnv(Env):
    -    """ A gym environement built from a `pyboy.PyBoy`
    -
    -    This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins.
    -    Additional kwargs are passed to the start_game method of the game_wrapper.
    -
    -    Args:
    -        observation_type (str): Define what the agent will be able to see:
    -        * `"raw"`: Gives the raw pixels color
    -        * `"tiles"`:  Gives the id of the sprites and tiles in 8x8 pixel zones of the game_area.
    -        * `"compressed"`: Like `"tiles"` but with slightly simplified id's (i.e. each type of enemy has a unique id).
    -        * `"minimal"`: Like `"compressed"` but gives a minimal representation (recommended; i.e. all enemies have the same id).
    -
    -        action_type (str): Define how the agent will interact with button inputs
    -        * `"press"`: The agent will only press inputs for 1 frame an then release it.
    -        * `"toggle"`: The agent will toggle inputs, first time it press and second time it release.
    -        * `"all"`: The agent have access to all inputs, press and release are separated.
    -
    -        simultaneous_actions (bool): Allow to inject multiple input at once. This dramatically increases the action_space: \\(n \\rightarrow 2^n\\)
    -
    -    Attributes:
    -        game_wrapper (`pyboy.plugins.base_plugin.PyBoyGameWrapper`): The game_wrapper of the PyBoy game instance over which the environment is built.
    -        action_space (Gym space): The action space of the environment.
    -        observation_space (Gym space): The observation space of the environment (depends of observation_type).
    -        actions (list): The list of input IDs of allowed input for the agent (depends of action_type).
    -
    -    """
    -    def __init__(self, pyboy, observation_type="tiles", action_type="toggle", simultaneous_actions=False, **kwargs):
    -        # Build pyboy game
    -        self.pyboy = pyboy
    -        if str(type(pyboy)) != "<class 'pyboy.pyboy.PyBoy'>":
    -            raise TypeError("pyboy must be a Pyboy object")
    -
    -        # Build game_wrapper
    -        self.game_wrapper = pyboy.game_wrapper()
    -        if self.game_wrapper is None:
    -            raise ValueError(
    -                "You need to build a game_wrapper to use this function. Otherwise there is no way to build a reward function automaticaly."
    -            )
    -        self.last_fitness = self.game_wrapper.fitness
    -
    -        # Building the action_space
    -        self._DO_NOTHING = WindowEvent.PASS
    -        self._buttons = [
    -            WindowEvent.PRESS_ARROW_UP, WindowEvent.PRESS_ARROW_DOWN, WindowEvent.PRESS_ARROW_RIGHT,
    -            WindowEvent.PRESS_ARROW_LEFT, WindowEvent.PRESS_BUTTON_A, WindowEvent.PRESS_BUTTON_B,
    -            WindowEvent.PRESS_BUTTON_SELECT, WindowEvent.PRESS_BUTTON_START
    -        ]
    -        self._button_is_pressed = {button: False for button in self._buttons}
    -
    -        self._buttons_release = [
    -            WindowEvent.RELEASE_ARROW_UP, WindowEvent.RELEASE_ARROW_DOWN, WindowEvent.RELEASE_ARROW_RIGHT,
    -            WindowEvent.RELEASE_ARROW_LEFT, WindowEvent.RELEASE_BUTTON_A, WindowEvent.RELEASE_BUTTON_B,
    -            WindowEvent.RELEASE_BUTTON_SELECT, WindowEvent.RELEASE_BUTTON_START
    -        ]
    -        self._release_button = {button: r_button for button, r_button in zip(self._buttons, self._buttons_release)}
    -
    -        self.actions = [self._DO_NOTHING] + self._buttons
    -        if action_type == "all":
    -            self.actions += self._buttons_release
    -        elif action_type not in ["press", "toggle"]:
    -            raise ValueError(f"action_type {action_type} is invalid")
    -        self.action_type = action_type
    -
    -        if simultaneous_actions:
    -            raise NotImplementedError("Not implemented yet, raise an issue on GitHub if needed")
    -        else:
    -            self.action_space = Discrete(len(self.actions))
    -
    -        # Building the observation_space
    -        if observation_type == "raw":
    -            screen = np.asarray(self.pyboy.botsupport_manager().screen().screen_ndarray())
    -            self.observation_space = Box(low=0, high=255, shape=screen.shape, dtype=np.uint8)
    -        elif observation_type in ["tiles", "compressed", "minimal"]:
    -            size_ids = TILES
    -            if observation_type == "compressed":
    -                try:
    -                    size_ids = np.max(self.game_wrapper.tiles_compressed) + 1
    -                except AttributeError:
    -                    raise AttributeError(
    -                        "You need to add the tiles_compressed attibute to the game_wrapper to use the compressed observation_type"
    -                    )
    -            elif observation_type == "minimal":
    -                try:
    -                    size_ids = np.max(self.game_wrapper.tiles_minimal) + 1
    -                except AttributeError:
    -                    raise AttributeError(
    -                        "You need to add the tiles_minimal attibute to the game_wrapper to use the minimal observation_type"
    -                    )
    -            nvec = size_ids * np.ones(self.game_wrapper.shape)
    -            self.observation_space = MultiDiscrete(nvec)
    -        else:
    -            raise NotImplementedError(f"observation_type {observation_type} is invalid")
    -        self.observation_type = observation_type
    -
    -        self._started = False
    -        self._kwargs = kwargs
    -
    -    def _get_observation(self):
    -        if self.observation_type == "raw":
    -            observation = np.asarray(self.pyboy.botsupport_manager().screen().screen_ndarray(), dtype=np.uint8)
    -        elif self.observation_type in ["tiles", "compressed", "minimal"]:
    -            observation = self.game_wrapper._game_area_np(self.observation_type)
    -        else:
    -            raise NotImplementedError(f"observation_type {self.observation_type} is invalid")
    -        return observation
    -
    -    def step(self, action_id):
    -        info = {}
    -
    -        action = self.actions[action_id]
    -        if action == self._DO_NOTHING:
    -            pyboy_done = self.pyboy.tick()
    -        else:
    -            if self.action_type == "toggle":
    -                if self._button_is_pressed[action]:
    -                    self._button_is_pressed[action] = False
    -                    action = self._release_button[action]
    -                else:
    -                    self._button_is_pressed[action] = True
    -
    -            self.pyboy.send_input(action)
    -            pyboy_done = self.pyboy.tick()
    -
    -            if self.action_type == "press":
    -                self.pyboy.send_input(self._release_button[action])
    -
    -        new_fitness = self.game_wrapper.fitness
    -        reward = new_fitness - self.last_fitness
    -        self.last_fitness = new_fitness
    -
    -        observation = self._get_observation()
    -        done = pyboy_done or self.game_wrapper.game_over()
    -
    -        return observation, reward, done, info
    -
    -    def reset(self):
    -        """ Reset (or start) the gym environment throught the game_wrapper """
    -        if not self._started:
    -            self.game_wrapper.start_game(**self._kwargs)
    -            self._started = True
    -        else:
    -            self.game_wrapper.reset_game()
    -        self.last_fitness = self.game_wrapper.fitness
    -        self.button_is_pressed = {button: False for button in self._buttons}
    -        return self._get_observation()
    -
    -    def render(self):
    -        pass
    -
    -    def close(self):
    -        self.pyboy.stop(save=False)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class PyBoyGymEnv -(pyboy, observation_type='tiles', action_type='toggle', simultaneous_actions=False, **kwargs) -
    -
    -

    A gym environement built from a PyBoy

    -

    This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins. -Additional kwargs are passed to the start_game method of the game_wrapper.

    -

    Args

    -
    -
    observation_type : str
    -
    Define what the agent will be able to see:
    -
    -
      -
    • "raw": Gives the raw pixels color
    • -
    • "tiles": -Gives the id of the sprites and tiles in 8x8 pixel zones of the game_area.
    • -
    • "compressed": Like "tiles" but with slightly simplified id's (i.e. each type of enemy has a unique id).
    • -
    • "minimal": Like "compressed" but gives a minimal representation (recommended; i.e. all enemies have the same id).
    • -
    -
    -
    action_type : str
    -
    Define how the agent will interact with button inputs
    -
    -
      -
    • "press": The agent will only press inputs for 1 frame an then release it.
    • -
    • "toggle": The agent will toggle inputs, first time it press and second time it release.
    • -
    • "all": The agent have access to all inputs, press and release are separated.
    • -
    -
    -
    simultaneous_actions : bool
    -
    Allow to inject multiple input at once. This dramatically increases the action_space: n \rightarrow 2^n
    -
    -

    Attributes

    -
    -
    game_wrapper (PyBoyGameWrapper): The game_wrapper of the PyBoy game instance over which the environment is built.
    -
    action_space : Gym space
    -
    The action space of the environment.
    -
    observation_space : Gym space
    -
    The observation space of the environment (depends of observation_type).
    -
    actions : list
    -
    The list of input IDs of allowed input for the agent (depends of action_type).
    -
    -
    - -Expand source code - -
    class PyBoyGymEnv(Env):
    -    """ A gym environement built from a `pyboy.PyBoy`
    -
    -    This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins.
    -    Additional kwargs are passed to the start_game method of the game_wrapper.
    -
    -    Args:
    -        observation_type (str): Define what the agent will be able to see:
    -        * `"raw"`: Gives the raw pixels color
    -        * `"tiles"`:  Gives the id of the sprites and tiles in 8x8 pixel zones of the game_area.
    -        * `"compressed"`: Like `"tiles"` but with slightly simplified id's (i.e. each type of enemy has a unique id).
    -        * `"minimal"`: Like `"compressed"` but gives a minimal representation (recommended; i.e. all enemies have the same id).
    -
    -        action_type (str): Define how the agent will interact with button inputs
    -        * `"press"`: The agent will only press inputs for 1 frame an then release it.
    -        * `"toggle"`: The agent will toggle inputs, first time it press and second time it release.
    -        * `"all"`: The agent have access to all inputs, press and release are separated.
    -
    -        simultaneous_actions (bool): Allow to inject multiple input at once. This dramatically increases the action_space: \\(n \\rightarrow 2^n\\)
    -
    -    Attributes:
    -        game_wrapper (`pyboy.plugins.base_plugin.PyBoyGameWrapper`): The game_wrapper of the PyBoy game instance over which the environment is built.
    -        action_space (Gym space): The action space of the environment.
    -        observation_space (Gym space): The observation space of the environment (depends of observation_type).
    -        actions (list): The list of input IDs of allowed input for the agent (depends of action_type).
    -
    -    """
    -    def __init__(self, pyboy, observation_type="tiles", action_type="toggle", simultaneous_actions=False, **kwargs):
    -        # Build pyboy game
    -        self.pyboy = pyboy
    -        if str(type(pyboy)) != "<class 'pyboy.pyboy.PyBoy'>":
    -            raise TypeError("pyboy must be a Pyboy object")
    -
    -        # Build game_wrapper
    -        self.game_wrapper = pyboy.game_wrapper()
    -        if self.game_wrapper is None:
    -            raise ValueError(
    -                "You need to build a game_wrapper to use this function. Otherwise there is no way to build a reward function automaticaly."
    -            )
    -        self.last_fitness = self.game_wrapper.fitness
    -
    -        # Building the action_space
    -        self._DO_NOTHING = WindowEvent.PASS
    -        self._buttons = [
    -            WindowEvent.PRESS_ARROW_UP, WindowEvent.PRESS_ARROW_DOWN, WindowEvent.PRESS_ARROW_RIGHT,
    -            WindowEvent.PRESS_ARROW_LEFT, WindowEvent.PRESS_BUTTON_A, WindowEvent.PRESS_BUTTON_B,
    -            WindowEvent.PRESS_BUTTON_SELECT, WindowEvent.PRESS_BUTTON_START
    -        ]
    -        self._button_is_pressed = {button: False for button in self._buttons}
    -
    -        self._buttons_release = [
    -            WindowEvent.RELEASE_ARROW_UP, WindowEvent.RELEASE_ARROW_DOWN, WindowEvent.RELEASE_ARROW_RIGHT,
    -            WindowEvent.RELEASE_ARROW_LEFT, WindowEvent.RELEASE_BUTTON_A, WindowEvent.RELEASE_BUTTON_B,
    -            WindowEvent.RELEASE_BUTTON_SELECT, WindowEvent.RELEASE_BUTTON_START
    -        ]
    -        self._release_button = {button: r_button for button, r_button in zip(self._buttons, self._buttons_release)}
    -
    -        self.actions = [self._DO_NOTHING] + self._buttons
    -        if action_type == "all":
    -            self.actions += self._buttons_release
    -        elif action_type not in ["press", "toggle"]:
    -            raise ValueError(f"action_type {action_type} is invalid")
    -        self.action_type = action_type
    -
    -        if simultaneous_actions:
    -            raise NotImplementedError("Not implemented yet, raise an issue on GitHub if needed")
    -        else:
    -            self.action_space = Discrete(len(self.actions))
    -
    -        # Building the observation_space
    -        if observation_type == "raw":
    -            screen = np.asarray(self.pyboy.botsupport_manager().screen().screen_ndarray())
    -            self.observation_space = Box(low=0, high=255, shape=screen.shape, dtype=np.uint8)
    -        elif observation_type in ["tiles", "compressed", "minimal"]:
    -            size_ids = TILES
    -            if observation_type == "compressed":
    -                try:
    -                    size_ids = np.max(self.game_wrapper.tiles_compressed) + 1
    -                except AttributeError:
    -                    raise AttributeError(
    -                        "You need to add the tiles_compressed attibute to the game_wrapper to use the compressed observation_type"
    -                    )
    -            elif observation_type == "minimal":
    -                try:
    -                    size_ids = np.max(self.game_wrapper.tiles_minimal) + 1
    -                except AttributeError:
    -                    raise AttributeError(
    -                        "You need to add the tiles_minimal attibute to the game_wrapper to use the minimal observation_type"
    -                    )
    -            nvec = size_ids * np.ones(self.game_wrapper.shape)
    -            self.observation_space = MultiDiscrete(nvec)
    -        else:
    -            raise NotImplementedError(f"observation_type {observation_type} is invalid")
    -        self.observation_type = observation_type
    -
    -        self._started = False
    -        self._kwargs = kwargs
    -
    -    def _get_observation(self):
    -        if self.observation_type == "raw":
    -            observation = np.asarray(self.pyboy.botsupport_manager().screen().screen_ndarray(), dtype=np.uint8)
    -        elif self.observation_type in ["tiles", "compressed", "minimal"]:
    -            observation = self.game_wrapper._game_area_np(self.observation_type)
    -        else:
    -            raise NotImplementedError(f"observation_type {self.observation_type} is invalid")
    -        return observation
    -
    -    def step(self, action_id):
    -        info = {}
    -
    -        action = self.actions[action_id]
    -        if action == self._DO_NOTHING:
    -            pyboy_done = self.pyboy.tick()
    -        else:
    -            if self.action_type == "toggle":
    -                if self._button_is_pressed[action]:
    -                    self._button_is_pressed[action] = False
    -                    action = self._release_button[action]
    -                else:
    -                    self._button_is_pressed[action] = True
    -
    -            self.pyboy.send_input(action)
    -            pyboy_done = self.pyboy.tick()
    -
    -            if self.action_type == "press":
    -                self.pyboy.send_input(self._release_button[action])
    -
    -        new_fitness = self.game_wrapper.fitness
    -        reward = new_fitness - self.last_fitness
    -        self.last_fitness = new_fitness
    -
    -        observation = self._get_observation()
    -        done = pyboy_done or self.game_wrapper.game_over()
    -
    -        return observation, reward, done, info
    -
    -    def reset(self):
    -        """ Reset (or start) the gym environment throught the game_wrapper """
    -        if not self._started:
    -            self.game_wrapper.start_game(**self._kwargs)
    -            self._started = True
    -        else:
    -            self.game_wrapper.reset_game()
    -        self.last_fitness = self.game_wrapper.fitness
    -        self.button_is_pressed = {button: False for button in self._buttons}
    -        return self._get_observation()
    -
    -    def render(self):
    -        pass
    -
    -    def close(self):
    -        self.pyboy.stop(save=False)
    -
    -

    Ancestors

    -
      -
    • gym.core.Env
    • -
    • typing.Generic
    • -
    -

    Methods

    -
    -
    -def step(self, action_id) -
    -
    -

    Run one timestep of the environment's dynamics.

    -

    When end of episode is reached, you are responsible for calling :meth:reset to reset this environment's state. -Accepts an action and returns either a tuple (observation, reward, terminated, truncated, info).

    -

    Args

    -
    -
    action : ActType
    -
    an action provided by the agent
    -
    -

    Returns

    -
    -
    observation (object): this will be an element of the environment's :attr:observation_space.
    -
    This may, for instance, be a numpy array containing the positions and velocities of certain objects.
    -
    reward (float): The amount of reward returned as a result of taking the action.
    -
    terminated (bool): whether a terminal state (as defined under the MDP of the task) is reached.
    -
    In this case further step() calls could return undefined results.
    -
    truncated (bool): whether a truncation condition outside the scope of the MDP is satisfied.
    -
    Typically a timelimit, but could also be used to indicate agent physically going out of bounds.
    -
    Can be used to end the episode prematurely before a terminal state is reached.
    -
    info (dictionary): info contains auxiliary diagnostic information (helpful for debugging, learning, and logging).
    -
    -This might, for instance, contain
    -
    metrics that describe the agent's performance state, variables that are -hidden from observations, or individual reward terms that are combined to produce the total reward. -It also can contain information that distinguishes truncation and termination, however this is deprecated in favour -of returning two booleans, and will be removed in a future version.
    -
    (deprecated)
    -
    done (bool): A boolean value for if the episode has ended, in which case further :meth:step calls will return undefined results.
    -
    -A done signal may be emitted for different reasons
    -
    Maybe the task underlying the environment was solved successfully, -a certain timelimit was exceeded, or the physics simulation has entered an invalid state.
    -
    -
    - -Expand source code - -
    def step(self, action_id):
    -    info = {}
    -
    -    action = self.actions[action_id]
    -    if action == self._DO_NOTHING:
    -        pyboy_done = self.pyboy.tick()
    -    else:
    -        if self.action_type == "toggle":
    -            if self._button_is_pressed[action]:
    -                self._button_is_pressed[action] = False
    -                action = self._release_button[action]
    -            else:
    -                self._button_is_pressed[action] = True
    -
    -        self.pyboy.send_input(action)
    -        pyboy_done = self.pyboy.tick()
    -
    -        if self.action_type == "press":
    -            self.pyboy.send_input(self._release_button[action])
    -
    -    new_fitness = self.game_wrapper.fitness
    -    reward = new_fitness - self.last_fitness
    -    self.last_fitness = new_fitness
    -
    -    observation = self._get_observation()
    -    done = pyboy_done or self.game_wrapper.game_over()
    -
    -    return observation, reward, done, info
    -
    -
    -
    -def reset(self) -
    -
    -

    Reset (or start) the gym environment throught the game_wrapper

    -
    - -Expand source code - -
    def reset(self):
    -    """ Reset (or start) the gym environment throught the game_wrapper """
    -    if not self._started:
    -        self.game_wrapper.start_game(**self._kwargs)
    -        self._started = True
    -    else:
    -        self.game_wrapper.reset_game()
    -    self.last_fitness = self.game_wrapper.fitness
    -    self.button_is_pressed = {button: False for button in self._buttons}
    -    return self._get_observation()
    -
    -
    -
    -def render(self) -
    -
    -

    Compute the render frames as specified by render_mode attribute during initialization of the environment.

    -

    The set of supported modes varies per environment. (And some -third-party environments may not support rendering at all.) -By convention, if render_mode is:

    -
      -
    • None (default): no render is computed.
    • -
    • human: render return None. -The environment is continuously rendered in the current display or terminal. Usually for human consumption.
    • -
    • rgb_array: return a single frame representing the current state of the environment. -A frame is a numpy.ndarray with shape (x, y, 3) representing RGB values for an x-by-y pixel image.
    • -
    • rgb_array_list: return a list of frames representing the states of the environment since the last reset. -Each frame is a numpy.ndarray with shape (x, y, 3), as with rgb_array.
    • -
    • ansi: Return a strings (str) or StringIO.StringIO containing a -terminal-style text representation for each time step. -The text can include newlines and ANSI escape sequences (e.g. for colors).
    • -
    -

    Note

    -

    Make sure that your class's metadata 'render_modes' key includes -the list of supported modes. It's recommended to call super() -in implementations to use the functionality of this method.

    -
    - -Expand source code - -
    def render(self):
    -    pass
    -
    -
    -
    -def close(self) -
    -
    -

    Override close in your subclass to perform any necessary cleanup.

    -

    Environments will automatically :meth:close() themselves when -garbage collected or when the program exits.

    -
    - -Expand source code - -
    def close(self):
    -    self.pyboy.stop(save=False)
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - - - \ No newline at end of file diff --git a/docs/plugins/base_plugin.html b/docs/plugins/base_plugin.html index 260852b8c..4d8a0e34e 100644 --- a/docs/plugins/base_plugin.html +++ b/docs/plugins/base_plugin.html @@ -39,14 +39,15 @@

    Module pyboy.plugins.base_plugin

    } import io -import logging import random from array import array import numpy as np -from pyboy.botsupport.sprite import Sprite -logger = logging.getLogger(__name__) +import pyboy +from pyboy.api.sprite import Sprite + +logger = pyboy.logging.get_logger(__name__) try: from cython import compiled @@ -99,7 +100,7 @@

    Module pyboy.plugins.base_plugin

    logger.debug("%s initialization" % self.__class__.__name__) self._scaledresolution = (scale * COLS, scale * ROWS) - logger.debug("Scale: x%s %s" % (self.scale, self._scaledresolution)) + logger.debug("Scale: x%d (%d, %d)", self.scale, self._scaledresolution[0], self._scaledresolution[1]) self.enable_title = True if not cythonmode: @@ -121,37 +122,45 @@

    Module pyboy.plugins.base_plugin

    , which shows both sprites and tiles on the screen as a simple matrix. """ + cartridge_title = None argv = [("--game-wrapper", {"action": "store_true", "help": "Enable game wrapper for the current game"})] - def __init__(self, *args, game_area_section=(0, 0, 32, 32), game_area_wrap_around=False, **kwargs): + mapping_minimal = np.arange(384 * 2, dtype=np.uint8) + """ + Example mapping of 1:1 + """ + def __init__(self, *args, game_area_section=(0, 0, 32, 32), game_area_follow_scxy=False, **kwargs): super().__init__(*args, **kwargs) - self.tilemap_background = self.pyboy.botsupport_manager().tilemap_background() - self.tilemap_window = self.pyboy.botsupport_manager().tilemap_window() + if not cythonmode: + self.tilemap_background = self.pyboy.tilemap_background + self.tilemap_window = self.pyboy.tilemap_window self.tilemap_use_background = True + self.mapping = np.asarray([x for x in range(768)], dtype=np.uint32) self.sprite_offset = 0 self.game_has_started = False self._tile_cache_invalid = True self._sprite_cache_invalid = True self.game_area_section = game_area_section - self.game_area_wrap_around = game_area_wrap_around + self.game_area_follow_scxy = game_area_follow_scxy width = self.game_area_section[2] - self.game_area_section[0] height = self.game_area_section[3] - self.game_area_section[1] self._cached_game_area_tiles_raw = array("B", [0xFF] * (width*height*4)) self.saved_state = io.BytesIO() - if cythonmode: - self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(width, height)) - else: - v = memoryview(self._cached_game_area_tiles_raw).cast("I") - self._cached_game_area_tiles = [v[i:i + height] for i in range(0, height * width, height)] + self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(width, height)) + + def __cinit__(self, pyboy, mb, pyboy_argv, *args, **kwargs): + self.tilemap_background = self.pyboy.tilemap_background + self.tilemap_window = self.pyboy.tilemap_window def enabled(self): - return self.pyboy_argv.get("game_wrapper") and self.pyboy.cartridge_title() == self.cartridge_title + return self.cartridge_title is None or self.pyboy.cartridge_title == self.cartridge_title def post_tick(self): - raise NotImplementedError("post_tick not implemented in game wrapper") + self._tile_cache_invalid = True + self._sprite_cache_invalid = True def _set_timer_div(self, timer_div): if timer_div is None: @@ -175,6 +184,9 @@

    Module pyboy.plugins.base_plugin

    if not self.pyboy.frame_count == 0: logger.warning("Calling start_game from an already running game. This might not work.") + self.game_has_started = True + self.saved_state.seek(0) + self.pyboy.save_state(self.saved_state) def reset_game(self, timer_div=None): """ @@ -213,9 +225,9 @@

    Module pyboy.plugins.base_plugin

    yy = self.game_area_section[1] width = self.game_area_section[2] height = self.game_area_section[3] - scanline_parameters = self.pyboy.botsupport_manager().screen().tilemap_position_list() + scanline_parameters = self.pyboy.screen.tilemap_position_list - if self.game_area_wrap_around: + if self.game_area_follow_scxy: self._cached_game_area_tiles = np.ndarray(shape=(height, width), dtype=np.uint32) for y in range(height): SCX = scanline_parameters[(yy+y) * 8][0] // 8 @@ -224,9 +236,9 @@

    Module pyboy.plugins.base_plugin

    _x = (xx+x+SCX) % 32 _y = (yy+y+SCY) % 32 if self.tilemap_use_background: - self._cached_game_area_tiles[y][x] = self.tilemap_background.tile_identifier(_x, _y) + 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) + self._cached_game_area_tiles[y, x] = self.tilemap_window.tile_identifier(_x, _y) else: if self.tilemap_use_background: self._cached_game_area_tiles = np.asarray( @@ -251,7 +263,7 @@

    Module pyboy.plugins.base_plugin

    memoryview: Simplified 2-dimensional memoryview of the screen """ - tiles_matrix = self._game_area_tiles() + tiles_matrix = self.mapping[self._game_area_tiles()] sprites = self._sprites_on_screen() xx = self.game_area_section[0] yy = self.game_area_section[1] @@ -261,29 +273,13 @@

    Module pyboy.plugins.base_plugin

    _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 + self.sprite_offset # Adding offset to try to seperate sprites from tiles + tiles_matrix[_y][_x] = self.mapping[ + 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"): - if observation_type == "tiles": - return np.asarray(self.game_area(), dtype=np.uint16) - elif observation_type == "compressed": - try: - return self.tiles_compressed[np.asarray(self.game_area(), dtype=np.uint16)] - except AttributeError: - raise AttributeError( - f"Game wrapper miss the attribute tiles_compressed for observation_type : {observation_type}" - ) - elif observation_type == "minimal": - try: - return self.tiles_minimal[np.asarray(self.game_area(), dtype=np.uint16)] - except AttributeError: - raise AttributeError( - f"Game wrapper miss the attribute tiles_minimal for observation_type : {observation_type}" - ) - else: - raise ValueError(f"Invalid observation_type : {observation_type}") + def game_area_mapping(self, mapping, sprite_offest): + self.mapping = np.asarray(mapping, dtype=np.uint32) + self.sprite_offset = sprite_offest def _sum_number_on_screen(self, x, y, length, blank_tile_identifier, tile_identifier_offset): number = 0 @@ -304,7 +300,7 @@

    Classes

    class PyBoyGameWrapper -(*args, game_area_section=(0, 0, 32, 32), game_area_wrap_around=False, **kwargs) +(*args, game_area_section=(0, 0, 32, 32), game_area_follow_scxy=False, **kwargs)

    This is the base-class for the game-wrappers. It provides some generic game-wrapping functionality, like game_area @@ -319,37 +315,45 @@

    Classes

    , which shows both sprites and tiles on the screen as a simple matrix. """ + cartridge_title = None argv = [("--game-wrapper", {"action": "store_true", "help": "Enable game wrapper for the current game"})] - def __init__(self, *args, game_area_section=(0, 0, 32, 32), game_area_wrap_around=False, **kwargs): + mapping_minimal = np.arange(384 * 2, dtype=np.uint8) + """ + Example mapping of 1:1 + """ + def __init__(self, *args, game_area_section=(0, 0, 32, 32), game_area_follow_scxy=False, **kwargs): super().__init__(*args, **kwargs) - self.tilemap_background = self.pyboy.botsupport_manager().tilemap_background() - self.tilemap_window = self.pyboy.botsupport_manager().tilemap_window() + if not cythonmode: + self.tilemap_background = self.pyboy.tilemap_background + self.tilemap_window = self.pyboy.tilemap_window self.tilemap_use_background = True + self.mapping = np.asarray([x for x in range(768)], dtype=np.uint32) self.sprite_offset = 0 self.game_has_started = False self._tile_cache_invalid = True self._sprite_cache_invalid = True self.game_area_section = game_area_section - self.game_area_wrap_around = game_area_wrap_around + self.game_area_follow_scxy = game_area_follow_scxy width = self.game_area_section[2] - self.game_area_section[0] height = self.game_area_section[3] - self.game_area_section[1] self._cached_game_area_tiles_raw = array("B", [0xFF] * (width*height*4)) self.saved_state = io.BytesIO() - if cythonmode: - self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(width, height)) - else: - v = memoryview(self._cached_game_area_tiles_raw).cast("I") - self._cached_game_area_tiles = [v[i:i + height] for i in range(0, height * width, height)] + self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(width, height)) + + def __cinit__(self, pyboy, mb, pyboy_argv, *args, **kwargs): + self.tilemap_background = self.pyboy.tilemap_background + self.tilemap_window = self.pyboy.tilemap_window def enabled(self): - return self.pyboy_argv.get("game_wrapper") and self.pyboy.cartridge_title() == self.cartridge_title + return self.cartridge_title is None or self.pyboy.cartridge_title == self.cartridge_title def post_tick(self): - raise NotImplementedError("post_tick not implemented in game wrapper") + self._tile_cache_invalid = True + self._sprite_cache_invalid = True def _set_timer_div(self, timer_div): if timer_div is None: @@ -373,6 +377,9 @@

    Classes

    if not self.pyboy.frame_count == 0: logger.warning("Calling start_game from an already running game. This might not work.") + self.game_has_started = True + self.saved_state.seek(0) + self.pyboy.save_state(self.saved_state) def reset_game(self, timer_div=None): """ @@ -411,9 +418,9 @@

    Classes

    yy = self.game_area_section[1] width = self.game_area_section[2] height = self.game_area_section[3] - scanline_parameters = self.pyboy.botsupport_manager().screen().tilemap_position_list() + scanline_parameters = self.pyboy.screen.tilemap_position_list - if self.game_area_wrap_around: + if self.game_area_follow_scxy: self._cached_game_area_tiles = np.ndarray(shape=(height, width), dtype=np.uint32) for y in range(height): SCX = scanline_parameters[(yy+y) * 8][0] // 8 @@ -422,9 +429,9 @@

    Classes

    _x = (xx+x+SCX) % 32 _y = (yy+y+SCY) % 32 if self.tilemap_use_background: - self._cached_game_area_tiles[y][x] = self.tilemap_background.tile_identifier(_x, _y) + 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) + self._cached_game_area_tiles[y, x] = self.tilemap_window.tile_identifier(_x, _y) else: if self.tilemap_use_background: self._cached_game_area_tiles = np.asarray( @@ -449,7 +456,7 @@

    Classes

    memoryview: Simplified 2-dimensional memoryview of the screen """ - tiles_matrix = self._game_area_tiles() + tiles_matrix = self.mapping[self._game_area_tiles()] sprites = self._sprites_on_screen() xx = self.game_area_section[0] yy = self.game_area_section[1] @@ -459,29 +466,13 @@

    Classes

    _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 + self.sprite_offset # Adding offset to try to seperate sprites from tiles + tiles_matrix[_y][_x] = self.mapping[ + 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"): - if observation_type == "tiles": - return np.asarray(self.game_area(), dtype=np.uint16) - elif observation_type == "compressed": - try: - return self.tiles_compressed[np.asarray(self.game_area(), dtype=np.uint16)] - except AttributeError: - raise AttributeError( - f"Game wrapper miss the attribute tiles_compressed for observation_type : {observation_type}" - ) - elif observation_type == "minimal": - try: - return self.tiles_minimal[np.asarray(self.game_area(), dtype=np.uint16)] - except AttributeError: - raise AttributeError( - f"Game wrapper miss the attribute tiles_minimal for observation_type : {observation_type}" - ) - else: - raise ValueError(f"Invalid observation_type : {observation_type}") + def game_area_mapping(self, mapping, sprite_offest): + self.mapping = np.asarray(mapping, dtype=np.uint32) + self.sprite_offset = sprite_offest def _sum_number_on_screen(self, x, y, length, blank_tile_identifier, tile_identifier_offset): number = 0 @@ -501,6 +492,17 @@

    Subclasses

  • GameWrapperSuperMarioLand
  • GameWrapperTetris
+

Class variables

+
+
var cartridge_title
+
+
+
+
var mapping_minimal
+
+

Example mapping of 1:1

+
+

Methods

@@ -536,7 +538,10 @@

Args

""" if not self.pyboy.frame_count == 0: - logger.warning("Calling start_game from an already running game. This might not work.")
+ logger.warning("Calling start_game from an already running game. This might not work.") + self.game_has_started = True + self.saved_state.seek(0) + self.pyboy.save_state(self.saved_state)
@@ -621,7 +626,7 @@

Returns

memoryview: Simplified 2-dimensional memoryview of the screen """ - tiles_matrix = self._game_area_tiles() + tiles_matrix = self.mapping[self._game_area_tiles()] sprites = self._sprites_on_screen() xx = self.game_area_section[0] yy = self.game_area_section[1] @@ -631,11 +636,25 @@

Returns

_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 + self.sprite_offset # Adding offset to try to seperate sprites from tiles + tiles_matrix[_y][_x] = self.mapping[ + s.tile_identifier] + self.sprite_offset # Adding offset to try to seperate sprites from tiles return tiles_matrix
+
+def game_area_mapping(self, mapping, sprite_offest) +
+
+
+
+ +Expand source code + +
def game_area_mapping(self, mapping, sprite_offest):
+    self.mapping = np.asarray(mapping, dtype=np.uint32)
+    self.sprite_offset = sprite_offest
+
+
@@ -656,12 +675,15 @@

Index

  • PyBoyGameWrapper

    - diff --git a/docs/plugins/game_wrapper_kirby_dream_land.html b/docs/plugins/game_wrapper_kirby_dream_land.html index 0140e758e..480c9dd91 100644 --- a/docs/plugins/game_wrapper_kirby_dream_land.html +++ b/docs/plugins/game_wrapper_kirby_dream_land.html @@ -34,24 +34,18 @@

    Module pyboy.plugins.game_wrapper_kirby_dream_landModule pyboy.plugins.game_wrapper_kirby_dream_landModule pyboy.plugins.game_wrapper_kirby_dream_landModule pyboy.plugins.game_wrapper_kirby_dream_landClasses

    (*args, **kwargs)
    -

    This class wraps Kirby Dream Land, and provides easy access to score and a "fitness" score for AIs.

    +

    This class wraps Kirby Dream Land, and provides easy access for AIs.

    If you call print on an instance of this object, it will show an overview of everything this object provides.

    @@ -240,7 +215,7 @@

    Classes

    class GameWrapperKirbyDreamLand(PyBoyGameWrapper):
         """
    -    This class wraps Kirby Dream Land, and provides easy access to score and a "fitness" score for AIs.
    +    This class wraps Kirby Dream Land, and provides easy access for AIs.
     
         If you call `print` on an instance of this object, it will show an overview of everything this object provides.
         """
    @@ -257,35 +232,26 @@ 

    Classes

    """The lives remaining provided by the game""" self._game_over = False """The game over state""" - self.fitness = 0 - """ - A built-in fitness scoring. Taking score, health, and lives left into account. - .. math:: - fitness = score \\cdot health \\cdot lives\\_left - """ - super().__init__(*args, game_area_section=(0, 0) + self.shape, game_area_wrap_around=True, **kwargs) + super().__init__(*args, game_area_section=(0, 0) + self.shape, game_area_follow_scxy=True, **kwargs) def post_tick(self): self._tile_cache_invalid = True self._sprite_cache_invalid = True self.score = 0 - score_digits = 5 + score_digits = 4 for n in range(score_digits): - self.score += self.pyboy.get_memory_value(0xD06F + n) * 10**(score_digits-n) + self.score += self.pyboy.memory[0xD070 + n] * 10**(score_digits - n) # Check if game is over prev_health = self.health - self.health = self.pyboy.get_memory_value(0xD086) + self.health = self.pyboy.memory[0xD086] if self.lives_left == 0: if prev_health > 0 and self.health == 0: self._game_over = True - self.lives_left = self.pyboy.get_memory_value(0xD089) - 1 - - if self.game_has_started: - self.fitness = self.score * self.health * self.lives_left + self.lives_left = self.pyboy.memory[0xD089] - 1 def start_game(self, timer_div=None): """ @@ -302,33 +268,24 @@

    Classes

    # Boot screen while True: - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) if self.tilemap_background[0:3, 16] == [231, 224, 235]: # 'HAL' on the first screen break # Wait for transition to finish (start screen) - for _ in range(25): - self.pyboy.tick() - - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.tick(25, False) + self.pyboy.button("start") self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) # Wait for transition to finish (exit start screen, enter level intro screen) - for _ in range(60): - self.pyboy.tick() + self.pyboy.tick(60, False) # Skip level intro - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.button("start") self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) # Wait for transition to finish (exit level intro screen, enter game) - for _ in range(60): - self.pyboy.tick() - - self.game_has_started = True + self.pyboy.tick(60, False) self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) @@ -389,7 +346,6 @@

    Classes

    f"Score: {self.score}\n" + f"Health: {self.health}\n" + f"Lives left: {self.lives_left}\n" + - f"Fitness: {self.fitness}\n" + "Sprites on screen:\n" + "\n".join([str(s) for s in self._sprites_on_screen()]) + "\n" + @@ -429,11 +385,6 @@

    Instance variables

    The lives remaining provided by the game

    -
    var fitness
    -
    -

    A built-in fitness scoring. Taking score, health, and lives left into account.

    -

    fitness = score \cdot health \cdot lives\_left

    -

    Methods

    @@ -466,33 +417,24 @@

    Kwargs

    # Boot screen while True: - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) if self.tilemap_background[0:3, 16] == [231, 224, 235]: # 'HAL' on the first screen break # Wait for transition to finish (start screen) - for _ in range(25): - self.pyboy.tick() - - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.tick(25, False) + self.pyboy.button("start") self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) # Wait for transition to finish (exit start screen, enter level intro screen) - for _ in range(60): - self.pyboy.tick() + self.pyboy.tick(60, False) # Skip level intro - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.button("start") self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) # Wait for transition to finish (exit level intro screen, enter game) - for _ in range(60): - self.pyboy.tick() - - self.game_has_started = True + self.pyboy.tick(60, False) self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) @@ -595,6 +537,7 @@

    Inherited members

  • PyBoyGameWrapper:
@@ -625,7 +568,6 @@

score
  • health
  • lives_left
  • -
  • fitness
  • diff --git a/docs/plugins/game_wrapper_pokemon_gen1.html b/docs/plugins/game_wrapper_pokemon_gen1.html index 12b1630d9..1590bd537 100644 --- a/docs/plugins/game_wrapper_pokemon_gen1.html +++ b/docs/plugins/game_wrapper_pokemon_gen1.html @@ -34,21 +34,15 @@

    Module pyboy.plugins.game_wrapper_pokemon_gen1Module pyboy.plugins.game_wrapper_pokemon_gen1Classes

    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 + super().__init__(*args, game_area_section=(0, 0) + self.shape, game_area_follow_scxy=True, **kwargs) + self.sprite_offset = 0 def enabled(self): - return self.pyboy_argv.get("game_wrapper") and ((self.pyboy.cartridge_title() == "POKEMON RED") or - (self.pyboy.cartridge_title() == "POKEMON BLUE")) + return (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() + scanline_parameters = self.pyboy.screen.tilemap_position_list WX = scanline_parameters[0][2] WY = scanline_parameters[0][3] self.use_background(WY != 0) def _get_screen_background_tilemap(self): - ### SIMILAR TO CURRENT pyboy.game_wrapper()._game_area_np(), BUT ONLY FOR BACKGROUND TILEMAP, SO NPC ARE SKIPPED + ### SIMILAR TO CURRENT pyboy.game_wrapper.game_area(), BUT ONLY FOR BACKGROUND TILEMAP, SO NPC ARE SKIPPED bsm = self.pyboy.botsupport_manager() ((scx, scy), (wx, wy)) = bsm.screen().tilemap_position() - tilemap = np.array(bsm.tilemap_background()[:, :]) + tilemap = np.array(bsm.tilemap_background[:, :]) return np.roll(np.roll(tilemap, -scy // 8, axis=0), -scx // 8, axis=1)[:18, :20] def _get_screen_walkable_matrix(self): walkable_tiles_indexes = [] - collision_ptr = self.pyboy.get_memory_value(0xD530) + (self.pyboy.get_memory_value(0xD531) << 8) - tileset_type = self.pyboy.get_memory_value(0xFFD7) + collision_ptr = self.pyboy.memory[0xD530] + (self.pyboy.memory[0xD531] << 8) + tileset_type = self.pyboy.memory[0xFFD7] if tileset_type > 0: - grass_tile_index = self.pyboy.get_memory_value(0xD535) + grass_tile_index = self.pyboy.memory[0xD535] if grass_tile_index != 0xFF: walkable_tiles_indexes.append(grass_tile_index + 0x100) for i in range(0x180): - tile_index = self.pyboy.get_memory_value(collision_ptr + i) + tile_index = self.pyboy.memory[collision_ptr + i] if tile_index == 0xFF: break else: @@ -257,8 +249,7 @@

    Methods

    Expand source code
    def enabled(self):
    -    return self.pyboy_argv.get("game_wrapper") and ((self.pyboy.cartridge_title() == "POKEMON RED") or
    -                                                    (self.pyboy.cartridge_title() == "POKEMON BLUE"))
    + return (self.pyboy.cartridge_title == "POKEMON RED") or (self.pyboy.cartridge_title == "POKEMON BLUE")
    @@ -289,6 +280,7 @@

    Inherited members

    diff --git a/docs/plugins/game_wrapper_super_mario_land.html b/docs/plugins/game_wrapper_super_mario_land.html index bc127196b..d885b015c 100644 --- a/docs/plugins/game_wrapper_super_mario_land.html +++ b/docs/plugins/game_wrapper_super_mario_land.html @@ -34,20 +34,14 @@

    Module pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landModule pyboy.plugins.game_wrapper_super_mario_landClasses

    (*args, **kwargs)
    -

    This class wraps Super Mario Land, and provides easy access to score, coins, lives left, time left, world and a -"fitness" score for AIs.

    +

    This class wraps Super Mario Land, and provides easy access to score, coins, lives left, time left and world for AIs.

    Only world 1-1 is officially supported at the moment. Support for more worlds coming soon.

    If you call print on an instance of this object, it will show an overview of everything this object provides.

    @@ -394,17 +373,21 @@

    Classes

    class GameWrapperSuperMarioLand(PyBoyGameWrapper):
         """
    -    This class wraps Super Mario Land, and provides easy access to score, coins, lives left, time left, world and a
    -    "fitness" score for AIs.
    +    This class wraps Super Mario Land, and provides easy access to score, coins, lives left, time left and world for AIs.
     
         __Only world 1-1 is officially supported at the moment. Support for more worlds coming soon.__
     
         If you call `print` on an instance of this object, it will show an overview of everything this object provides.
         """
         cartridge_title = "SUPER MARIOLAN"
    -    tiles_compressed = tiles_compressed
    -    tiles_minimal = tiles_minimal
    -
    +    mapping_compressed = mapping_compressed
    +    """
    +    Compressed mapping for `pyboy.PyBoy.game_area_mapping`
    +    """
    +    mapping_minimal = mapping_minimal
    +    """
    +    Minimal mapping for `pyboy.PyBoy.game_area_mapping`
    +    """
         def __init__(self, *args, **kwargs):
             self.shape = (20, 16)
             """The shape of the game area"""
    @@ -420,39 +403,26 @@ 

    Classes

    """The number of seconds left to finish the level""" self.level_progress = 0 """An integer of the current "global" X position in this level. Can be used for AI scoring.""" - self._level_progress_max = 0 - self.fitness = 0 - """ - A built-in fitness scoring. Taking points, level progression, time left, and lives left into account. - - .. math:: - fitness = (lives\\_left \\cdot 10000) + (score + time\\_left \\cdot 10) + (\\_level\\_progress\\_max \\cdot 10) - """ - super().__init__(*args, game_area_section=(0, 2) + self.shape, game_area_wrap_around=True, **kwargs) + super().__init__(*args, game_area_section=(0, 2) + self.shape, game_area_follow_scxy=True, **kwargs) def post_tick(self): self._tile_cache_invalid = True self._sprite_cache_invalid = True - world_level = self.pyboy.get_memory_value(ADDR_WORLD_LEVEL) + world_level = self.pyboy.memory[ADDR_WORLD_LEVEL] self.world = world_level >> 4, world_level & 0x0F blank = 300 self.coins = self._sum_number_on_screen(9, 1, 2, blank, -256) - self.lives_left = _bcm_to_dec(self.pyboy.get_memory_value(ADDR_LIVES_LEFT)) + self.lives_left = _bcm_to_dec(self.pyboy.memory[ADDR_LIVES_LEFT]) self.score = self._sum_number_on_screen(0, 1, 6, blank, -256) self.time_left = self._sum_number_on_screen(17, 1, 3, blank, -256) - level_block = self.pyboy.get_memory_value(0xC0AB) - mario_x = self.pyboy.get_memory_value(0xC202) - scx = self.pyboy.botsupport_manager().screen().tilemap_position_list()[16][0] + level_block = self.pyboy.memory[0xC0AB] + mario_x = self.pyboy.memory[0xC202] + scx = self.pyboy.screen.tilemap_position_list[16][0] self.level_progress = level_block*16 + (scx-7) % 16 + mario_x - if self.game_has_started: - self._level_progress_max = max(self.level_progress, self._level_progress_max) - end_score = self.score + self.time_left * 10 - self.fitness = self.lives_left * 10000 + end_score + self._level_progress_max * 10 - def set_lives_left(self, amount): """ Set the amount lives to any number between 0 and 99. @@ -468,11 +438,11 @@

    Classes

    if 0 <= amount <= 99: tens = amount // 10 ones = amount % 10 - self.pyboy.set_memory_value(ADDR_LIVES_LEFT, (tens << 4) | ones) - self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY, tens) - self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY + 1, ones) + self.pyboy.memory[ADDR_LIVES_LEFT] = (tens << 4) | ones + self.pyboy.memory[ADDR_LIVES_LEFT_DISPLAY] = tens + self.pyboy.memory[ADDR_LIVES_LEFT_DISPLAY + 1] = ones else: - logger.error(f"{amount} is out of bounds. Only values between 0 and 99 allowed.") + logger.error("%d is out of bounds. Only values between 0 and 99 allowed.", amount) def set_world_level(self, world, level): """ @@ -484,7 +454,7 @@

    Classes

    """ for i in range(0x450, 0x461): - self.pyboy.override_memory_value(0, i, 0x00) + self.pyboy.memory[0, i] = 0x00 patch1 = [ 0x3E, # LD A, d8 @@ -492,7 +462,7 @@

    Classes

    ] for i, byte in enumerate(patch1): - self.pyboy.override_memory_value(0, 0x451 + i, byte) + self.pyboy.memory[0, 0x451 + i] = byte def start_game(self, timer_div=None, world_level=None, unlock_level_select=False): """ @@ -520,28 +490,24 @@

    Classes

    # Boot screen while True: - self.pyboy.tick() + self.pyboy.tick(1, False) if self.tilemap_background[6:11, 13] == [284, 285, 266, 283, 285]: # "START" on the main menu break - self.pyboy.tick() - self.pyboy.tick() - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) + self.pyboy.tick(3, False) + self.pyboy.button("start") + self.pyboy.tick(1, False) while True: if unlock_level_select and self.pyboy.frame_count == 71: # An arbitrary frame count, where the write will work - self.pyboy.set_memory_value(ADDR_WIN_COUNT, 2 if unlock_level_select else 0) + self.pyboy.memory[ADDR_WIN_COUNT] = 2 if unlock_level_select else 0 break - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) # "MARIO" in the title bar and 0 is placed at score if self.tilemap_background[0:5, 0] == [278, 266, 283, 274, 280] and \ self.tilemap_background[5, 1] == 256: - self.game_has_started = True + # Game has started break self.saved_state.seek(0) @@ -603,7 +569,7 @@

    Classes

    def game_over(self): # Apparantly that address is for game over # https://datacrystal.romhacking.net/wiki/Super_Mario_Land:RAM_map - return self.pyboy.get_memory_value(0xC0A4) == 0x39 + return self.pyboy.memory[0xC0A4] == 0x39 def __repr__(self): adjust = 4 @@ -615,7 +581,6 @@

    Classes

    f"Score: {self.score}\n" + f"Time left: {self.time_left}\n" + f"Level progress: {self.level_progress}\n" + - f"Fitness: {self.fitness}\n" + "Sprites on screen:\n" + "\n".join([str(s) for s in self._sprites_on_screen()]) + "\n" + @@ -639,13 +604,9 @@

    Ancestors

    Class variables

    -
    var tiles_compressed
    -
    -
    -
    -
    var tiles_minimal
    +
    var mapping_compressed
    -
    +

    Compressed mapping for PyBoy.game_area_mapping()

    Instance variables

    @@ -678,11 +639,6 @@

    Instance variables

    An integer of the current "global" X position in this level. Can be used for AI scoring.

    -
    var fitness
    -
    -

    A built-in fitness scoring. Taking points, level progression, time left, and lives left into account.

    -

    fitness = (lives\_left \cdot 10000) + (score + time\_left \cdot 10) + (\_level\_progress\_max \cdot 10)

    -

    Methods

    @@ -716,11 +672,11 @@

    Args

    if 0 <= amount <= 99: tens = amount // 10 ones = amount % 10 - self.pyboy.set_memory_value(ADDR_LIVES_LEFT, (tens << 4) | ones) - self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY, tens) - self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY + 1, ones) + self.pyboy.memory[ADDR_LIVES_LEFT] = (tens << 4) | ones + self.pyboy.memory[ADDR_LIVES_LEFT_DISPLAY] = tens + self.pyboy.memory[ADDR_LIVES_LEFT_DISPLAY + 1] = ones else: - logger.error(f"{amount} is out of bounds. Only values between 0 and 99 allowed.")
    + logger.error("%d is out of bounds. Only values between 0 and 99 allowed.", amount)
    @@ -749,7 +705,7 @@

    Args

    """ for i in range(0x450, 0x461): - self.pyboy.override_memory_value(0, i, 0x00) + self.pyboy.memory[0, i] = 0x00 patch1 = [ 0x3E, # LD A, d8 @@ -757,7 +713,7 @@

    Args

    ] for i, byte in enumerate(patch1): - self.pyboy.override_memory_value(0, 0x451 + i, byte)
    + self.pyboy.memory[0, 0x451 + i] = byte
    @@ -806,28 +762,24 @@

    Kwargs

    # Boot screen while True: - self.pyboy.tick() + self.pyboy.tick(1, False) if self.tilemap_background[6:11, 13] == [284, 285, 266, 283, 285]: # "START" on the main menu break - self.pyboy.tick() - self.pyboy.tick() - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) + self.pyboy.tick(3, False) + self.pyboy.button("start") + self.pyboy.tick(1, False) while True: if unlock_level_select and self.pyboy.frame_count == 71: # An arbitrary frame count, where the write will work - self.pyboy.set_memory_value(ADDR_WIN_COUNT, 2 if unlock_level_select else 0) + self.pyboy.memory[ADDR_WIN_COUNT] = 2 if unlock_level_select else 0 break - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) # "MARIO" in the title bar and 0 is placed at score if self.tilemap_background[0:5, 0] == [278, 266, 283, 274, 280] and \ self.tilemap_background[5, 1] == 256: - self.game_has_started = True + # Game has started break self.saved_state.seek(0) @@ -945,6 +897,7 @@

    Inherited members

  • PyBoyGameWrapper:
  • @@ -980,9 +933,7 @@

    score
  • time_left
  • level_progress
  • -
  • fitness
  • -
  • tiles_compressed
  • -
  • tiles_minimal
  • +
  • mapping_compressed
  • diff --git a/docs/plugins/game_wrapper_tetris.html b/docs/plugins/game_wrapper_tetris.html index 3d4a533e4..9878c3373 100644 --- a/docs/plugins/game_wrapper_tetris.html +++ b/docs/plugins/game_wrapper_tetris.html @@ -34,21 +34,16 @@

    Module pyboy.plugins.game_wrapper_tetris

    "GameWrapperTetris.post_tick": False, } -import logging from array import array import numpy as np + +import pyboy 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 +logger = pyboy.logging.get_logger(__name__) # Table for translating game-representation of Tetromino types (8-bit int) to string tetromino_table = { @@ -68,29 +63,34 @@

    Module pyboy.plugins.game_wrapper_tetris

    TILES = 384 # Compressed assigns an ID to each Tetromino type -tiles_compressed = np.zeros(TILES, dtype=np.uint8) +mapping_compressed = np.zeros(TILES, dtype=np.uint8) # BLANK, J, Z, O, L, T, S, I, BLACK tiles_types = [[47], [129], [130], [131], [132], [133], [134], [128, 136, 137, 138, 139, 143], [135]] for tiles_type_ID, tiles_type in enumerate(tiles_types): for tile_ID in tiles_type: - tiles_compressed[tile_ID] = tiles_type_ID + mapping_compressed[tile_ID] = tiles_type_ID # Minimal has 3 id's: Background, Tetromino and "losing tile" (which fills the board when losing) -tiles_minimal = np.ones(TILES, dtype=np.uint8) # For minimal everything is 1 -tiles_minimal[47] = 0 # Except BLANK which is 0 -tiles_minimal[135] = 2 # And background losing tiles BLACK which is 2 +mapping_minimal = np.ones(TILES, dtype=np.uint8) # For minimal everything is 1 +mapping_minimal[47] = 0 # Except BLANK which is 0 +mapping_minimal[135] = 2 # And background losing tiles BLACK which is 2 class GameWrapperTetris(PyBoyGameWrapper): """ - This class wraps Tetris, and provides easy access to score, lines, level and a "fitness" score for AIs. + This class wraps Tetris, and provides easy access to score, lines and level for AIs. If you call `print` on an instance of this object, it will show an overview of everything this object provides. """ cartridge_title = "TETRIS" - tiles_compressed = tiles_compressed - tiles_minimal = tiles_minimal - + mapping_compressed = mapping_compressed + """ + Compressed mapping for `pyboy.PyBoy.game_area_mapping` + """ + mapping_minimal = mapping_minimal + """ + Minimal mapping for `pyboy.PyBoy.game_area_mapping` + """ def __init__(self, *args, **kwargs): self.shape = (10, 18) """The shape of the game area""" @@ -100,25 +100,14 @@

    Module pyboy.plugins.game_wrapper_tetris

    """The current level""" self.lines = 0 """The number of cleared lines""" - self.fitness = 0 - """ - A built-in fitness scoring. The scoring is equals to `score`. - .. math:: - fitness = score - """ super().__init__(*args, **kwargs) ROWS, COLS = self.shape self._cached_game_area_tiles_raw = array("B", [0xFF] * (ROWS*COLS*4)) + self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(ROWS, COLS)) - if cythonmode: - self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(ROWS, COLS)) - else: - v = memoryview(self._cached_game_area_tiles_raw).cast("I") - self._cached_game_area_tiles = [v[i:i + COLS] for i in range(0, COLS * ROWS, COLS)] - - super().__init__(*args, game_area_section=(2, 0) + self.shape, game_area_wrap_around=True, **kwargs) + super().__init__(*args, game_area_section=(2, 0) + self.shape, game_area_follow_scxy=False, **kwargs) def _game_area_tiles(self): if self._tile_cache_invalid: @@ -131,14 +120,10 @@

    Module pyboy.plugins.game_wrapper_tetris

    self._sprite_cache_invalid = True blank = 47 - self.tilemap_background.refresh_lcdc() self.score = self._sum_number_on_screen(13, 3, 6, blank, 0) self.level = self._sum_number_on_screen(14, 7, 4, blank, 0) self.lines = self._sum_number_on_screen(14, 10, 4, blank, 0) - if self.game_has_started: - self.fitness = self.score - def start_game(self, timer_div=None): """ Call this function right after initializing PyBoy. This will navigate through menus to start the game at the @@ -155,25 +140,18 @@

    Module pyboy.plugins.game_wrapper_tetris

    # Boot screen while True: - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) if self.tilemap_background[2:9, 14] == [89, 25, 21, 10, 34, 14, 27]: # '1PLAYER' on the first screen break # Start game. Just press Start when the game allows us. for i in range(2): - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick() + self.pyboy.button("start") + self.pyboy.tick(7, False) self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) - self.game_has_started = True - self.reset_game(timer_div=timer_div) def reset_game(self, timer_div=None): @@ -186,13 +164,8 @@

    Module pyboy.plugins.game_wrapper_tetris

    PyBoyGameWrapper.reset_game(self, timer_div=timer_div) self._set_timer_div(timer_div) - - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick() + self.pyboy.button("start") + self.pyboy.tick(7, False) def game_area(self): """ @@ -252,7 +225,7 @@

    Module pyboy.plugins.game_wrapper_tetris

    * `"T"`: T-shape """ # Bitmask, as the last two bits determine the direction - return inverse_tetromino_table[self.pyboy.get_memory_value(NEXT_TETROMINO_ADDR) & 0b11111100] + return inverse_tetromino_table[self.pyboy.memory[NEXT_TETROMINO_ADDR] & 0b11111100] def set_tetromino(self, shape): """ @@ -291,7 +264,7 @@

    Module pyboy.plugins.game_wrapper_tetris

    ] for i, byte in enumerate(patch1): - self.pyboy.override_memory_value(0, 0x206E + i, byte) + self.pyboy.memory[0, 0x206E + i] = byte patch2 = [ 0x3E, # LD A, Tetromino @@ -299,7 +272,7 @@

    Module pyboy.plugins.game_wrapper_tetris

    ] for i, byte in enumerate(patch2): - self.pyboy.override_memory_value(0, 0x20B0 + i, byte) + self.pyboy.memory[0, 0x20B0 + i] = byte def game_over(self): """ @@ -317,7 +290,6 @@

    Module pyboy.plugins.game_wrapper_tetris

    f"Score: {self.score}\n" + f"Level: {self.level}\n" + f"Lines: {self.lines}\n" + - f"Fitness: {self.fitness}\n" + "Sprites on screen:\n" + "\n".join([str(s) for s in self._sprites_on_screen()]) + "\n" + @@ -328,7 +300,7 @@

    Module pyboy.plugins.game_wrapper_tetris

    "\n".join( [ f"{i: <3}| " + "".join([str(tile).ljust(adjust) for tile in line]) - for i, line in enumerate(self._game_area_np()) + for i, line in enumerate(self.game_area()) ] ) ) @@ -349,7 +321,7 @@

    Classes

    (*args, **kwargs)
    -

    This class wraps Tetris, and provides easy access to score, lines, level and a "fitness" score for AIs.

    +

    This class wraps Tetris, and provides easy access to score, lines and level for AIs.

    If you call print on an instance of this object, it will show an overview of everything this object provides.

    @@ -357,14 +329,19 @@

    Classes

    class GameWrapperTetris(PyBoyGameWrapper):
         """
    -    This class wraps Tetris, and provides easy access to score, lines, level and a "fitness" score for AIs.
    +    This class wraps Tetris, and provides easy access to score, lines and level for AIs.
     
         If you call `print` on an instance of this object, it will show an overview of everything this object provides.
         """
         cartridge_title = "TETRIS"
    -    tiles_compressed = tiles_compressed
    -    tiles_minimal = tiles_minimal
    -
    +    mapping_compressed = mapping_compressed
    +    """
    +    Compressed mapping for `pyboy.PyBoy.game_area_mapping`
    +    """
    +    mapping_minimal = mapping_minimal
    +    """
    +    Minimal mapping for `pyboy.PyBoy.game_area_mapping`
    +    """
         def __init__(self, *args, **kwargs):
             self.shape = (10, 18)
             """The shape of the game area"""
    @@ -374,25 +351,14 @@ 

    Classes

    """The current level""" self.lines = 0 """The number of cleared lines""" - self.fitness = 0 - """ - A built-in fitness scoring. The scoring is equals to `score`. - .. math:: - fitness = score - """ super().__init__(*args, **kwargs) ROWS, COLS = self.shape self._cached_game_area_tiles_raw = array("B", [0xFF] * (ROWS*COLS*4)) + self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(ROWS, COLS)) - if cythonmode: - self._cached_game_area_tiles = memoryview(self._cached_game_area_tiles_raw).cast("I", shape=(ROWS, COLS)) - else: - v = memoryview(self._cached_game_area_tiles_raw).cast("I") - self._cached_game_area_tiles = [v[i:i + COLS] for i in range(0, COLS * ROWS, COLS)] - - super().__init__(*args, game_area_section=(2, 0) + self.shape, game_area_wrap_around=True, **kwargs) + super().__init__(*args, game_area_section=(2, 0) + self.shape, game_area_follow_scxy=False, **kwargs) def _game_area_tiles(self): if self._tile_cache_invalid: @@ -405,14 +371,10 @@

    Classes

    self._sprite_cache_invalid = True blank = 47 - self.tilemap_background.refresh_lcdc() self.score = self._sum_number_on_screen(13, 3, 6, blank, 0) self.level = self._sum_number_on_screen(14, 7, 4, blank, 0) self.lines = self._sum_number_on_screen(14, 10, 4, blank, 0) - if self.game_has_started: - self.fitness = self.score - def start_game(self, timer_div=None): """ Call this function right after initializing PyBoy. This will navigate through menus to start the game at the @@ -429,25 +391,18 @@

    Classes

    # Boot screen while True: - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) if self.tilemap_background[2:9, 14] == [89, 25, 21, 10, 34, 14, 27]: # '1PLAYER' on the first screen break # Start game. Just press Start when the game allows us. for i in range(2): - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick() + self.pyboy.button("start") + self.pyboy.tick(7, False) self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) - self.game_has_started = True - self.reset_game(timer_div=timer_div) def reset_game(self, timer_div=None): @@ -460,13 +415,8 @@

    Classes

    PyBoyGameWrapper.reset_game(self, timer_div=timer_div) self._set_timer_div(timer_div) - - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick() + self.pyboy.button("start") + self.pyboy.tick(7, False) def game_area(self): """ @@ -526,7 +476,7 @@

    Classes

    * `"T"`: T-shape """ # Bitmask, as the last two bits determine the direction - return inverse_tetromino_table[self.pyboy.get_memory_value(NEXT_TETROMINO_ADDR) & 0b11111100] + return inverse_tetromino_table[self.pyboy.memory[NEXT_TETROMINO_ADDR] & 0b11111100] def set_tetromino(self, shape): """ @@ -565,7 +515,7 @@

    Classes

    ] for i, byte in enumerate(patch1): - self.pyboy.override_memory_value(0, 0x206E + i, byte) + self.pyboy.memory[0, 0x206E + i] = byte patch2 = [ 0x3E, # LD A, Tetromino @@ -573,7 +523,7 @@

    Classes

    ] for i, byte in enumerate(patch2): - self.pyboy.override_memory_value(0, 0x20B0 + i, byte) + self.pyboy.memory[0, 0x20B0 + i] = byte def game_over(self): """ @@ -591,7 +541,6 @@

    Classes

    f"Score: {self.score}\n" + f"Level: {self.level}\n" + f"Lines: {self.lines}\n" + - f"Fitness: {self.fitness}\n" + "Sprites on screen:\n" + "\n".join([str(s) for s in self._sprites_on_screen()]) + "\n" + @@ -602,7 +551,7 @@

    Classes

    "\n".join( [ f"{i: <3}| " + "".join([str(tile).ljust(adjust) for tile in line]) - for i, line in enumerate(self._game_area_np()) + for i, line in enumerate(self.game_area()) ] ) ) @@ -615,13 +564,9 @@

    Ancestors

    Class variables

    -
    var tiles_compressed
    +
    var mapping_compressed
    -
    -
    -
    var tiles_minimal
    -
    -
    +

    Compressed mapping for PyBoy.game_area_mapping()

    Instance variables

    @@ -642,11 +587,6 @@

    Instance variables

    The number of cleared lines

    -
    var fitness
    -
    -

    A built-in fitness scoring. The scoring is equals to score.

    -

    fitness = score

    -

    Methods

    @@ -680,25 +620,18 @@

    Kwargs

    # Boot screen while True: - self.pyboy.tick() - self.tilemap_background.refresh_lcdc() + self.pyboy.tick(1, False) if self.tilemap_background[2:9, 14] == [89, 25, 21, 10, 34, 14, 27]: # '1PLAYER' on the first screen break # Start game. Just press Start when the game allows us. for i in range(2): - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick() + self.pyboy.button("start") + self.pyboy.tick(7, False) self.saved_state.seek(0) self.pyboy.save_state(self.saved_state) - self.game_has_started = True - self.reset_game(timer_div=timer_div)
    @@ -723,13 +656,8 @@

    Kwargs

    PyBoyGameWrapper.reset_game(self, timer_div=timer_div) self._set_timer_div(timer_div) - - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.tick() - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - - for _ in range(6): - self.pyboy.tick()
    + self.pyboy.button("start") + self.pyboy.tick(7, False)
    @@ -852,7 +780,7 @@

    Returns

    * `"T"`: T-shape """ # Bitmask, as the last two bits determine the direction - return inverse_tetromino_table[self.pyboy.get_memory_value(NEXT_TETROMINO_ADDR) & 0b11111100]
    + return inverse_tetromino_table[self.pyboy.memory[NEXT_TETROMINO_ADDR] & 0b11111100]
    @@ -919,7 +847,7 @@

    Args

    ] for i, byte in enumerate(patch1): - self.pyboy.override_memory_value(0, 0x206E + i, byte) + self.pyboy.memory[0, 0x206E + i] = byte patch2 = [ 0x3E, # LD A, Tetromino @@ -927,7 +855,7 @@

    Args

    ] for i, byte in enumerate(patch2): - self.pyboy.override_memory_value(0, 0x20B0 + i, byte)
    + self.pyboy.memory[0, 0x20B0 + i] = byte
    @@ -950,6 +878,14 @@

    Args

    +

    Inherited members

    + @@ -980,9 +916,7 @@

    score
  • level
  • lines
  • -
  • fitness
  • -
  • tiles_compressed
  • -
  • tiles_minimal
  • +
  • mapping_compressed
  • diff --git a/docs/plugins/index.html b/docs/plugins/index.html index 414c1fe5e..9632b8256 100644 --- a/docs/plugins/index.html +++ b/docs/plugins/index.html @@ -36,19 +36,19 @@

    Module pyboy.plugins

    __pdoc__ = { # docs exclude - "window_headless": False, - "window_open_gl": False, - "screen_recorder": False, - "rewind": False, - "window_dummy": False, "disable_input": False, - "manager_gen": False, - "auto_pause": False, - "manager": False, - "record_replay": False, + "rewind": False, + "window_sdl2": False, "screenshot_recorder": False, + "debug_prompt": False, + "screen_recorder": False, "debug": False, - "window_sdl2": False, + "manager": False, + "record_replay": False, + "manager_gen": False, + "window_open_gl": False, + "auto_pause": False, + "window_null": False, # docs exclude end }
    diff --git a/docs/utils.html b/docs/utils.html new file mode 100644 index 000000000..34ddbd65d --- /dev/null +++ b/docs/utils.html @@ -0,0 +1,889 @@ + + + + + + +pyboy.utils API documentation + + + + + + + + + + +
    +
    +
    +

    Module pyboy.utils

    +
    +
    +
    + +Expand source code + +
    #
    +# License: See LICENSE.md file
    +# GitHub: https://github.com/Baekalfen/PyBoy
    +#
    +
    +__all__ = ["WindowEvent", "dec_to_bcd", "bcd_to_dec"]
    +
    +STATE_VERSION = 10
    +
    +##############################################################
    +# Buffer classes
    +
    +
    +class IntIOInterface:
    +    def __init__(self, buf):
    +        pass
    +
    +    def write(self, byte):
    +        raise Exception("Not implemented!")
    +
    +    def write_64bit(self, value):
    +        self.write(value & 0xFF)
    +        self.write((value >> 8) & 0xFF)
    +        self.write((value >> 16) & 0xFF)
    +        self.write((value >> 24) & 0xFF)
    +        self.write((value >> 32) & 0xFF)
    +        self.write((value >> 40) & 0xFF)
    +        self.write((value >> 48) & 0xFF)
    +        self.write((value >> 56) & 0xFF)
    +
    +    def read_64bit(self):
    +        a = self.read()
    +        b = self.read()
    +        c = self.read()
    +        d = self.read()
    +        e = self.read()
    +        f = self.read()
    +        g = self.read()
    +        h = self.read()
    +        return a | (b << 8) | (c << 16) | (d << 24) | (e << 32) | (f << 40) | (g << 48) | (h << 56)
    +
    +    def write_32bit(self, value):
    +        self.write(value & 0xFF)
    +        self.write((value >> 8) & 0xFF)
    +        self.write((value >> 16) & 0xFF)
    +        self.write((value >> 24) & 0xFF)
    +
    +    def read_32bit(self):
    +        a = self.read()
    +        b = self.read()
    +        c = self.read()
    +        d = self.read()
    +        return int(a | (b << 8) | (c << 16) | (d << 24))
    +
    +    def write_16bit(self, value):
    +        self.write(value & 0xFF)
    +        self.write((value >> 8) & 0xFF)
    +
    +    def read_16bit(self):
    +        a = self.read()
    +        b = self.read()
    +        return int(a | (b << 8))
    +
    +    def read(self):
    +        raise Exception("Not implemented!")
    +
    +    def seek(self, pos):
    +        raise Exception("Not implemented!")
    +
    +    def flush(self):
    +        raise Exception("Not implemented!")
    +
    +    def new(self):
    +        raise Exception("Not implemented!")
    +
    +    def commit(self):
    +        raise Exception("Not implemented!")
    +
    +    def seek_frame(self, _):
    +        raise Exception("Not implemented!")
    +
    +    def tell(self):
    +        raise Exception("Not implemented!")
    +
    +
    +class IntIOWrapper(IntIOInterface):
    +    """
    +    Wraps a file-like object to allow writing integers to it.
    +    This allows for higher performance, when writing to a memory map in rewind.
    +    """
    +    def __init__(self, buf):
    +        self.buffer = buf
    +
    +    def write(self, byte):
    +        assert isinstance(byte, int)
    +        assert 0 <= byte <= 0xFF
    +        return self.buffer.write(byte.to_bytes(1, "little"))
    +
    +    def read(self):
    +        # assert count == 1, "Only a count of 1 is supported"
    +        data = self.buffer.read(1)
    +        assert len(data) == 1, "No data"
    +        return ord(data)
    +
    +    def seek(self, pos):
    +        self.buffer.seek(pos)
    +
    +    def flush(self):
    +        self.buffer.flush()
    +
    +    def tell(self):
    +        return self.buffer.tell()
    +
    +
    +##############################################################
    +# Misc
    +
    +
    +# TODO: Would a lookup-table increase performance? For example a lookup table of each 4-bit nibble?
    +# That's 16**2 = 256 values. Index calculated as: (byte1 & 0xF0) | ((byte2 & 0xF0) >> 4)
    +# and then: (byte1 & 0x0F) | ((byte2 & 0x0F) >> 4)
    +# Then could even be preloaded for each color palette
    +def color_code(byte1, byte2, offset):
    +    """Convert 2 bytes into color code at a given offset.
    +
    +    The colors are 2 bit and are found like this:
    +
    +    Color of the first pixel is 0b10
    +    | Color of the second pixel is 0b01
    +    v v
    +    1 0 0 1 0 0 0 1 <- byte1
    +    0 1 1 1 1 1 0 0 <- byte2
    +    """
    +    return (((byte2 >> (offset)) & 0b1) << 1) + ((byte1 >> (offset)) & 0b1)
    +
    +
    +##############################################################
    +# Window Events
    +# Temporarily placed here to not be exposed on public API
    +
    +
    +class WindowEvent:
    +    """
    +    All supported events can be found in the class description below.
    +
    +    It can be used as follows:
    +
    +    ```python
    +    >>> from pyboy.utils import WindowEvent
    +    >>> pyboy.send_input(WindowEvent.PAUSE)
    +
    +    ```
    +
    +    Just for button presses, it might be easier to use: `pyboy.PyBoy.button`,
    +    `pyboy.PyBoy.button_press` and `pyboy.PyBoy.button_release`.
    +    """
    +
    +    # ONLY ADD NEW EVENTS AT THE END OF THE LIST!
    +    # Otherwise, it will break replays, which depend on the id of the event
    +    (
    +        QUIT,
    +        PRESS_ARROW_UP,
    +        PRESS_ARROW_DOWN,
    +        PRESS_ARROW_RIGHT,
    +        PRESS_ARROW_LEFT,
    +        PRESS_BUTTON_A,
    +        PRESS_BUTTON_B,
    +        PRESS_BUTTON_SELECT,
    +        PRESS_BUTTON_START,
    +        RELEASE_ARROW_UP,
    +        RELEASE_ARROW_DOWN,
    +        RELEASE_ARROW_RIGHT,
    +        RELEASE_ARROW_LEFT,
    +        RELEASE_BUTTON_A,
    +        RELEASE_BUTTON_B,
    +        RELEASE_BUTTON_SELECT,
    +        RELEASE_BUTTON_START,
    +        _INTERNAL_TOGGLE_DEBUG,
    +        PRESS_SPEED_UP,
    +        RELEASE_SPEED_UP,
    +        STATE_SAVE,
    +        STATE_LOAD,
    +        PASS,
    +        SCREEN_RECORDING_TOGGLE,
    +        PAUSE,
    +        UNPAUSE,
    +        PAUSE_TOGGLE,
    +        PRESS_REWIND_BACK,
    +        PRESS_REWIND_FORWARD,
    +        RELEASE_REWIND_BACK,
    +        RELEASE_REWIND_FORWARD,
    +        WINDOW_FOCUS,
    +        WINDOW_UNFOCUS,
    +        _INTERNAL_RENDERER_FLUSH,
    +        _INTERNAL_MOUSE,
    +        _INTERNAL_MARK_TILE,
    +        SCREENSHOT_RECORD,
    +        DEBUG_MEMORY_SCROLL_DOWN,
    +        DEBUG_MEMORY_SCROLL_UP,
    +        MOD_SHIFT_ON,
    +        MOD_SHIFT_OFF,
    +        FULL_SCREEN_TOGGLE,
    +    ) = range(42)
    +
    +    def __init__(self, event):
    +        self.event = event
    +
    +    def __eq__(self, x):
    +        if isinstance(x, int):
    +            return self.event == x
    +        else:
    +            return self.event == x.event
    +
    +    def __int__(self):
    +        return self.event
    +
    +    def __str__(self):
    +        return (
    +            "QUIT",
    +            "PRESS_ARROW_UP",
    +            "PRESS_ARROW_DOWN",
    +            "PRESS_ARROW_RIGHT",
    +            "PRESS_ARROW_LEFT",
    +            "PRESS_BUTTON_A",
    +            "PRESS_BUTTON_B",
    +            "PRESS_BUTTON_SELECT",
    +            "PRESS_BUTTON_START",
    +            "RELEASE_ARROW_UP",
    +            "RELEASE_ARROW_DOWN",
    +            "RELEASE_ARROW_RIGHT",
    +            "RELEASE_ARROW_LEFT",
    +            "RELEASE_BUTTON_A",
    +            "RELEASE_BUTTON_B",
    +            "RELEASE_BUTTON_SELECT",
    +            "RELEASE_BUTTON_START",
    +            "_INTERNAL_TOGGLE_DEBUG",
    +            "PRESS_SPEED_UP",
    +            "RELEASE_SPEED_UP",
    +            "STATE_SAVE",
    +            "STATE_LOAD",
    +            "PASS",
    +            "SCREEN_RECORDING_TOGGLE",
    +            "PAUSE",
    +            "UNPAUSE",
    +            "PAUSE_TOGGLE",
    +            "PRESS_REWIND_BACK",
    +            "PRESS_REWIND_FORWARD",
    +            "RELEASE_REWIND_BACK",
    +            "RELEASE_REWIND_FORWARD",
    +            "WINDOW_FOCUS",
    +            "WINDOW_UNFOCUS",
    +            "_INTERNAL_RENDERER_FLUSH",
    +            "_INTERNAL_MOUSE",
    +            "_INTERNAL_MARK_TILE",
    +            "SCREENSHOT_RECORD",
    +            "DEBUG_MEMORY_SCROLL_DOWN",
    +            "DEBUG_MEMORY_SCROLL_UP",
    +            "MOD_SHIFT_ON",
    +            "MOD_SHIFT_OFF",
    +            "FULL_SCREEN_TOGGLE",
    +        )[self.event]
    +
    +
    +class WindowEventMouse(WindowEvent):
    +    def __init__(
    +        self, *args, window_id=-1, mouse_x=-1, mouse_y=-1, mouse_scroll_x=-1, mouse_scroll_y=-1, mouse_button=-1
    +    ):
    +        super().__init__(*args)
    +        self.window_id = window_id
    +        self.mouse_x = mouse_x
    +        self.mouse_y = mouse_y
    +        self.mouse_scroll_x = mouse_scroll_x
    +        self.mouse_scroll_y = mouse_scroll_y
    +        self.mouse_button = mouse_button
    +
    +
    +##############################################################
    +# Memory Scanning
    +#
    +
    +
    +def dec_to_bcd(value, byte_width=1, byteorder="little"):
    +    """
    +    Converts a decimal value to Binary Coded Decimal (BCD).
    +
    +    Args:
    +        value (int): The integer value to convert.
    +        byte_width (int): The number of bytes to consider for each value.
    +        byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
    +
    +    Example:
    +    ```python
    +    >>> from pyboy.utils import dec_to_bcd
    +    >>> f"{dec_to_bcd(30):08b}"
    +    '00110000'
    +    >>> f"{dec_to_bcd(32):08b}"
    +    '00110010'
    +
    +    ```
    +
    +    Returns:
    +        int: The BCD equivalent of the decimal value.
    +    """
    +    bcd_result = []
    +    for _ in range(byte_width):
    +        tens = ((value%100) // 10) << 4
    +        units = value % 10
    +        bcd_byte = (tens | units) & 0xFF
    +        bcd_result.append(bcd_byte)
    +        value //= 100
    +    return int.from_bytes(bcd_result, byteorder)
    +
    +
    +def bcd_to_dec(value, byte_width=1, byteorder="little"):
    +    """
    +    Converts a Binary Coded Decimal (BCD) value to decimal.
    +
    +    Args:
    +        value (int): The BCD value to convert.
    +        byte_width (int): The number of bytes to consider for each value.
    +        byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.to_bytes](https://docs.python.org/3/library/stdtypes.html#int.to_bytes) for more details.
    +
    +    Example:
    +    ```python
    +    >>> from pyboy.utils import bcd_to_dec
    +    >>> bcd_to_dec(0b00110000)
    +    30
    +    >>> bcd_to_dec(0b00110010)
    +    32
    +
    +    ```
    +
    +    Returns:
    +        int: The decimal equivalent of the BCD value.
    +    """
    +    decimal_value = 0
    +    multiplier = 1
    +
    +    bcd_bytes = value.to_bytes(byte_width, byteorder)
    +
    +    for bcd_byte in bcd_bytes:
    +        decimal_value += ((bcd_byte >> 4) * 10 + (bcd_byte & 0x0F)) * multiplier
    +        multiplier *= 100
    +
    +    return decimal_value
    +
    +
    +
    +
    +
    +
    +
    +

    Functions

    +
    +
    +def dec_to_bcd(value, byte_width=1, byteorder='little') +
    +
    +

    Converts a decimal value to Binary Coded Decimal (BCD).

    +

    Args

    +
    +
    value : int
    +
    The integer value to convert.
    +
    byte_width : int
    +
    The number of bytes to consider for each value.
    +
    byteorder : str
    +
    The endian type to use. This is only used for 16-bit values and higher. See int.from_bytes for more details.
    +
    +

    Example:

    +
    >>> from pyboy.utils import dec_to_bcd
    +>>> f"{dec_to_bcd(30):08b}"
    +'00110000'
    +>>> f"{dec_to_bcd(32):08b}"
    +'00110010'
    +
    +
    +

    Returns

    +
    +
    int
    +
    The BCD equivalent of the decimal value.
    +
    +
    + +Expand source code + +
    def dec_to_bcd(value, byte_width=1, byteorder="little"):
    +    """
    +    Converts a decimal value to Binary Coded Decimal (BCD).
    +
    +    Args:
    +        value (int): The integer value to convert.
    +        byte_width (int): The number of bytes to consider for each value.
    +        byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
    +
    +    Example:
    +    ```python
    +    >>> from pyboy.utils import dec_to_bcd
    +    >>> f"{dec_to_bcd(30):08b}"
    +    '00110000'
    +    >>> f"{dec_to_bcd(32):08b}"
    +    '00110010'
    +
    +    ```
    +
    +    Returns:
    +        int: The BCD equivalent of the decimal value.
    +    """
    +    bcd_result = []
    +    for _ in range(byte_width):
    +        tens = ((value%100) // 10) << 4
    +        units = value % 10
    +        bcd_byte = (tens | units) & 0xFF
    +        bcd_result.append(bcd_byte)
    +        value //= 100
    +    return int.from_bytes(bcd_result, byteorder)
    +
    +
    +
    +def bcd_to_dec(value, byte_width=1, byteorder='little') +
    +
    +

    Converts a Binary Coded Decimal (BCD) value to decimal.

    +

    Args

    +
    +
    value : int
    +
    The BCD value to convert.
    +
    byte_width : int
    +
    The number of bytes to consider for each value.
    +
    byteorder : str
    +
    The endian type to use. This is only used for 16-bit values and higher. See int.to_bytes for more details.
    +
    +

    Example:

    +
    >>> from pyboy.utils import bcd_to_dec
    +>>> bcd_to_dec(0b00110000)
    +30
    +>>> bcd_to_dec(0b00110010)
    +32
    +
    +
    +

    Returns

    +
    +
    int
    +
    The decimal equivalent of the BCD value.
    +
    +
    + +Expand source code + +
    def bcd_to_dec(value, byte_width=1, byteorder="little"):
    +    """
    +    Converts a Binary Coded Decimal (BCD) value to decimal.
    +
    +    Args:
    +        value (int): The BCD value to convert.
    +        byte_width (int): The number of bytes to consider for each value.
    +        byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.to_bytes](https://docs.python.org/3/library/stdtypes.html#int.to_bytes) for more details.
    +
    +    Example:
    +    ```python
    +    >>> from pyboy.utils import bcd_to_dec
    +    >>> bcd_to_dec(0b00110000)
    +    30
    +    >>> bcd_to_dec(0b00110010)
    +    32
    +
    +    ```
    +
    +    Returns:
    +        int: The decimal equivalent of the BCD value.
    +    """
    +    decimal_value = 0
    +    multiplier = 1
    +
    +    bcd_bytes = value.to_bytes(byte_width, byteorder)
    +
    +    for bcd_byte in bcd_bytes:
    +        decimal_value += ((bcd_byte >> 4) * 10 + (bcd_byte & 0x0F)) * multiplier
    +        multiplier *= 100
    +
    +    return decimal_value
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class WindowEvent +(event) +
    +
    +

    All supported events can be found in the class description below.

    +

    It can be used as follows:

    +
    >>> from pyboy.utils import WindowEvent
    +>>> pyboy.send_input(WindowEvent.PAUSE)
    +
    +
    +

    Just for button presses, it might be easier to use: PyBoy.button(), +PyBoy.button_press() and PyBoy.button_release().

    +
    + +Expand source code + +
    class WindowEvent:
    +    """
    +    All supported events can be found in the class description below.
    +
    +    It can be used as follows:
    +
    +    ```python
    +    >>> from pyboy.utils import WindowEvent
    +    >>> pyboy.send_input(WindowEvent.PAUSE)
    +
    +    ```
    +
    +    Just for button presses, it might be easier to use: `pyboy.PyBoy.button`,
    +    `pyboy.PyBoy.button_press` and `pyboy.PyBoy.button_release`.
    +    """
    +
    +    # ONLY ADD NEW EVENTS AT THE END OF THE LIST!
    +    # Otherwise, it will break replays, which depend on the id of the event
    +    (
    +        QUIT,
    +        PRESS_ARROW_UP,
    +        PRESS_ARROW_DOWN,
    +        PRESS_ARROW_RIGHT,
    +        PRESS_ARROW_LEFT,
    +        PRESS_BUTTON_A,
    +        PRESS_BUTTON_B,
    +        PRESS_BUTTON_SELECT,
    +        PRESS_BUTTON_START,
    +        RELEASE_ARROW_UP,
    +        RELEASE_ARROW_DOWN,
    +        RELEASE_ARROW_RIGHT,
    +        RELEASE_ARROW_LEFT,
    +        RELEASE_BUTTON_A,
    +        RELEASE_BUTTON_B,
    +        RELEASE_BUTTON_SELECT,
    +        RELEASE_BUTTON_START,
    +        _INTERNAL_TOGGLE_DEBUG,
    +        PRESS_SPEED_UP,
    +        RELEASE_SPEED_UP,
    +        STATE_SAVE,
    +        STATE_LOAD,
    +        PASS,
    +        SCREEN_RECORDING_TOGGLE,
    +        PAUSE,
    +        UNPAUSE,
    +        PAUSE_TOGGLE,
    +        PRESS_REWIND_BACK,
    +        PRESS_REWIND_FORWARD,
    +        RELEASE_REWIND_BACK,
    +        RELEASE_REWIND_FORWARD,
    +        WINDOW_FOCUS,
    +        WINDOW_UNFOCUS,
    +        _INTERNAL_RENDERER_FLUSH,
    +        _INTERNAL_MOUSE,
    +        _INTERNAL_MARK_TILE,
    +        SCREENSHOT_RECORD,
    +        DEBUG_MEMORY_SCROLL_DOWN,
    +        DEBUG_MEMORY_SCROLL_UP,
    +        MOD_SHIFT_ON,
    +        MOD_SHIFT_OFF,
    +        FULL_SCREEN_TOGGLE,
    +    ) = range(42)
    +
    +    def __init__(self, event):
    +        self.event = event
    +
    +    def __eq__(self, x):
    +        if isinstance(x, int):
    +            return self.event == x
    +        else:
    +            return self.event == x.event
    +
    +    def __int__(self):
    +        return self.event
    +
    +    def __str__(self):
    +        return (
    +            "QUIT",
    +            "PRESS_ARROW_UP",
    +            "PRESS_ARROW_DOWN",
    +            "PRESS_ARROW_RIGHT",
    +            "PRESS_ARROW_LEFT",
    +            "PRESS_BUTTON_A",
    +            "PRESS_BUTTON_B",
    +            "PRESS_BUTTON_SELECT",
    +            "PRESS_BUTTON_START",
    +            "RELEASE_ARROW_UP",
    +            "RELEASE_ARROW_DOWN",
    +            "RELEASE_ARROW_RIGHT",
    +            "RELEASE_ARROW_LEFT",
    +            "RELEASE_BUTTON_A",
    +            "RELEASE_BUTTON_B",
    +            "RELEASE_BUTTON_SELECT",
    +            "RELEASE_BUTTON_START",
    +            "_INTERNAL_TOGGLE_DEBUG",
    +            "PRESS_SPEED_UP",
    +            "RELEASE_SPEED_UP",
    +            "STATE_SAVE",
    +            "STATE_LOAD",
    +            "PASS",
    +            "SCREEN_RECORDING_TOGGLE",
    +            "PAUSE",
    +            "UNPAUSE",
    +            "PAUSE_TOGGLE",
    +            "PRESS_REWIND_BACK",
    +            "PRESS_REWIND_FORWARD",
    +            "RELEASE_REWIND_BACK",
    +            "RELEASE_REWIND_FORWARD",
    +            "WINDOW_FOCUS",
    +            "WINDOW_UNFOCUS",
    +            "_INTERNAL_RENDERER_FLUSH",
    +            "_INTERNAL_MOUSE",
    +            "_INTERNAL_MARK_TILE",
    +            "SCREENSHOT_RECORD",
    +            "DEBUG_MEMORY_SCROLL_DOWN",
    +            "DEBUG_MEMORY_SCROLL_UP",
    +            "MOD_SHIFT_ON",
    +            "MOD_SHIFT_OFF",
    +            "FULL_SCREEN_TOGGLE",
    +        )[self.event]
    +
    +

    Subclasses

    +
      +
    • pyboy.utils.WindowEventMouse
    • +
    +

    Class variables

    +
    +
    var QUIT
    +
    +
    +
    +
    var PRESS_ARROW_UP
    +
    +
    +
    +
    var PRESS_ARROW_DOWN
    +
    +
    +
    +
    var PRESS_ARROW_RIGHT
    +
    +
    +
    +
    var PRESS_ARROW_LEFT
    +
    +
    +
    +
    var PRESS_BUTTON_A
    +
    +
    +
    +
    var PRESS_BUTTON_B
    +
    +
    +
    +
    var PRESS_BUTTON_SELECT
    +
    +
    +
    +
    var PRESS_BUTTON_START
    +
    +
    +
    +
    var RELEASE_ARROW_UP
    +
    +
    +
    +
    var RELEASE_ARROW_DOWN
    +
    +
    +
    +
    var RELEASE_ARROW_RIGHT
    +
    +
    +
    +
    var RELEASE_ARROW_LEFT
    +
    +
    +
    +
    var RELEASE_BUTTON_A
    +
    +
    +
    +
    var RELEASE_BUTTON_B
    +
    +
    +
    +
    var RELEASE_BUTTON_SELECT
    +
    +
    +
    +
    var RELEASE_BUTTON_START
    +
    +
    +
    +
    var PRESS_SPEED_UP
    +
    +
    +
    +
    var RELEASE_SPEED_UP
    +
    +
    +
    +
    var STATE_SAVE
    +
    +
    +
    +
    var STATE_LOAD
    +
    +
    +
    +
    var PASS
    +
    +
    +
    +
    var SCREEN_RECORDING_TOGGLE
    +
    +
    +
    +
    var PAUSE
    +
    +
    +
    +
    var UNPAUSE
    +
    +
    +
    +
    var PAUSE_TOGGLE
    +
    +
    +
    +
    var PRESS_REWIND_BACK
    +
    +
    +
    +
    var PRESS_REWIND_FORWARD
    +
    +
    +
    +
    var RELEASE_REWIND_BACK
    +
    +
    +
    +
    var RELEASE_REWIND_FORWARD
    +
    +
    +
    +
    var WINDOW_FOCUS
    +
    +
    +
    +
    var WINDOW_UNFOCUS
    +
    +
    +
    +
    var SCREENSHOT_RECORD
    +
    +
    +
    +
    var DEBUG_MEMORY_SCROLL_DOWN
    +
    +
    +
    +
    var DEBUG_MEMORY_SCROLL_UP
    +
    +
    +
    +
    var MOD_SHIFT_ON
    +
    +
    +
    +
    var MOD_SHIFT_OFF
    +
    +
    +
    +
    var FULL_SCREEN_TOGGLE
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + + + \ No newline at end of file