Dada uma lista de movimentos do Tetris, retorne o número de linhas concluídas

37

Descrição

Consideramos uma versão ligeiramente simplificada do Tetris, em que cada movimento consiste em:

  • girando a peça no sentido horário, 0 a 3 vezes
  • posicionando a peça em uma determinada coluna
  • queda rápida

O objetivo é determinar o número de linhas concluídas, dada uma lista desses movimentos do Tetris.

As linhas concluídas são removidas quando as peças são descartadas, seguindo as regras padrão do Tetris.

Playfield

O campo de jogo tem 10 colunas de largura. Não há Game Over e presume-se que sempre haja espaço e tempo suficientes para executar as ações acima, independentemente da configuração do campo de jogo. A altura do campo de jogo realmente não importa aqui, mas você pode usar as 22 linhas padrão como limite superior.

Formas de tetrominós

formas

Entrada / Saída

Entrada

Uma lista separada por vírgula de movimentos do Tetris codificados com 3 caracteres. Os dois primeiros caracteres descrevem a forma de Tetromino a ser usada e o último descreve a posição em que caiu.

  1. Tetromino: I, O, T, L, J, Zou S, na mesma ordem como acima.
  2. Número de rotações no sentido horário: 0a3
  3. Coluna: 0para 9. Esta é a coluna na qual o canto superior esquerdo da peça (marcado com um xna foto acima) fica após a rotação 1

Supõe-se que todas as movimentações na lista fornecida são válidas. Não há necessidade de verificar entradas inválidas, como I07( Iforma horizontal colocada muito à direita).

1 Você pode implementar um algoritmo de rotação real ou codificar todas as formas diferentes, contanto que ele xesteja localizado na coluna fornecida pelo terceiro caractere da movimentação.

Saída

Número de linhas concluídas.

Exemplo

exemplo

O00,T24irá gerar a primeira posição e O00,T24,S02,T01,L00,Z03,O07,L06,I05irá gerar a segunda posição.

Portanto, a seguinte sequência irá gerar um Tetris e deve retornar 4:

O00,T24,S02,T01,L00,Z03,O07,L06,I05,I19

Casos de teste

1) "O00,T24,S02,T01,L00,Z03,O07,L06,I05,I19" -> 4
2) "S00,J03,L27,Z16,Z18,I10,T22,I01,I05,O01,L27,O05,S13" -> 5
3) "I01,T30,J18,L15,J37,I01,S15,L07,O03,O03,L00,Z00,T38,T01,S06,L18,L14" -> 4
4) "S14,T00,I13,I06,I05,I19,L20,J26,O07,Z14,Z10,Z12,O01,L27,L04,I03,S07,I01,T25,J23,J27,O01,
    I10,I10" -> 8
5) "O00,T24,L32,T16,L04,Z11,O06,L03,I18,J30,L23,Z07,I19,T05,T18,L30,I01,I01,I05,T02" -> 8

Página de teste

Você pode usar este JSFiddle para testar uma lista de movimentação.

Arnauld
fonte
11
Em que eixo as peças são giradas?
11
@ Arnauld Sugiro que você dê uma olhada no sistema de super rotação e edite a imagem um pouco mais. tetris.wikia.com/wiki/SRS
11
Então, podemos tratá-las como 25 (15 se você não contar duplicatas) de formas diferentes, então?
11
As soluções podem receber entrada como uma matriz e não como uma sequência separada por vírgula?
Jordânia
11
Esta é a melhor pergunta sobre PCG que eu já vi há muito tempo. Que grande idéia! Melhor no sentido subjetivo de interessante e prático e não muito grande, mas não muito pequeno.
GreenAsJade

Respostas:

5

PHP, 405 399 378 372 368 360 354 347 331 330 328 319 309 300 bytes

(com o mapeamento de blocos de Dave )

<?$f=[~0];L:for($y=0;$f[++$d+$y]|$s;$s>>=4)$y-=1022<$f[$d+$y]=$f[$d]|$s%16<<$x;$c-=$y;if(list($p,$r,$x)=$argv[++$z])for($s=I==$p?$r&1?4369:15:hexdec(decoct(O==$p?27:ord(base64_decode('M1ozWjqaF1kemR6ZPJMPyTnSJ0s')[ord($p)/4%5*4+$r])));;$d--)for($y=4;$y--;)if ($f[$d+$y]>>$x&$s>>$y*4&15)goto L;echo$c;

programa, executa movimentos como argumentos separados, imprime o resultado

repartição para funcionar:

faz movimentos como matriz, retorna resultado

