El desafío es escribir un solucionador para el clásico juego de lápiz y papel Dots and Boxes . Su código debe tomar dos enteros m
y n
como entrada que especifica el tamaño de la placa.
Comenzando con una cuadrícula de puntos vacía, los jugadores se turnan y agregan una sola línea horizontal o vertical entre dos puntos adyacentes no unidos. Un jugador que completa el cuarto lado de un cuadro de 1 × 1 gana un punto y toma otro turno. (Los puntos se registran típicamente colocando en el cuadro una marca de identificación del jugador, como una inicial). El juego termina cuando no se pueden colocar más líneas. El ganador del juego es el jugador con más puntos.
Puede suponer que o n = m
o n = m - 1
y m
es al menos 2.
El desafío es solve
el juego de puntos y cajas más grande posible en menos de un minuto. El tamaño de un juego es simple n*m
. La salida de su código debe serwin
, draw
o lose
cuál debería ser el resultado para el primer jugador, suponiendo que ambos jugadores jueguen de manera óptima.
Su código debe ser compilable / ejecutable en ubuntu usando herramientas fáciles de instalar y gratuitas. Informe su puntaje como el área más grande que puede resolver en su computadora en 1 minuto junto con el tiempo. Luego probaré el código en mi computadora y haré una tabla ordenada de entradas.
En el caso de un desempate, el ganador será el código más rápido en el tablero de mayor tamaño que pueda resolver en menos de un minuto.
Sería mejor si el código generado no solo ganara o perdiera, sino también el puntaje real. Esto hace que se verifique la corrección de la cordura.
Respuestas:
C99 - tablero 3x3 en 0.084s
Editar: modifiqué mi código e hice un análisis más profundo de los resultados.
Ediciones adicionales: poda adicional por simetrías. Esto hace 4 configuraciones de algoritmo: con o sin simetrías X con o sin poda alfa-beta
Ediciones más lejanas: lejanas agregó la memorización usando una tabla hash, finalmente logrando lo imposible: ¡resolver un tablero 3x3!
Características principales:
#define HASHTABLE_BITWIDTH
). Cuando este tamaño es mayor o igual que el número de paredes, garantiza que no haya colisiones y actualizaciones de O (1). Las tablas hash más pequeñas tendrán más colisiones y serán un poco más lentas.-DDEBUG
impresionesPosibles mejoras:
arregla una pequeña pérdida de memoriaarreglada en la primera ediciónpoda alfa / betaagregada en la 2da ediciónpodar simetríasagregadas en la tercera edición (tenga en cuenta que las simetrías no se manejan mediante la memorización, por lo que sigue siendo una optimización separada).memoriaagregada en la 4ta ediciónCódigo
Debido a la falta de organización, el número de archivos se ha ido de las manos. Todo el código se ha movido a este repositorio de Github . En la edición de la memorización, agregué un archivo MAKE y un script de prueba.
Resultados
Notas sobre la complejidad
Los enfoques de fuerza bruta a los puntos y cuadros explotan en complejidad muy rápidamente .
Considere un tablero con
R
filas yC
columnas. HayR*C
cuadrados,R*(C+1)
paredes verticales yC*(R+1)
paredes horizontales. Eso es un total deW = 2*R*C + R + C
.Debido a que Lembik nos pidió que resolvamos el juego con minimax, debemos atravesar las hojas del árbol del juego. Ignoremos la poda por ahora, porque lo que importa son órdenes de magnitud.
Hay
W
opciones para el primer movimiento. Para cada uno de ellos, el siguiente jugador puede jugar cualquiera de losW-1
muros restantes, etc. Eso nos da un espacio de búsqueda deSS = W * (W-1) * (W-2) * ... * 1
, oSS = W!
. Los factoriales son enormes, pero eso es solo el comienzo.SS
es el número de nodos hoja en el espacio de búsqueda. Más relevante para nuestro análisis es el número total de decisiones que tuvieron que tomarse (es decir, el número de ramasB
en el árbol). La primera capa de ramas tieneW
opciones. Para cada uno de ellos, el siguiente nivel tieneW-1
, etc.Veamos algunos tamaños de mesa pequeños:
Estas cifras se están volviendo ridículas. Al menos explican por qué el código de fuerza bruta parece colgar para siempre en una placa de 2x3. El espacio de búsqueda de una placa de 2x3 es 742560 veces mayor que 2x2 . Si 2x2 tarda 20 segundos en completarse, una extrapolación conservadora predice más de 100 días de tiempo de ejecución para 2x3. Claramente necesitamos podar.
Análisis de poda
Comencé agregando podas muy simples usando el algoritmo alfa-beta. Básicamente, deja de buscar si un oponente ideal nunca le daría sus oportunidades actuales. "Oye, mira, ¡gano mucho si mi oponente me permite obtener todas las casillas!", Pensó AI, nunca.
editar También he agregado poda basada en tablas simétricas.
No uso un enfoque de memorización, por si algún día agrego memorización y quiero mantener ese análisis por separado. En cambio,funciona así: la mayoría de las líneas tienen un "par simétrico" en otro lugar de la cuadrícula. Hay hasta 7 simetrías (horizontal, vertical, rotación 180, rotación 90, rotación 270, diagonal y la otra diagonal). Los 7 se aplican a tableros cuadrados, pero los últimos 4 no se aplican a tableros no cuadrados. Cada pared tiene un puntero a su "par" para cada una de estas simetrías. Si, al entrar en un turno, el tablero es horizontalmente simétrico, entonces solo se necesita jugar uno de cada par horizontal .editar editar ¡Memorización! Cada muro obtiene una identificación única, que convenientemente configuré para ser un bit indicador; la enésima pared tiene la identificación
1 << n
. El hash de un tablero, entonces, es solo el OR de todas las paredes jugadas. Esto se actualiza en cada rama en el tiempo O (1). El tamaño de la tabla hash se establece en a#define
. Todas las pruebas se ejecutaron con tamaño 2 ^ 12, porque ¿por qué no? Cuando hay más paredes que bits que indexan la tabla hash (12 bits en este caso), los 12 menos significativos se enmascaran y se usan como índice. Las colisiones se manejan con una lista vinculada en cada índice de tabla hash. El siguiente cuadro es mi análisis rápido y sucio de cómo el tamaño de la tabla hash afecta el rendimiento. En una computadora con RAM infinita, siempre establecemos el tamaño de la mesa en el número de paredes. Una tabla de 3x4 tendría una tabla hash de 2 ^ 31 de largo. Por desgracia no tenemos ese lujo.Ok, volvamos a la poda. Al detener la búsqueda en lo alto del árbol, podemos ahorrar mucho tiempo al no bajar a las hojas. El 'Factor de poda' es la fracción de todas las ramas posibles que tuvimos que visitar. La fuerza bruta tiene un factor de poda de 1. Cuanto más pequeño es, mejor.
fuente
rows columns
especifican el tamaño de la placaPython - 2x2 en 29s
Publicaciones cruzadas de rompecabezas . No está especialmente optimizado, pero puede ser un punto de partida útil para otros participantes.
fuente
Javascript: placa 1x2 en 20 ms
Demostración en línea disponible aquí (advertencia: muy lenta si es mayor que 1x2 con profundidad de búsqueda completa ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html
Fue desarrollado para los criterios de victoria originales (código golf) y no para velocidad.
Probado en google chrome v35 en windows 7.
fuente