Je salue tout le monde dans ma section traditionnelle pleine de folie lovecraftienne.
En train d'écrire l'un des articles précédents (ne regardez pas, ce n'était pas particulièrement bon), j'ai pensé au fait que quine ... Oui, juste au cas où, je vous le rappelle: quine est un programme qui affiche son propre texte, et le fait "honnêtement" ( sans regarder, par exemple, ce texte dans un fichier sur votre disque dur). En général, la puzomerka traditionnelle vide de sens des programmeurs.
J'ai donc pensé au fait que quine, en principe, peut transporter une charge utile arbitraire. C'est-à-dire faire autre chose que sa fonction principale. Et comme preuve de concept, j'ai décidé d'écrire une quine qui joue du tic-tac-toe. Et a écrit. Détails sales sous la coupe.

"Mais comment peut-il faire autre chose que d'afficher son texte?" - vous demandez peut-être. Et facile. En plus de la sortie, le programme a également une entrée. Si le programme affiche son texte en l'absence de saisie - alors c'est quine. Si le programme fait autre chose lorsque des données sont disponibles, qui sommes-nous pour le condamner?
Je vais peut-être immédiatement disposer les cartes sur la table.
Voici un lien vers ma création. Le fichier contient trois entités par référence:
- La fonction quine est ce dont je parle.
- La fonction evalAndCall est auxiliaire.
- Classe de jeu - encore plus d'aide
Tout d'abord, nous parlerons de la façon de travailler avec la fonction quine, puis de son fonctionnement.
Comment travailler avec elle
Au tout début de la fonction quine, vous pouvez voir ce qui suit:
function quine(input){
Le commentaire au tout début de la fonction est l'interface utilisateur. Grâce à elle, la fonction communiquera avec ceux qui l'utilisent pour le jeu. Au début, j'ai pensé à le faire via la console, mais j'ai ensuite décidé qu'il serait plus correct de garder la
fonction propre .
Vérifions que la fonction est vraiment quine.
Ici, pour plus de commodité, j'ai posté une page HTML (presque) vide avec le script quine.js attaché. En ouvrant les outils du développeur, vous pouvez y conduire le code suivant de manière non sélective:
const quineText = quine(); const evaluatedQuine = eval("(" + quineText + ")");
Mod d'alésageEn fait, bien sûr, nous avons seulement vérifié que la fonction quine retourne le texte du quine, et non que ce soit lui-même le quine. Et pour être absolument exact - nous avons seulement vérifié que la fonction quine renvoie le texte de la fonction, qui dans notre cas fonctionnait comme quine. Il n'y a aucune garantie que l'intérieur ne contient pas quelque chose comme:
if(Math.random() < 0.99){ beAGoodQuine(); }else{ haltAndCatchFire(); }
Maintenant, nous pouvons essayer de jouer avec elle. Disons que nous faisons le premier pas dans le coin supérieur gauche du champ.
let quineText = quine(1);
Maintenant, "l'interface utilisateur" est la suivante:
function quine(input){
Quine a pris en compte notre déménagement et a fait une réponse dans le champ central supérieur. Soit dit en passant, la fonction résultante est également appelée quine sans arguments, elle renverra son nouveau texte, et non le texte de la fonction d'origine. Nous pouvons jouer à tout le jeu, mais pour plus de commodité, nous utiliserons la fonction auxiliaire evalAndCall.
let quineText = quine();
Voila! Quine joue, gagne et marque même. Vous pouvez jouer avec plus longtemps, mais pour plus de commodité, je recommande d'utiliser la classe Game, avec laquelle j'ai testé le jeu moi-même. Je pense que si vous lisez jusqu'à ce point, je n'ai pas besoin d'expliquer comment l'utiliser.
Comment ça marche
En général, la tâche consistait en deux parties: écrire, si possible, une fonction concise du jeu de tic-tac-toe, puis la pousser dans la quine. Commençons par le tic-tac-toe.
Le cœur de «l'intelligence artificielle» se trouve sur les lignes 66-90 et sa silhouette ressemble à un écureuil têtu:
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; }
Ce code semble un peu obscurci parce que je voulais le rendre aussi court que possible. Son essence est la suivante: l'état du terrain - un tableau de neuf éléments - et le numéro de la cellule où le joueur-personne fait un coup (le prochain vérifie la validité de ce coup), entre dans la fonction suivante. Chaque élément du champ peut être une croix, un zéro, un trait de soulignement (cellule vide) ou un point d'exclamation (une cellule vide sur laquelle placer une croix à la première occasion). S'il y a un point d'exclamation sur le terrain, nous nous y déplaçons immédiatement et gagnons. Sinon, nous collons le tableau dans une chaîne et recherchons la règle correspondante dans l'objet rules. Pour économiser de l'espace, les règles sont précises à la rotation; par conséquent, pour la recherche, le champ est tourné quatre fois. Lorsque la règle souhaitée est trouvée, l'état du champ est remplacé par la valeur de la règle, divisée en caractères. Ensuite, la fonction suivante renvoie le nouvel état du champ. Dans le même temps, un dixième personnage supplémentaire peut le rejoindre: "w" - si l'IA a gagné, "d" - s'il y avait match nul.
Grâce aux points d'exclamation, aux «indices» et à la rotation du champ, et aussi, bien sûr, du fait que l'IA se déplace en premier, la stratégie optimale a été décrite en seulement 6 règles.
En utilisant la fonction suivante, la fonction quine traite l'entrée et écrit certains champs dans l'objet magicHash. Et ici, nous passons en douceur à la deuxième partie: comment fonctionne le composant «quayne». Toute la magie se trouve dans l'objet magicHash et sa propriété magicString.
La ligne magicString, comme vous pouvez facilement le voir, contient un texte de programme presque complètement dupliqué. Cependant, comme le savent tous ceux qui ont déjà essayé d'écrire un quine, vous ne pouvez pas y insérer un texte complètement complet, car il devra alors se contenir strictement, ce qui est impossible pour les chaînes de longueur finie. Par conséquent, en plus du texte «brut», il contient également des séquences génériques «magiques» délimitées des deux côtés par le caractère «$».
Lorsque le moment vient X et que nous devons retourner le texte de la fonction, nous prenons simplement magicString et y remplaçons les caractères génériques par les propriétés correspondantes de l'objet magicHash. Ces propriétés peuvent être statiques (backtick), modifiées lors de l'exécution (champ) ou même être ajoutées lors de l'exécution (message) - cela n'a pas d'importance. Il est important que pour chaque morceau de code «problématique» qui ne peut pas être simplement dupliqué dans une ligne, nous ayons une propriété «magique» de l'objet magicHash. Le dernier remplace magicString lui-même. Ce dernier - car sinon, il y aurait des séquences génériques supplémentaires qui seraient également remplacées.
Résumé
J'ai vérifié par ma propre expérience que vous pouvez tout ranger dans une quine. En principe, si vous dérangez, vous pouvez créer un générateur de quine - une fonction qui transforme toute autre fonction pure en quine et vous permet de stocker un état arbitraire auquel la fonction pure ci-dessus peut accéder. Cependant, les marges de ce manuscrit sont trop étroites ...
En général, je ne prétends pas à la nouveauté particulière de ma "découverte". Les fonctionnaires hardcore doivent avoir lu ce texte avec un sourire d'excellence. Mais c'était intéressant pour moi de le ramasser de mes propres mains, de mettre, pour ainsi dire, des doigts dans des plaies. Et j'espère que cela vous a intéressé.
Au revoir, filles et garçons. A bientôt.