function t($a)
{
    $f=[~$z=0];             // init field $f (no need to init $z in golfed version)
    L:                      // jump label
                            // A: paint previous piece at line $d+1:
#   if($s)paint($f,$s,$d+1,$x);
    for($y=0;               // $y = working line offset and temporary score
        $f[++$d-$y]|$s;$s>>=4)// next field line; while field or piece have pixels ...
        $s>>=4)                 // next piece line
        $y+=1022<               // 3: decrease temp counter if updated line is full
            $f[$d-$y]=$f[$d]    // 2: use $y to copy over dropped lines
                |$s%16<<$x;     // 1: set pixels in working line
    $c+=$y;                         // add temp score to global score
#   paint($f);var_dump($c);
    if(list($p,$r,$x)=$a[$z++])// B: next piece: $p=name; $r=rotation, $x=column
#   {echo"<b>$z:$p$r-$x</b><br>";
        for(                // C: create base 16 value:
            $s=I==$p
                ? $r&1?4369:15  // I shape (rotated:0x1111, not rotated:0xf)
                : hexdec(decoct(    // 5: convert from base 8 to base 16
                    O==$p ? 27  // O shape (rotation irrelevant: 033)
                    : ord(          // 4: cast char to byte
                                    // 0: 4 bytes for each remaining tetromino
                        base64_decode('M1ozWjqaF1kemR6ZPJMPyTnSJ0s')[
                            ord($p)/4%5 // 1: map STZJL to 01234
                            *4      // 2: tetromino offset
                            +$r]    // 3: rotation offset
            )));;$d--
        )
            for($y=4;$y--;) // D: loop $y through piece lines
                if ($f[$d+$y]>>$x & $s>>$y*4 & 15) // if collision ...
                    goto L;         // goto Label: paint piece, tetris, next piece
#   }
    return$c;               // return score
}

para referência: o mapeamento antigo

            hexdec(decoct(          // 3. map from base 8 to base 16
                                    // 0. dword values - from high to low: rotation 3,2,1,0
                [0x991e991e,0xc90f933c,0x4b27d239,0x1b1b1b1b,0,0x5a335a33,0x59179a3a]
                [ord($m[0])/2%9]    // 1. map ZJLO.ST to 0123.56 -> fetch wanted tetromino
                >>8*$m[1]&255       // 2. the $m[1]th byte -> rotated piece
            ))

teste

veja minha outra resposta PHP

quer assistir?

remova o #da fonte da função e adicione:

function paint($f,$s=0,$d=0,$x=0){echo'<pre>';for($y=count($f)+5;$y--;$g=0)if(($t=(($z=$y-$d)==$z&3)*($s>>4*$z&15)<<$x)|$r=$f[$y]|0){$z=$t?" $r|$t=<b".(1022<($z=$t|$r)?' style=color:green':'').">$z":" <b>$r";for($b=1;$b<513;$b*=2)$z=($t&$b?'<b>'.($r&$b?2:1).'</b>':($r&$b?1:'.')).$z;printf("%02d $z</b>\n",$y);}echo'</pre>';}

algumas etapas de golfe

Rev. 5: Um grande salto (399 - 21 = 378) ocorreu simplesmente movendo o deslocamento da coluna
de um loop separado para os dois loops existentes.

Rev. 8: Mudar da matriz para a base 16 para a peça ($ s) não deu muito,
mas abriu caminho para um pouco mais de golfe.

Rev. 17: triturou os valores com base64_encode(pack('V*',<values>))
e usou a indexação de bytes em vez de unpacksalvar 16 bytes

Rev. 25 a 29: inspirado no código de Dave: novo hash (-2), novo design de loop (-9), goto (-10)
sem pré-turno; isso custaria 17 bytes.

mais potencial

Com /2%9, pude salvar 15 bytes (apenas 14 bytes /4%5)
colocando dados binários em um arquivo be depois indexando file(b)[0].
Eu quero isso?

Caracteres UTF-8 custariam muito pela transformação.

no hash

Eu usei ZJLO.ST /2%9 -> 0123.56; mas T.ZJLOS /3%7 -> 0.23456é tão bom.
um byte a mais: O.STJLZ %13/2 -> 0.23456
e mais três:OSTZJ.L %17%12%9 -> 01234.6

Não foi possível encontrar um hash curto (máximo de 5 bytes) que não deixa espaço;
mas Dave encontrou STZJL /4%5 -> 01234, largando o O da lista. wtg!

Entre: TIJSL.ZO (%12%8) -> 01234.67sala de folhas para a Iforma
(e um imaginário A, Mou Yforma). %28%8e %84%8faça o mesmo (mas com em Evez de A).

