# -*- coding: utf-8 -*-

##  Copyright 2005 Sébastien Fagot
##
##    This program is free software; you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation; either version 2 of the License, or
##    (at your option) any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program; if not, write to the Free Software
##    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

from Tkinter import *
from random import sample
from tkSimpleDialog import *
from time import *
from os import name

class Jeu_HexaMineur:
    "jeu de démineur version hexagonale"
    def __init__(self,rayon=4,nombre_de_mine=10,x_1er_coup=3,y_1er_coup=3):
        self.rayon = rayon
        self.cote = 2*self.rayon + 1
        self.nombre_de_mine = nombre_de_mine
        self.x_1er_coup = x_1er_coup
        self.y_1er_coup = y_1er_coup
        # création de la variable carte_des_mines[i][j]
        self.carte_des_mines = [[0]*30]
        for i in range(29) : self.carte_des_mines.append(self.carte_des_mines[0][:])
        # création de la variable nombre_de_mine_voisine[i][j]
        self.nombre_de_mine_voisine = [[0]*30]
        for i in range(29) : self.nombre_de_mine_voisine.append(self.nombre_de_mine_voisine[0][:])
        # placement des mines
        self.placement_des_mines()
        # remplissage de la carte d'exploration
        self.calcul_des_indices()   
    def placement_des_mines(self):
        "place les mines au hasard"
        if 1+self.rayon*(self.rayon+1)*3 == self.nombre_de_mine:
            for i in range(30):
                for j in range(30):
                    self.carte_des_mines[i][j] = 1
        else:
            for i in range(30):
                for j in range(30):
                    self.carte_des_mines[i][j] = 0
            n=0
            liste_case_libre = []
            for i in range(self.cote):
                for j in range(self.cote):
                    if (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 and not(i==self.x_1er_coup and j==self.y_1er_coup):
                        liste_case_libre.append([i,j])
            while n < self.nombre_de_mine :
                case = sample(liste_case_libre,1)
                i = case[0][0]
                j = case[0][1]
                self.carte_des_mines[i][j] = 1
                liste_case_libre.remove([i,j])
                n = n + 1 
    def calcul_des_indices(self):
        "remplit la carte d'exploration"
        for i in range(30):
            for j in range(30):
                self.nombre_de_mine_voisine[i][j] = 0
        for i in range(self.cote):
            for j in range(self.cote):
                for k in (-1,0,1):
                    if i+k<self.cote and i+k>-1 and self.carte_des_mines[i+k][j]:
                        self.nombre_de_mine_voisine[i][j] = self.nombre_de_mine_voisine[i][j] + 1
                for l in (-1,01):
                    if j+l<self.cote and j+l>-1 and self.carte_des_mines[i][j+l]:
                        self.nombre_de_mine_voisine[i][j] = self.nombre_de_mine_voisine[i][j] + 1
                if i-1<self.cote and i-1>-1 and j+1<self.cote and j+1>-1 and self.carte_des_mines[i-1][j+1]:
                    self.nombre_de_mine_voisine[i][j] = self.nombre_de_mine_voisine[i][j] + 1
                if i+1<self.cote and i+1>-1 and j-1<self.cote and j-1>-1 and self.carte_des_mines[i+1][j-1]:
                    self.nombre_de_mine_voisine[i][j] = self.nombre_de_mine_voisine[i][j] + 1

class Case:
    "définit les attributs d'une case du jeu"
    def __init__(self):
        self.id = 0
        self.deja_testee = 0
        self.marque = 0
        self.x = 0
        self.y = 0

class Gui_HexaMineur:
    "interface graphique d'HexaMineur"
    def __init__(self,rayon=4,nombre_de_mine=10):
        # initialisation des variables
        self.rayon = rayon
        self.cote = 2*rayon + 1
        self.nombre_de_mine = nombre_de_mine
        self.nombre_de_marque = 0
        self.jeu = Jeu_HexaMineur()
        # réglage des polices en fonction du système d'exploitation
        if name == 'nt' : # windows
            self.police_mine = "comic 10 bold"
            self.police_explose = "comic 10 bold"
            self.police_message = "arial 12 bold"
        else : # linux ou mac ou autre
            self.police_mine = "comic 13 bold"
            self.police_explose = "comic 15 bold"
            self.police_message = "arial 17 bold"
        # création de la variable case[i][j]
        self.case = []
        for i in range(30) : self.case.append([[]]*30)
        for i in range(30):
            for j in range(30):
                self.case[i][j]=Case()
        # création de la fenetre principale
        self.fenetre = Tk()
        self.fenetre.title('HexaMineur')
        self.fenetre.resizable(0, 0)
        # message
        self.message = Label(self.fenetre,text='Bienvenue',\
                             font=self.police_message)
        self.message.grid(row=0)
        # information sur les mines
        self.info = Label(self.fenetre)
        self.info.grid(row=1,sticky=W)
        # horloge
        self.horloge = Label(self.fenetre)
        self.horloge.grid(row=1,sticky=E)
        # création du plateau de jeu
        self.plateau = Canvas(self.fenetre,borderwidth=1, \
                    bg='grey',highlightthickness=0)
        self.plateau.grid(row=2)
        # création du menu général
        menu_general = Menu(self.fenetre)
        self.fenetre.configure(menu = menu_general)
        menu_general.add_command(label='Nouvelle partie',\
                                 command=self.commencer_partie)
        menu_niveau = Menu(menu_general)
        menu_general.add_cascade(label='Niveau',menu=menu_niveau)
        menu_niveau.add_command(label ='Débutant', command = self.debutant)
        menu_niveau.add_command(label ='Intermédiaire',\
                                command = self.intermediaire)
        menu_niveau.add_command(label ='Expert', command = self.expert)
        menu_niveau.add_separator()
        menu_niveau.add_command(label ='Libre',command = self.libre)
        menu_general.add_command(label='Quitter',command=self.fenetre.destroy)
        menu_general.add_command(label='Aide',command=self.aide)
        # commencement du jeu
        self.fenetre.bind("<Destroy>",self.arret)
        self.commencer_partie()
        self.continuer = 1
        while self.continuer:
            self.fenetre.after(40,self.temps)
            self.fenetre.mainloop() # démarrage du réceptionnaire d'événements
    def aide(self):
        "affiche une fenêtre d'aide"
        fenetre_aide = Toplevel(self.fenetre)
        fenetre_aide.title("Aide - SebMineur")
        fenetre_aide.resizable(0, 0)
        texte = "Aide du jeu\n\n"\
                "Le but est de découvrir toutes les cases sans mines.\n\n"\
                "clic gauche : teste la case, un chiffre indique le "\
                "nombre de mines présentes sur l'ensemble des 7 cases "\
                "voisines\n\n"\
                "clic droit : protège la case, empèche un clic "\
                "gauche sur cette case (utile pour éviter une erreur de "\
                "clic), clic une 2ème fois pour déprotéger la case\n\n"\
                "clic milieu : découvre toutes les cases voisines "\
                "non-protégées (si le nombre de cases protégées est égal "\
                "au nombre de mines voisines)\n\n"\
                "Le temps de jeu s'affiche en haut à droite.\n"\
                "Le nombre de cases protégées et le nombre total de mines "\
                "s'affichent en haut à gauche.\n\n"\
                "N'aie pas peur ! Ton 1er coup sera toujours sur une case "\
                "sans mine ! (sauf si tu fais un niveau totalement rempli "\
                "de mines...)\n\n"\
                "!!! BONNE CHANCE !!!"
        Message(fenetre_aide, text = texte).grid()
    def arret(self,event):
        "arrêt du programme"
        self.continuer = 0
    def temps(self):
        "chronomètre le temps de jeu"
        if self.partie_en_cours:
            duree = int(time() - self.instant_zero)
            self.horloge.configure(text=str(duree))
        self.fenetre.quit()
    def dessin_hexagone(self,canevas,position_x,position_y):
        "dessine un hexagone"
        x = position_x
        y = position_y
        return canevas.create_polygon(x,y+14, x-12,y+7, x-12,y-7, x,y-14,\
                            x+12,y-7, x+12,y+7, fill='white',outline='grey')
    def libre(self):
        "réglages du niveau libre"
        Niveau_libre(self.fenetre,self.rayon,self.nombre_de_mine,self)
    def debutant(self):
        "réglages du niveau débutant"
        self.efface_le_plateau()
        self.rayon = 4
        self.nombre_de_mine = 10
        self.commencer_partie()
    def intermediaire(self):
        "réglages du niveau intermédiaire"
        self.efface_le_plateau()
        self.rayon = 9
        self.nombre_de_mine = 43
        self.commencer_partie()
    def expert(self):
        "réglages du niveau expert"
        self.efface_le_plateau()
        self.rayon = 13
        self.nombre_de_mine = 113
        self.commencer_partie()
    def efface_le_plateau(self):
        "efface le plateau"
        liste_de_tous = self.plateau.find_all()
        for i in liste_de_tous:
            self.plateau.delete(i)
    def commencer_partie(self):
        "commence une nouvelle partie"
        self.efface_le_plateau()
        self.message.configure(text='Bienvenue',fg='black')
        self.cote = 2*self.rayon + 1
        self.plateau_premier_coup()
        self.nombre_de_case_decouverte = 0
        self.nombre_de_marque = 0
        self.affiche_info()
    def plateau_premier_coup(self):
        "dessine le plateau de départ avant le premier coup"
        self.plateau.configure(width=24*self.cote+1, height=28+self.rayon*42+1)
        for i in range(self.cote):
            for j in range(self.cote):
                if (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    self.case[i][j].id = self.dessin_hexagone(self.plateau,\
                                        i*24+(j-self.rayon+1)*12+1,j*21+14+1)
                    self.case[i][j].deja_testee = 0
                    self.case[i][j].marque = 0
                    self.case[i][j].x = i
                    self.case[i][j].y = j
                    def premier_clic(event,self=self,case=self.case[i][j]):
                        return self.premier_clic_gauche(event,case)
                    self.plateau.tag_bind(self.case[i][j].id,"<Button-1>",premier_clic)
        self.partie_en_cours = 0
        self.duree = 0
        self.horloge.configure(text=str(self.duree))
    def premier_clic_gauche(self,event,case):
        "initialisation du jeu après le premier clic"
        self.instant_zero = time()
        self.jeu = Jeu_HexaMineur(self.rayon,self.nombre_de_mine,case.x,case.y)
        self.dessine_plateau()
        self.partie_en_cours = 1
        self.clic_gauche(event,case)
    def dessine_plateau(self):
        "dessine le plateau de départ"
        for i in range(self.cote):
            for j in range(self.cote):
                if (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    self.case[i][j].mine = self.jeu.carte_des_mines[i][j]
                    self.case[i][j].exploration = self.jeu.nombre_de_mine_voisine[i][j]
                    def clic_gauche(event,self=self,case=self.case[i][j]):
                        return self.clic_gauche(event,case)
                    def clic_milieu(event,self=self,case=self.case[i][j]):
                        return self.clic_milieu(event,case)
                    def clic_droit(event,self=self,case=self.case[i][j]):
                        return self.clic_droit(event,case)
                    self.plateau.tag_bind(self.case[i][j].id, "<Button-1>",\
                                          clic_gauche)
                    self.plateau.tag_bind(self.case[i][j].id, "<Button-2>",\
                                          clic_milieu)
                    self.plateau.tag_bind(self.case[i][j].id, "<Button-3>",\
                                          clic_droit)
    def clic_milieu(self,event,case):
        "action lors d'un clic sur une case avec le bouton du milieu"
        if not case.deja_testee : return
        if case.marque : return
        if case.mine : return
        # compte le nombre de marques autour de la case
        nombre_de_marque = 0
        j = case.y
        for i in (-1+case.x,1+case.x):
            if i<self.cote and i>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                if self.case[i][j].marque:
                    nombre_de_marque = nombre_de_marque + 1
        i = case.x
        for j in (-1+case.y,1+case.y):
            if j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                if self.case[i][j].marque:
                    nombre_de_marque = nombre_de_marque + 1
        i = case.x -1
        j = case.y +1
        if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
            if self.case[i][j].marque:
                nombre_de_marque = nombre_de_marque + 1
        i = case.x +1
        j = case.y -1
        if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
            if self.case[i][j].marque:
                nombre_de_marque = nombre_de_marque + 1
        # appuie sur toutes les cases voisines si c'est cohérent
        if nombre_de_marque == case.exploration:
            j = case.y
            for i in (-1+case.x,1+case.x):
                if i<self.cote and i>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    case_sans_risk = self.case[i][j]
                    self.clic_gauche(event,case_sans_risk)
            i = case.x
            for j in (-1+case.y,1+case.y):
                if j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    case_sans_risk = self.case[i][j]
                    self.clic_gauche(event,case_sans_risk)
            i = case.x -1
            j = case.y +1
            if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                case_sans_risk = self.case[i][j]
                self.clic_gauche(event,case_sans_risk)
            i = case.x +1
            j = case.y -1
            if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                case_sans_risk = self.case[i][j]
                self.clic_gauche(event,case_sans_risk)
    def clic_droit(self,event,case):
        "action lors d'un clic sur une case avec le bouton droit"
        if case.deja_testee : return
        if case.marque :
            self.plateau.itemconfigure(case.id,fill='white')
            case.marque = 0
            self.nombre_de_marque = self.nombre_de_marque - 1
        else :
            self.plateau.itemconfigure(case.id,fill='blue')
            case.marque = 1
            self.nombre_de_marque = self.nombre_de_marque + 1
        self.affiche_info()
    def clic_gauche(self,event,case):
        "action lors d'un clic sur une case avec le bouton gauche"
        couleurs = ['blue','darkgreen','red','purple','brown','yellow']
        if case.marque : return
        if case.deja_testee : return
        case.deja_testee = 1
        self.nombre_de_case_decouverte = self.nombre_de_case_decouverte + 1
        if case.mine :
            self.message.configure(text='!!! BOOOM !!!',fg="red")
            self.stop(case)
        else:
            self.plateau.itemconfigure(case.id,fill='lightgrey')
            if case.exploration:
                texte=self.plateau.create_text(case.x*24+(case.y-self.rayon+1)*12+1,\
                    case.y*21+14+1,fill=couleurs[case.exploration-1],\
                    font=self.police_mine,text=str(case.exploration))
                def clic_milieu(event,self=self,case=case):
                    return self.clic_milieu(event,case)
                self.plateau.tag_bind(texte,"<Button-2>",clic_milieu)
            else :
                j = case.y
                for i in (-1+case.x,1+case.x):
                    if i<self.cote and i>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                        case_sans_risk = self.case[i][j]
                        self.clic_gauche(event,case_sans_risk)
                i = case.x
                for j in (-1+case.y,1+case.y):
                    if j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                        case_sans_risk = self.case[i][j]
                        self.clic_gauche(event,case_sans_risk)
                i = case.x -1
                j = case.y +1
                if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    case_sans_risk = self.case[i][j]
                    self.clic_gauche(event,case_sans_risk)
                i = case.x +1
                j = case.y -1
                if i<self.cote and i>-1 and j<self.cote and j>-1 and (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                    case_sans_risk = self.case[i][j]
                    self.clic_gauche(event,case_sans_risk)
            if self.nombre_de_case_decouverte == \
               1+self.rayon*(self.rayon+1)*3 - self.nombre_de_mine:
               self.message.configure(text='!!! BRAVO !!!',fg='green')
               self.stop(case)
    def stop(self,case):
        "arrête la partie"
        liste_de_tous = self.plateau.find_all()
        for i in liste_de_tous:
            self.plateau.tag_unbind(i,"<Button-1>")
            self.plateau.tag_unbind(i,"<Button-2>")
            self.plateau.tag_unbind(i,"<Button-3>")
        if not case.mine:
            for i in range(self.cote):
                for j in range(self.cote):
                    if (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                        if self.case[i][j].mine:
                            self.plateau.itemconfigure(self.case[i][j].id,\
                                                       fill='green')
        else :
            for i in range(self.cote):
                for j in range(self.cote):
                    if (i+j)>self.rayon-1 and (i+j)<3*self.rayon+1 :
                        if self.case[i][j].mine:
                            self.plateau.itemconfigure(self.case[i][j].id,\
                                                       fill='red')
                            self.plateau.create_text(i*24+(j-self.rayon+1)*12+1,\
                                j*21+14+1,fill='black',text='M',\
                                font=self.police_explose)
        self.partie_en_cours = 0
    def affiche_info(self):
        "affiche des infos sur la partie en cours"
        self.info.configure(text=str(self.nombre_de_mine - \
            self.nombre_de_marque)+' / '+str(self.nombre_de_mine))        

class Niveau_libre(Dialog):
    "fenetre de réglage pour le niveau libre"
    def __init__(self,parent,rayon,nombre_de_mine,jeu):
        self.rayon = rayon
        self.nombre_de_mine = nombre_de_mine
        self.jeu = jeu
        Dialog.__init__(self, parent)
    def body(self, master):
        self.title("Niveau libre")
        self.reglage_rayon = Scale(master,from_=0,to=13,orient="horizontal",\
                        tickinterval=1,label='Rayon',command=self.max_mine)
        self.reglage_rayon.set(self.rayon)
        self.reglage_rayon.pack(fill=X)
        
        self.reglage_nombre_de_mine = Scale(master,from_=0,length=547+1+33,\
            to=1+self.reglage_rayon.get()*(self.reglage_rayon.get()+1)*3,\
            orient="horizontal",label='Nombre de mine')
        self.max_mine(None)
        self.reglage_nombre_de_mine.set(self.nombre_de_mine)
        self.reglage_nombre_de_mine.pack(fill=X)
    def apply(self):
        self.jeu.rayon = self.reglage_rayon.get()
        self.jeu.nombre_de_mine = self.reglage_nombre_de_mine.get()
        self.jeu.commencer_partie()
    def max_mine(self,event):
        interval = (1+self.reglage_rayon.get()*(self.reglage_rayon.get()+1)*3)/8
        if interval == 0 : interval = 1
        self.reglage_nombre_de_mine.config(tickinterval=interval,\
            to=1+self.reglage_rayon.get()*(self.reglage_rayon.get()+1)*3)

Gui_HexaMineur()
