Module pyboy.plugins.game_wrapper_pokemon_gen1

Expand source code
#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#
__pdoc__ = {
    "GameWrapperPokemonGen1.cartridge_title": False,
    "GameWrapperPokemonGen1.post_tick": False,
}

import numpy as np

import pyboy
from pyboy.utils import bcd_to_dec, dec_to_bcd

from .base_plugin import PyBoyGameWrapper
from .game_wrapper_pokemon_gen1_constants import (
    BAG_ITEM_CAPACITY,
    BAG_ITEM_COUNT_ADDRESS,
    BAG_ITEMS_ADDRESS,
    BADGES,
    EVENT_FLAGS,
    EVENT_FLAGS_ADDRESS,
    MAPS,
    POKEMON_MOVES,
    POKEMON_SPECIES,
    POKEMON_SPECIES_NAMES,
    POKEMON_TEXT_DECODING,
    POKEMON_TEXT_ENCODING,
    TRAINER_CLASSES,
    TRAINER_CLASS_NAMES,
    TRAINER_SET_COUNTS,
    PARTY_COUNT_ADDRESS,
    PARTY_SPECIES_ADDRESS,
    PARTY_MONS_ADDRESS,
    PARTY_MON_SIZE,
    PARTY_LENGTH,
    PARTY_SPECIES_SENTINEL,
    NAME_LENGTH,
    TEXT_TERMINATOR,
    PLAYER_NAME_ADDRESS,
    PARTY_MON_OT_ADDRESS,
    PARTY_MON_NICKNAME_ADDRESS,
    DESTINATION_WARP_ID_ADDRESS,
    STATUS_FLAGS3_ADDRESS,
    WARP_DESTINATION_MAP_ADDRESS,
    BIT_WARP_FROM_CURRENT_SCRIPT,
    CURRENT_OPPONENT_ADDRESS,
    BATTLE_TYPE_ADDRESS,
    TRAINER_NUMBER_ADDRESS,
    CURRENT_ENEMY_LEVEL_ADDRESS,
    OPPONENT_ID_OFFSET,
    OBTAINED_BADGES_ADDRESS,
    ITEMS,
    ITEM_QUANTITY_MAX,
    MAX_MONEY,
    PLAYER_MONEY_ADDRESS,
    TRAINER_CLASS_COUNT,
)

logger = pyboy.logging.get_logger(__name__)


