Salut les zéros.
Aujourd'hui, pour vous exercer, je vous ai concocté un petit projet de jeu, très connu, plutôt simple mais formateur : un démineur.
Rappel des règles du jeu
Pour ceux qui ne connaitraient pas encore ce petit jeu aussi simple qu'addictif, je vous en rappelle rapidement le principe.
Vous incarnez un agent de l'équipe de déminage chargé de sécuriser un champ de mines anti-personnelles (eh oui, malheureusement, ces horreurs existent encore à notre époque), en disposant des petits drapeaux « Attention, ne marchez pas ici, ça va vous péter à la tronche » sur chacune d'entre elles.
Le champ de mines se présente sous la forme d'un tableau à L lignes et C colonnes. Chacune des cases de ce tableau peut contenir une mine ou non. Au début de la partie, toutes les cases sont masquées (il y a de la terre partout).
La suite des explications prendra en exemple la petite matrice suivante à 5 lignes et 5 colonnes.
Dans le jeu, vous n'avez que deux actions possibles. Creuser ou poser un drapeau.
Creuser
Creuser constitue l'action principale du jeu : c'est la seule action indispensable pour terminer la partie, et elle est irréversible.
Lorsque vous creusez sur une case du tableau, il n'y a que deux possibilités :
La case contient une mine, elle vous pète à la tronche et la partie est terminée
La case ne contient pas de mine, et elle vous indique le nombre de cases adjacentes qui contiennent une mine.
Ici, on travaille en 8-connexité, c'est-à-dire que chaque case a 8 cases adjacentes : au-dessus, en-dessous, à gauche, à droite, au-dessus à gauche, au-dessus à droite, en-dessous à gauche, et en-dessous à droite d'elle.
Par exemple, je décide de commencer la partie en creusant la case se trouvant à la seconde ligne, seconde colonne de la matrice (donc de coordonnées (1, 1)):
Ouf! Pas de mine, à cette position, je suis toujours en vie, et la case m'indique qu'il se trouve une mine (et une seule) dans son voisinage direct...
Un petit raccourcis
Pour éviter que le jeu ne soit trop répétitif, on ajoute une petite règle très pratique. Si la case où l'on a creusé ne contient aucune mine dans son voisinage (une case 0), alors le jeu creuse automatiquement sur toutes les cases autour d'elle, ce qui permet de dégager les grands espaces libres d'un seul coup.
Par exemple, je tente ma chance, et je creuse maintenant au-dessus à gauche de la case précédente (position (0,0), donc).
Cette case ne contenait pas de mine dans son voisinage direct, le jeu a donc creusé automatiquement partout autour d'elle, ce qui, récursivement, a découvert tout un champ libre.
La fin de la partie
Pour terminer la partie (et gagner), il faut et il suffit que toutes les cases du tableau ne contenant pas de mine aient été creusées.
Poser un drapeau
La seconde action possible dans le jeu, poser un drapeau, n'est absolument pas indispensable pour jouer, mais elle constitue une sécurité pour le joueur (ce qui est extrêmement pratique lorsque l'on joue avec un grand champ de 1000 cases). Lorsque l'on pose un drapeau sur une case, celle-ci affiche un symbole "attention", et on ne peut plus creuser dessus tant que le drapeau s'y trouve. Cela évite donc de se faire péter une mine à la tronche par erreur alors que l'on avait deviné qu'elle était là.
Par exemple, je suis sûr que la case (2,2) contient une mine, je décide donc de poser un drapeau dessus (symbolisé ici par une arobase).
Ainsi, si j'essayais par erreur de creuser cette case, le programme l'ignorerait. C'est une sécurité.
Le fait de poser un drapeau doit être RÉVERSIBLE.
En effet, on peut tout à fait poser un drapeau sur une case parce où l'on soupçonne la présence d'une mine à un moment donné, mais on doit pouvoir aussi retirer ce drapeau par la suite si l'on en a envie.
Consignes
Je vous propose de programmer ce jeu en plusieurs phases, de manière à ce que les débutants ne se perdent pas dès le départ, et que vous ayez la possibilité d'apprendre des choses avec ce mini-projet quel que soit votre niveau.
Phase 1 : votre base de travail.
Dans un premier temps, il va vous falloir coder le cœur du programme, à savoir la version jouable du jeu la plus simple possible.
Si vous débutez dans la POO, je vous recommande très chaudement de vous forcer à programmer objet pour ce projet : il est suffisamment simple pour que vous vous en sortiez avec 3 classes.
Si vous n'avez pas encore appris la POO, qu'à cela ne tienne, vous n'êtes pas obligé d'utiliser ce paradigme pour obtenir une version jouable du jeu, mais c'est peut-être une bonne occasion de s'y coller.
Pour valider cette première phase, vous devrez obtenir la version la plus simple possible, qui vous servira ensuite de base pour le reste du développement :
Déterminer un moyen pertinent de représenter les données avec lesquelles vous devrez interagir. Si je rappelle cette analyse préalable obligatoire (et évidente), c'est simplement parce que cette modélisation va influencer tout le reste du mini-projet. Plus les structures de données que vous utiliserez seront pertinentes, plus le jeu sera facile à programmer.
Coder la fonctionnalité creuser de base. Comme je viens de l'expliquer dans les règles du jeu, l'action creuser est la seule indispensable pour finir la partie. C'est donc la seule requise dans cette phase. Il n'est pas non plus obligatoire de coder pour le moment la petite règle supplémentaire qui creuse automatiquement autour des casses nulles : c'est gadget, et vous pouvez tester le fonctionnement de votre programme sans ça.
Une interface utilisateur rudimentaire. Dans cette première phase, ne vous focalisez surtout pas sur l'interface. Elle peut être moche, on s'en fout... À vrai dire elle DOIT être moche. Le but, c'est simplement que vous puissiez afficher l'état du champ (la grille du jeu), Y COMPRIS LES MINES, et creuser une case donnée en donnant ses numéro de ligne et numéro de colonne. Cette interface rudimentaire va vous permettre de tester votre jeu, voir si les règles sont bien programmées, qu'il n'y a pas de bug, que tout est sous contrôle. Rien de plus ! Attention cependant à ce que cette interface rudimentaire soit bien séparée du reste du programme (bien encapsulée), parce qu'évidemment, elle va changer dans la suite.
Une partie minimaliste. Contentez-vous pour le moment d'un tableau à 5 lignes et 5 colonnes grand maximum, et de quelques mines (2 ou 3) (de toute façon avec l'interface "console print" ce serait trop fastidieux de faire plus). Encore une fois, votre jeu ne doit pas à ce niveau vous offrir un challenge, au contraire, les parties doivent être finies rapidement, de manière à vérifier que vous SAVEZ détecter quand une partie est perdue ou gagnée, sans passer 20 minutes à rentrer des coordonnées au clavier à chaque fois.
Une fois cette phase achevée, vous devriez être capable d'avoir une version "jouable minimale" du démineur, avec les mines qui s'initialisent au hasard dans le champ (mais sont affichées, pour vous aider à débugger), la possibilité de creuser (et donc l'affichage de nombre de mines autour de la case dans laquelle vous venez de creuser) et la détection de la fin de la partie. Une fois que tout ceci fonctionne et est bien en place, vous pouvez passer à la phase 2.
Phase 2 : La totalité des règles du jeu.
Dans cette phase, votre modèle du démineur va être complété, avec toutes les règles du jeu. Si votre code a bien été organisé dans la première partie, vous ne devriez avoir que du code à rajouter, et très peu à modifier.
Il vous faudra donc coder :
La fonctionnalité creuser intelligente. Si la case que vous creusez ne contient aucune mine dans son entourage, alors vous creusez automatiquement partout autour d'elle afin de dégager les grands champs vides.
La pose de drapeaux. Si un drapeau est posé sur une case, on ne peut plus la creuser, mais on peut retirer le drapeau pour creuser ensuite.
Pour l'interface utilisateur. Pas de changement exceptionnel, mais comme vous avez deux actions différentes, je vous conseille (c'est le plus simple) de faire en sorte que l'interface soit « modale » : On appuie sur "D" pour passer en mode drapeau, et sur "C" pour passer en mode creusage. Le reste est identique.
Une fois que tout ceci fonctionne bien, vous pouvez masquer les mines et avoir un "vrai" jeu.
Phase 3 : l'interface
Maintenant, et seulement maintenant que le cœur du jeu est en place avec toutes les règles, vous pouvez penser à l'interface.
Ici, vous avez le choix des armes. PyQt, PyGTK, tkInter, PyGame... Profitez-en peut-être pour choisir un module ou une bibliothèque d'interface à laquelle vous désirez vous essayer, c'est un bon moyen d'apprendre.
Personnellement, en préparant ce mini-projet, je me suis souvenu que j'avais toujours voulu essayer ncurses (les interfaces console riches, pour les terminaux Unix), donc j'ai codé mon interface en ncurses avec le module curses de l'API standard de Python (par chance, le jeu s'y prête bien). Voici deux screenshots :
Séparez, de préférence, votre code d'interface du moteur du jeu (collez-les dans 2 modules différents).
Personnellement, avant de soumettre une proposition de correction, je vais refaire un module d'interface utilisant autre chose que ncurses, histoire d'avoir une correction portable.
Phase 4 : Lâchez-vous !
Maintenant que vous avez un démineur complet avec une belle interface, lâchez-vous, rajoutez des parties en temps limité, un système de score, une liste des highscores, des options modifiables, ou même, que sais-je ? un mode multi-joueur coopératif en ligne ou une IA qui vous donne un indice si vous êtes bloqué... Bref, amusez-vous, et venez nous montrer vos belles idées !
Un mini-projet très bien expliqué avec plusieurs phases pour que les débutants comme moi ne s'y perdent pas, jolie .
Voilà un autre projet intéressant. Je m'y atèle une fois les Threads et les Sockets vus.
Un exercice qui me fera réviser les classes, utiles pour le jeu d'échec et... le bomber man.
Pas spécialement, le sujet est ouvert, codez ce que vous voulez, comme vous pouvez.
Je posterai une correction de la phase 2 d'ici une semaine, et une ou deux versions possibles pour la phase 3 dans 2 semaines.
Edit : À vrai dire, c'est en voyant comment tu galérais avec tes projets sur le forum que j'ai eu l'idée de ce mini-projet. Je compte aborder ces problématiques dans le tuto sur Pygame (par où commencer, comment organiser le développement ? etc.), mais celui-ci n'étant pas prêt encore à voir le jour, j'ai pensé qu'un mini-projet facile, plutôt extensible, et surtout, orienté (avec des phases de développement prédéfinies), était un bon moyen pour s'habituer à cette démarche, et peut-être la réutiliser inconsciemment dans d'autres projets plus sérieux.
C'est en mettant les mains dans le cambouis qu'on prend de bonnes habitudes. Ici, le but n'est pas de travailler un aspect de Python particulier mais plus d'obtenir sans trop de difficultés un programme fini, en suivant des étapes simples. Les deux premières phases sont d'ailleurs volontairement assez courtes à réaliser, de manière à donner le sentiment d'avancer.
ton exercice m'intéresse beaucoup. Je n'aurais pas le temps de m'y atteler cette semaine, mais je le ferais dès que j'aurais du temps.
Ce qui m'a attiré, c'est le fait que tu nous donne une démarche afin de nous guider dans la réflexion et la réalisation avant qu'on fonce tête baissée dans le code, comme un brute.
Edit : À vrai dire, c'est en voyant comment tu galérais avec tes projets sur le forum que j'ai eu l'idée de ce mini-projet. Je compte aborder ces problématiques dans le tuto sur Pygame (par où commencer, comment organiser le développement ? etc.), mais celui-ci n'étant pas prêt encore à voir le jour, j'ai pensé qu'un mini-projet facile, plutôt extensible, et surtout, orienté (avec des phases de développement prédéfinies), était un bon moyen pour s'habituer à cette démarche, et peut-être la réutiliser inconsciemment dans d'autres projets plus sérieux.
Et je t'en remercie 1000 fois. Ça me (EDIT: '/nous')fera progresser
Juste une remarque :
C'est écrit en rouge en gras dans la phase 1 de ne pas se prendre la tête avec l'affichage et tu fais une classe... Affichage... heuu, lol ?
Pour les 2 autres classes, elles me paraissent être une bonne base de départ.
Je vais moi-même faire un petit diagramme des classes de ce que j'ai fait pour les phases 1 et 2. C'est un peu overkill pour un projet aussi facile, mais bon, ça permet de guider sur la structure du programme sans soumettre à la tentation de copier-coller du code.
C'est écrit en rouge en gras dans la phase 1 de ne pas se prendre la tête avec l'affichage et tu fais une classe... Affichage... heuu, lol ?
Hum en effet , je l'ai fait par 'instinct'. Mais tu n'as pas dit qu'il ne fallait rien afficher.
Je te rassure l'affichage sera vraiment moche (enfin si j'arrive à l'afficher et à faire fonctionner mon programme )
Citation : Nohar
Pour les 2 autres classes, elles me paraissent être une bonne base de départ.
j'ai tout de même un peu réfléchi à ma manière de traiter les données, et une matrice en tant que plateau me semble une bonne idées. Je pourrais l'utiliser pour stocker mes objets "case", de cette manière (là c'est du moche juste pour l'idée):
Cependant, cela oblige à faire deux boucles for imbriquées, et (même si pour le moment on se moque des performances), cela n'est-il pas un peu trop un bricolage menaçant de ne pas marcher à tout moment ?
Je pensais aussi faire comme ça pour gérer mon plateau. Cependant je ne sais pas si c'est judicieux tant au niveau pratique que performance (même si la deuxième raison m'importe peu)
Question: Doit on faire le plateau de jeu à la main ou bien avec un algorithme qui place automatiquement les numéros des cases en fonction des bombes posées aléatoirement ?
Deux boucles for imbriquées, niveau performances, ne vous en faites pas, ça passera quand même, d'autant plus qu'a priori vous ne devriez pas avoir à le parcourir tout le temps.
Pour un démineur, la question des performances est vraiment... vraiment très secondaire.
Un tableau à une ou deux dimensions, c'est effectivement une structure adaptée (c'est probablement plus intuitif à gérer à 2 dimensions).
Si ça vous gène d'utiliser un tableau à 2 dimensions, vous pouvez vous inspirer de ce qui suit :
Voilà l'architecture que j'ai utilisée, personnellement, pour les deux premières phases.
Pour l'affichage, je me suis contenté de surcharger les méthodes __str__() pour afficher le champ avec un simple print.
Edit:
Citation : realmagma
Question: Doit on faire le plateau de jeu à la main ou bien avec un algorithme qui place automatiquement les numéros des cases en fonction des bombes posées aléatoirement ?
Ça, c'est à toi de décider. (Edit2: m'enfin la seconde option me semble meilleure )
finalement, j'ai changé certaines choses au niveau du diagramme (je ne peux pas faire de 'screen' car je l'ai fait sur papier cette fois ).
Mon problème est au niveau de la génération/création d'une grille chiffrée.
Je l'ai mise dans ma classe <Plateau> et pour voir si tout marchait bien je l'ai sortie de mon programme pour la tester individuellement. Mais comme vous vous en doutiez, mon algorithme plante sans que j'arrive à trouver pourquoi.
Pourriez-vous éclaircie les idées s'il vous plaît. (Décidément j'ai beaucoup de mal avec les classes moi ) Ma classe <Plateau>:
#-*-*- Fin de <Jeu> || Début de <Plateau> -*-*-#
class Plateau(object):
"""Permet de tenir à jour le plateau du jeu 'démineur'."""
def __init__(self):
"""
Définie les variables nécessaires
"""
self.mapp = []
self.longueur = 5 #Pour le nombres de case 5²
self.nombreMines = 0
def genererGrille(self):
"""Génère une grille de démineur. Appelle placerBombe()
Retourne la grille ainsi qu'une copie remplie de '*'
"""
for i in range(self.longueur): #Pour un tableau de 5x5
self.mapp.append([0] * self.longueur)#Remplie de '0' (type = int)
#Appel à la fonction: placerBombe (deux fois pour l'essai)
self.mapp = self.placerBombe() #On met à
self.mapp = self.placerBombe() #jour la map
#Appel à la fonction: placerChiffre
def placerBombe(self):
"""Génère des nombres aléatoire
pour placer une bombe et appelle chiffrerMap()
"""
#On pose une bombe 'B' aléatoirement dans la map suivant la longueur de celle-ci ->(self.longueur)
self.col, self.ligne, self.bombe = random.randrange(0, self.longueur), random.randrange(0, self.longueur), 'B'
#On place la bombe aux coordonnées trouvées aléatoirement
self.mapp[self.col][self.ligne] = self.bombe
#Appel de la fonction chiffrerMap
self.mapp = self.chiffrerMap(self.col, self.ligne, self.mapp)
print(self.mapp)
return self.mapp
def chiffrerMap(self, col, ligne, mapp):
"""Permet de chiffrer ma map pour
savoir où se trouvent les bombes
"""
#Implémentation de l'algorithme pour chiffrer ma map
#[[0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, B, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0]]
#Implémentation de l'algo
#Si il y a un 'B' dans la grille alors on met un '1' dans les huit cases
#qui l'entoure donc dans les cases suivantes (<C>olonne;<L>igne) = '<B>ombe'
#Sens d'une aiguille d'une montre. On commence en haut à gauche:
#(C-1;L-1) -> (C-1;L) -> (C-1;L+1)
#(C;L-1) -> B:(C;L) -> (C;L+1)
#(C+1;L-1) -> (C+1;L) -> (C+1,L+1)
#Si une des cases est en contacte avec plusieurs bombes, alors:
#compteur += 1, donc on placera un 2/3 au lieu du 1
#La case aux coordonnées C;L = compteur
#Ligne n°1:
self.mapp[self.col-1][self.ligne-1] = 1
self.mapp[self.col-1][self.ligne] = 1
self.mapp[self.col-1][self.ligne+1] = 1
#Ligne n°2:
self.mapp[self.col][self.ligne-1] = 1
self.mapp[self.col][self.ligne] = self.bombe
self.mapp[self.col][self.ligne+1] = 1
#Ligne n°3:
self.mapp[self.col+1][self.ligne-1] = 1
self.mapp[self.col+1][self.ligne] = 1
self.mapp[self.col+1][self.ligne+1] = 1
return self.mapp
Edit : Voici une solution naïve, mais qui fonctionne apparemment bien :
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from random import randint
if __name__ == '__main__':
plateau = []
longueur = 4
r = 0
for i in range(longueur): #Pour un tableau de 5x5
plateau.append([0] * longueur)#Remplie de '0' (type = int)
#on place des bombes - peu fiable
for i in range(longueur):
for j in range(longueur):
r = randint(1, longueur)
if r == longueur:
plateau[i][j] = "B"
#et là on va vérifier les cases - implémentation naive
for i in range(longueur):
for j in range(longueur):
nbBombes = 0
if plateau[i][j] != "B":
if j > 0: #gauche
if plateau[i][j-1] == "B":
nbBombes += 1
if j < longueur-1:#droite
if plateau[i][j+1] == "B":
nbBombes += 1
if i > 0: #faut voir si on est pas première ligne
#on va regarder toutes les cases de la ligne précédente
if j > 0: #haut gauche
if plateau[i-1][j-1] == "B":
nbBombes += 1
#haut
if plateau[i-1][j] == "B":
nbBombes += 1
if j < longueur-1: #haut droite
if plateau[i-1][j+1] == "B":
nbBombes += 1
if i < longueur-1: #si on est pas dernière ligne
if j > 0: #bas gauche
if plateau[i+1][j-1] == "B":
nbBombes += 1
#bas
if plateau[i+1][j] == "B":
nbBombes += 1
if j < longueur-1: #bas droite
if plateau[i+1][j+1] == "B":
nbBombes += 1
#maj de la case
plateau[i][j] = nbBombes
#debug
print "case " + str(i) + "/" + str(j) + " = " + str(nbBombes)
print plateau
#fin debug
J'ai fait ça vite à des fins de tests, et je pense m'en servir
Après, comme l'a dit NoHaR, on est pas obligé de le faire dès le début, mais ça peut être utile, ça dépend comment on veut le faire.
Salut.
Pour "chiffrer" ta grille, comme tu dis, tu peux aussi utiliser un algorithme naïf (et largement suffisant... j'ai testé, ça fonctionne sans problème):
pour chaque case, tu comptes effectivement le nombre de mines dans son entourage (donc 2 boucles et tu incrémentes un compteur).
Par ailleurs, ça peut être intéressant de remarquer que tu n'es pas obligé de connaitre le nombre de mines autour d'une case donnée avant d'avoir vraiment besoin d'afficher cette case...
Après, imagine que plusieurs mines se touchent : tel quel ton algo ne compte les mines qu'une par une. peut-être voudrais-tu faire "+=1" au lieu de "=1" dans ta méthode "chiffrer_grille"...
Enfin, fais attention avec les indices : si tu dépasses la longueur du tableau, Python te lèvera un IndexError, et si tu passes un indice négatif, il comptera simplement les cases à partir de la fin (ce qui n'est pas le comportement que tu attends dans ce cas précis).
Edit : Si malgré tout ça tu bloques vraiment sur l'initialisation de la grille, reporte ça à plus tard et initialises-en une à la main, pour coder le reste, et revenir sur ton initialisation automatique plus tard.
J'ai ré-écrit mon algorithme à l'aide d'une double-boucle, seulement ma mapp (mon plateau de jeu) ne veut pas se modifier; même si je retourne ma mapp en fin de fonction .
Pourriez-vous me dire si mon algorithme est bon ou pas ?
Ma méthode *chiffrerMap* issue de ma classe <Plateau>:
class Plateau(object):
"""Permet de tenir à jour le plateau du jeu 'démineur'."""
def __init__(self):
"""
Définie les variables nécessaires
"""
self.mapp = []
self.longueur = 5 #Pour le nombres de case 5²
self.nombreMines = 0
def genererGrille(self):
"""Génère une grille de démineur. Appelle placerBombe()
Retourne la grille ainsi qu'une copie remplie de '*'
"""
for i in range(self.longueur): #Pour un tableau de 5x5
self.mapp.append([0] * self.longueur)#Remplie de '0' (type = int)
#Appel à la fonction: placerBombe (deux fois pour l'essai)
self.mapp = self.placerBombe() #On met à
self.mapp = self.placerBombe() #jour la map
print(self.mapp)
def placerBombe(self):
"""Génère des nombres aléatoire
pour placer une bombe et appelle chiffrerMap()
"""
#On pose une bombe 'B' aléatoirement dans la ma suivant la longueur de celle-ci ->(self.longueur)
self.col, self.ligne, self.bombe = random.randrange(0, self.longueur), random.randrange(0, self.longueur), 'B'
#On place la bombe aux coordonnées trouvées aléatoirement
self.mapp[self.col][self.ligne] = self.bombe
#Appel de la fonction chiffrerMap
self.mapp = self.chiffrerMap(self.col, self.ligne, self.mapp)
return self.mapp
def chiffrerMap(self, col, ligne, mapp):
"""Permet de chiffrer ma map pour
savoir où se trouvent les bombes
"""
#Implémentation de l'algorithme pour chiffrer ma map
#[[0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, B, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0]]
#Implémentation de l'algorithme
#On parcours notre mapp à l'aide d'une double boucle
#On ajoute dans une liste à part les 8 case entourant la case actuelle
#On regarde combien il y a de 'B'ombes.
#Ma case prendra la valeur du nombre d'occurences de 'B' dans ma listeDesC...
self.listeDesCasesAutourDeJ = []
for i in self.mapp: #On parcours mon tableau 2D
for j in i:
#On ajoute les 8 valeurs entourant ma case 'J'
self.listeDesCasesAutourDeJ.append([self.col-1, self.col+1, self.ligne-1, self.ligne+1])
#On compte le nombre d'occurences de la lettre "B"
self.compteur = self.listeDesCasesAutourDeJ.count("B")
self.mapp[self.col][self.ligne] = self.compteur
self.listeDesCasesAutourDeJ = [] #On remet à '0'
return self.mapp #On retourne la grille chiffrée
#On ajoute les 8 valeurs entourant ma case 'J'
self.listeDesCasesAutourDeJ.append([self.col-1, self.col+1, self.ligne-1, self.ligne+1])
Tu cherches "B" dans une liste de listes d'entiers...
Pas forcément vu qu'il se peut qu'a col-1 (ou ligne+1 ou autre) il y ai un 'B'. On n'en sait rien.
C'est par rapport à ma case 'J'. On regarde ce qu'il y a dans chacune des 8 cases qui l'entoure s'il y a un 'B'. (Le 'B' est définie aléatoirement)
for row in range(lignes):
for col in range(colonnes):
maliste = [] # entourage de maMap[row][col]
for r in range(max(0, row-1), min(row+2, lignes)):
for c in range(max(0, col-1), min(col+2, colonnes)):
maliste.append(maMapp[r][c])
# faire un truc avec maliste
# faire un truc avec maMap[row][col]
Edit : attention, si tu utilises Python 2.x à bien remplacer range par xrange.
ou plus proprement, avec du slicing:
for row in range(lignes):
for col in range(colonnes):
maliste = [] # entourage de maMap[row][col]
for s_row in maMap[max(0, row-1):min(row+2, lignes)]:
maliste += s_row[max(0, col-1):min(col+2, colonnes)]
, il y a beaucoup trop de boucles 'for' imbriqués pour moi.
Pourquoi fais-tu deux boucles supplémentaires ?
Je ne comprends pas très bien ton code à partir de la troisième boucles 'for'. (Même si je sais que tu as ré-écrit mon algorithme)
J'ai essayé ceci, mais il n'y a aucun chiffrement. (J'ai repris le tiens en l'arrangeant pour mon code)
for i in range(self.ligne):
for j in range(self.col):
self.newL = []
for ii in range(max(0, i-1), min(i+2, self.ligne-1)):
for jj in range(max(0, j-1), min(j+2, self.col-1)):
self.newL.append(self.mapp[ii][jj])
self.compteur = self.newL.count("B")
self.mapp[ii][jj] = self.compteur
return self.mapp
Attention, dans ton code ce ne sont pas self.ligne-1 et self.col-1 les limites, mais le nombre de lignes et de colonnes de ta grille.
La première double-boucle for parcourt tout le tableau.
La seconde double-boucle for parcourt l'entourage de la case courante (les cases adjacentes).
Les min et max sont là pour s'assurer qu'on ne dépasse pas du tableau.
Rien à faire, je n'arrive pas à faire ce que je veux .
Je vais faire une petite pause car j'y suis depuis ce matin. Il est possible que ça me remette les idées en place; qui sait...
Je poste mon code au cas où:
for i in range(self.ligne):
for j in range(self.col):
self.newL = []
for ii in range(max(0, i-1), min(i+2, self.longueur-1)):
for jj in range(max(0, j-1), min(j+2, self.longueur-1)):
self.newL.append(self.mapp[ii][jj]) #On capture les 8 cases
self.compteur = self.newL.count("B") #Combien il y a t'il de bombe
self.mapp[ii][jj] = self.compteur #Ma case porte son nouveau numéro
return self.mapp #On retourne le tout
for i in range(self.longueur):
for j in range(self.longueur):
newL = [] # pas besoin d'en faire un attribut
for ii in range(max(0, i-1), min(i+2, self.longueur)):
for jj in range(max(0, j-1), min(j+2, self.longueur)):
newL.append(self.mapp[ii][jj])
if self.mapp[i][j] != "B":
self.mapp[i][j] = newL.count("B")
return self.mapp
Edit : je viens de voir qu'il fallait que tu rajoutes une condition pour éviter de remplacer les "B" par des nombres, vu la structure que tu utilises.
Ça ne va toujours pas car on ne voit plus les 'B' et il faut au minimum 8 chiffrements
1,1,1
1,B,1
1,1,1
... voir plus vu qu'il y a deux 'B'.
J'ai essayé toutes les possibilités possibles et inimaginable (dont celle-ci) mais aucunes ne veut faire ce que je souhaite
Je vais re-créer un algorithme en prenant celui-ci comme base.
Je le posterai ce soir, car j'ai la tête qui va exploser.
Merci quand même pour tes suggestions qui m'ont (ré-orientés dans 'le droit chemin')
J'ai édité mon précédent message...
Tu aurais pu trouver tout seul.
Pour info, voici le début de mon code, qui contient entre autres ma réponse au premier point de la phase 1 (y compris l'initialisation de la grille), j'utilise une liste simple pour stocker les cases au lieu d'un tableau à 2 dimensions, mais c'est plus un choix personnel qu'autre chose.
Les deux conviennent très bien.
#!/usr/bin/env python
#-*- coding: utf-8 -*-
from __future__ import print_function
from random import randint
###############################################################################
# Constantes globales
MINE = '*' # Dessin d'une mine
FLAG = '!' # Drapeau
NOFLAG = '.' # Case cachée normale
NOTHING = ' ' # Case vide
class Cell(object):
"""
Classe modélisant une case du champ de mines.
"""
def __init__(self):
"""
Initialisation des attributs.
"""
self.mine = False # La case contient-elle une mine ?
self.flag = False # Y-a t'il un drapeau posé sur cette case ?
self.show = False # La case est-elle visible ?
self.value = 0 # Combien de cases adjacentes contiennent une mine ?
def __str__(self):
"""
Retourne un caractère symbolisant l'état de la case.
"""
if self.show:
return str(self.value) if self.value else NOTHING
else:
return FLAG if self.flag else NOFLAG
class Field(object):
"""
Classe modélisant un champ de mines.
"""
def __init__(self, rows, cols, n_mines):
"""
Initialisation du champ de mines.
rows: nombre de lignes
cols: nombre de colonnes
n_mines: nombre de mines
"""
self.rows = rows
self.cols = cols
self.n_mines = n_mines
self.hidden = rows * cols # nombre de cases cachées
self.flagged = 0 # nombre de cases portant un drapeau
self.gameover = False
self.cells = []
self._init_cells() # initialisation des cases
# remplissage aléatoire du champ de mines
for _ in xrange(n_mines):
idx = randint(0, rows * cols -1)
while self.cells[idx].mine:
idx = randint(0, rows * cols -1)
row = idx // cols
col = idx - row * cols
self.cells[idx].mine = True
self._count_mines(row, col)
def _init_cells(self):
"""
Initialisation des cases.
Cette fonction pourra être surchargée si une classe fille désire
utiliser une autre classe pour les cases. (voir interface)
"""
self.cells = [Cell() for _ in xrange(self.rows * self.cols)]
def _count_mines(self, row, col):
"""
Incrémente les compteurs de mines autour de la case (row, col)
row: numéro de ligne de la case.
col: numéro de colonne de la case.
"""
for r in xrange(max(row-1, 0), min(row+2, self.rows)):
for c in xrange(max(col-1, 0), min(col+2, self.cols)):
self.cells[r * self.cols + c].value += 1
NoHaR: C'est possible de faire en sorte que ta version du démineur soit accessible par PuTTy via simplement un protocole telnet ou autre. C'est pratique pour les utilisateurs de Windows qui veulent tester ta version du jeu réalisée avec curses. En tout cas, j'aime beaucoup. Bon boulot !
NoHaR: C'est possible de faire en sorte que ta version du démineur soit accessible par PuTTy via simplement un protocole telnet ou autre. C'est pratique pour les utilisateurs de Windows qui veulent tester ta version du jeu réalisée avec curses. En tout cas, j'aime beaucoup. Bon boulot !
Ah oui remarque, c'est pas bête.
À la base j'en profitais pour y jouer dans un screen, mais j'avais zappé cet avantage de ncurses : ça peut se jouer en client léger.
Je verrai ça quand j'aurai posté "ma" correction complète. J'ai encore du temps avant ça, pour rajouter des fonctionnalités, fignoler un peu le code, etc.
Je pense que j'essayerai de faire une interface tkinter aussi (jamais essayé non plus ), si le temps me le permet, histoire de rester dans l'API Python standard et d'avoir au moins une version totalement portable.
voici la phase 2. Vous pouvez donc jouer en mode console, creuser (avec révélation intelligente), et poser des drapeaux.
Sans plus tarder, voici le code :
main.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from plateau import Plateau
class Main:
"""Classe principale du jeu. Gere les I/O
c pour mode creuser, d pour mode drapeau, q pour quitter"""
def __init__(self):
"""Initialisation de la partie"""
self.__flagMode = False
self.__taille = 5
self.__arreter = False
self.__plateau = Plateau(self.__taille)
self.jouer()
def jouer(self):
"""Fonction pour jouer, affiche l'invite
puis appelle la fonction pour trairer l'entrée"""
invite = "c: creuser - d : drapeau - q quitter\n{0} > "
while not self.__arreter:
print
print self.__plateau
saisieJoueur = raw_input(
invite.format('drapeau' if self.__flagMode else 'creuser'))
self.traiterInput(saisieJoueur.strip().lower())
self.__plateau.revelerMines()
print self.__plateau
def traiterInput(self, chaine):
"""Permet de traiter l'entrée utilisateur, et appelle les fonctions au besoin
inspiré de la fonction écrite par NoHaR :)"""
if chaine == "q":
raise SystemExit
if chaine == "c":
self.__flagMode = False
return 0
if chaine == "d":
self.__flagMode = True
return 0
try:
lig, col = [int(num) for num in chaine.split()] #splitter et affecter à la fois
#si on est en mode drapeau
if self.__flagMode:
self.__plateau.traiterDrapeaux(lig, col)
#non? alors on creuse
else:
if not self.creuser(int(lig), int(col)):
self.__arreter = True
print "PERDU"
if self.__plateau.getNbCasesSansMines() == 0:
self.__arreter = True
print "VICTOIRE"
except (ValueError, TypeError):
print 'Entrée invalide : 2 nombres attendus'
except IndexError:
print 'Entrée invalide : Ligne est colonne compris entre 0 et ' + str(self.__taille)
def creuser(self, ligne=0, colonne=0):
"""Fonction qui demande à creuser une certaine case"""
if ligne < 0 or colonne < 0:
print "Erreur d'une coordonnée. 0 est le minimum"
return True
elif ligne > self.__taille or colonne > self.__taille:
print "Erreur d'une coordonnée. " + str(self.__taille) + " est le maximum"
return True
else:
return self.__plateau.creuserCase(ligne, colonne)
if __name__ == '__main__':
partie = Main()
plateau.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from case import Case
from random import randint
class Plateau:
"""Classe contenant le plateau de jeu"""
def __init__(self, taille=5):
"""Initialisation du plateau de jeu"""
self.__taille = taille
self.__plateau = self.creerPlateau()
self.__nbBombes = 0
while self.__nbBombes == 0:
self.__nbBombes = self.placerBombes()
self.__nbCasesSansMines = (self.__taille*self.__taille)-self.__nbBombes
def creerPlateau(self):
"""Creer le plateau en tant que tab a deux dimensions et place les bombes
puis retourne le tableau"""
plateau = []
for i in xrange(self.__taille):
plateau.append([None] * self.__taille) #remplir de 0
return plateau
def placerBombes(self):
"""Place les bombes de manière aléatoire en fonction de la taille
et renvoie le nombre au plateau.
Il ne peut pas y avoir plus de <taille plateau> bombes"""
nb=0
for i in xrange(self.__taille):
for j in xrange(self.__taille):
r = randint(1, self.__taille)
self.__plateau[i][j] = Case()
if r == self.__taille and nb < self.__taille: #pas plus de <taille> bombes
self.__plateau[i][j].devenirBombe()
nb += 1
return nb
def compterBombes(self, ligne=0, colonne=0):
"""Compter les bombes pour une case donnée"""
l_min = max(0, ligne-1)
l_max = min(ligne+2, self.__taille)
c_min = max(0, colonne-1)
c_max = min(colonne+2, self.__taille)
bombes = 0
for lig in xrange(l_min, l_max):
for col in xrange(c_min, c_max):
if self.__plateau[lig][col].getEstUneBombe():
bombes += 1
self.__plateau[ligne][colonne].setNbBombesAutour(bombes)
def traiterDrapeaux(self, ligne=0, colonne=0):
"""Place ou retire un drapeau sur la case donnée"""
self.__plateau[ligne][colonne].setEstDreapeau()
def creuserCase(self, ligne=0, colonne=0):
"""Creuser une case : si pas encore révélée ou pas un drapeau
compter les mines autour de cette case,
la rendre visible. Si c'est une mine, on retourne false
------
v2 : la fonction permet de creuser intelligemment (et est plus lisible)
merci NoHaR :)"""
case = self.__plateau[ligne][colonne]
if case.getEstDrapeau():
return True
#si la case n'(est pas déjà creusée
if not case.getEstVisible():
self.compterBombes(ligne, colonne)
case.rendreVisible()
#si c'est une bombe : perdu
if case.getEstUneBombe():
return False
#ça n'en est pas une
else:
self.__nbCasesSansMines -= 1
#si la case vaut 0, on creuse les voisines
if case.getNbBombesAutour() == 0:
#voir fonction compterBombes pour ces 2 boucles en détail
for lig in xrange( max(0, ligne-1), min(ligne+2, self.__taille) ):
for col in xrange( max(0, colonne-1), min(colonne+2, self.__taille) ):
self.creuserCase(lig, col)
return True
else:
return True
def getNbCasesSansMines(self):
return self.__nbCasesSansMines
def revelerMines(self):
"""Revèler les bombes du plateau"""
for i in xrange(self.__taille):
for j in xrange(self.__taille):
if self.__plateau[i][j].getEstUneBombe():
self.__plateau[i][j].rendreVisible()
def __str__(self):
"""Pour pouvoir afficher le tableau avec un simple print"""
affichage = "+" + ("-" * 2 * self.__taille) + "-+"
ligne = ""
for i in xrange(self.__taille):
ligne = "| "
for j in xrange(self.__taille):
ligne = ligne + str(self.__plateau[i][j]) + " "
affichage = affichage + "\n" + ligne + "|"
ligne = ""
affichage += "\n+" + ("-" * 2 * self.__taille) + "-+"
return affichage
if __name__ == '__main__':
print "Veuillez lancer le script 'main.py'"
case.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
class Case:
"""Classe gérant les cases du plateau de jeu"""
def __init__(self):
"""Initialisation d'une case"""
self.__estUneBombe = False
self.__estDrapeau = False
self.__estVisible = False
self.__nbBombesAutour = 0
def devenirBombe(self):
"""La case est une bombe"""
self.__estUneBombe = True
def rendreVisible(self):
"""Rend une case visible"""
self.__estVisible = True
def getEstUneBombe(self):
return self.__estUneBombe
def getEstVisible(self):
return self.__estVisible
def setEstDreapeau(self):
if self.__estDrapeau:
self.__estDrapeau = False
else:
self.__estDrapeau = True
def getEstDrapeau(self):
return self.__estDrapeau
def setNbBombesAutour(self, nb=0):
self.__nbBombesAutour = nb
def getNbBombesAutour(self):
return self.__nbBombesAutour
def __str__(self):
"""Pour faire un print d'une case"""
if self.__estVisible:
if self.__estUneBombe:
return "x"
else:
if self.__nbBombesAutour > 0:
return str(self.__nbBombesAutour)
else:
return " "
elif self.__estDrapeau:
return "D"
else:
return "."
if __name__ == '__main__':
print "Veuillez lancer le script 'main.py'"
EDIT : c'est devenu le code de la phase 2 suite à une erreur d'édition
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.