Befunge, 444 368 323 byte
&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
! "#%$
%$"#!
!!##%
|||_
_ __
Provalo online!
L'approccio tipico per disegnare la curva di Hilbert è quello di seguire il percorso come una serie di tratti e curve, trasformando il risultato in una bitmap o un'area di memoria e quindi scrivendo quel rendering quando il percorso è completo. Questo non è fattibile in Befunge quando abbiamo solo 2000 byte di memoria con cui lavorare, e questo include la fonte del programma stesso.
Quindi l'approccio che abbiamo adottato qui è quello di elaborare una formula che ci dica esattamente quale carattere emettere per una determinata coordinata x, y. Per capire come funziona questo, è più facile ignorare il rendering ASCII per iniziare, e basti pensare alla curva di resa da caratteri della scatola: ┌
, ┐
, └
, ┘
, │
, e ─
.
Quando osserviamo la curva in questo modo, possiamo immediatamente vedere che il lato destro è uno specchio esatto del lato sinistro. I personaggi a destra possono essere determinati semplicemente guardando il loro partner a sinistra e riflettendolo in senso orizzontale (cioè le occorrenze di ┌
e ┐
vengono scambiate, così come sono └
e ┘
).
Quindi, guardando l'angolo in basso a sinistra, possiamo ancora vedere che la metà inferiore è un riflesso della metà superiore. Quindi i personaggi in basso sono semplicemente determinati guardando in alto il loro partner e riflettendolo verticalmente (cioè le occorrenze di ┌
e └
vengono scambiate, così come sono ┐
e ┘
).
La restante metà di questo angolo è un po 'meno ovvia. Il blocco di destra può essere derivato da una riflessione verticale del blocco in diagonale adiacente ad esso.
E il blocco a sinistra può essere derivato da un riflesso verticale del blocco nella parte superiore sinistra dell'intera curva.
A questo punto, ci rimane solo l'angolo in alto a sinistra, che è solo un'altra curva di Hilbert una iterazione in basso. In teoria, ora dovremmo semplicemente ripetere di nuovo il processo, ma c'è un po 'di problema: a questo livello, le metà sinistra e destra del blocco non sono specchi esatti l'uno dell'altro.
Quindi, a qualsiasi cosa diversa dal livello superiore, i personaggi nell'angolo inferiore devono essere gestiti come un caso speciale, in cui il ┌
personaggio viene riflesso ─
e il │
personaggio viene riflesso come └
.
Ma a parte questo, possiamo davvero ripetere questo processo in modo ricorsivo. All'ultimo livello codifichiamo il carattere in alto a sinistra come ┌
e il carattere sotto di esso come │
.
Ora che abbiamo un modo per determinare la forma della curva in corrispondenza di una particolare coordinata x, y, come possiamo tradurla nel rendering ASCII? In realtà è solo una semplice mappatura che traduce ogni possibile riquadro in due caratteri ASCII.
┌
diventa _
(spazio più trattino basso)
┐
diventa
(due spazi)
└
diventa |_
(barra verticale più trattino basso)
┘
diventa |
(barra verticale più spazio)
│
diventa |
(di nuovo una barra verticale più spazio)
─
diventa __
(due trattini bassi)
Questa mappatura all'inizio non è intuitiva, ma puoi vedere come funziona guardando due rendering corrispondenti fianco a fianco.
E questo è praticamente tutto quello che c'è da fare. In realtà implementare questo algoritmo in Befunge è un altro problema, ma lascerò quella spiegazione per un'altra volta.