import copy
import solutions
Projet 1 - Puissance 4
Dans ce projet nous allons implémenter un puissance 4 avec une interface graphique assez sommaire. Pour y arriver, nous allons utiliser les objets fondamentaux de Python.
Règles du jeu
Le but du jeu de Puissance 4 est d’aligner une suite de 4 pions de même couleur sur une grille comptant 6 rangées et 7 colonnes. Chaque joueur dispose de 21 pions d’une couleur (par convention, en général jaune ou rouge). Tour à tour, les deux joueurs placent un pion dans la colonne de leur choix, le pion coulisse alors jusqu’à la position la plus basse possible dans la dite colonne à la suite de quoi c’est à l’adversaire de jouer. Le vainqueur est le joueur qui réalise le premier un alignement (horizontal, vertical ou diagonal) consécutif d’au moins quatre pions de sa couleur. Si, alors que toutes les cases de la grille de jeu sont remplies, aucun des deux joueurs n’a réalisé un tel alignement, la partie est déclarée nulle.
Afin de simplifier le code de ce projet, on partira du principe que les alignements victorieux ne peuvent être qu’horizontaux ou verticaux. Les diagonales ne seront donc pas considérées (mais constituent un exercice intéressant pour aller plus loin !).
Plan du projet
Nous allons décomposer la construction du jeu en différentes parties :
initialisation de la grille
représentation de la grille
fonction de jeu
détection d’une victoire (horizontale)
détection d’une victoire (verticale)
fin de partie
Initialisation de la grille
L’objectif de cette partie est d’initialiser un objet Python qui représente une grille de puissance 4. Le choix que nous allons faire est de représenter la grille comme une liste de listes. Il s’agira d’une matrice 6x7 : on aura par conséquent une liste de 6 élements (qui représenteront les lignes de la grille), dont chacun des éléments sera une liste contenant 7 éléments (qui représenterons les pions).
Chaque élément de la grille sera représenté par un string, qui pourra prendre trois valeurs :
’ ’ : s’il s’agit d’une case vide
‘R’ : s’il s’agit d’un pion rouge.
‘Y’ : s’il s’agit d’un pion jaune (yellow).
Dans la fonction d’initialisation de la grille, chaque élément sera donc initialisé comme un string contenant un espace.
Attention : Bien faire attention à ce que les lignes soient des objets indépendants, autrement dit que modifier l’une des listes n’affecte pas les autres.
Résultat attendu
= solutions.initialize_grid()
grid_solution grid_solution
print(f'Nombre de lignes : {len(grid_solution)}')
print(f'Nombre de colonnes : {len(grid_solution[0])}')
A vous de jouer !
def initialize_grid():
# Votre code ici
return grid
# Vérification du résultat
= initialize_grid()
grid grid
# Vérification du résultat
print(f'Nombre de lignes : {len(grid)}')
print(f'Nombre de colonnes : {len(grid[0])}')
Représentation de la grille
Notre grille est initialisée, mais son affichage est assez sommaire. L’idée de cette partie est d’offrir une représentation plus visuelle du jeu pendant une partie.
Pour cela, nous allons créer une fonction qui prend en entrée la grille précédemment initialisée et renvoie sa représentation (via la fonction print
). Les colonnes seront séparées par le caractère | (barre verticale).
Indice : une solution possible fait intervenir deux notions que nous avons vues dans les TP précédents : la concaténation de strings et la fonction join
qui “joint” les éléments d’une liste en les séparant par un certain caractère. Pour rappel, voici un exemple qui utilise ces deux concepts :
= ["a", "b", "c", "d", "e"]
l = "DEBUT " + ", ".join(l) + " FIN"
l_join print(l_join)
Résultat attendu
solutions.display_grid(grid_solution)
A vous de jouer !
def display_grid():
# Votre code ici
# Vérification du résultat
display_grid(grid)
Fonction de jeu
Maintenant que nous pouvons représenter notre grille, intéressons-nous au coeur du puissance 4 : le jeu. L’objectif de cette partie est de coder une fonction make_move
qui va modifier la grille lorsqu’un joueur joue son tour.
Cette fonction prend en entrée :
la grille
la colonne choisie par le joueur
la couleur du pion (‘R’ pour le pion rouge, et ‘Y’ pour le pion jaune)
et renvoie en sortie la grille actualisée suite au tour du joueur.
Si la colonne choisie est déjà complète, renvoyer un message d’erreur.
Attention : en Python, la numérotation commence à 0. La première colonne correspond donc à la colonne 0 du point de vue de l’indexation.
Optionnel : Renvoyer un message d’erreur si un joueur essaie de jouer dans une colonne inexistante ou bien avec une couleur non autorisée.
Résultat attendu
= solutions.initialize_grid() # Initialisation
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="R") # 1er tour de jeu
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=5, disc_color="J") # 2ème tour de jeu
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="R") # 3ème tour de jeu
grid_solution solutions.display_grid(grid_solution)
A vous de jouer !
def make_move(grid, column_to_play, disc_color):
= copy.deepcopy(grid) # Evite la modification de la grille initiale
new_grid # Votre code ici
return new_grid
# Vérification du résultat
= initialize_grid() # Initialisation
grid = make_move(grid=grid, column_to_play=2, disc_color="R") # 1er tour de jeu
grid = make_move(grid=grid, column_to_play=5, disc_color="J") # 2ème tour de jeu
grid = make_move(grid=grid, column_to_play=2, disc_color="R") # 3ème tour de jeu
grid display_grid(grid)
Détection d’une victoire (horizontale)
Maintenant qu’il est possible de jouer effectivement à notre puissance 4, il faut pouvoir détecter une victoire pour mettre fin à la partie en cours. Pour se faire, on va simplifier le problème en le décomposant au maximum.
Dans un premier temps, on s’intéresse à la détection d’une victoire horizontale. Pour cela, on va s’aider de deux fonctions :
une fonction
check_row_victory
qui prend en entrée une ligne du puissance 4 (i.e. une liste de taille 7) et retourneTrue
si jamais 4 pions consécutifs de même couleur se trouvent sur la ligne, etFalse
sinonune fonction
check_horizontal_victory
qui prend en entrée une grille complète et retourneTrue
si jamais une ligne de la grille remplit la condition précédente, etFalse
sinon
Résultat attendu
# Détection d'une victoire (horizontale) sur une ligne
= [" ", "R", "R", "R", "J", "J", " "]
ligne1 = [" ", "R", "R", "R", "R", "J", " "]
ligne2
print(solutions.check_row_victory(ligne1)) # Renvoie False
print() # Retour à la ligne
print(solutions.check_row_victory(ligne2)) # Renvoie True
# Détection d'une victoire (horizontale) sur une grille
= solutions.initialize_grid() # Initialisation
grid_solution print(solutions.check_horizontal_victory(grid_solution)) # Renvoie False
print() # Retour à la ligne
= solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="R")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=3, disc_color="R")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=4, disc_color="R")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=5, disc_color="R")
grid_solution
solutions.display_grid(grid_solution)print() # Retour à la ligne
print(solutions.check_horizontal_victory(grid_solution)) # Renvoie True
A vous de jouer !
def check_row_victory(ligne):
# Votre code ici
# Vérification du résultat
= [" ", "R", "R", "R", "J", "R", " "]
row1 = [" ", "R", "R", "R", "R", "J", " "]
row2
print(check_row_victory(row1)) # Renvoie False
print(check_row_victory(row2)) # Renvoie True
def check_horizontal_victory(grid):
# Votre code ici
# Vérification du résultat
= initialize_grid() # Initialisation
grid print(check_horizontal_victory(grid)) # Renvoie False
= make_move(grid=grid, column_to_play=2, disc_color="R")
grid = make_move(grid=grid, column_to_play=3, disc_color="R")
grid = make_move(grid=grid, column_to_play=4, disc_color="R")
grid = make_move(grid=grid, column_to_play=5, disc_color="R")
grid
display_grid(grid)print(check_horizontal_victory(grid)) # Renvoie True
Détection d’une victoire (verticale)
A présent, on s’intéresse à la détection d’une victoire verticale. Par rapport à la situation précédente, la difficulté est que l’on ne peut pas directement boucler sur les colonnes. On va donc construire une fonction check_vertical_victory
qui, pour chaque colonne :
récupère les éléments de la colonne dans une liste
applique à cette liste la fonction
check_row_victory
pour vérifier la présence de 4 pions consécutifs de même couleur dans la colonne considérée
Résultat attendu
# Détection d'une victoire (verticale) sur une grille
= solutions.initialize_grid() # Initialisation
grid_solution print(solutions.check_vertical_victory(grid_solution)) # Renvoie False
print() # Retour à la ligne
= solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution = solutions.make_move(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution
solutions.display_grid(grid_solution)print() # Retour à la ligne
print(solutions.check_vertical_victory(grid_solution)) # Renvoie True
A vous de jouer !
def check_vertical_victory(grid):
# Votre code ici
# Vérification du résultat
= initialize_grid() # Initialisation
grid print(check_vertical_victory(grid)) # Renvoie False
print() # Retour à la ligne
= make_move(grid=grid, column_to_play=2, disc_color="J")
grid = make_move(grid=grid, column_to_play=2, disc_color="J")
grid = make_move(grid=grid, column_to_play=2, disc_color="J")
grid = make_move(grid=grid, column_to_play=2, disc_color="J")
grid
display_grid(grid)print() # Retour à la ligne
print(check_vertical_victory(grid)) # Renvoie True
Fin de partie
Dans notre version simplifiée du puissance 4, on peut à présent déclarer la fin de partie : dès lors qu’une victoire horizontale ou verticale est détectée.
On va donc pour commencer créer une fonction victoire
qui prend la grille en entrée et renvoie True
si une victoire horizontale ou verticale est détectée, et False
sinon.
Dans l’idéal, on voudrait ne pas avoir à tester manuellement après chaque coup si la partie est terminée afin de limiter la duplication de code. On va donc ensuite créer une fonction make_move_and_check_victory
qui :
prend en entrée les mêmes inputs que la fonction
tour
va appeler la fonction
tour
pour réaliser le tour de jeuva tester après le tour de jeu si une victoire est détectée via la fonction
victoire
. Si une victoire est détectée, la fonction imprime “FIN DE PARTIE”.
Résultat attendu
= solutions.initialize_grid() # Initialisation
grid_solution print("Tour 1")
= solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution print("Tour 2")
= solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution print("Tour 3")
= solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
grid_solution print("Tour 4")
= solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J") grid_solution
A vous de jouer !
def check_victory(grid):
# Votre code ici
def make_move_and_check_victory(grille, column_to_play, disc_color):
= copy.deepcopy(grid)
grid # Votre code ici
return grid
# Vérification du résultat
= initialize_grid() # Initialisation
grid print("Tour 1")
= make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
grid print("Tour 2")
= make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
grid print("Tour 3")
= make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
grid print("Tour 4")
= make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J") grid