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
! "#%$
%$"#!
!!##%
|||_
_ __
Pruébalo en línea!
El enfoque típico para dibujar la curva de Hilbert es seguir la ruta como una serie de trazos y vueltas, renderizando el resultado en un mapa de bits o alguna área de memoria, y luego escribiendo esa representación cuando se completa la ruta. Esto simplemente no es factible en Befunge cuando solo tenemos 2000 bytes de memoria para trabajar, y eso incluye la fuente del programa en sí.
Entonces, el enfoque que hemos tomado aquí es crear una fórmula que nos diga exactamente qué carácter generar para una determinada coordenada x, y. Para entender cómo funciona esto, es más fácil hacer caso omiso de la prestación ASCII para empezar, y sólo pensar en la curva como formado por caracteres de cuadro: ┌
, ┐
, └
, ┘
, │
, y ─
.
Cuando miramos la curva así, podemos ver de inmediato que el lado derecho es un espejo exacto del lado izquierdo. Los caracteres a la derecha se pueden determinar simplemente mirando a su compañero a la izquierda y reflejándolo horizontalmente (es decir, las ocurrencias de ┌
y ┐
se intercambian, como son └
y ┘
).
Luego, mirando la esquina inferior izquierda, nuevamente podemos ver que la mitad inferior es un reflejo de la mitad superior. Por lo tanto, los caracteres en la parte inferior se determinan simplemente al buscar a su compañero arriba y reflejarlo verticalmente (es decir, las ocurrencias de ┌
y └
se intercambian, como son ┐
y ┘
).
La mitad restante de esta esquina es un poco menos obvia. El bloque de la derecha se puede derivar de una reflexión vertical del bloque diagonalmente adyacente a él.
Y el bloque de la izquierda puede derivarse de una reflexión vertical del bloque en la esquina superior izquierda de la curva completa.
En este punto, todo lo que nos queda es la esquina superior izquierda, que es solo otra curva de Hilbert una iteración más baja. En teoría, ahora solo deberíamos repetir el proceso nuevamente, pero hay un problema: en este nivel, las mitades izquierda y derecha del bloque no son espejos exactos entre sí.
Entonces, en cualquier otra cosa que no sea el nivel superior, los caracteres de la esquina inferior deben manejarse como un caso especial, donde el ┌
personaje se refleja como ─
, y el │
personaje se refleja como └
.
Pero aparte de eso, realmente podemos repetir este proceso de forma recursiva. En el último nivel codificamos el carácter superior izquierdo como ┌
, y el carácter inferior como │
.
Ahora que tenemos una manera de determinar la forma de la curva en una coordenada x, y particular, ¿cómo traducimos eso a la representación ASCII? En realidad, es solo un mapeo simple que traduce cada posible mosaico en dos caracteres ASCII.
┌
se convierte _
(espacio más guión bajo)
┐
se convierte
(dos espacios)
└
se convierte |_
(barra vertical más guión bajo)
┘
se convierte |
(barra vertical más espacio)
│
se convierte |
(nuevamente una barra vertical más espacio)
─
se convierte __
(dos guiones bajos)
Esta asignación no es intuitiva al principio, pero puede ver cómo funciona cuando mira dos representaciones correspondientes una al lado de la otra.
Y eso es básicamente todo lo que hay que hacer. En realidad, implementar este algoritmo en Befunge es otro problema por completo, pero dejaré esa explicación para otro momento.