Tetris! Alturas finales (día 3)

19

Reto Tomado de mi concurso de desafío de código de la universidad

Este es en realidad el Día 0, pero el desafío de ayer fue demasiado fácil y puede ser una trampa de otra pregunta aquí.


Tetris es un videojuego que se hizo popular en los años 80. Consiste en colocar una serie de piezas con diferentes formas que caen en un tablero, para que encajen de la manera más compacta posible.

En este problema asumiremos una secuencia de piezas que caen, cada una en una determinada posición y con una determinada orientación que no se puede cambiar. Las piezas se apilan a medida que caen y las filas completas no se eliminan (como en el juego original). El objetivo es determinar la altura final de cada columna del tablero después de que caen todas las piezas.

Hay un total de 7 piezas diferentes, que se muestran en la figura:

formas

Desafío

Dada una lista de piezas, muestre la altura de todas las columnas del tablero después de que todas las piezas caigan

Una pieza consta de tres números: I, R y P. El primer número, I, es el identificador de la pieza (un número entre 1 y 7, en el mismo orden que en la figura). El segundo número, R, es la rotación de la pieza. Puede tomar los valores 0, 90, 180 o 270 y representa el ángulo de rotación de la pieza en el sentido antihorario. El tercer número, P, indica la posición de la pieza. Representa la columna a la izquierda ocupada por la pieza (puede ser un índice 1 o 0. Especifique).

Ejemplo y caso de prueba (1 índice)

  • Dado [[1, 0, 1], [4, 0, 1], [5, 90, 4]]

caso 1

  • Salida [3, 3, 1, 3, 2]

  • Dado [[6, 270, 4], [1, 180, 5], [1, 90, 6], [7, 0, 4]]

caso # 2

  • Salida [0, 0, 0, 9, 9, 8, 3, 3]

  • [[3,0,1],[3,180,3]]Salida dada[1,1,4,4,4]

  • [[2,180,1],[2,0,3]]Salida dada[2,2,4,3,3]

Notas

  • Este es el
  • La fila / columna puede ser 1 o 0 índice. Por favor especifica.
  • Puede redefinir los valores de entrada (tal vez desee llamar a la pieza 1 como A, etc.). En ese caso, especifique

Preguntas

  • ¿Podemos usar 4 valores distintos en lugar de un ángulo en grados ?:

  • ¿Se supone que debemos manejar "agujeros" si una pieza no encaja exactamente sobre los anteriores ?:

  • ¿Es acotada la altura o el ancho del tablero? No. Ni el ancho ni la altura están delimitados


Gracias @Arnauld por las imágenes y los casos de prueba *. *

Luis felipe De jesus Munoz
fuente
Puede I, Ry Pser introducidos en un orden diferente?
Neil
@Neil sí. Puede estar en cualquier orden
Luis felipe De jesus Munoz
Si podemos redefinir los valores de entrada, ¿puedo tomar la identificación de la pieza como una matriz que representa la forma de las piezas (sin rotación)?
Encarnación de la ignorancia
1
Creo que no podemos ingresar una matriz que represente la forma de las piezas por 2 razones. La entrada está claramente definida: 1,2,3 .. o A, B, C .. Y una parte fundamental de este desafío es manejar esta restricción.
AZTECCO
1
¿Estaría bien incluir los ceros al final?
dana

Respuestas:

10

JavaScript (Node.js) ,  286 284 270  266 bytes

Las piezas y las posiciones están indexadas en 0. Los ángulos están en .[0..3]

a=>a.map(([p,r,x])=>(g=y=>y>3?g(+!Y--):b[Y+y]&(m[y]=('0x'+`717433667233ff4717333327661${1e12+0x5e7056a566ffff57efa65n.toString(4)}`[(p*2+r*56+y*99+13)%113])<<x)?m.map(v=>(g=x=>v&&g(x+1,H[x]=v&1?Y:~~H[x],v>>=1))(0,b[++Y]|=v)):g(y+1))(Y=a.length*4),m=[b=[-1]],H=[])&&H

Pruébalo en línea! o pruebe una versión mejorada que también muestre el tablero final.

Codificación de forma

