Outils pour utilisateurs

Outils du site


nsi:tds:pygame:snake

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Snake sur Pygame

Présentation

On se propose de réaliser un jeu de serpent sur Pygame. Le jeu du serpent a un principe simple : on contrôle un serpent sur une grille, on peut le faire aller en haut, en bas, à gauche et à droite.

  • C'est la tête du serpent que l'on contrôle. Les anneaux du serpents passent toujours par les mêmes cases que les anneaux précédents.
  • Si la tête du serpent percute un de ses anneaux, c'est perdu.
  • Des récompenses apparaissent aléatoirement. Quand le serpent passe dessus, il grandit.
  • Le serpent devient donc de plus en plus long et il est de plus en plus difficile de le déplacer sans qu'il se croque lui même.

Pour les premières : Pygame utilise la programmation orientée objet. On parle de classes et des objets. On peut s'en sortir sans comprendre tous les détails.

L'idée générale est la suivante : En Python, on dispose d'entités de formes diverses. On a des entiers, des tableaux… On a le droit de manipuler ces entités, elles ont des propriété. Par exemple on peut faire len sur un tableau et une chaîne mais pas sur un entier.

Une classe permet de définir une nouvelle entité et ses propriétés. La classe est donc une sorte de plan de construction et un manuel d'utilisation. Les entités crées selon se modèle sont appelées des objets. Quand on crée un objet, on dit qu'on l'instancie.

Dit autrement, par exemple, on peut parler du concept de pomme. Qu'est ce que c'est qu'une pomme ? à quoi on la reconnaît ? qu'est ce qu'on peut faire avec ? Toutes ces considérations abstraites, c'est la classe. Quand on a une vraie pomme, c'est une instance de pomme.

Les modules

Nous allons créer 2 modules et un principal. Un premier module définira le fonctionnement du serpent. Un second module définira les récompenses.

Les récompenses

C'est extrêmement simple et cela va permettre de présenter des choses.

# fichier apple.py
import pygame

class Apple(pygame.sprite.Sprite):
    COLOR = (255,100,0)
    SIZE = 20
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((self.SIZE, self.SIZE))
        self.image.fill(self.COLOR)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

Dans les versions classiques du Snake, le serpent mange des pommes, c'est pourquoi j'ai appelé la récompense Apple.

Dans ce module bien sûr, on import pygame.

signature de la classe
class Apple(pygame.sprite.Sprite):

signifie que l'on crée un modèle Apple. Ce modèle est censé être un objet graphique, un sprite. Gérer le graphique n'est jamais simple c'est pourquoi on utilise une bibliothèque comme Pygame.

(pygame.sprite.Sprite) signifie que notre modèle reprend tout un tas de définitions déjà faites par Pygame. Notre Apple sera donc un sprite.

constantes de classe
    COLOR = (255,100,0)
    SIZE = 20

Ces constantes nous permettent de définir l'apparence de la pomme. Pour nous, ce sera un simple carré de couleur. La couleur est définie en RGB, ici c'est du rouge. SIZE nous donne la taille en pixels. La pomme sera donc un carré rouge de 20 pixels de côté.

Comme ces constantes sont définies à l'intérieur de la classe, on peut les atteindre en écrivant Apple.COLOR par exemple, ou bien self.COLOR quand on parle depuis l'intérieur de la classe.

initialisation
    def __init__(self, x, y):

Cette fonction spéciale (en Python, les __ indiquent toujours quelque chose de particulier) sert à la création de l'objet.

Quand on va créer un objet Apple, Python va réserver l'espace nécessaire en mémoire puis va automatiquement appeler la fonction __init__ pour l'initialiser.

Dans toutes les fonctions de la classe (on dit des méthodes), self représente la pomme elle même.

Dans ce cas, on veut créer la pomme quelque à la position x et y.

super
    super().__init__()

Comme notre Apple est un sprite Pygame, Pygame a déjà défini sa propre fonction d'initialisation. Cette ligne permet d'appeler l'initialisation Pygame de sorte que notre Apple ait bien toutes les caractéristiques nécessaires pour que Pygame en fasse un sprite valable.

Nous pourrons poursuivre avec nos initialisations.

Le graphique
    self.image = pygame.Surface((self.SIZE, self.SIZE))
    self.image.fill(self.COLOR)
    self.rect = self.image.get_rect()
    self.rect.x = x
    self.rect.y = y

