Skip to content

Example Tetris

Mads Ynddal edited this page Mar 16, 2024 · 1 revision

Example: Tetris

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. Take a look at the example below, which interacts with the game.

All external components can be found in the PyBoy Documentation. If more features are needed, or if you find a bug, don't hesitate to make an issue here on GitHub, or write on our Discord channel.

For Game Boy documentation in general, have a look at the Pan Docs, which has clear-cut details about every conceivable topic.

First frame GIF Last frame
>>> pyboy = PyBoy(tetris_rom)
>>> pyboy.set_emulation_speed(0)
>>> assert pyboy.cartridge_title == "TETRIS"
>>>
>>> tetris = pyboy.game_wrapper
>>> tetris.game_area_mapping(tetris.mapping_compressed, 0)
>>> tetris.start_game(timer_div=0x00) # The timer_div works like a random seed in Tetris
>>> pyboy.tick() # To render screen after `.start_game`
True
>>> pyboy.screen.image.save("Tetris1.png")
>>>
>>> from pyboy.utils import WindowEvent
>>> pyboy.send_input(WindowEvent.SCREEN_RECORDING_TOGGLE)
>>>
>>> tetromino_at_0x00 = tetris.next_tetromino()
>>> assert tetromino_at_0x00 == "Z", tetris.next_tetromino()
>>> assert tetris.score == 0
>>> assert tetris.level == 0
>>> assert tetris.lines == 0
>>>
>>> # Checking that a reset on the same `timer_div` results in the same Tetromino
>>> tetris.reset_game(timer_div=0x00)
>>> assert tetris.next_tetromino() == tetromino_at_0x00, tetris.next_tetromino()
>>>
>>> blank_tile = 0
>>> first_brick = False
>>> for frame in range(1000): # Enough frames for the test. Otherwise do: `while pyboy.tick():`
...     pyboy.tick(1, True)
...
...     # The playing "technique" is just to move the Tetromino to the right.
...     if frame % 2 == 0: # Even frames to let PyBoy release the button on odd frames
...         pyboy.button("right")
...
...     # Illustrating how we can extract the game board quite simply. This can be used to read the tile identifiers.
...     game_area = tetris.game_area()
...     # game_area is accessed as [<row>, <column>].
...     # 'game_area[-1,:]' is asking for all (:) the columns in the last row (-1)
...     if not first_brick and any(filter(lambda x: x != blank_tile, game_area[-1, :])):
...         first_brick = True
...         print("First brick touched the bottom!")
...         print(tetris)
True
...
First brick touched the bottom!
Tetris:
Score: 0
Level: 0
Lines: 0
Sprites on screen:
Sprite [4]: Position: (72, 128), Shape: (8, 8), Tiles: (Tile: 129), On screen: True
Sprite [5]: Position: (80, 128), Shape: (8, 8), Tiles: (Tile: 129), On screen: True
Sprite [6]: Position: (88, 128), Shape: (8, 8), Tiles: (Tile: 129), On screen: True
Sprite [7]: Position: (88, 136), Shape: (8, 8), Tiles: (Tile: 129), On screen: True
Sprite [8]: Position: (120, 112), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [9]: Position: (128, 112), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [10]: Position: (128, 120), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [11]: Position: (136, 120), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Tiles on screen:
       0   1   2   3   4   5   6   7   8   9