Titus
fonte
Agradável; Gosto da detecção combinada de pintura + linha e break 2é muito mais limpa do que o que eu tinha que fazer em C! Você pode salvar alguns bytes usando array_diff(defina as linhas concluídas para um valor fixo em vez de usar unsete substitua array_valuespor array_diff), mas não posso dizer pelos documentos se isso nivelaria os valores repetidos (por exemplo, array_diff ([1,2, 2,3], [1]) -> [2,2,3] ou apenas [2,3])
Dave
@ Dave: array_diffnão remove valores duplicados; e eu já tenho o valor fixo (1023); mas não reindexa a matriz. Ótima idéia, mas custaria um byte.
Titus
uau, isso foi um barulho alto quando você passou por mim! Parece que eu tenho que me atualizar!
Dave
Parabéns por atingir 300! Implementarei sua alteração sugerida mais recente (não pensei em simplificar a verificação de rotação agora que não preciso de /10todo o lugar), mas, caso contrário, acho que já terminei. Estou surpreso com o quão diretamente competitivo PHP e C se mostraram. Isso foi divertido - espero que o OP aceite sua resposta!
Dave
@ Dave & Titus - Eu gostaria de poder aceitar as duas respostas. Vocês fizeram um ótimo trabalho. De qualquer forma, parabéns a Titus por atingir 300. E acho que isso é realmente 299, já que você tem um espaço inútil depois de um if.
Arnauld
16

C, 401 392 383 378 374 351 335 324 320 318 316 305 bytes

d[99]={'3Z3Z',983177049,513351321,1016270793,970073931,~0},*L=d+5,V,s;char c;main(x){B:for(c=0;c[++L]|V;V>>=4)c-=(L[c]=*L|(V&15)<<x)>1022;s-=c;if(scanf("%c%1d%d,",&c,&V,&x)>2)for(V=c^79?d[c/4%5]>>24-V*8:27,V=c^73?V/64%4<<8|V/8%8<<4|V%8:V&32?15:4369;;--L)for(c=4;c--;)if(L[c]>>x&V>>c*4&15)goto B;return s;}

Pega entrada separada por vírgula em stdin, retorna a pontuação no status de saída.

Requer charassinatura (que é o padrão para o GCC) e '3Z3Z'deve ser interpretado como 861549402 (que é o caso do GCC em pequenas máquinas endian, pelo menos).


Exemplo de uso:

echo "O00,T24,S02,T01,L00,Z03,O07,L06,I05,I19" | ./tetris; echo "$?";

Explicação de alto nível:

Todas as formas, exceto a linha, podem caber em uma grade 3x3 com um canto ausente:

6 7 -
3 4 5
0 1 2

Isso significa que é fácil armazená-los em um byte cada. Por exemplo:

- - -         0 0 -
- # -   -->   0 1 0   -->   0b00010111
# # #         1 1 1

(alinhamos cada peça à parte inferior esquerda da caixa para facilitar a queda)

Como obtemos pelo menos 4 bytes para um int, isso significa que podemos armazenar todas as 4 rotações de cada peça em um único número inteiro, com um caso especial para a linha. Também podemos ajustar cada linha da grade do jogo em um int (precisa apenas de 10 bits), e a peça atualmente em queda em uma longa (4 linhas = 40 bits).


Demolir:

d[99]={             // General memory
  '3Z3Z',           //  Shape of "S" piece (multi-char literal, value = 861549402)
  983177049,        //  Shape of "T" piece
  513351321,        //  Shape of "Z" piece
  1016270793,       //  Shape of "J" piece
  970073931,        //  Shape of "L" piece
  ~0                //  Bottom of game grid (solid row)
},                  //  Rest of array stores lines in the game
*L=d+5,             // Working line pointer (start >= base of grid)
V,                  // Current piece after expansion (also stores rotation)
s;                  // Current score (global to initialise as 0)
char c;             // Input shape identifier (also counts deleted lines)
main(x){            // "x" used to store x location
  B:                                // Loop label; jumps here when piece hits floor
  for(c=0;c[++L]|V;V>>=4)           // Paint latest piece & delete completed lines
    c-=(L[c]=*L|(V&15)<<x)>1022;
  s-=c;                             // Increment score
  if(scanf("%c%1d%d,",&c,&V,&x)>2)  // Load next command
    for(V=c^79?                     // Identify piece & rotation
          d[c/4%5]>>24-V*8          //  Not "O": load from data
        :27,                        //  Else "O": always 27
        V=c^73?                     // If not "I" (line):
          V/64%4<<8|V/8%8<<4|V%8    //  expand shape to 16-bits
        :                           // Else we are a line:
          V&32?                     //  "I" coincides with "J"; use this to check
               15:4369;             //  horizontal/vertical
        ;--L)                       // Loop from top-to-bottom of grid
      for(c=4;c--;)                 //  Loop through rows of piece
        if(L[c]>>x&V>>c*4&15)       //   If collides with existing piece:
          goto B;                   //    Exit loops
  return s;                         // Return score in exit status
}