Les sprites Pygame ont toujours un attribut image et un attribut rect.

  • image définit ce qui est affiché. Ici on crée une surface carrée que l'on remplit en couleur.
  • rect représente le cadre où sera affiché cette image. Si nous voulions pouvoir déplacer le sprite, il faudrait jouer sur rect.

Le main avec les récompenses

# fichier main.py
import pygame
from random import randrange
pygame.init() # initialisation de pygame

from apple import Apple

clock = pygame.time.Clock() # Pour contrôler la vitesse d'affichage

# création de la zone de dessin
WIDTH = 600
HEIGHT = 500
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# c'est plus simple quand on met les sprites dans des groupes
apples = pygame.sprite.Group() # groupe contenant les pommes
all_sprites = pygame.sprite.Group()    # groupe contenant tous les sprites

# ajout de 15 pommes
for i in range(15):
    x = randrange(100, 500)
    y = randrange(100, 400)
    apple = Apple(x, y) # création de la pomme en x, y
    apples.add(apple)   # ajout de la pomme crée dans le groupe des pommes
    all_sprites.add(apple)      # ajout de la pomme au groupe de tous les sprites

# lancement d'une boucle assurant le rafraichissement du jeu
running = True
BLACK = (0,0,0) # couleur noire
while running:
    clock.tick(60) # limite a 60 fps (frames per second)

    # remplit le fond en noir pour créer la prochaine image
    screen.fill(BLACK)

    # La gestion des éléments du jeu aura lieu ici : déplacement du personnage
    # apparition des bonbons, etc.

    for event in pygame.event.get():
        # parcours les événements qui se sont produit
        if event.type == pygame.QUIT:
            # c'est un événement de type fermeture de fenêtre
            running = False # permet la fin de la boucle

    # dessine les sprites
    all_sprites.draw(screen)

    # met à jour l'affichage
    pygame.display.flip()


# boucle finie
pygame.quit() # lance la fermeture de la fenêtre

Le code est commenté, vous devriez comprendre.

Avec le module apple.py, ce programme est déjà fonctionnel. Mais il n'affiche que des pommes fixes et pas de serpent…

La tête du serpent

On va se contenter pour l'instant d'un anneau de serpent que l'on pourra déplacer avec les flèches du clavier.

# fichier snake.py
import pygame

class Head(pygame.sprite.Sprite):
    COLOR = (0, 255, 0)
    SIZE = 20
    VELOCITY = 2 # vitesse du serpent
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((self.SIZE, self.SIZE))
        self.image.fill(self.COLOR)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.vx = 0
        self.vy = 0

    def move_up(self):
        # amorce un mouvement vers le haut
        # attention, sur l'écran, aller vers le haut, c'est diminuer les y !
        if self.vy > 0:
            # interdit d'aller dans le sens opposé -> sort de la fonction sans rien faire
            return
        self.vy = - self.VELOCITY
        self.vx = 0

    def move_down(self):
        # amorce un mouvement vers le haut
        # attention, sur l'écran, aller vers le bas, c'est augmenter les y !
        if self.vy < 0:
            # interdit d'aller dans le sens opposé -> sort de la fonction sans rien faire
            return
        self.vy = self.VELOCITY
        self.vx = 0

    def move_left(self):
        if self.vx > 0:
            # interdit d'aller dans le sens opposé -> sort de la fonction sans rien faire
            return
        self.vx = - self.VELOCITY
        self.vy = 0

    def move_right(self):
        if self.vx < 0:
            # interdit d'aller dans le sens opposé -> sort de la fonction sans rien faire
            return
        self.vx = self.VELOCITY
        self.vy = 0

    def update_position(self):
        # modifie les coordonnees selon la vitesse en cours
        self.rect.x += self.vx
        self.rect.y += self.vy

Le main avec la tête de serpent

Le main avec les récompenses

# fichier main.py
import pygame
from random import randrange
pygame.init() # initialisation de pygame

from apple import Apple
from snake import Head

clock = pygame.time.Clock() # Pour contrôler la vitesse d'affichage

# création de la zone de dessin
WIDTH = 600
HEIGHT = 500
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# c'est plus simple quand on met les sprites dans des groupes
apples = pygame.sprite.Group() # groupe contenant les pommes
all_prites = pygame.sprite.Group()    # groupe contenant tous les sprites

# ajout de 15 pommes
for i in range(15):
    x = randrange(100, 500)
    y = randrange(100, 400)
    apple = Apple(x, y) # création de la pomme en x, y
    apples.add(apple)   # ajout de la pomme crée dans le groupe des pommes
    all_sprites.add(apple)      # ajout de la pomme au groupe de tous les sprites

