################################################################################
################################################################################
#   OTHELLO
################################################################################
################################################################################

import  numpy.random as rd
from copy import deepcopy

global pinf,minf
pinf=float('inf')
minf=-float('inf')


################################################################################
#   FONCTIONS DU JEU
################################################################################

def valide(pos):
    """valide(pos:list)->bool
    pos : position [i,j]
    Renvoie True si pos est une position du plateau, False sinon"""
    i,j=pos
    return i>=0 and i<8 and j>=0 and j<8


def adversaire(x):
    """adversaire(x:int)->int
    Renvoie l'adversaire du joueur x"""
    return 1-x


def etat(tab,pos):
    """etat(tab:list,pos)->int or None
    tab : liste décrivant un plateau de jeu
    pos : une position[i,j] du plateau
    Renvoie l'état de la case en position pos"""
    i,j=pos
    return tab[i][j]


def place(tab,pos,J_i):
    """place(tab:list,pos:list,J_i:int)->None
    tab : liste décrivant un plateau de jeu
    pos : une position [i,j] du plateau
    J_i : numéro d'un joueur
    Place le pion de J_i en position pos"""
    i,j=pos
    tab[i][j]=J_i

    
def bascule(tab,pos):
    """"bascule(tab:list,pos:list)->None
    tab : liste décrivant un plateau de jeu
    pos : une position [i,j] du plateau
    Bascule la case en position pos dans le camp adverse"""
    J_i=etat(tab,pos)
    place(tab,pos,adversaire(J_i))


def suivant(pos,direction):
    """suivant(pos:list,direction:list)->list
    pos : une position [i,j] du plateau
    direction : une direction [di,dj]
    Renvoie la position [i+di,j+dj]"""
    i,j=pos
    di,dj=direction
    return [i+di,j+dj]


def retourne_dir(tab,pos,direction,J_i):
    """"retourne_dir(tab:list,pos:list,direction:list,J_i:int)->list
    tab : liste décrivant un plateau de jeu
    pos : position [i,j]
    direction : une direction [di,dj]
    J_i : numéro d'un joueur
    Renvoie la liste des cases à retourner selon direction
    si le joueur J_i joue en pos"""
    res=[]
    pos_cur=pos[:]
    fini=False
    while not fini:
        pos_cur=suivant(pos_cur,direction)
        fini=not valide(pos_cur) or etat(tab,pos_cur)!=adversaire(J_i)
        if not fini:
            res.append(pos_cur)
    if valide(pos_cur) and etat(tab,pos_cur)==J_i:
        return res
    return []


def init_tab():
    """init_tab()->list
    Creation du plateau de jeu initial"""
    tab=[[None]*8 for k in range(8)]
    tab[3][3]=0
    tab[3][4]=1
    tab[4][3]=1
    tab[4][4]=0
    return tab


def aff(tab):
    """aff(tab:list)->None
    Affichage du plateau de jeu décrit par tab"""
    n=len(tab)
    dico={None:'   ', 0:' O ', 1:' X '}
    ligne="   "
    for i in range(n):
        ligne+="| "+str(i)+" "
    print(ligne)
    print("----"*(n+1))
    for i in range(n):
        ligne=" "+ str(i)+" |"
        for j in range(n):
            ligne+=dico[tab[i][j]]
            if j<n-1:
                ligne+="|"
        print(ligne)
        if i<n-1:
            print("----"*(n+1))


def retourne(tab,pos,J_i):
    """retourne(tab:list,pos:list,J_i:int)->list
    tab : liste décrivant un plateau de jeu
    pos: une position [i,j] du plateau
    J_i : numéro du joueur
    Renvoie la liste des cases à retourner si le joueur J_i joue en pos"""
    res=[]
    L_direction=[[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]]
    for direction in L_direction:
        res+=retourne_dir(tab,pos,direction,J_i)
    return res


def coup_valide(tab,pos,J_i):
    """coup_valide(tab:list,pos:list,J_i:int)->bool
    tab : liste décrivant un plateau de jeu
    pos : une position [i,j] du plateau
    J_i : numéro de Joueur
    Renvoie True si J_i peut jouer en pos, False sinon"""
    return len(retourne(tab,pos,J_i))>0


def joue(tab,pos,J_i):
    """"joue pour J_i en pos, position valide pour J_i"""
    #tab_libre.pop(pos)
    place(tab,pos,J_i)
    L_pos=retourne(tab,pos,J_i)
    for pos_retourne in L_pos:
        bascule(tab,pos_retourne)


