# -*- coding: utf-8 -*-
"""
Created on Sat May 25 15:22:41 2024

@author: Stéphane Pasquet
@url : https://www.mathweb.fr/euclide/le-jeu-du-morpion/
"""

# pip install pillow pour PIL
# pyinstaller --onefile --windowed --icon=morpion.ico morpion.py

from random import choice
from tkinter import Tk, Canvas, Button, Label, StringVar, OptionMenu, DISABLED, NORMAL
from PIL import Image, ImageTk
from pickle import dump, load

class Morpion:
    def __init__(self, window):
        window.geometry("750x505")
        window.title("Le jeu du Morpion - Stéphane Pasquet (mathweb.fr)")
        window.iconbitmap("morpion.ico")
        Label(window, text="Le jeu du Morpion (OXO)", font=("Helvetica", 20)).place(x=190,y=1)
        
        self.game = Canvas(window, width = 405, height = 405, bg = "white")
        
        self.gameOn = False
        
    
        # Menu de droite
        
        Label(text="Quel niveau ?", font=('Helvetica', 12), fg='red').place(x = 420, y = 50)
        self.LevelList = [ "?????" , "Facile", "Difficile" ]
        self.LevelVar = StringVar(window)
        self.LevelVar.set(self.LevelList[0])
        self.LevelOptions = OptionMenu(window, self.LevelVar, *self.LevelList)
        self.LevelOptions.config(width = 10, font=('Helvetica', 12))
        self.LevelOptions.place(x = 600 , y = 44)
        self.niveau = self.LevelVar.trace("w", self.jeu)
    
        Label(text="Qui commence à jouer ?", font=('Helvetica', 12), fg='red').place(x = 420, y = 100)
        self.FirstPlayerList = [ "?????" , "Joueur", "Ordinateur" ]
        self.FirstPlayerVar = StringVar(window)
        self.FirstPlayerVar.set(self.FirstPlayerList[0])
        self.FirstPlayerOptions = OptionMenu(window, self.FirstPlayerVar, *self.FirstPlayerList)
        self.FirstPlayerOptions.config(width = 10, font=('Helvetica', 12))
        self.FirstPlayerOptions.place(x = 600 , y = 94)
        self.player_one = self.FirstPlayerVar.trace("w", self.jeu)
        
        # bouton "rejouer"
        
        self.rejouer = Button(window , text = 'Rejouer' , state=DISABLED , command=self.restart)
        self.rejouer.place(x = 560 , y = 400)
        
        # bouton "voir les stats"
        
        self.stats = Button(window , text = 'Voir les statistiques'  , command=self.visu_stats)
        self.stats.place(x = 530 , y = 450)
        
    
        # les images : croix & cercles
    
        self.image_croix = Image.open("croix.png")
        self.croix = ImageTk.PhotoImage(self.image_croix)
        
        self.image_cercle= Image.open("cercle.png")
        self.cercle = ImageTk.PhotoImage(self.image_cercle)
        
        self.text_gamer = StringVar()
        self.texte_joueur = Label(root , textvariable=self.text_gamer , font=('Helvetica', 12), fg='blue').place(x = 420, y = 150)
        
        self.text_result = StringVar()
        self.texte_result = Label(root , textvariable=self.text_result , font=('Helvetica', 12), fg='purple').place(x = 420, y = 200)

        self.restart() # initialisation
        
    def restart(self):
        self.G = [ [0,0,0] , [0,0,0] , [0,0,0] ]
        self.nb_cercles = 0
        self.nb_croix = 0
        self.text_gamer.set(' '*100)
        self.text_result.set(' '*100)
        self.draw_grid()
        self.FirstPlayerVar.set(self.FirstPlayerList[0])
        self.LevelVar.set(self.LevelList[0])
        self.rejouer.config(state=DISABLED , bg = 'gray')
        
    def draw_grid(self):
        self.game.create_rectangle(0,0,405,405,fill='white')
        for x in [ 4 , 135 , 270 , 405 ]:
            self.game.create_line(0 , x , 405 , x , fill = 'black' , width = 4)
            self.game.create_line(x , 0 , x , 405 , fill = 'black' , width = 4)
        
        self.game.place(x = 0 , y = 50)
        
    def fin_partie(self):
        # on teste s'il y a alignement
        if ( self.G[0][0] == self.G[1][0] and self.G[1][0] == self.G[2][0] and self.G[0][0] != 0) \
                   or ( self.G[0][1] == self.G[1][1] and self.G[1][1] == self.G[2][1] and self.G[0][1] != 0) \
                   or ( self.G[0][2] == self.G[1][2] and self.G[1][2] == self.G[2][2] and self.G[0][2] != 0) \
                   or ( self.G[0][0] == self.G[0][1] and self.G[0][1] == self.G[0][2] and self.G[0][0] != 0) \
                   or ( self.G[1][0] == self.G[1][1] and self.G[1][1] == self.G[1][2] and self.G[1][0] != 0) \
                   or ( self.G[2][0] == self.G[2][1] and self.G[2][1] == self.G[2][2] and self.G[2][0] != 0) \
                   or ( self.G[0][0] == self.G[1][1] and self.G[1][1] == self.G[2][2] and self.G[0][0] != 0) \
                   or ( self.G[0][2] == self.G[1][1] and self.G[1][1] == self.G[2][0] and self.G[0][2] != 0):
            if self.FirstPlayerVar.get() == 'Ordinateur' and self.nb_croix > self.nb_cercles:
                return ( True , "Ordinateur" )
            elif self.FirstPlayerVar.get() == 'Ordinateur' and self.nb_croix < self.nb_cercles:
                return ( True , "Joueur" )
            elif self.FirstPlayerVar.get() == 'Joueur' and self.nb_croix < self.nb_cercles:
                return ( True , "Joueur" )
            else:
                return (True , "Ordinateur")
        # sinon on teste si la grille est pleine
        elif self.nb_croix + self.nb_cercles == 9:
            return (True , None)
        else:
            return ( False , None )
        
    def there_is_two(self,symb):
        if symb == 'X':
            symbcomp = 'O'
        else:
            symbcomp = 'X'
        GoodPlaceList = [ [(0,0),(1,0),(2,0)] , [(1,0),(2,0),(0,0)] , [(0,0),(2,0),(1,0)] , \
                          [(0,1),(1,1),(2,1)] , [(1,1),(2,1),(0,1)] , [(0,1),(2,1),(1,1)] , \
                          [(0,2),(1,2),(2,2)] , [(1,2),(2,2),(0,2)] , [(0,2),(2,2),(1,2)] , \
                          [(0,0),(1,1),(2,2)] , [(1,1),(2,2),(0,0)] , [(0,0),(2,2),(1,1)] , \
                          [(0,2),(1,1),(2,0)] , [(1,1),(2,0),(0,2)] , [(0,2),(2,0),(1,1)] , \
                          [(0,0),(0,2),(0,1)] , [(1,0),(1,2),(1,1)] , [(2,0),(2,2),(2,1)] , \
                          [(0,0),(0,1),(0,2)] , [(1,0),(1,1),(1,2)] , [(2,0),(2,1),(2,2)] ]
        
        for i in GoodPlaceList:
            if (self.G[i[0][1]][i[0][0]] == symb) and (self.G[i[1][1]][i[1][0]] == symb) \
               and (self.G[i[2][1]][i[2][0]] != '0') and (self.G[i[2][1]][i[2][0]] != symbcomp):
                return True, i[2]
            
        return False, (None,None)
    
    def play(self,joueur,niveau_game):            
        if joueur == 1:
            # cas où c'est à l'ordinateur de jouer
            tab_coord = []  # tableaux des coordonnées libres
            for i in range(3):
                for j in range(3):
                    if self.G[i][j] == 0:
                        # si la case est vide...
                        tab_coord.append( (j,i) )
            
            if niveau_game == "Facile":
                x, y = choice(tab_coord)
            else:
                # niveau "Difficile"
                if self.G[1][1] == 0:
                    x,y = 1,1
                elif self.nb_cercles == 1:
                    # on choisit au hasard quand il n'y a qu'un cercle ou aucun, quand la case du milieu est prise
                    tab_coord = [ (j,i) for i in range(3) for j in range(3) if self.G[i][j] == 0 ]
                    x, y = choice(tab_coord)
                else:
                    # s'il y a plus d'un cercle
                    b,coord = self.there_is_two('X')
                    if b == True:
                        x,y = coord
                    else:
                        b,coord = self.there_is_two('O')
                        if b == True:
                            x,y = coord
                        else:
                            tab_coord = []  # tableaux des coordonnées libres
                            for i in range(3):
                                for j in range(3):
                                    if self.G[i][j] == 0:
                                        # si la case est vide...
                                        tab_coord.append( (j,i) )
                            x,y = choice(tab_coord)
                
            self.G[y][x] = 'X'
            self.nb_croix += 1

            self.game.create_image(68 + x * 135 , 68 + y * 135, anchor = 'center', image = self.croix)
            
            if self.fin_partie()[0] == True:       
                self.final(self.fin_partie()[1])
            else:
                self.play(0 , niveau_game)
            
        else:
            # cas où c'est au joueur de jouer
            self.game.bind("<Button-1>", self.valid_pointeur)
            
        

    def jeu(self,*args):
        if self.LevelVar.get() != "?????" and self.FirstPlayerVar.get() != "?????":
            self.gameOn = True
            if self.rejouer['state'] == DISABLED:
                if self.FirstPlayerVar.get() == "Joueur":
                    self.text_gamer.set("C'est à vous de jouer: cliquez sur une case.")
                    self.play(0 , self.LevelVar.get())
                else:
                    self.play(1 , self.LevelVar.get())
        
    def valid_pointeur(self,event):
        if self.gameOn == True:
            x , y = event.x//135, event.y//135
            self.G[y][x] = 'O'
            self.nb_cercles += 1
            
            self.game.create_image(68 + x * 135 , 68 + y * 135, anchor = 'center', image = self.cercle)
            
            if self.fin_partie()[0] == True:       
                self.final(self.fin_partie()[1])
            else:
                self.play(1 , self.LevelVar.get() )

    def final(self , gagnant):
        if gagnant == None:
            txt = '{:^70}'.format('Match nul !')
        else:
            txttmp = gagnant + " gagne cette partie !"
            txt = '{:^70}'.format(txttmp)
            
        self.text_gamer.set(' '*100)
        self.text_gamer.set(txt)
        self.rejouer.config(bg = 'blue' , fg = 'white' , state = NORMAL)
        self.gameOn = False
        
        # on enregistre les stats
        
        L = [ self.FirstPlayerVar.get() , self.LevelVar.get() , gagnant ]
        
        config = [ [first,level,winner] for first in self.FirstPlayerList if first != '?????' \
                   for level in self.LevelList if level != '?????' for winner in ['Ordinateur','Joueur',None] ]
        
        try:
            with open('stats.txt' , 'rb') as f:
                LL = load(f) # liste des couples (combinaisons_liste , nb)
                new_LL = [ (i[0],i[1]+1) if i[0] == L else i  for i in LL]
            
            with open('stats.txt' , 'wb') as f:
                dump(new_LL,f)
                
        except IOError:
            with open('stats.txt', 'wb') as f:
                statList = [ ]
                for c in config:
                    if c == L:
                        statList.append( (c,1) )
                    else:
                        statList.append( (c,0) )
                
                dump(statList,f)
                
    def visu_stats(self):
        try:
            with open('stats.txt' , 'rb') as f:
                statsWindow = Tk()
                statsWindow.geometry("500x520")
                statsWindow.title("Statistiques")
                canvas = Canvas(statsWindow, width=480, height=510)
                canvas.place(x=10,y=10)
                Label(canvas, text="Nombres de parties gagnées", font=("Helvetica", 20)).place(x=70,y=1)
                canvas.create_rectangle(10,10,490,38,fill='black')
                canvas.create_rectangle(10,60,478,448)
                canvas.create_line(10,60+97,478,60+97)
                canvas.create_line(10,60+2*97,478,60+2*97)
                canvas.create_line(10,60+3*97,478,60+3*97)
                canvas.create_line(10+156,60,10+156,448)
                canvas.create_line(10+2*156,60,10+2*156,448)
                canvas.create_line(10,60,10+156,60+97)
                Label(canvas,text='1er joueur').place(x=90,y=80)
                Label(canvas,text='Gagnant').place(x=40,y=120)
                Label(canvas,text='{:^10}'.format('Joueur'),font=("Helvetica", 20)).place(x=25,y=190)
                Label(canvas,text='{:^10}'.format('Ordinateur'),font=("Helvetica", 20)).place(x=25,y=290)
                Label(canvas,text='{:^10}'.format('Match nul'),font=("Helvetica", 20)).place(x=25,y=390)
                Label(canvas,text='{:^10}'.format('Joueur'),font=("Helvetica", 20)).place(x=186,y=90)
                Label(canvas,text='{:^10}'.format('Ordinateur'),font=("Helvetica", 20)).place(x=336,y=90)
                LL = load(f) # liste des couples (combinaisons_liste , nb)
                for i in LL:
                    if i[0][0] == 'Joueur':
                        abscisse = 170
                    else:
                        abscisse = 325
                        
                    if i[0][2] == 'Joueur':
                        ordonnee = 170
                    elif i[0][2] == 'Ordinateur':
                        ordonnee = 270
                    else:
                        ordonnee = 370
                        
                    if i[0][1] == 'Difficile':
                        ordonnee += 20
                        
                    Label(canvas,text='Niveau {} : {}'.format(i[0][1] , i[1])).place(x=abscisse,y=ordonnee)
        except IOError:
            print('Pas de stats.')

""" ----------------------------------->
Fin de la classe
<------------------------------------"""

if __name__ == '__main__':    
    root = Tk()
    Morpion(root)
    root.mainloop()
    