
De Scripts Básicos a Algoritmos Avanzados: Mid-Game en The Farmer Was Replaced
1. Optimización del Movimiento: Geometría Toroidal
Cuando le pides al dron que vaya a una posición (X, Y), el camino más rápido no siempre es caminar en línea recta. Dado que el mapa de The Farmer Was Replaced tiene geometría toroidal (si caminas hacia el Oeste en X=0, apareces en el extremo Este), la función debe calcular si es más corto cruzar el borde o avanzar directo.
La Matemática detrás de go_to(x, y)
Si estás en X = 0 , Y = 0 y quieres ir a X = 13 , Y=1 en una grilla de 10x10:
- Camino directo: 0 -> 1 -> 2 -> … -> 14 (14 pasos al Este).
- Camino directo: 0 -> 1 (1 pasos al Norte).
- Total de pasos: 15
- Camino toroidal: 0 -> 1 -> 2 -> … -> 4 (4 solo paso al Oeste).
- Camino toroidal: 0 -> 1 (1 pasos al Norte).
- Total de pasos: 5
Esto, reduce demasiado el tiempo entre distancias largas.
El operador módulo % size nos permite calcular la distancia real convirtiendo desplazamientos negativos en la ruta más corta alrededor del toroide.
def go_to(tx, ty):
# Sacamos el tamaño de nuestro espacio
size = get_world_size()
# Calculo para determinar camino en X
x = get_pos_x()
east = (tx - x) % size #Obtine valor absoluto de la distancia
west = (x - tx) % size #Obtine valor absoluto de la distancia
#Determina cual es el camino mas corto y ejecuta
if east <= west:
for _ in range(east):
move(East)
else:
for _ in range(west):
move(West)
# Calculo para determinar camino en Y
y = get_pos_y()
north = (ty - y) % size #Obtine valor absoluto de la distancia
south = (y - ty) % size #Obtine valor absoluto de la distancia
#Determina cual es el camino mas corto y ejecuta
if north <= south:
for _ in range(north):
move(North)
else:
for _ in range(south):
move(South)
2. Helpers y Funciones Utilitarias Defensivas
Para evitar que las funciones principales fallen por falta de tecnología o insumos, encapsulamos las acciones básicas con comprobaciones de seguridad.
def get_full_grid_coords():
size = get_world_size()
coords = []
for x in range(size):
for y in range(size):
coords.append((x, y))
return coords
def safe_plant_type(entity_type):
safe_plant()
plant(entity_type)
def safe_plant():
if can_harvest():
harvest()
def safe_water():
if num_unlocked(Unlocks.Watering) == 0:
return
if num_items(Items.Water) > 0 and get_water() < 0.7:
use_item(Items.Water)
def ensure_grassland():
if get_ground_type() != Grounds.Grassland:
till()
def ensure_soil():
if get_ground_type() != Grounds.Soil:
till()
def safe_fertilize():
if num_unlocked(Unlocks.Fertilizer) > 0 and num_items(Items.Fertilizer) > 0:
use_item(Items.Fertilizer)
def safe_cure():
if num_items(Items.Weird_Substance) > 0:
use_item(Items.Weird_Substance)
3. Módulos de Farmeo Especializados
Cada cultivo del mid-game introduce un problema algorítmico único que debemos resolver antes de pasar al orquestador principal.
A. Girasoles: Clasificación en Memoria (Bucket Sort / Counting Sort)
Cosechar el girasol con la mayor cantidad de pétalos en el campo otorga un multiplicador de hasta 8x en la recolección de Power. Sin embargo, los girasoles nacen con un número variable de pétalos (entre 7 y 15) distribuidos de forma aleatoria por la grilla.
El Dilema de Eficiencia: O(N^3) vs O(N^2)
-
Enfoque Ingenuo (O(N^3)): Buscar en cada paso el girasol con más pétalos escaneando todo el mapa. En una grilla de 10x10 (100 casillas), esto implica recorrer 100 casillas para encontrar el de 15 pétalos, cosecharlo, y volver a recorrer 100 casillas para buscar el siguiente. Esto genera un desperdicio masivo de ticks de ejecución.
-
Solución con Bucket Sort (O(N^2)): Aplicamos una variante de Bucket Sort (o Counting Sort). Hacemos una sola pasada de reconocimiento para escanear el terreno y clasificar las coordenadas en “cubos” o contenedores según su valor.
¿Cómo funciona la teoría del Bucket Sort en el código?
-
Creación de Cubos (Buckets): Inicializamos un diccionario donde cada clave es el número de pétalos posible (de 15 a 7) y su valor es una lista vacía de coordenadas:
petals_map = {15: [], 14: [], ..., 7: []}. -
Distribución en Memoria (Scatter): El dron recorre la grilla una sola vez (100 pasos). Mide cada girasol con
measure()y guarda tuplas(x, y)en el cubo correspondiente a su número de pétalos. -
Cosecha Directa (Gather): Iteramos sobre las claves del diccionario en orden descendente (15 -> 7). El dron se desplaza directo a las coordenadas guardadas usando
go_to(x, y)y cosecha con la garantía de mantener el multiplicador al máximo sin realizar un solo escaneo extra.
def farm_sunflower(coords):
# Se obtiene costos
cost = get_cost(Entities.Sunflower)
cost_carrot = cost[Items.Carrot]
# Realiza un ciclo de plantacion
for x, y in coords:
if num_items(Items.Carrot) < cost_carrot:
return False
go_to(x, y)
ensure_soil()
safe_water()
safe_plant_type(Entities.Sunflower)
# Realiza el mapeo de los petalos mediante un diccionario
petals_map = {}
for p in range(15, 6, -1):
petals_map[p] = []
# Realiza el llenado de los tamaños
for x, y in coords:
go_to(x, y)
p_count = measure()
if p_count != None and p_count in petals_map:
petals_map[p_count].append((x, y))
# Realiza la cosecha en orden
for p_count in range(15, 6, -1):
target_coords = petals_map[p_count]
for x, y in target_coords:
go_to(x, y)
if can_harvest():
harvest()
return True
B. Cactus: Ordenamiento de Matrices (Bubble Sort Bidimensional)
Cosechar un cactus en una grilla perfectamente ordenada desencadena un efecto dominó recursivo que recolecta todo el campo de golpe. La recompensa es exponencial: cosechar n cactus de forma simultánea otorga n^2 Items.Cactus.
La Regla del Orden Estricto
Para que la reacción en cadena ocurra, cada cactus debe cumplir con una condición de vecindad basada en su tamaño (medido con measure(), de 0 a 9):
- Norte (N) y Este (E): Deben ser vecinos de tamaño mayor o igual (>=).
- Sur (S) y Oeste (W): Deben ser vecinos de tamaño menor o igual (<=).
Esta regla implica que la grilla debe quedar ordenada de forma ascendente de Oeste a Este en las filas, y de Sur a Norte en las columnas.
Aplicando Bubble Sort en 2D con swap()
A diferencia de una lista convencional en memoria donde reordenamos índices, aquí el dron debe desplazarse físicamente e intercambiar las plantas en el mapa usando swap(North) o swap(East).
El algoritmo se divide en dos fases iterativas de Bubble Sort (O(N^3) en llamadas de movimiento):
- Ordenamiento Horizontal (Filas): Iteramos sobre cada fila Y. Comparamos el cactus actual con su vecino del Este mediante
measure() > measure(East). Si está desordenado, ejecutamosswap(East)y avanzamos. - Ordenamiento Vertical (Columnas): Iteramos sobre cada columna X. Comparamos la casilla actual con su vecina del Norte mediante
measure() > measure(North). Si es mayor, ejecutamosswap(North).
Al finalizar ambas pasadas, la matriz entera queda perfectamente ordenada. Bastará con posicionar al dron en la esquina (0,0) y ejecutar un solo harvest() para recoger la cosecha masiva de n^2.
def farm_cactus(coords):
size = get_world_size()
cost = get_cost(Entities.Cactus)
cost_carrot = cost[Items.Pumpkin]
# 1. FASE DE SIEMBRA
for x, y in coords:
if num_items(Items.Carrot) < cost_carrot:
return False
go_to(x, y)
ensure_soil()
safe_water()
safe_plant_type(Entities.Cactus)
# 2. ESPERA DE CRECIMIENTO
for x, y in coords:
go_to(x, y)
while not can_harvest():
safe_water()
# 3. ORDENAMIENTO BUBBLE SORT DE MATRIZ
# Ordenar Filas (Eje X: De Oeste a Este)
for y in range(size):
for i in range(size):
for x in range(size - 1):
go_to(x, y)
if measure() > measure(East):
swap(East)
# Ordenar Columnas (Eje Y: De Sur a Norte)
for x in range(size):
for i in range(size):
for y in range(size - 1):
go_to(x, y)
if measure() > measure(North):
swap(North)
# 4. COSECHA EN CADENA (Recompensa n^2)
go_to(0, 0)
harvest()
return True
C. Dinosaurio: Ciclo Hamiltoniano (Snake Solver)
Equipar el Sombrero de Dinosaurio transformará la dinámica del juego por completo, convirtiendo la grilla en una partida del clásico Snake. Cada manzana recolectada hace crecer la cola del dron en 1 segmento y genera una nueva manzana en una posición aleatoria. Al desequipar el sombrero, se cosecha la cola obteniendo n^2 Items.Bone (donde n es la longitud alcanzada).
El Problema de las Colisiones y la Topología Rígida
Bajo esta mecánica, el juego impone dos restricciones severas:
- Desactivación del Toroide: No se puede cruzar los bordes del mapa.
- Colisiones Fatales: Si el dron intenta avanzar hacia una casilla ocupada por su propio cuerpo o cola, la función
move()falla retornandoFalse.
Si intentamos ir directamente tras la manzana mediante búsquedas voraces (Greedy), la cola terminará encerrando al dron y provocando una colisión inevitable.
Garantía Topológica: El Ciclo Hamiltoniano
Un Ciclo Hamiltoniano es un camino cerrado dentro de un grafo que visita cada vértice (casilla) exactamente una vez y regresa al punto de partida. Al obligar al dron a seguir un ciclo hamiltoniano cerrado en loop infinito, garantizamos matemáticamente que nunca se estrellará, ya que la cabeza únicamente avanza hacia la casilla que la punta de la cola acaba de liberar.
Estructura del Recorrido en la Granja
Para construir este ciclo cerrado de forma determinista en una grilla NxN, dividimos el mapa en dos zonas:
- Autopista de Retorno Reservada (Columna X = 0): El dron asciende en línea recta desde (0,0) hasta (0, N-1) exclusivamente para regresar del Sur al Norte.
- Serpenteo de Limpieza (X<1): Descendemos serpenteando en Zig-Zag (Este-Oeste / Norte-Sur) por el resto del mapa hasta volver a la base en (1,0) y reconectar con (0,0).
Cuando el cuerpo llena completamente el espacio y ya no existen casillas libres para avanzar, el dron detecta el bloqueo final, rompe el bucle de forma segura y desequipa el sombrero para cobrar la recompensa masiva en huesos.
def farm_dinosaur_bones(coords):
clear()
size = get_world_size()
cost = get_cost(Entities.Apple)
cost_cactus = cost[Items.Cactus]
if num_items(Items.Cactus) < cost_cactus:
return False
# 1. Posicionar al dron en (0,0) antes de iniciar
go_to(0, 0)
# 2. Equipar el sombrero para arrancar el minijuego
change_hat(Hats.Dinosaur_Hat)
# 3. Bucle en Ciclo Hamiltoniano
game_running = True
while game_running:
# A. Subir por la columna 0 hasta el extremo Norte
while get_pos_y() < size - 1:
if not move(North):
game_running = False
break
if not game_running:
break
# B. Moverse a la derecha para iniciar el patrón en zigzag descendente/ascendente
if not move(East):
break
# C. Recorrer el resto de la grilla (Columnas 1 a N-1)
# El dron desciende por x=1..N-1 y serpentea
for y in range(size - 1, -1, -1):
if y % 2 == (size - 1) % 2:
# Ir hacia la derecha (East) hasta el borde
while get_pos_x() < size - 1:
if not move(East):
game_running = False
break
else:
# Ir hacia la izquierda (West) hasta x = 1 (sin tocar x = 0)
while get_pos_x() > 1:
if not move(West):
game_running = False
break
if not game_running:
break
# Bajar una fila si no estamos en la base (y = 0)
if y > 0:
if not move(South):
game_running = False
break
if not game_running:
break
# D. Regresar a la columna 0 para cerrar el ciclo
if not move(West):
break
# Verificar si nos quedamos sin cactus para la siguiente manzana
if num_items(Items.Cactus) < cost_cactus:
break
# 4. Desequipar para cosechar los huesos (Items.Bone = n^2)
change_hat(Hats.Straw_Hat)
return True
D. Laberintos: Búsqueda en Grafos (DFS con Backtracking)
Fertilizar un arbusto con suficiente Items.Weird_Substance transformará la granja en un laberinto de setos (Entities.Hedge). Escondido en alguna coordenada aleatoria se encuentra un tesoro de oro (Entities.Treasure) cuya recompensa equivale al área total del mapa.
El Desafío de la Navegación a Ciegas
A diferencia de los cultivos convencionales, en el laberinto el dron no puede sobrevolar las paredes: la función move() retornará False si intenta atravesar un seto. Para encontrar el tesoro sin destruir el laberinto por error (lo cual ocurre si ejecutamos harvest() en una casilla equivocada), debemos tratar la grilla como un grafo no dirigido y aplicar un algoritmo de exploración sistemática.
La Teoría: Depth-First Search (DFS) y Retroceso (Backtracking)
El algoritmo de Búsqueda en Profundidad (DFS) explora un camino hacia adelante tanto como sea posible antes de retroceder. Representamos las casillas del laberinto como nodos y los pasillos abiertos como aristas.
- Memoria de Nodos Visitados (
set()): Mantenemos un conjunto dinámicovisitedcon las tuplas(x, y)por las que el dron ya transitó. Esto evita que el dron entre en bucles infinitos oscilando entre dos casillas contiguas. - Exploración Recursiva: En cada casilla, el dron evalúa las 4 direcciones (
North,East,South,West). Sican_move(dir)es verdadero y la casilla destino no está envisited, el dron se desplaza e invoca recursivamente la función de búsqueda. - Mecánica de Backtracking (Regreso sobre sus Pasos): Si el dron llega a un callejón sin salida (todas las direcciones adyacentes están bloqueadas o ya fueron visitadas), la llamada recursiva finaliza y el dron ejecuta
move(reverse_dir)para regresar exactamente una casilla atrás y probar un camino alternativo.
En cuanto get_entity_type() == Entities.Treasure se vuelve verdadero, la llamada retorna True, la pila recursiva se desenreda al instante y el dron reclama el botín sin dar un solo paso innecesario.
def solve_maze_dfs(visited, directions, rev_directions):
if get_entity_type() == Entities.Treasure:
harvest()
return True
curr_pos = (get_pos_x(), get_pos_y())
visited.add(curr_pos)
for i in range(4):
direction = directions[i]
reverse_dir = rev_directions[i]
if can_move(direction):
# Calcular la posición adyacente
nx, ny = curr_pos[0], curr_pos[1]
if direction == North:
ny += 1
elif direction == South:
ny -= 1
elif direction == East:
nx += 1
elif direction == West:
nx -= 1
# Si la casilla no ha sido visitada, avanzamos
if (nx, ny) not in visited:
move(direction)
if solve_maze_dfs(visited, directions, rev_directions):
return True
# Backtracking: regresar si el camino fue un callejón sin salida
move(reverse_dir)
return False
def farm_maze(coords):
# 1. Calcular el costo exacto de sustancia rara
substance_needed = get_world_size() * (2 ** (num_unlocked(Unlocks.Mazes) - 1))
if num_items(Items.Weird_Substance) < substance_needed:
return False
# 2. Plantar el arbusto inicial e invocar el laberinto
go_to(0, 0)
if get_ground_type() != Grounds.Grassland:
till() # Asegurar terreno si es necesario
plant(Entities.Bush)
use_item(Items.Weird_Substance, substance_needed)
# 3. Estructuras para la resolución DFS
directions = [North, East, South, West]
rev_directions = [South, West, North, East]
visited = set()
# 4. Resolver y Cosechar Tesoro
solve_maze_dfs(visited, directions, rev_directions)
return True
4. Orquestación Inteligente mediante Histeresis
Conforme escalamos hacia múltiples cultivos, surge un problema clásico de los sistemas de control: el Flapping o conmutación crítica.
El Problema del Limite Fijo (Sin Histeresis)
Si programamos el bot con un umbral único (por ejemplo: “Si Hay < 100,000, junta heno”), el dron entrará en un bucle ineficiente. Al llegar a 99,999, se desplazará a sembrar heno; cosechará un par de casillas, llegará a 100,001 y cambiará a otra tarea. Acto seguido, gastará 2 unidades de heno para sembrar zanahorias, el stock caerá a 99,999 y el ciclo de cambio brusco de contexto se repetirá infinitamente.
Este comportamiento destruye la tasa de cosecha por segundo (TPS) debido al costo de movimiento entre sectores.
La Solución: Control por Banda Min/Max (Histeresis)
Tomado del diseño de termostatos y sistemas electrónicos, aplicamos un Control por Histeresis utilizando banderas de estado booleanas que conservan memoria entre iteraciones del orquestador.
- Gatillo de Activación (Min / Low): El bot ignora la recolección de un recurso mientras se mantenga sobre el nivel crítico (
STOCK_MIN_BASE = 100,000). Solo activa la banderafarming_x = Truecuando cae por debajo de este límite. - Gatillo de Desactivación (Max / High): Una vez encendida la bandera, la tarea no se interrumpe hasta que el inventario se llena por completo hasta el techo máximo (
STOCK_MAX_BASE = 120,000).
Evaluación de Prioridades en Cascadas
Al combinar la histeresis con una máquina de estados, el smart_farm() evalúa la salud de los almacenes en orden de dependencia en cada iteración del bucle principal:
- Prioridad 1 (Power): Mantener la energía activa (2,000+) para sostener la velocidad 2x del dron.
- Prioridad 2 y 3 (Insumos Base): Asegurar que
HayyWoodno caigan del colchón de 100k. - Prioridad 4 (Moneda Intermedia): Mantener el flujo de
Carrotspara pagar semillas avanzadas. - Prioridad 5 (Oro / Minijuegos): Ejecutar laberintos o minijuegos mientras los recursos base se mantengan en zona segura.
- Prioridad por Defecto (Producción de Alto Valor): Farmeo continuo de
Pumpkinsgigantes o cultivos avanzados.
# --- UMBRALES DE HISTERESIS ---
STOCK_MIN_BASE = 100000
STOCK_MAX_BASE = 120000
POWER_MIN = 2000
POWER_MAX = 10000
GOLD_MIN = 10000
GOLD_MAX = 50000
# Banderas de estado
farming_grass = False
farming_wood = False
farming_power = False
farming_carrots = False
farming_gold = False
coords = get_full_grid_coords()
def smart_farm():
global farming_grass
global farming_wood
global farming_power
global farming_carrots
global farming_gold
hay = num_items(Items.Hay)
wood = num_items(Items.Wood)
carrots = num_items(Items.Carrot)
power = num_items(Items.Power)
gold = num_items(Items.Gold)
# Evaluadores de Histeresis
if hay < STOCK_MIN_BASE:
farming_grass = True
elif hay >= STOCK_MAX_BASE:
farming_grass = False
if wood < STOCK_MIN_BASE:
farming_wood = True
elif wood >= STOCK_MAX_BASE:
farming_wood = False
if power < POWER_MIN:
farming_power = True
elif power >= POWER_MAX:
farming_power = False
if carrots < STOCK_MIN_BASE:
farming_carrots = True
elif carrots >= STOCK_MAX_BASE:
farming_carrots = False
if gold < GOLD_MIN:
farming_gold = True
elif gold >= GOLD_MAX:
farming_gold = False
# PRIORIDADES
if farming_power:
if carrots >= 100:
farm_sunflower(coords)
return
if farming_grass:
farm_grass(coords)
return
if farming_wood:
farm_wood(coords)
return
if farming_carrots:
farm_carrots(coords)
return
# Prioridad: Recolectar Oro mediante Laberintos
if farming_gold:
farm_maze(coords)
return
# Producción Avanzada
farm_giant_pumpkins(coords)
# Una vez finalizada la ronda paralela, continúa el flujo del dron principal
while True:
smart_farm()
Conclusión
Con esta arquitectura modular, nuestra granja es capaz de gestionar recursos complejos, autocurarse, resolver minijuegos y mantener un flujo constante de producción sin atascarse.
En la Parte 3 (Late-Game / End-Game) exploraremos el procesamiento distribuido con múltiples drones en paralelo, optimización multihilo y sincronización sin condiciones de carrera.