def cases_libres(tab):
    """Renvoie les cases inoccupées de tab"""
    res=[]
    for i in range(8):
        for j in range(8):
            if tab[i][j]==None:
                res.append((i,j))
    return res


def cases_jouables(tab,J_i):
    """cases_jouables(tab:list,J_i:int)->list
    tab : liste décrivant un plateau de jeu
    J_i : numéro du joueur
    Renvoie la liste des cases jouables par le joueur J_i"""
    res=[]
    for pos in cases_libres(tab):
        if coup_valide(tab,pos,J_i):
            res.append(pos)
    return res


def score(tab):
    """score(tab:list)->float
    tab : liste décrivant un plateau de jeu
    Renvoie +inf si J_0 gagne, -inf si J_1 gagne et 0 sinon"""
    n0,n1=comptage(tab)
    if n0>n1:
        return pinf
    elif n0<n1:
        return minf
    else:
        return 0
    
def comptage(tab):
    """comptage(tab:list)->tuple
    tab : liste décrivant une plateau de jeu
    Renvoie le tuple (n0,n1) où
    n0 : nombre de pions de J_0
    n1 : nombre de pions de J_1"""
    n0,n1=0,0
    for i in range(8):
        for j in range(8):
            val=etat(tab,[i,j])
            if val==0:
                n0+=1
            elif val==1:
                n1+=1
    return n0,n1


################################################################################
#   OTHELLO ALEA
################################################################################


def othello_alea():
    """othello_alea()->None
    Jeu d'Othello contre la machine
    J_0 : joueur 
    J_1 : machine"""
    tab=init_tab()
    J_i=1
    fini=False
    print("Partie d'Othello")
    print("J_0 -> O")
    print("J_1 -> X")
    print()
    while not fini:
        J_i=adversaire(J_i)
        aff(tab)
        if len(cases_jouables(tab,J_i))>0:
            if J_i==0:
                valide=False
                while not valide:
                    print()
                    saisie=input("J_"+str(J_i)+" : x,y =")
                    coords=saisie.split(",")
                    pos=[int(coords[0]),int(coords[1])]
                    valide=coup_valide(tab,pos,J_i)
                    if not valide:
                        print("Coup invalide")
            else:
                # construction liste cases jouables  pour J_i
                jouables=cases_jouables(tab,J_i)
                ind=rd.randint(len(jouables))
                pos=jouables[ind]
                print("\nLa machine joue ",pos)
            joue(tab,pos,J_i)
        elif len(cases_libres(tab))>0:
            print("\nJ_"+str(J_i)+" passe")
        J0_bloque=len(cases_jouables(tab,0))==0
        J1_bloque=len(cases_jouables(tab,1))==0
        fini=J0_bloque and J1_bloque
        print()    
    n0,n1=comptage(tab)
    if n0>n1:
        print("J_0 gagne")
        print("O = ",n0, " X =",n1)
    elif n0<n1:
        print("J_1 gagne")
        print("O = ",n0, " X =",n1)
    else:
        print("Match nul")
    print()
    aff(tab)



################################################################################
#   HEURISTIQUE
################################################################################


c,b=10,4
poids=[[c]+[b]*6+[c]]+[[b]+[1]*6+[b] for k in range(6)] +[[c]+[b]*6+[c]]

def H(tab,poids):
    """H(tab:list,poids:list)->int
    tab : liste décrivant un plateau de jeu
    poids : liste de poids des cases pour une heuristique
    Renvoie une heuristique du plateau"""
    res=0
    for i in range(8):
        for j in range(8):
            val=etat(tab,[i,j])
            if val!=None:
                res+=poids[i][j]*(1-2*val)
    return res


################################################################################
#   MINIMAX DEPTH
################################################################################


def minimax_depth(tab,J_i,p):
    """minimax_depth(tab:list,J_i:int,p:list)->float
    tab : liste décrivant un plateau de jeu
    J_i : numéro d'un joueur
    p : niveau de profondeur
    Renvoie une estimation de l'évaluation de la configuration du jeu
    selon l'algorithme du Minimax avec un niveau de profondeur p"""
    pass
        

def othello_minimax_depth(p):
    """othello_minimax_depth(p:int)->None
    Partie d'othello contre la machine qui suit l'algorithme Minimax
    avec un niveau de profondeur p
    J_0 : joueur
    J_1 : machine"""
    pass


