

class Orpailleur:
    
    def actions(self):
        return(['gauche','droite'])

    def etats(self):
        return(['MG','C','MD'])
        
    def transition(self,s,a):
        if (a=='gauche'):
            if (s=='MG'):
                return ('MG')
            if (s=='C'):
                return ('MG')
            if (s=='MD'):
                return ('C')
              
        if (a=='droite'):
            if (s=='MG'):
                return ('C')
            if (s=='C'):
                return ('MD')
            if (s=='MD'):
                return ('MD')
        return('erreur')
            

    def recompense(self,s,a,sarr):
        if (a=='gauche'):
            if (s=='MG'):
                return(+3)

        if (a=='droite'):
            if (s=='C'):
                return(+8)
            if (s=='MD'):
                return(+1)
        
        return(0)

      
class SystemeExecute:

    def __init__(self,pb):
        self.pb=pb

    def executerPi(self,pi,depart,nb):
        s=depart
        for i in range(nb):
            action=pi[s]
            sFin=pb.transition(s,action)
            print(s," -> ",action," : ",sFin)
            s=sFin

    def executerPiRec(self,pi,depart,nb):
        s=depart
        somme=0
        for i in range(nb):
            action=pi[s]
            sFin=pb.transition(s,action)
            r=pb.recompense(s,action,sFin)
            somme=somme+r
            print(s," -> ",action," : ",sFin,"<",r,">")
            s=sFin
        return somme

            



#************************************************************

import random

class Systeme:
    """permet d'executer un probleme
    - pb : attribut du probleme"""

    def __init__(self,pb):
        """construit un systeme a partir d'un probleme"""
        self.pb=pb

    def execute(self,s,a):
        ''' permet d'executer une actions dans un etat - retourne etat d arrivee'''
        #print ("etat depart: ",s)
        #print ("action: ",a)
        sArriv = self.pb.transition(s,a)
        #print ("etat arrivee: ",sArriv)
        #print ("recompense: ",self.pb.recompense(s,a,sArriv))
        #print("********")
        return(sArriv)

    def intialiseQ(self):
        """construit des Qvaleurs vides"""
        Q = {}
    

    def valueIteration(self,nb):
        '''effectue l'algorithme value iteration a partir du probleme donne'''
        ''' retourne la Qvaleur calculee'''
        gamma=0.99
        #Q=self.intialiseQ()
        Q={}
       
        #une iteration
        for i in range(0,nb):
            Q2={}
            #pour chaque etat,action
            for etat in self.pb.etats():            
                for action in self.pb.actions():
                    
                    #calculer arrivee
                    sArriv=self.pb.transition(etat,action)
                    r=self.pb.recompense(etat,action,sArriv)
                    # cherche max arrivee
                    max=-100000
                    for actionMax in self.pb.actions():
                        #si la clef n'existe pas, on cree
                        if (not((sArriv,actionMax) in Q)):
                            Q[(sArriv,actionMax)]=0
                        if (Q[(sArriv,actionMax)]>max):
                            max=Q[(sArriv,actionMax)]                  
                    #mise à jour                    
                    Q2[(etat,action)]=r+gamma*max
                    
            #on augmente iteration
            Q=Q2
        return(Q)

    

    def executerPi(self,pi,depart,nb):
        '''permet d'executer une politique donnee'''
        s=depart
        # faire nb actions
        for i in range(nb):
            # recupere action de l'etat
            action=pi[s]
            # trouve etat d arrivee et recompense
            sFin=self.execute(s,action)
            r=self.pb.recompense(s,action,sFin)
            print(s," -> ",action," : ",sFin,"<",r,">")
            # change etat de depart
            s=sFin
                    

    def afficherQ(self,Q):
        '''permet d'afficher les Qvaleurs par etat/action'''
        for etat in self.pb.etats():
            chaine = ""+str(etat)+" - "
            for action in self.pb.actions():
                chaine+=action+" -> "+str(Q[(etat,action)])+", "
            print(chaine)



    def afficherQS(self,Q,etat):
        '''permet d'afficher une Qvaleur correspondant à un etat donne'''
        chaine = ""+str(etat)+" - "
        for action in self.pb.actions():
            chaine+=action+" -> "+str(Q[(etat,action)])+", "
        print(chaine)

            

    def politiqueFromQ(self,Q):
        '''permet de construire la politique a partir de la Qvaleur'''
        pi={}
        #pour chaque etat, on cherche la meilleure action
        for etat in self.pb.etats():
            # cherche max arrivee
            max=-100000
            amax=-1;
            for actionMax in self.pb.actions():
                if (Q[(etat,actionMax)]>max):
                    max=Q[(etat,actionMax)]
                    amax=actionMax
            # on dit que l'action a faire dans l etat est celle qui rapporte le plus
            pi[etat]=amax
        return(pi)



    def QLearning(self,Sdep,nPas,nEpisode,affiche):
        '''Permet d'effectuer du Qlearning'''
        Q={}
        #repeter n episode
        for episode in range(0,nEpisode):
            if (affiche):
                print("** nouvel episode ** ")
            #on reinitiliase au depart
            s=Sdep
            #pour chaque valeur de l'episode
            for pas in range(0,nPas):
                #miseAJour QValeur
                s=self.majQLearning(s,Q,affiche)
        return(Q)

    def majQLearning(self,s,Q,affiche):
        '''Permet de faire une mise a jour du Qlearning'''
        #valeur de gamma
        gamma=0.99
        
        #on recupere les actions possibles
        actions=self.pb.actions()
        
        #on choisit une action au hasard
        numAction = random.randint(0, len(actions) - 1)  
        a = actions[numAction]

        #on execute l'action (sarrivee et rec)
        sArriv = self.execute(s,a)
        rec=self.pb.recompense(s,a,sArriv)
        if affiche:
            print ("  - ",s,"+",a,"=>",sArriv,"  ",rec)
       
        # cherche max arrivee
        max=-100000
        for actionMax in actions:
            #si la clef n'existe pas, on cree
            if (not((sArriv,actionMax) in Q)):
                Q[(sArriv,actionMax)]=0
            if (Q[(sArriv,actionMax)]>max):
                max=Q[(sArriv,actionMax)]
        
        #on met a jour Qvaleur
        Q[(s,a)]=rec+gamma*max

        #on retourne le nouvel etat
        return(sArriv)


    
        
        
        

print("****************************************")

pb=Orpailleur()
print(pb.etats())
print(pb.actions())

print("****************************************")

print("****************************************")
print("planification a 1 pas")
systeme=Systeme(pb)
Q=systeme.valueIteration(1)
print(Q)

print("****************************************")
print("planification a 2 pas")
systeme=Systeme(pb)
Q=systeme.valueIteration(2)
print(Q)

print("****************************************")
print("planification a 3 pas")
systeme=Systeme(pb)
Q=systeme.valueIteration(3)
print(Q)


print("****************************************")
print("planification a 10 pas")
systeme=Systeme(pb)
Q=systeme.valueIteration(10)
print(Q)

print("****************************************")
print("planification a 1000 pas")
systeme=Systeme(pb)
Q=systeme.valueIteration(1000)
print(Q)


print("****************************************")
pi=systeme.politiqueFromQ(Q)
print(pi)
systeme.executerPi(pi, 'MG',30)


print("*********QLearning*********")
print()
print()
Q=systeme.QLearning('C',15,100,True)
systeme.afficherQ(Q)


        
    



