Module pyboy.plugins.game_wrapper_pandoras_blocks
Expand source code
#
# License: See LICENSE.md file
# GitHub: https://github.com/Baekalfen/PyBoy
#
__pdoc__ = {
"GameWrapperPandorasBlocks.cartridge_title": False,
"GameWrapperPandorasBlocks.post_tick": False,
}
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
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
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()
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()
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)
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]]
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]
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__()
)
Classes
class GameWrapperPandorasBlocks (*args, **kwargs)-
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
linesandlevelhave the same value. If you callprinton an instance of this object, it will show an overview of everything this object provides.Expand source code
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 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() 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() 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) 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]] 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] 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__() )Ancestors
- PyBoyGameWrapper
- pyboy.plugins.base_plugin.PyBoyPlugin
Class variables
var mapping_compressed-
Compressed mapping for
PyBoy.game_area_mapping() var mapping_minimal-
Minimal mapping for
PyBoy.game_area_mapping()
Instance variables
var score-
The score provided by the game
var level-
The current level
var lines-
The number of cleared lines
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.Args
timer_div:int- Replace timer's DIV register with this value.
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. 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() 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.
Expand source code
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() 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.
Expand source code
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) def next_block(self)-
Return the next block to drop.
Returns
shape:stridentifying the block:"I","Z","S","J","L","O"or"T".
Expand source code
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]] def set_block(self, shape)-
Set the next block to drop.
Args
shape:str- One of
"I","Z","S","J","L","O"or"T".
Expand source code
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] def game_over(self)-
Return whether the current game has reached a game-over state.
Expand source code
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)
Inherited members