-4, -1 graças a @Titus e -23, -11 com inspiração na resposta

Dave
fonte
Agradável! Você poderia simplesmente fazer s+=(d[A-x]=d[A])sem usar x?
Arnauld
Infelizmente xé necessário para manter o controle de quantas linhas ao colapso na etapa atual (cada linha Aé definido para o valor de linha A-xcomo os avanços de loop)
Dave
D'oh! Minha culpa. Desculpe por uma sugestão tão boba. :)
Arnauld
11
@ Titus é um abuso da indexação de array de C. Simplificando, 1[a]e a[1]faça a mesma coisa (ou mais precisamente, a[b]traduz para *(a+b)). É abusado assim como uma maneira de evitar colchetes. Nesse caso, 1[*v]== (*v)[1], ou seja, a segunda letra do comando, ou seja, a rotação.
Dave
11
Você pode se livrar do I espaço reservado? Nesse caso, tente /2%9como hash em vez de %12. %12%8se não.
Titus
2

Ruby, 474 443 428 379 + 48 = 427 bytes

-1 graças a @Titus

Definitivamente, isso pode ser jogado mais.

Lê um dicionário binário de peças (veja abaixo) de STDIN ou um nome de arquivo e faz uma lista de movimentos como argumento, por exemplo $ cat pieces | ruby script.rb O00,T24,S02,....

q=$*.pop
z=$<.read.unpack('S*')
b=c=g=i=0
x=10
u=1023
r=->n{n&2**x-1>0?n:r[n>>x]}
y=->n{n>0?[n&u]+y[n>>x]:[]}
l=->c,n{z.find{|m|m>>x==c<<2|n.to_i}||l[c,n-2]}
q.scan(/(\w)(\d)(\d)/){n=l["IOTLJSZ".index($1),$2.to_i]
v=(n&1|(n&6)<<9|(n&56)<<17|(n&960)<<24)<<$3.to_i
(y[b].size+1).times{t=b<<40
t&v>0&&break
g=t|v
v<<=x}
b=0
a=y[r[g]]
a.grep_v(u){|o|b|=o<<x*i
i+=1}
c+=a.count u}
p c

Dados binários da peça (formato xxd)

0000000: c003 4b04 d810 d814 b820 9c24 d029 5a2c  ..K...... .$.)Z,
0000010: 7830 9634 e039 ca3c 3841 d444 c849 4e4c  x0.4.9.<8A.D.INL
0000020: 9861 9869 5c64 5c6c f050 f058 9a54 9a5c  .a.i\d\l.P.X.T.\

Veja em repl.it (com argumentos codificados, dicionário): https://repl.it/Cqft/2

Ungolfed & explicação

# Move list from ARGV
q = $*.pop

# Read piece dictionary; pieces are 16-bit integers: 3 for letter,
# 2 for rotation, 10 for shape (and 1 wasted)
z = $<.read.unpack('S*')

# Empty board and various counters
b = c = g = i = 0
x = 10 # Magic numbers
u = 1023

# A function to remove empty lines
r = ->n{ n & 2**x - 1 > 0 ? n : r[n >> x] }

# A function to split the board into lines
y = ->n{ n > 0 ? [n & u] + y[n >> x] : [] }

# A function to lookup a piece by letter and rotation index
l = ->c,n{ z.find {|m| m >> x == c << 2 | n.to_i } || l[c, n-2] }

# Read the move list
q.scan(/(\w)(\d)(\d)/) {
  # Look up the piece
  n = l["IOTLJSZ".index($1), $2.to_i]

  # Convert the 10-bit piece to a 40-bit piece (4 rows of 10 columns)
  v = (n & 1 |
        (n & 6) << 9 |
        (n & 56) << 17 |
        (n & 960) << 24
      ) << $3.to_i # Shift by the appropriate number of columns

  # Drop the piece onto the board
  (y[b].size + 1).times {
    t = b << 40
    t & v > 0 && break
    g = t | v
    v <<= x
  }

  # Clear completed rows
  b = 0
  a = y[r[g]]
  a.grep_v(u) {|o|
    b |= o << x * i
    i += 1
  }

  c += a.count u # Count cleared rows
}
p c
Jordânia
fonte
1 byte: m >> 10poderia serm >> x
Titus
@Titus Bom olho. Obrigado!
Jordan
Não é necessário exigir explicitamente \ds na expressão regular: /(\w)(\d)(\d)//(\w)(.)(.)/
manatwork
2

