Game ending management.
Time management and flashing.
Access to the complete game code.
Here is a little demo that represents how to display things at the end of the game:
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.
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.
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?
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: