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.

import copy

import solutions

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

grid_solution = solutions.initialize_grid()
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
grid = initialize_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 :

l = ["a", "b", "c", "d", "e"]
l_join = "DEBUT " + ", ".join(l) + " FIN"
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

grid_solution = 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
solutions.display_grid(grid_solution)

A vous de jouer !

def make_move(grid, column_to_play, disc_color):
    new_grid = copy.deepcopy(grid)  # Evite la modification de la grille initiale
    # Votre code ici
    return new_grid
# Vérification du résultat
grid = 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
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 retourne True si jamais 4 pions consécutifs de même couleur se trouvent sur la ligne, et False sinon

  • une fonction check_horizontal_victory qui prend en entrée une grille complète et retourne True si jamais une ligne de la grille remplit la condition précédente, et False sinon

Résultat attendu

# Détection d'une victoire (horizontale) sur une ligne
ligne1 = [" ", "R", "R", "R", "J", "J", " "]
ligne2 = [" ", "R", "R", "R", "R", "J", " "]

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
grid_solution = solutions.initialize_grid()  # Initialisation
print(solutions.check_horizontal_victory(grid_solution))  # Renvoie False
print()  # Retour à la ligne

grid_solution = 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")
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
row1 = [" ", "R", "R", "R", "J", "R", " "]
row2 = [" ", "R", "R", "R", "R", "J", " "]

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
grid = initialize_grid()  # Initialisation
print(check_horizontal_victory(grid))  # Renvoie False

grid = 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")
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
grid_solution = solutions.initialize_grid()  # Initialisation
print(solutions.check_vertical_victory(grid_solution))  # Renvoie False
print()  # Retour à la ligne

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.make_move(grid=grid_solution, column_to_play=2, disc_color="J")
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
grid = initialize_grid()  # Initialisation
print(check_vertical_victory(grid))  # Renvoie False
print()  # Retour à la ligne

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 = make_move(grid=grid, column_to_play=2, disc_color="J")
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 jeu

  • va 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

grid_solution = solutions.initialize_grid()  # Initialisation
print("Tour 1")
grid_solution = solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
print("Tour 2")
grid_solution = solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
print("Tour 3")
grid_solution = solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")
print("Tour 4")
grid_solution = solutions.make_move_and_check_victory(grid=grid_solution, column_to_play=2, disc_color="J")

A vous de jouer !

def check_victory(grid):
    # Votre code ici
def make_move_and_check_victory(grille, column_to_play, disc_color):
    grid = copy.deepcopy(grid)
    # Votre code ici
    return grid
# Vérification du résultat
grid = initialize_grid()  # Initialisation
print("Tour 1")
grid = make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
print("Tour 2")
grid = make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
print("Tour 3")
grid = make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")
print("Tour 4")
grid = make_move_and_check_victory(grid=grid, column_to_play=2, disc_color="J")