Table des matières

Puissance 4

Nous souhaitons créer un environnement permettant de jouer au puissance 4.

Les règles du jeu

Bibliothèque et principal

Nous devons distinguer deux aspects différents de notre programme :

On va commencer par produire toute une liste de fonctions utiles. Chacune de ces fonctions réalisent un petit aspect du jeu. Ensuite, on pourra réaliser le programme principal.

Bibliothèque

Paramètres

Le jeu normal se joue sur 7 colonnes, 6 lignes et avec des alignements de 4. Nous allons donc trouver dans notre programme des 7, des 6 et des 4 à de nombreux endroits.

Supposons que l'on veuille essayer un puissance 4 sur une grille de 11×8. Cela ne devrait pas poser de difficultés pour un ordinateur. Pourtant, si nous avons écrit 7 et 6 un peu partout dans le programme, il nous faudra remplacer tous les 7 par 11 et tous les 6 par 8. C'est pénible…

Au lieu de cela on commence le programme en définissant des constantes :

"""
module fonctionspuissance4
"""

COLS = 7   # Nombre de colonnes de la grille
LIGNES = 6 # Nombre de lignes de la grille
TO_WIN = 4 # Nombre de jetons à aligner pour gagner

Format de la grille

Le programme a besoin de savoir à chaque instant l'état de la grille : que contient telle ou telle case ?

Il y a plusieurs possibilité.

Je préconise la seconde solution. Le tableau contiendra donc LIGNES * COLS cases. La case pour la ligne lig et la colonne col sera située à la position lig * COLS + col. Les numéros de lignes et colonnes commencent à 0, la ligne 0 est en haut.

On décide aussi que

Voici un exemple. Ici le joueur 1 a les jeton jaunes et le joueur 2 a les jetons rouges.

# tableau correspondant
[0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0,
 0, 0, 1, 0, 0, 0, 0,
 0, 0, 2, 1, 2, 0, 0,
 0, 1, 2, 2, 1, 0, 0,
 2, 1, 1, 1, 2, 2, 0]

Bien que j'ai noté ce tableau sur plusieurs lignes, il s'agit bien d'un tableau à une dimension puisqu'il n'y a qu'une paire de crochets.

# c'est exactement le même tableau du point de vue de Python :
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 1, 2, 2, 1, 0, 0, 2, 1, 1, 1, 2, 2, 0]

Créer une grille vide

def make_empty_grid() -> list:
    """
    Renvoie un tableau de LIGNES * COLS cases et ne contenant que des 0
    """
    # à vous de compléter

Lire le contenu d'une case d'une grille donnée

def read(grid:list, lig:int, col:int) -> int:
    """
    grid: tableau de LIGNES * COLS éléments
    lig: entier dans [0;LIGNES[, indice de ligne
    col: entier dans [0;COLS[, indice de colonne
    Renvoie le contenu de grid correspondant à la ligne lig et colonne col
    """
    assert 0 <= lig < LIGNES
    assert 0 <= col < COLS
    assert len(grid) == LIGNES * COLS
    return # Complétez !

Afficher une grille

Il pourra être utile de donner une version lisible du contenu de la grille. Je propose la solution suivante.

SYMBOLS = " OX" # symboles pour les différents joueurs.
# SYMBOLS[0] correspond à une case vide.

def grid_to_str(grid:list) -> str:
    """
    grid: tableau de LIGNES * COLS éléments
    renvoie une version texte de la grille.
    """
    # création d'une grille avec les symboles au lieu des numéros
    symb_grid = [SYMBOLS[joueur] for joueur in grid]
    # création d'une grille de ligne
    symb_lignes = ["|"+"|".join(symb_grid[lig*COLS:(lig+1)*COLS])+"|" for lig in range(LIGNES)]
    return "\n".join(symb_lignes)

C'est un peu compliqué. Quelques détails pour les plus curieux :

Vérifier si une colonne est pleine

Une colonne si la case du haut de cette colonne est occupée. Pour une colonne col il nous suffit de lire le contenu de cette colonne et de la ligne 0 et de voir si le contenu est occupé.

def col_is_full(grid:list, col:int) -> bool:
    """
    grid: tableau de LIGNES * COLS éléments
    col: indice de la colonne
    renvoie True si la colonne est pleine, False sinon
    """
    # à vous...

Vérifier si la grille est pleine

Il faut vérifier chaque colonne. Si au moins une n'est pas pleine, c'est que la grille n'est pas pleine.

def grid_is_full(grid:list) -> bool:
    """
    grid: tableau de LIGNES * COLS éléments
    renvoie True si la grille est pleine, False sinon
    """
    # à vous...

insérer jeton

Ajoute un jeton du joueur donné dans la plus haute case libre de la colonne.

def insert(grid:list, col:int, player:int):
    """
    grid: tableau de LIGNES * COLS éléments
    col: indice de la colonne
    player: numéro du joueur
    """
    assert not col_is_full(grid, col)
    # À vous !

Vérifier un alignement

Cette partie est plus difficile. L'humain reconnaît d'un coup d'œil la présence ou l'absence d'alignement. Je vous propose l'approche suivante :

FONCTION get_line
ENTRÉES
    grille
    pas: déplacement à faire d'un case à l'autre
    depart: position de départ
