Freitag JS: eine Quine, die Tic-Tac-Toe spielt

Ich grüße alle in meinem traditionellen Bereich voller Lovecraftian-Wahnsinn.

Beim Schreiben eines der vorherigen Artikel (nicht schauen, es war nicht besonders gut) habe ich über die Tatsache nachgedacht, dass Quine ... Ja, nur für den Fall, ich erinnere Sie daran: Quine ist ein Programm, das seinen eigenen Text anzeigt und es "ehrlich" macht ( ohne beispielsweise diesen Text in einer Datei auf Ihrer Festplatte zu suchen). Im Allgemeinen die traditionelle bedeutungslose Puzomerka der Programmierer.

Also dachte ich darüber nach, dass Quine im Prinzip eine beliebige Nutzlast tragen kann. Das heißt, etwas anderes als seine Hauptfunktion zu tun. Und als Proof-of-Concept habe ich beschlossen, eine Quine zu schreiben, die Tic-Tac-Toe spielt. Und schrieb. Schmutzige Details unter dem Schnitt.

Bild

"Aber wie kann er etwas anderes tun, als seinen Text anzuzeigen?" - Vielleicht fragst du. Und einfach. Neben der Ausgabe verfügt das Programm auch über eine Eingabe. Wenn das Programm seinen Text ohne Eingabe anzeigt, ist es quine. Wenn das Programm etwas anderes tut, wenn Eingaben verfügbar sind, wen sollen wir dann verurteilen?

Vielleicht lege ich sofort die Karten auf den Tisch. Hier ist ein Link zu meiner Kreation. Die Datei enthält drei Entitäten, auf die verwiesen wird:

  1. Die Quine-Funktion ist das, worüber ich spreche.
  2. Die evalAndCall-Funktion ist ein Helfer.
  3. Spielklasse - noch mehr Helfer

Zuerst werden wir darüber sprechen, wie man mit der Quine-Funktion arbeitet und dann, wie es funktioniert.

Wie man mit ihr arbeitet


Ganz am Anfang der Quine-Funktion sehen Sie Folgendes:

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

Der Kommentar ganz am Anfang der Funktion ist die Benutzeroberfläche. Dadurch wird die Funktion mit denen kommunizieren, die sie für das Spiel verwenden. Zuerst dachte ich darüber nach, es über die Konsole zu tun, aber dann entschied ich, dass es korrekter wäre, die Funktion sauber zu halten.

Lassen Sie uns überprüfen, ob die Funktion wirklich quine ist. Hier habe ich der Einfachheit halber eine (fast) leere HTML-Seite mit dem angehängten Skript quine.js gepostet. Wenn Sie die Tools des Entwicklers öffnen, können Sie den folgenden Code dort nicht selektiv steuern:

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

Bohrung mod
Tatsächlich haben wir natürlich nur überprüft, ob die Quine-Funktion den Text des Quines zurückgibt und nicht, ob es sich selbst um den Quine handelt. Und um genau zu sein - wir haben nur überprüft, ob die Quine-Funktion den Text der Funktion zurückgibt, die in unserem Fall wie Quine funktioniert hat. Es gibt keine Garantie dafür, dass das Innere nichts enthält wie:

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


Jetzt können wir versuchen, mit ihr zu spielen. Nehmen wir an, wir machen den ersten Schritt in die obere linke Ecke des Feldes.

 let quineText = quine(1); 

Nun ist die "Benutzeroberfläche" wie folgt:

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

Quine berücksichtigte unseren Umzug und gab eine Antwort im oberen zentralen Feld. Übrigens wird die resultierende Funktion auch ohne Argumente quine aufgerufen. Sie gibt ihren neuen Text zurück, nicht den Text der ursprünglichen Funktion. Wir können das ganze Spiel spielen, aber der Einfachheit halber werden wir die Hilfsfunktion evalAndCall verwenden.

 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 spielt, gewinnt und punktet sogar. Sie können länger damit spielen, aber für mehr Komfort empfehle ich die Verwendung der Spielklasse, mit der ich das Spiel selbst getestet habe. Ich denke, wenn Sie bis zu diesem Punkt lesen, muss ich nicht erklären, wie man es benutzt.

Wie es funktioniert


Im Allgemeinen bestand die Aufgabe aus zwei Teilen: Wenn möglich, eine präzise Funktion des Tic-Tac-Toe-Spiels zu schreiben und sie dann in die Quine zu schieben. Beginnen wir mit Tic-Tac-Toe.

