Programación de bloques de tetris (literalmente)

33

En el juego Tetris , hay 7 tipos de ladrillos o Tetr i minoes , que matemáticamente se conocen como tetr o minoes porque todos están hechos con 4 segmentos cuadrados:

Ladrillos de tetris

Tienen los nombres I, J, L, O, S, T y Z, que corresponden a sus formas aproximadas. Contando rotaciones de 90 °, hay 19 formas únicas en total:

I
I
I
I

IIII

 J
 J
JJ

JJJ
  J

JJ
J
J

J
JJJ

L
L
LL

  L
LLL

LL
 L
 L

LLL
L

OO
OO

 SS
SS

S
SS
 S

TTT
 T

T
TT
T

 T
TTT

 T
TT
 T

ZZ
 ZZ

 Z
ZZ
Z

Reto

Escriba un bloque rectangular de código que actúe como el segmento base del que están hechas estas 19 formas. Cuando este código se organiza en una de las formas, se debe formar un programa que genere la letra mayúscula única asociada con esa forma. Esto debe funcionar para las 19 formas.

Las principales áreas vacías presentes en algunas de las 19 formas se completan completamente con espacios ( ). Las áreas vacías finales no están llenas de nada (por lo que los programas no siempre son exactamente rectangulares).

Ejemplo

Supongamos que este fuera su bloque de código:

ABC
123

Entonces, cualquier disposición del bloque en la pieza S Tetris sería un programa que imprime S:

   ABCABC
   123123
ABCABC
123123

ABC
123
ABCABC
123123
   ABC
   123

(Observe que todo el espacio vacío inicial está lleno de caracteres de espacio, y que ninguna línea tiene espacios finales).

La misma idea se aplica a las otras 6 piezas y sus respectivas rotaciones.

Notas

  • Los 19 programas finales se ejecutarán en el mismo lenguaje de programación.
  • Si lo desea, puede agregar una nueva línea final a todos los programas (no solo algunos, todos o ninguno).
  • Su bloque de código puede contener cualquier carácter (incluidos espacios) que no sean terminadores de línea .
  • Envíe la carta a stdout (o la alternativa más cercana a su idioma) con una nueva línea final opcional.

Tanteo

El envío cuyo bloque de código tiene el área más pequeña (ancho por alto) gana. Esto esencialmente significa que gana el código más corto, razón por la cual esto se etiqueta con . Tiebreaker va a la respuesta más votada .

El ABC\n123ejemplo tiene área 3 × 2 = 6.

Retazo

Dado un bloque de código, este fragmento generará los 19 programas:

<script>function drawShape(X,n,v){for(var t="",e=0;e<v.length;e++)for(var l=0;l<n.length;l++){for(var r=0;r<v[e].length;r++)t+="X"===v[e][r]?n[l]:X[l];t+="\n"}return t}function go(){var X=document.getElementById("input").value;if(0!=X.length){var n=X.replace(/./g," ").split("\n");X=X.split("\n");for(var v="I (v1):|I (v2):|J (v1):|J (v2):|J (v3):|J (v4):|L (v1):|L (v2):|L (v3):|L (v4):|O:|S (v1):|S (v2):|T (v1):|T (v2):|T (v3):|T (v4):|Z (v1):|Z (v2):".split("|"),t="X\nX\nX\nX|XXXX| X\n X\nXX|XXX\n  X|XX\nX\nX|X\nXXX|X\nX\nXX|  X\nXXX|XX\n X\n X|XXX\nX|XX\nXX| XX\nXX|X\nXX\n X|XXX\n X|X\nXX\nX| X\nXXX| X\nXX\n X|XX\n XX| X\nXX\nX".split("|"),e="",l=0;l<v.length;l++)e+=v[l]+"\n\n"+drawShape(n,X,t[l].split("\n"))+"\n";e=e.substring(0,e.length-2),document.getElementById("output").value=e}}</script><style>html *{font-family: monospace;}</style>Code Block:<br><textarea id='input' rows='8' cols='64'>ABC&#010;123</textarea><br><button type='button' onclick='go()'>Go</button><br><br>All 19 Programs:<br><textarea id='output' rows='24' cols='64'></textarea>