Todas las piezas se almacenan exactamente como 4 nibbles (4x4 bits) con las filas ordenadas en orden inverso y el píxel más a la izquierda asignado al bit menos significativo. En otras palabras, la representación binaria de la forma se refleja tanto vertical como horizontalmente.

Ejemplo:

ejemplo de codificación de forma

Función hash y tabla de búsqueda

Dada una pieza , una rotación y una fila , utilizamos la siguiente función hash para obtener el índice de máscara de bits correspondiente de una tabla de búsqueda:p[0..6]r[0..3]y[0..3]n

n=(2p+56r+99y+13)mod113

Solo las primeras entradas se almacenan explícitamente. Todo lo demás se establece en .820

Estas entradas se empaquetan como:

`717433667233ff4717333327661${1e12+0x5e7056a566ffff57efa65n.toString(4)}`

que se expande a los siguientes 82 mordiscos:

"717433667233ff47173333276611000000000000113213001112221112123333333311133233221211"

El uso de hexadecimal en el formato final solo se requiere para las dos representaciones horizontales de la pieza , por lo tanto, en la cadena anterior.I"ff"

Los parámetros de la función hash fueron forzados por la fuerza bruta de una manera que optimiza los ceros iniciales y finales. El hecho de que la cadena se pueda comprimir un poco más usando 1e12los ceros en el medio y una conversión de base-16 a base-4 para la parte derecha es solo un efecto secundario bienvenido pero inesperado. :-)

Aquí hay una demostración del proceso de desembalaje para todas las piezas y todas las rotaciones.

Comentado

a => a.map(([p, r, x]) => (     // for each piece p with rotation r and position x:
  g = y =>                      //   g = recursive function taking y
    y > 3 ?                     //   if y is greater than 3:
      g(+!Y--)                  //     reset y to 0, decrement Y and try again
    :                           //   else:
      b[Y + y] & (              //     test if we have a collision of the board with
        m[y] =                  //     the y-th row m[y] of the current piece
          ('0x' + `717...`[     //     which is extracted from a lookup table
            (p * 2 + r * 56 +   //     using the hash function described in the
             y * 99 + 13) % 113 //     previous paragraph
          ]) << x               //     and shifted to the left according to x
      ) ?                       //     if we have a collision:
        m.map(v => (            //       we iterate again on the piece rows stored in m[]
          g = x =>              //         g = recursive function taking x
            v &&                //         if v is not equal to 0:
            g(                  //           do a recursive call:
              x + 1,            //             increment x
              H[x] =            //             update the height at x:
                v & 1 ?         //               if this bit is set:
                  Y             //                 set it to Y
                :               //               else:
                  ~~H[x],       //                 leave it unchanged or force it to 0
                                //                 if it was still undefined
              v >>= 1           //             shift v to the right
            )                   //           end of recursive call
          )(0,                  //         initial call to g with x = 0
               b[++Y] |= v)     //         increment Y and copy the piece row to the board
        )                       //     end of map()
      :                         //   else (no collision):
        g(y + 1)                //     do a recursive call to test the next row
  )(Y = a.length * 4),          //   initial call to g with y = Y = 4 * the number of pieces
                                //   (assuming the worst case: piled vertical I pieces)
  m = [b = [-1]], H = []        //   initialize m[], b[] and H[]
                                //   we set a full line at the bottom of b[]
) && H                          // end of map(); return H[]
Arnauld
fuente
3
Buen trabajo empacando / desempacando las piezas. Eso es realmente impresionante :)
dana
7

C (clang) , 253 239 221 212 bytes

