#!/usr/bin/python
# -*- coding: iso-8859-15 -*-

from time    import sleep
from random  import random
from Tkinter import *


###----- Variables globales -----###

NOMJINT = 'Piotellino'

MAXLIG = 8
MAXCOL = 8

VIDE = '.'

DIRECTIONS = [(-1,-1),(-1,0),(-1,+1),(0,-1),(0,+1),(+1,-1),(+1,0),(+1,+1)]

PIONS_INITIAUX = [(MAXLIG/2-1,MAXCOL/2-1,'B'),(MAXLIG/2,MAXCOL/2,'B'),(MAXLIG/2-1,MAXCOL/2,'N'),(MAXLIG/2,MAXCOL/2-1,'N')]

COULEUR_FOND = "Medium Sea Green"
COULEUR_BORD   ="gray"

COULEUR_PION = {}
COULEUR_PION['B'] = 'white'
COULEUR_PION['N'] = 'black'
                
LARGEUR = 400
COTE    = LARGEUR/MAXCOL

X1 = [0]*MAXCOL
Y1 = [0]*MAXLIG
X2 = [0]*MAXCOL
Y2 = [0]*MAXLIG

for col in range(MAXCOL):
    X1[col] =     col*COTE + 3
    X2[col] = (col+1)*COTE - 3
    
for lig in range(MAXLIG):
    Y1[lig] =     lig*COTE + 3
    Y2[lig] = (lig+1)*COTE - 3

ATTENTE = 1

MSG_ACCUEIL       = "Bonjour ! Je m'appelle "+NOMJINT+". On joue ?"
MSG_INT_COMMENCE  = NOMJINT+" commence, vous avez les blancs."
MSG_EXT_COMMENCE  = "A vous de commencer, vous avez les noirs."
MSG_INT_JOUE      = "C'est à "+NOMJINT+" de jouer ! "+NOMJINT+" réfléchit..."
MSG_EXT_JOUE      = "C'est à vous de jouer !"
MSG_INT_PASSE     = "Vous passez... à "+NOMJINT+" ! "+NOMJINT+" réfléchit..."
MSG_EXT_PASSE     = NOMJINT+" passe... c'est à vous de jouer!"

MSG_FIN    = {}
MSG_FIN[0] = "Match nul"
MSG_FIN[1] = NOMJINT+" gagne la partie"
MSG_FIN[2] = "Vous gagnez la partie"

ERR_COUP_INTERDIT = "Vous ne pouvez pas jouer dans cette case."
ERR_PAS_DE_PARTIE = "Cliquer sur le bouton 'Nouvelle Partie' pour commencer."


###----- Définition du jeu d'Othello -----###

# Définit le plateau de début de partie
def PlateauInitial():
    p = ['0']*MAXLIG
    for lig in range(MAXLIG):
        p[lig] = [VIDE]*MAXCOL
    for (lig,col,j) in PIONS_INITIAUX:
        p[lig][col] = j
    return p

# Teste si les coordonnées d'une case sont bien sur le plateau de jeu
def DansLeJeu(y,x):
    return (y>=0) and (y<MAXLIG) and (x>=0) and (x<MAXCOL)

# Affiche un plateau dans la console
def AffichePlateauTexte(p):
    for ligne in p:
        for case in ligne:
            print case,
        print

# Donne le symbole du joueur adverse
def Adversaire(pion):
    if pion=='B':
        return 'N'
    else:
        return 'B'

# Compte de le nombre de cases vides sur un plateau
def NbCasesVides(p):
    cpt = 0
    for lig in range(MAXLIG):
        for col in range(MAXCOL):
            if p[lig][col] == VIDE:
                cpt += 1
    return cpt

# Compte de le nombre de pions d'un joueur sur un plateau
def NbPions(j,p):
    cpt = 0
    for lig in range(MAXLIG):
        for col in range(MAXCOL):
            if p[lig][col] == j:
                cpt += 1
    return cpt