Pasatiempos de Calvin
fuente
Entonces, ¿la relación longitud-ancho es de 2 a 3? ¿O puede ser de otro tamaño? Además, ¿qué debe hacer el programa, como mínimo? Suponiendo que los programas vacíos no cuentan, pero los programas que no generan nada sí lo hacen.
ASCIIThenANSI
@ASCIIThenANSI Cualquier ancho y alto están bien. Me imagino que será necesario algo más grande que 2 * 3. Hay 19 programas, uno para cada disposición del bloque en una de las 19 formas distintas de tetromino. Cuando se ejecuta uno de esos programas, genera la letra de pieza de tetris correspondiente.
Hobbies de Calvin
¡Guauu! ¡Qué gran desafío! ¿Importa qué idioma usamos?
theonlygusti
@theonlygusti Casi todas las preguntas en este sitio permiten cualquier idioma. Esto no es una excepción.
Aficiones de Calvin
@ Calvin'sHobbies Sí, lo sé; Acabo de malinterpretar el fragmento como un controlador para ejecutar respuestas de JavaScript. Aparentemente solo organiza bloques de código.
theonlygusti

Respuestas:

16

<> <(Pez) - 12 * 32 = 384

Estaba planeando buscar una solución más elegante, pero de alguna manera terminé con esto, que es bastante fuerza bruta:

c  0  g84*%\
c2*0  g84*%\
0  84*g84*%\
c  84*g84*%\
c2*84*g84*%\
0  88*g84*%\
c  88*g84*%\
?v         \
;>?v~~?vv   
"L" o;  >   
"S" o; >~?v 
"T" o;    > 
;  >~?v"L"o 
;     >"J"o 
?v         \
 >~?v~~?vv  
"I"  o;  >  
"J"  o; >   
    \~~?vv  
"T"  o;  >  
"Z"  o; >   
?v         \
 >?v"J"o;   
   >?v"Z"o; 
"L"o;>?!v   
"J"o;   >?v 
"T"o;     > 
?v?v"I"o;  >
   >"L"o;   
 >?v"T"o;   
   >?v"O"o; 
     >"S"o; 

Es bastante simple, verifica el código en un cuadrado de 3x3 para texto y usa los resultados para ver qué tetrimino corresponde a la forma del código. No hice mucho esfuerzo para jugar golf todavía.

Pruebe el código aquí (después de usar el fragmento para darle forma como un tetrimino)

Ejemplo de código en forma Z (v1) aquí

Thijs ter Haar
fuente
14

C (gcc) , 26x20 = 520 25x19 = 475 23x17 = 391

#ifndef M            //
#define M(a,b)a##b   //
#define W(z,x)M(z,x) //
char*s,*S[]={"!!!!c",//
"8M !7! M8 878","77",//
"7!!MO887","788OM!!7"//
,"N7!78","7N87!"},r[5//
],*p=r;i=7;main(){for//
(;i--;)strstr(S[i],r)//
&&putchar("ITOJLSZ"[i//
]);}                 //
#endif               //
__attribute__((      //
constructor(__LINE__)//
))W(f,__LINE__)(){s= //
"                     \
";*p++=strlen(s)+12;}//

Recientemente fui informado de los atributos de función de GNU, y lo más interesante del constructoratributo, que permite una implementación más concisa de lo que estaba haciendo de una manera más indirecta en mi enfoque anterior de este problema.

El objetivo de la idea es el mismo que antes: construir una cadena y buscarla en una lista para identificar qué bloque de tetris se presenta el código. Esto se hace llamando a funciones, cada una agregando un carácter a la cadena. La complicación fue y sigue siendo que el número de funciones varía.

La definición de una función attribute((constructor(x)))hace que la función se ejecute antes de que main()se ingrese, xsiendo la prioridad la opción opcional (menor significa que se ejecuta antes). Esto elimina la necesidad de punteros de función, lo que nos permite descartar una macro, algunas declaraciones y la cadena de llamada.

Usar __LINE__para prioridad es dudoso, ya que los niveles de prioridad 0-100 están reservados. Sin embargo, no genera errores, solo advertencias, y son abundantes cuando se juega al golf, entonces, ¿qué más?

