Befunge, 444 368 323 bytes
&1>\1-:v
0v^*2\<_$00p>
_>:10p\:20pv^_@#-*2g00:+1,+55$
^!-<v*2g000<>$#<0>>-\:v
g2*^>>10g20g+v \ ^*84g_$:88+g,89+g,\1+:00
v#*!-1g02!g01_4^2_
>::00g2*-!\1-:10g-\20g-++>v
87+#^\#p01#<<v!`g01/2\+76:_
vv1-^#1-g01:\_$:2/20g`!
_ 2/^>:10g#vv#`g02/4*3:\+77
v>0p^^/2:/2_
<^2-1-g02</2`#*3:
0g+10p2*:^*3_1
! "#%$
%$"#!
!!##%
|||_
_ __
Experimente online!
A abordagem típica para desenhar a curva de Hilbert é seguir o caminho como uma série de traços e curvas, renderizando o resultado em um bitmap ou em alguma área da memória e, em seguida, gravando essa renderização quando o caminho estiver completo. Isso não é viável no Befunge quando temos apenas 2000 bytes de memória para trabalhar, e isso inclui a fonte do próprio programa.
Portanto, a abordagem que adotamos aqui é criar uma fórmula que nos diga exatamente qual caractere produzir para uma determinada coordenada x, y. Para entender como isso funciona, é mais fácil de ignorar a prestação ASCII para começar, e apenas pensar na curva como composta de caracteres de caixa: ┌
, ┐
, └
, ┘
, │
, e ─
.
Quando olhamos para a curva assim, podemos ver imediatamente que o lado direito é um espelho exato do lado esquerdo. Os caracteres à direita podem ser simplesmente determinados procurando o parceiro à esquerda e refletindo-o horizontalmente (ou seja, ocorrências de ┌
e ┐
são trocadas, como são └
e ┘
).
Então, olhando para o canto inferior esquerdo, novamente podemos ver que a metade inferior é um reflexo da metade superior. Assim, os caracteres na parte inferior são simplesmente determinados procurando o parceiro acima e refletindo-o verticalmente (ou seja, ocorrências de ┌
e └
são trocadas, como são ┐
e ┘
).
A metade restante deste canto é um pouco menos óbvia. O bloco do lado direito pode ser derivado de uma reflexão vertical do bloco na diagonal adjacente a ele.
E o bloco da mão esquerda pode ser derivado de uma reflexão vertical do bloco no canto superior esquerdo da curva completa.
Neste ponto, tudo o que resta é o canto superior esquerdo, que é apenas mais uma curva de Hilbert uma iteração mais baixa. Em teoria, agora precisamos repetir o processo novamente, mas há um problema - nesse nível, as metades esquerda e direita do bloco não são espelhos exatos uma da outra.
Portanto, em qualquer coisa que não seja o nível superior, os caracteres do canto inferior precisam ser tratados como um caso especial, onde o ┌
caractere é refletido como ─
e o │
caractere é refletido como └
.
Mas, fora isso, podemos realmente repetir esse processo recursivamente. No último nível, codificamos o caractere superior esquerdo como ┌
e o caractere abaixo dele como │
.
Agora que temos uma maneira de determinar a forma da curva em uma determinada coordenada x, y, como podemos traduzir isso na renderização ASCII? Na verdade, é apenas um mapeamento simples que traduz cada bloco possível em dois caracteres ASCII.
┌
torna-se _
(espaço mais sublinhado)
┐
torna-se
(dois espaços)
└
torna-se |_
(barra vertical mais sublinhado)
┘
torna-se |
(barra vertical mais espaço)
│
torna-se |
(novamente uma barra vertical mais espaço)
─
torna-se __
(dois sublinhados)
Esse mapeamento não é intuitivo a princípio, mas você pode ver como ele funciona quando olha duas renderizações correspondentes lado a lado.
E isso é basicamente tudo o que existe. Na verdade, implementar esse algoritmo no Befunge é outro problema, mas deixarei essa explicação para outro momento.