# Compte de le nombre de pions adverses connexes dans une direction donnée
def NbPionsAdvConnexes(j,lig,col,Dy,Dx,p):
    i,adv = 1,Adversaire(j)
    while (DansLeJeu(lig+i*Dy,col+i*Dx) and (p[lig+i*Dy][col+i*Dx]==adv)):
        i += 1

    return i-1

# Détermine si un joueur peut jouer à une position sur un plateau
def PeutJouerEn(j,lig,col,p):
    modifs = []
    if p[lig][col] != VIDE:
        return False
    else:
        for (Dy,Dx) in DIRECTIONS:
            prise = NbPionsAdvConnexes(j,lig,col,Dy,Dx,p)
            if prise > 0:
                bouty,boutx = lig+Dy*(prise+1),col+Dx*(prise+1)
                if (DansLeJeu(bouty,boutx)) and (p[bouty][boutx] == j):
                    for i in range(1,prise+1):
                        modifs.append((lig+Dy*i,col+Dx*i))
        if modifs==[]:
            return False
        else:
            return modifs

# Détermine si un joueur a un coup à jouer sur un plateau
def ExisteUnCoup(j,p):
    for lig in range(MAXLIG):
        for col in range(MAXCOL):
            modifs = PeutJouerEn(j,lig,col,p)
            if (modifs):
                return True
    return False

# Détermine la liste des coups possibles pour un joueur et un plateau
def CoupsPossibles(j,p):
    coups = []
    for lig in range(MAXLIG):
        for col in range(MAXCOL):
            modifs = PeutJouerEn(j,lig,col,p)
            if (modifs):
                coups.append((lig,col,modifs))

    if coups==[]:
        return False
    else:
        return coups

# Détermine à partir d'un plateau si la partie est terminée
def FinPartie(p):
    return (not ExisteUnCoup('N',p)) and (not ExisteUnCoup('B',p))

# Détermine le joueur gagnant à partir d'un plateau
def JoueurGagnant(p):
    nbn,nbb = NbPions('N',p),NbPions('B',p)
    if nbn>nbb:
        return ('N',nbn,nbb)
    else:
        if nbn<nbb:
            return ('B',nbb,nbn)
        else:
            return ('-',nbb,nbn)

# Modifie le plateau après un coup
def AppliqueCoup(j,lig,col,modifs,p) :
    p[lig][col] = j
    for (y,x) in modifs:
        p[y][x] = j

# Modifie le plateau pour annuler un coup
def AnnuleCoup(j,lig,col,modifs,p) :
    adv = Adversaire(j)
    p[lig][col] = VIDE
    for (y,x) in modifs:
        p[y][x] = adv


###----- Gestion de l'I.A. -----###

# Evaluation heuristique d'une situation pour un joueur
MAX_H = 10000
def fonction_h(j,p) :
    h = NbPions(j,p) - NbPions(Adversaire(j),p)
    return h

# Evalue une situation de jeu pour le joueur j suivant l'algorithme MinMax
def ValeurMinMax(j,p,prof,EstMax) :
    adv = Adversaire(j)
    if FinPartie(p):
        (g,nbg,nbp) = JoueurGagnant(p)
        if g==j:
            return MAX_H-nbp
        elif g==adv:
            return -MAX_H+nbp
        else:
            return 0
    elif prof==0:
        return fonction_h(j,p)
    elif EstMax:
        coups = CoupsPossibles(j,p)
        if not(coups):
            return ValeurMinMax(j,p,prof-1,False)
        else:
            bscore = -(MAX_H+1)
            for (lig,col,modifs) in coups:
                AppliqueCoup(j,lig,col,modifs,p)
                score = ValeurMinMax(j,p,prof-1,False);
                if score > bscore:
                    bscore = score
                AnnuleCoup(j,lig,col,modifs,p)
            return bscore
    else:
        coups = CoupsPossibles(adv,p)
        if not(coups):
            return ValeurMinMax(j,p,prof-1,True)
        else:
            bscore = +(MAX_H+1)
            for (lig,col,modifs) in coups:
                AppliqueCoup(adv,lig,col,modifs,p)
                score = ValeurMinMax(j,p,prof-1,True);
                if score < bscore:
                    bscore = score
                AnnuleCoup(adv,lig,col,modifs,p)
            return bscore