Hubiera ayudado a eliminar otra columna para no usar prioridades en absoluto, pero el orden de ejecución no parece estar definido. (Se invierten en este caso, pero otras pruebas no son concluyentes).

Ejemplo de L v2 aquí

Enfoque más antiguo y portátil

#ifndef M              //
#define M(a,b) a##b    //
#define W(z,x)M(z,x)   //
#define F W(f,__LINE__)//
#define A W(a,__LINE__)//
char r[5],*S[]={"####k"//
,";<<US##;",";##SU<<;",//
";;",";T<;#","<S #;# S"//
"< <;<","T;#;<"},*s,*p=//
r;i;typedef(*T)();T a17//
,a36,a55,a74;main(){for//
(a17(),a36&&a36(),a55&&//
a55(),a74&&a74();i--;) //
strstr(S[i],r)&&putchar//
("ILJOZTS"[i]);}i=7;   //
#endif                 //
F();T A=F;F(){s=       //
"                       \
";*p++=strlen(s)+12;}  //

Uno de mis problemas favoritos que he resuelto en este sitio.

Comencé imaginando que cada bloque adivinaría sus propias coordenadas de alguna manera. Las filas son fáciles __LINE__y el número de bloques adyacentes horizontalmente se puede encontrar usando la longitud de un literal de cadena, de esta manera:

char*s=//char*s=//
"       ""       "
;        ;        

Tome la longitud de la cadena resultante y divida por un número apropiado y tendrá el ancho. Lamentablemente, cualquier espacio vacío antes del bloque es invisible por este método. Todavía sospechosos cuerdas sería la solución, ya que los espacios en blanco sólo tiene sentido fuera de cadenas muy raramente, en cosas como a+++bvs a+ ++b. Brevemente consideré algo así, pero no se me ocurrió nada útil. Otra posibilidad habría sido permitir que los identificadores se "peguen" juntos donde se encuentran los bloques:

A  BA  B

No me sorprendería si esto todavía podría ser una solución interesante.

A pesar de su simplicidad, me llevó bastante tiempo encontrar la solución de cadena, que se basa en este fragmento de bloque:

s=//
"  \
";//

Si el fragmento no tiene vecinos horizontales, la nueva línea en la segunda línea se escapa por la barra invertida, creando una cadena de longitud 2. Sin embargo, si tiene un vecino, la barra invertida escapará de la comilla al comienzo de la línea 2 del siguiente bloque:

s=//s=//
"  \"  \
";//";//

Esto creará la cadena "\" "de longitud 5.

Más importante aún, esto también permite la detección de espacio vacío antes del bloque:

    s=//
    "  \
    ";//

Nuevamente, la nueva línea se escapa y el espacio en blanco del bloque vacío a la izquierda se incluye en la cadena resultante "" de longitud 6.

En total, hay siete configuraciones diferentes de bloques en una fila de las que debemos preocuparnos, y todas ellas forman cadenas de longitudes únicas:

2 "  "
---
s=//
"  \
";//

5 "  \"  "
---
s=//s=//
"  \"  \
";//";//

6 "      "
---
    s=//
    "  \
    ";//

9 "  \"      "
----
    s=//s=//
    "  \"  \
    ";//";//

10 "          "
---
        s=//
        "  \
        ";//

8 "  \"  \"  "
---
s=//s=//s=//
"  \"  \"  \
";//";//";//

11 "  \"  \"  \"  "
----
s=//s=//s=//s=//
"  \"  \"  \"  \
";//";//";//";//

Los bloques finales, por supuesto, no tendrán una longitud tan corta, pero el principio es el mismo independientemente del tamaño del bloque. Esto también tiene la ventaja de que no es necesario un mecanismo separado para detectar el ancho. Al agregar un carácter correspondiente a la longitud de esta cadena a una cadena de resultados, cada una de las 19 configuraciones produce una cadena única, que solo necesita ser comparada con una lista adecuada una vez que se han ejecutado todos los bloques.

