Te presento el primer 3% de un autointerpretador de Hexagony ...
|./...\..._..>}{<$}=<;>'<..../;<_'\{*46\..8._~;/;{{;<..|M..'{.>{{=.<.).|.."~....._.>(=</.\=\'$/}{<}.\../>../..._>../_....@/{$|....>...</..~\.>,<$/'";{}({/>-'(<\=&\><${~-"~<$)<....'.>=&'*){=&')&}\'\'2"'23}}_}&<_3.>.'*)'-<>{=/{\*={(&)'){\$<....={\>}}}\&32'-<=._.)}=)+'_+'&<
Pruébalo en línea! También puede ejecutarlo en sí mismo, pero tardará entre 5 y 10 segundos.
En principio, esto podría encajar en la longitud lateral 9 (para una puntuación de 217 o menos), porque esto usa solo 201 comandos, y la versión no escrita que escribí primero (en la longitud lateral 30) solo necesitaba 178 comandos. Sin embargo, estoy bastante seguro de que tomaría una eternidad hacer que todo encajara, así que no estoy seguro de si realmente lo intentaré.
También debería ser posible jugar golf un poco en tamaño 10 evitando el uso de las últimas una o dos filas, de modo que se puedan omitir las operaciones no finales, pero eso requeriría una reescritura sustancial, como una de las primeras rutas une hace uso de la esquina inferior izquierda.
Explicación
Comencemos desplegando el código y anotando las rutas de flujo de control:
Eso sigue siendo bastante desordenado, así que aquí está el mismo diagrama para el código "no golfista" que escribí primero (de hecho, esto es 20 de longitud lateral y originalmente escribí el código en 30 de longitud lateral pero fue tan escaso que no sería no mejora la legibilidad en absoluto, así que lo compacté solo un poco para que el tamaño sea un poco más razonable):
Haga clic para una versión más grande.
Los colores son exactamente los mismos, aparte de algunos detalles menores, los comandos sin control de flujo también son exactamente los mismos. Así que explicaré cómo funciona esto en función de la versión sin golf, y si realmente quieres saber cómo funciona el golf, puedes verificar qué partes corresponden a cuáles en el hexágono más grande. (El único inconveniente es que el código de golf comienza con un espejo para que el código real comience en la esquina derecha hacia la izquierda).
El algoritmo básico es casi idéntico a mi respuesta CJam . Hay dos diferencias:
- En lugar de resolver la ecuación de números hexagonales centrados, solo calculo números hexagonales centrados consecutivos hasta que uno sea igual o mayor que la longitud de la entrada. Esto se debe a que Hexagony no tiene una manera simple de calcular una raíz cuadrada.
- En lugar de rellenar la entrada con no-ops de inmediato, verifico más tarde si ya he agotado los comandos en la entrada e imprimo un
.
en su lugar si lo he hecho.
Eso significa que la idea básica se reduce a:
- Lea y almacene la cadena de entrada mientras calcula su longitud.
- Encuentre la longitud lateral más pequeña
N
(y el número hexagonal centrado correspondiente hex(N)
) que puede contener toda la entrada.
- Calcule el diámetro
2N-1
.
- Para cada línea, calcule la sangría y el número de celdas (que suman
2N-1
). Imprima la sangría, imprima las celdas ( .
si la entrada ya está agotada), imprima un salto de línea.
Tenga en cuenta que solo hay no-ops, por lo que el código real comienza en la esquina izquierda (el $
, que salta sobre el >
, por lo que realmente comenzamos en el ,
camino en gris oscuro).
Aquí está la cuadrícula de memoria inicial:
Entonces, el puntero de la memoria comienza en la entrada etiquetada en el borde , apuntando hacia el norte. ,
lee un byte de STDIN o un -1
si hemos golpeado EOF en ese borde. Por lo tanto, <
justo después es condicional si hemos leído toda la entrada. Permanezcamos en el bucle de entrada por ahora. El siguiente código que ejecutamos es
{&32'-
Esto escribe un 32 en el espacio etiquetado como borde y luego lo resta del valor de entrada en el borde etiquetado como diff . Tenga en cuenta que esto nunca puede ser negativo porque estamos garantizados de que la entrada contiene solo ASCII imprimible. Será cero cuando la entrada sea un espacio. (Como lo señala Timwi, esto aún funcionaría si la entrada pudiera contener saltos de línea o pestañas, pero también eliminaría todos los demás caracteres no imprimibles con códigos de caracteres menores que 32). En ese caso, <
desvía el puntero de instrucción (IP) a la izquierda y se toma el camino gris claro. Ese camino simplemente restablece la posición del MP {=
y luego lee el siguiente carácter, por lo tanto, se omiten los espacios. De lo contrario, si el personaje no era un espacio, ejecutamos
=}}})&'+'+)=}
Este primero se mueve alrededor del hexágono a través del borde de longitud hasta su lado opuesto al borde de diferencia , con =}}}
. Luego copia el valor de enfrente del borde de longitud en el borde de longitud , y lo incrementa con )&'+'+)
. Veremos en un segundo por qué esto tiene sentido. Finalmente, movemos una nueva ventaja con =}
:
(Los valores de borde particulares son del último caso de prueba dado en el desafío.) En este punto, el ciclo se repite, pero con todo desplazado un hexágono al noreste. Entonces, después de leer otro personaje, obtenemos esto:
Ahora puede ver que estamos escribiendo gradualmente la entrada (menos espacios) a lo largo de la diagonal noreste, con los caracteres en cada otro borde, y la longitud hasta ese carácter se almacena paralela al borde etiquetado como longitud .
Cuando hayamos terminado con el bucle de entrada, la memoria se verá así (donde ya he etiquetado algunos bordes nuevos para la siguiente parte):
El %
es el último carácter que leemos, el 29
es el número de caracteres no espaciales que leemos. Ahora queremos encontrar la longitud lateral del hexágono. Primero, hay un código de inicialización lineal en la ruta verde / gris oscuro:
=&''3{
Aquí, =&
copia la longitud (29 en nuestro ejemplo) en el borde etiquetado como longitud . Luego se ''3
mueve al borde etiquetado como 3 y establece su valor en 3
(que solo necesitamos como constante en el cálculo). Finalmente se {
mueve al borde etiquetado N (N-1) .
Ahora entramos en el bucle azul. Este bucle se incrementa N
(almacenado en la celda etiquetada como N ) y luego calcula su número hexagonal centrado y lo resta de la longitud de entrada. El código lineal que hace eso es:
{)')&({=*'*)'-
Aquí, {)
se mueve a y en incrementos de N . ')&(
se mueve al borde etiquetado como N-1 , copia N
allí y lo disminuye. {=*
calcula su producto en N (N-1) . '*)
multiplica eso por la constante 3
e incrementa el resultado en el borde etiquetado como hexadecimal (N) . Como se esperaba, este es el enésimo número hexagonal centrado. Finalmente '-
calcula la diferencia entre eso y la longitud de entrada. Si el resultado es positivo, la longitud del lado aún no es lo suficientemente grande y el bucle se repite (donde }}
mueve el MP nuevamente al borde etiquetado como N (N-1) ).
Una vez que la longitud lateral es lo suficientemente grande, la diferencia será cero o negativa y obtenemos esto:
En primer lugar, ahora existe el camino verde lineal realmente largo que realiza algunas inicializaciones necesarias para el bucle de salida:
{=&}}}32'"2'=&'*){=&')&}}
Las {=&
aperturas de copiar el resultado en el diff borde en la longitud del borde, ya que después no necesitamos algo no positivo. }}}32
escribe un 32 en el espacio etiquetado como borde . '"2
escribe un constante 2 en el borde sin marcar arriba de diff . '=&
copias N-1
en el segundo borde con la misma etiqueta. '*)
lo multiplica por 2 y lo incrementa para que obtengamos el valor correcto en el borde etiquetado 2N-1 en la parte superior. Este es el diámetro del hexágono. {=&')&
copia el diámetro en el otro borde etiquetado como 2N-1 . Finalmente }}
regresa al borde etiquetado 2N-1 en la parte superior.
Vuelva a etiquetar los bordes:
El borde en el que estamos actualmente (que todavía mantiene el diámetro del hexágono) se usará para iterar sobre las líneas de la salida. El borde etiquetado como sangría calculará cuántos espacios se necesitan en la línea actual. Las celdas etiquetadas de borde se usarán para iterar sobre el número de celdas en la línea actual.
Ahora estamos en el camino rosado que calcula la sangría . ('-
disminuye el iterador de líneas y lo resta de N-1 (en el borde de sangría ). La rama corta azul / gris en el código simplemente calcula el módulo del resultado ( ~
niega el valor si es negativo o cero, y no sucede nada si es positivo). El resto del camino rosado es el "-~{
que resta la sangría del diámetro al borde de las celdas y luego vuelve al borde de la sangría .
El camino amarillo sucio ahora imprime la sangría. El contenido del bucle es realmente justo
'";{}(
Donde se '"
mueve al borde del espacio , lo ;
imprime, {}
vuelve al sangrado y lo (
disminuye.
Cuando hayamos terminado con eso, el (segundo) camino gris oscuro busca el siguiente carácter para imprimir. Los =}
movimientos en posición (lo que significa, en el borde de las celdas , apuntando hacia el sur). Luego tenemos un bucle muy apretado del {}
cual simplemente se mueve dos bordes hacia abajo en la dirección suroeste, hasta llegar al final de la cadena almacenada:
¿Te das cuenta de que he vuelto a etiquetar un borde allí EOF? . Una vez que hayamos procesado este carácter, haremos que ese borde sea negativo, de modo que el {}
ciclo termine aquí en lugar de la siguiente iteración:
En el código, estamos al final del camino gris oscuro, donde '
retrocede un paso hacia el carácter de entrada. Si la situación es uno de los dos últimos diagramas (es decir, todavía hay un carácter de la entrada que aún no hemos impreso), entonces estamos tomando el camino verde (el inferior, para las personas que no son buenas con el verde y azul). Ese es bastante simple: ;
imprime el personaje en sí. '
se mueve al borde del espacio correspondiente que todavía tiene un 32 de antes e ;
imprime ese espacio. Entonces {~
hace nuestro EOF? negativo para la siguiente iteración, '
retrocede un paso para que podamos volver al extremo noroeste de la cadena con otro }{
bucle cerrado . Que termina en la longitudcelda (la no positiva debajo del hex (N) . Finalmente se }
mueve de regreso al borde de las celdas .
Sin embargo, si ya hemos agotado la entrada, ¿el bucle que busca EOF? en realidad terminará aquí:
En ese caso, se '
mueve a la celda de longitud , y en su lugar estamos tomando el camino azul claro (superior), que imprime un no-op. El código en esta rama es lineal:
{*46;{{;{{=
El {*46;
escribe un 46 en el borde marcado no-op y lo imprime (es decir, un período). Luego se {{;
mueve al borde del espacio e imprime eso. El {{=
retrocede al borde de las celdas para la siguiente iteración.
En este punto, los caminos se unen y (
disminuye el borde de las celdas . Si el iterador aún no es cero, tomaremos el camino gris claro, que simplemente invierte la dirección del MP =
y luego busca el siguiente carácter para imprimir.
De lo contrario, hemos llegado al final de la línea actual, y la IP tomará el camino púrpura en su lugar. Así es como se ve la cuadrícula de memoria en ese punto:
El camino púrpura contiene esto:
=M8;~'"=
El =
revierte la dirección del MP nuevamente. M8
establece el valor de establece en 778
(porque el código de caracteres de M
is 77
y dígitos se agregarán al valor actual). Esto sucede 10 (mod 256)
, así que cuando lo imprimimos ;
, obtenemos un salto de línea. Luego ~
hace que el borde sea negativo nuevamente, '"
retrocede al borde de las líneas e =
invierte el MP una vez más.
Ahora, si el borde de las líneas es cero, hemos terminado. La IP tomará la ruta roja (muy corta), donde @
termina el programa. De lo contrario, continuamos en el camino púrpura que vuelve al rosa para imprimir otra línea.
Controle los diagramas de flujo creados con HexagonyColorer de Timwi . Diagramas de memoria creados con el depurador visual en su IDE esotérico .
abc`defg
que en realidad se convertiría en pastebin.com/ZrdJmHiR