Cette page fait partie du cours de polytech PeiP1 et 2 Bio
7. Mise en pratique : Tennis ou jeu de Pong
7.1. Introduction
Pong est l'un des premiers jeux vidéo d'arcade
commercialisé en 1972. Il s'inspire du tennis de table. Devant l'engouement généré par le jeu, Pong est porté sur une console de salon à partir de 1975.
Dans la même veine, il a existé beaucoup plus tard l'ancêtre de la Nintendo switch
Le package PyGame permet de réaliser avec Python des jeux en quelques heures. Nous allons donc
créer un jeu de pong rudimentaire en utilisant des classes pour gérer la balle et la raquette
Voici ce à quoi ressemblera l'interface du jeu que nous allons créer :
7.2. Objets et classes
La Programmation (Orientée) Objet (POO) est un paradigme de programmation dont le but est de simplifier le codage des concepts liés à un domaine.
Elle se fonde sur plusieurs principes dont les principaux sont :
- la classe qui permet de créer des structures de données :
- l'encapsulation des données
- constructeur et destructeur
- attributs et méthodes
- l'héritage qui permet la réutilisabilité
- la généricité qui permet la généralisation
On remarquera qu'en Python la paradigme Objet est plutôt contraignant notamment avec l'utilisation
du mot-clé self.
Par exemple, dans un jeu de quête dans un univers en deux ou trois dimensions, on trouvera :
- des personnages qui correspondent aux joueurs (guerriers, elfes, magiciens)
- des monstres (gobelins, orcs, dragons)
- des armes (épées, armures, boucliers)
En ce qui concerne les personnages et les monstres, il ont tous une caractéritique commune : le nombre
de points de vie par exemple.
Les personnages auront des capacités comme la force, la dextérité qui autorisent les guerriers à porter
des armes et des armures lourdes alors que les magiciens qui jetteront des sorts n'auront pas la force de
porter des armes lourdes.
En modélisation objet cela permet de créer des classes qui sont des structures de données dotées de
méthodes (sous-programmes) qui peuvent être héritées par d'autres classes.
Pour utiliser une classe il faut en créer un représentant qui s'appelle une instance de la
classe.
7.3. Pygame
Nous allons mettre en oeuvre le concept objet en définissant une classe Balle et une classe Raquette pour le jeu de Pong.
7.3.1. Ouverture d'une fenêtre graphique
Voici le code à saisir pour créer une fenêtre graphique dans laquelle nous dessinerons :
pygame_initialisation.py
import pygame
from pygame.locals import *
HAUTEUR_FENETRE = 480
LARGEUR_FENETRE = 640
fenetre = pygame.display.set_mode( (LARGEUR_FENETRE, HAUTEUR_FENETRE) )
pygame.display.set_caption("Titre de la fenêtre")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
La fenêtre s'affiche mais disparait aussitôt. il faut créer une boucle pour la garder ouverte et rafraichir
l'affichage dans la fenêtre :
pygame_fenetre_boucle.py
import pygame
from pygame.locals import *
HAUTEUR_FENETRE = 480
LARGEUR_FENETRE = 640
fenetre = pygame.display.set_mode( (LARGEUR_FENETRE, HAUTEUR_FENETRE) )
pygame.display.set_caption("Titre de la fenêtre")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
while True:
pygame.display.update()
L'écran reste noir et rien ne s'affiche. Dans la boucle while il faut réaliser une série d'actions : afficher des images, du texte, etc.
pygame_fenetre_balle.py
import pygame
from pygame.locals import *
HAUTEUR_FENETRE = 480
LARGEUR_FENETRE = 640
fenetre = pygame.display.set_mode( (LARGEUR_FENETRE, HAUTEUR_FENETRE) )
pygame.display.set_caption("Titre de la fenêtre")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
x = LARGEUR_FENETRE // 2
y = HAUTEUR_FENETRE // 2
rayon = 10
while True:
# dessine la balle
pygame.draw.circle(fenetre, (255,255,255), (x, y), rayon)
pygame.display.update()
# attente du clic souris sur x pour fermer la fenêtre
# ou Alt+F4 sous Linux
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
Enfin il est nécessaire de déplacer les éléments dans la fenêtre :
pygame_fenetre_balle_deplace_evenement.py
import pygame
from pygame.locals import *
HAUTEUR_FENETRE = 480
LARGEUR_FENETRE = 640
fenetre = pygame.display.set_mode( (LARGEUR_FENETRE, HAUTEUR_FENETRE) )
pygame.display.set_caption("Titre de la fenêtre")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
# données de la balle
x = LARGEUR_FENETRE // 2
y = HAUTEUR_FENETRE // 2
rayon = 10
# vecteur de déplacement
vx = 1
vy = 1
while True:
# temporise
pygame.time.delay(15)
# dessine le fond de l'écran en noir
fenetre.fill( (0,0,0) )
# dessine la balle
pygame.draw.circle(fenetre, (255,255,255), (x, y), rayon)
# nouvelle position
x = x + vx
y = y + vy
if x >= LARGEUR_FENETRE or x <= 0:
vx = -vx
if y >= HAUTEUR_FENETRE or y <= 0:
vy = -vy
pygame.display.update()
Exercice 7.1
Améliorer la gestion du rebond car la balle sort de l'écran.
7.3.2. Gestion des événements
Afin de gérer les évènements (appui sur une touche, clic souris, etc), il faut énumérer chacun des événements
reçus et vérifier celui qui nous intéresse.
Sur l'exemple suivant on test l'appui sur la touche 'A', un clic souris ou le fait de quitter la fenêtre en
cliquant sur l'icone correspondante en haut de la fenêtre.
pygame_fenetre_balle_deplace.py
import pygame
import sys
from pygame.locals import *
HAUTEUR_FENETRE = 480
LARGEUR_FENETRE = 640
fenetre = pygame.display.set_mode( (LARGEUR_FENETRE, HAUTEUR_FENETRE) )
pygame.display.set_caption("Titre de la fenêtre")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
# données de la balle
x = LARGEUR_FENETRE // 2
y = HAUTEUR_FENETRE // 2
rayon = 10
# vecteur de déplacement
vx = 1
vy = 1
while True:
# temporise
pygame.time.delay(15)
# dessine le fond de l'écran en noir
fenetre.fill( (0,0,0) )
# dessine la balle
pygame.draw.circle(fenetre, (255,255,255), (x, y), rayon)
# nouvelle position
x = x + vx
y = y + vy
if x >= LARGEUR_FENETRE or x <= 0:
vx = -vx
if y >= HAUTEUR_FENETRE or y <= 0:
vy = -vy
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == pygame.K_a:
vx = -vx
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
vy = -vy
pygame.display.update()
7.4. Le jeu de tennis (à la Pong)
Pour la création du jeu de tennis, nous allons organiser notre code en plusieurs fichiers :
- un fichier tennis.py qui sera le programme principal
- un fichier constantes.py qui contiendra les constantes utilisées par les autres fichiers
(hauteur et largeur de fenêtre, couleurs)
- un fichier balle.py qui implantera une classe Balle qui représentera une balle qui rebondit
sur les murs et la raquette
- un fichier raquette.py qui représente une Raquette qui se déplace verticalement et dont le
but et de taper dans la balle
7.4.1. Les packages utilisés
On commence par introduire les packages (bibliothèques) qui seront utilisées :
# paquet pygame pour gestion des graphismes et des sons
import pygame
# constantes de pygame pour le clavier
from pygame.locals import *
# paquet system pour quitter le jeu
import sys
# paquet random pour générer une position aléatoire de la balle au début
import random
7.4.2. Les constantes du jeu
On définit la hauteur et la largeur de la fenêtre ainsi que l'abscisse du mur
qui sera situé à droite.
On définit également un jeu de couleurs qui utilisent des nuances proches de
celles du tournoi du grand chelem de Roland Garros.
# dimensions de la fenêtre
LARGEUR_FENETRE = 640
HAUTEUR_FENETRE = 480
# abscisse pour le mur
COORD_X_MUR = LARGEUR_FENETRE-20
# couleurs
BLANC_ROLAND_GARROS = 0
NOIR = 1
ORANGE_ROLAND_GARROS = 2
VERT_ROLAND_GARROS = 3
couleurs = [ (217,223,226), (0,0,0), (0xc8, 0x62, 0x2f), (0x22, 0x3f, 0x34) ]
7.4.3. La classe Balle
La balle se déplace dans l'aire de jeu et rebondit sur les bords haut, bas et droit de la fenêtre.
Par contre sur la gauche elle doit rebondir sur la raquette pour que le jeu continue.
On crée une classe Balle que l'on instanciera par la suite. La classe est une structure de donnée et on pourra créer autant de balles que nécessaire par la suite mais dans le jeu qui
nous intéresse il n'y a qu'une seule balle.
Pour la balle, on doit définir les méthodes suivantes :
- un constructeur __init__
- dessine pour dessiner la balle dans la fenêtre courante
- déplace pour déplacer la balle dans la fenêtre courante
- rebond_x pour les rebonds sur l'axe des x (mur, raquette)
- rebond_y pour les rebonds sur l'axe des y (bords fenetre)
- perdue pour vérifier si la balle est toujours dans la zone de jeu
On notera l'utilisation du mot-clé self qui permet de faire référence aux
variables et méthodes de la classe.
balle.py
import random
import pygame
from constantes import *
"""
classe Balle
une balle est définie par les attributs (champs) suivants :
- sa position x, y dans la fenêtre en pixels
- son rayon en pixel
- son vecteur de déplacement (dx, dy) en pixels
- une référence à la fenêtre du jeu pour pouvoir dessiner la balle
"""
class Balle(object):
"""
initialisation de la balle
"""
def __init__(self, fenetre):
self.x = 100
self.y = random.randint(20, HAUTEUR_FENETRE-20)
self.rayon = 10
self.dx = 1
self.dy = 1
self.fenetre = fenetre
"""
dessine la balle sous forme d'un cercle dans la fenêtre graphique
avec une couleur blanche
"""
def dessine(self):
pygame.draw.circle(self.fenetre, couleurs[BLANC_ROLAND_GARROS], \
(self.x, self.y), self.rayon)
"""
déplace la balle en gérant les rebonds
"""
def deplace(self):
self.x += self.dx
self.y += self.dy
# rebond sur le mur à droite
# TODO
# rebond sur les bords haut et bas
# TODO
"""
rebond sur l'axe des x
"""
def rebond_x(self):
self.dx = -self.dx
"""
rebond sur l'axe des y
"""
def rebond_y(self):
self.dy = -self.dy
"""
indique si la balle est en dehors de l'aire de jeu
"""
def perdue(self):
return self.x < 0
7.4.4. La classe Raquette
La raquette se déplace verticalement sur le bord gauche. On créera une instance de la raquette.
Pour la raquette, on doit définir les méthodes suivantes :
- un constructeur __init__
- dessine pour dessiner la raquette dans la fenêtre courante
- déplace pour déplacer la raquette dans la fenêtre courante
- vers_le_haut pour modifier le déplacement de la raquette vers le haut
- vers_le_bas pour modifier le déplacement de la raquette vers le bas
- contact(balle) pour vérifier si la balle touche la raquette
raquette.py
import random
import pygame
from constantes import *
"""
classe raquette
une raquette est définie par les attributs (champs) suivants :
- sa position x, y dans la fenêtre en pixels, sachant que x est fixé
- sa largeur et sa hauteur en pixels
- son vecteur de déplacement en dy en pixels
- une référence à la fenêtre du jeu pour pouvoir dessiner la balle
"""
class Raquette(object):
"""
initialisation de la raquette
"""
def __init__(self, fenetre):
self.hauteur = 80
self.largeur = 20
self.x = 20
self.y = (HAUTEUR_FENETRE - self.hauteur) // 2
self.dy = 2
self.fenetre = fenetre
"""
dessine la raquette dans la fenêtre graphique
en blanc sous forme d'un rectangle
"""
def dessine(self):
pygame.draw.rect(self.fenetre, couleurs[BLANC_ROLAND_GARROS], \
(self.x, self.y, self.largeur, self.hauteur), 0)
"""
déplace la raquette suivant le vecteur de déplacement en inversant la direction
si on arrive en haut ou en bas de la fenêtre
"""
def deplace(self):
self.y += self.dy
# si la raquette est en haut on change la direction du déplacement
# TODO
# si la raquette est en bas on change la direction du déplacement
# TODO
"""
modifie le vecteur de déplacement pour que la raquette aille vers le haut
"""
def vers_le_haut(self):
self.dy = -1
"""
modifie le vecteur de déplacement pour que la raquette aille vers le bas
"""
def vers_le_bas(self):
self.dy = +1
"""
vérifie s'il y a contact entre la raquette et la balle
"""
def contact(self, balle):
# TODO
7.4.5. Le jeu
On crée la fenêtre de jeu, on utilise la fonte courante et on charge les sons
qui seront utilisé pour le jeu :
Le jeu se compose de trois parties :
- l'écran d'accueil qui indique quelles sont les touches pour jouer
- le jeu de tennis
- la fin de partie qui affiche le score obtenu par le joueur
tennis.py
###########################################################
# librairies
###########################################################
import pygame
from pygame.locals import *
import sys
import random
from constantes import *
import balle
import raquette
###########################################################
# fonctions utiles
###########################################################
"""
afficher un texte au centre de l'écran
si la coordonnée y est égale à -1, on place le texte
au milieu de la fenetred'affichage
on peut également spécifier la couleur qui est l'orange
par défaut
"""
def affiche_texte_centre(texte, y=-1, couleur=None):
if couleur == None:
couleur = couleurs[ ORANGE_ROLAND_GARROS ]
rendu = fonte.render(texte, True, couleur)
rectangle = rendu.get_rect()
if y == -1:
rectangle.center = ((LARGEUR_FENETRE) / 2 , (HAUTEUR_FENETRE) / 2)
else:
rectangle.center = ((LARGEUR_FENETRE) / 2 , y)
fenetre.blit(rendu, rectangle)
"""
afficher un texte au centre de l'écran en précisant
les coordonnées x et y
on peut également spécifier la couleur qui est l'orange
par défaut
"""
def affiche_texte(texte, x, y, couleur=None):
if couleur == None:
couleur = couleurs[ ORANGE_ROLAND_GARROS ]
rendu = fonte.render(texte, True, couleur)
rectangle = rendu.get_rect()
rectangle.center = (x, y)
fenetre.blit(rendu, rectangle)
"""
quitter le programme
on arrête la musique d'ambiance et on libère les resources
utilisées par pygame
"""
def quitter():
pygame.mixer.music.stop()
pygame.quit()
sys.exit()
"""
attend qu'on tape une touche au clavier
on parcourt les événements (clavier, souris) mais en ne
traitant que l'appui sur une touche (KEYDOWN)
"""
def touche_clavier():
for event in pygame.event.get():
if event.type == KEYDOWN:
# Ctrl-C pour quitter le jeu
if event.key == pygame.K_c and pygame.key.get_mods() & pygame.KMOD_CTRL:
quitter()
# retourner la touche pressée
return event.key
# sinon, ne rien retourner (valeur nulle)
return None
"""
on attend que l'utilisateur tape une touche du clavier avant
d'aller plus loin. A chaque itération on redessine la fenêtre
"""
def attente():
while touche_clavier() == None:
pygame.display.update()
###########################################################
# initialisation du jeu
###########################################################
pygame.init()
fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE))
pygame.display.set_caption("Tennis")
pygame.font.init()
fonte = pygame.font.Font(None, 30)
crash_sound = pygame.mixer.Sound("glass_break.wav")
pygame.mixer.music.load("music.mp3")
pygame.mixer.music.play()
###########################################################
# définition des variables du jeu
###########################################################
balle = balle.Balle(fenetre)
raquette = raquette.Raquette(fenetre)
# nombre de rebonds sur la raquette
score = 0
# variable booléenne à vrai si on est en pause
pause = False
###########################################################
# écran d'accueil
###########################################################
fenetre.fill(couleurs[NOIR])
affiche_texte_centre("Appuyez sur une touche pour commencer", 100)
affiche_texte_centre("Flèche haut pour faire monter la raquette", 140)
affiche_texte_centre("Flèche bas pour faire descendre la raquette", 170)
affiche_texte_centre("p pour pause", 200)
affiche_texte_centre("Ctrl-C pour quitter", 230)
attente()
###########################################################
# écran de jeu : boucle principale
###########################################################
while True:
# temporisation : arrêt de l'exécution du programme pendant 5 ms
pygame.time.delay(5)
# -------------------------------
# dessine les différents éléments
# - fond d'écran
# - mur de droite
# - balle
# - raquette
# - score
# -------------------------------
# dessine le fond en orange
fenetre.fill( couleurs[ ORANGE_ROLAND_GARROS ] )
# dessine le mur de droite
pygame.draw.rect(fenetre, couleurs[ VERT_ROLAND_GARROS ], (COORD_X_MUR, 0, 20, HAUTEUR_FENETRE), 0)
balle.dessine()
raquette.dessine()
# affichage du score
texte = "Score = {}".format(score)
affiche_texte_centre(texte, 20, couleurs[VERT_ROLAND_GARROS])
pygame.display.update()
# -------------------------------
# gestion des événements clavier
# - flèche vers le haut
# - flèche vers le bas
# - 'p' pour pause
# -------------------------------
clavier = touche_clavier()
if clavier == K_UP:
raquette.vers_le_haut()
if clavier == K_DOWN:
raquette.vers_le_bas()
if clavier == K_p:
pause = not pause
# si on a appuyé sur 'p' et qu'on met le jeu en pause
# il ne faut pas aller plus loin c'est à dire qu'il ne
# faut pas déplacer ni la raquette, ni la balle
if pause:
continue
# ------------------------------
# déplacement de la raquette et de la balle
# vérification de fin de jeu
# ------------------------------
raquette.deplace()
balle.deplace()
# si balle en dehors de l'aire de jeu, on quitte la boucle
# de jeu
# TODO
# si la balle touche la raquette, on réalise le rebond
# TODO
################################
# écran de fin de partie
################################
fenetre.fill( couleurs[NOIR] )
texte = "Votre score est de {} points".format(score)
affiche_texte_centre(texte)
affiche_texte_centre("Appuyez sur une touche pour terminer", 200)
pygame.mixer.Sound.play(crash_sound)
pygame.mixer.music.stop()
attente()
quitter()
7.5. Multi Pong
Exercice 7.2
A titre d'exercice vous pouvez modifier le jeu de manière à lancer plusieurs balles au début
du jeu. Il suffit de créer une liste de balles et de travailler avec cette liste plutôt qu'avec
une seule balle.
Exercice 7.3
On peut également faire en sorte qu'à mesure que le temps passe :
- les balles se déplacent plus vite
- la hauteur de la raquette diminue