Friday JS: una quine que juega tic-tac-toe

Saludo a todos en mi sección tradicional llena de locura lovecraftiana.

En el proceso de escribir uno de los artículos anteriores (no mires, no fue particularmente bueno) pensé en el hecho de que quine ... Sí, por si acaso, te recuerdo: quine es un programa que muestra su propio texto y lo hace "honestamente" ( sin mirar, por ejemplo, este texto en un archivo en su disco duro). En general, el tradicional puzomerka sin sentido de los programadores.

Entonces, pensé en el hecho de que quine, en principio, puede llevar una carga útil arbitraria. Es decir, hacer cualquier otra cosa además de su función principal. Y como prueba de concepto, decidí escribir una quine que juega al tic-tac-toe. Y escribió Detalles sucios debajo del corte.

imagen

"Pero, ¿cómo puede hacer algo más que mostrar su texto?" - tal vez preguntas. Y fácil. Además de la salida, el programa también tiene entrada. Si el programa muestra su texto en ausencia de entrada, entonces es quine. Si el programa hace algo más cuando hay información disponible, ¿quiénes somos para condenarlo?

Quizás exponga inmediatamente las cartas sobre la mesa. Aquí hay un enlace a mi creación. Hay tres entidades en el archivo por referencia:

  1. La función quine es de lo que estoy hablando.
  2. La función evalAndCall es auxiliar.
  3. Clase de juego: incluso más ayudante

Primero, hablaremos sobre cómo trabajar con la función quine, y luego cómo funciona.

Como trabajar con ella


Al comienzo de la función quine, puede ver lo siguiente:

