Le module Pygame de Python¶
Le module Pygame de Python permet de construire des jeux d'arcade très simple.
Je vous propose ici de découvrir ce module et quelques unes de ses fonctionnalité avec l'objectif de construire le jeu de puissance 4.

Ce que vous devez faire!
Presque tout le code est donné pour fabriquer ce jeu. Vous devez lire ce document, comprendre les stratégies utilisées puis copier et compléter le code Python lorsque les exercices le demandent. Ne soyez pas trop impatient!
Configuration de base¶
Comme dans tout projet Python, il faut importer des bibliothèque nécessaires:
Import du module
| 🐍 Script Python | |
|---|---|
1 2 | |
Tous les projets utilisant Pygame contiennent ces imports.
Initialisation du Jeu
| 🐍 Script Python | |
|---|---|
1 | |
La fonction init() initialise le moteur du jeu. Elle aussi reste obligatoire
La boucle de jeu¶
La boucle de jeu est l'endroit où tous les événements du jeu se produisent, se mettent à jour et sont affichés à l'écran.
La boucle de Jeu
| 🐍 Script Python | |
|---|---|
1 2 3 4 5 6 | |
Une fois la configuration initiale et l'initialisation des variables terminées, la boucle de jeu commence là où le programme continue de boucler encore et encore jusqu'à ce qu'un événement de type QUIT se produise.
Un « événement » Pygame se produit lorsque l'utilisateur effectue une action spécifique, comme cliquer sur sa souris ou appuyer sur un bouton du clavier: la fonction pygame.event.get() retourne la liste des événements créés.
Creation de l'écran de jeu¶
Pour chaque jeu, il faut créer une fenêtre de dimension donnée:
Création de la fenêtre
screen = pygame.display.set_mode((700, 600)) # largeur: 700 pixels hauteur: 600 pixels
icon = pygame.image.load("logoNSI.png") # charger une nouvelle image comme icone
pygame.display.set_icon(icon) #changer l'icone
pygame.display.set_caption("Puissance 4") #changer le titre
La fenêtre a par défaut un icône et un titre que j'ai changé ici.
Je vais créer deux constantes BLEU et WHITE pour colorer le background d'une part et les emplacements des jetons d'autre part:
Création des couleurs
BLEU = (0, 0, 255)
WHITE = (255, 255, 255)
JAUNE = (255, 255, 0)
ROUGE = (255, 0, 0)
À ce stade, le code complet est donc:
Premiers fichiers
- Télécharger le logo de la NSI (ici) dans un dossier
JeuPuissance - Copier le code ci-après dans un fichier Python et enregistrez-le dans le dossier précédent sous le nom
jeu.py.
Code complet à recopier
| 🐍 Script Python | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Définir les emplacements des jetons¶
Le puissance 4 traditionnel est composé de 7 colonnes et 6 lignes: c'est la raison pour laquelle j'ai choisi la taille de l'écran de largeur 700pixels (\(7\times 100\)) et de hauteur 600 pixels(\(6\times 100\)).
Maintenant, nous allons définir les 42 emplacements pour les jetons.
Pour déssiner un cercle dans Pygame, il faut utiliser la commande pygame.draw.circle() qui prend en paramètres l'endroit où on le construit, la couleur, les coordonnées du centre et le rayon. On obtient:
Premier emplacement
pygame.draw.circle(screen, WHITE, (50, 50), 40)
Pour construire les autres emplacements, nous pourrions le faire à la main: il y aurait autant d'instructions pygame.draw.circle() que de jetons, soit 42. Il y a certainement mieux à faire...
Astuce
En informatique, chaque fois que l'on répète du code on utilise une boucle.
Nous allons donc utiliser une première boucle pour construire les 7 premiers jetons de la ligne du haut.
Remarquons que les coordonnées de ces disques sont (50, 50), (100, 50), (150, 50), (200, 50), ..., (650, 50): en mathématiques, nous dirions que les abscisses de ces points sont en progression arithmétique (ou linéaire...) et que le i-ième jeton a pour coordonnées (50 + 100*i, 50).
Voici donc la construction des 7 premiers jetons avec une boucle:
Première ligne
for i in range(7):
pygame.draw.circle(screen, WHITE, (50 + 100*i, 50), 40)

