
Resumen
PokeIA es un agente autónomo para el PTCG AI Battle Challenge de Kaggle, donde un mazo de 60 cartas juega contra otro agente en simulaciones discretas con acciones legales. El objetivo es maximizar la tasa de victorias en el leaderboard de la plataforma. El agente combina una política heurística tuneada a mano con un crítico aprendido por datos y un ciclo de búsqueda de composición de mazo basado en round-robin y algoritmos genéticos.
Este proyecto documenta la arquitectura del agente, los datos utilizados para el entrenamiento, los resultados de la primera iteración y los próximos pasos planeados.
Arquitectura
El agente vive en un único proceso Python que recibe observaciones del simulador y devuelve la acción legal elegida. La observación llega como un JSON con el estado del campo, las cartas en mano, los premios restantes y una lista de opciones legales. La política combina tres componentes:
-
Puntuación heurística que asigna un entero a cada opción legal según reglas de la TCG (jugar Pokémon básico primero, atacar el Pokémon con menor HP del oponente, etc.). Esta puntuación es la línea base y aporta la mayor parte de la señal.
-
Modelo de valor (V(s) \in [0, 1]) entrenado como un random forest de YDF sobre 50k transiciones etiquetadas con el resultado del partido. Predice la probabilidad de ganar dado el estado. Se usa en la búsqueda hacia adelante para evaluar estados futuros.
-
Modelo crítico (C(s, a) \in [0, 1]) que re-pesa las opciones en función del estado y de la acción específica. Entrenado con pares contrastivos: la acción elegida por el jugador se etiqueta como 1, otras acciones en el mismo estado como 0. Así el modelo aprende qué acciones son buenas relativamente.
Los tres componentes se combinan en una suma ponderada: (\text{score}(a) = h(a) + \alpha \cdot C(s, a) + \beta \cdot \hat V(s')), donde (h(a)) es la heurística, (C) el crítico y (\hat V) el valor del estado siguiente.
Datos
Entrenamos con tres fuentes:
- Selfplay local (~512 archivos, ~50k transiciones): partidas entre nuestra política contra sí misma, capturadas con
agent/selfplay/collect.py. - Replays propios de Kaggle (~18 archivos, ~553 transiciones): los episodios que nuestro agente jugó en la plataforma.
- Replays de la leaderboard pública (~196 archivos, ~74k transiciones): los episodios de los 195 mejores agentes del leaderboard, parseados con
agent/selfplay/hermes_to_transitions.py.
Cada transición guarda el estado del juego, las opciones legales, la acción tomada y el resultado del partido desde la perspectiva del jugador. El modelo de valor se entrena con la etiqueta del resultado (1 si ganó, 0 si perdió); el crítico con la acción elegida como positiva y un muestreo de las alternativas como negativas.
Resultados hasta ahora
Pusimos en producción la primera versión del agente. Los scores en Kaggle han sido:
- Heurístico puro: 628.8 (en el momento de la primera submission, leaderboard con muchos bots básicos).
- Heurístico + crítico (sin contraste): 420.9.
- Heurístico + crítico (con pares contrastivos): 460.9 a 500.0 con ajustes de hiperparámetros.
El crítico aporta señal útil cuando la heurística falla, pero su peso (\alpha) debe ser bajo: valores altos hacen que el agente tome decisiones inconsistentes con la lógica del juego. El sweet-spot encontrado es (\alpha = 0.3).
En el frente de composición de mazo, iteramos 50 variantes del mazo activo contra 7 decks de referencia y elegimos las tres mejores. La mejor de ellas (la que llamó iterated_top1) logra 76.7% de victorias contra los 7 mazos de referencia. Esta variante se convirtió en la base de la submission submission_clean.tar.gz.
Análisis cuantitativo
Usamos dos corpus para medir la calidad del agente:
-
Partidas propias (selfplay): 50k transiciones con resultado conocido. Sirven para entrenar y validar el modelo de valor.
-
Replays de la leaderboard (Hermes): 74k transiciones de 195 agentes distintos. El mazo del agente contrario no siempre se conoce (el simulador no lo expone), así que reconstruimos el mazo probable a partir de las cartas vistas durante el partido. La reconstrucción se valida con el simulador: descartamos los mazos que no son legales.
Con los 431 mazos válidos de Limitless (NAIC 2026 y eventos similares) más los 103 reconstruidos de Hermes, tenemos un pool de 534 oponentes diversos. Cada variante de nuestro mazo se prueba contra 20 oponentes aleatorios, dos veces cada uno (alternando quién juega primero), para un total de 40 partidas por variante. Esto da una señal robusta de la fuerza relativa del mazo.
Próximos pasos
Tres líneas de trabajo abiertas:
-
Re-entrenar el crítico con más datos: los ~150k transiciones disponibles dan un modelo más estable, con un mejor trade-off entre varianza y sobreajuste.
-
Búsqueda evolutiva de mazos: 50 generaciones de mutaciones genéticas sobre los 50 mazos más fuertes del pool. Las mutaciones cambian 1-3 cartas a la vez. Cada generación toma ~20 minutos con 8 procesos paralelos.
-
RL sobre la política directamente: hasta ahora el crítico solo re-pesa la heurística, pero podría entrenarse una política neuronal end-to-end con REINFORCE o PPO. El cuello de botella actual es el simulador mismo, que es CPU-only y de código cerrado.
Reproducibilidad
Los artefactos viven en agent/ y los datos en data/. Los modelos entrenados están en agent/models/:
value_model.ydf: random forest de 200 árboles, 64 features, ~16 MB.critical_model.ydf: random forest de 300 árboles, 96 features, ~7 MB.
Para empaquetar y enviar a Kaggle:
mkdir -p /tmp/sub && rm -rf /tmp/sub/*
cp agent/main_ml.py /tmp/sub/main.py
cp agent/deck.csv agent/features.py agent/models/*.ydf /tmp/sub/
cp -r agent/cg /tmp/sub/
find /tmp/sub -name __pycache__ -exec rm -rf {} +
tar -czf submission.tar.gz -C /tmp/sub .
El límite diario de submissions de Kaggle es cinco, así que iteramos localmente primero y solo subimos cuando tenemos una hipótesis concreta que validar.