<jupyter_start><jupyter_text>Documentación del proyecto "Quoridor"  Desarrollo de la clase DoubleLinkedListEsta **clase** contiene la implementación de una **lista doblemente enlazada** con sus diferentes métodos, tales como insertar al principio, insertar al final, buscar por valor, eliminar al prinicipio y eliminar al final, esta clase nos servirá para implementar nuestra clase Tablero, la cual es el grafo.<jupyter_code>import gc
class Node:
    def __init__(self, value=None):
        self.value = value
        self.next = None
        self.prev = None
class DoubleLinkedList:

    def __init__(self):
        self.start = Node()
        self.end = Node()
        self.size = 0
    def __contains__(self, value):
        aux = self.start
        while aux != None:
            if aux.value == value:
                return True
        aux = aux.next
        return False
    def __len__(self):
        return self.size

    def __iter__(self):
        node = self.start
        while node:
            yield node.value
            node = node.next

    def push_front(self, value):
        new = Node(value)
        if self.size == 0:
            self.start = self.end = new
            self.size += 1
            return
        new.next = self.start
        self.start.prev = new
        self.start = new
        self.size += 1

    def push_back(self, value):
        if self.size == 0:
            self.push_front(value)
            return
        new = Node(value)
        self.end.next = new
        new.prev = self.end
        self.end = new
        self.size += 1

    def insert(self, index, value):
        if self.size == 0 or index == 0:
            self.push_front(value)
            return
        if index == self.size - 1:
            self.push_back(value)
            return
        if index < 0 or index >= self.size:
            return
        new = Node(value)
        c = self.start
        for i in range(index - 1):
            c = c.next
        new.next = c.next
        c.next.prev = new
        c.next = new
        new.prev = c
        self.size += 1

    def pop_back(self):
        if self.size <= 0:
            return
        if self.size == 1:
            self.start = self.end = None
            self.size -= 1
            gc.collect()  # eliminar memoria sin referencia
            return
        self.end = self.end.prev
        self.end.next.prev = None
        self.end.next = None
        self.size -= 1
        gc.collect()  # eliminar memoria sin referencia

    def pop_front(self):
        if self.size <= 1:
            self.pop_back()
            return
        self.start = self.start.next
        self.start.prev.next = None
        self.start.prev = None
        self.size -= 1
        gc.collect()

    def delete(self, index):
        # lo hacen uds, eliminar en una posicion :v
        self.size -= 1

    def erase(self, pos):
        if pos < self.size and pos >= 0:
            if (pos == 0):
                self.pop_front()
            elif pos == self.size:
                self.pop_back()
            else:
                punt = self.start
                contador = 0
                while (contador != pos - 1):
                    punt = punt.next
                    contador += 1
                    elimi = punt.next
                punt.next = elimi.next
                elimi.next = None
                del elimi
                self.size -= 1

    def buscar_por_valor(self, valor):
        contador = 0
        punt = self.start
        while (contador != self.size):
            if punt.value == valor:
                return contador
            punt = punt.next
            contador += 1
        return -1<jupyter_output><empty_output><jupyter_text>Desarrollo de la clase TableroEsta clase contiene el grafo para el juego, el grafo se compone de un vector de listas doblemente enlazadas, lo formamos así con el objetivo de tener un acceso $O(1)$ a los elementos y un insertado dinámico.La función $__init__$ inicializa el grafo, es aquí donde se hace cada conexión, la cual se representará en forma de una lista de adyacencia.<jupyter_code>class Tablero:
    def __init__(self, n):
        self.tam = 792/n
        self.n = n
        self.q_nodos = n * n
        self.grafo = [DoubleLinkedList() for i in range(self.q_nodos)]
        for i, nodo in enumerate(self.grafo):
            if i % n != n - 1:
                self.grafo[i].push_back(i + 1)
                self.grafo[i + 1].push_back(i)
            if i + n < n * n:
                self.grafo[i].push_back(i + n)
                self.grafo[i + n].push_back(i)<jupyter_output><empty_output><jupyter_text>La función $_get_coord_$ que recibe como parametro un valor obtiene ....<jupyter_code>def get_coord(self, value):
          coord = {"x": None, "y": None}
          coord['y'] =  value//self.n
          coord['x'] = value %self.n
          return coord<jupyter_output><empty_output><jupyter_text>La función $graficar_tablero_$ que recibe como parametros a la pantalla y a la librería pygame ....<jupyter_code>def graficar_tablero(self, pantalla, pygame):
          coord = None
          for i, _ in enumerate(self.grafo):
              coord = self.get_coord(i)
              pygame.draw.rect(pantalla, negro, (coord['x']*self.tam, coord['y']*self.tam, self.tam, self.tam), 1)<jupyter_output><empty_output><jupyter_text>Por último, la funcion $conectados_$ que recibe dos nodos como atributo determina si un nodo está conectado con el otro, debido a que el grafo tiene forma de matriz, si un nodo1 está conectado con un nodo2, el nodo2 también estará conectado con el nodo 1.<jupyter_code>def conectados(self,nodo1,nodo2):
          return nodo2 in self.grafo[nodo1]<jupyter_output><empty_output><jupyter_text>Desarrollo de la clase QuoridorEste contiene el juego donde se inicializa con la cantidad de jugadores y renderiza la parte gráfica  La función $__init__$ recibe como parametro el grafo, el lado del grafo n, la cantidad de jugadores. El cual se encarga de inicializar el juego Quoridor con la cantidad de jugadores y la posición en la que iniciaran. Ademas de determinar el turno de cada jugador<jupyter_code>def __init__(self, n, qjugadores):
        self.turno = 0
        self.tablero = Tablero(n)
        self.ganador = False
        self.lista_de_jugadores = []
        self.lista_de_jugadores.append(
            Jugador(1, self.tablero.grafo, self.tablero.n // 2, (self.tablero.q_nodos - self.tablero.n // 2)))
        self.lista_de_jugadores.append(
            Jugador(1, self.tablero.grafo, (self.tablero.q_nodos - self.tablero.n // 2) , self.tablero.n // 2))
        if qjugadores == 4:
            self.lista_de_jugadores.append(
                Jugador(1, self.tablero.grafo, (self.tablero.n * (self.tablero.n / 2 + 1)) - 1,
                        (self.tablero.n * (self.tablero.n / 2 + 1)) - self.tablero.n))
            self.lista_de_jugadores.append(
                Jugador(3, self.tablero.grafo, (self.tablero.n * (self.tablero.n / 2 + 1)) - self.tablero.n,
                        (self.tablero.n * (self.tablero.n / 2 + 1)) - 1))<jupyter_output><empty_output><jupyter_text>La función $_start_$ recibe como parametro el grafo se encarga de renderizar el tablero con los jugadores gracias a la libreria PygamesAsimismo, determina la jugada de cada jugador, lo cual fue inicializado en la función anterior, pero que ahora es actualizado. Como en la vida real, el jugador piensa (llama a su método pensar) lo cual realiza en la siguiente función:``` self.lista_de_jugadores[self.turno].piensa(self.lista_de_jugadores[(self.turno + 1) % 2], self.tablero)```Y luego mueve (llama a su método mover), el cual asumirá el valor de ganador:``` self.ganador = self.lista_de_jugadores[self.turno].mueve(self.lista_de_jugadores[(self.turno + 1) % 2], self.tablero)```Pero que en cada iteración se verificará, si hubiera un ganador, se termina el juego :``` if(self.ganador == True):                break```<jupyter_code>def start(self):
        pygame.init()
        pygame.display.set_caption(u'Quoridor')
        pantalla = pygame.display.set_mode((792, 792))
        clock = pygame.time.Clock()
        is_running = True
        pantalla.fill(blanco)
        self.tablero.graficar_tablero(pantalla, pygame)
        for i, jugador in enumerate(self.lista_de_jugadores):
            jugador.graficar(pantalla, pygame, self.tablero.n, self.tablero.tam,colores[i])
        pygame.display.update()
        pygame.time.wait(3000)  
        while is_running:
            
            if(self.ganador == True):
                break
            clock.tick(1)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    is_running = False  
            pantalla.fill(blanco)
            self.tablero.graficar_tablero(pantalla, pygame)
            for i, jugador in enumerate(self.lista_de_jugadores):
                jugador.graficar(pantalla, pygame, self.tablero.n, self.tablero.tam,colores[i])
            self.lista_de_jugadores[self.turno].piensa(self.lista_de_jugadores[(self.turno + 1) % 2], self.tablero)
            self.ganador = self.lista_de_jugadores[self.turno].mueve(self.lista_de_jugadores[(self.turno + 1) % 2], self.tablero)
            self.turno = (self.turno + 1) % 2
            pygame.display.update()

        pantalla.fill(blanco)
        self.tablero.graficar_tablero(pantalla, pygame)
        for i, jugador in enumerate(self.lista_de_jugadores):
            jugador.graficar(pantalla, pygame, self.tablero.n, self.tablero.tam,colores[i])
        pygame.display.update()        
        pygame.time.wait(3000)                              
        pygame.quit()<jupyter_output><empty_output><jupyter_text>Desarrollo de la clase PensamientoSe encarga de darle el pensamiento a cada jugador, el pensamiento se trata de elegir cualquiera de los algoritmos de path finding para poder realizar su ruta. Además este contiene un método llamado actualiza_ruta, el cual será explicado más adelante.  Inicializamos la clase Pensamiento, recibimos el grafo (tablero donde se movera el peon), asimismo qué pensamiento seguirá el peón, lo cual es delimitado por el atributo indica<jupyter_code>def __init__(self, Grafo, indica):
        self.grafo = Grafo
        self.indica = indica<jupyter_output><empty_output><jupyter_text>Este método se llamará si fuera necesario actualizar la ruta $_actualiza_ruta_$, esto es necesario si vemos que alguno de nuestros rivales está cambiando de ruta, la cual el peon actual no predecio, como nota que ha cambiado de ruta, pues este peón también debería de hacerlo.<jupyter_code>def actualiza_ruta(self, start, end):
        if self.indica == 1:
            return self.bellman_ford(start, end)
        elif self.indica == 2:
            return self.dijkstra(start, end)
        else:
            return self.astar(start, end)<jupyter_output><empty_output><jupyter_text>Algoritmo dijstra es uno de los algortimos de búsqueda de camino mas corto que usamos $_dijstra_$  tiene como parametros el grafo y los nodos que representan el start y el end<jupyter_code>def dijsktra(self, Nodos):

     S = []
     Queue = []
     anterior = [0 for i in range(max(grafo) +1)]
     distancia = [0 for i in range(max(grafo) +1)]

     for nodo in grafo:
         distancia[nodo] = 10000
         Queue.append(nodo)
     distancia[Nodos[0]] = 0

     while not len(Queue) == 0:
        distancia_minima = 10000
        for nodo in Queue:
            if distancia[nodo] < distancia_minima:
                distancia_minima = distancia[nodo]
                nodo_temporal = nodo
        nodo_distancia_minima = nodo_temporal
        Queue.remove(nodo_distancia_minima)

        for vecino in grafo[nodo_distancia_minima]:
            if distancia[nodo_distancia_minima] == 10000:
                distancia_temporal = 0
            else:
                distancia_temporal = distancia[nodo_distancia_minima]
            distancia_con_peso = distancia_temporal + 1
            if distancia_con_peso < distancia[vecino]:
                distancia[vecino] = distancia_con_peso
                anterior[vecino] = nodo_distancia_minima

        if nodo_distancia_minima == Nodos[1]:
            if anterior[nodo_distancia_minima] != 0 or nodo_distancia_minima == Nodos[0]:
                while nodo_distancia_minima != 0:
                    S.insert(0, nodo_distancia_minima)
                    nodo_distancia_minima = anterior[nodo_distancia_minima]
                return S<jupyter_output><empty_output><jupyter_text>Contamos con la inicializacion de las listas que usaremos donde 1. S: lista que retorna los puntos a recorrer2. Queque: Cola de prioridad3. anterior y distancia: listas con tamaño del grafo +1<jupyter_code>S = []
     Queue = []
     anterior = [0 for i in range(max(grafo) +1)]
     distancia = [0 for i in range(max(grafo) +1)]<jupyter_output><empty_output><jupyter_text>Inicializamos todo con un número muy grande en este caso infinito<jupyter_code>for nodo in grafo:
         distancia[nodo] = 10000
         Queue.append(nodo)
     distancia[Nodos[0]] = 0<jupyter_output><empty_output><jupyter_text>Mientras el tamaño de la lista de la distancia minima no sea igual a 0, la distancia es igual a un número muy grande. Para cada nodo en la cola, se hace la comparación si la distancia del nodo es menor a la distancia minima, luego se  actualiza la distancia minima con la distancia del nodo y removemos de la cola la distancia minima.<jupyter_code>if distancia[nodo] < distancia_minima:
                distancia_minima = distancia[nodo]
                nodo_temporal = nodo
        nodo_distancia_minima = nodo_temporal
        Queue.remove(nodo_distancia_minima)<jupyter_output><empty_output><jupyter_text>Para los nodos vecinos en el grafo, si el nodo de la distancia minima es igual a un número muy grande la distancia temporal es igual a 0Sino la distancia temporal se actualiza con el nodo de la distancia minima. Se comparan las distancias con pesos, en este trabajo consideramos peso 1 a cada vertice en el grafo. Entoncces si la distancia con peso es menor a la distancia del vecino se actualizara la distancia del vecino con la distancia con peso y la distancia del vecino anterior ahora es el nodo de distancia minima.<jupyter_code>for vecino in grafo[nodo_distancia_minima]:
            if distancia[nodo_distancia_minima] == 10000:
                distancia_temporal = 0
            else:
                distancia_temporal = distancia[nodo_distancia_minima]
            distancia_con_peso = distancia_temporal + 1
            if distancia_con_peso < distancia[vecino]:
                distancia[vecino] = distancia_con_peso
                anterior[vecino] = nodo_distancia_minima<jupyter_output><empty_output><jupyter_text>Si el nodo de distancia minima es igual a nodo final: Entramos a la sentencia donde compara si el nodo anterior es diferente a 0 o el nodo de distancia minima es igual a al comienzo.Mientras la distancia minima sea diferente de 0 se inserta en la cola de prioridad y se actualiza el nodo de distancia minima con el nodo anterior de distancia minima. Finalmente retorna S donde S nos regresa la ruta Nodo a Nodo<jupyter_code>if nodo_distancia_minima == Nodos[1]:
            if anterior[nodo_distancia_minima] != 0 or nodo_distancia_minima == Nodos[0]:
                while nodo_distancia_minima != 0:
                    S.insert(0, nodo_distancia_minima)
                    nodo_distancia_minima = anterior[nodo_distancia_minima]
                return S<jupyter_output><empty_output><jupyter_text>Algoritmo bellman_ford es uno de los algortimos de búsqueda de camino mas corto que usamos $_bellman_ford_$  tiene como parametros el grafo y el inicio y fin de los nodos.<jupyter_code>def bellman_ford(self, start, end):
        ruta = DoubleLinkedList()
        ruta.push_front(end)

        distancia = [float("Inf") for x in range(len(self.grafo))]

        previo = [-1 for x in range(len(self.grafo))]
        distancia[start] = 0
        for iteracion in range(len(self.grafo) - 1):
            for actual, nodo in enumerate(self.grafo):
                for adyacente in nodo:
                    if distancia[actual] + 1 < distancia[adyacente]:
                        distancia[adyacente] = distancia[actual] + 1
                        previo[adyacente] = actual
        anterior = previo[end]
        while anterior != start:
            ruta.push_front(anterior)
            anterior = previo[anterior]
        return list(ruta)<jupyter_output><empty_output><jupyter_text>Inicializacion:1. la ruta con una lista doblemente enlazada2. Agregamos el nodo final a la ruta 3. Inicializamos la distancia con un número infinito con el tamaño del grafo4. Inicializamos la distancia previa con -15. La distancia en el nodo inicio es igual a 0<jupyter_code>ruta = DoubleLinkedList()
        ruta.push_front(end)

        distancia = [float("Inf") for x in range(len(self.grafo))]
        previo = [-1 for x in range(len(self.grafo))]
        distancia[start] = 0<jupyter_output><empty_output><jupyter_text>Iteramos en el tamaño del grafo -1 1. Recorremos el grafo para cada nodo actual en el tamaño del grafo2. Para cada nodo adyacente 3. Si la distancia actual + 1 es menor a la distancia adyacente:4. Actualizamos la distancia adyacente con la distancia actual +15. Actualizamos la distancia previa adyacente con la distancia actual6. Actualizamos la distancia anterior con el nodo final previo7. mientras anterior sea diferente de nodo inicial8. Agregamos a la lista de ruta el nodo anterior  9. Retornamos la lista de rutas<jupyter_code>for iteracion in range(len(self.grafo) - 1):
            for actual, nodo in enumerate(self.grafo):
                for adyacente in nodo:
                    if distancia[actual] + 1 < distancia[adyacente]:
                        distancia[adyacente] = distancia[actual] + 1
                        previo[adyacente] = actual
        anterior = previo[end]
        while anterior != start:
            ruta.push_front(anterior)
            anterior = previo[anterior]
        return list(ruta)<jupyter_output><empty_output><jupyter_text>Algoritmo astar es uno de los algortimos de búsqueda de camino mas corto que usamos $_astar_$  tiene como parametros el grafo y el inicio y fin de los nodos. Dentro de este algoritmo, se declaran dos funciones:La funcion heuristic que retorna una distancia aproximada entre el nodo en el que me encuentro y el nodo al que quiero llegar, esto servirá para actualizar los valores globales (valor de la heuristica + la distancia recorrida hasta el momento) de acuerdo a cada nodo.```  def heuristic(current, goal):            h = abs(current[0] - goal[0]) + abs(current[1] - goal[1])            return float(h)```Adicionalmente, se declara get_heuristic_value, que recibe como parametro un nodo, esto servirá como lambda para cuando ordenemos la lista de nodos de acuerdo a su valor global.```   def get_heuristic_value(node):            return node.globhal```Lo siguiente es igualar el nodo en el que estoy ahora al nodo del principio, pues al no haberme movido me encuentro en el principio.Las variables c,f,c1,f1 representan a las coordenadas de el nodo start (c,f) y el nodo goal (c1,f1), con ello podemos obtener la distancia aproximada que existe entre ellos.```        current = start        c = start % self.grafo.n + start // self.grafo.n        f = start // self.grafo.n        c1 = goal % self.grafo.n + goal // self.grafo.n        f1 = goal // self.grafo.n```Luego, se inicializa cada Nodo como instancia de Node_Astar, una clase que contiene como atributos a su padre, su valor global, su valor local y si ha visitado o no.``` class Node_Astar:    def __init__(self, pos, padre=None, local=float("inf"), globhal=float("inf"), visited=False):        self.pos = pos        self.padre = padre        self.local = local        self.globhal = globhal        self.visited = visited```El valor de la distancia del nodo del principio hacia el mismo es 0, ya que aún no recorre ningún camino. Además, se inicializa su valor global con el valor de la heuristica para este nodo. Finalmente se le agrega a una lista que posteriormente testeará a cada nodo para poder encontrar la ruta adecuada:```        grafoo = [Node_Astar(i) for i in range(self.grafo.n * self.grafo.n)]        grafoo[start].local = 0.0        grafoo[start].globhal = heuristic((c, f), (c1, f1))        not_tested_nodes = [grafoo[start]]        ruta = []```A partir de ello, empieza a correr el algoritmo, si ya probe con todos los nodos o el nodo donde me encuentro es al que queria llegar terminamos de buscar la ruta.Luego ordeno la lista de manera ascendente de acuerdo al valor global de los nodos, para testear al que tiene al mínimo.Mientras la lista de nodos no esté vacía y ya haya visitado al primer nodo luego de ordenarlos, lo eliminaré pues no deseo evaluarlo nuevamente.Current se actualiza a la posición del primer nodo de la lista de nodos no testeados, la ruta añade al padre de este, este nodo se marca como visitado.Adicionalmente, busco en sus vecinos. Si hay alguno que no he testeado lo añado a la lista de nodos no testeados, luego evaluo si la suma de mi posicion actual + 1 es menor a el valor local de uno de mis vecinos, si es así actualizo cada parametro de este.Así continua hasta recorrer todos sus vecinos y testear todos los nodos hasta encontrar una ruta.A continuación el algoritmo Astar completo:<jupyter_code>def astar(self, start, goal):  # self.n

        def heuristic(current, goal):
            h = abs(current[0] - goal[0]) + abs(current[1] - goal[1])
            return float(h)

        def get_heuristic_value(node):
            return node.globhal

        current = start
        c = start % self.grafo.n + start // self.grafo.n
        f = start // self.grafo.n
        c1 = goal % self.grafo.n + goal // self.grafo.n
        f1 = goal // self.grafo.n
        grafoo = [Node_Astar(i) for i in range(self.grafo.n * self.grafo.n)]
        grafoo[start].local = 0.0
        grafoo[start].globhal = heuristic((c, f), (c1, f1))
        not_tested_nodes = [grafoo[start]]
        ruta = []

        while len(not_tested_nodes) != 0 and current != goal:

            not_tested_nodes.sort(key=get_heuristic_value)

            while len(not_tested_nodes) != 0 and not_tested_nodes[0].visited:
                not_tested_nodes.pop(0)

            if len(not_tested_nodes) == 0:
                break

            current = not_tested_nodes[0].pos
            ruta.append(grafoo[current].padre)
            not_tested_nodes[0].visited = True

            for neighbor in self.grafo[current]:

                if not grafoo[neighbor].visited:
                    not_tested_nodes.append(grafoo[neighbor])

                sum = float(grafoo[current].local + 1)

                if sum < grafoo[neighbor].local:
                    grafoo[neighbor].padre = current
                    grafoo[neighbor].local = sum
                    c = neighbor % self.grafo.n + neighbor // self.grafo.n
                    f = neighbor // self.grafo.n
                    grafoo[neighbor].globhal = grafoo[neighbor].local + \
                        heuristic((c, f), (c1, f1))

        ruta.append(goal)
        ruta.pop(0)
        return ruta<jupyter_output><empty_output><jupyter_text>Desarrollo de la clase Jugador Función $_init_$ recibe como parámetros al grafo, la variable indicar, g<jupyter_code>def __init__(self, indica, Grafo, nodostart, nodogoal):
        self.nodogoal = nodogoal
        self.pensamiento = Pensamiento(Grafo, indica)
        self.lista_ruta = []
        self.lista_ruta_rival = []
        self.current = nodostart
        self.primero = True<jupyter_output><empty_output><jupyter_text>En este método del jugador, es donde evalua los movimientos a dar.<jupyter_code>def piensa(self, rival, tablero):
        if self.primero:
            self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, self.nodogoal)
            self.lista_ruta_rival = self.pensamiento.actualiza_ruta(rival.current, rival.nodogoal)
            self.primero = False
            return

        if rival.current == self.lista_ruta_rival[0]:
            self.lista_ruta_rival.pop(0)
        else:
            self.lista_ruta_rival = self.pensamiento.actualiza_ruta(rival.current, rival.nodogoal)
        if self.current == rival.nodogoal and self.primero == False:
            self.lista_ruta = [self.current - 1]
            return
        if self.lista_ruta == []:
            self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, self.nodogoal)
            return
        if len(self.lista_ruta) < len(self.lista_ruta_rival):
            return
        if self.alcostado(self.current, rival.current, tablero.n) is True:
            self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, rival.nodogoal)
            return
        if self.alcostado(self.lista_ruta[0], rival.current, tablero.n) is True:
            self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, rival.nodogoal)
            return<jupyter_output><empty_output><jupyter_text>Verifico si el nodo está a algun lado mío (izquierda, derecha, arriba y abajo).<jupyter_code>def alcostado(self, nodo1, nodo2, n):
        return nodo1 - 1 is nodo2 or nodo1 + 1 is nodo2 or nodo1 + n is nodo2 or nodo1 - n is nodo2<jupyter_output><empty_output><jupyter_text>Valido la dirección a la que me estoy dirigiendo o en otros casos un nodo se está dirigiendo, es el mismo caso de arriba, se evalua arriba, abajo, izquierda y derecha.<jupyter_code>def validar_direccion(self, nodoI, nodoF, n):
        validar = nodoI - nodoF
        if (validar == n):
            return 0  # arriba a abajo
        elif (validar == -n):
            return 1  # arriba a abajo
        elif (validar == -1):
            return 2  # izquierda a derecha
        elif (validar == 1):
            return 3  # derecha a izquierda<jupyter_output><empty_output><jupyter_text>Esté método determina a dónde el jugador se mueve después de haber analizado el juego.<jupyter_code>def mueve(self, enemigo, tablero):
        inicial = self.current
        self.current = self.lista_ruta[0]
        self.lista_ruta.pop(0)
        if self.current == enemigo.current:
            direccion = self.validar_direccion(inicial, self.current, tablero.n)
            if direccion is 0:
                if self.current - tablero.n >= 0:
                    if tablero.conectados(self.current, self.current - tablero.n):
                        self.current -= tablero.n
                    else:
                        pass
                else:
                    self.current += 1
                    self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, self.nodogoal)
                    return self.current == self.nodogoal
            elif direccion is 1:
                if self.current + tablero.n < tablero.n:
                    if tablero.conectados(self.current, self.current + tablero.n):
                        self.current += tablero.n
                    else:
                        pass
                else:
                    self.current += 1
                    self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, self.nodogoal)
                    return self.current == self.nodogoal
            elif direccion is 2:
                if tablero.conectados(self.current, self.current + 1):
                    self.current += 1
                else:
                    pass
            elif direccion is 3:
                if tablero.conectados(self.current, self.current - 1):
                    self.current -= 1
                else:
                    pass
            if self.current == self.lista_ruta[0]:
                self.lista_ruta.pop(0)
            else:
                self.lista_ruta = self.pensamiento.actualiza_ruta(self.current, self.nodogoal)
        return self.current == self.nodogoal<jupyter_output><empty_output><jupyter_text>Este método se encarga de graficar cada rectangulo(nodo del tablero) y los circulos (<jupyter_code>def graficar(self, pantalla, pygame, n, lado, color):
        x = self.current % n
        y = self.current // n
        x1 = self.nodogoal % n
        y1 = self.nodogoal // n
        if self.primero == False:
            pygame.draw.rect(pantalla, blue, (x1 * lado, y1 * lado, lado, lado), 0)
        pygame.draw.ellipse(pantalla, color, (x * lado, y * lado, lado, lado), 0)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08430273300630693, 'fraction_numerical': 0.009145059565522074, 'mean_word_length': 2.406253729562, 'pattern_counts': {'":': 2, '<': 115, '<?xml version=': 0, '>': 104, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '242102', 'license': 'MIT', 'n_tokens_mistral': 9445, 'n_tokens_neox': 9102, 'n_words': 2654, 'path': 'Informes/Informes TP/TP_Documentacion_Codigo.ipynb', 'repo_name': 'lucas1619/tf-complejidad', 'size': '28540'}