Mais finalement pourquoi ne pas boucler aussi sur la hauteur??
Construction des emplacements
for j in range(6):
for i in range(7):
pygame.draw.circle(screen, WHITE, (50 + 100*i, 50 + 100*j), 40)

Code à compléter
- Ajouter les deux boucles
forau code précédent à la fin de la partieInitialisation. - Exécuter le code, vous devriez obtenir l'interface prévue.
À ce stade, l'interface graphique est prête à recevoir les instructions des joueurs...
Un jeton sur le canvas¶
Nous allons jouer à la souris, c'est plus simple. Une question vient alors...
Souris et Canvas
Comment tracer des cercles jaunes ou rouges dans le canvas?
Il faut se renseigner pour cela et on trouve rapidement comment faire.
La souris dans Pygame
Les clics de souris génèrent des événements dont le type est pygame.MOUSEBUTTONUP et pygame.MOUSEBUTTONDOWN.
Ces deux événements disposent d'un attribut pos qui permet de récupérer sous forme de tuple, les coordonnées du clic de souris dans le canvas.
Code à modifier
Modifier la boucle d'événements du précédent code pour obtenir ceci:
while True: #boucle infinie
for event in pygame.event.get(): #pour tous les événements
if event.type == QUIT: #si l'événement quit est déclenché
pygame.quit()
sys.exit() #fin du jeu
if event.type == pygame.MOUSEBUTTONDOWN: # si clic de souris
print(event.pos) #alors affichage des coordonnées dans la console
pygame.display.update()
À chaque clic de souris, vous voyez apparaître les coordonnées de ce clic dans la console.
On peut donc maintenant, tracer un jeton jaune ou rouge au clic de souris en ajoutant l'instruction suivante:
Code à compléter
Ajouter dans le if précédent, après le print(event.pos), l'instruction suivante:
pygame.draw.circle(screen, JAUNE, event.pos, 40)
Bon, on se rapproche mais quelque chose ne va pas!
Jeton mal positionné!
Il faudrait que le jeton soit au milieu de son emplacement blanc et pas à cheval sur la grille.
Pas de soucis, les mathématiques vont encore nous aider...
La grille est divisée en sept colonnes et il faudrait que quand je clique sur une colonne le disque soit automatiquement centré dans cette colonne. Donc:
- si l'abscisse du clic de souris est entre
0et100, on doit le remplacer par50 - si l'abscisse du clic de souris est entre
100et200, on doit le remplacer par150 - si l'abscisse du clic de souris est entre
200et300, on doit le remplacer par250 - si l'abscisse du clic de souris est entre
300et400, on doit le remplacer par350 - si l'abscisse du clic de souris est entre
400et500, on doit le remplacer par450 - si l'abscisse du clic de souris est entre
500et600, on doit le remplacer par550 - si l'abscisse du clic de souris est entre
600et700, on doit le remplacer par650
Question
Que fait-on en informatique lorsqu'on est amené à répéter des instruction??
ON BOUCLE
On peut donc réduire les instructions suivantes par une boucle qui commencerait par for i in range(7): car on répète 7fois. Remarquons enfin que le remplaçant
est la moyenne des deux bornes de l'intervalle.
Je vais donc créer une fonction Python, nommée cible qui prend en paramètre un entier(l'abscisse ou l'ordonnée du clic de souris) et qui retourne l'une des valeurs précédentes.
def cible(valeur):
for i in range(7):
if 100*i < valeur < 100*(i + 1):
valeur = (100*i + 100*(i + 1))//2
return valeur
(valeur//100)*100 + 50 retourne la valeur attendue...
J'appelle ensuite cette fonction dans la construction des disques jaunes.
pygame.draw.circle(screen, JAUNE, (cible(event.pos[0]), cible(event.pos[1])), 40)
Voici le code:
Utiliser une fonction
Remplacer tout le code existant par celui qui suit.
nouveau code
import pygame, sys
from pygame.locals import *
#----- CONSTANTE DU JEU --------------------#
BLEU = (0, 0, 255)
WHITE = (255, 255, 255)
ROUGE = (255, 0, 0)
JAUNE = (255, 255, 0)
# ----- INITIALISATION ------------------- #
pygame.init()
screen = pygame.display.set_mode( (700, 600))
icon = pygame.image.load("logoNSI.png")
pygame.display.set_icon(icon)
pygame.display.set_caption("Puissance 4")
screen.fill(BLEU)
for j in range(6):
for i in range(7):
pygame.draw.circle(screen, WHITE, (50 + 100*i, 50 + 100*j), 40)
# --- FONCTIONS ----------- #
def cible(valeur):
for i in range(7):
if 100*i < valeur <100*(i + 1):
valeur = (100*i + 100*(i + 1))//2
return valeur
# ---------------------------------------- #
while True: #boucle infinie
for event in pygame.event.get(): #pour tous les événements
if event.type == QUIT: #si l'événement quit est déclenché
pygame.quit()
sys.exit() #fin du jeu
if event.type == pygame.MOUSEBUTTONDOWN:
print(event.pos)
pygame.draw.circle(screen, JAUNE, (cible(event.pos[0]), cible(event.pos[1])), 40)
pygame.display.update()
C'est mieux mais ce n'est pas encore gagné...
Jeton encore mal positionné!
Il faudrait que le jeton soit au milieu et au plus bas de la colonne dans laquelle je clique pour simuler la gravité...
Il me faudrait une mémoire qui retient le nombre de disque construit dans chaque colonne. Pour cela, je vais utiliser une liste mem de 7 éléments( car 7 colonnes)initialisés à 0.
mem = [0, 0, 0, 0, 0, 0, 0]
mem = [0 for i in range(7)]
Astuce
À chaque fois que je construis un jeton dans une colonne, j'incrémente le nombre dans la liste correspondant à la colonne!
Voici alors l'état de la liste après quelques coups joués:
mem = [0, 1, 2, 4, 1, 2, 0]

Cette liste me sert à déterminer les ordonnées des jetons jaunes (ou rouges).
pygame.draw.circle(screen, JAUNE, (cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100), 40)
mem[event.pos[0]//100] += 1
Code pas facile à comprendre mais tentons tout de même une explication.
- l'abscisse d'un jeton est donnée par l'instruction
cible(event.pos[0]) - l'ordonnée est par défaut, lorsque la colonne est vide, à 550. D'où le
550. Mais il faut lui enlever100autant de fois qu'il y a de jetons présents dans la colonne: or ce nombre est précisémentmem[event.pos[0]//100].
Division entière
L'instruction event.pos[0]//100 réalise la division entière de event.pos[0] par 100 qui donnera dans notre cas soit 0,1,... ou 6, les indices des colonnes!
Donc au final, l'ordonnée d'un jeton dans le canvas est 550 - mem[event.pos[0]//100]*100.
Re-voici le code complet pour l'instant que je vous demande de recopier.
Recopier le code
Remplacer tout le code existant par celui qui suit.
nouveau code
import pygame, sys
from pygame.locals import *
#----- CONSTANTE DU JEU --------------------#
BLEU = (0, 0, 255)
WHITE = (255, 255, 255)
ROUGE = (255, 0, 0)
JAUNE = (255, 255, 0)
mem = [0 for i in range(7)]
# ----- INITIALISATION ------------------- #
pygame.init()
screen = pygame.display.set_mode( (700, 600))
icon = pygame.image.load("logoNSI.png")
pygame.display.set_icon(icon)
pygame.display.set_caption("Puissance 4")
screen.fill(BLEU)
for j in range(6):
for i in range(7):
pygame.draw.circle(screen, WHITE, (50 + 100*i, 50 + 100*j), 40)
# --- FONCTIONS ----------- #
def cible(valeur):
for i in range(7):
if 100*i < valeur <100*(i + 1):
valeur = (100*i + 100*(i + 1))//2
return valeur
# ---------------------------------------- #
while True: #boucle infinie
for event in pygame.event.get(): #pour tous les événements
if event.type == QUIT: #si l'événement quit est déclenché
pygame.quit()
sys.exit() #fin du jeu
if event.type == pygame.MOUSEBUTTONDOWN:
pygame.draw.circle(screen, JAUNE, (cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100), 40)
mem[event.pos[0]//100] += 1
print(mem)
pygame.display.update()
Alternance des couleurs¶
Les deux joueurs jouent l'un après l'autre: il faut donc coder l'alternance des couleurs. Voici ma stratégie (il en existe d'autres!):
Jaune ou rouge
Je crée une variable tour initialisée à 0 et incrémentée à chaque placement d'un jeton.
Si la valeur de la variable est paire alors la couleur sera JAUNE. Sinon elle sera ROUGE. Voici mon code:
if tour%2 == 0:
pygame.draw.circle(screen, JAUNE, (cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100), 40)
else:
pygame.draw.circle(screen, ROUGE, (cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100), 40)
tour += 1
Astuce
Pour savoir si la variable tour contient une valeur paire on peut utiliser le modulo 2 (tour%2), reste de la division euclidienne du nombre par 2. Si le reste de la division euclidienne d'un nombre par 2 est 0 alors ce nombre est pair. Sinon il est impair!
Alternance des couleurs
- Ajouter la variable
tourinitialisée à 0 au début du programme principal dans la partieCONSTANTE DU JEU. - Modifier le programme et ajouter le code précédent au bon endroit.
Maintenant, il reste à savoir si un joueur a gagné: pas évident à coder.
Et le gagnant est...¶
Un joueur gagne lorsqu'il aligne quatre jetons les uns à côtés des autres. Regardons de plus près...

On peut gagner en horizontal, en vertical ou en diagonal.
Le cas vertical
Le cas du gain vertical est facile à gérer: on ne peut gagner dans ce mode si le dernier jeton est positionné sur une pile de trois autres.
Un jeton est positionné dans le canvas en fonction des coordonnées de son centre: ce sont ces données que nous exploiterons.
Memoriser les emplacements¶
Memoriser les emplacements
Il faut une structure pour conserver les coordonnées des jetons, pour les JAUNES comme pour les ROUGES.
Nous allons donc créer naturellement deux listes place_jeton_jaune et place_jeton_rouge de coordonnées, qui seront complétées à chaque construction d'un jeton.
place_jeton_jaune = []
place_jeton_rouge = []
x, y = cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100
if tour%2 == 0:
pygame.draw.circle(screen, JAUNE, (x, y), 40)
place_jeton_jaune.append((x, y))
else:
pygame.draw.circle(screen, ROUGE, (x, y), 40)
place_jeton_rouge.append((x, y))
Compléter le code
- Ajouter les deux listes vides dans la partie
CONSTANTE DU JEU. - Puis intégrer les instructions précédentes au programme existant.
Gain vertical¶
Un jeton étant placé, il suffit de tester si il y en a un en dessous, puis un autre puis un autre. Voici mon code:
#exemple pour les jaunes
gain = 0
x, y = cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100
for i in range(4):
if (x, y + 100*i) in place_jeton_jaune:
gain += 1
else:
break
Le break casse la boucle: si il n'y a pas de jeton en dessous, inutile de continuer la boucle pour tester d'autres cas. En revanche, si à la fin de la boucle la variable gain vaut 4 alors c'est gagné!
La fonction gain_vertical prend en paramètres les coordonnées x,y du clic et une liste puis retourne le booléen True en cas de gain vertical et False sinon:
def gain_vertical(x, y , liste):
gain = 0
for i in range(4):
if (x, y + 100*i) in liste:
gain += 1
if gain == 4:
return True
else:
return False
Gain horizontal¶
Un jeton étant positionné, il faut regarder à sa droite et éventuellement à sa gauche si il devient le quatrième jeton d'un alignement. Voici la fonction correspondante:
def gain_horizontal(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
Gain diagonal¶
Pour les diagonales, on s'inspire du code précédent et on obtient les deux fonctions suivantes:
def gain_diagonal_1(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y + 100*i) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y - 100*(i + 1)) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
def gain_diagonal_2(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y - 100*i) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y + 100*(i + 1)) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
Déclaration du gagnant¶
Nous allons maintenant utiliser ces fonctions pour tester à chaque coup si un joueur a gagné.
Avant cela, nous allons créer une variable globale flag initialisée à 0. Elle sert à contrôler le déroulement du jeu:
- si
flagest à 0 je jeu continue - si
flagpasse à 1 (ou autre chose...) le jeu s'arrête!
Question
Comment arrêter le jeu?
On pourrait stopper la boucle infinie while True mais les événements de type QUIT ne seraient plus accessibles. On va cependant conditionner les actions de type pygame.MOUSEBUTTONDOWN à la valeur de la variable flag. On obtient alors le code maintenant complet ci-dessous:
Code complet
Voici le code complet du jeu. Recopiez-le!
Code complet pour jouer
import pygame, sys
from pygame.locals import *
#----- CONSTANTE DU JEU --------------------#
BLEU = (0, 0, 255)
WHITE = (255, 255, 255)
JAUNE = (255, 255, 0)
ROUGE = (255, 0, 0)
tour = 0
mem = [0 for i in range(7)]
place_jeton_jaune = []
place_jeton_rouge = []
gain_j = 0
gain_r = 0
flag = 0
# ----- INITIALISATION CONSTRUCTEURS------------------- #
pygame.init()
screen = pygame.display.set_mode( (700, 600))
icon = pygame.image.load("logoNSI.png")
pygame.display.set_icon(icon)
pygame.display.set_caption("Puissance 4")
screen.fill(BLEU)
for j in range(6):
for i in range(7):
pygame.draw.circle(screen, WHITE, (50 + 100*i, 50 + 100*j), 40)
# --- FONCTIONS ----------- #
def cible(valeur):
for i in range(7):
if 100*i < valeur <100*(i + 1):
valeur = (100*i + 100*(i + 1))//2
return valeur
def gain_vertical(x, y , liste):
gain = 0
for i in range(4):
if (x, y + 100*i) in liste:
gain += 1
if gain == 4:
return True
else:
return False
def gain_horizontal(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
def gain_diagonal_1(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y + 100*i) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y - 100*(i + 1)) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
def gain_diagonal_2(x, y, liste):
gain = 0
for i in range(4):
if (x + 100*i, y - 100*i) in liste:
gain += 1
else:
break
for i in range(3):
if (x - 100*(i + 1), y + 100*(i + 1)) in liste:
gain += 1
else:
break
if gain >= 4:
return True
else:
return False
while True: #boucle infinie
for event in pygame.event.get(): #pour tous les événements
if event.type == QUIT: #si l'événement quit est déclenché
pygame.quit()
sys.exit() #fin du jeu
if event.type == pygame.MOUSEBUTTONDOWN:
if flag == 0:
x, y = cible(event.pos[0]), 550 - mem[event.pos[0]//100]*100
if tour%2 == 0:
pygame.draw.circle(screen, JAUNE, (x, y), 40)
place_jeton_jaune.append((x, y))
else:
pygame.draw.circle(screen, ROUGE, (x, y), 40)
place_jeton_rouge.append((x, y))
if gain_vertical(x, y, place_jeton_jaune) or gain_horizontal(x, y, place_jeton_jaune) or gain_diagonal_1(x, y, place_jeton_jaune) or gain_diagonal_2(x, y, place_jeton_jaune):
print("Jaune a gagné")
flag = 1
if gain_vertical(x, y, place_jeton_rouge) or gain_horizontal(x, y, place_jeton_rouge) or gain_diagonal_1(x, y, place_jeton_rouge) or gain_diagonal_2(x, y, place_jeton_rouge):
print("Rouge a gagné")
flag = 1
tour += 1
mem[event.pos[0]//100] += 1
pygame.display.update()
Un peu de musique et de bruitage¶
Pygame permet d'utiliser de la musique pour dynamiser votre jeu.
On suppose que vous avez trois fichiers musicaux:
musique_ambiance.mp3pour une musique de fondbruit.wavpour un bruitage quand vous positionner un jetonapplaudissement.wavpour applaudir quand un joueur a gagné
J'ai pour ma part, rangé ces trois musiques dans un dossier son.
Pour jouer de la musique, il faut d'abord initialiser le contexte avec les instructions suivantes:
pygame.mixer.init()
pygame.mixer.set_num_channels(3) # creation de 3 channels sonores
puis charger la musique dans une variable:
musique_de_fond = "./son/musique_ambiance.mp3"
ambiance = pygame.mixer.Sound(musique_de_fond)
ambiance.set_volume(0.2)
ch1 = pygame.mixer.find_channel()
ch1.play(ambiance)
ch1.stop()
Animation musicale
- Chercher sur la toile, trois musiques libres de droits que vous renommerez et sauvegarderez dans un dossier
sonau même niveau d'arborescence que votre fichier principal Python. Moi j'ai pioché ici. - En s'inspirant des routines précédentes, jouer les musiques en les associant aux actions correspondantes. Par exemple, le bruitage
bruitest associer au placement d'un jeton...