Estoy creando una implementación simple de MiniMax en el lenguaje de programación funcional Elixir. Debido a que hay muchos juegos de conocimiento perfecto (tic tac toe, connect-four, damas, ajedrez, etc.), esta implementación podría ser un marco para crear IA de juegos para cualquiera de estos juegos.
Sin embargo, un problema al que me enfrento es cómo almacenar correctamente un estado de juego en un lenguaje funcional. Estos juegos tratan principalmente de tableros de juego bidimensionales, donde las siguientes operaciones son frecuentes:
- Leer el contenido de una ubicación específica del tablero
- Actualice el contenido de una ubicación específica del tablero (cuando devuelva una nueva posibilidad de movimiento)
- Considerando el contenido de una o más ubicaciones que están conectadas a la ubicación actual (es decir, las ubicaciones horizontales, verticales o diagonales siguientes o anteriores)
- Teniendo en cuenta los contenidos de múltiples ubicaciones conectadas en cualquier dirección.
- Considerando el contenido de archivos completos, rangos y diagonales.
- Girar o reflejar el tablero (para verificar las simetrías que proporcionan el mismo resultado que algo ya calculado).
La mayoría de los lenguajes funcionales usan Listas vinculadas y Tuplas como bloques de construcción básicos de estructuras de datos de elementos múltiples. Sin embargo, estos parecen muy mal hechos para el trabajo:
- Las listas vinculadas tienen un tiempo de búsqueda O (n) (lineal). Además, como no podemos "escanear y actualizar el tablero" de una sola vez, el uso de listas parece poco práctico.
- Las tuplas tienen un tiempo de búsqueda O (1) (constante). Sin embargo, representar el tablero como una tupla de tamaño fijo hace que sea muy difícil iterar sobre rangos, archivos, diagonales u otros tipos de cuadrados consecutivos. Además, tanto Elixir, y Haskell (que son los dos lenguajes funcionales que conozco) sintaxis falta para leer el n -ésimo elemento de una tupla. Esto haría imposible escribir una solución dinámica que funcione para tableros de un tamaño arbitrario.
Elixir tiene una estructura de datos de mapa incorporada (y Haskell Data.Map
) que permite el acceso O (log n) (logarítmico) a los elementos. En este momento uso un mapa, con x, y
tuplas que representan la posición como claves.
Esto 'funciona' pero se siente mal abusar de los mapas de esta manera, aunque no sé exactamente por qué. Estoy buscando una mejor manera de almacenar un tablero de juego bidimensional en un lenguaje de programación funcional.
Respuestas:
A
Map
es precisamente la estructura de datos base correcta aquí. No estoy seguro de por qué te pondría incómodo. Tiene buenos tiempos de búsqueda y actualización, es de tamaño dinámico y es muy fácil crear estructuras de datos derivados. Por ejemplo (en Haskell):La otra cosa que con frecuencia es difícil de comprender para los programadores cuando comienzan a programar con total inmutabilidad es que no es necesario atenerse a una sola estructura de datos. Por lo general, elige una estructura de datos como su "fuente de verdad", pero puede hacer tantos derivados como desee, incluso derivados de derivados, y sabe que permanecerán sincronizados todo el tiempo que los necesite.
Esto significa que puede usar una
Map
en el nivel superior, pasando después a unaLists
oArrays
para el análisis de la fila, a continuación,Arrays
indexada a la inversa para el análisis de la columna, a continuación, máscaras de bits para el análisis de patrones, a continuación,Strings
para su visualización. Los programas funcionales bien diseñados no pasan una sola estructura de datos. Son una serie de pasos que toman una estructura de datos y emiten una nueva que es adecuada para el siguiente paso.Siempre que pueda salir del otro lado con un movimiento en un formato que el nivel superior pueda entender, no necesita preocuparse de cuánto reestructura los datos intermedios. Es inmutable, por lo que es posible trazar un camino de regreso a la fuente de la verdad en el nivel superior.
fuente
Hice esto recientemente en F # y terminé usando una lista unidimensional (en F #, esa es una lista de enlace único). En la práctica, la velocidad del indexador de lista O (n) no es un cuello de botella para los tamaños de tableros utilizables por el hombre. Experimenté con otros tipos como la matriz 2d, pero al final, fue una compensación de escribir mi propio código de verificación de igualdad de valores o una traducción de rango y archivo a índice y viceversa. Este último era más simple. Yo diría que primero funcione, y luego optimice su tipo de datos más tarde si es necesario. No es probable que haga una diferencia lo suficientemente grande como para importar.
Al final, su implementación no debería importar tanto, siempre y cuando las operaciones de su placa estén correctamente encapsuladas por tipo de placa y operaciones. Por ejemplo, así es como pueden verse algunas de mis pruebas para configurar un tablero:
Para este código (o cualquier código de llamada), no importaría cómo se representara el tablero. El tablero está representado por las operaciones en él más que su estructura subyacente.
fuente