SORTIE: une chaîne de caractères line valeurs de l'alignement
DÉBUT
    soit position en cours définie par la position de depart
    soit line une chaine vide
    TANT QUE position en cours à l'intérieur de la grille RÉPÉTER
        lire le contenu de la grille en position,
        ajouter ce qui a été lu au bout de line
        modifier position selon le pas
    FIN
    RENVOYER line
FIN

Exemple :

0000000
0000000
0010000
0022200
0012120
1121211

Si on se donne depart= (0,0), cela désigne le coin supérieur gauche. pas = (1,1) fait se déplacer en diagonale ↘. L'appel get_line(grille, pas, depart) doit renvoyer "001211".

Exemple d'utilisation :

On ajoute une deuxième fonction pour compter le nombre max de répétitions d'un symbole :

def line_has_victory(line:str, valeur:int) -> bool:
    '''
    line: chaîne représentant un alignement. ex : "012111"
    valeur: valeur recherchée. ex : 1
    renvoie True si line contient un alignement assez long
      ex: ici non, il n'y a que 3 fois le 1 à la suite
    '''

Et maintenant vous pouvez envisager une fonction qui parcours toutes les lignes possibles à la recherche d'une victoire.

def grid_has_victory(grid:list) -> bool:
    """
    grid: grille de jeu
    renvoie True si un alignement gagnant a été trouvé, False sinon
    """
    # À vous !

Fichier complet

À placer dans un fichier fonctionspuissance4.py.

"""
module fonctionspuissance4
"""

COLS = 7   # Nombre de colonnes de la grille
LIGNES = 6 # Nombre de lignes de la grille
TO_WIN = 4 # Nombre de jetons à aligner pour gagner

def make_empty_grid() -> list:
    """
    Renvoie un tableau de LIGNES * COLS cases et ne contenant que des 0
    """
    # à vous de compléter
    
def read(grid:list, lig:int, col:int) -> int:
    """
    grid: tableau de LIGNES * COLS éléments
    lig: entier dans [0;LIGNES[, indice de ligne
    col: entier dans [0;COLS[, indice de colonne
    Renvoie le contenu de grid correspondant à la ligne lig et colonne col
    """
    assert 0 <= lig < LIGNES
    assert 0 <= col < COLS
    assert len(grid) == LIGNES * COLS
    return # Complétez !

SYMBOLS = " OX" # symboles pour les différents joueurs.
# SYMBOLS[0] correspond à une case vide.

def grid_to_str(grid:list) -> str:
    """
    grid: tableau de LIGNES * COLS éléments
    renvoie une version texte de la grille.
    """
    # création d'une grille avec les symboles au lieu des numéros
    symb_grid = [SYMBOLS[joueur] for joueur in grid]
    # création d'une grille de ligne
    symb_lignes = ["|"+"|".join(symb_grid[lig*COLS:(lig+1)*COLS])+"|" for lig in range(LIGNES)]
    return "\n".join(symb_lignes)

def col_is_full(grid:list, col:int) -> bool:
    """
    grid: tableau de LIGNES * COLS éléments
    col: indice de la colonne
    renvoie True si la colonne est pleine, False sinon
    """
    # à vous...

def grid_is_full(grid:list) -> bool:
    """
    grid: tableau de LIGNES * COLS éléments
    renvoie True si la grille est pleine, False sinon
    """
    # à vous...

def insert(grid:list, col:int, player:int):
    """
    grid: tableau de LIGNES * COLS éléments
    col: indice de la colonne
    player: numéro du joueur
    """
    assert not col_is_full(grid, col)
    # À vous !

def get_line(grid:list, step:tuple, start:tuple) -> str:
    """
    grid: grille de jeu
    step: paire (step_lig,step_col) indiquant le déplacement d'une case à la suivante
    start: paire (start_lig, start_col) indiquant la position de départ
    renvoie une chaîne correspondant à l'alignement demandé
    """
    # À vous !

def line_has_victory(line:str, valeur:int) -> bool:
    '''
    line: chaîne représentant un alignement. ex : "012111"
    valeur: valeur recherchée. ex : 1
    renvoie True si line contient un alignement assez long
      ex: ici non, il n'y a que 3 fois le 1 à la suite
    '''

def grid_has_victory(grid:list) -> bool:
    """
    grid: grille de jeu
    renvoie True si un alignement gagnant a été trouvé, False sinon
    """
    # À vous !

Fichier principal

Il faut importer les fonctions définies précédemment.

# main.py

from fonctionspuissance4 import *

Ensuite le jeu suit l'algorithme suivant :

DÉBUT
    créer une grille vide
    afficher la grille
    joueur en cours est 1
    partie_finie est faux
    TANT QUE partie finie est faux RÉPÉTER
        demander au joueur en quelle colonne il souhaite jouer
        jouer le coup pour le joueur en cours
        afficher la grille
        SI la grille contient une victoire ALORS
            partie_finie passe à vrai
            afficher un message indiquant la victoire du joueur
        SINON SI la grille est pleine ALORS
            partie_finie passe à vrai
            afficher un message indiquant le match nul
        SINON
            changer de joueur
        FIN
    FIN
FIN

À vous de compléter le fichier main.py