# création de la tête    
head = Head(300,200)
all_sprites.add(head)
    
# lancement d'une boucle assurant le rafraichissement du jeu
running = True
BLACK = (0,0,0) # couleur noire
while running:
    clock.tick(60) # limite a 60 fps (frames per second)

    # remplit le fond en noir pour créer la prochaine image
    screen.fill(BLACK)

    # La gestion des éléments du jeu aura lieu ici : déplacement du personnage
    # apparition des bonbons, etc.

    for event in pygame.event.get():
        # parcours les événements qui se sont produit
        if event.type == pygame.QUIT:
            # c'est un événement de type fermeture de fenêtre
            running = False # permet la fin de la boucle

    # récupère la liste des états des touches
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and not keys[pygame.K_RIGHT]:
        # la touche flèche gauche est appuyée mais pas la flèche droite
        head.move_left()
    elif keys[pygame.K_RIGHT] and not keys[pygame.K_LEFT]:
        # la touche flèche droite est appuyée
        head.move_right()
    if keys[pygame.K_UP] and not keys[pygame.K_DOWN]:
        # la touche flèche haut est appuyée mais pas la flèche bas
        head.move_up()
    elif keys[pygame.K_DOWN] and not keys[pygame.K_UP]:
        # la touche flèche bas est appuyée mais pas la flèche haut
        head.move_down()

    # met à jour la tête
    head.update_position()

    # la tête touche-t-elle une pomme ?
    # l'argument True indique que les pommes touchées sont détruites
    hits = pygame.sprite.spritecollide(head, apples, True)
    n = len(hits)
    # pour l'instant on ne fait rien de n, on se contente de l'afficher en console
    if n > 0:
        print("mangé !")

    # dessine les pommes
    all_sprites.draw(screen)

    # met à jour l'affichage
    pygame.display.flip()


# boucle finie
pygame.quit() # lance la fermeture de la fenêtre

Il reste à faire...

  • ne faire apparaître les pommes qu'alignés sur une grille,
  • adapter le mouvement pour obéir à une logique de grille,
  • prévoir les anneaux du serpent et les faire se déplacer convenablement,
  • augmenter la taille du serpent à mesure qu'il mange,
  • faire perdre quand le serpent touche un bord ou quand il se croque lui-même,
  • afficher un score
Aligner sur une grille

Les pommes on un taille Apple.SIZE = 20. On voudrait qu'elle ne puissent avoir que des coordonnées en 0, 20, 40, 60, … de sorte qu'elles ne tombent pas sur n'importe quelles coordonnées mais sur une grille.

C'est rapide à faire : si x est un entier quelconque et qu'on veut le ramener à un multiple de Apple.SIZE, il suffit de faire le calcul x -= x % Apple.SIZE (ou self.SIZE si on fait ce calcul depuis l'intérieur de la classe).

De la même façon, on désire que le serpent ne puisse tourner que quand il est aligné sur une grille. C'est simple également : il suffit n'autoriser le changement de sens que quand les coordonnées sont multiples de SIZE.

self.rect.x % self.SIZE == 0 and self.rect.y % self.SIZE == 0

plusieurs approche sont possibles :

  • ne permettre le changement que quand la tête est alignée. Autrement dit une pression sur un bouton est ignorée si elle n'est pas faite au moment de l'alignement,
  • enregistrer le dernier bouton appuyé mais n'en tenir compte qu'au prochain alignement.
Créer un anneau

On peut créer la classe Anneau dans le module snake.py. L'anneau sera très proche de Head. Voici ce que l'on pourrait faire :

  • définir class Anneau(Head): de sorte que l'anneau hérite de toutes les fonctions de Head.
  • modifier la fonction init : quand on crée un anneau, on le superpose avec le dernier anneau du serpent. Cette anneau sera l'anneau précédent. L'anneau créé devra suivre l'anneau précédent d'une certaine façon et devra donc toujours se rappeler son anneau précédent. Un anneau est donc comme une tête qui a un anneau précédent…
    def __init__(self, precedent):
        super().__init__(precedent.rect.x, precedent.rect.y)
        self.precedent = precedent
    
  • enfin modifier la fonction update_position de façon à, si l'anneau est aligné et si le précédent est à au moins une distance SIZE d'ajuster la vitesse.
    def update_position(self):
        # code disant : si aligné
        #     si précédent assez loin
        #         ajuster la vitesse
        super().update_position() # pour le reste, faire comme avec Head
    
nsi/tds/pygame/snake.txt · Dernière modification : de goupillwiki