Para aplicar con éxito min / max a un juego de estrategia por turnos, debes aplicar correctamente todas las técnicas de ajedrez disponibles ...
Función de evaluación
Incluso los motores de ajedrez tienen una fuerza muy mala, si sus funciones de evaluación son malas. La versión más simple de una función de evaluación es: 1 = juego ganado por blanco, -1 = juego ganado por negro, 0 = todos los demás casos; Pero, esto te daría un muy mal desempeño. ¡Lo mismo sucede con tu juego por turnos! Si desea usar min / max (con poda alfa / beta y otras cosas) como en el ajedrez, ¡también debe implementar una función de evaluación razonable! De lo contrario, no puede comparar el rendimiento de esos algoritmos cuando se aplica a su juego de estrategia al caso que se aplica al ajedrez.
Lo que hacen las funciones de evaluación de los motores de ajedrez es evaluar cosas como:
- ¿Qué tan bien es la posición de una pieza en el tablero?
- ¿Cuántas veces se ataca una pieza?
- ¿Cuántas veces está protegida la pieza?
- ¿Qué tan bien puede cada pieza "moverse" libremente en el tablero? (o: ¿Cuántos mosaicos "controla")?
Esas partes de la función de evaluación primero deben "traducirse" a su juego:
- Posición de la pieza: ¿Está, por ejemplo, en una colina que extiende su campo de tiro?
- Atacado: ¿Cuánto está en peligro cada pieza? (por ejemplo, la suma de los valores de ataque de las unidades capaces de atacar a una unidad especial multiplicada por alguna probabilidad de ser atacada por ella; la probabilidad aumenta, si la unidad ya está dañada; disminuye si muchas otras unidades están dentro del alcance de la unidad atacante)
- Ataque propio: ¿Cuántas unidades puede ser atacada por esta unidad?
- Protección: ¿Cuántas piezas propias hay al lado (para ayudar)? Tal vez una unidad no pueda atacar unidades a una distancia mínima y es preferible protegerla por unidad que tiene la posibilidad de atacar a las unidades cercanas.
- Movilidad: ¿Qué tan móvil es su unidad? (¿puede huir?)
Las diferentes clasificaciones deben resumirse mediante la función de ponderación (factor_a * rating_a + factor_b * ranting_b + ...) para todas las unidades ...
En los juegos de estrategia también se deben tener en cuenta los recursos (oro, madera, ...) que quedan.
Si su función de evaluación es lo suficientemente buena, no necesita buscar realmente "profundamente" en el árbol para la mayoría de los casos. Por lo tanto, probablemente solo necesite observar más de cerca las 3 o 10 opciones más prometedoras. Ver el próximo capítulo ...
Posibles movimientos en cada posición
Lo más problemático de usar min / max para juegos de estrategia es que puedes comandar múltiples unidades en un turno, mientras que en el ajedrez solo se te permite comandar una unidad (excepto el enroque, pero esta es una combinación de movimientos claramente definida). Esto causa 5 ^ N movimientos posibles para N unidades para cada "posición" (término de ajedrez), si solo decidiera entre "moverse al norte, sur, oeste, este O parar" para cada unidad. Puede resolver esto dividiendo el comando complejo en los comandos de bajo nivel: por ejemplo, elija la acción para la unidad A, profundice y decida la unidad B ... decida la unidad N ... y luego termine este turno. ¡Pero esto solo no cambia la complejidad! Debe optimizar el orden en que las acciones se asignan a las unidades (por ejemplo, primera unidad B, C, D y luego la unidad A). Puede registrar el impacto de la decisión para cada unidad durante el último cálculo y luego ordenar por importancia. De esta forma, la poda alfa-beta se puede utilizar para eliminar cualquier combinación incorrecta del árbol de búsqueda muy pronto. La prioridad más alta siempre debe ser "no hacer nada más y finalizar su turno" (poda de movimiento nulo) en cada iteración. De esta manera, puede "omitir" la asignación de la mayoría de las tareas a la mayoría de las unidades y dejar que continúen lo que hicieron antes. De esta manera, la búsqueda se profundizará rápidamente con solo echar un vistazo a las unidades "críticas" (por ejemplo, las que realmente están en combate en este momento). Asegúrate de mandar solo cada unidad una vez ... También puedes usar algo de aleatoriedad para asegurarte de que las unidades "importantes" también reciban un comando de vez en cuando. Especialmente, las unidades que terminan algún trabajo (p. Ej.
Tabla de profundización iterativa + almacenamiento en caché / hash
Luego, puede "profundizar de manera interactiva" para profundizar más y más hasta que se alcance un límite de tiempo. Por lo tanto, buscará más profundamente si hay menos unidades, y siempre tendrá algún "resultado" si deja de buscar una solución mejor. La profundización iterativa requeriría usar una tabla hash para almacenar en caché los resultados anteriores de las búsquedas. Esto también permite reutilizar algunos de los resultados de la búsqueda de los últimos turnos (la rama del árbol de búsqueda que cubre los comandos que realmente se ejecutaron en el último turno). Para implementar esto, necesita una función hash muy buena (eche un vistazo a la "clave zobrist"), que puede actualizarse de forma iterativa. Actualizar la clave hash significa que puede tomar la clave hash de la antigua "posición" y simplemente iniciar el cambio de posición (p. Ej. retire la unidad en la posición xy colóquela en la posición y). De esta manera, calcular la clave hash es rápido y no necesita procesar toda la situación de los tableros para calcularlo, solo para verificar si el hash contiene una entrada anterior para esta posición. En cierto modo, debe asegurarse de que no ocurran colisiones de hash.
Comportamiento no determinista
El comportamiento no determinista es un problema para las búsquedas mínimas / máximas. Esto significa que no está seguro si golpeará a un objetivo atacado (por ejemplo, la probabilidad es del 10%). Entonces no puedes simplemente planear que esto suceda. En ese caso, debe modificar el algoritmo y poner una capa de "probabilidad" en el medio. Es un poco como "es el turno de las probabilidades". Cada resultado independiente debe considerarse por separado. La evaluación a través de esta "capa" de profundidad debe ser muestreada (muestreo de Monte Carlo) y el resultado de la evaluación en profundidad debe ser ponderado por la probabilidad de ocurrencia. Los diferentes resultados de la capa de probabilidad deben considerarse como diferentes movimientos del oponente (pero en lugar de min / max se debe calcular el "promedio"). Por supuesto, esto aumentará la complejidad del árbol de búsqueda.
Resumen
Al aplicar todas esas técnicas (que son utilizadas por los motores de ajedrez actuales) a un juego determinista, seguramente también podrá lograr resultados razonables para un juego. Para juegos no deterministas, esto será probablemente más complicado, pero creo que aún es manejable.
Un buen recurso para explicar esas técnicas (para el ajedrez) es http://chessprogramming.wikispaces.com/
Incluso puede implementar algún tipo de aleatoriedad dirigida en búsquedas mínimas / máximas. En lugar de investigar deterministamente los mejores resultados primero en cada iteración, puede aleatorizar esto y dejar que su orden se decida por una distribución de probabilidad que se base en las evaluaciones actuales ...