Una vez que esto se resolvió, el siguiente gran problema fue cómo "visitar" cada fila de bloques. En C, estamos muy limitados a lo que se puede hacer fuera de las funciones. También necesitamos main()aparecer, pero solo una vez. Esto último se logra fácilmente por algunos #defines, pero si queremos que el código de los bloques posteriores esté dentro main(), el problema de cómo saber cuándo colocar el corchete final de cierre. Después de todo, no sabemos cuántas filas de bloques se usarán realmente. Por lo tanto, necesitamos tener main()estática y, de alguna manera, el resto para ser dinámicos.

Si las otras filas de bloques deben ser autocontenidas, deben ser funciones, pero debemos asegurarnos de que cada función tenga un nombre que sea único y, al mismo tiempo, sea lo suficientemente predecible como para ser invocable main(). También necesitamos un mecanismo para saber qué funciones están realmente allí para ser llamadas. La generación de nombres únicos se resuelve mediante macros de ayuda:

#define M(a,b) a##b     //
#define W(z,x)M(z,x)    //
#define F W(f,__LINE__) //
#define A W(a,__LINE__) //

Llamar Fcreará un identificador cuyo nombre comienza con una f y termina con el número de línea. Ahace lo mismo pero con un prefijo as, que se usa para la segunda parte de la solución, que es punteros de función. Declaramos cuatro de estos indicadores:

typedef(*T)();T a17,a36,a55,a74;

Como se declaran como variables globales, se establecen convenientemente en NULL. Más adelante, cada fila de bloques tendrá el siguiente código:

F();T A=F;F()

Esto declarará primero una función, definirá el puntero de función apropiado para apuntar a esa función (solo podemos definir globales una vez, pero la declaración anterior no contaba como una definición, incluso si se inicializaba a NULL), y luego definía el real función. Esto permite main()llamar a cualquier puntero de función que no sea NULL (a17 nunca será NULL):

a17(),a36&&a36(),a55&&a55(),a74&&a74()

Al hacerlo, se generará la cadena r, que luego se busca en la tabla de cadenas y, si se encuentra, se genera la letra correspondiente.

El único truco que queda es que la lista de cadenas con las que se debe coincidir se acorta cada vez que se puede evitar la ambigüedad o se pueden combinar cadenas superpuestas.

Ejemplo de L v2 aquí

gastropner
fuente
6

x86 opcode (.com), 86 82 bytes

Ensayador:

org 100h
macro e {
db $F6,$04,$DF,$78,$13,$75,$08,$00,$C0,$40,$83,$C6,$52,$EB,$F1,$88
db $C2,$00,$D0,$00,$D0,$46,$EB,$E8,$05,$02,$40,$73,$ED,$E8,$26,$00
db $50,$08,$43,$4D,$2C,$0C,$1C,$15,$A5,$14,$10,$13,$3F,$27,$20,$0F
db $51,$1D,$29,$49,$49,$4A,$4A,$4A,$4A,$4C,$4C,$4C,$4C,$4F,$53,$53
db $54,$54,$54,$54,$5A,$5A,$5F,$AE,$75,$FD,$8A,$55,$12,$B4,$02,$CD
db $21,$C3
}

macro n { db 82 dup $20 }

macro s { db 10 }

n
e
s
n
e
s
e
e  

Fuente:

BOF:    ;mov bx, 100h
p:      test [si], byte $DF
        js _a ; exist
        jnz _b ; newline
_z:     add al, al
        inc ax
q:      add si, EOF-BOF
        jmp p
_b:     mov dl, al
        add al, dl
        add al, dl
        inc si
        jmp p
_a:     add ax, 4002h
        jnc q
        call y
        db 80,8,67,77,44,12,28,21,165,20,16,19,63,39,32,15,81,29,41
        db 'IIJJJJLLLLOSSTTTTZZ'
y:      pop di
        scasb
        jnz y+1
        mov dl,[di+18]
        mov ah,2
        int $21
        ret
EOF:

Ejecutar en win7dos donde init AX = 0, SI = 100, BX = 0 Referencias

l4m2
fuente
Si se siente cómodo reduciendo un poco el número de entornos compatibles, puede suponer SI = 100h y usar ese registro en lugar de BX para la indexación, para ahorrar 3 bytes cayendo mov bx, 100hal inicio.
Gastropner
@gastropner Hecho y arreglado un punto en el que no me di cuenta
l4m2