# Décider du coup à jouer en appliquant l'algorithme MinMax
def DecisionMinMax(j,coups,p) :
    profmax = 2
    S = -(MAX_H+1)
    for (lig,col,modifs) in coups:
        AppliqueCoup(j,lig,col,modifs,p)
        score = ValeurMinMax(j,p,profmax-1,False);
        if (score > S) or ((score == S) and (random()<0.5)):
            S,L,C,M = score,lig,col,modifs
        AnnuleCoup(j,lig,col,modifs,p)

    return (L,C,M)


###----- Gestion de l'Interface Graphique -----###

# Envoi un message d'information
def EnvoiMessageInfo(msg):
    MessageTk.config(text=msg)
    MessageTk.update()

# Affiche un message d'erreur
def EnvoiMessageErreur(msg):
    old = MessageTk.cget('text')
    EnvoiMessageInfo(msg)
    sleep(ATTENTE)
    EnvoiMessageInfo(old)

# Dessine un pion sur l'othellier
def PosePionTk(j,lig,col):
    PlateauTk.create_oval(X1[col],Y1[lig],X2[col],Y2[lig],outline=COULEUR_PION[j],width=2,fill=COULEUR_PION[j])     

# Met en valeur un pion sur l'othellier
def DesignePionTk(j,lig,col):
    PlateauTk.create_oval(X1[col],Y1[lig],X2[col],Y2[lig],outline=COULEUR_PION[j],width=2)

# Dessine le quadrille et les premiers pions
def PlateauInitialTk():
    PlateauTk.delete(ALL)
    s = COTE;        
    for l in range(MAXLIG-1):
        PlateauTk.create_line(0,s,LARGEUR,s,fill=COULEUR_BORD)
        s += COTE
    s = COTE
    for c in range(MAXCOL-1):
        PlateauTk.create_line(s,0,s,LARGEUR,fill=COULEUR_BORD)
        s += COTE
    for (lig,col,j) in PIONS_INITIAUX:
        PosePionTk(j,lig,col)

# Animation simulant la pose d'un pion
def AppliqueCoupTk(j,lig,col,modifs):
    EnvoiMessageInfo("")
    DesignePionTk(j,lig,col)
    for (l,c) in modifs:
        DesignePionTk(j,l,c)
    PlateauTk.update()
    sleep(ATTENTE)
    PosePionTk(j,lig,col)
    for (l,c) in modifs:
        PosePionTk(j,l,c)
    PlateauTk.update()

# Applique le coup de l'adversaire
def CoupExterneAccepte(lig,col):
    global PlateauCourant
    modifs = PeutJouerEn(JEXT,lig,col,PlateauCourant)
    if modifs:
        AppliqueCoup(JEXT,lig,col,modifs,PlateauCourant)
        AppliqueCoupTk(JEXT,lig,col,modifs)
        return True
    else:
        return False

# Le joueur interne joue un coup
def CoupInterne(coups,msg=MSG_INT_JOUE):
    global PlateauCourant
    EnvoiMessageInfo(MSG_INT_JOUE)
    (lig,col,modifs) = DecisionMinMax(JINT,coups,PlateauCourant)
    sleep(ATTENTE)
    AppliqueCoup(JINT,lig,col,modifs,PlateauCourant)
    AppliqueCoupTk(JINT,lig,col,modifs)

# Le joueur interne joue tant que l'adversaire passe
def FaireJouerJoueurInterne():
    coups,alautre,ajoue,msg = CoupsPossibles(JINT,PlateauCourant),False,False,MSG_INT_JOUE
    while not(alautre) and (coups):
        CoupInterne(coups,msg)
        ajoue,msg,alautre = True,MSG_INT_PASSE,ExisteUnCoup(JEXT,PlateauCourant)
        if not(alautre):
            coups = CoupsPossibles(JINT,PlateauCourant)
    return (ajoue,alautre)

