Extension: Large boards and scrolling views#
By default, the game board and the view are the same size — everything on the board is always visible on screen. But what if you want a board that is much larger than what fits on the screen? Think of a maze that stretches far beyond the edges of the terminal, or a world the player can explore by moving through it.
This is an optional extension. If your game fits comfortably on one screen, you can skip this entirely. If you want to build something bigger, read on.
The idea#
When you create a Game, you can set two sizes
independently:
board_size— how large the game world actually is.view_size— how much of it is visible on screen at once.
The visible portion is called the view. The view has a position on the
board, called the view position, which is the board coordinate of the
view’s top-left corner. By changing game.view_position during the game,
you can scroll the view around the board.
Board (400 × 200)
┌────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────┐ │
│ │ View (40 × 20) │ │
│ │ │ │
│ │ * │ │
│ │ │ │
│ └──────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
Only agents inside the view are drawn on screen. Agents outside the view still exist and still take their turns — they just aren’t visible.
Setting up a large board#
Pass both board_size and view_size when creating the game:
from retro.game import Game
game = Game(
agents,
state,
board_size=(400, 200),
view_size=(40, 20),
)
You can also set the initial view position with view_position:
game = Game(
agents,
state,
board_size=(400, 200),
view_size=(40, 20),
view_position=(180, 90), # start near the center of the board
)
Moving the view#
During the game, any agent can move the view by setting
game.view_position to a new (x, y) coordinate:
def play_turn(self, game):
# scroll the view one step to the right
vx, vy = game.view_position
game.view_position = (vx + 1, vy)
You can also check whether a position is currently visible using
on_view():
if game.on_view(self.position):
game.log("I'm on screen!")
CenterViewAgent#
The most common thing to want is for the view to follow the player around the
board, keeping the player centered on screen. The built-in
CenterViewAgent mixin does exactly this.
To use it, add CenterViewAgent as a second base class alongside whatever
other class your player inherits from:
from retro.agent import ArrowKeyAgent, CenterViewAgent
class Player(ArrowKeyAgent, CenterViewAgent):
character = "*"
color = "red_on_black"
def __init__(self, position):
self.position = position
On every turn, CenterViewAgent will adjust
game.view_position so that the player stays in the center of the view.
Using a margin
Re-centering the view on every single turn can feel a bit jittery — the view
shifts even if the player only moves one step. If you’d prefer the view to only
re-center when the player gets close to the edge, set a margin:
class Player(ArrowKeyAgent, CenterViewAgent):
character = "*"
margin = 5 # re-center when within 5 spaces of any edge
With margin = 5, the view stays still while the player is comfortably in
the middle, and only snaps back to center when the player gets within five
spaces of the edge.
Putting it together: a labyrinth#
Here is a complete example of a game that uses a large board with a scrolling view. The player navigates a maze looking for the goal. Try it:
python -m retro.examples.labyrinth
The key parts are the board and view sizes in the game setup:
WIDTH, HEIGHT = 400, 200
VIEW_WIDTH, VIEW_HEIGHT = 40, 20
game = Game(
[player] + maze.get_agents(),
state,
board_size=(WIDTH, HEIGHT),
view_size=(VIEW_WIDTH, VIEW_HEIGHT),
)
And the Player class, which inherits from both
ArrowKeyAgent and
CenterViewAgent to get arrow-key movement and
automatic view-following for free: