Le serpent

Sébastien Boisgérault, Mines Paris – PSL

Friday 25 august 2023

https://github.com/boisgera/python-fr

#7da617f

Edition

Table des matières


🙏 Projet original par Aurélien Noce (@ushu).


Introduction

Ce TP vous propose de développer un petit jeu en Python. Il constitue une introduction à la conception et à la réalisation d’un programme complet.

Son sujet est un standard du jeu vidéo, le 🐍 snake.

🎮 Snake! ; une version classique du snake, réalisée avec la plate-forme de retro-gaming Python Pyxel.

De nombreuses variantes de ce jeu existent ; slither.io est un bon exemple de snake modernisé (et notamment, massivement multijoueur !).

Rassurez-vous, notre objectif sera modeste et donc proche de la version classique du jeu : nous réaliserons plusieurs versions d’un programme qui marche (et pas un programme parfait) dont les fonctionnalités s’enrichiront à chaque nouvelle étape.

Prérequis

⚠️ Ce qui suit suppose que vous avez installé Python avec conda et que vous avez un terminal bash fonctionnel sur votre ordinateur.

Commencez par créer un environnement nommé snake, dédié au TP et contenant Python 3.10

(base) $ conda create -n snake python=3.10

Puis activez-le

(base) $ conda activate snake

Vous devriez alors avoir une nouvelle invite de commande :

(snake) $
Dépannage 🛠️

Si vous ne voyez pas l’invite de commande (snake) $ alors

  1. exécutez la commande

    $ conda init bash

    puis

  2. créez un nouveau terminal.


Installez ensuite le module pygame avec pip dans cet environnement :

(snake) $ pip install pygame

Pour tester votre installation, lancez le programme d’exemple :

(snake) $ python -m pygame.examples.aliens

Avec Visual Studio Code

Suggestion #1.  Installez l’extension de VS Code pour Python.

Suggestion #2. Indiquez à VS Code (et pas uniquement au terminal) qu’on souhaite travailler dans l’environnement conda snake : cliquez dans la bannière du bas la zone qui indique le Python courant.

Code de démarrage

Notre point de départ : un arrière-plan dont la couleur varie aléatoirement.

import random
import pygame

pygame.init()
screen = pygame.display.set_mode([300, 300])
clock = pygame.time.Clock()

while True:
    red = random.randint(0, 255)
    green = random.randint(0, 255)
    blue = random.randint(0, 255)
    color = [red, green, blue]
    screen.fill(color)
    pygame.display.update()
    clock.tick(1)

Copiez ce code dans un fichier snake.py et exécutez-le :

(snake) $ python snake.py

⚠️ Pour quitter le programme tapez Control-C dans le terminal.

Exercices

Événements

Pygame permet de spécifier comment réagir aux actions de l’utilisateur, par exemple son utilisation du clavier ou de la souris.

Nous pouvons ainsi faire en sorte de forcer l’arrêt du programme lorsque l’utilisateur clique sur le bouton de fermeture de la fenêtre ou appuie sur la touche Q :

import random
import sys
import pygame

pygame.init()
screen = pygame.display.set_mode([300, 300])
clock = pygame.time.Clock()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                pygame.quit()
                sys.exit()
    red = random.randint(0, 255)
    green = random.randint(0, 255)
    blue = random.randint(0, 255)
    color = [red, green, blue]
    screen.fill(color)
    pygame.display.update()
    clock.tick(1)

Modifier ce programme pour que lorsque l’utilisateur presse les flêches de son clavier, le programme affiche (avec la fonction print) les caractères , , ou dans le terminal.

🗝️ Le code renvoyé par la flêche vers le haut est pygame.K_UP par exemple.

Le damier

Nous allons commencer par construire notre plateau de jeu ainsi :

Pour vérifier la validité de ce plateau de jeu, écrivez un programme qui dessine un damier :

🗝️ Vous pouvez utiliser la méthode pygame.draw.rect :

x = 100
y = 100
width = 30
height = 30
rect = [x, y, width, height]
red = 255
green = 0
blue = 0
color = [red, green, blue]
pygame.draw.rect(screen, color, rect)

Un serpent fixe

L’étape suivante est de dessiner le serpent, comme une suite de segments représentés par des rectangles colorés. On veut dessiner le serpent aux coordonnées suivantes :

snake = [
    [10, 15],
    [11, 15],
    [12, 15],
]

pour obtenir un schéma comme suit ; disons pour fixer les idées que dans ce cas de figure [10, 15] est la queue et [12, 15] est la tête :

Solution
import sys
import pygame

white = [255, 255, 255]
black = [0, 0, 0]
snake = [
[10, 15],
[11, 15],
[12, 15],
]

pygame.init()
screen = pygame.display.set_mode([20*30, 20*30])
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
pygame.quit()
sys.exit()
screen.fill(white)
for x, y in snake:
rect = [x*20, y*20, 20, 20]
pygame.draw.rect(screen, black, rect)  
 pygame.display.update()
clock.tick(1)

Un serpent qui bouge

Ensuite, nous allons faire bouger le serpent :

Une fois que le serpent bouge, ajouter les commandes pour se déplacer dans les 4 directions, en appuyant sur les touches de direction du clavier.

Aussi on peut commencer à envisager d’accélérer un peu le jeu à ce stade …

Bonus. Faites en sorte que le serpent ne puisse pas faire demi-tour.

Solution
import sys
import pygame

white = [255, 255, 255]
black = [0, 0, 0]
snake = [
    [10, 15],
    [11, 15],
    [12, 15],
]
direction = [1, 0]

pygame.init()
screen = pygame.display.set_mode([20*30, 20*30])
clock = pygame.time.Clock()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                pygame.quit()
                sys.exit()
            if event.key == pygame.K_UP:
                direction = [0.0, -1.0]
            elif event.key == pygame.K_LEFT:
                direction = [-1.0, 0.0]
            elif event.key == pygame.K_DOWN:
                direction = [0.0, 1.0]
            elif event.key == pygame.K_RIGHT:
                direction = [1.0, 0.0]
    head = snake[-1]
    new_head = [
      head[0] + direction[0],
      head[1] + direction[1]
    ]
    snake = snake[1:] + [new_head]
    screen.fill(white)
    for x, y in snake:
        rect = [x*20, y*20, 20, 20]
        pygame.draw.rect(screen, black, rect)
    pygame.display.update()
    clock.tick(1)

Le fruit

Il faut maintenant faire manger notre serpent. On va procéder comme suit:

Solution
import random
import sys
import pygame

white = [255, 255, 255]
black = [0, 0, 0]
red = [255, 0, 0]
snake = [
    [10, 15],
    [11, 15],
    [12, 15],
]
direction = [1, 0]
fruit = [10, 10]

pygame.init()
screen = pygame.display.set_mode([20*30, 20*30])
clock = pygame.time.Clock()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                pygame.quit()
                sys.exit()
            if event.key == pygame.K_UP:
                direction = [0, -1]
            elif event.key == pygame.K_LEFT:
                direction = [-1, 0]
            elif event.key == pygame.K_DOWN:
                direction = [0, 1]
            elif event.key == pygame.K_RIGHT:
                direction = [1, 0]
    head = snake[-1]
    new_head = [
      head[0] + direction[0],
      head[1] + direction[1]
    ]
    if new_head == fruit:
        snake = snake + [new_head]
        fruit = [
            random.randint(0, 29),
            random.randint(0, 29)
        ]
    else:
        snake = snake[1:] + [new_head]
    screen.fill(white)
    for x, y in snake:
        rect = [x*20, y*20, 20, 20]
        pygame.draw.rect(screen, black, rect)
    rect = [fruit[0]*20, fruit[1]*20, 20, 20]
    pygame.draw.rect(screen, red, rect)
    pygame.display.update()
    clock.tick(1)

Épilogue

Il nous reste deux petits changements pour avoir un serpent complètement fonctionnel :

Solution
import random
import sys
import pygame

white = [255, 255, 255]
black = [0, 0, 0]
red = [255, 0, 0]
snake = [
    [10, 15],
    [11, 15],
    [12, 15],
]
direction = [1, 0]
fruit = [10, 10]
score = 0

pygame.init()
screen = pygame.display.set_mode([20*30, 20*30])
clock = pygame.time.Clock()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                pygame.quit()
                sys.exit()
            if event.key == pygame.K_UP:
                direction = [0, -1]
            elif event.key == pygame.K_LEFT:
                direction = [-1, 0]
            elif event.key == pygame.K_DOWN:
                direction = [0, 1]
            elif event.key == pygame.K_RIGHT:
                direction = [1, 0]
    head = snake[-1]
    new_head = [
      head[0] + direction[0],
      head[1] + direction[1]
    ]
    if (
        new_head in snake
        or new_head[0] < 0
        or new_head[0] >= 30
        or new_head[1] < 0
        or new_head[1] >= 30
    ):
        pygame.quit()
        sys.exit()
    if new_head == fruit:
        score = score + 1
        snake = snake + [new_head]
        fruit = [
            random.randint(0, 29),
            random.randint(0, 29)
        ]
    else:
        snake = snake[1:] + [new_head]
    screen.fill(white)
    for x, y in snake:
        rect = [x*20, y*20, 20, 20]
        pygame.draw.rect(screen, black, rect)
    rect = [fruit[0]*20, fruit[1]*20, 20, 20]
    pygame.draw.rect(screen, red, rect)
    pygame.display.update()
    pygame.display.set_caption(f"🐍 Score: {score}")
    clock.tick(1)

Annexe – Codes RGB

La couleur d’un pixel est décrite par son code RGB : un triplet d’entiers compris entre 0 et 255 qui déterminent l’intensité de ses composantes rouge, verte et bleue. On a par exemple :

R G B Couleur

   255           0           0      🟥
     0         255           0      🟩
     0           0         255      🟦
   255         255         255      ⬜
     0           0           0      ⬛
   128          64           0      🟫
   255         128           0      🟧
   255         255           0      🟨
   106          13         173      🟪