# -*- coding: utf-8 -*-
"""EXPLICATION : 
A chaque tour mon programme doit posseder une cible.
Si la cible precedente est atteinte, il choisit une nouvelle cible.
Tant que la cible n'est pas atteinte, il se deplace vers la cible.
Il capture la cible, en la neutralisant si elle est a l'adversaire.
Il la lie a tous les portails possibles.
Lorsque ces actions sont effectuees, le programme considere la cible atteinte.
Il reprend ces actions tant qu'elles n'ont pas etees terminees.
Il profite alors des derniers points d'action pour placer des boucliers.

Pour choisir un portail, il faut considerer deux choses :
-Les points potentiels gagnes :
Il s'agit des points que l'on gagne en creant des champs autour de ce portail
plus les points que l'adversaire ne gagne pas en detruisant
ses champs autour de ce portail.
-Le nombre de tour pour capturer ce portail :
Le nombre de tours necessaires pour atteindre ce portail et le capturer.

Le programme choisit en preference les portails qui s'qtteignent en un
nombre minimal de coups (1 en preference) et parmi ces portails, celui
qui rapporte le plus grand nombre potentiel de points.

L'agent se deplace vers la cible selon les coordonnees x, puis y.
Si necessaire il utilise des turbos.

Si l'agent gagne moins de points par tour que l'adversaire, il est
possible que le JEU tourne en rond au profit de l'adversaire. Il a alors
10% de chances de prendre une nouvelle cible aleatoirement. Le programme
garde en memoire egalement les dernieres captures. Si ces captures se
repetent et que le programme est perdant, il  choisit systematiquement une
nouvelle cible aleatoire. Si le programme est gagnant, il continue de jouer
normalement considerant que le jeu tourne en rond a son avantage.

Variables globales :
cible = Cible()
MOI = moi()
PORTAILS = liste_portails()
portails_moi = tous les portails a moi
historique = Historique()"""

from api import *
from random import random, choice




class Cible:
    '''Classe definissant la cible.'''
    def reinitialiser(self, aleatoire):
        '''Reinitialisation :
        -Choisit une nouvelle position pour la cible.
        On signifie que la cible n'a etee ni coisie, atteinte ou capturee'''
        self.position = choisir(aleatoire)
        self.atteinte = False
        self.capturee = False
        self.OK = False

class Historique:
    '''Permet de sauvegarder les dernieres actions des deux joueurs.'''
    def __init__(self):
        self.captures_adversaires = []
        self.captures_moi = []
        #Les listes des cibles capturees pour chaque joueur a chaque tour.
        self.scores_adversaire = []
        self.scores_moi = []
        #Les listes des scores pour chaque joueur a chaque tour.
        
    def ajouter_adversaire(self, captures, score):
        '''Ajoute les portails captures par l'adversaire
        et son score au dernier tour.'''
        self.captures_adversaires.append(captures)
        self.scores_adversaire.append(score)
        
    def ajouter_moi(self, captures, score):
        self.captures_moi.append(captures)
        self.scores_moi.append(score)
        
    def repetition(self):
        '''Verifie si le joueur et l'adversaire ont capture la meme cible
        chacuns au trois derniers coups.'''
        return ((self.captures_adversaires[-1]== self.captures_adversaires[-2]
                 == self.captures_adversaires[-3]) and (self.captures_moi[-1]
                == self.captures_moi[-2] == self.captures_moi[-3]))
        
    def moi_perds(self):
        '''Verifie si le nombre de points gagnes par l'adveraire au dernier
        coup est superieur au nombre de points gegnes par l'agent.'''
        return ((self.scores_moi[-1] - self.scores_moi[-2]) <= 
                (self.scores_adversaire[-1] - self.scores_adversaire[-2]))

def chemin(position1, position2):
    '''Itere la liste des intermediaires pour aller d'un point a l'autre.'''
    x1, y1 = position1
    x2, y2 = position2
    #On parcourt les x puis les y. A chaue fois on avance d'un pas et on
    #'cede' la position intermediaire.
    while x1 < x2:
        x1 += 1
        yield (x1, y1)
    while x1 > x2:
        x1 -= 1
        yield (x1, y1)
    while y1 < y2:
        y1 += 1
        yield (x1, y1)
    while y1 > y2:
        y1 -= 1
        yield (x1, y1)
        
def mettre_a_jour_portails():
    '''Supprime les portails neutralises par l'adversaire des portails a moi.'''
    global portails_moi
    for portail in hist_portails_neutralises():
        portails_moi.remove(portail)
        
def cout_neutralisation(portail):
    '''Cout en points d'action de la neutralisation
    d'un portail de l'adversaire.'''
    return (COUT_NEUTRALISATION +
            COUT_NEUTRALISATION_BOUCLIER*portail_boucliers(portail))

def voisins(portail):
    '''Itere les portails reliables a un portail donne.'''
    global portails_moi
    champs_autour = case_champs(portail)
    if champs_autour:
        #Si le portail est dans un champ, Il n'a aucun voisin.
        #On stoppe l'iterateur.
        return
    for voisin in portails_moi:
        if not liens_bloquants(portail, voisin): # si il n'existe pas de liens bloquants.
            yield voisin

