Extended example: Snake#

Here’s a longer example, a classic game where you guide a snake around the board, searching for delicious apples. Every time you eat an apple you grow longer, until it’s hard to stay out of your own way.

Before we go any further, try out the game:

$ python -m retro.examples.snake

Think about which agents we’ll need in this game:

  • We need an apple. One will be enough; whenever the snake catches the apple, we can have it move to a new location.

  • We also need a snake. But the snake needs more than one character, and needs to grow as more apples are eaten. We can accomplish this by creating two different kinds of Agents: a SnakeHead and a SnakeBodySegment.

Now we will go through these Agents one by one. Read the documentation below to get an overview of how the class works, and then click [source] to read the source code.

Apple#

class retro.examples.snake.Apple[source]#

An agent representing the Apple. Note how Apple doesn’t have play_turn or handle_keystroke methods: the Apple doesn’t need to do anything in this game. It just sits there waiting to get eaten.

name#

“Apple”

character#

‘@’

color#

“red_on_black” (Here’s documentation on how colors work

position#

(0, 0). The Apple will choose a random position as soon as the game starts, but it needs an initial position to be assigned.

random_empty_position(game)[source]#

Returns a randomly-selected empty position. Uses a very simple algorithm: Get the game’s board size, choose a random x-value between 0 and the board width, and choose a random y-value between 0 and the board height. Now use the game to check whether any Agents are occupying this position. If so, keep randomly choosing a new position until the position is empty.

relocate(game)[source]#

Sets position to a random empty position. This method is called whenever the snake’s head touches the apple.

Parameters:

game (Game) – The current game.

SnakeHead#

class retro.examples.snake.SnakeHead[source]#

An Agent representing the snake’s head. When the game starts, you control the snake head using the arrow keys. The SnakeHead always has a direction, and will keep moving in that direction every turn. When you press an arrow key, you change the SnakeHead’s direction.

name#

“Snake head”

position#

(0,0)

character#

'v' Depending on the snake head’s direction, its character changes to '<', '^', '>', or 'v'.

next_segment#

Initially None, this is a reference to a SnakeBodySegment.

growing#

When set to True, the snake will grow a new segment on its next move.

handle_keystroke(keystroke, game)[source]#

Checks whether one of the arrow keys has been pressed. If so, sets the SnakeHead’s direction and character.

play_turn(game)[source]#

On each turn, the snake head uses its position and direction to figure out its next position. If the snake head is able to move there (it’s on the board and not occuppied by part of the snake’s body), it moves.

Then, if the snake head is on the Apple, the Apple moves to a new random position and growing is set to True.

Now we need to deal with two situations. First, if next_segment is not None, there is a SnakeBodySegment attached to the head. We need the body to follow the head, so we call self.next_segment.move, passing the head’s old position (this will be the body’s new position), a reference to the game, and a value for growing. If the snake needs to grow, we need to pass this information along the body until it reaches the tail–this is where the next segment will be attached.

If there is no next_segment but self.growing is True, it’s time to add a body! We set self.next_segment to a new SnakeBodySegment, set its position to the head’s old position, and add it to the game. We also add 1 to the game’s score.

SnakeBodySegment#

class retro.examples.snake.SnakeBodySegment(segment_id, position)[source]#

Finally, we need an Agent for the snake’s body segments. SnakeBodySegment doesn’t have play_turn or handle_keystroke methods because it never does anything on its own. It only moves when the SnakeHead, or the previous segment, tells it to move.

Parameters:
  • segment_id (int) – Keeps track of how far back this segment is from the head. This is used to give the segment a unique name, and also to keep track of how many points the player earns for eating the next apple.

  • position (int, int) – The initial position.

character#

‘*’

next_segment#

Initially None, this is a reference to a SnakeBodySegment when this segment is not the last one in the snake’s body.

move(new_position, game, growing=False)[source]#

When SnakeHead moves, it sets off a chain reaction, moving all its body segments. Whenever the head or a body segment has another segment (next_segment), it calls that segment’s move method.

This method updates the SnakeBodySegment’s position. Then, if self.next_segment is not None, calls that segment’s move method. If there is no next segment and growing is True, then we set self.next_segment to a new SnakeBodySegment in this segment’s old position, and update the game’s score.

Parameters:
  • new_position (int, int) – The new position.

  • game (Game) – A reference to the current game.

  • growing (bool) – (Default False) When True, the snake needs to add a new segment.