################################################################################
################################################################################
#   PUISSANCE 4
################################################################################
################################################################################

import  numpy.random as rd
from copy import deepcopy
from functools import *
import time

global pinf,minf
pinf=float('inf')
minf=-float('inf')


################################################################################
#   FONCTIONS DU JEU
################################################################################

def init_tab():
    """init_tab()->list
    Creation de la grille de jeu initiale"""
    tab=[[0]*7]+[[None]*7 for k in range(6)]
    return tab


def pos_valide(pos):
    """valide(pos:list)->bool
    pos : position [i,j]
    Renvoie True si pos est une position de la grille, False sinon"""
    i,j=pos
    return i>0 and i<7 and j>=0 and j<7  

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 aff(tab):
    """aff(tab:list)->None
    Affichage de la grille de jeu décrite par tab"""
    n=7
    dico={None:'   ', 0:' O ', 1:' X '}
    ligne="   "
    for i in range(n):
        ligne+="| "+str(i)+" "
    ligne+="|"
    print(ligne)
    print("   "+"----"*n)
    for i in range(n-1,0,-1):
        #ligne=" "+ str(i)+" |"
        ligne="   |"
        for j in range(n):
            ligne+=dico[tab[i][j]]
            if j<n:
                ligne+="|"
        print(ligne)
        if i>1:
            print("   "+"----"*n+"-")


def coup_valide(tab,col):
    """coup_valide(tab:list,col:int)->bool
    tab : liste décrivant une grille de jeu
    col : une colonne de la grille
    Renvoie True si le coup est valide, False sinon"""
    return col>=0 and col<7 and tab[0][col]<6

def place(tab,col,J_i):
    """place(tab:list,pos:list,J_i:int)->None
    tab : liste décrivant une grille de jeu
    col : une colonne de la grille
    J_i : numéro d'un joueur
    Place le jeton de J_i en colonne col"""
    tab[0][col]+=1
    tab[tab[0][col]][col]=J_i


def suivant(pos,direction):
    """suivant(pos:list,direction:list)->list
    pos : une position [i,j] de la grille
    direction : une direction [di,dj]
    Renvoie la position [i+di,j+dj]"""
    i,j=pos
    di,dj=direction
    return [i+di,j+dj]

def score(x):
    """score(x:int)->float
    Si J_0 gagne -> score=+inf
    Si J_1 gagne -> score=-inf
    x : joueur"""
    return (1-2*x)*float('inf')

def adversaire(x):
    """adversaire(x:int)->int
    Renvoie l'adversaire du joueur x"""
    return 1-x


def compte(tab,pos,direction,J_i):
    """compte(tab:list,pos:list,direction:list,J_i:int)->int
    Renvoie le nombre de cases de la couleur de J_i
    depuis la position pos selon la direction voulue
    tab : grille de jeu
    pos : une position [i,j] de la grille
    dir : une direction [di,dj]
    J_i : joueur dont c'est le tour"""
    res=0
    fini=False
    pos_cur=pos
    while not fini:
        pos_cur=suivant(pos_cur,direction)
        fini=not pos_valide(pos_cur) or etat(tab,pos_cur)!=J_i
        if not fini:
            res+=1
    return res

def oppose(d):
    """oppose(d:list)->list
    Renvoie direction opposée à direction d"""
    return [-d[0],-d[1]]

def gagne(tab,col,J_i):
    """gagne(tab:list,col:int,J_i:int)->bool
    Renvoie True si J_i gagne en jouant en colonne col et False sinon
    tab : grille de jeu
    col : une colonne de la grille
    J_i : joueur dont c'est le tour"""
    pos=tab[0][col],col
    L_direction=[[1,0],[0,1],[1,1],[-1,1]]
    for direction in L_direction:
        res=0
        res+=compte(tab,pos,direction,J_i)
        res+=compte(tab,pos,oppose(direction),J_i)
        if res>=3:
            return True
    return False
    
def grille_pleine(tab):
    """grille_pleine(tab:list)->bool
    Renvoie True si la grille de jeu est pleine, False sinon
    tab : grille de jeu"""
    return tab[0]==[6]*7


################################################################################
#   PUISS4 ALEA
################################################################################

def puiss4_alea():
    """puiss4_alea()->None
    Jeu de puissance 4 contre la machine
    J_0 : joueur 
    J_1 : machine"""
    tab=init_tab()
    J_i=1
    fini=False
    print("Partie de puissance 4")
    print("J_0 -> O")
    print("J_1 -> X")
    print()
    while not fini:
        J_i=adversaire(J_i)
        aff(tab)
        print()
        valide=False
        while not valide:
            if J_i==0:
                saisie=input("J_"+str(J_i)+" : col = ")
                col=int(saisie)
            else:
                col=rd.randint(0,7)
            valide=coup_valide(tab,col)
            if not valide:
                print("Coup invalide")
        if J_i==1:
            print("La machine joue : col =",col)
        place(tab,col,J_i)
        fini=grille_pleine(tab) or gagne(tab,col,J_i)
        print()
    if gagne(tab,col,J_i):
        print("J_"+str(J_i)+" gagne")
    else:
        print("Match nul")
    aff(tab)


################################################################################
#   HEURISTIQUE
################################################################################


c,b=10,4
poids=[[3,4,5,7,5,4,3],
       [4,6,8,10,8,6,4],
       [5,8,11,13,11,8,5],
       [5,8,11,13,11,8,5],
       [4,6,8,10,8,6,4],
       [3,4,5,7,5,4,3]]


def H(tab,poids):
    """H(tab:list,poids:list)->int
    tab : liste décrivant une grille de jeu
    poids : liste de poids des cases pour une heuristique
    Renvoie une heuristique de la grille"""
    res=0
    for i in range(6):
        for j in range(7):
            val=etat(tab,[i+1,j])
            if val!=None:
                res+=poids[i][j]*(1-2*val)
    return res


################################################################################
#   PUISS4 - MINIMAX
################################################################################


def minimax_depth(tab,col,J_i,p):
    """minimax_depth(tab:list,J_i:int,p:list)->float
    tab : liste décrivant une grille 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 puiss4_minimax_depth(p):
    """puiss4_minimax_depth(p:int)->None
    Partie de puissance 4 contre la machine qui suit l'algorithme Minimax
    avec un niveau de profondeur p
    J_0 : joueur
    J_1 : machine"""
    pass