def nb_voisins(portail):
    '''Le nombre de portails reliables a un portail donne.'''
    return len(list(voisins(portail)))

def triangles_possibles(portail):
    '''Itere les champs que l'on peut creer a partir d'un portail.
    on relie tous les voisins et si deux voisins sont relies entre eux, 
    on obtient un triangle.'''
    voisins_portail = list(voisins(portail))
    for (rang, som1) in enumerate(voisins_portail[:-1]):
        for som2 in voisins_portail[rang+1:]:
            if lien_existe(som1, som2):
                yield (portail, som1, som2)

def valeur_portail_potentielle(portail):
    '''Nombre de points que rapporte potentiellement la prise d'un portail.
    On additionne le nombre de points que rapporte chaque creation
    d'un champ potentiel.'''
    global portails_moi
    gain = 0
    for triangle in triangles_possibles(portail):
        gain += score_triangle(*triangle)
    return gain

def valeur_portail(portail):
    '''Le nombre de points que rapportent par tour les champs relies a un portail.'''
    valeur = 0
    for champ in champs_incidents_portail(portail):
        valeur += score_triangle(champ.som1, champ.som2, champ.som3)
    return valeur

def cout_deplacement(nb_points_deplacement, portail):
    global MOI
    dis = distance(position_agent(MOI), portail)
    if dis <= points_deplacement():
        return 0
    return COUT_TURBO*(dis - points_deplacement())

def cout_prise(portail):
    '''Cout en pts d'action de la prise d'un portail.'''
    cout = cout_deplacement(points_deplacement(), portail) + COUT_CAPTURE + COUT_LIEN*nb_voisins(portail)
    #Cout pour aller au portail, le capturer et le lier.
    if portail_joueur(portail) == -1: #portail vide
        return cout
    else: #portail a l'adversaire
        return cout + cout_neutralisation(portail)
        #Supplement pour neutraliser le portail.

def gain_prise(portail):
    '''Nombre de points potentiels que rapporte la prise d'un portail
    en comptant les points que l'on gagne et les points que l'adveraire perd.'''
    if portail_joueur(portail) == -1:
        #portail vide
         return .1 + valeur_portail_potentielle(portail)
        #Le nombre de points du portail gagne.
        #On rajoute .1 pour ne pas que ca soit egal a 0.
    else:
         #portail a l'adversaire
        return .1 + valeur_portail(portail) + valeur_portail_potentielle(portail)
        #Le nombre de points du portail gagne + le no;bre de points du portail perdu.

def temps_capture(portail):
    global MOI
    '''Nombre de tour necessaire pour aller a un portail et le capturer.'''
    tours = 1 # Le nombre minimum de tour
    points_action_restants = points_action()
    points_deplacement_restants = points_deplacement()
    dis = distance(portail, position_agent(MOI))
    #La distance initiale au portail.
    dis -= points_deplacement_restants
    if dis > 0:
        #Si les points de deplacement ne suffisent pas,
        #on utilise les turbos.
        if points_action_restants >= COUT_TURBO*dis:
            #Si les turbos suffisent, on en utilise le minimum
            points_action_restants -= COUT_TURBO*dis
            dis = 0
        else:
            dis -= points_action_restants // COUT_TURBO
            #si les turbos ne suffisent pas, on en utilise le plus possible.
        while dis > 0:
            #Tant qu'on n'est pas arrive, on recommence un tour
            #et on repete ces operations.
            tours += 1
            points_action_restants = NB_POINTS_ACTION
            dis -= NB_POINTS_DEPLACEMENT
            if dis > 0:
                if points_action_restants >= COUT_TURBO*dis:
                    points_action_restants -= COUT_TURBO*dis
                    dis = 0
                else:
                    dis -= points_action_restants // COUT_TURBO
    if points_action_restants <= cout_prise(portail):
        #Si le portail n'est pas prenable, on le prend au tour suivant.
        tours += 1
    return tours



###Ces fonctions definissent les actions qui seront executees durant un tour.

def choisir(aleatoire):
    '''Choisit un portail parmi ce qui ne m'appartiennent pas.
    Si le mode aleatoire=True, on tire au sort un portail.
    On cherche d'abord la liste des portails capturables
    en le moins de coups possibles.
    On choisit enfin celui qui vaut le plus de points.'''
    global PORTAILS
    global MOI
    autres_portails = [portail for portail in PORTAILS if portail_joueur(portail) != MOI]
    #la liste des portails qui ne m'appartiennent pas.
    if not autres_portails:
        #Tous les portails m'appartiennent, on interromp.
        return
    if aleatoire:
        #tirage au sort.
        return choice(autres_portails)
    portails_min = []
    capture_min = float('inf')
    #on liste les portails qui necessitent le - de coups pour etre captures.
    for portail in autres_portails:
        capture_portail = temps_capture(portail)
        if capture_portail < capture_min:
            capture_min = capture_portail
            portails_min = [portail]
        elif capture_portail == capture_min:
            portails_min.append(portail)
    return max(portails_min, key=gain_prise)


