Apresento a vocês os primeiros 3% de um auto-intérprete da Hexagony ...
|./...\..._..>}{<$}=<;>'<..../;<_'\{*46\..8._~;/;{{;<..|M..'{.>{{=.<.).|.."~....._.>(=</.\=\'$/}{<}.\../>../..._>../_....@/{$|....>...</..~\.>,<$/'";{}({/>-'(<\=&\><${~-"~<$)<....'.>=&'*){=&')&}\'\'2"'23}}_}&<_3.>.'*)'-<>{=/{\*={(&)'){\$<....={\>}}}\&32'-<=._.)}=)+'_+'&<
Experimente online! Você também pode executá-lo por si só, mas isso levará cerca de 5 a 10 segundos.
Em princípio, isso pode caber no comprimento lateral 9 (para uma pontuação de 217 ou menos), porque ele usa apenas 201 comandos, e a versão desolfada que escrevi primeiro (no comprimento lateral 30) precisava de apenas 178 comandos. No entanto, tenho certeza de que levaria uma eternidade para realmente fazer tudo encaixar, então não tenho certeza se vou realmente tentar.
Também deve ser possível jogar um pouco no tamanho 10, evitando o uso das últimas uma ou duas linhas, de modo que os no-ops finais possam ser omitidos, mas isso exigiria uma reescrita substancial, como um dos primeiros caminhos junta faz uso do canto inferior esquerdo.
Explicação
Vamos começar desdobrando o código e anotando os caminhos do fluxo de controle:
Isso ainda é bastante confuso, então aqui está o mesmo diagrama para o código "não-destruído" que eu escrevi primeiro (na verdade, esse é o comprimento do lado 20 e originalmente eu escrevi o código no comprimento do lado 30, mas era tão escasso que não para melhorar a legibilidade, então eu a compactuei um pouco para tornar o tamanho um pouco mais razoável):
Clique para uma versão maior.
As cores são exatamente as mesmas, com exceção de alguns detalhes muito menores, os comandos sem fluxo de controle também são exatamente os mesmos. Então, eu vou explicar como isso funciona com base na versão sem golfinhos, e se você realmente quiser saber como funciona a versão com golfe, poderá verificar quais partes correspondem a qual no hexágono maior. (O único problema é que o código golfado começa com um espelho, para que o código real comece no canto direito, à esquerda.)
O algoritmo básico é quase idêntico à minha resposta CJam . Existem duas diferenças:
- Em vez de resolver a equação do número hexagonal centralizado, apenas calculo números hexagonais centralizados consecutivos até que um seja igual ou maior que o comprimento da entrada. Isso ocorre porque o Hexagony não possui uma maneira simples de calcular uma raiz quadrada.
- Em vez de preencher a entrada com no-ops imediatamente, verifico mais tarde se já esgotei os comandos na entrada e imprimo um,
.
se tiver.
Isso significa que a ideia básica se resume a:
- Leia e armazene a sequência de entrada enquanto calcula seu comprimento.
- Encontre o menor comprimento lateral
N
(e o número hexagonal centralizado correspondente hex(N)
) que pode conter toda a entrada.
- Calcule o diâmetro
2N-1
.
- Para cada linha, calcule o recuo e o número de células (que somam
2N-1
). Imprima o recuo, imprima as células (usando .
se a entrada já estiver esgotada), imprima um avanço de linha.
Observe que existem apenas no-ops; portanto, o código real começa no canto esquerdo (o $
, que pula sobre o >
, de modo que realmente começamos no ,
caminho no cinza escuro).
Aqui está a grade de memória inicial:
Portanto, o ponteiro da memória começa na entrada rotulada pela borda , apontando para o norte. ,
lê um byte de STDIN ou a -1
se tivermos atingido o EOF nessa borda. Portanto, o <
seguinte é uma condição para saber se lemos toda a entrada. Vamos permanecer no loop de entrada por enquanto. O próximo código que executamos é
{&32'-
Isso grava um 32 no espaço rotulado pela borda e subtrai-o do valor de entrada no diff rotulado pela borda . Observe que isso nunca pode ser negativo, porque garantimos que a entrada contém apenas ASCII imprimível. Será zero quando a entrada for um espaço. (Como Timwi aponta, isso ainda funcionaria se a entrada pudesse conter linhas ou tabulações, mas também removeria todos os outros caracteres não imprimíveis com códigos de caracteres menores que 32.) Nesse caso, ele <
desvia o ponteiro de instrução (IP) restante e o caminho cinza claro é seguido. Esse caminho simplesmente redefine a posição do MP {=
e depois lê o próximo caractere - assim, os espaços são ignorados. Caso contrário, se o personagem não fosse um espaço, executamos
=}}})&'+'+)=}
Isso primeiro se move ao redor do hexágono pela borda do comprimento até seu lado oposto à borda do diferencial , com =}}}
. Em seguida, ele copia o valor da frente do comprimento de aresta para o comprimento do bordo, e incrementa com )&'+'+)
. Veremos em um segundo por que isso faz sentido. Por fim, movemos a nova borda com =}
:
(Os valores das arestas particulares são do último caso de teste apresentado no desafio.) Nesse ponto, o loop se repete, mas com tudo mudou um hexágono para nordeste. Então, depois de ler outro personagem, temos o seguinte:
Agora você pode ver que estamos escrevendo gradualmente a entrada (menos espaços) ao longo da diagonal nordeste, com os caracteres em todas as outras arestas, e o comprimento até esse caractere sendo armazenado paralelamente ao comprimento rotulado pela borda .
Quando terminarmos o loop de entrada, a memória ficará assim (onde eu já identifiquei algumas novas arestas para a próxima parte):
O %
é o último caractere que lemos, 29
é o número de caracteres não espaciais que lemos. Agora queremos encontrar o comprimento lateral do hexágono. Primeiro, há algum código de inicialização linear no caminho verde / cinza escuro:
=&''3{
Aqui, =&
copia o comprimento (29 no nosso exemplo) para o comprimento rotulado da borda . Em seguida, ''3
move-se para a aresta rotulada 3 e define seu valor como 3
(o que precisamos apenas como uma constante no cálculo). Finalmente, {
move-se para a aresta rotulada N (N-1) .
Agora entramos no loop azul. Esse loop é incrementado N
(armazenado na célula N ) e calcula seu número hexagonal centralizado e subtrai-o do comprimento da entrada. O código linear que faz isso é:
{)')&({=*'*)'-
Aqui, {)
move-se para e incrementa N . ')&(
move-se para a borda N-1 , copia N
lá e diminui-o. {=*
calcula seu produto em N (N-1) . '*)
multiplica isso pela constante 3
e incrementa o resultado na aresta rotulada hex (N) . Como esperado, este é o número hexagonal centésimo enésimo. Finalmente '-
calcula a diferença entre isso e o comprimento da entrada. Se o resultado for positivo, o comprimento lateral ainda não é grande o suficiente e o loop será repetido (onde }}
mova o MP de volta para a borda rotulada N (N-1) ).
Quando o comprimento lateral for grande o suficiente, a diferença será zero ou negativa e obtemos o seguinte:
Primeiro, agora existe o caminho verde linear realmente longo, que faz alguma inicialização necessária para o loop de saída:
{=&}}}32'"2'=&'*){=&')&}}
Os {=&
começa por copiar o resultado do diff borda no comprimento de ponta, porque depois precisa de algo não-positivo lá. }}}32
grava um 32 no espaço rotulado da borda . '"2
escreve uma constante 2 na aresta não identificada acima de diff . '=&
copia N-1
para a segunda borda com a mesma etiqueta. '*)
multiplica por 2 e incrementa para obter o valor correto na aresta 2N-1 na parte superior. Este é o diâmetro do hexágono. {=&')&
copia o diâmetro para a outra borda rotulada 2N-1 . Finalmente, }}
volta para a borda 2N-1 na parte superior.
Vamos rotular novamente as arestas:
A borda em que estamos atualmente (que ainda mantém o diâmetro do hexágono) será usada para iterar sobre as linhas da saída. O recuo rotulado da borda calculará quantos espaços são necessários na linha atual. As células rotuladas de borda serão usadas para iterar sobre o número de células na linha atual.
Agora estamos no caminho rosa que calcula o recuo . ('-
diminui o iterador de linhas e o subtrai de N-1 (para a borda de recuo ). O pequeno ramo azul / cinza no código simplesmente calcula o módulo do resultado ( ~
nega o valor se for negativo ou zero e nada acontece se for positivo). O restante do caminho rosa é o "-~{
que subtrai o recuo do diâmetro para a borda das células e depois volta para a borda do recuo .
O caminho amarelo sujo agora imprime o recuo. O conteúdo do loop é realmente apenas
'";{}(
Onde se '"
move para a borda do espaço , ;
imprime, {}
volta para recuo e (
diminui.
Quando terminamos, o (segundo) caminho cinza escuro procura o próximo caractere a ser impresso. Os =}
movimentos na posição (o que significa, na borda da célula , apontando para o sul). Então temos um loop muito apertado, {}
que simplesmente desce duas arestas na direção sudoeste, até atingirmos o final da string armazenada:
Percebe que eu rotulei uma borda para lá EOF? . Depois de processarmos esse caractere, tornaremos a borda negativa, para que o {}
loop termine aqui em vez da próxima iteração:
No código, estamos no final do caminho cinza escuro, onde '
retrocede um passo no caractere de entrada. Se a situação for um dos dois últimos diagramas (ou seja, ainda houver um caractere da entrada que ainda não imprimimos), seguiremos o caminho verde (o inferior, para pessoas que não são boas com verde e azul). Essa é bastante simples: ;
imprime o próprio personagem. '
move-se para a borda do espaço correspondente que ainda retém 32 e ;
imprime esse espaço. Então {~
faz o nosso EOF? negativo para a próxima iteração, '
recua um passo para que possamos retornar ao extremo noroeste da sequência com outro }{
loop apertado . Que termina no comprimento(a não positiva abaixo de hex (N) . Finalmente, }
retorna à borda da célula .
Se já esgotamos a entrada, o loop que procura por EOF? realmente terminará aqui:
Nesse caso, '
move-se para a célula de comprimento e seguimos o caminho azul claro (superior), que imprime um não-op. O código neste ramo é linear:
{*46;{{;{{=
Ele {*46;
grava um 46 na borda rotulada como não operacional e o imprime (isto é, um ponto). Depois, {{;
move-se para a borda do espaço e imprime isso. A {{=
volta para a borda da célula para a próxima iteração.
Nesse ponto, os caminhos se juntam novamente e (
diminuem a borda das células . Se o iterador ainda não for zero, seguiremos o caminho cinza claro, que simplesmente inverte a direção do MP =
e depois procura o próximo caractere a ser impresso.
Caso contrário, chegamos ao final da linha atual e o IP seguirá o caminho roxo. É assim que a grade de memória se parece nesse ponto:
O caminho roxo contém o seguinte:
=M8;~'"=
O =
inverte a direção do MP novamente. M8
define o valor definido como 778
(porque o código de caractere de M
é 77
e dígitos serão anexados ao valor atual). Isso acontece 10 (mod 256)
, então, quando o imprimimos ;
, obtemos um avanço de linha. Em seguida, ~
torna a aresta negativa novamente, '"
volta para a aresta das linhas e =
reverte o MP mais uma vez.
Agora, se a borda das linhas é zero, terminamos. O IP seguirá o caminho vermelho (muito curto), onde @
finaliza o programa. Caso contrário, continuamos no caminho púrpura, que volta ao rosa, para imprimir outra linha.
Diagramas de fluxo de controle criados com o HexagonyColorer da Timwi . Diagramas de memória criados com o depurador visual em seu IDE esotérico .
abc`defg
, na verdade, ele se tornaria pastebin.com/ZrdJmHiR