Der Kern der „künstlichen Intelligenz“ befindet sich in den Zeilen 66 bis 90 und ihre Silhouette ähnelt einem hartnäckigen Eichhörnchen:

  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; } 

Dieser Code sieht etwas verschleiert aus, weil ich daran interessiert war, ihn so kurz wie möglich zu halten. Das Wesentliche ist Folgendes: Bei der Eingabe der nächsten Funktion werden der Status des Feldes - ein Array von neun Elementen - und die Zellennummer, in der die Spielerperson einen Zug ausführt (der nächste prüft die Gültigkeit dieses Zuges), empfangen. Jedes Element des Feldes kann ein Kreuz, eine Null, ein Unterstrich (leere Zelle) oder ein Ausrufezeichen (eine leere Zelle, auf die bei der ersten Gelegenheit ein Kreuz gesetzt werden soll) sein. Wenn es ein Ausrufezeichen auf dem Feld gibt, machen wir sofort einen Zug dorthin und gewinnen. Andernfalls kleben wir das Array in eine Zeichenfolge und suchen im Regelobjekt nach der entsprechenden Regel. Um Platz zu sparen, sind die Regeln drehgenau. Bei der Suche wird das Feld daher viermal gedreht. Wenn die gewünschte Regel gefunden wurde, wird der Status des Felds durch den Wert der Regel ersetzt, der in Zeichen unterteilt ist. Als nächstes gibt die nächste Funktion den neuen Status des Feldes zurück. Gleichzeitig kann ein weiteres zehntes Zeichen hinzukommen: "w" - wenn die KI gewonnen hat, "d" - wenn es ein Unentschieden gab.

Dank der Ausrufezeichen, „Hinweise“ und der Rotation des Feldes und natürlich auch aufgrund der Tatsache, dass die KI an erster Stelle steht, wurde die optimale Strategie in nur 6 Regeln beschrieben.

Mit der nächsten Funktion verarbeitet die Quine-Funktion die Eingabe und schreibt einige Felder in das magicHash-Objekt. Und hier kommen wir reibungslos zum zweiten Teil: Wie die „Quayne“ -Komponente funktioniert. Alle Magie befindet sich im magicHash-Objekt und seiner magicString-Eigenschaft.

Wie Sie leicht sehen können, enthält die magicString-Zeile fast vollständig duplizierten Programmtext. Wie jedoch jeder weiß, der jemals versucht hat, eine Quine zu schreiben, kann man keinen vollständig vollständigen Text hineinschieben - denn dann muss er sich streng selbst enthalten, was für Strings endlicher Länge unmöglich ist. Daher enthält es neben dem „einfachen“ Text auch „magische“ Platzhaltersequenzen, die auf beiden Seiten durch das Zeichen „$“ begrenzt sind.

Wenn die Zeit X kommt und wir den Text der Funktion zurückgeben müssen, nehmen wir einfach magicString und ersetzen die darin enthaltenen Platzhalter durch die entsprechenden Eigenschaften des magicHash-Objekts. Diese Eigenschaften können statisch sein (Backtick), sich zur Laufzeit ändern (Feld) oder sogar zur Laufzeit hinzugefügt werden (Nachricht) - das spielt keine Rolle. Es ist wichtig, dass wir für jeden „problematischen“ Code, der nicht einfach in einer Zeile dupliziert werden kann, eine „magische“ Eigenschaft des magicHash-Objekts haben. Der letzte ersetzt magicString selbst. Letzteres - denn sonst gäbe es zusätzliche Platzhaltersequenzen, die ebenfalls ersetzt würden.

Zusammenfassung


Ich habe aus eigener Erfahrung überprüft, ob man alles in eine Quine stopfen kann. Wenn Sie sich die Mühe machen, können Sie im Prinzip einen Quine-Generator erstellen - eine Funktion, die jede andere reine Funktion in Quine umwandelt und es Ihnen ermöglicht, einen beliebigen Zustand zu speichern, auf den die obige reine Funktion zugreifen kann. Die Ränder dieses Manuskripts sind jedoch zu eng ...

Im Allgemeinen gebe ich nicht vor, die besondere Neuheit meiner "Entdeckung" zu sein. Hardcore-Funktionäre müssen diesen Text mit einem hervorragenden Lächeln gelesen haben. Aber es war interessant für mich, es mit meinen eigenen Händen aufzunehmen, sozusagen Finger in Wunden zu stecken. Und ich hoffe, Sie waren daran interessiert, darüber zu lesen.

Auf Wiedersehen, Mädchen und Jungen. Wir sehen uns wieder.

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


All Articles