No todos los hechizos y ataques en CodedArena se pueden lanzar a la misma distancia.

Hay hechizos, como Lightning, o atacar con la varita (Wand), que tienen rangos de lanzamiento mucho mayores que otros, como Slam, que son para luchar cuerpo a cuerpo.

¡Debemos tener en cuenta las distancias individuales de los hechizos!

La estrategia

Imaginemos que queremos que nuestro personaje:

  1. Detecte al adversario.
  2. Se acerque a distancia de ataque.
  3. Empiece con el hechizo Lightning.
  4. Siga con Slam si Lightning no está disponible (cooldown)
  5. Ataque cuerpo a cuerpo (melee) si los otros dos hechizos no están disponibles.

Las distancias

En el caso del personaje de este artículo, las distancias máximas son:

  • 271 para lanzar Lightning
  • 45 para lanzar Slam

Fijaos que hemos utilizado self.debug para ver a qué distancias pueden lanzar los hechizos nuestro personaje.

El código

¡Vamos a programar la estrategia de nuestro personaje!

Miremos el siguiente fragmento de código:

# En la variable enemy almacenamos al adversario

# Nos aproximamos al adversario

# Si estamos a distancia de lucha, elaboramos la estrategia de ataques
if self.distance_to(enemy) <= self.max_distance_of("lightning"):
    if self.is_ready("lightning") and self.cost_of("lightning") <= self.mana:
        return ["cast", "lightning", enemy]
    
    if self.is_ready("slam") and self.cost_of("slam") <= self.mana:
        return ["cast", "slam", enemy]
        
    return ["attack", "melee", enemy]

Ejecutamos la misión, y llegan los problemas

Este código tiene un gran problema, que vamos a ver a continuación.

Si ejecutamos la misión, veremos que en la ventana de Actividad salen unas trazas similares a las siguientes:

¿Qué esperábamos?

Esperábamos ver cómo nuestro personaje se acercaba al adversario, le atacaba con Lightning, a continuación con Slam mientras el otro hechizo se enfriaba, y a continuación ataques cuerpo a cuerpo hasta tener disponible de nuevo Lightning.

Y en cambio, ¿qué ha sucedido?

Si nos fijamos en las trazas de Actividad, nuestro personaje, una vez se ha acercado al adversario, le ha lanzado un Lightning, pero después, en vez de atacar con Slam, nuestro personaje ha recibido una penalización al tratar de utilizar una acción demasiado lejos.

El problema y la explicación

El problema viene dado en la línea:

if self.distance_to(enemy) <= self.max_distance_of("lightning"):

y que el resto de programación de nuestro personaje está dentro de este condicional.

Así pues, el problema es que la comprobación de la distancia de un hechizo ha condicionado al resto de ataques.

Cuando se cumple la condición anterior por primera vez, el adversario está a 271 (o cercano a este número, según se esté moviendo), así que entra en el condicional, y Python comprueba la siguiente línea:

    if self.is_ready("lightning") and self.cost_of("lightning") <= self.mana:

Como ha empezado la lucha, Lightning está disponible, y nuestro personaje tiene maná suficiente para invocar el hechizo, así que el condicional se cumple, y lanza Lightning:

        return ["cast", "lightning", enemy]

Una vez lanzado con éxito el hechizo Lightning, el juego le vuelve a preguntar a nuestro personaje qué quiere hacer, así que se vuelve a ejecutar nuestro código, y llegamos al problema:

if self.distance_to(enemy) <= self.max_distance_of("lightning"):

En este punto, el adversario está a distancia de Lightning (<= 271), pero todavía muy lejos para el hechizo Slam (<= 45). Pero el condicional se cumple, así que entra:

    if self.is_ready("lightning") and self.cost_of("lightning") <= self.mana:
        return ["cast", "lightning", enemy]
    
    if self.is_ready("slam") and self.cost_of("slam") <= self.mana:
        return ["cast", "slam", enemy]

El primer condicional (el que controla el lanzamiento de Lightning) no se cumple, ya que este hechizo no está disponible (self.is_ready("lightning") devuelve False), pero el siguiente condicional (el que controla Slam) si se cumple, ya que el hechizo está disponible... ¡aunque el adversario no está a distancia de Slam!.

Y entonces es cuando el juego se queja. Nos indica que hemos tratado de lanzar Slam, pero no estábamos a la distancia adecuada.

La solución

Una posible solución es medir las distancias de los hechizos conjuntamente con el resto de comprobaciones, hechizo a hechizo.

Por ejemplo, en el código anterior, bastaría con hacer lo siguiente:

# En la variable enemy almacenamos al adversario

# Nos aproximamos al adversario

# Si estamos a distancia de lucha, elaboramos la estrategia de ataques
if self.distance_to(enemy) <= self.max_distance_of("lightning") and \
    self.is_ready("lightning") and self.cost_of("lightning") <= self.mana:
    return ["cast", "lightning", enemy]
    
if self.distance_to(enemy) <= self.max_distance_of("slam") and \
    self.is_ready("slam") and self.cost_of("slam") <= self.mana:
    return ["cast", "slam", enemy]
        
if self.distance_to(enemy) <= 30:
    return ["attack", "melee", enemy]

Aunque no es el código más cómodo, es un buen ejemplo para que veamos que cada intento de utilización de un hechizo cuenta también con su comprobación de distancia. Así pues, sólo lanzaremos Lightnig si se cumple que la distancia es la adecuada, está disponible, y nuestro personaje tiene maná suficiente. Lo mismo para Slam.

Conclusiones

  • Tener siempre en mente que cada hechizo tiene sus particularidades, y eso afecta a la programación.
  • Cuidado a la hora de agrupar código dentro de condicionales, porque la lógica de estos condicionales afecta al bloque de código de su interior.
  • Utilizad la ventana de Actividad siempre.
  • Utilizad self.debug cuando tengáis dudas sobre datos numéricos de la partida.