t(*a,*b,c){char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVWhU😎EQV😀RTYT😉UU";for(size_t*p,f,n,y,i;c--;b++){f=1<<(8-*b)/3;p=z+*b++*8+*b++%f*2;f=n=*p;for(y=i=0;i<=f%4;y=fmax(y,a[*b+i++]+n%4))n/=4;for(;i--;a[*b+i]=y+n%4)n/=4;}}

Pruébalo en línea!

ps En realidad, el tamaño del código es de 221 bytes (pero 212 caracteres) debido a los caracteres UNICODE codificados en UTF-8. Pero tio.run lo trata como un código de 212 bytes ...

El tamaño del código en mi computadora es de 209 caracteres (218 bytes). Pero no pude reemplazar \225por char visible en tio.run 😞

Código sin golf

// a - output array (must be zeroed), b - array of block info, c - number of blocks

// Figure codes: 2->0, 3->1, 6->2, 1->3, 5->4, 7->5, 4->6 (0,1 are L-figures, 2 is is T-figure, 3 is a line 1x4; 4,5 are zigzags; 6 is a cube 2x2)
// Vertical and horizontal positions are zero-indexed, angles = 0..3

t(*a,*b,c)
{
  char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVWhU😎EQV😀RTYT😉UU";  // UTF-8
//char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVW\1hU😎\26EQV😀RTYT😉UU";  // 3 bytes longer (use it if you can't copy previous string correctly) :)
  // Blocks
  for(size_t*p,f,n,y,i;c--;b++){
    f=1<<(8-*b)/3;  // number of figure variants
    p=z+*b++*8+*b++%f*2;
    // Get top base line position (y)
    f=n=*p;  // figure width, TBLs and HATs
    for(y=i=0;i<=f%4;
      y=fmax(y,a[*b+i++]+n%4))
      n/=4;
    // Add heights (HATs)
    for(;i--;
      a[*b+i]=y+n%4)
      n/=4;
  }
}  // 215 chars (224 bytes)

Descripción

Busquemos la línea base superior ( TBL ) de cada figura y describámosla como un número de celdas debajo de TBL para cada posición horizontal. También describamos el número de celdas (altura) sobre TBL ( HAT ).

P.ej:

                       ________ ________
_ [] _____ SOMBRERO = 1,0,0 [] [] [] SOMBRERO = 0,0,0 ___ [] [] _ ​​SOMBRERO = 0,1,1 [] [] [] SOMBRERO = 0,0,0
 [] [] [] TBL = 1,1,1 [] TBL = 2,1,1 [] [] TBL = 1,1,0 [] TBL = 1,2,1

Describamos TBL y HAT para cada figura y cada ángulo de rotación:

Ancho TBLs SOMBREROS
----- ------- -------
L-figuras:
  3 1 1 1 1 0 0 // 0 °
  2 1 1 0 2 // 90 °
  3 1 1 2 0 0 0 // 180 °
  2 3 1 0 0 // 270 °

  3 1 1 1 0 0 1 // 0 °
  2 1 3 0 0 // 90 °
  3 2 1 1 0 0 0 // 180 °
  2 1 1 2 0 // 270 °

Figura T:
  3 1 1 1 0 1 0 // 0 °
  2 1 2 0 1 // 90 °
  3 1 2 1 0 0 0 // 180 °
  2 2 1 1 0 // 270 °

Línea:
  4 1 1 1 1 0 0 0 0 // 0 °, 180 °
  1 4 0 // 90 °, 270 °

Zigzags:
  3 1 1 0 0 1 1 // 0 °, 180 °
  2 1 2 1 0 // 90 °, 270 °

  3 0 1 1 1 1 0 // 0 °, 180 °
  2 2 1 0 1 // 90 °, 270 °

Cubo:
  2 2 2 0 0 // cualquier ángulo

Ahora deberíamos codificar estos números como una secuencia de 2 bits y colocarlos en una matriz (reemplazando 4 0por 3 1un ángulo de 90 ° de "línea" para que quepa en 2 bits; el resultado será el mismo y disminuirá los anchos en 1).

Codificaremos en orden: ancho (en 2 LSB), TBL , HAT (hacia atrás para bucle hacia atrás). Ej 2 2 1 1 0 para 270 ° ángulo de T-figura será codificado como 1 0 1 2 1(última 1 es ancho-1 ): 0b0100011001 = 281.

actualizado 12.02:

a) He convertido una matriz en una cadena y he guardado 18 caracteres (puede ver el código anterior de 239 bytes ) :))

b) Más optimización, el código se reduce en 9 caracteres.
Este es mi último intento (¡creo que sí, jaja!) 😀