def se_deplacer(destination):
    '''Se deplace vers le portail le plus proche.
    Va le plus loin possible si le portail est trop loin.
    Renvoie True si le deplacement est reussi, False sinon.'''
    for intermediaire in chemin(position_agent(MOI), destination):
        if points_deplacement() == 0:
            #On ne peut plus se deplacer : on doit acheter un turbo.
            if points_action() < COUT_TURBO:
                #On ne peut pas acheter de turbo, on est bloque.
                return False
            utiliser_turbo()
        deplacer(intermediaire)
    #On est arrive a destination
    return True

_neutraliser = neutraliser #On va modifier la fonction 'neutraliser' d'origine
def neutraliser():
    '''Neutralise le portail et renvoie True si l'operation est reussie.'''
    global MOI
    if points_action() < cout_neutralisation(position_agent(MOI)):
        return False
    _neutraliser()
    return True

_capturer = capturer #pareil
def capturer():
    '''Si le portail actuel est a l'adversaire : le neutralise.
    Le capture.
    Renvoie True se le portail a reussi a etre capture.
    Ajoute ce portail a la liste des portails captures.'''
    global MOI
    global portails_moi
    if portail_joueur(position_agent(MOI)) == adversaire():
        #Si le portail est a l'adversaire.
        if not neutraliser():
            #On tente la neutralisation.
            return False
    if points_action() < COUT_CAPTURE:
        return False
    _capturer()
    portails_moi.append(position_agent(MOI))
    return True

def relier():
    '''Relie la case a toute les cases possibles.
    Renvoie True si cette action a ete executee.'''
    global portails_moi
    for portail in portails_moi:
        if points_action() < COUT_LIEN:
            #Si on ne peut plus rien lier.
            return False
        lier(portail) #On essaie de lier.
    return True
    

    
    
### Debut du programme 
# Fonction appelée au début de la partie.
def partie_init():
    '''Initialise les variables globales.'''
    global cible
    global MOI
    global PORTAILS
    global portails_moi
    global historique
    MOI = moi()
    PORTAILS = liste_portails()
    portails_moi = []
    cible = Cible()
    cible.reinitialiser(False)
    historique = Historique()

# Fonction appelée à chaque tour.
def jouer_tour():
    '''Effectue les actions suivantes des cet ordre :
    -Si la cible precedente est atteinte ; choisir un nouvelle cible
    -Si la cible n'est pas atteinte : se deplacer vers la cible
    -Si la cible n'est pas neutralisee ; la neutraliser
    -La relier a tous les portails possibles
    S'arrette lorsque l'on n'a plus assez de points d'action
    pour realiser l'une de ses actions.
    Si il reste des points d'action, complete avec des boucliers.
    Rempli les historiques avant de s'arreter.'''
    global cible
    global MOI
    global portails_moi
    global historique
    
    mettre_a_jour_portails()
    historique.ajouter_adversaire(hist_portails_captures(), score(adversaire()))
    #On met a jour d'apres ce que l'adversaire a fait au dernier tour.
    aleatoire = False
    if tour_actuel() > 3 and historique.moi_perds():
        #Si je perds.
        #On attend le troisieme tour pour etre sur que les historiques
        #sont suffisamment remplis.
        if historique.repetition() or random() > .9:
            aleatoire = True
            #Dans 90% des cas ou si le JEU tourne en rond, je change.
    
    if cible.OK:
        cible.reinitialiser(aleatoire)
        #Le parametre aleatoire est le parametre qui sera envoye a 'choisir'.
        
    if not cible.atteinte:
        if not se_deplacer(cible.position):
            #Si une action n'a pas pu etre realisee,
            #on remplit les historique et on s'arrete.
            historique.ajouter_moi([], score(MOI))
            #Dans ce cas, on n'a visite aucun portail et le score et le meme.
            return
        cible.atteinte = True
        
    if (not cible.capturee) or portail_joueur(cible.position)== adversaire() :
        #Si la cible n'a pas etee capturee ou si elle a etee
        #reprise par l'adversaire.
        if not capturer():
            historique.ajouter_moi([], score(MOI))
            #Pareil.
            return
        cible.capturee = True
        
    if not cible.OK:
        #il faut juste relier la cible.
        if not relier():
            historique.ajouter_moi([position_agent(MOI)], score(MOI) +
                                   valeur_portail(position_agent(MOI)))
            #Dans ce cas, on a pris un portail.
            #on rajoute aussi la liste des champs connectes a ce portail
            return
        cible.OK = True
    
    for i in range(MAX_BOUCLIERS):
        #On depense les derniers points d'action pour acheter es boucliers.
        if points_action() < COUT_BOUCLIER + i:
            break
        ajouter_bouclier()
    historique.ajouter_moi([position_agent(MOI)], score(MOI) +
                           valeur_portail(position_agent(MOI)))

# Fonction appelée à la fin de la partie.
def partie_fin():
    print("C'est fini")