######################################### ### Info224, TP4 : démineur en Python ### import random from tkinter import * ################## ### Plateau de jeu ### Le plateau est simplement représenté par une matrice (un tableau de ### tableaux). ### La case de coordonnées "(i,j)" est un dictionnaire à deux champs : ### - "mine" qui est un booléen et qui indique si la case contient une mine ### - "etat" qui indique l'état de la case : ### - INCONNU quand le joueur n'a pas découvert la case ### - un entier entre 0 et 8 qui indique le nombre de mines voisines, ### quand le joueur a découvert la case ### - DRAPEAU quand le joueur a mis un drapeau sur la case ### - QUESTION quand le joueur n'est pas sûr. ### - PERDU quand il s'agit d'une case avec une mine, sur laquelle le ### joueur a cliqué ### Les 13 états possibles sont modélisés par des entiers, avec les ### déclarations suivantes : INCONNU = -1 PERDU = -2 DRAPEAU = -3 QUESTION = -4 probabilite_mine= random.random() ############################################################ # TP4: Le demineur # ############################################################ #Question 1: def affiche(n): if n>0: print(n) affiche(n-1) # appel récursif # Permet d'afficher a la suite tout les entiers n, n-1,...,0 # La definition affiche(n) est un procedure # Lorsque l'on donne un argument négatif, la procédure ne s'execute pas. #Question 2: def fonction_mystere(n): if n <= 1: return 1 else: resultat = n * fonction_mystere(n-1) # appel récursif return resultat #La definition fonction_mystere(n) est une fonction. #Donne le resultat n*(n-1) jusqu'a que n=1 par exemple pour 6 nous avons #(((((6*5)*4)*3)*2)*1) qui donne 720. #Question 3: à modifier def genere_plateau(largeur, hauteur, probabilite_mine): """Génère un plateau de jeu de taille donnée.""" plateau = [] for i in range(largeur): plateau.append([]) for j in range(hauteur): plateau[i].append({"mine": random.random() < probabilite_mine, "etat": INCONNU}) return(plateau) #Question 4: écrire les trois fonctions suivantes def dans_plateau(plateau, x, y): """Teste si une case est sur le plateau.""" if len(plateau)> x and y < len(plateau[0]) and x>=0 and y>=0: return True else: return False def cases_voisines(plateau, x, y): """Donne la liste des coordonnées (tableaux de 2 entiers) des cases voisines de la case "(x,y)".""" res=[] voisin = [[x-1,y],[x-1,y-1],[x,y-1],[x+1,y-1],[x+1,y],[x+1,y+1],[x,y+1],[x-1,y+1]] for i in range (0, len(voisin)): if (dans_plateau(plateau,voisin[i][0],voisin[i][1])): res.append(voisin[i]) return res def compte_mines_voisines(plateau, x, y): """Compte le nombre de mines voisines de la case "(x,y)" sur le plateau "plateau".""" res= 0 voisin= cases_voisines(plateau, x, y) for i in range (len(voisin)): if plateau[voisin[i][0]][voisin[i][1]]["mine"]: res+=1 return res #Question 5: écrire la procédure récursive suivante def composante_connexe(plateau, x, y): """Met le plateau à jour en ouvrant toutes les cases vides à partir de la case "(x,y)". Attention, c'est une procédure... """ #pour le moment, il n'y a aucune propagation, et on compte simplement le #nombre de mines autours de la case (x,y) if (plateau[x][y]["etat"]==INCONNU): c=compte_mines_voisines(plateau,x,y) plateau[x][y]["etat"] = c if (c==0): for case in cases_voisines(plateau,x,y): composante_connexe(plateau,case[0],case[1]) #appel récursif #Question 6: def clic_droit(clic): # clic.x et clic.y contiennent les coordonnées, en pixel, # du clic à l'intérieur de la fenêtre x = clic.x // (largeur_case+1) # x et y contiennent les y = clic.y // (hauteur_case+1) # coordonnées de la case if not dans_plateau(plateau_courant, x, y): return if plateau_courant[x][y]["etat"] != INCONNU: return ok= plateau_courant[x][y]["etat"]= DRAPEAU dessine_plateau(plateau_courant) if plateau_courant[x][y]["etat"] == DRAPEAU: return ok= plateau_courant[x][y]["etat"]= QUESTION dessine_plateau(plateau_courant) #Question 7 écrire la fonction suivante def perdu(plateau): """renvoie True lorsque que le plateau contient une case découverte avec une mine""" # if plateau[x][y]["mine"]: # return True return False ### QUESTION : écrire la fonction suivante def gagne(plateau): """renvoie True lorsque que le plateau contient les drapeaux exactement sur les cases minées""" return False ############################### ############################### def decouvre_case(plateau, x, y): """Découvre une case sur le plateau. Le plateau est mis à jours en découvrant toute la composante connexe de la case "(x,y)", et la fonction renvoie un booléen pour dire si la case "(x,y)" était une mine ou pas. Attention, c'est à la fois une procédure (modification de l'argument "plateau" et une fonction (qui renvoie un booléen). """ if plateau[x][y]["mine"]: plateau[x][y]["etat"] = PERDU #print("OUPS... La case ({},{}) contenait une mine !".format(x,y)) return False composante_connexe(plateau, x, y) return True def compte_mines_solution(plateau): """Met le plateau à jour en comptant le nombre de mines partout. Attention, ceci est une procédure. """ l = len(plateau) h = len(plateau[0]) for x in range(l): for y in range(h): if plateau[x][y]["etat"] == INCONNU and not plateau[x][y]["mine"]: plateau[x][y]["etat"] = compte_mines_voisines(plateau, x, y) ####################################### ### Fonctions d'affichage sur la grille ### La fonction "dessine_case" utilise une constante globale (définie plus ### bas) "grille" qui représente la grille. ### Cette grille est un objet de type "Canvas" et a des méthodes de dessin ### comme "create_rectangle" et autre def dessine_case(plateau, x, y, solution=False): """Dessine la case "(x,y)" sur le plateau. Si "solution" est vraie, dessine aussi les mines qui sont dans des cases fermées. """ x1 = x*(largeur_case+1)+2 y1 = y*(hauteur_case+1)+2 x2 = (x+1)*(largeur_case+1) y2 = (y+1)*(hauteur_case+1) etat = plateau[x][y]["etat"] if etat == 0: grille.create_rectangle(x1, y1, x2, y2, outline='#c0c0c0', fill='#c0c0c0') elif 0 < etat < 9: grille.create_rectangle(x1, y1, x2, y2, outline='#c0c0c0', fill='#c0c0c0') x1 = x1 + largeur_case//2 y1 = y1 + hauteur_case//2 grille.create_text(x1, y1, justify=CENTER, text=str(etat)) elif etat == DRAPEAU: grille.create_image(x1, y1, image=drapeau_img, anchor=NW) elif etat == QUESTION: grille.create_image(x1, y1, image=question_img, anchor=NW) elif etat == INCONNU: if plateau[x][y]["mine"] and solution: grille.create_image(x1, y1, image=mine_img, anchor=NW) else: grille.create_image(x1, y1, image=inconnu_img, anchor=NW) elif etat == PERDU: grille.create_image(x1, y1, image=perdu_img, anchor=NW) else: assert(False) def dessine_plateau(plateau, solution=False): l = len(plateau) h = len(plateau[0]) grille.delete(ALL) for x in range(l): for y in range(h): dessine_case(plateau, x, y, solution) ####################################### ### Fonctions pour gérer les évènements ### Dans ces fonctions, ### - "plateau" est une variable globale qui contient le plateau courant, ### - "grille" est une constante globale qui contient la fenêtre. ### def __action_clic(clic): """Fonction appelée quand on fait un clic sur la fenêtre.""" # clic.x et clic.y contiennent les coordonnées, en pixel, # du clic à l'intérieur de la fenêtre x = clic.x // (largeur_case+1) # x et y contiennent les y = clic.y // (hauteur_case+1) # coordonnées de la case if not dans_plateau(plateau_courant, x, y): return if plateau_courant[x][y]["etat"] != INCONNU: return ok = decouvre_case(plateau_courant, x, y) dessine_plateau(plateau_courant) def __action_m(e): """Permet d'afficher la solution pendant 1 seconde.""" import copy from time import sleep p = copy.deepcopy(plateau_courant) compte_mines_solution(p) dessine_plateau(p, True) grille.update_idletasks() sleep(1) dessine_plateau(plateau_courant) def __action_q(e): """Permet de quitter le jeux.""" root.destroy() ############################################################### ### initialisation de la fenêtre, et autres constantes globales ### # quelques variables globales, modifiable par l'utilisateur largeur = 10 # largeur du plateau, en nombre de cases hauteur = 20 # hauteur du plateau, en nombre de cases probabilite_mine = 0.15 # probabilité qu'une case contienne une mine # fenêtre principale root = Tk() root.title("Démineur") root.resizable(width=False, height=False) Label(text="Info224 : démineur en Python").pack() # les images utilisées pour les cases spéciales # # Vous pouvez supprimer les déclarations contenant data='...' et les remplacer # par les lignes contenant file='...' # Bien sûr, il faut disposer des fichiers images correspondants... # Attention, si vous modifiez les fichiers images, il faut que toutes les # images aient la même taille (15x15 pixels pour les images par défaut). # # Remarque : les données "data" sont obtenues par codage en base64 des # fichiers binaires : # import base64 # data = base64.b64encode(open("inconnu.gif", mode="rb").read()) # #inconnu_img = PhotoImage(file="inconnu.gif") inconnu_img = PhotoImage(data='R0lGODdhDwAPAKUvAG1tbX5+foGBgYODg4SEhIWFhZycnK' 'GhoaioqKmpqaurq6ysrK2srKytrKytra2trbS0tLm5ub6+' 'vr+/v87PztbW1tjY2NnZ2dva2tvb29vb3Nzb29zb3Nvc29' 'zc29zc3N3c3enp6e3t7e7u7vDw7/Ly8vX19fX19vb19vX2' '9fX29vb29fb29vf39/j4+P////////////////////////' '///////////////////////////////////////////ywA' 'AAAADwAPAAAGXMBNqaU6sVioVeroMiFClIplSqVeIIERRj' 'Lper2SxED0+Zq7i3HnbE6LMuyvmxP3usv1iVuT1489fXeB' 'Y3R5aSMgfQoEJBEJDgwPDw0LDpILBgQHAgSdnp8FBQBBAD' 's=') #question_img = PhotoImage(file="question.gif") question_img = PhotoImage(data='R0lGODdhDwAPAMZpAAAA/wEB/wYG/RMT+Bsb9iMj8y4u7' 'y4u8EJC6VFR5FlZ4Vxc4F1d4GBg32pqamJi3m1t2nx8e3' 'l51oCAgIGAgYCBgIODg4SEhIWFhYGB1IOD04SE04mJ0Yq' 'K0ZeYl5ycnJ6eyqKioqKiyaamx6mpqaenx6usq6ysrKqq' 'xq2srK2sraytrK2trK2trbCwq66uxbCwrLCwxLKyxLS0t' 'bS0w7W1wri4uLi4wbq6wLu7wby8wL6+vr6+v76+wL+/v8' 'C/v8DAv8HBv8LCvsPDvcPDvsTEvcXFvcjIvMjIvcrKu8z' 'Mu87OutDQ0NHR0dbW1tjY2NnY2NnZ19nZ2dra19vb19rb' '29zc3N3c3dzd3N3d3N3d3d3e3d/f3+Pj4+Tk5OXl5fT09' 'PX19fX19vb19vX29fb29fb29vf39/j4+P////////////' '/////////////////////////////////////////////' '//////////////////////////////////ywAAAAADwAP' 'AAAHxYBNYGdmYmZmY2Vkh2hhIVxMTk9PUVNUU1CTUjMRX' 'lU7PERFODc8R0A+OyQTXVo+QyMIBAMGEkKoLKxXPkcbAQ' 'cFAAAdSD4nrK5CKCJEPgcAD0o+KaxYPj5CRksvBAAcxcd' 'd1j5APBAEAg23PiusVtdCNcIZSUHX7V1Z10E5CwkxQ675' 'aOEOngwGCkAUERjOlY8iJQQA0HCE4QQvW3b4+NGDBg0dq' 'K6ZoPDFBokWKVjAcMFCBYsWJz5U8GDhgs2bODFgcBAIAD' 's=') #mine_img = PhotoImage(file="mine.gif") mine_img = PhotoImage(data='R0lGODdhDwAPAMZQAAAAAAMDAwYGBhYWFhoaGhwcHB4eHiMjI' 'yQkIyYmJicnJygoKCoqKisrKy0tLS8vLzAwMDIyMzMzMzQ0ND' 'k5OUhIR1VVVVZWVl5eXl9fX2VlZWZmZmdnZ2hnZ2pqamtra3F' 'xcXR0dHZ2dnd3d3d3eH9/f4GAgIGBgYKCgoeHh4iIiI+Pj5OT' 'k6KioqOjo6SkpKampqenp6qqqqurq6+ur6+wsLKysrOzs7S0t' 'LW1tbe3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwc' 'LCwsPDw8TExMXFxcbGxs7Ozs/Pz9HR0dPT09TU1PDw8P/////' '/////////////////////////////////////////////////' '/////////////////////////////////////////////////' '/////////////////////////////////////////////////' '///////////////////////////////////////ywAAAAADwA' 'PAAAHvIA+REOEhEWFhEQ+gohDPEKIRD2MSElGOxIzR0lJQ5KC' 'SCElSjUDLU4rHkZFk0RJKQYiHQ4PGgYhSayCRUsjHjQqDQAWT' 'EefRDs2Gy5PORUADDg7Qz1BPRMGCCQyJg4AAAYMNkA+QzMxEQ' 'UUCQIACzAyQq1HThzf9xhOxq1ILAcXDAIoyGDgBJJjSD6AYHK' 'DwIsmKDCsajXECJIjOyDMqGTEU48fRYgUCUmEhyeRIn3oAPKj' 'ZUshLlsC0REIADs=') #drapeau_img = PhotoImage(file="drapeau.gif") drapeau_img = PhotoImage(data='R0lGODdhDwAPAMZxAAAAAAUFBQYGBhERERYaGhoaGikWFo' 'MAAJEAAHwGBn4HB34ICIIHB9cAAOQAANQHB/8AAP4DA/wJ' 'CfsSEl5eXmBgYNdCQuo+PmNjY+4+PmZmZudGRmpqan19fI' 'GBgYKBgoKCgoODg4SEhIWFhZaWlpiYmJuam6qXl5ycnKOj' 'o7KiorWjo7ajo7WlpampqKmpqcikpKusrKysrK2srK6srK' 'ytrK2trK2trbqsrLCwsLGxsbKysrOzs7S0tbK2tsOysra2' 'tsOzs7i4uMK2trm5ubu7u8C9vb6+vr+/v7/AwMDAwL7Bwc' 'HBwb7Cwr/CwsHCwsLCwr3ExMPDw8DExMHExMTHx8vLy9LS' '0tbW1tjY2NjY2dfZ2dfZ2tjZ2dnZ2dvb293d3d7d3d3e3t' '7e3uLi4eXl5ebm5ujo6PPz8/X19fb19vX29fX29vb29fb2' '9vf39/j4+P////////////////////////////////////' '///////////////////////ywAAAAADwAPAAAHv4BXaG9u' 'bG5uam1rh3BpKWRXWFlcXVuWWllZXj0dZl9HSE2iTU9KSE' 'hHLx5lYktDGxcXGRY4Tqc2q2BNPxEQEA4IKlSnMrlNQRIQ' 'DQcKK8NIM8ZBEw8MCQvOxLlJRjAsLScGNFKnNbmnUVNVPg' 'Q35EjmZWCnp1BABTrvN+f1ViUDJawwQVKsTJhTTHhQCABA' 'AIciTIqZGQMKCokKGDRgwJACSowPZ4q4uCFjB5GTRITkkI' 'EChIkQImLKnDliBIdAADs=') #mauvais_drapeau_img = PhotoImage(file="mauvais_drapeau.gif") mauvais_drapeau_img = PhotoImage(data='R0lGODdhDwAPAMZdAP8AAP4BAf4CAv8CAvUdHf' 'UfH/AtLfAuLulDQ+hEROdGRudHR2pqauNSUuNT' 'U+JVVeJWVuJXV3x8e95jY91mZoCAgN1nZ4GAgY' 'CBgISEhIWFhdSBgdSCgpycnM6RkaKioqCkpKmp' 'qausq6ysrKysra2srK2sraytrK2trK2trbS0tb' 'a2tsO0tMG4uMK4uMC7u7+8vMC8vMC9vb6+vr++' 'vsC+vr+/v77AwL/AwL7Bwb/Bwb3Cwr7Cwr7Dw7' '3ExL7ExL3FxbzGxr3GxrzHx7zIyM7OztbW1tjY' '2NnZ2dra2tva2trb2tvb29vf3+js7ezw8ezx8O' 'zx8e3y8u7y8vLy8vX19fX19vb19vX29fb29fb2' '9vf39/j4+P////////////////////////////' '//////////////////////////////////////' '//////////////////////////////////////' '///////////////////////////////////ywA' 'AAAADwAPAAAHyoBNVFtaVlpaV1lYh1xVH05FRk' 'eTlJRIKhJSRzM4QTk2oJ04MyEVT0s2MB4vOzQ5' 'Nx4tOCgVUUlCHgANQD5EEwAcRCe1SjkxDgAPQh' 'QACS45JaZJNjxADwAHAQs5PjYj0qBAQgYABDRD' 'NDbDUNM5RBYCBQMRQDzq0jg3vwoyEAAONXSkkB' 'YkF4IcQ4Ag20CERAV2OlpwcPHDRo8aHFjo+CaF' 'CQ0dRD6BAqmDhogLU1aESFGiRIoTI0ygSDGiAw' 'YQFzLo3MlTgwYGgQAAOw==') #perdu_img = PhotoImage(file="perdu.gif") perdu_img = PhotoImage(data='R0lGODdhDwAPAMZZAAAAAAABAQcBAQMDAx4AACAAACIAACYA' 'ACMBASUBAScBASsBATMAADsAAD0AAD4DAz8EBFAAAEcDA0sC' 'AioNDWoCAhQbG3ECAnsAAHYDA4ECAooAAIUDAx8fHx4gIJcA' 'AJgAAI4DA6kAAKoAAKwAACYmJroAALwAANIAANkAADAvMNwB' 'ASsxMeIAAN8BAeQAAOUAAOoAAOsAAOsBAfAAAPMAAPQAAPIB' 'AfcAADM2N/wAAP4AAP8AAP8BAZYeHvcEA/UFBfUFBoAlJPgF' 'BfUGBfYGBvkGBfYHB/oHB/cICPgICPoICO8ODmE4OENDQmtL' 'S29wb3d4d2yHiJGRkZ6enqGpqbOzs7nAwO7u7v//////////' '////////////////////////////////////////////////' '////////////////////////////////////////////////' '////////////////////////////////////////////////' '/ywAAAAADwAPAAAHnYBHPIOEhYRFgoU7MjqGQYmENQwthkCJ' 'GyA8LQQoPCMYOzxEiSYHQk85FBUKIoOWhD5SV1MsABc9rkc7' 'LylNVVhWTgALKy87REg4DgYeUFRRJQABBg04Rro0MRAWKh0D' 'AA8zNDuPhBoA6NIZhKODJAgcEwISIQmZookfJDw3BS48JzaE' 'KlfIRgQYjoYY4tHI0A8mSSJKXKJEYkQmgQAAOw==') # on vérifie que les images ont toute les même dimensions assert (mine_img.height() == perdu_img.height() == drapeau_img.height() == mauvais_drapeau_img.height() == question_img.height() == inconnu_img.height()) assert (mine_img.width() == perdu_img.width() == drapeau_img.width() == mauvais_drapeau_img.width() == question_img.width() == inconnu_img.width()) largeur_case = mine_img.width() hauteur_case = mine_img.height() # la grille : un objet de type "Canvas" pour pouvoir dessiner dedans. grille = Canvas(root, width=largeur*(largeur_case+1)+1, height=hauteur*(hauteur_case+1)+1, bg="#7f7f7f") grille.pack() # les évènements à gérer root.bind("q", __action_q) root.bind("m", __action_m) grille.bind("", __action_clic) grille.bind("",clic_droit) # création du plateau, et début du programme... plateau_courant = genere_plateau(largeur, hauteur, probabilite_mine) dessine_plateau(plateau_courant) grille.mainloop() ### Fin du fichier ### ######################