Source code for pyboy.plugins.game_wrapper_pandoras_blocks

#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#

import numpy as np

import pyboy

from .base_plugin import PyBoyGameWrapper
from pyboy.api.constants import TILES

logger = pyboy.logging.get_logger(__name__)

PIECE_NAMES = ("I", "Z", "S", "J", "L", "O", "T")
piece_table = {name: piece for piece, name in enumerate(PIECE_NAMES)}

FIELD_EMPTY_TILE = 108
PIECE_TILE_START = 48
PIECE_TILE_END = 105
GAME_OVER_TILES = (145, 147, 167, 187)
TITLE_SCREEN_TILES = [171, 177, 179, 185, 187, 193, 195, 201, 203, 209, 211, 217, 219, 225]

GAME_STATE_ADDR = 0xFFFD
GAMEPLAY_STATE = 3
MODE_ADDR = 0xFFDB
MODE_GAME_OVER = 21
MODE_PRE_GAME_OVER = 24
NEXT_BLOCK_ADDR = 0xFFD2
LEVEL_ADDR = 0xFF90
SCORE_ADDR = 0xFFB8

mapping_compressed = np.zeros(TILES, dtype=np.uint8)
mapping_compressed[PIECE_TILE_START:PIECE_TILE_END] = 1
mapping_compressed[106] = 1  # Clearing line
mapping_compressed[107] = 1  # Ghost piece
for tile in GAME_OVER_TILES:
    mapping_compressed[tile] = 2

mapping_minimal = np.ones(TILES, dtype=np.uint8)
mapping_minimal[1] = 0
mapping_minimal[FIELD_EMPTY_TILE] = 0
for tile in GAME_OVER_TILES:
    mapping_minimal[tile] = 2


[docs] class GameWrapperPandorasBlocks(PyBoyGameWrapper): """ This class wraps Pandora's Blocks and provides easy access to score, lines and level for AIs. Pandora's Blocks uses its level as the number of lines cleared, so ``lines`` and ``level`` have the same value. If you call ``print`` on an instance of this object, it will show an overview of everything this object provides. """ cartridge_title = "DMGTRIS" 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.score = 0 """The score provided by the game""" self.level = 0 """The current level""" self.lines = 0 """The number of cleared lines""" super().__init__(*args, game_area_section=(2, 0, 10, 18), game_area_follow_scxy=False, **kwargs) def _game_area_tiles(self): if self._tile_cache_invalid: self._cached_game_area_tiles = np.asarray(self.tilemap_background[2:12, :18], dtype=np.uint32) self._tile_cache_invalid = False return self._cached_game_area_tiles def post_tick(self): self._tile_cache_invalid = True self._sprite_cache_invalid = True self.score = 0 for digit in self.pyboy.memory[SCORE_ADDR : SCORE_ADDR + 8]: self.score = self.score * 10 + digit self.level = 0 for digit in self.pyboy.memory[LEVEL_ADDR : LEVEL_ADDR + 4]: self.level = self.level * 10 + digit self.lines = self.level def _enter_gameplay(self): while self.pyboy.memory[GAME_STATE_ADDR] != GAMEPLAY_STATE: self.pyboy.button("start") for _ in range(8): self.pyboy.tick(1, False, False) if self.pyboy.memory[GAME_STATE_ADDR] == GAMEPLAY_STATE: return
[docs] 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. Args: timer_div (int): Replace timer's DIV register with this value. """ while self.tilemap_background[3:17, 2] != TITLE_SCREEN_TILES: self.pyboy.tick(1, False, False) PyBoyGameWrapper.start_game(self, timer_div=timer_div) self._enter_gameplay()
[docs] def reset_game(self, timer_div=None): """ After calling `start_game`, you can call this method at any time to reset the game. Args: timer_div (int): Replace timer's DIV register with this value. """ PyBoyGameWrapper.reset_game(self, timer_div=timer_div) self._enter_gameplay()
[docs] def game_area(self): """ Return the 10 by 18 playfield as a matrix of tile identifiers. The score, lines cleared, and level are available as attributes of this class. """ return PyBoyGameWrapper.game_area(self)
[docs] def next_block(self): """ Return the next block to drop. Returns ------- shape: ``str`` identifying the block: ``"I"``, ``"Z"``, ``"S"``, ``"J"``, ``"L"``, ``"O"`` or ``"T"``. """ return PIECE_NAMES[self.pyboy.memory[NEXT_BLOCK_ADDR]]
[docs] def set_block(self, shape): """ Set the next block to drop. Args: shape (str): One of ``"I"``, ``"Z"``, ``"S"``, ``"J"``, ``"L"``, ``"O"`` or ``"T"``. """ if shape not in piece_table: raise KeyError("Invalid block shape!") self.pyboy.memory[NEXT_BLOCK_ADDR] = piece_table[shape]
[docs] def game_over(self): """ Return whether the current game has reached a game-over state. """ return self.pyboy.memory[MODE_ADDR] in (MODE_GAME_OVER, MODE_PRE_GAME_OVER)
def __repr__(self): return ( "Pandora's Blocks:\n" + f"Score: {self.score}\n" + f"Level: {self.level}\n" + f"Lines: {self.lines}\n" + super().__repr__() )