PHP, 454 435 427 420 414 bytes

campos de bits para peças e mapa; mas nenhum caso especial para a Iforma como o golfe de Dave.

<?$t=[I=>[15,4369],O=>[51],T=>[114,562,39,305],L=>[113,802,71,275],J=>[116,547,23,785],Z=>[54,561],S=>[99,306]];foreach($argv as$z=>$m)if($z){$s=$t[$m[0]][$m[1]%count($t[$m[0]])];for($d=$i=0;$i<4;$i++)for($k=0;$k<4;$k++)if($s>>4*$k&1<<$i){for($y=0;$y++<count($f);)if($f[$y-1]&1<<$m[2]+$i)$d=max($d,$y-$k);$k=3;}for($k=$d;$s;$k++,$s>>=4)if(1022<$f[$k]|=$s%16<<$m[2]){$c++;unset($f[$k]);}$f=array_values($f);}echo$c;

recebe argumentos da linha de comando, imprime resultado

ungolfed como função

recebe argumentos como array, retorna resultado

function t($a)
{
    // bitwise description of the stones and rotations
    $t=[I=>[15,4369],O=>[51],T=>[114,562,39,305],L=>[113,802,71,275],J=>[116,547,23,785],Z=>[54,561],S=>[99,306]];
    foreach($a as$m)
    {
        $s=$t[$m[0]][$m[1]%count($t[$m[0]])];   // $s=stone
        // find dropping space
        for($d=$i=0;$i<4;$i++)
            // a) lowest pixel of stone in column i
            for($k=0;$k<4;$k++)
                if($s>>4*$k&1<<$i)
                {
                    // b) top pixel of field in column x+i 
                    for($y=0;$y++<count($f);)
                        if($f[$y-1]&1<<$m[2]+$i)$d=max($d,$y-$k);
                    $k=3; // one byte shorter than `break;`
                }
        // do drop
        for($k=$d;$s;$k++,$s>>=4)
            if(1022<$f[$k]|=$s%16<<$m[2])   // add block pixels to line pixels ... if full,
            {$c++;unset($f[$k]);}           // tetris
        $f=array_values($f);
    }
    return$c;
}

testes (em função)

$data=[
    "O00,T24,S02,T01,L00,Z03,O07,L06,I05,I19"=>4,
    "S00,J03,L27,Z16,Z18,I10,T22,I01,I05,O01,L27,O05,S13" => 5,
    "I01,T30,J18,L15,J37,I01,S15,L07,O03,O03,L00,Z00,T38,T01,S06,L18,L14" => 4,
    "S14,T00,I13,I06,I05,I19,L20,J26,O07,Z14,Z10,Z12,O01,L27,L04,I03,S07,I01,T25,J23,J27,O01,I10,I10" => 8,
    // additional example for the two last tetrominoes:
    'O00,T24,L32,T16,L04,Z11,O06,L03,I18,J30,L23,Z07,I19,T05,T18,L30,I01,I01,I05,T02' => 8,
];
function out($a){if(is_object($a)){foreach($a as$v)$r[]=$v;return'{'.implode(',',$r).'}';}if(!is_array($a))return$a;$r=[];foreach($a as$v)$r[]=out($v);return'['.join(',',$r).']';}
function cmp($a,$b){if(is_numeric($a)&&is_numeric($b))return 1e-2<abs($a-$b);if(is_array($a)&&is_array($b)&&count($a)==count($b)){foreach($a as $v){$w = array_shift($b);if(cmp($v,$w))return true;}return false;}return strcmp($a,$b);}
function test($x,$e,$y){static $h='<table border=1><tr><th>input</th><th>output</th><th>expected</th><th>ok?</th></tr>';echo"$h<tr><td>",out($x),'</td><td>',out($y),'</td><td>',out($e),'</td><td>',cmp($e,$y)?'N':'Y',"</td></tr>";$h='';}
foreach($data as $v=>$e)
{
    $x=explode(',',$v);
    test($x,$e,t($x));
}
Titus
fonte
427? Você está ligado!
Jordânia
@Jordan: e os 427 incluem a <?sobrecarga :)
Titus