L'objectif de ce TP est de réaliser un jeu de Démineur fonctionnel sous Pyxel en adoptant une architecture modulaire. Vous allez concevoir le moteur logique du jeu dans un premier fichier en pur Python, le valider via des tests unitaires (assert), puis l'importer dans un second fichier gérant l'interface graphique.
moteur_demineur.py
Créez un fichier nommé moteur_demineur.py. Tout le travail d'écriture algorithmique de cette partie se fera exclusivement dans ce fichier.
Pour représenter le plateau de jeu de taille taille × taille, nous utilisons une liste de listes de dictionnaires. Chaque case du plateau est modélisée par un dictionnaire possédant exactement 4 clés :
"mine" (booléen) : True si la case contient une mine, sinon False."decouverte" (booléen) : True si la case a été dévoilée par le joueur."drapeau" (booléen) : True si un drapeau y est posé."voisins" (entier) : le nombre de mines présentes dans les 8 cases adjacentes.creer_grille_vide(taille)Écrivez la fonction creer_grille_vide(taille: int) -> list qui prend en paramètre la dimension du plateau et renvoie une grille 2D initialisée. Par défaut, aucune case ne contient de mine, aucune n'est découverte, aucun drapeau n'est posé, et le nombre de voisins vaut 0.
placer_mines(grille, nb_mines)Écrivez la fonction placer_mines(grille: list, nb_mines: int) -> None. Elle doit modifier la grille reçue en argument en plaçant aléatoirement nb_mines distinctes (attention à ne pas placer deux mines sur la même case !).
calculer_tous_les_voisins(grille)Écrivez la fonction calculer_tous_les_voisins(grille: list) -> None. Cette fonction parcourt l'ensemble des cases de la grille. Pour chaque case qui ne contient pas de mine, elle compte combien de mines se trouvent dans les 8 cases adjacentes (horizontalement, verticalement et en diagonale) et met à jour la valeur associée à la clé "voisins".
(x, y), assurez-vous de ne pas générer d'erreur d'indice (IndexError) en sortant des limites de la grille.
devoiler(grille, x, y)Lorsque l'utilisateur clique sur une case (x, y) sans mine, la case est dévoilée. Si cette case n'a aucune mine dans son voisinage (voisins == 0), le jeu propage automatiquement le dévoilement aux cases environnantes.
Voici l'enchaînement exact à coder dans la fonction devoiler(grille: list, x: int, y: int) -> None :
return) si :
(x, y) sont hors de la grille.(x, y) courante comme découverte (case["decouverte"] = True).devoiler sur chacun de ses 8 voisins (en évitant la case elle-même).RecursionError: maximum recursion depth exceeded).
Ajoutez le bloc de tests unitaires suivant à la fin de votre fichier moteur_demineur.py et exécutez le script. Vos 4 fonctions doivent valider l'ensemble des assertions sans lever d'erreur.
def tester_moteur(): """Suite de tests unitaires pour valider les fonctions du moteur.""" print("Lancement des tests unitaires...") # --- TEST 1 : Initialisation de la grille --- g_vide = creer_grille_vide(5) assert len(g_vide) == 5 and len(g_vide[0]) == 5, "Test 1 Échec : Dimensions incorrectes" assert g_vide[0][0]["mine"] is False, "Test 1 Échec : Case mal initialisée" # --- TEST 2 : Placement des mines --- placer_mines(g_vide, 4) nb_mines = sum(1 for x in range(5) for y in range(5) if g_vide[x][y]["mine"]) assert nb_mines == 4, "Test 2 Échec : Mauvais nombre de mines placées" # --- TEST 3 : Calcul des voisins --- g_test = creer_grille_vide(3) g_test[0][0]["mine"] = True calculer_tous_les_voisins(g_test) assert g_test[0][1]["voisins"] == 1, "Test 3 Échec : Voisinage (0,1)" assert g_test[1][1]["voisins"] == 1, "Test 3 Échec : Voisinage (1,1)" assert g_test[2][2]["voisins"] == 0, "Test 3 Échec : Voisinage (2,2)" # --- TEST 4 : Récursivité devoiler() --- g_rec = creer_grille_vide(4) g_rec[3][3]["mine"] = True calculer_tous_les_voisins(g_rec) devoiler(g_rec, 0, 0) assert g_rec[0][0]["decouverte"] is True, "Test 4 Échec : Origine non dévoilée" assert g_rec[2][2]["decouverte"] is True, "Test 4 Échec : Frontière (2,2) non dévoilée" assert g_rec[3][3]["decouverte"] is False, "Test 4 Échec : La mine en (3,3) a été dévoilée !" devoiler(g_rec, -1, 0) devoiler(g_rec, 10, 10) print("TOUS LES TESTS SONT PASSÉS AVEC SUCCÈS !") if __name__ == "__main__": tester_moteur()
Une fois la totalité des tests validée, téléchargez le fichier d'interface graphique ci-dessous et enregistrez-le dans le même dossier que votre fichier moteur_demineur.py :
Remarque : Ce fichier importe les fonctions déjà programmées grâce à la ligne :
from moteur_demineur import ( creer_grille_vide, placer_mines, calculer_tous_les_voisins, devoiler )
Lancez l'exécution de demineur.py pour jouer au Démineur !