Gracias a FryAmTheEggman por la inspiración necesaria para la solución XOR.
0000 !@
0001 ?.|@!
0010 #?#!)@
0011 ?!@
0100 +?|@!?
0101 ??!@
0110 ?<@!!<_\~(
0111 ?<<@!
1000 )\!#?@{
1001 (~?/@#!
1010 ??|@!)
1011 \#??!1@
1100 ?(~!@
1101 ?.|@!)
1110 ?$@#)!<
1111 1!@
Todos los programas se usan 0
para falso y 1
para verdadero.
Pruébalo en línea! Este no es un conjunto de pruebas, tendrá que copiar los diferentes programas y entradas usted mismo.
La solución anterior está dentro de los 2 bytes de la optimización (a menos que relajemos la interpretación verdadero / falso, supongo). He dejado un bruto barrido de búsqueda vigor durante cerca de dos días más de todos los programas que encaja en el lado de longitud 2, es decir, hasta 7 bytes (no bastantes Todos los programas - Hice algunas suposiciones sobre lo que las necesidades de cada programa válido y lo que hay programa válido podría tener). La búsqueda encontró soluciones para 15 de las 16 puertas posibles, y a menudo mucho más que solo una. Puede encontrar una lista de todas las soluciones alternativas en este pastebin donde también las he agrupado por comportamiento equivalente. Las que muestro arriba las he seleccionado porque son la solución más simple o la más interesante, y agregaré explicaciones para ellas mañana.
En cuanto a la 16ª puerta: XOR es la única puerta que aparentemente no se puede implementar en 7 bytes. Desafortunadamente, una búsqueda de fuerza bruta en programas más grandes no es factible con el código que tengo actualmente. Entonces XOR tuvo que ser escrito a mano. Lo más corto que he encontrado hasta ahora es el programa anterior de 10 bytes, que se basa en un intento fallido (pero muy cercano) de FryAmTheEggman. Es posible que exista una solución de 8 bytes o 9 bytes, pero aparte de eso, todas las soluciones deberían ser óptimas.
Explicaciones
Advertencia: muro de texto. En caso de que alguien esté interesado en cómo funcionan realmente estos programas de Hexagonía altamente comprimidos, he incluido explicaciones para cada uno de ellos a continuación. Intenté elegir la solución más simple para cada puerta en los casos en que existe más de un programa óptimo, para mantener las explicaciones razonablemente cortas. Sin embargo, algunos de ellos todavía aturden la mente, así que pensé que merecían un poco más de elaboración.
0000
: Falso
No creo que necesitemos un diagrama para este:
! @
. . .
. .
Como toda la cuadrícula de memoria se inicializa en ceros, !
simplemente imprime un cero y @
finaliza el programa.
Esta es también la única solución de 2 bytes.
0001
: Y
? .
| @ !
. .
Esto básicamente implementa cortocircuito . El siguiente diagrama gris muestra el comienzo del programa, donde se lee la primera entrada ?
y el puntero de instrucción (IP) se ajusta a la esquina izquierda donde lo |
refleja el espejo. Ahora la esquina actúa como condicional, por lo que hay dos rutas de ejecución diferentes según el valor de la primera entrada. El diagrama rojo muestra el flujo de control para A = 0
y el diagrama verde para A = 1
:
Como puede ver, cuando A
está 0
, simplemente lo imprimimos y terminamos (recuerde que todos .
son no-ops). Pero cuando A
es así 1
, la IP atraviesa la primera fila de nuevo, leyendo B
e imprimiendo eso.
En total hay dieciséis soluciones de 5 bytes para esta puerta. Catorce de ellos son esencialmente los mismos que los anteriores, ya sea usando en >
lugar de |
o reemplazando .
con un comando que es efectivamente un no-op, o colocando ?
en la segunda posición:
?.|@! .?|@! ?=|@! =?|@! ?_|@! _?|@! ?0|@!
?.>@! .?>@! ?=>@! =?>@! ?_>@! _?>@! ?0>@!
Y luego hay otras dos soluciones (que son equivalentes entre sí). Estos también implementan la misma lógica de cortocircuito, pero las rutas de ejecución son un poco más locas (y se dejan como ejercicio para el lector):
?<!@|
?<!@<
0010
: A y no B
# ?
# ! )
@ .
Esto también implementa una forma de cortocircuito, pero debido al uso del #
flujo de control es mucho más complicado. #
es un conmutador de IP condicional. Hexagony realidad viene con seis direcciones IP marcadas 0
a 5
, que comienzan en las seis esquinas de la rejilla, apuntando a lo largo de su borde hacia la derecha (y el programa siempre comienza con IP 0
). Cuando #
se encuentra a, el valor actual se toma módulo 6
y el flujo de control continúa con la IP correspondiente. No estoy seguro de qué ataque de locura me hizo agregar esta función, pero ciertamente permite algunos programas sorprendentes (como este).
Distinguiremos tres casos. Cuando A = 0
, el programa es bastante simple, porque el valor siempre es 0
cuando #
se encuentra de tal manera que no se produce el cambio de IP:
#
no hace nada, ?
lee A
(es decir, tampoco hace nada), #
sigue sin hacer nada, !
imprime 0
, lo )
incrementa (esto es importante, de lo contrario la IP no saltaría a la tercera línea), @
finaliza el programa. Suficientemente simple. Ahora consideremos el caso (A, B) = (1, 0)
:
La ruta roja todavía corresponde a IP 0
, y he agregado la ruta verde para IP 1
. Vemos que después de las ?
lecturas A
( 1
esta vez), #
cambia a la IP que comienza en la esquina superior derecha. Eso significa que ?
puede leer B
( 0
). Ahora )
incrementa eso a 1
tal que la #
esquina superior izquierda no hace nada y nos quedamos con IP 1
. El !
imprime 1
y el IP se envuelve alrededor de la diagonal izquierda. #
Todavía no hace nada y @
finaliza el programa.
Finalmente, el caso realmente extraño donde ambas entradas son 1
:
Esta vez, la segunda entrada también es 1
y la )
incrementa a 2
. Eso significa que #
en la esquina superior izquierda causa otro cambio de IP a IP 2
, indicar en azul. En ese camino, primero lo incrementamos aún más 3
(aunque eso es irrelevante) y luego pasamos la ?
tercera vez. Como ahora hemos alcanzado EOF (es decir, la entrada está agotada), ?
devuelve 0
, !
imprime eso y @
finaliza el programa.
Notablemente, esta es la única solución de 6 bytes para esta puerta.
0011
: UNA
? !
@ . .
. .
Esto es lo suficientemente simple como para que no necesitemos un diagrama: lo ?
lee A
, lo !
imprime y @
termina.
Esta es la única solución de 3 bytes para esta puerta. (En principio, también sería posible hacerlo ,;@
, pero la búsqueda no incluyó ;
, porque no creo que pueda guardar bytes !
para esta tarea).
0100
: B y no A
+ ?
| @ !
? .
Este es mucho más simple que su "hermano" 0010
. El flujo de control es en realidad el mismo que hemos visto anteriormente para 0001
(Y). Si A = 0
, entonces el IP atraviesa la línea inferior, leyendo B
e imprimiendo eso antes de terminar. Si A = 1
luego, el IP atraviesa la primera línea nuevamente, también lee B
, pero +
agrega dos bordes de memoria no utilizados, por lo que todo lo que hace es restablecer el valor actual 0
, para que !
siempre se imprima 0
.
Hay bastantes alternativas de 6 bytes para esto (42 en total). Primero, hay un montón de soluciones equivalentes a las anteriores. Nuevamente podemos elegir libremente entre |
y >
, y +
podemos reemplazarlo con cualquier otro comando que nos dé un borde vacío:
"?|@!? &?|@!? '?|@!? *?|@!? +?|@!? -?|@!? ^?|@!? {?|@!? }?|@!?
"?>@!? &?>@!? '?>@!? *?>@!? +?>@!? -?>@!? ^?>@!? {?>@!? }?>@!?
Además, también podemos usar en ]
lugar de ?
. ]
se mueve a la siguiente IP (es decir, selecciona la IP 1
), de modo que esta rama reutiliza ?
en su lugar la esquina superior derecha. Eso da otras 18 soluciones:
"?|@!] &?|@!] '?|@!] *?|@!] +?|@!] -?|@!] ^?|@!] {?|@!] }?|@!]
"?>@!] &?>@!] '?>@!] *?>@!] +?>@!] -?>@!] ^?>@!] {?>@!] }?>@!]
Y luego hay otras seis soluciones que funcionan de manera diferente con diferentes niveles de locura:
/[<@!? ?(#!@] ?(#>@! ?/@#/! [<<@!? [@$\!?
0101
: B
? ?
! @ .
. .
Woohoo, otro simple: leer A
, leer B
, imprimir B
, terminar. Sin embargo, en realidad hay alternativas a esto. Como A
solo es un carácter, también podemos leerlo con ,
:
,?!@
Y también existe la opción de usar un solo ?
y usar un espejo para ejecutarlo dos veces:
?|@! ?>@!
0110
: Xor
? < @
! ! < _
\ ~ ( . .
. . . .
. . .
Como dije anteriormente, esta era la única puerta que no cabía en la longitud lateral 2, por lo que esta es una solución escrita a mano por FryAmTheEggman y yo, y hay una buena posibilidad de que no sea óptima. Hay dos casos para distinguir. Si A = 0
el flujo de control es bastante simple (porque en ese caso solo necesitamos imprimir B
):
Comenzamos en el camino rojo. ?
lee A
, <
es una rama que desvía el cero a la izquierda. La IP se ajusta a la parte inferior, luego _
es otro espejo, y cuando la IP llega a la esquina, se ajusta a la esquina superior izquierda y continúa en el camino azul. ?
lo lee B
, lo !
imprime. Ahora lo (
decrementa. Esto es importante porque garantiza que el valor no sea positivo (es 0
o -1
ahora). Eso hace que IP se ajuste a la esquina derecha, donde @
termina el programa.
Cuando las A = 1
cosas se ponen un poco más difíciles. En ese caso, queremos imprimir not B
, lo que en sí mismo no es demasiado difícil, pero la ruta de ejecución es un poco extraña.
Esta vez, <
desvía la IP hacia la derecha y luego <
actúa como un espejo. Entonces, la IP atraviesa la misma ruta en reversa, leyendo B
cuando se encuentra ?
nuevamente. La IP se ajusta a la esquina derecha y continúa en el camino verde. Encuentra a continuación (~
que es "decremento, se multiplica por -1", que permutas 0
y 1
y por lo tanto calcula not B
. \
es solo un espejo e !
imprime el resultado deseado. Luego ?
intenta devolver otro número pero devuelve cero. La IP ahora continúa en la esquina inferior izquierda del camino azul. (
decrementos, <
refleja,(
disminuye nuevamente, de modo que el valor actual es negativo cuando la IP llega a la esquina. Se mueve a través de la diagonal inferior derecha y luego finalmente golpea @
para terminar el programa.
0111
: O
? <
< @ !
. .
Más cortocircuito.
El A = 0
caso (el camino rojo) es un poco confuso aquí. El IP se desvía a la izquierda, se envuelve en la esquina inferior izquierda, se refleja inmediatamente en el <
y vuelve a la ?
lectura B
. A continuación, envuelve a la esquina rigt, imprime B
con !
y termina.
El A = 1
caso (el camino verde) es un poco más simple. La <
rama desvía la IP hacia la derecha, por lo que simplemente imprimimos !
, volvemos a la esquina superior izquierda y terminamos en @
.
Solo hay otra solución de 5 bytes:
\>?@!
Funciona esencialmente igual, pero las rutas de ejecución reales son bastante diferentes y utiliza una esquina para ramificar en lugar de a <
.
1000
: Ni
) \
! # ?
@ {
Este podría ser mi programa favorito encontrado en esta búsqueda. Lo mejor es que esta implementación nor
realmente funciona para hasta 5 entradas. Tendré que entrar un poco en los detalles del modelo de memoria para explicarlo. Entonces, como una actualización rápida, el modelo de memoria de Hexagony es una cuadrícula hexagonal separada, donde cada borde tiene un valor entero (inicialmente todo cero). Hay un puntero de memoria (MP) que indica un borde y una dirección a lo largo de ese borde (de modo que hay dos bordes vecinos delante y detrás del borde actual, con vecinos significativos izquierdo y derecho). Aquí hay un diagrama de los bordes que usaremos, con el MP comenzando como se muestra en rojo:
Primero consideremos el caso donde ambas entradas son 0
:
Comenzamos en la ruta gris, que simplemente incrementa el borde A para 1
que #
cambie a IP, 1
que es la ruta azul, comenzando en la esquina superior derecha. \
no hace nada allí y ?
lee una entrada. Nos ajustamos a la esquina superior izquierda donde )
incrementa esa entrada. Ahora, siempre que la entrada sea cero, esto dará como resultado a 1
, por lo que eso #
no hace nada. Entonces {
se mueve la MP a la izquierda, es decir, en la primera iteración de A a B . Dado que este borde aún tiene su cero inicial, la IP vuelve a la esquina superior derecha y en un nuevo borde de memoria. Entonces, este bucle continuará mientras ?
lea ceros, moviendo el MP alrededor del hexágono desde Ba C a D y así sucesivamente. No importa si ?
devuelve un cero porque era una entrada o porque era EOF.
Después de seis iteraciones a través de este bucle, {
vuelve a A . Esta vez, el borde ya contiene el valor 1
desde la primera iteración, por lo que la IP se ajusta a la esquina izquierda y continúa en el camino verde. !
simplemente imprime eso 1
y @
finaliza el programa.
Ahora, ¿qué pasa si alguna de las entradas es 1
?
Luego ?
lee eso 1
en algún momento y lo )
incrementa a 2
. Eso significa #
que ahora cambiará las direcciones IP nuevamente y continuaremos en la esquina derecha del camino rojo. ?
lee otra entrada (si hay una), que realmente no importa y se {
mueve un poco más. Esto tiene que ser un borde no utilizado, por lo tanto, funciona para hasta 5 entradas. La IP se ajusta a la esquina superior derecha donde se refleja inmediatamente y se ajusta a la esquina izquierda. !
imprime 0
en el borde no utilizado y #
vuelve a cambiar a IP 0
. Esa IP todavía estaba esperando en el #
, yendo hacia el suroeste (camino gris), por lo que inmediatamente golpea @
y finaliza el programa.
En total, hay siete soluciones de 7 bytes para esta puerta. 5 de ellos funcionan igual que esto y simplemente usan otros comandos para moverse a un borde no utilizado (y pueden caminar alrededor de un hexágono diferente o en una dirección diferente):
)\!#?@" )\!#?@' )\!#?@^ )\!#?@{ )\!#?@}
Y hay otra clase de soluciones que solo funciona con dos entradas, pero cuyas rutas de ejecución son incluso más desordenadas:
?]!|<)@ ?]!|<1@
1001
: Igualdad
( ~
? / @
# !
Esto también hace un uso muy inteligente de la selección de IP condicional. Necesitamos distinguir nuevamente entre A = 0
y A = 1
. En el primer caso queremos imprimir not B
, en el segundo queremos imprimir B
. Porque A = 0
también distinguimos los dos casos para B
. Comencemos con A = B = 0
:
Comenzamos en el camino gris. (~
se puede ignorar, la IP se ajusta a la esquina izquierda (todavía en el camino gris) y se lee A
con ?
. (
disminuye eso, por lo que obtenemos una -1
envoltura de IP en la esquina inferior izquierda. Ahora, como dije antes, #
toma el módulo de valor 6
antes de elegir la IP, por lo que un valor de -1
realmente sale de IP 5
, que comienza en la esquina izquierda del camino rojo. ?
lee B
, (
decrementa eso también para que permanezcamos en IP 5
cuando golpeemos #
nuevamente. ~
niega -1
para que la IP se ajuste a la esquina inferior derecha, imprima 1
y finalice.
Ahora, si B
es así 1
, el valor actual será 0
cuando lleguemos a #
la segunda vez, así que volveremos a IP 0
(ahora en la ruta verde). Eso golpea ?
por tercera vez, cediendo 0
, lo !
imprime y @
termina.
Finalmente, el caso donde A = 1
. Esta vez, el valor actual ya es cero cuando golpeamos #
por primera vez, por lo que esto nunca cambia a IP 5
en primer lugar. Simplemente continuamos inmediatamente en el camino verde. ?
ahora no solo da un cero, sino que regresa B
. !
lo imprime y @
termina de nuevo.
En total, hay tres soluciones de 7 bytes para esta puerta. Los otros dos funcionan de manera muy diferente (incluso el uno del otro), y hacen un uso aún más extraño #
. En particular, leen uno o más valores con ,
(leyendo un código de caracteres en lugar de un entero) y luego usan ese valor módulo 6 para elegir una IP. Es bastante loco.
),)#?@!
?~#,~!@
1010
: No es b
? ?
| @ !
) .
Este es bastante simple. La ruta de ejecución es la rama horizontal que ya conocemos de and
antes. ??
lee A
y luego inmediatamente B
. Después de reflexionar |
y bifurcar, B = 0
ejecutaremos la rama inferior, donde )
incrementa el valor al 1
cual se imprime !
. En la rama superior (si B = 1
), ?
simplemente restablezca el borde en el 0
que también se imprime !
.
Hay ocho programas de 6 bytes para esta puerta. Cuatro de ellos son más o menos lo mismo, usando en >
lugar de |
o en 1
lugar de )
(o ambos):
??>@!) ??>@!1 ??|@!) ??|@!1
Dos usan un solo ?
que se usa dos veces debido a un espejo. La negación entonces sucede como lo hicimos xor
con cualquiera (~
o ~)
.
?>!)~@ ?>!~(@
Y finalmente, dos soluciones usan un conmutador de IP condicional, porque ¿por qué usar la forma simple si la enrevesada también funciona?
??#)!@ ??#1!@
1011
: B implica A
\ #
? ? !
1 @
Esto utiliza una conmutación de IP bastante elaborada. Comenzaré con el A = 1
caso esta vez, porque es más simple:
Comenzamos en el camino gris, que lee A
con ?
y luego golpea el #
. Dado A
que 1
esto cambia a IP 1
(ruta verde). El !
imprime de inmediato que, la IP se ajusta a la parte superior izquierda, lee B
(innecesariamente) y termina.
Cuando las A = 0
cosas se ponen un poco más interesantes. Primero consideremos A = B = 0
:
Esta vez, #
no hace nada y permanecemos en IP 0
(ruta roja desde ese punto en adelante). ?
lo lee B
y lo 1
convierte en a 1
. Después de pasar a la esquina superior izquierda, golpeamos #
nuevamente, así que terminamos en el camino verde después de todo, e imprimimos 1
como antes, antes de terminar.
Finalmente, aquí está (A, B) = (0, 1)
el caso falso:
Tenga en cuenta que he eliminado la ruta gris inicial para mayor claridad, pero el programa comienza de la misma manera y terminamos en la ruta roja como antes. Entonces esta vez ?
vuelve el segundo 1
. Ahora nos encontramos con el 1
. En este punto, es importante comprender qué hacen realmente los dígitos en Hexagony (hasta ahora solo los hemos usado en ceros): cuando se encuentra un dígito, el valor actual se multiplica por 10 y luego se agrega el dígito. Esto normalmente se usa para escribir números decimales textualmente en el código fuente, pero significa que en B = 1
realidad está asignado al valor 11
. Entonces, cuando golpeamos #
, esto se toma un módulo 6
para dar 5
y, por lo tanto, cambiamos a IP 5
(en lugar de 1
como antes) y continuamos en el camino azul. Golpear?
una tercera vez devuelve un cero, por lo !
que imprime eso, y después de otras dos ?
, la IP se ajusta a la parte inferior derecha donde termina el programa.
Hay cuatro soluciones de 7 bytes para esto y todas funcionan de manera diferente:
#)/!?@$ <!?_@#1 \#??!1@ |/)#?@!
1100
: No un
? (
~ ! @
. .
Solo una lineal simple: leer A
con ?
, negar con (~
, imprimir con !
, terminar con @
.
Hay una solución alternativa, y eso es negar con ~)
:
?~)!@
1101
: A implica B
? .
| @ !
) .
Esto es mucho más simple que la implicación opuesta de la que acabamos de hablar. Es nuevamente uno de esos programas de rama horizontal, como el de and
. Si A
es así 0
, simplemente se incrementa 1
en la rama inferior y se imprime. De lo contrario, la rama superior se ejecuta nuevamente donde ?
lee B
y luego !
imprime eso.
Aquí hay un montón de alternativas (66 soluciones en total), principalmente debido a la libre elección de no-operaciones efectivas. Para empezar, podemos variar la solución anterior de la misma manera que podríamos and
y también podemos elegir entre )
y 1
:
?.|@!) .?|@!) ?=|@!) =?|@!) ?_|@!) _?|@!) ?0|@!)
?.|@!1 .?|@!1 ?=|@!1 =?|@!1 ?_|@!1 _?|@!1 ?0|@!1
?.>@!) .?>@!) ?=>@!) =?>@!) ?_>@!) _?>@!) ?0>@!)
?.>@!1 .?>@!1 ?=>@!1 =?>@!1 ?_>@!1 _?>@!1 ?0>@!1
Y luego hay una versión diferente que usa la selección de IP condicional, donde el primer comando se puede elegir casi arbitrariamente, y también hay una opción entre )
y 1
para algunas de esas opciones:
"?#1!@ &?#1!@ '?#1!@ )?#1!@ *?#1!@ +?#1!@ -?#1!@ .?#1!@
0?#1!@ 1?#1!@ 2?#1!@ 3?#1!@ 4?#1!@ 5?#1!@ 6?#1!@ 7?#1!@
8?#1!@ 9?#1!@ =?#1!@ ^?#1!@ _?#1!@ {?#1!@ }?#1!@
"?#)!@ &?#)!@ '?#)!@ *?#)!@ +?#)!@ -?#)!@
0?#)!@ 2?#)!@ 4?#)!@ 6?#)!@
8?#)!@ ^?#)!@ _?#)!@ {?#)!@ }?#)!@
1110
: Nand
? $
@ # )
! <
El último complicado. Si todavía estás leyendo, casi lo logras. :) Veamos A = 0
primero:
?
lee A
y luego golpeamos $
. Este es un comando de salto (como el de Befunge #
) que omite la siguiente instrucción para que no terminemos en el @
. En cambio, la IP continúa en #
. Sin embargo, como A
es 0
, esto no hace nada. )
lo incrementa para 1
que la IP continúe en la ruta inferior donde 1
se imprime. El <
desvío de la IP hacia la derecha donde se ajusta a la esquina izquierda y el programa termina.
Luego, cuando la entrada es (A, B) = (1, 0)
, obtenemos esta situación:
Es esencialmente lo mismo que antes, excepto que en el #
cambio a IP 1
(ruta verde), pero dado B
que 0
cambiamos a IP 0
cuando golpeamos #
por segunda vez (ahora ruta azul), donde se imprime 1
como antes.
Finalmente, el A = B = 1
caso:
Esta vez, cuando somos #
la segunda vez, el valor actual sigue siendo 1
así que no volveremos a cambiar la IP. Lo <
refleja y la tercera vez que golpeamos ?
obtenemos un cero. Por lo tanto, la IP se ajusta a la parte inferior izquierda donde !
imprime el cero y finaliza el programa.
Hay nueve soluciones de 7 bytes en total para esto. La primera alternativa simplemente usa en 1
lugar de )
:
?$@#1!<
Luego, hay dos soluciones que te ayudarán con la cantidad de conmutación de IP que está ocurriendo:
)?#_[!@ 1?#_[!@
Esto realmente me dejó alucinado: la parte interesante es que la conmutación de IP se puede usar como un condicional diferido. Las reglas de cambio de IP del lenguaje son tales que la IP actual da otro paso antes de que ocurra el cambio. Si ese paso pasa por una esquina, entonces el valor actual decide en qué rama continuará la IP si alguna vez volvemos a ella. Exactamente esto sucede cuando la entrada es A = B = 1
. Aunque todo esto es coherente con la forma en que diseñé el lenguaje, nunca fui consciente de esta implicación de la especificación, por lo que es bueno cuando mi lenguaje me enseña algunos trucos nuevos: D.
Luego hay una tercera solución cuya cantidad de conmutación de IP es aún peor (aunque no hace uso de ese efecto condicional diferido):
>?1]#!@
Y luego hay otro:
?$@#)!<
Y luego están estas cuatro soluciones equivalentes, que utilizan una conmutación de IP no condicional y en su lugar implementan toda la lógica a través de ramas y esquinas:
]<?<@!) ]<?<@!1 ]|?<@!) ]|?<@!1
1111
: Cierto
1 !
@ . .
. .
Te has ganado algo simple para el final: establece el borde en 1
, imprime con !
, termina con @
. :)
Por supuesto, hay una alternativa:
)!@
Como de costumbre, todos los diagramas de flujo de control creados con HexagonyColorer de Timwi y el diagrama de memoria con su EsotericIDE .