Jin X
fuente
1
Puedes atacar usando <s> ... </s>.
Jonathan Frech
1
243 bytes .
Jonathan Frech
Oh genial Gracias. Lol :))
Jin X
¡Guauu! Tetris de bajo nivel 🤔
Rustem B.
TBL es el número de celdas de figuras debajo de la línea más alta que solo tiene espacio libre o bloques de celdas debajo y encima (sin espacio libre y luego celdas). TBL + HAT = altura de la figura (en cada posición horizontal). TBL> 0 y HAT> 0 también.
Jin X
5

Lisp común, 634 bytes

(let((w(make-hash-table))(r 0))(defun z(c)(or(gethash c w)0))(defun x(c v)(setf r(max r c))(setf(gethash c w)v))(defun m(s)(dolist(c s)(apply(lambda(n u p)(let*((i(let*((j'(2 2 2))(k'(3 3))(l'(2 3))(m'(3 2))(o(case n(1(list'(1 1 1 1)'(4)))(2(list j k'(1 1 2)'(3 1)))(3(list j'(1 3)'(2 1 1)k))(4(list'(2 2)))(5(list'(2 2 1)l))(6(list j l'(1 2 1)m))(7(list'(1 2 2)m)))))(setf(cdr(last o))o)))(o(nth(+ u 2)i))(b(nth u i))(s(length o))(d 0)(h 0))(dotimes(i s)(let*((w(nth i b))(g(z(+ i p)))(m(+ g w)))(when(> m d)(setf d m)(setf h(- g(-(apply'max b)w))))))(dotimes(i s)(x(-(+ s p)i 1)(+(nth i o)h)))))c))(dotimes(i r)(print(z (+ i 1))))))

Verboso

(defun circular (list)
  (setf (cdr (last list)) list))

(defun get-piece (piece-number)
  (circular (case piece-number
              (1 (list '(1 1 1 1)
                       '(4)))
              (2 (list '(2 2 2)
                       '(3 3)
                       '(1 1 2)
                       '(3 1)))
              (3 (list '(2 2 2)
                       '(1 3)
                       '(2 1 1)
                       '(3 3)))
              (4 (list '(2 2)))
              (5 (list '(2 2 1)
                       '(2 3)))
              (6 (list '(2 2 2)
                       '(2 3)
                       '(1 2 1)
                       '(3 2)))
              (7 (list '(1 2 2)
                       '(3 2))))))

(let ((world (make-hash-table))
      (rightmost-column 0))
  (defun get-world-column (column)
    (or (gethash column world) 0))

  (defun set-world-column (column value)
    (setf rightmost-column (max rightmost-column column))
    (setf (gethash column world) value))

  (defun drop-piece (piece-number rotation position)
    (let* ((piece (get-piece piece-number))
           (top (nth (+ rotation 2) piece))
           (bottom (nth rotation piece))
           (size (length top))
           (max-combined-height 0)
           (contact-height 0))
      (dotimes (i size)
        (let* ((down-distance (nth i bottom))
               (height (get-world-column (+ i position)))
               (combined-height (+ height down-distance)))
          (when (> combined-height max-combined-height)
            (setf max-combined-height combined-height)
            (setf contact-height
                  (- height
                     (- (apply #'max bottom)
                        down-distance))))))
      (dotimes (i size)
        (set-world-column (- (+ size position) i 1)
                          (+ (nth i top) contact-height)))))

  (defun drop-pieces (pieces)
    (dolist (piece pieces)
      (apply #'drop-piece piece)))

  (defun print-world ()
    (loop for i from 1 to rightmost-column
          do (print (get-world-column i)))))

(defun play-tetris (pieces)
  (drop-pieces pieces)
  (print-world))

Pruébalo

Las piezas son listas circulares de listas de números. Cada una de estas sublistas representa un lado de la forma, los números indican qué tan lejos están del lado opuesto. Se dejan de izquierda a derecha cuando ese lado está en la parte inferior, de derecha a izquierda cuando está en la parte superior, de arriba a abajo cuando está a la izquierda y de abajo a arriba cuando está a la derecha. Estas opciones de diseño eliminan la necesidad de escribir código para la rotación. Desafortunadamente, la falta de código de rotación no parece haber compensado las largas representaciones de formas, o la lógica algo complicada que usé para calcular las nuevas alturas de columna.

La rotación es un número entero no negativo. 0 = 0 grados, 1 = 90 grados, 2 = 180 grados, 4 = 270 grados

Charlim
fuente
5

C # (compilador interactivo de Visual C #) , 308 bytes

a=>{var o=new int[a.Max(x=>x.Item3+4)];foreach(var(i,r,p)in a){var b="\"4TqzŒª!\0\0HSš	Ó\0$\n\0!“A“š š@";int m=0,n=b[i],t=0,u=n/8+r%(n%8),v=b[u*=2]<<8|b[u-1];for(;t<v/8%8;m=m>n?m:n)n=o[p+t]+v%8-(n=(u=v>>6+3*t++)/2&1)-(n&u);for(;t-->0;)o[p+t]=m-(n=(u=v>>6+3*t)/4&1)-(n&u);}return o;}

Pruébalo en línea!

OK, eso fue una locura ... envié una respuesta que utilizaba técnicas de código de golf de rutina. Pero cuando vi lo que otros enviaban, me di cuenta de que había una mejor manera.

Cada (shape, rotation)tupla se codifica en un literal de cadena C # con duplicados eliminados. El proceso de codificación captura cada una de estas configuraciones en 2 bytes.

La altura de almacenamiento más baja de 3 bits y el siguiente ancho de almacenamiento de 3. Como cada uno de estos valores nunca es más de 4, pueden leerse directamente desde los 3 bits sin ninguna conversión. Aquí hay unos ejemplos:

  W   H
010 010 (2x2)
010 011 (2x3)
001 100 (1x4)
011 010 (3x2)
100 001 (4x1)

A continuación, cada columna se almacena en 3 bits. Lo más útil para mí fue almacenar el número de cuadrados faltantes en la parte superior e inferior de la columna.

// missing squares per column

+------ 0 top / 0 bottom
|+----- 0 top / 1 bottom
||+---- 0 top / 1 bottom
|||
HHH (L-Shape)         HH (Jagged-Shape)
H                    HH
                     |||
1 top / 0 bottom ----+||
0 top / 0 bottom -----+|
0 top / 1 bottom ------+

Nunca faltan más de 2 cuadrados en la parte superior o inferior, y nunca faltan más de 1 cuadrado en ambos al mismo tiempo. Dado este conjunto de restricciones, se me ocurrió la siguiente codificación:

// column encoding of missing squares per column

000: none missing
100: 1 missing on top
101: 2 missing on top
010: 1 missing on bottom
011: 2 missing on bottom
110: 1 missing on top and bottom

Como tenemos que tener en cuenta como máximo 3 columnas con cuadrados faltantes arriba o abajo, podemos codificar cada (shape, rotation)tupla en 15 bits.

 C3  C2  C1   W   H
000 000 000 010 010 - 2x2 with no missing squares
000 000 000 100 001 - 4x1 with no missing squares
100 000 100 011 010 - 3x2 with missings square on top of columns 1 and 3
000 110 000 010 011 - 2x3 with missing squares on top and bottom of column 2

Por último, se han eliminado las formas duplicadas. El siguiente ejemplo muestra cómo múltiples (shape,rotation)tuplas pueden producir salidas duplicadas para la misma forma en diferentes rotaciones:

// Square
HH  (0, 90, 180, 270)
HH
#-------------------------------#
// ZigZag
HH  (0, 180)    H  (90, 270)
 HH            HH
               H
#-------------------------------#
// T
 H  (0)        HHH  (180)
HHH             H

 H  (90)       H    (270)
HH             HH
 H             H

Todas las salidas únicas se determinan y guardan en ay se byte[]convierten en un literal de cadena C #. Para buscar rápidamente dónde se basa una forma Iy R, los primeros 7 bytes de la matriz consisten en una clave de búsqueda codificada.

A continuación hay un enlace al programa que utilicé para comprimir las piezas.

Pruébalo en línea!

Código menos golfizado y comentado:

// a: input list of (i,r,p) tuples
a=>{
  // create an output array that 4 more than
  // the largest position. this may result
  // in some trailing 0's
  var o=new int[a.Max(x=>x.Item3+4)];

  // iterate over each (i,r,p) tuple
  foreach(var(i,r,p)in a){
    // escaped string
    var b="\"4Tqzª!\0\0HS   Ó\0$\n\0!A @";
    // declare several variables that will be used later
    int m=0,n=b[i],t=0,
      // u is the decoded index into b for the current (i,r) pair
      u=n/8+r%(n%8),
      // convert 2 bytes from b into an encoded (shape,rotation) pair
      v=b[u*=2]<<8|b[u-1];
    // iterate over the columns, determining the top of the current
    // piece. The formula here is:
    //   piece_top = max(column_height + shape_height - shape_space_bottom)
    for(;t<v/8%8;m=m>n?m:n)
      n=o[p+t]+v%8-(n=(u=v>>6+3*t++)/2&1)-(n&u);
    // iterate over the columns again, saving the the new height
    // in each column. The formula here is:
    //   new_column_height = piece_top - shape_space_top
    for(;t-->0;)
      o[p+t]=m-(n=(u=v>>6+3*t)/4&1)-(n&u);
  }
  return o;
}
dana
fuente
4

Carbón , 98 bytes

Fθ«≔§⪪§⪪”)¶∧↷"e«↨U∧0%3;D∧⁼~h⊟⁵h/₂dΦ↗(EF”2⊟ι1⊟ιη≔⊟ιζW‹Lυ⁺ζLη⊞υ⁰≔⌈Eη⁻§υ⁺ζλ﹪Iκ³εFLη§≔υ⁺ζκ⁺ε⊕÷I§ηκ³»Iυ

Pruébalo en línea! El enlace es a la versión detallada del código. Toma la entrada como una matriz de valores [P, R, I], donde I es de 0 a 6, R es de 0 o 3 y P también está indexado a 0. Explicación:

Fθ«

Pase sobre las piezas de entrada.

≔§⪪§⪪”)¶∧↷"e«↨U∧0%3;D∧⁼~h⊟⁵h/₂dΦ↗(EF”2⊟ι1⊟ιη

Extraiga la descripción de la pieza actual y la rotación. (Vea abajo.)

≔⊟ιζ

Extraer la posición.

W‹Lυ⁺ζLη⊞υ⁰

Asegúrese de que haya suficiente espacio horizontal para colocar la pieza.

≔⌈Eη⁻§υ⁺ζλ﹪Iκ³ε

Asegúrese de que haya suficiente espacio vertical para colocar la pieza.

FLη§≔υ⁺ζκ⁺ε⊕÷I§ηκ³

Calcule las nuevas alturas de las columnas afectadas.

»Iυ

Cuando se hayan procesado todas las piezas, envíe la lista final de alturas de columna en líneas separadas.

La cadena comprimida representa la cadena original 00001923001061443168200318613441602332034173203014614341642430137. Aquí los 2s son Iseparadores y los 1s son Rseparadores. Por lo tanto, las piezas se decodifican de la siguiente manera:

P\R  0    1    2    3
0    0000 9
1    300  06   443  68
2    003  86   344  60
4    33
5    034  73
6    030  46   434  64
7    430  37

Los Rvalores faltantes se rellenan automáticamente de forma cíclica con carbón. Cada dígito se asigna a dos valores, voladizo y altura total, de acuerdo con la siguiente tabla:

\ O H
0 0 1
3 0 2
4 1 2
6 0 3
7 1 3
8 2 3
9 0 4

El voladizo y la altura total se relacionan con las alturas de las columnas de la siguiente manera: dada una pieza que queremos colocar en una posición determinada e, es posible colocar la pieza aunque una de las columnas sea más alta que e. La cantidad de espacio libre está dada por el voladizo. La nueva altura de la columna después de colocar la pieza es simplemente la posición colocada más la altura total.

Ejemplo: Supongamos que comenzamos colocando una 5pieza en la columna 1. Dado que todavía no hay nada más, la pieza se coloca en la posición 0 y las columnas 1 y 3 ahora tienen altura 1 mientras que la columna 2 tiene altura 2. Entonces queremos colocar una 6pieza con 1rotación en la columna 0. Aquí podemos colocar esta pieza en la posición 0; aunque la columna 1 tiene una altura de 1, la pieza tiene un saliente de 1, por lo que hay suficiente espacio para colocarla. La columna 0 termina con una altura de 2 y la columna 1 termina con una altura de 3.

Neil
fuente