class GameWrapperPokemonGen1(PyBoyGameWrapper):
    """
    This class wraps Pokemon Red/Blue, and provides basic access for AIs.

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

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.start_game()
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.start_wild_battle("RATTATA", level=3)
    ```
    """

    cartridge_title = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, game_area_section=(0, 0, 20, 18), game_area_follow_scxy=True, **kwargs)
        self.sprite_offset = 0

    def _skip_dialogue(self, verbose):
        while self.pyboy.tilemap_window[18, 16] != 238:  # The text dialog 'arrow'
            self.pyboy.tick(10, verbose, False)

        self.pyboy.button("a")
        self.pyboy.tick(60, verbose, False)

    def start_game(self, timer_div=None):
        """
        Call this function right after initializing PyBoy. This navigates through the intro and menu to start the game
        at the first playable state.

        The title-screen state is saved, and using `reset_game`, you can get back to this point instantly.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        ```
        """
        verbose = False

        self.pyboy.tick(120, verbose, False)

        for _ in range(8000):
            if self.pyboy.tilemap_window[2, 2] == 141:  # New game
                break
            if self.pyboy.tilemap_window[2, 2] == 130:  # Continue
                raise Exception("Continue game")
            self.pyboy.button("start")
            self.pyboy.tick(10, verbose, False)
        else:
            raise Exception("Didn't find menu")

        self.pyboy.button("start")
        self.pyboy.tick(100, verbose, False)  # Transition to oak talking

        for _ in range(15):
            self._skip_dialogue(verbose)

        if self.pyboy.tilemap_window[2, 4] != 129:  # "B" in Blue
            raise Exception("Not name screen?")

        # Select "BLUE" name
        self.pyboy.button("down")
        self.pyboy.tick(10, verbose, False)
        self.pyboy.button("a")
        self.pyboy.tick(10, verbose, False)

        for _ in range(6):
            self._skip_dialogue(verbose)

        if self.pyboy.tilemap_window[2, 4] != 145:  # "R" in Red
            raise Exception("Not name screen?")

        # Select "RED" name
        self.pyboy.button("down")
        self.pyboy.tick(10, verbose, False)
        self.pyboy.button("a")
        self.pyboy.tick(10, verbose, False)

        for _ in range(8):
            self._skip_dialogue(verbose)

        # Shrinking transition to Mom's house
        self.pyboy.tick(240, verbose, False)

        PyBoyGameWrapper.start_game(self, timer_div=timer_div)

    def _read_u16(self, address):
        return (self.pyboy.memory[address] << 8) | self.pyboy.memory[address + 1]

    def _write_u16(self, address, value):
        self.pyboy.memory[address] = (value >> 8) & 0xFF
        self.pyboy.memory[address + 1] = value & 0xFF

    def _decode_text(self, encoded, skip_invalid=False):
        decoded = []
        for value in encoded:
            if value == TEXT_TERMINATOR:
                break
            elif value in POKEMON_TEXT_DECODING:
                decoded.append(POKEMON_TEXT_DECODING[value])
            elif not skip_invalid:
                raise ValueError(f"Unknown pokered text character: {value:#x}")
        return "".join(decoded)

    def _encode_text(self, text, terminate=False):
        if isinstance(text, str):
            encoded = []
            tokens = sorted(POKEMON_TEXT_ENCODING, key=len, reverse=True)
            offset = 0
            while offset < len(text):
                for token in tokens:
                    if text.startswith(token, offset):
                        if token == "@":
                            raise ValueError("Text cannot contain the terminator")
                        encoded.append(POKEMON_TEXT_ENCODING[token])
                        offset += len(token)
                        break
                else:
                    raise ValueError(f"Unsupported pokered text character: {text[offset]!r}")
        else:
            encoded = list(text)
            if any(not 0 <= value <= 0xFF or value == TEXT_TERMINATOR for value in encoded):
                raise ValueError("Text bytes must be valid pokered text characters")
        return encoded + ([TEXT_TERMINATOR] if terminate else [])

    def _read_name(self, address):
        name = []
        for offset in range(NAME_LENGTH - 1):
            value = self.pyboy.memory[address + offset]
            if value == TEXT_TERMINATOR:
                break
            name.append(value)
        return tuple(name)

    def _write_name(self, address, name):
        if isinstance(name, str):
            encoded = self._encode_text(name.upper())
        else:
            encoded = list(name)
        if len(encoded) > NAME_LENGTH:
            raise ValueError(f"Names must contain at most {NAME_LENGTH - 1} characters")
        encoded.extend([TEXT_TERMINATOR] * (NAME_LENGTH - len(encoded)))
        for offset, value in enumerate(encoded):
            self.pyboy.memory[address + offset] = value

    def _read_party_mon(self, index):
        address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
        return {
            "species": self.pyboy.memory[address],
            "ot_name": self._read_name(PARTY_MON_OT_ADDRESS + index * NAME_LENGTH),
            "nickname": self._read_name(PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH),
            "hp": self._read_u16(address + 1),
            "level": self.pyboy.memory[address + 0x21],
            "status": self.pyboy.memory[address + 4],
            "type1": self.pyboy.memory[address + 5],
            "type2": self.pyboy.memory[address + 6],
            "catch_rate": self.pyboy.memory[address + 7],
            "moves": tuple(self.pyboy.memory[address + 8 + i] for i in range(4)),
            "ot_id": self._read_u16(address + 12),
            "exp": (
                self.pyboy.memory[address + 14] << 16
                | self.pyboy.memory[address + 15] << 8
                | self.pyboy.memory[address + 16]
            ),
            "hp_exp": self._read_u16(address + 17),
            "attack_exp": self._read_u16(address + 19),
            "defense_exp": self._read_u16(address + 21),
            "speed_exp": self._read_u16(address + 23),
            "special_exp": self._read_u16(address + 25),
            "dvs": self._read_u16(address + 27),
            "pp": tuple(self.pyboy.memory[address + 29 + i] for i in range(4)),
            "max_hp": self._read_u16(address + 0x22),
            "attack": self._read_u16(address + 0x24),
            "defense": self._read_u16(address + 0x26),
            "speed": self._read_u16(address + 0x28),
            "special": self._read_u16(address + 0x2A),
        }

    def _write_party_mon(self, index, pokemon):
        address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
        moves = tuple(pokemon.get("moves", (0x21, 0, 0, 0)))
        default_pp = tuple(35 if move else 0 for move in moves)
        pp = tuple(pokemon.get("pp", default_pp))
        if len(moves) != 4 or len(pp) != 4:
            raise ValueError("moves and pp must contain exactly four values")

        level = pokemon["level"]
        self.pyboy.memory[address] = pokemon["species"]
        self._write_u16(address + 1, pokemon["hp"])
        self.pyboy.memory[address + 3] = level
        self.pyboy.memory[address + 4] = pokemon.get("status", 0)
        self.pyboy.memory[address + 5] = pokemon.get("type1", 0)
        self.pyboy.memory[address + 6] = pokemon.get("type2", 0)
        self.pyboy.memory[address + 7] = pokemon.get("catch_rate", 45)
        for i, move in enumerate(moves):
            self.pyboy.memory[address + 8 + i] = move
        self._write_u16(address + 12, pokemon.get("ot_id", 0))
        exp = pokemon.get("exp", level**3)
        self.pyboy.memory[address + 14] = (exp >> 16) & 0xFF
        self.pyboy.memory[address + 15] = (exp >> 8) & 0xFF
        self.pyboy.memory[address + 16] = exp & 0xFF
        for offset, key in (
            (17, "hp_exp"),
            (19, "attack_exp"),
            (21, "defense_exp"),
            (23, "speed_exp"),
            (25, "special_exp"),
        ):
            self._write_u16(address + offset, pokemon.get(key, 0))
        self._write_u16(address + 27, pokemon.get("dvs", 0xFFFF))
        for i, move_pp in enumerate(pp):
            self.pyboy.memory[address + 29 + i] = move_pp
        self.pyboy.memory[address + 0x21] = level
        for offset, key in ((0x22, "max_hp"), (0x24, "attack"), (0x26, "defense"), (0x28, "speed"), (0x2A, "special")):
            self._write_u16(address + offset, pokemon[key])
        self._write_name(
            PARTY_MON_OT_ADDRESS + index * NAME_LENGTH,
            pokemon.get("ot_name", self._read_name(PLAYER_NAME_ADDRESS)),
        )
        self._write_name(
            PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH,
            pokemon.get("nickname", POKEMON_SPECIES_NAMES.get(pokemon["species"], "MON")),
        )

    @property
    def party(self):
        """Return the current party as a list of dictionaries."""

        count = self.pyboy.memory[PARTY_COUNT_ADDRESS]
        if count > PARTY_LENGTH:
            raise ValueError(f"Invalid party count: {count}")
        return [self._read_party_mon(index) for index in range(count)]

    @party.setter
    def party(self, party):
        self.set_party(party)

    def set_party(self, party):
        """
        Replace the party with up to six complete Gen I party records.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> from pyboy.plugins.game_wrapper_pokemon_gen1_constants import POKEMON_SPECIES
        >>> pyboy.game_wrapper.set_party([
        ...     {"species": POKEMON_SPECIES["PIDGEY"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
        ...     {"species": POKEMON_SPECIES["MEWTWO"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
        ... ])
        ```
        """

        party = list(party)
        if len(party) > PARTY_LENGTH:
            raise ValueError("A Pokemon party can contain at most six Pokemon")
        for pokemon in party:
            species = pokemon["species"]
            level = pokemon["level"]
            if not 1 <= species <= 0xBE:
                raise ValueError(f"Invalid Pokemon species ID: {species:#x}")
            if not 1 <= level <= 100:
                raise ValueError(f"Invalid Pokemon level: {level}")

        self.pyboy.memory[PARTY_COUNT_ADDRESS] = len(party)
        for index in range(PARTY_LENGTH + 1):
            self.pyboy.memory[PARTY_SPECIES_ADDRESS + index] = (
                party[index]["species"] if index < len(party) else PARTY_SPECIES_SENTINEL
            )
        for index, pokemon in enumerate(party):
            self._write_party_mon(index, pokemon)

        for index in range(len(party), PARTY_LENGTH):
            address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
            for offset in range(PARTY_MON_SIZE):
                self.pyboy.memory[address + offset] = 0
            self._write_name(PARTY_MON_OT_ADDRESS + index * NAME_LENGTH, ())
            self._write_name(PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH, ())

    def add_pokemon(self, species, level=2, moves=None, **kwargs):
        """
        Add a battle-ready Pokemon using a species ID from pokered's constants.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.party[0]["species"]
        84
        ```
        """

        if len(self.party) == PARTY_LENGTH:
            raise ValueError("The party is full")
        if isinstance(species, str):
            species = POKEMON_SPECIES[species.upper()]
        moves = tuple(POKEMON_MOVES[move.upper()] if isinstance(move, str) else move for move in (moves or ()))
        if len(moves) > 4:
            raise ValueError("moves must contain at most four move IDs")
        moves += (0,) * (4 - len(moves))
        default_pp = tuple(35 if move else 0 for move in moves)
        max_hp = kwargs.pop("max_hp", level * 2 + 10)
        stats = {
            "attack": level + 5,
            "defense": level + 5,
            "speed": level + 5,
            "special": level + 5,
        }
        stats.update(kwargs.pop("stats", {}))
        pokemon = {
            "species": species,
            "level": level,
            "hp": kwargs.pop("hp", max_hp),
            "max_hp": max_hp,
            "moves": moves,
            "pp": kwargs.pop("pp", default_pp),
            **stats,
            **kwargs,
        }
        self.set_party(self.party + [pokemon])

    def remove_pokemon(self, index):
        """
        Remove a Pokemon from the party by its zero-based index.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.remove_pokemon(0)
        ```
        """

        party = self.party
        if not 0 <= index < len(party):
            raise IndexError(f"Invalid party index: {index} of {len(party)-1}")
        del party[index]
        self.set_party(party)

    @property
    def money(self):
        """Return the player's money, from 0 through 999999."""

        value = int.from_bytes(self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3], "big")
        return bcd_to_dec(value, byte_width=3, byteorder="big")

    def set_money(self, amount):
        """
        Set the player's money.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_money(999999)
        ```
        """

        if not isinstance(amount, int) or not 0 <= amount <= MAX_MONEY:
            raise ValueError(f"Money must be an integer from 0 to {MAX_MONEY}")
        value = dec_to_bcd(amount, byte_width=3, byteorder="big")
        self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3] = value.to_bytes(3, "big")

    def _item_id(self, item):
        if isinstance(item, str):
            try:
                item = ITEMS[item.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown item: {item}") from error
        if not isinstance(item, int) or not 1 <= item <= 0xFE:
            raise ValueError(f"Invalid item ID: {item}")
        return item

    @property
    def inventory(self):
        """Return the bag contents as a list of item and quantity dictionaries."""

        count = self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS]
        if count > BAG_ITEM_CAPACITY:
            raise ValueError(f"Invalid bag item count: {count}")
        return [
            {
                "item": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index],
                "quantity": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index + 1],
            }
            for index in range(count)
        ]

    def set_inventory(self, inventory, force=False):
        """
        Replace the bag contents with item and quantity pairs.

        Items may be pokered IDs or names from ``ITEMS``. Quantities range
        from 1 to 99.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_inventory({"POTION": 20, "MASTER_BALL": 1})
        ```
        """

        if isinstance(inventory, dict):
            inventory = inventory.items()
        entries = []
        seen = set()
        for item, quantity in inventory:
            item = self._item_id(item)
            if item in seen:
                raise ValueError(f"Duplicate item: {item:#x}")
            if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
                raise ValueError(f"Item quantity must be an integer from 1 to {ITEM_QUANTITY_MAX}")
            seen.add(item)
            entries.append((item, quantity))
        if len(entries) > BAG_ITEM_CAPACITY:
            raise ValueError(f"The bag can contain at most {BAG_ITEM_CAPACITY} item types")

        self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS] = len(entries)
        for index in range(BAG_ITEM_CAPACITY):
            address = BAG_ITEMS_ADDRESS + 2 * index
            if index < len(entries):
                self.pyboy.memory[address] = entries[index][0]
                self.pyboy.memory[address + 1] = entries[index][1]
            else:
                self.pyboy.memory[address] = 0xFF
                self.pyboy.memory[address + 1] = 0

    def set_item(self, item, quantity, force=False):
        """Set one bag item's quantity, removing it when ``quantity`` is zero."""

        item = self._item_id(item)
        if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
            raise ValueError(f"Item quantity must be an integer from 0 to {ITEM_QUANTITY_MAX}")
        inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
        if quantity:
            inventory[item] = quantity
        else:
            inventory.pop(item, None)
        preserve_forced_quantities = any(
            item_quantity == 0 or item_quantity > ITEM_QUANTITY_MAX for item_quantity in inventory.values()
        )
        self.set_inventory(inventory, force=force or preserve_forced_quantities)

    def remove_item(self, item, quantity=1):
        """Remove a quantity of an item from the bag."""

        item = self._item_id(item)
        inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
        if item not in inventory or not isinstance(quantity, int) or not 1 <= quantity <= inventory[item]:
            raise ValueError(f"Cannot remove quantity {quantity} of item {item:#x}")
        self.set_item(item, inventory[item] - quantity, force=inventory[item] > ITEM_QUANTITY_MAX)

    def start_wild_battle(self, species, level, force=False):
        """
        Schedule a wild battle against a species at the given level.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.start_wild_battle("PIKACHU", level=5)
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(species, str):
            try:
                species = POKEMON_SPECIES[species.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown Pokemon species: {species}") from error
        if not force:
            if not isinstance(species, int) or not 1 <= species <= 0xBE:
                raise ValueError(f"Invalid Pokemon species ID: {species}")
            if not isinstance(level, int) or not 1 <= level <= 100:
                raise ValueError(f"Invalid Pokemon level: {level}")

        self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = species
        self.pyboy.memory[CURRENT_ENEMY_LEVEL_ADDRESS] = level
        self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0

    def start_trainer_battle(self, trainer_class, trainer_set=1, force=False):
        """
        Schedule a trainer battle by class and party-set IDs.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.start_trainer_battle("PROF_OAK", trainer_set=1)
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(trainer_class, str):
            try:
                trainer_class = TRAINER_CLASSES[trainer_class.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown trainer class: {trainer_class}") from error
        if not force:
            if not isinstance(trainer_class, int) or not 1 <= trainer_class <= TRAINER_CLASS_COUNT:
                raise ValueError(f"Invalid trainer class ID: {trainer_class}")
            trainer_class_name = TRAINER_CLASS_NAMES[trainer_class]
            trainer_set_count = TRAINER_SET_COUNTS[trainer_class_name]
            if not isinstance(trainer_set, int) or not 1 <= trainer_set <= trainer_set_count:
                raise ValueError(f"Invalid trainer set ID: {trainer_set}")

        opponent = OPPONENT_ID_OFFSET + trainer_class
        self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = opponent
        self.pyboy.memory[TRAINER_NUMBER_ADDRESS] = trainer_set
        self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0

    def warp(self, destination):
        """
        Schedule a script warp to a map identified by name or ID.

        The map transition is performed by pokered on the next emulator tick.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.warp("viridian_pokecenter")
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(destination, str):
            try:
                map_id = MAPS[destination]
            except KeyError as error:
                raise ValueError(f"Unknown map name: {destination}") from error
        else:
            map_id = destination
        if not 0 <= map_id <= 0xFF:
            raise ValueError(f"Invalid map ID: {map_id:#x}")
        self.pyboy.memory[WARP_DESTINATION_MAP_ADDRESS] = map_id
        self.pyboy.memory[DESTINATION_WARP_ID_ADDRESS] = 0
        self.pyboy.memory[STATUS_FLAGS3_ADDRESS] |= 1 << BIT_WARP_FROM_CURRENT_SCRIPT

    def set_event_flag(self, event):
        """
        Set a story event flag by name or numeric pokered event ID.

        Gym victory event flags are separate from the badges owned by the
        player. Use :meth:`set_badge` to grant a badge.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_event_flag("got_pokedex")
        ```
        """
        event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
        if not isinstance(event, int):
            raise ValueError(f"Invalid event flag: {event}")
        address = EVENT_FLAGS_ADDRESS + event // 8
        self.pyboy.memory[address] |= 1 << (event % 8)

    def reset_event_flag(self, event):
        """
        Reset a story event flag by name or numeric pokered event ID.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.reset_event_flag("got_pokedex")
        ```
        """
        event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
        if not isinstance(event, int):
            raise ValueError(f"Invalid event flag: {event}")
        address = EVENT_FLAGS_ADDRESS + event // 8
        self.pyboy.memory[address] &= ~(1 << (event % 8))

    def set_badge(self, badge):
        """
        Grant a badge by name or numeric bit position.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_badge("boulder")
        ```
        """
        badge = BADGES.get(badge, badge)
        if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
            raise ValueError(f"Invalid badge: {badge}")
        self.pyboy.memory[OBTAINED_BADGES_ADDRESS] |= 1 << badge

    def reset_badge(self, badge):
        """
        Remove a badge by name or numeric bit position.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.reset_badge("boulder")
        ```
        """
        badge = BADGES.get(badge, badge)
        if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
            raise ValueError(f"Invalid badge: {badge}")
        self.pyboy.memory[OBTAINED_BADGES_ADDRESS] &= ~(1 << badge)

    def enabled(self):
        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.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(), BUT ONLY FOR BACKGROUND TILEMAP, SO NPC ARE SKIPPED
        ((scx, scy), (wx, wy)) = self.pyboy.screen.get_tilemap_position()
        tilemap = np.array(self.pyboy.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.memory[0xD530] + (self.pyboy.memory[0xD531] << 8)
        tileset_type = self.pyboy.memory[0xFFD7]
        if tileset_type > 0:
            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.memory[collision_ptr + i]
            if tile_index == 0xFF:
                break
            else:
                walkable_tiles_indexes.append(tile_index + 0x100)
        screen_tiles = self._get_screen_background_tilemap()
        bottom_left_screen_tiles = screen_tiles[1 : 1 + screen_tiles.shape[0] : 2, ::2]
        walkable_matrix = np.isin(bottom_left_screen_tiles, walkable_tiles_indexes).astype(np.uint8)
        return walkable_matrix

    def game_area_collision(self):
        width = self.game_area_section[2]
        height = self.game_area_section[3]
        game_area = np.ndarray(shape=(height, width), dtype=np.uint32)
        _collision = self._get_screen_walkable_matrix()
        for i in range(height // 2):
            for j in range(width // 2):
                game_area[i * 2][j * 2 : j * 2 + 2] = _collision[i][j]
                game_area[i * 2 + 1][j * 2 : j * 2 + 2] = _collision[i][j]
        return game_area

    def __repr__(self):
        return "Pokemon Gen 1:\n" + super().__repr__()

Classes

class GameWrapperPokemonGen1 (*args, **kwargs)

This class wraps Pokemon Red/Blue, and provides basic access for AIs.

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

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.start_game()
>>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
>>> pyboy.game_wrapper.start_wild_battle("RATTATA", level=3)
Expand source code
class GameWrapperPokemonGen1(PyBoyGameWrapper):
    """
    This class wraps Pokemon Red/Blue, and provides basic access for AIs.

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

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.start_game()
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.start_wild_battle("RATTATA", level=3)
    ```
    """

    cartridge_title = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, game_area_section=(0, 0, 20, 18), game_area_follow_scxy=True, **kwargs)
        self.sprite_offset = 0

    def _skip_dialogue(self, verbose):
        while self.pyboy.tilemap_window[18, 16] != 238:  # The text dialog 'arrow'
            self.pyboy.tick(10, verbose, False)

        self.pyboy.button("a")
        self.pyboy.tick(60, verbose, False)

    def start_game(self, timer_div=None):
        """
        Call this function right after initializing PyBoy. This navigates through the intro and menu to start the game
        at the first playable state.

        The title-screen state is saved, and using `reset_game`, you can get back to this point instantly.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        ```
        """
        verbose = False

        self.pyboy.tick(120, verbose, False)

        for _ in range(8000):
            if self.pyboy.tilemap_window[2, 2] == 141:  # New game
                break
            if self.pyboy.tilemap_window[2, 2] == 130:  # Continue
                raise Exception("Continue game")
            self.pyboy.button("start")
            self.pyboy.tick(10, verbose, False)
        else:
            raise Exception("Didn't find menu")

        self.pyboy.button("start")
        self.pyboy.tick(100, verbose, False)  # Transition to oak talking

        for _ in range(15):
            self._skip_dialogue(verbose)

        if self.pyboy.tilemap_window[2, 4] != 129:  # "B" in Blue
            raise Exception("Not name screen?")

        # Select "BLUE" name
        self.pyboy.button("down")
        self.pyboy.tick(10, verbose, False)
        self.pyboy.button("a")
        self.pyboy.tick(10, verbose, False)

        for _ in range(6):
            self._skip_dialogue(verbose)

        if self.pyboy.tilemap_window[2, 4] != 145:  # "R" in Red
            raise Exception("Not name screen?")

        # Select "RED" name
        self.pyboy.button("down")
        self.pyboy.tick(10, verbose, False)
        self.pyboy.button("a")
        self.pyboy.tick(10, verbose, False)

        for _ in range(8):
            self._skip_dialogue(verbose)

        # Shrinking transition to Mom's house
        self.pyboy.tick(240, verbose, False)

        PyBoyGameWrapper.start_game(self, timer_div=timer_div)

    def _read_u16(self, address):
        return (self.pyboy.memory[address] << 8) | self.pyboy.memory[address + 1]

    def _write_u16(self, address, value):
        self.pyboy.memory[address] = (value >> 8) & 0xFF
        self.pyboy.memory[address + 1] = value & 0xFF

    def _decode_text(self, encoded, skip_invalid=False):
        decoded = []
        for value in encoded:
            if value == TEXT_TERMINATOR:
                break
            elif value in POKEMON_TEXT_DECODING:
                decoded.append(POKEMON_TEXT_DECODING[value])
            elif not skip_invalid:
                raise ValueError(f"Unknown pokered text character: {value:#x}")
        return "".join(decoded)

    def _encode_text(self, text, terminate=False):
        if isinstance(text, str):
            encoded = []
            tokens = sorted(POKEMON_TEXT_ENCODING, key=len, reverse=True)
            offset = 0
            while offset < len(text):
                for token in tokens:
                    if text.startswith(token, offset):
                        if token == "@":
                            raise ValueError("Text cannot contain the terminator")
                        encoded.append(POKEMON_TEXT_ENCODING[token])
                        offset += len(token)
                        break
                else:
                    raise ValueError(f"Unsupported pokered text character: {text[offset]!r}")
        else:
            encoded = list(text)
            if any(not 0 <= value <= 0xFF or value == TEXT_TERMINATOR for value in encoded):
                raise ValueError("Text bytes must be valid pokered text characters")
        return encoded + ([TEXT_TERMINATOR] if terminate else [])

    def _read_name(self, address):
        name = []
        for offset in range(NAME_LENGTH - 1):
            value = self.pyboy.memory[address + offset]
            if value == TEXT_TERMINATOR:
                break
            name.append(value)
        return tuple(name)

    def _write_name(self, address, name):
        if isinstance(name, str):
            encoded = self._encode_text(name.upper())
        else:
            encoded = list(name)
        if len(encoded) > NAME_LENGTH:
            raise ValueError(f"Names must contain at most {NAME_LENGTH - 1} characters")
        encoded.extend([TEXT_TERMINATOR] * (NAME_LENGTH - len(encoded)))
        for offset, value in enumerate(encoded):
            self.pyboy.memory[address + offset] = value

    def _read_party_mon(self, index):
        address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
        return {
            "species": self.pyboy.memory[address],
            "ot_name": self._read_name(PARTY_MON_OT_ADDRESS + index * NAME_LENGTH),
            "nickname": self._read_name(PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH),
            "hp": self._read_u16(address + 1),
            "level": self.pyboy.memory[address + 0x21],
            "status": self.pyboy.memory[address + 4],
            "type1": self.pyboy.memory[address + 5],
            "type2": self.pyboy.memory[address + 6],
            "catch_rate": self.pyboy.memory[address + 7],
            "moves": tuple(self.pyboy.memory[address + 8 + i] for i in range(4)),
            "ot_id": self._read_u16(address + 12),
            "exp": (
                self.pyboy.memory[address + 14] << 16
                | self.pyboy.memory[address + 15] << 8
                | self.pyboy.memory[address + 16]
            ),
            "hp_exp": self._read_u16(address + 17),
            "attack_exp": self._read_u16(address + 19),
            "defense_exp": self._read_u16(address + 21),
            "speed_exp": self._read_u16(address + 23),
            "special_exp": self._read_u16(address + 25),
            "dvs": self._read_u16(address + 27),
            "pp": tuple(self.pyboy.memory[address + 29 + i] for i in range(4)),
            "max_hp": self._read_u16(address + 0x22),
            "attack": self._read_u16(address + 0x24),
            "defense": self._read_u16(address + 0x26),
            "speed": self._read_u16(address + 0x28),
            "special": self._read_u16(address + 0x2A),
        }

    def _write_party_mon(self, index, pokemon):
        address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
        moves = tuple(pokemon.get("moves", (0x21, 0, 0, 0)))
        default_pp = tuple(35 if move else 0 for move in moves)
        pp = tuple(pokemon.get("pp", default_pp))
        if len(moves) != 4 or len(pp) != 4:
            raise ValueError("moves and pp must contain exactly four values")

        level = pokemon["level"]
        self.pyboy.memory[address] = pokemon["species"]
        self._write_u16(address + 1, pokemon["hp"])
        self.pyboy.memory[address + 3] = level
        self.pyboy.memory[address + 4] = pokemon.get("status", 0)
        self.pyboy.memory[address + 5] = pokemon.get("type1", 0)
        self.pyboy.memory[address + 6] = pokemon.get("type2", 0)
        self.pyboy.memory[address + 7] = pokemon.get("catch_rate", 45)
        for i, move in enumerate(moves):
            self.pyboy.memory[address + 8 + i] = move
        self._write_u16(address + 12, pokemon.get("ot_id", 0))
        exp = pokemon.get("exp", level**3)
        self.pyboy.memory[address + 14] = (exp >> 16) & 0xFF
        self.pyboy.memory[address + 15] = (exp >> 8) & 0xFF
        self.pyboy.memory[address + 16] = exp & 0xFF
        for offset, key in (
            (17, "hp_exp"),
            (19, "attack_exp"),
            (21, "defense_exp"),
            (23, "speed_exp"),
            (25, "special_exp"),
        ):
            self._write_u16(address + offset, pokemon.get(key, 0))
        self._write_u16(address + 27, pokemon.get("dvs", 0xFFFF))
        for i, move_pp in enumerate(pp):
            self.pyboy.memory[address + 29 + i] = move_pp
        self.pyboy.memory[address + 0x21] = level
        for offset, key in ((0x22, "max_hp"), (0x24, "attack"), (0x26, "defense"), (0x28, "speed"), (0x2A, "special")):
            self._write_u16(address + offset, pokemon[key])
        self._write_name(
            PARTY_MON_OT_ADDRESS + index * NAME_LENGTH,
            pokemon.get("ot_name", self._read_name(PLAYER_NAME_ADDRESS)),
        )
        self._write_name(
            PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH,
            pokemon.get("nickname", POKEMON_SPECIES_NAMES.get(pokemon["species"], "MON")),
        )

    @property
    def party(self):
        """Return the current party as a list of dictionaries."""

        count = self.pyboy.memory[PARTY_COUNT_ADDRESS]
        if count > PARTY_LENGTH:
            raise ValueError(f"Invalid party count: {count}")
        return [self._read_party_mon(index) for index in range(count)]

    @party.setter
    def party(self, party):
        self.set_party(party)

    def set_party(self, party):
        """
        Replace the party with up to six complete Gen I party records.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> from pyboy.plugins.game_wrapper_pokemon_gen1_constants import POKEMON_SPECIES
        >>> pyboy.game_wrapper.set_party([
        ...     {"species": POKEMON_SPECIES["PIDGEY"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
        ...     {"species": POKEMON_SPECIES["MEWTWO"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
        ... ])
        ```
        """

        party = list(party)
        if len(party) > PARTY_LENGTH:
            raise ValueError("A Pokemon party can contain at most six Pokemon")
        for pokemon in party:
            species = pokemon["species"]
            level = pokemon["level"]
            if not 1 <= species <= 0xBE:
                raise ValueError(f"Invalid Pokemon species ID: {species:#x}")
            if not 1 <= level <= 100:
                raise ValueError(f"Invalid Pokemon level: {level}")

        self.pyboy.memory[PARTY_COUNT_ADDRESS] = len(party)
        for index in range(PARTY_LENGTH + 1):
            self.pyboy.memory[PARTY_SPECIES_ADDRESS + index] = (
                party[index]["species"] if index < len(party) else PARTY_SPECIES_SENTINEL
            )
        for index, pokemon in enumerate(party):
            self._write_party_mon(index, pokemon)

        for index in range(len(party), PARTY_LENGTH):
            address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
            for offset in range(PARTY_MON_SIZE):
                self.pyboy.memory[address + offset] = 0
            self._write_name(PARTY_MON_OT_ADDRESS + index * NAME_LENGTH, ())
            self._write_name(PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH, ())

    def add_pokemon(self, species, level=2, moves=None, **kwargs):
        """
        Add a battle-ready Pokemon using a species ID from pokered's constants.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.party[0]["species"]
        84
        ```
        """

        if len(self.party) == PARTY_LENGTH:
            raise ValueError("The party is full")
        if isinstance(species, str):
            species = POKEMON_SPECIES[species.upper()]
        moves = tuple(POKEMON_MOVES[move.upper()] if isinstance(move, str) else move for move in (moves or ()))
        if len(moves) > 4:
            raise ValueError("moves must contain at most four move IDs")
        moves += (0,) * (4 - len(moves))
        default_pp = tuple(35 if move else 0 for move in moves)
        max_hp = kwargs.pop("max_hp", level * 2 + 10)
        stats = {
            "attack": level + 5,
            "defense": level + 5,
            "speed": level + 5,
            "special": level + 5,
        }
        stats.update(kwargs.pop("stats", {}))
        pokemon = {
            "species": species,
            "level": level,
            "hp": kwargs.pop("hp", max_hp),
            "max_hp": max_hp,
            "moves": moves,
            "pp": kwargs.pop("pp", default_pp),
            **stats,
            **kwargs,
        }
        self.set_party(self.party + [pokemon])

    def remove_pokemon(self, index):
        """
        Remove a Pokemon from the party by its zero-based index.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.remove_pokemon(0)
        ```
        """

        party = self.party
        if not 0 <= index < len(party):
            raise IndexError(f"Invalid party index: {index} of {len(party)-1}")
        del party[index]
        self.set_party(party)

    @property
    def money(self):
        """Return the player's money, from 0 through 999999."""

        value = int.from_bytes(self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3], "big")
        return bcd_to_dec(value, byte_width=3, byteorder="big")

    def set_money(self, amount):
        """
        Set the player's money.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_money(999999)
        ```
        """

        if not isinstance(amount, int) or not 0 <= amount <= MAX_MONEY:
            raise ValueError(f"Money must be an integer from 0 to {MAX_MONEY}")
        value = dec_to_bcd(amount, byte_width=3, byteorder="big")
        self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3] = value.to_bytes(3, "big")

    def _item_id(self, item):
        if isinstance(item, str):
            try:
                item = ITEMS[item.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown item: {item}") from error
        if not isinstance(item, int) or not 1 <= item <= 0xFE:
            raise ValueError(f"Invalid item ID: {item}")
        return item

    @property
    def inventory(self):
        """Return the bag contents as a list of item and quantity dictionaries."""

        count = self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS]
        if count > BAG_ITEM_CAPACITY:
            raise ValueError(f"Invalid bag item count: {count}")
        return [
            {
                "item": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index],
                "quantity": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index + 1],
            }
            for index in range(count)
        ]

    def set_inventory(self, inventory, force=False):
        """
        Replace the bag contents with item and quantity pairs.

        Items may be pokered IDs or names from ``ITEMS``. Quantities range
        from 1 to 99.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_inventory({"POTION": 20, "MASTER_BALL": 1})
        ```
        """

        if isinstance(inventory, dict):
            inventory = inventory.items()
        entries = []
        seen = set()
        for item, quantity in inventory:
            item = self._item_id(item)
            if item in seen:
                raise ValueError(f"Duplicate item: {item:#x}")
            if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
                raise ValueError(f"Item quantity must be an integer from 1 to {ITEM_QUANTITY_MAX}")
            seen.add(item)
            entries.append((item, quantity))
        if len(entries) > BAG_ITEM_CAPACITY:
            raise ValueError(f"The bag can contain at most {BAG_ITEM_CAPACITY} item types")

        self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS] = len(entries)
        for index in range(BAG_ITEM_CAPACITY):
            address = BAG_ITEMS_ADDRESS + 2 * index
            if index < len(entries):
                self.pyboy.memory[address] = entries[index][0]
                self.pyboy.memory[address + 1] = entries[index][1]
            else:
                self.pyboy.memory[address] = 0xFF
                self.pyboy.memory[address + 1] = 0

    def set_item(self, item, quantity, force=False):
        """Set one bag item's quantity, removing it when ``quantity`` is zero."""

        item = self._item_id(item)
        if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
            raise ValueError(f"Item quantity must be an integer from 0 to {ITEM_QUANTITY_MAX}")
        inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
        if quantity:
            inventory[item] = quantity
        else:
            inventory.pop(item, None)
        preserve_forced_quantities = any(
            item_quantity == 0 or item_quantity > ITEM_QUANTITY_MAX for item_quantity in inventory.values()
        )
        self.set_inventory(inventory, force=force or preserve_forced_quantities)

    def remove_item(self, item, quantity=1):
        """Remove a quantity of an item from the bag."""

        item = self._item_id(item)
        inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
        if item not in inventory or not isinstance(quantity, int) or not 1 <= quantity <= inventory[item]:
            raise ValueError(f"Cannot remove quantity {quantity} of item {item:#x}")
        self.set_item(item, inventory[item] - quantity, force=inventory[item] > ITEM_QUANTITY_MAX)

    def start_wild_battle(self, species, level, force=False):
        """
        Schedule a wild battle against a species at the given level.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.start_wild_battle("PIKACHU", level=5)
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(species, str):
            try:
                species = POKEMON_SPECIES[species.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown Pokemon species: {species}") from error
        if not force:
            if not isinstance(species, int) or not 1 <= species <= 0xBE:
                raise ValueError(f"Invalid Pokemon species ID: {species}")
            if not isinstance(level, int) or not 1 <= level <= 100:
                raise ValueError(f"Invalid Pokemon level: {level}")

        self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = species
        self.pyboy.memory[CURRENT_ENEMY_LEVEL_ADDRESS] = level
        self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0

    def start_trainer_battle(self, trainer_class, trainer_set=1, force=False):
        """
        Schedule a trainer battle by class and party-set IDs.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.start_game()
        >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
        >>> pyboy.game_wrapper.start_trainer_battle("PROF_OAK", trainer_set=1)
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(trainer_class, str):
            try:
                trainer_class = TRAINER_CLASSES[trainer_class.upper()]
            except KeyError as error:
                raise ValueError(f"Unknown trainer class: {trainer_class}") from error
        if not force:
            if not isinstance(trainer_class, int) or not 1 <= trainer_class <= TRAINER_CLASS_COUNT:
                raise ValueError(f"Invalid trainer class ID: {trainer_class}")
            trainer_class_name = TRAINER_CLASS_NAMES[trainer_class]
            trainer_set_count = TRAINER_SET_COUNTS[trainer_class_name]
            if not isinstance(trainer_set, int) or not 1 <= trainer_set <= trainer_set_count:
                raise ValueError(f"Invalid trainer set ID: {trainer_set}")

        opponent = OPPONENT_ID_OFFSET + trainer_class
        self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = opponent
        self.pyboy.memory[TRAINER_NUMBER_ADDRESS] = trainer_set
        self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0

    def warp(self, destination):
        """
        Schedule a script warp to a map identified by name or ID.

        The map transition is performed by pokered on the next emulator tick.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.warp("viridian_pokecenter")
        >>> pyboy.tick(1)
        True
        ```
        """

        if isinstance(destination, str):
            try:
                map_id = MAPS[destination]
            except KeyError as error:
                raise ValueError(f"Unknown map name: {destination}") from error
        else:
            map_id = destination
        if not 0 <= map_id <= 0xFF:
            raise ValueError(f"Invalid map ID: {map_id:#x}")
        self.pyboy.memory[WARP_DESTINATION_MAP_ADDRESS] = map_id
        self.pyboy.memory[DESTINATION_WARP_ID_ADDRESS] = 0
        self.pyboy.memory[STATUS_FLAGS3_ADDRESS] |= 1 << BIT_WARP_FROM_CURRENT_SCRIPT

    def set_event_flag(self, event):
        """
        Set a story event flag by name or numeric pokered event ID.

        Gym victory event flags are separate from the badges owned by the
        player. Use :meth:`set_badge` to grant a badge.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_event_flag("got_pokedex")
        ```
        """
        event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
        if not isinstance(event, int):
            raise ValueError(f"Invalid event flag: {event}")
        address = EVENT_FLAGS_ADDRESS + event // 8
        self.pyboy.memory[address] |= 1 << (event % 8)

    def reset_event_flag(self, event):
        """
        Reset a story event flag by name or numeric pokered event ID.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.reset_event_flag("got_pokedex")
        ```
        """
        event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
        if not isinstance(event, int):
            raise ValueError(f"Invalid event flag: {event}")
        address = EVENT_FLAGS_ADDRESS + event // 8
        self.pyboy.memory[address] &= ~(1 << (event % 8))

    def set_badge(self, badge):
        """
        Grant a badge by name or numeric bit position.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.set_badge("boulder")
        ```
        """
        badge = BADGES.get(badge, badge)
        if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
            raise ValueError(f"Invalid badge: {badge}")
        self.pyboy.memory[OBTAINED_BADGES_ADDRESS] |= 1 << badge

    def reset_badge(self, badge):
        """
        Remove a badge by name or numeric bit position.

        Example:
        ```python
        >>> pyboy = PyBoy(pokemon_blue_rom)
        >>> pyboy.game_wrapper.reset_badge("boulder")
        ```
        """
        badge = BADGES.get(badge, badge)
        if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
            raise ValueError(f"Invalid badge: {badge}")
        self.pyboy.memory[OBTAINED_BADGES_ADDRESS] &= ~(1 << badge)

    def enabled(self):
        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.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(), BUT ONLY FOR BACKGROUND TILEMAP, SO NPC ARE SKIPPED
        ((scx, scy), (wx, wy)) = self.pyboy.screen.get_tilemap_position()
        tilemap = np.array(self.pyboy.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.memory[0xD530] + (self.pyboy.memory[0xD531] << 8)
        tileset_type = self.pyboy.memory[0xFFD7]
        if tileset_type > 0:
            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.memory[collision_ptr + i]
            if tile_index == 0xFF:
                break
            else:
                walkable_tiles_indexes.append(tile_index + 0x100)
        screen_tiles = self._get_screen_background_tilemap()
        bottom_left_screen_tiles = screen_tiles[1 : 1 + screen_tiles.shape[0] : 2, ::2]
        walkable_matrix = np.isin(bottom_left_screen_tiles, walkable_tiles_indexes).astype(np.uint8)
        return walkable_matrix

    def game_area_collision(self):
        width = self.game_area_section[2]
        height = self.game_area_section[3]
        game_area = np.ndarray(shape=(height, width), dtype=np.uint32)
        _collision = self._get_screen_walkable_matrix()
        for i in range(height // 2):
            for j in range(width // 2):
                game_area[i * 2][j * 2 : j * 2 + 2] = _collision[i][j]
                game_area[i * 2 + 1][j * 2 : j * 2 + 2] = _collision[i][j]
        return game_area

    def __repr__(self):
        return "Pokemon Gen 1:\n" + super().__repr__()

Ancestors

Instance variables

var party

Return the current party as a list of dictionaries.

Expand source code
@property
def party(self):
    """Return the current party as a list of dictionaries."""

    count = self.pyboy.memory[PARTY_COUNT_ADDRESS]
    if count > PARTY_LENGTH:
        raise ValueError(f"Invalid party count: {count}")
    return [self._read_party_mon(index) for index in range(count)]
var money

Return the player's money, from 0 through 999999.

Expand source code
@property
def money(self):
    """Return the player's money, from 0 through 999999."""

    value = int.from_bytes(self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3], "big")
    return bcd_to_dec(value, byte_width=3, byteorder="big")
var inventory

Return the bag contents as a list of item and quantity dictionaries.

Expand source code
@property
def inventory(self):
    """Return the bag contents as a list of item and quantity dictionaries."""

    count = self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS]
    if count > BAG_ITEM_CAPACITY:
        raise ValueError(f"Invalid bag item count: {count}")
    return [
        {
            "item": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index],
            "quantity": self.pyboy.memory[BAG_ITEMS_ADDRESS + 2 * index + 1],
        }
        for index in range(count)
    ]

Methods

def start_game(self, timer_div=None)

Call this function right after initializing PyBoy. This navigates through the intro and menu to start the game at the first playable state.

The title-screen state is saved, and using reset_game, you can get back to this point instantly.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.start_game()
Expand source code
def start_game(self, timer_div=None):
    """
    Call this function right after initializing PyBoy. This navigates through the intro and menu to start the game
    at the first playable state.

    The title-screen state is saved, and using `reset_game`, you can get back to this point instantly.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.start_game()
    ```
    """
    verbose = False

    self.pyboy.tick(120, verbose, False)

    for _ in range(8000):
        if self.pyboy.tilemap_window[2, 2] == 141:  # New game
            break
        if self.pyboy.tilemap_window[2, 2] == 130:  # Continue
            raise Exception("Continue game")
        self.pyboy.button("start")
        self.pyboy.tick(10, verbose, False)
    else:
        raise Exception("Didn't find menu")

    self.pyboy.button("start")
    self.pyboy.tick(100, verbose, False)  # Transition to oak talking

    for _ in range(15):
        self._skip_dialogue(verbose)

    if self.pyboy.tilemap_window[2, 4] != 129:  # "B" in Blue
        raise Exception("Not name screen?")

    # Select "BLUE" name
    self.pyboy.button("down")
    self.pyboy.tick(10, verbose, False)
    self.pyboy.button("a")
    self.pyboy.tick(10, verbose, False)

    for _ in range(6):
        self._skip_dialogue(verbose)

    if self.pyboy.tilemap_window[2, 4] != 145:  # "R" in Red
        raise Exception("Not name screen?")

    # Select "RED" name
    self.pyboy.button("down")
    self.pyboy.tick(10, verbose, False)
    self.pyboy.button("a")
    self.pyboy.tick(10, verbose, False)

    for _ in range(8):
        self._skip_dialogue(verbose)

    # Shrinking transition to Mom's house
    self.pyboy.tick(240, verbose, False)

    PyBoyGameWrapper.start_game(self, timer_div=timer_div)
def set_party(self, party)

Replace the party with up to six complete Gen I party records.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> from pyboy.plugins.game_wrapper_pokemon_gen1_constants import POKEMON_SPECIES
>>> pyboy.game_wrapper.set_party([
...     {"species": POKEMON_SPECIES["PIDGEY"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
...     {"species": POKEMON_SPECIES["MEWTWO"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
... ])
Expand source code
def set_party(self, party):
    """
    Replace the party with up to six complete Gen I party records.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> from pyboy.plugins.game_wrapper_pokemon_gen1_constants import POKEMON_SPECIES
    >>> pyboy.game_wrapper.set_party([
    ...     {"species": POKEMON_SPECIES["PIDGEY"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
    ...     {"species": POKEMON_SPECIES["MEWTWO"], "level": 5, "hp": 20, "max_hp": 20, "attack": 10, "defense": 10, "speed": 10, "special": 10},
    ... ])
    ```
    """

    party = list(party)
    if len(party) > PARTY_LENGTH:
        raise ValueError("A Pokemon party can contain at most six Pokemon")
    for pokemon in party:
        species = pokemon["species"]
        level = pokemon["level"]
        if not 1 <= species <= 0xBE:
            raise ValueError(f"Invalid Pokemon species ID: {species:#x}")
        if not 1 <= level <= 100:
            raise ValueError(f"Invalid Pokemon level: {level}")

    self.pyboy.memory[PARTY_COUNT_ADDRESS] = len(party)
    for index in range(PARTY_LENGTH + 1):
        self.pyboy.memory[PARTY_SPECIES_ADDRESS + index] = (
            party[index]["species"] if index < len(party) else PARTY_SPECIES_SENTINEL
        )
    for index, pokemon in enumerate(party):
        self._write_party_mon(index, pokemon)

    for index in range(len(party), PARTY_LENGTH):
        address = PARTY_MONS_ADDRESS + index * PARTY_MON_SIZE
        for offset in range(PARTY_MON_SIZE):
            self.pyboy.memory[address + offset] = 0
        self._write_name(PARTY_MON_OT_ADDRESS + index * NAME_LENGTH, ())
        self._write_name(PARTY_MON_NICKNAME_ADDRESS + index * NAME_LENGTH, ())
def add_pokemon(self, species, level=2, moves=None, **kwargs)

Add a battle-ready Pokemon using a species ID from pokered's constants.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
>>> pyboy.game_wrapper.party[0]["species"]
84
Expand source code
def add_pokemon(self, species, level=2, moves=None, **kwargs):
    """
    Add a battle-ready Pokemon using a species ID from pokered's constants.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.party[0]["species"]
    84
    ```
    """

    if len(self.party) == PARTY_LENGTH:
        raise ValueError("The party is full")
    if isinstance(species, str):
        species = POKEMON_SPECIES[species.upper()]
    moves = tuple(POKEMON_MOVES[move.upper()] if isinstance(move, str) else move for move in (moves or ()))
    if len(moves) > 4:
        raise ValueError("moves must contain at most four move IDs")
    moves += (0,) * (4 - len(moves))
    default_pp = tuple(35 if move else 0 for move in moves)
    max_hp = kwargs.pop("max_hp", level * 2 + 10)
    stats = {
        "attack": level + 5,
        "defense": level + 5,
        "speed": level + 5,
        "special": level + 5,
    }
    stats.update(kwargs.pop("stats", {}))
    pokemon = {
        "species": species,
        "level": level,
        "hp": kwargs.pop("hp", max_hp),
        "max_hp": max_hp,
        "moves": moves,
        "pp": kwargs.pop("pp", default_pp),
        **stats,
        **kwargs,
    }
    self.set_party(self.party + [pokemon])
def remove_pokemon(self, index)

Remove a Pokemon from the party by its zero-based index.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
>>> pyboy.game_wrapper.remove_pokemon(0)
Expand source code
def remove_pokemon(self, index):
    """
    Remove a Pokemon from the party by its zero-based index.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.remove_pokemon(0)
    ```
    """

    party = self.party
    if not 0 <= index < len(party):
        raise IndexError(f"Invalid party index: {index} of {len(party)-1}")
    del party[index]
    self.set_party(party)
def set_money(self, amount)

Set the player's money.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.set_money(999999)
Expand source code
def set_money(self, amount):
    """
    Set the player's money.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.set_money(999999)
    ```
    """

    if not isinstance(amount, int) or not 0 <= amount <= MAX_MONEY:
        raise ValueError(f"Money must be an integer from 0 to {MAX_MONEY}")
    value = dec_to_bcd(amount, byte_width=3, byteorder="big")
    self.pyboy.memory[PLAYER_MONEY_ADDRESS : PLAYER_MONEY_ADDRESS + 3] = value.to_bytes(3, "big")
def set_inventory(self, inventory, force=False)

Replace the bag contents with item and quantity pairs.

Items may be pokered IDs or names from ITEMS. Quantities range from 1 to 99.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.set_inventory({"POTION": 20, "MASTER_BALL": 1})
Expand source code
def set_inventory(self, inventory, force=False):
    """
    Replace the bag contents with item and quantity pairs.

    Items may be pokered IDs or names from ``ITEMS``. Quantities range
    from 1 to 99.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.set_inventory({"POTION": 20, "MASTER_BALL": 1})
    ```
    """

    if isinstance(inventory, dict):
        inventory = inventory.items()
    entries = []
    seen = set()
    for item, quantity in inventory:
        item = self._item_id(item)
        if item in seen:
            raise ValueError(f"Duplicate item: {item:#x}")
        if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
            raise ValueError(f"Item quantity must be an integer from 1 to {ITEM_QUANTITY_MAX}")
        seen.add(item)
        entries.append((item, quantity))
    if len(entries) > BAG_ITEM_CAPACITY:
        raise ValueError(f"The bag can contain at most {BAG_ITEM_CAPACITY} item types")

    self.pyboy.memory[BAG_ITEM_COUNT_ADDRESS] = len(entries)
    for index in range(BAG_ITEM_CAPACITY):
        address = BAG_ITEMS_ADDRESS + 2 * index
        if index < len(entries):
            self.pyboy.memory[address] = entries[index][0]
            self.pyboy.memory[address + 1] = entries[index][1]
        else:
            self.pyboy.memory[address] = 0xFF
            self.pyboy.memory[address + 1] = 0
def set_item(self, item, quantity, force=False)

Set one bag item's quantity, removing it when quantity is zero.

Expand source code
def set_item(self, item, quantity, force=False):
    """Set one bag item's quantity, removing it when ``quantity`` is zero."""

    item = self._item_id(item)
    if not isinstance(quantity, int) or (not force and (not 0 <= quantity <= ITEM_QUANTITY_MAX)):
        raise ValueError(f"Item quantity must be an integer from 0 to {ITEM_QUANTITY_MAX}")
    inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
    if quantity:
        inventory[item] = quantity
    else:
        inventory.pop(item, None)
    preserve_forced_quantities = any(
        item_quantity == 0 or item_quantity > ITEM_QUANTITY_MAX for item_quantity in inventory.values()
    )
    self.set_inventory(inventory, force=force or preserve_forced_quantities)
def remove_item(self, item, quantity=1)

Remove a quantity of an item from the bag.

Expand source code
def remove_item(self, item, quantity=1):
    """Remove a quantity of an item from the bag."""

    item = self._item_id(item)
    inventory = {entry["item"]: entry["quantity"] for entry in self.inventory}
    if item not in inventory or not isinstance(quantity, int) or not 1 <= quantity <= inventory[item]:
        raise ValueError(f"Cannot remove quantity {quantity} of item {item:#x}")
    self.set_item(item, inventory[item] - quantity, force=inventory[item] > ITEM_QUANTITY_MAX)
def start_wild_battle(self, species, level, force=False)

Schedule a wild battle against a species at the given level.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.start_game()
>>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
>>> pyboy.game_wrapper.start_wild_battle("PIKACHU", level=5)
>>> pyboy.tick(1)
True
Expand source code
def start_wild_battle(self, species, level, force=False):
    """
    Schedule a wild battle against a species at the given level.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.start_game()
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.start_wild_battle("PIKACHU", level=5)
    >>> pyboy.tick(1)
    True
    ```
    """

    if isinstance(species, str):
        try:
            species = POKEMON_SPECIES[species.upper()]
        except KeyError as error:
            raise ValueError(f"Unknown Pokemon species: {species}") from error
    if not force:
        if not isinstance(species, int) or not 1 <= species <= 0xBE:
            raise ValueError(f"Invalid Pokemon species ID: {species}")
        if not isinstance(level, int) or not 1 <= level <= 100:
            raise ValueError(f"Invalid Pokemon level: {level}")

    self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = species
    self.pyboy.memory[CURRENT_ENEMY_LEVEL_ADDRESS] = level
    self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0
def start_trainer_battle(self, trainer_class, trainer_set=1, force=False)

Schedule a trainer battle by class and party-set IDs.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.start_game()
>>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
>>> pyboy.game_wrapper.start_trainer_battle("PROF_OAK", trainer_set=1)
>>> pyboy.tick(1)
True
Expand source code
def start_trainer_battle(self, trainer_class, trainer_set=1, force=False):
    """
    Schedule a trainer battle by class and party-set IDs.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.start_game()
    >>> pyboy.game_wrapper.add_pokemon("PIKACHU", level=5)
    >>> pyboy.game_wrapper.start_trainer_battle("PROF_OAK", trainer_set=1)
    >>> pyboy.tick(1)
    True
    ```
    """

    if isinstance(trainer_class, str):
        try:
            trainer_class = TRAINER_CLASSES[trainer_class.upper()]
        except KeyError as error:
            raise ValueError(f"Unknown trainer class: {trainer_class}") from error
    if not force:
        if not isinstance(trainer_class, int) or not 1 <= trainer_class <= TRAINER_CLASS_COUNT:
            raise ValueError(f"Invalid trainer class ID: {trainer_class}")
        trainer_class_name = TRAINER_CLASS_NAMES[trainer_class]
        trainer_set_count = TRAINER_SET_COUNTS[trainer_class_name]
        if not isinstance(trainer_set, int) or not 1 <= trainer_set <= trainer_set_count:
            raise ValueError(f"Invalid trainer set ID: {trainer_set}")

    opponent = OPPONENT_ID_OFFSET + trainer_class
    self.pyboy.memory[CURRENT_OPPONENT_ADDRESS] = opponent
    self.pyboy.memory[TRAINER_NUMBER_ADDRESS] = trainer_set
    self.pyboy.memory[BATTLE_TYPE_ADDRESS] = 0
def warp(self, destination)

Schedule a script warp to a map identified by name or ID.

The map transition is performed by pokered on the next emulator tick.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.warp("viridian_pokecenter")
>>> pyboy.tick(1)
True
Expand source code
def warp(self, destination):
    """
    Schedule a script warp to a map identified by name or ID.

    The map transition is performed by pokered on the next emulator tick.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.warp("viridian_pokecenter")
    >>> pyboy.tick(1)
    True
    ```
    """

    if isinstance(destination, str):
        try:
            map_id = MAPS[destination]
        except KeyError as error:
            raise ValueError(f"Unknown map name: {destination}") from error
    else:
        map_id = destination
    if not 0 <= map_id <= 0xFF:
        raise ValueError(f"Invalid map ID: {map_id:#x}")
    self.pyboy.memory[WARP_DESTINATION_MAP_ADDRESS] = map_id
    self.pyboy.memory[DESTINATION_WARP_ID_ADDRESS] = 0
    self.pyboy.memory[STATUS_FLAGS3_ADDRESS] |= 1 << BIT_WARP_FROM_CURRENT_SCRIPT
def set_event_flag(self, event)

Set a story event flag by name or numeric pokered event ID.

Gym victory event flags are separate from the badges owned by the player. Use :meth:set_badge to grant a badge.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.set_event_flag("got_pokedex")
Expand source code
def set_event_flag(self, event):
    """
    Set a story event flag by name or numeric pokered event ID.

    Gym victory event flags are separate from the badges owned by the
    player. Use :meth:`set_badge` to grant a badge.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.set_event_flag("got_pokedex")
    ```
    """
    event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
    if not isinstance(event, int):
        raise ValueError(f"Invalid event flag: {event}")
    address = EVENT_FLAGS_ADDRESS + event // 8
    self.pyboy.memory[address] |= 1 << (event % 8)
def reset_event_flag(self, event)

Reset a story event flag by name or numeric pokered event ID.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.reset_event_flag("got_pokedex")
Expand source code
def reset_event_flag(self, event):
    """
    Reset a story event flag by name or numeric pokered event ID.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.reset_event_flag("got_pokedex")
    ```
    """
    event = EVENT_FLAGS.get(event, event)  # Try lookup or default to the original value
    if not isinstance(event, int):
        raise ValueError(f"Invalid event flag: {event}")
    address = EVENT_FLAGS_ADDRESS + event // 8
    self.pyboy.memory[address] &= ~(1 << (event % 8))
def set_badge(self, badge)

Grant a badge by name or numeric bit position.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.set_badge("boulder")
Expand source code
def set_badge(self, badge):
    """
    Grant a badge by name or numeric bit position.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.set_badge("boulder")
    ```
    """
    badge = BADGES.get(badge, badge)
    if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
        raise ValueError(f"Invalid badge: {badge}")
    self.pyboy.memory[OBTAINED_BADGES_ADDRESS] |= 1 << badge
def reset_badge(self, badge)

Remove a badge by name or numeric bit position.

Example:

>>> pyboy = PyBoy(pokemon_blue_rom)
>>> pyboy.game_wrapper.reset_badge("boulder")
Expand source code
def reset_badge(self, badge):
    """
    Remove a badge by name or numeric bit position.

    Example:
    ```python
    >>> pyboy = PyBoy(pokemon_blue_rom)
    >>> pyboy.game_wrapper.reset_badge("boulder")
    ```
    """
    badge = BADGES.get(badge, badge)
    if not isinstance(badge, int) or not 0 <= badge < len(BADGES):
        raise ValueError(f"Invalid badge: {badge}")
    self.pyboy.memory[OBTAINED_BADGES_ADDRESS] &= ~(1 << badge)
def enabled(self)
Expand source code
def enabled(self):
    return (self.pyboy.cartridge_title == "POKEMON RED") or (self.pyboy.cartridge_title == "POKEMON BLUE")
def game_area_collision(self)
Expand source code
def game_area_collision(self):
    width = self.game_area_section[2]
    height = self.game_area_section[3]
    game_area = np.ndarray(shape=(height, width), dtype=np.uint32)
    _collision = self._get_screen_walkable_matrix()
    for i in range(height // 2):
        for j in range(width // 2):
            game_area[i * 2][j * 2 : j * 2 + 2] = _collision[i][j]
            game_area[i * 2 + 1][j * 2 : j * 2 + 2] = _collision[i][j]
    return game_area

Inherited members