Finishes

Step 8
Step completed?

Game ending management.
Time management and flashing.
Access to the complete game code.

Manage the end of the game

Here is a little demo that represents how to display things at the end of the game:

Démo

Roughly speaking, we change the color of the walls and background, and we make the snake blink until the player presses the A button to restart a new game.

Change of colours

Let's start by managing the color change. We will define two new global variables to characterize the new colors: a background color COLOR_LOST_BG and a color for the plots COLOR_LOST_FG. To detect that the game is lost, we will also have to define a new game phase MODE_LOST:

# ----------------------------------------------------------
# Global variables
# ----------------------------------------------------------

COLOR_LOST_BG = 0x8800 # background
COLOR_LOST_FG = 0xffff # foreground
MODE_START    = 0
MODE_READY    = 1
MODE_PLAY     = 2
MODE_LOST     = 3

Then, we will modify the scheduler so that it takes into account this new phase of play:

# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------

def tick():
    if not game['refresh']:
        clearSnakeTail()
    
    if game['mode'] == MODE_START:
        resetSnake()
        spawnApple()
        game['mode'] = MODE_READY
        game['score'] = 0
    elif game['mode'] == MODE_READY:
        game['refresh'] = False
        handleButtons()
        moveSnake()
        if snakeHasMoved():
            game['mode'] = MODE_PLAY
    elif game['mode'] == MODE_PLAY:
        handleButtons()
        moveSnake()
        if game['refresh']:
            game['refresh'] = False
        if didSnakeEatApple():
            game['score'] += 1
            game['refresh'] = True
            extendSnakeTail()
            spawnApple()
        if didSnakeBiteItsTail() or didSnakeHitTheWall():
            game['mode'] = MODE_LOST
            game['refresh'] = True
    else:
        handleButtons()

    draw()

This time, when the snake bites its tail or hits a wall, we switch to the MODE_LOST phase:

if didSnakeBiteItsTail() or didSnakeHitTheWall():
    game['mode'] = MODE_LOST
    game['refresh'] = True

Then we add an ultimate test that will be validated if the current phase of the game corresponds to MODE_LOST, since neither MODE_START, nor MODE_READY, nor MODE_PLAY will have validated the previous tests:

else:
    handleButtons()

This allows to give the hand to the player by giving him the possibility to restart the game by pressing a button. We will therefore add the consideration of this situation in our controller:

# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------

def handleButtons():
    if buttons.pressed(buttons.LEFT):
        dirSnake(-1, 0)
    elif buttons.pressed(buttons.RIGHT):
        dirSnake(1, 0)
    elif buttons.pressed(buttons.UP):
        dirSnake(0, -1)
    elif buttons.pressed(buttons.DOWN):
        dirSnake(0, 1)
    elif game['mode'] == MODE_LOST and buttons.pressed(buttons.A):
        game['mode'] = MODE_START

If the player presses the A button while the game is in the MODE_LOST phase, the game is automatically restarted by simply switching back to the MODE_START one.

All we have to do now is modify the display functions of the game scene to apply the color change. To do this, simply modify the clearScreen() and drawWalls() functions to adapt the colors to the game phase:

# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------

def clearScreen():
    color = COLOR_LOST_BG if game['mode'] == MODE_LOST else COLOR_BG
    display.clear(color)

def drawWalls():
    color = COLOR_LOST_FG if game['mode'] == MODE_LOST else COLOR_WALL
    display.setColor(color)
    display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)

At this stage, the end of the game is marked by the change of colors of the game scene... but we still have to make the snake flash.

Snake blinking

The snake flashes by applying a color change as well, but alternatively, with a specific frequency. And if we want to be able to control the frequency of flashes, we need some kind of internal clock.

So, we will add a new time property to the game engine to take into account the time that elapses:

# ----------------------------------------------------------
# Initialization
# ----------------------------------------------------------

game  = {
    'mode':    MODE_START,
    'score':   0,
    'time':    0,
    'refresh': True
}

This time variable will be incremented by one unit each time it passes through the scheduler and initialized during the MODE_START phase:

# ----------------------------------------------------------
# Game management
# ----------------------------------------------------------

def tick():
    if not game['refresh']:
        clearSnakeTail()
    
    if game['mode'] == MODE_START:
        resetSnake()
        spawnApple()
        game['mode'] = MODE_READY
        game['score'] = 0
        game['time'] = 0
    elif game['mode'] == MODE_READY:
        game['refresh'] = False
        handleButtons()
        moveSnake()
        if snakeHasMoved():
            game['mode'] = MODE_PLAY
    elif game['mode'] == MODE_PLAY:
        handleButtons()
        moveSnake()
        if game['refresh']:
            game['refresh'] = False
        if didSnakeEatApple():
            game['score'] += 1
            game['refresh'] = True
            extendSnakeTail()
            spawnApple()
        if didSnakeBiteItsTail() or didSnakeHitTheWall():
            game['mode'] = MODE_LOST
            game['refresh'] = True
    else:
        handleButtons()

    draw()
    game['time'] += 1

Initialization is done during the MODE_START phase:

if game['mode'] == MODE_START:
    resetSnake()
    spawnApple()
    game['mode'] = MODE_READY
    game['score'] = 0
    game['time']  = 0

And the incrementation occurs just before the end of the scheduler cycle:

draw()
game['time'] += 1

Now that this time variable is in place, all that remains is to modify the snake's drawing function:

# ----------------------------------------------------------
# Graphic display
# ----------------------------------------------------------

def drawSnake():
    isTimeToBlink = game['time'] % 4 < 2
    color = COLOR_LOST_FG if game['mode'] == MODE_LOST and isTimeToBlink else COLOR_SNAKE
    n = snake['len']
    for i in range(n):
        drawDot(snake['x'][i], snake['y'][i], color)

The variable isTimeToBlink allows you to implement the flashing:

isTimeToBlink = game['time'] % 4 < 2

Here is a table of some of the values obtained by this calculation:

game['time'] game['time'] % 4 isTimeToBlink
0 0 True
1 1 True
2 2 False
3 3 False
4 0 True
5 1 True
6 2 False
7 3 False

No need to go any further, since the result is cyclical of period 4 (this is the effect obtained by applying the modulo operator %).

Finally, we observe that the variable isTimeToBlink changes value every 2 cycles... and consequently, the snake flashing too.

There you go! We have reached the end of this workshop. You now know how to program a Snake game with Python. You notice that there are finally quite a few things to put in place and important notions to master. But the result is pretty cool, right?

Démo

I hope you enjoyed it and that it will make you want to program other little games by yourself. Feel free to ask questions or leave a comment on the page dedicated to the discussions related to this workshop!

I apologize for my approximate English... it's not my natural language, and I did what I could! I hope it didn't make the reading of this workshop too painful...

You can get the complete code directly on my GitHub:

Steps