Finitions

Étape 8
Étape terminée ?

Gestion de la fin de la partie.
Gestion du temps et clignotement.
Accès au code complet du jeu.

Gérer la fin de la partie

Voilà une petite démo qui représente comment afficher les choses en fin de partie :

Démo

Grosso modo, on change la couleur des murs et du fond, et on fait clignoter le serpent jusqu'à ce que le joueur presse le bouton A pour relancer une nouvelle partie.

Changement de couleurs

Commençons par gérer le changement des couleurs. On va définir deux nouvelles variables globales pour caractériser les nouvelles couleurs : une couleur de fond COLOR_LOST_BG et une couleur pour les tracés COLOR_LOST_FG. Pour détecter que la partie est perdue, on va également devoir définir une nouvelle phase de jeu 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

Ensuite, on va modifier l'ordonnanceur pour qu'il prenne en compte cette nouvelle phase de jeu :

# ----------------------------------------------------------
# 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()

Cette fois, lorsque le serpent se mord la queue ou heurte un mur, on bascule dans la phase MODE_LOST :

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

Puis on rajoute un test ultime qui sera validé si la phase courante du jeu correspond à MODE_LOST, puisque ni MODE_START, ni MODE_READY, ni MODE_PLAY n'auront validé les tests précédents :

else:
    handleButtons()

Ceci permet de donner la main au joueur en lui laissant la possiblité de relancer la partie en appuyant sur un bouton. On va donc ajouter la prise en compte de cette situation dans notre contrôleur :

# ----------------------------------------------------------
# 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

Si le joueur appuie sur le bouton A alors que le jeu est en phase MODE_LOST, la partie est automatiquement relancée en repassant simplement en phase MODE_START.

Il ne nous reste plus qu'à modifier les fonctions d'affichage de la scène de jeu pour appliquer le changement de couleurs. Il suffit pour cela de modifier les fonctions clearScreen() et drawWalls() pour qu'elles adaptent les couleurs à la phase de jeu :

# ----------------------------------------------------------
# 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)

À ce stade, la fin de partie est marquée par le changement de couleurs de la scène de jeu... mais il nous reste à faire clignoter le serpent.

Clignotement du serpent

Le clignotement du serpent s'effectue en appliquant un changement de couleurs également, mais de manière alternative, avec une fréquence spécifique. Et si on veut pouvoir contrôler la fréquence des clignotements, il nous faut une sorte d'horloge interne.

Donc, on va ajouter une nouvelle propriété time au moteur de jeu pour prendre en compte le temps qui s'écoule :

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

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

Cette variable temporelle sera incrémentée d'une unité à chaque passage dans l'ordonnanceur et initialisée lors de la phase MODE_START :

# ----------------------------------------------------------
# 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

L'initialisation se fait pendant la phase MODE_START :

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

Et l'incrémentation intervient juste avant la fin du cycle de l'ordonnanceur :

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

Maintenant que cette variable temporelle est en place, il n'y a plus qu'à modifier la fonction de tracé du serpent :

# ----------------------------------------------------------
# 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)

La variable isTimeToBlink permet de mettre en œuvre le clignotement :

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

Voilà un tableau de quelques valeurs obtenues par ce calcul :

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

Inutile d'aller plus loin, puisque le résultat est cyclique de période 4 (c'est l'effet obtenu en appliquant l'opérateur modulo %).

On observe finalement que la variable isTimeToBlink change de valeur tous les 2 cycles... et par conséquent, le clignotement du serpent aussi.

Et voilà ! Nous sommes arrivés au terme de cet atelier. Tu sais maintenant comment programmer un Snake en Python. Tu constates qu'il y a finalement pas mal de choses à mettre en place et de notions importantes à maîtriser. Mais le résultat obtenu est plutôt sympa, nan ?

Démo

J'espère que ça t'a plu et que ça te donnera envie de programmer d'autres petits jeux par toi-même. N'hésite pas à poser des questions ou à laisser un commentaire sur la page consacrée aux discussions liées à cet atelier !

Tu peux récupérer le code complet directement sur mon GitHub :

Étapes