____________________________________________
0  |   0   0   0   0   0   0   0   0   0   0
1  |   0   0   0   0   0   0   0   0   0   0
2  |   0   0   0   0   0   0   0   0   0   0
3  |   0   0   0   0   0   0   0   0   0   0
4  |   0   0   0   0   0   0   0   0   0   0
5  |   0   0   0   0   0   0   0   0   0   0
6  |   0   0   0   0   0   0   0   0   0   0
7  |   0   0   0   0   0   0   0   0   0   0
8  |   0   0   0   0   0   0   0   0   0   0
9  |   0   0   0   0   0   0   0   0   0   0
10 |   0   0   0   0   0   0   0   0   0   0
11 |   0   0   0   0   0   0   0   0   0   0
12 |   0   0   0   0   0   0   0   0   0   0
13 |   0   0   0   0   0   0   0   0   0   0
14 |   0   0   0   0   0   0   0   0   0   0
15 |   0   0   0   0   0   0   0   0   0   0
16 |   0   0   0   0   0   0   0   1   1   1
17 |   0   0   0   0   0   0   0   0   0   1
...
>>>
>>> # Final game board:
>>> print(tetris)
Tetris:
Score: 0
Level: 0
Lines: 0
Sprites on screen:
Sprite [4]: Position: (72, 24), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [5]: Position: (80, 24), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [6]: Position: (80, 32), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [7]: Position: (88, 32), Shape: (8, 8), Tiles: (Tile: 130), On screen: True
Sprite [8]: Position: (120, 112), Shape: (8, 8), Tiles: (Tile: 133), On screen: True
Sprite [9]: Position: (128, 112), Shape: (8, 8), Tiles: (Tile: 133), On screen: True
Sprite [10]: Position: (136, 112), Shape: (8, 8), Tiles: (Tile: 133), On screen: True
Sprite [11]: Position: (128, 120), Shape: (8, 8), Tiles: (Tile: 133), On screen: True
Tiles on screen:
       0   1   2   3   4   5   6   7   8   9
____________________________________________
0  |   0   0   0   0   0   0   0   0   0   0
1  |   0   0   0   0   0   0   0   0   0   0
2  |   0   0   0   0   0   0   0   0   0   0
3  |   0   0   0   0   0   0   0   2   2   0
4  |   0   0   0   0   0   0   0   0   2   2
5  |   0   0   0   0   0   0   0   0   0   0
6  |   0   0   0   0   0   0   0   0   0   0
7  |   0   0   0   0   0   0   0   0   0   0
8  |   0   0   0   0   0   0   0   0   0   0
9  |   0   0   0   0   0   0   0   0   0   0
10 |   0   0   0   0   0   0   0   0   0   0
11 |   0   0   0   0   0   0   0   0   0   0
12 |   0   0   0   0   0   0   0   0   0   0
13 |   0   0   0   0   0   0   0   0   0   0
14 |   0   0   0   0   0   0   0   0   0   0
15 |   0   0   0   0   0   0   0   0   0   0
16 |   0   0   0   0   0   0   0   1   1   1
17 |   0   0   0   0   0   0   0   0   0   1
>>> pyboy.screen.image.save("Tetris2.png")
>>> pyboy.send_input(WindowEvent.SCREEN_RECORDING_TOGGLE)
>>>
>>> # We shouldn't have made any progress with the moves we made
>>> assert tetris.score == 0
>>> assert tetris.level == 0
>>> assert tetris.lines == 0
>>>
>>> # Assert there is something on the bottom of the game area
>>> assert any(filter(lambda x: x != blank_tile, game_area[-1, :]))
>>> tetris.reset_game(timer_div=0x00)
>>> assert tetris.next_tetromino() == tetromino_at_0x00, tetris.next_tetromino()
>>>
>>> tetris.reset_game(timer_div=0x00)
>>> assert tetris.next_tetromino() == tetromino_at_0x00, tetris.next_tetromino()
>>> # After reseting, we should have a clean game area
>>> assert all(filter(lambda x: x != blank_tile, game_area[-1, :]))
>>>
>>> tetris.reset_game(timer_div=0x55) # The timer_div works like a random seed in Tetris
>>> assert tetris.next_tetromino() != tetromino_at_0x00, tetris.next_tetromino()
>>>
>>> # Testing that it defaults to random Tetrominos
>>> selection = set()
>>> for _ in range(10):
...     tetris.reset_game()
...     selection.add(tetris.next_tetromino())
>>> assert len(selection) > 1 # If it's random, we will see more than one kind
>>>
>>> pyboy.stop()