# Débute la partie
def CommencerLaPartie():
    global PlateauCourant,PartieEnCours,JINT,JEXT
    PartieEnCours,PlateauCourant = True,PlateauInitial()
    PlateauInitialTk()
    if jintcommence.get() == 0:
        JINT,JEXT = 'B','N'
        EnvoiMessageInfo(MSG_EXT_COMMENCE)
    else:
        DesactiverInterface()
        JINT,JEXT = 'N','B'
        EnvoiMessageInfo(MSG_INT_COMMENCE)
        sleep(ATTENTE)
        CoupInterne(CoupsPossibles(JINT,PlateauCourant))
        EnvoiMessageInfo("C'est à vous de jouer !")
        ActiverInterface()

# Met fin à la partie et affiche le score
def TerminerLaPartie():
    global PartieEnCours
    PartieEnCours,(g,nbg,nbp) = False,JoueurGagnant(PlateauCourant)
    EnvoiMessageInfo(MSG_FIN[(1+abs(cmp(g,JINT)))*cmp(nbg,nbp)]+' : '+str(nbg)+' - '+str(nbp)+'.')

# Le joueur externe clique sur une case
def ClicSurCase(evenement):
    DesactiverInterface()
    if not(PartieEnCours):
        EnvoiMessageErreur(ERR_PAS_DE_PARTIE)
    else:
        lig, col = evenement.y/COTE, evenement.x/COTE
        if CoupExterneAccepte(lig,col):
            (ajoue,alautre) = FaireJouerJoueurInterne()
            if ajoue:
                if alautre:
                    EnvoiMessageInfo(MSG_EXT_JOUE)
                else:
                    TerminerLaPartie()
            else:
                if ExisteUnCoup(JEXT,PlateauCourant):
                    EnvoiMessageInfo(MSG_EXT_PASSE)
                else:
                    TerminerLaPartie()
        else:
            EnvoiMessageErreur(ERR_COUP_INTERDIT)
    ActiverInterface()

# Desactive les boutons et le plateau
def DesactiverInterface():
    PlateauTk.unbind("<Button-1>")
    BtQuitter.configure(state=DISABLED)
    BtCommencer.configure(state=DISABLED)
    BtPremier.configure(state=DISABLED)
    
# Active les boutons et le plateau
def ActiverInterface():
    PlateauTk.bind("<Button-1>",ClicSurCase)
    BtQuitter.configure(state=NORMAL)
    BtCommencer.configure(state=NORMAL)
    BtPremier.configure(state=NORMAL)


###----- Programme Principal -----###

InterfaceTk = Tk()
InterfaceTk.geometry("600x500")
InterfaceTk.title(NOMJINT)

PlateauCourant = 0
PartieEnCours  = False
jintcommence   = IntVar()

PlateauTk = Canvas(InterfaceTk,bg=COULEUR_FOND,borderwidth=0,highlightbackground=COULEUR_BORD,highlightthickness=1)
PlateauTk.configure(width=LARGEUR,height=LARGEUR)
PlateauTk.grid(row=1,column=1,rowspan=3,padx=10,pady=10)

BtPremier = Checkbutton(InterfaceTk,text=NOMJINT+" commence",variable=jintcommence)
BtPremier.grid(row=1,column=2)

BtCommencer = Button(InterfaceTk,text="Nouvelle partie",command=CommencerLaPartie)
BtCommencer.grid(row=2,column=2,sticky=N)

BtQuitter = Button(InterfaceTk,text="Quitter",command=InterfaceTk.quit)
BtQuitter.grid(row=3,column=2,sticky=S)

MessageTk = Label(InterfaceTk,text=MSG_ACCUEIL)
MessageTk.grid(row=4,column=1)

ActiverInterface()

InterfaceTk.mainloop()