function quine(input){/* |1|2|3| |4|x|6| |7|8|9| !  - ,     -. ,   .     ,    ,     .             ,    . :   - 0   - 0  - 0 */ 

El comentario al comienzo de la función es la interfaz de usuario. A través de él, la función se comunicará con quienes la usen para el juego. Al principio, pensé en hacerlo a través de la consola, pero luego decidí que sería más correcto mantener limpia la función .

Vamos a comprobar que la función es realmente quine. Aquí, por conveniencia, publiqué una página HTML (casi) vacía con el script quine.js adjunto. Al abrir las herramientas del desarrollador, puede conducir el siguiente código de forma no selectiva allí:

 const quineText = quine(); const evaluatedQuine = eval("(" + quineText + ")"); // ,     eval       //  undefined,   const evaluatedQuineText = evaluatedQuine(); quineText == evaluatedQuineText; // true 

Bore mod
De hecho, por supuesto, verificamos solo que la función quine devuelve el texto de la quine, y no que en sí misma sea la quine. Y para ser absolutamente precisos, verificamos solo que la función quine devuelve el texto de la función, que en nuestro caso funcionó como quine. No hay garantías de que adentro no contenga algo como:

 if(Math.random() < 0.99){ beAGoodQuine(); }else{ haltAndCatchFire(); } 


Ahora podemos intentar jugar con ella. Digamos que hacemos el primer movimiento a la esquina superior izquierda del campo.

 let quineText = quine(1); 

Ahora la "interfaz de usuario" es la siguiente:

 function quine(input){/* |o|x|3| |4|x|6| |7|8|9| !  - ,     -. ,   .     ,    ,     .             ,    . :   - 0   - 0  - 0 */ 

Quine tomó en cuenta nuestro movimiento e hizo una respuesta en el campo central superior. Por cierto, la función resultante también es quine: llamada sin argumentos, devolverá su nuevo texto, y no el texto de la función original. Podemos jugar todo el juego, pero por conveniencia usaremos la función auxiliar evalAndCall.

 let quineText = quine(); //         ,    . /* |1|2|3| |4|x|6| |7|8|9| */ quineText = evalAndCall(quineText, 1) /* |x|o|3| |4|x|6| |7|8|9| */ quineText = evalAndCall(quineText, 3) /* |x|o|o| |4|x|6| |7|8|x|     .    ,   0    */ quineText = evalAndCall(quineText, 0) /* |1|2|3| |4|x|6| |7|8|9| :   - 0   - 1  - 0 */ 

Voila! Quine juega, gana e incluso anota. Puedes jugar con él por más tiempo, pero para mayor comodidad, te recomiendo usar la clase Game, con la que probé el juego yo mismo. Creo que si lees hasta este punto, no necesito explicarte cómo usarlo.

Como funciona


En general, la tarea consistía en dos partes: escribir, si es posible, una función concisa del juego del tic-tac-toe, y luego meterlo en la quine. Comencemos con el tic-tac-toe.

El núcleo de la "inteligencia artificial" está en las líneas 66-90 y su silueta se asemeja a una ardilla obstinada:

  const rules = { "o___x____": "ox__x__!_", "ox__x__o_": "ox!_x_xo_", "oxo_x_xo_": "oxo!xxxo_", "oxooxxxo_": "oxooxxxoxd", "_o__x____": "xo__x___!", "xo__x___o": "xo_xx!!_o" }; const next = (field, move) => { if(!~"!_".indexOf(field[--move])){ return null; } field[move] = "o"; const win = field.indexOf("!"); if(~win){ field[win] = "x"; return [...field, "w"]; } for(let n = 0; n < 4; n++){ field = field.map((_, i) => field[[2, 5, 8, 1, 4, 7, 0, 3, 6][i]]); rules[field.join("")] && (field = rules[field.join("")].split("")); } return field; } 

Este código parece un poco confuso porque estaba interesado en hacerlo lo más breve posible. Su esencia es la siguiente: el estado del campo, una matriz de nueve elementos, y el número de la celda donde el jugador-persona realiza un movimiento (el siguiente verifica la validez de este movimiento), ingresa a la siguiente función. Cada elemento del campo puede ser una cruz, un cero, un guión bajo (celda vacía) o un signo de exclamación (una celda vacía en la que poner una cruz en la primera oportunidad). Si hay un signo de exclamación en el campo, inmediatamente hacemos un movimiento allí y ganamos. De lo contrario, pegamos la matriz en una cadena y buscamos la regla correspondiente en el objeto de reglas. Para ahorrar espacio, las reglas son precisas para la rotación; por lo tanto, para buscar, el campo se gira cuatro veces. Cuando se encuentra la regla deseada, el estado del campo se reemplaza por el valor de la regla, dividido en caracteres. A continuación, la siguiente función devuelve el nuevo estado del campo. Al mismo tiempo, puede unirse un décimo personaje adicional: "w" - si la IA ganó, "d" - si hubo un empate.

Gracias a los signos de exclamación, "pistas" y la rotación del campo, y también, por supuesto, debido a que la IA se mueve primero, la estrategia óptima se describió en solo 6 reglas.

Usando la siguiente función, la función quine procesa la entrada y escribe algunos campos en el objeto magicHash. Y aquí pasamos sin problemas a la segunda parte: cómo funciona el componente "quayne". Toda la magia está en el objeto magicHash y su propiedad magicString.

La línea magicString, como puede ver fácilmente, contiene texto de programa casi completamente duplicado. Sin embargo, como todos los que han intentado escribir un quine lo saben, no se puede insertar un texto completamente completo en él, porque entonces tendrá que contenerse estrictamente, lo que es imposible para cadenas de longitud finita. Por lo tanto, además del texto "simple", también contiene secuencias de comodines "mágicas" delimitadas en ambos lados por el carácter "$".

Cuando llega el momento X y tenemos que devolver el texto de la función, simplemente tomamos magicString y reemplazamos las secuencias comodín con las propiedades correspondientes del objeto magicHash. Estas propiedades pueden ser estáticas (retroceso), cambiar en tiempo de ejecución (campo) o incluso agregarse en tiempo de ejecución (mensaje); no importa. Es importante que para cada fragmento de código "problemático" que no se pueda simplemente duplicar en una línea, tengamos una propiedad "mágica" del objeto magicHash. El último sustituye a magicString. El último, porque de lo contrario habría secuencias comodín adicionales que también serían reemplazadas.

Resumen


Compruebo por experiencia propia que puedes meter cualquier cosa en una quine. En principio, si te molestas, puedes hacer un generador de quine, una función que convierte cualquier otra función pura en quine y te permite almacenar un estado arbitrario al que puede acceder la función pura anterior. Sin embargo, los márgenes de este manuscrito son demasiado estrechos ...

En general, no pretendo la novedad especial de mi "descubrimiento". Los funcionarios hardcore deben haber leído este texto con una sonrisa de excelencia. Pero fue interesante para mí levantarlo con mis propias manos, poner, por así decirlo, dedos en heridas. Y espero que te haya interesado leer sobre eso.

Adiós, niñas y niños. Nos vemos de nuevo.

Source: https://habr.com/ru/post/es435732/


All Articles