#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 25 22:33:33 2023

@author: vincentleprince

Implémentation de l'exemple vu en cours.
Remarque : les scores des positions du jeu sont vus du point de vue du joueur 1.
Donc la fonction (min-max) donnant le meilleur score et le coup optimal
du point de vue de cette stratégie donne ce score pour le joueur 1.
"""
import numpy as np


# on représente le graphe par un dictionnaire
dicoGraph = {}
for i in [1,2]: # numéro du joueur 
    for j in range(1,6):
        dicoGraph[(i,j)] = [ (3-i , j), (3-i , j+1)]
        
# un dictionnaire contient également les valeurs de l'heuristique pour chaque position       
dicoH  = { (1,1) : 5 , (2,1) : -3 ,
                    (1,2) : 12 , (2,2) : 1 ,
                    (1,3) : -4 , (2,3) : -2 ,
                    (1,4) : -5 , (2,4) : 3 ,
                    (1,5) : 7  , (2,5) : 8
                   }

def minMax( pos , nbCoups  ):
    """pos :  position  dans l'arène
    nbCoups : nombre de coups restants à jouer
              pour atteindre l'horizon de la stratégie
    La fonction renvoie le    score attribué à chaque position
    en appliquent la stratégie min/max à horizon nbCoups 
    à partir de la position pos.
    """
    if nbCoups == 0: # jeu à 0 coup
        return dicoH[pos]
    elif dicoGraph[pos] == []: # cas où la position n'a pas de successeurs
        return dicoH[pos]
    else : 
        if pos[0] == 1 : # position où le premier joueur doit jouer
                        # on maximise le score parmi ceux des positions atteignables 
            maxi = -np.inf
            for pos_suivante in dicoGraph[pos]:
                score  = minMax( pos_suivante , nbCoups -1 )
                if score > maxi:
                    maxi = score
            return maxi
        if pos[0] == 2 : # position où le deuxième joueur doit jouer
                        # on minimise le score parmi ceux des positions atteignables 
            mini = np.inf
            for pos_suivante in dicoGraph[pos]:
                score  = minMax( pos_suivante , nbCoups -1 )
                if score < mini:
                    mini = score
            return mini

#print(minMax((1,1) , 4) )  

def minMax2( pos , nbCoups  ):
    """évolution de la fonction précédente renvoyant également le coup à jouer
    pour obtenir le score optimal"""
    if nbCoups == 0: # jeu à 0 coup
        return dicoH[pos] , None
    elif dicoGraph[pos] == []: # cas où la position n'a pas de successeurs
        return dicoH[pos] , None
    else : 
        if pos[0] == 1 : # position où le premier joueur doit jouer
                        # on maximise le score parmi ceux des positions atteignables 
            maxi = -np.inf 
            for pos_suivante in dicoGraph[pos]:
                score , coupOptimal = minMax2( pos_suivante , nbCoups -1 )
                if score > maxi:
                    maxi = score
                    pos_maxi = pos_suivante
            return maxi , pos_maxi
        if pos[0] == 2 : # position où le deuxième joueur doit jouer
                        # on minimise le score parmi ceux des positions atteignables 
            mini = np.inf
            for pos_suivante in dicoGraph[pos]:
                score  , coupOptimal = minMax2( pos_suivante , nbCoups -1 )
                if score < mini:
                    mini = score
                    pos_mini = pos_suivante
            return mini , pos_mini


print(minMax2((1,1) , 4) ) 

"""Avec Mémoïzation"""

dicoScore = {} #les clef seront des couples (pos,nbCoups),
               #nbCoups : nombre de coups restant à jouer
               #pour atteindre l'horizon de la stratégie
def minMaxMemo( pos , nbCoups  ):
    """pos :  position  dans l'arène
    nbCoups : nombre de coups restant à jouer
         pour atteindre l'horizon de la stratégie
    La fonction remplit dicoScore 
    en appliquent la stratégie min/max à horizon nbCoups 
    à partir de la position pos.
    """
    if not ((pos , nbCoups) in dicoScore):
        if nbCoups == 0: 
            dicoScore[(pos , nbCoups)] = dicoH[pos]
        elif dicoGraph[pos] == []: # cas où la position n'a pas de successeurs
            dicoScore[(pos , nbCoups)] = dicoH[pos]
        else : 
            if pos[0] == 1 : # position où le premier joueur doit jouer
                            # on maximise le score parmi ceux des positions atteignables 
                maxi = -np.inf
                for pos_suivante in dicoGraph[pos]:
                    minMaxMemo( pos_suivante , nbCoups -1  )
                    score  = dicoScore[(pos_suivante, nbCoups -1  )]
                    if score > maxi:
                        maxi = score
                dicoScore[(pos,nbCoups)] = maxi
            if pos[0] == 2 : # position où le deuxième joueur doit jouer
                            # on minimise le score parmi ceux des positions atteignables 
                mini = np.inf
                for pos_suivante in dicoGraph[pos]:
                    minMaxMemo( pos_suivante , nbCoups -1  )
                    score  = dicoScore[(pos_suivante, nbCoups -1  )]
                    if score < mini:
                        mini = score
                dicoScore[(pos,nbCoups)] = mini

minMaxMemo((1,1) , 4)   
print(dicoScore[(1,1) , 4])   
                