Saúdo todos na minha seção tradicional, cheios de loucura Lovecraftiana.
No processo de escrever um dos artigos anteriores (não olhe, não era particularmente bom), pensei no fato de o quine ... Sim, só para garantir, lembro: o quine é um programa que exibe seu próprio texto e o faz "honestamente" ( sem procurar, por exemplo, este texto em um arquivo no seu disco rígido). Em geral, o tradicional puzomerka sem sentido de programadores.
Então, pensei no fato de que a quine, em princípio, pode transportar uma carga útil arbitrária. Ou seja, fazer qualquer outra coisa além de sua função principal. E como prova de conceito, decidi escrever um quine que toca tic-tac-toe. E escreveu. Detalhes sujos sob o corte.

"Mas como ele pode fazer outra coisa senão exibir seu texto?" - talvez você pergunte. E fácil. Além da saída, o programa também tem entrada. Se o programa exibir seu texto na ausência de entrada - então é correto. Se o programa faz outra coisa quando há informações, quem somos nós para condená-lo?
Talvez eu imediatamente coloque as cartas na mesa.
Aqui está um link para minha criação. Existem três entidades no arquivo por referência:
- A função quine é do que estou falando.
- A função evalAndCall é auxiliar.
- Classe do jogo - ainda mais ajudante
Primeiro, falaremos sobre como trabalhar com a função quine e depois como ela funciona.
Como trabalhar com ela
No início da função quine, você pode ver o seguinte:
function quine(input){
O comentário no início da função é a interface do usuário. Por meio dele, a função se comunicará com quem a usar no jogo. No começo, pensei em fazê-lo através do console, mas depois decidi que seria mais correto manter a
função limpa .
Vamos verificar se a função está realmente correta.
Aqui, por conveniência, publiquei uma página HTML (quase) vazia com o script quine.js anexado. Ao abrir as ferramentas do desenvolvedor, você pode direcionar o código a seguir de maneira não seletiva:
const quineText = quine(); const evaluatedQuine = eval("(" + quineText + ")");
Modificação do furoDe fato, é claro, verificamos apenas que a função quine retorna o texto da quine, e não que ela mesma seja a quine. E para ser absolutamente preciso - verificamos apenas que a função quine retorna o texto da função, que no nosso caso funcionava como quine. Não há garantias de que o interior não contenha algo como:
if(Math.random() < 0.99){ beAGoodQuine(); }else{ haltAndCatchFire(); }
Agora podemos tentar brincar com ela. Digamos que fazemos o primeiro movimento para o canto superior esquerdo do campo.
let quineText = quine(1);
Agora a "interface do usuário" é a seguinte:
function quine(input){
Quine levou em conta a nossa jogada e deu uma resposta no campo central superior. A propósito, a função resultante também é denominada quine sem argumentos, ela retornará seu novo texto, não o texto da função original. Podemos jogar o jogo inteiro, mas por conveniência, usaremos a função auxiliar evalAndCall.
let quineText = quine();
Voila! Quine joga, vence e até pontua. Você pode jogar por mais tempo, mas para maior comodidade, recomendo usar a classe Game, com a qual eu mesmo testei o jogo. Acho que se você ler até este ponto, não preciso explicar como usá-lo.
Como funciona
Em geral, a tarefa consistia em duas partes: escrever, se possível, uma função concisa do jogo do jogo da velha, depois empurrá-lo para o jogo. Vamos começar com o jogo da velha.
O núcleo da "inteligência artificial" está nas linhas 66-90 e sua silhueta se assemelha a um esquilo teimoso:
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; }
Esse código parece um pouco ofuscado porque eu estava interessado em torná-lo o mais curto possível. Sua essência é a seguinte: o estado do campo - uma matriz de nove elementos - e o número da célula onde a pessoa-jogador faz um movimento (o próximo verifica a validade desse movimento) entra na próxima função. Cada elemento do campo pode ser uma cruz, um zero, um sublinhado (célula vazia) ou um ponto de exclamação (uma célula vazia na qual colocar uma cruz na primeira oportunidade). Se houver um ponto de exclamação no campo, imediatamente fazemos uma jogada para lá e vencemos. Caso contrário, colamos o array em uma string e procuramos a regra correspondente no objeto de regras. Para economizar espaço, as regras são precisas para rotação, portanto, para pesquisar, o campo é girado quatro vezes. Quando a regra desejada é encontrada, o estado do campo é substituído pelo valor da regra, dividido em caracteres. Em seguida, a próxima função retorna o novo estado do campo. Ao mesmo tempo, um décimo personagem adicional pode se juntar a ele: "w" - se a IA vencer, "d" - se houver um empate.
Graças aos pontos de exclamação, "dicas" e à rotação do campo, e também, é claro, devido ao fato de a IA se mover primeiro, a estratégia ideal foi descrita em apenas 6 regras.
Usando a próxima função, a função quine processa a entrada e grava alguns campos no objeto magicHash. E aqui passamos sem problemas para a segunda parte: como o componente "quayne" funciona. Toda mágica está no objeto magicHash e sua propriedade magicString.
A linha magicString, como você pode ver facilmente, contém texto de programa quase completamente duplicado. No entanto, como todo mundo que já tentou escrever um quine sabe, você não pode inserir um texto completamente completo nele - porque ele precisará se conter estritamente, o que é impossível para strings de comprimento finito. Portanto, além do texto "simples", ele também contém sequências curinga "mágicas" delimitadas em ambos os lados pelo caractere "$".
Quando chega a hora de X e temos que retornar o texto da função, pegamos magicString e substituímos as seqüências curinga nela pelas propriedades correspondentes do objeto magicHash. Essas propriedades podem ser estáticas (backtick), alterar no tempo de execução (campo) ou até mesmo serem adicionadas no tempo de execução (mensagem) - isso não importa. É importante que, para cada pedaço de código "problemático" que não possa ser simplesmente duplicado em uma linha, tenhamos uma propriedade "mágica" do objeto magicHash. O último substitui o magicString. O último - porque, caso contrário, haveria sequências curinga adicionais que também seriam substituídas.
Sumário
Eu verifiquei por experiência própria que você pode colocar qualquer coisa em uma solução. Em princípio, se você se incomodar, poderá criar um gerador de quine - uma função que transforma qualquer outra função pura em quine e permite armazenar um estado arbitrário que a função pura acima pode acessar. No entanto, as margens deste manuscrito são muito estreitas ...
Em geral, não finjo a novidade especial da minha "descoberta". Funcionários hardcore devem ter lido este texto com um sorriso de excelência. Mas foi interessante para mim pegá-lo com minhas próprias mãos, colocar, por assim dizer, dedos em feridas. E espero que você esteja interessado em ler sobre isso.
Adeus, meninas e meninos. Vejo você de novo.