(Re) Implementando o Tetris

43

No espírito de reimplementar os videogames clássicos , gostaria de convidar a comunidade para criar sua melhor implementação do Tetris .

insira a descrição da imagem aqui
Para referência, uma captura de tela da versão oficial do NES do Tetris.

Recursos Necessários

  • Um sistema de pontuação razoável deve estar em vigor, o que recompensa as limpezas de várias linhas mais do que as de uma linha. A pontuação atual deve estar visível o tempo todo.
  • A próxima peça que aparecerá deve ser indicada de alguma maneira.
  • A distribuição dos sete tetrominos deve ser bastante uniforme (ou seja, escolhida pseudo-aleatoriamente).
  • O usuário deve ter a capacidade de girar a peça atual em ambas as direções, bem como acelerar sua descida.
  • Quando o jogo terminar, deve ser claramente indicado que o jogo acabou.
  • O código fonte deve ser estruturado e facilmente compreensível.

Recursos opcionais

  • Avanço da velocidade de queda após um certo número de limpezas (ou seja, aumento do nível de dificuldade) e avanço da pontuação por linha limpa, proporcional à velocidade.
  • Gravidade. Você pode optar por implementar a gravidade 'clássica', na qual os blocos podem permanecer flutuando sobre as lacunas, ou você pode optar por implementar a gravidade de 'preenchimento', na qual os blocos que foram separados do seu tetromino original por meio da limpeza de linha podem cair em aberto lacunas.
  • Pontuações mais altas com entrada de nome.
  • A animação após a linha é limpa e / ou após a obtenção de uma nova pontuação alta.

Limitações

  • Quaisquer bibliotecas usadas ( jQuery , PyGame etc.) devem estar disponíveis gratuitamente.
  • O tamanho do código-fonte não deve exceder 4096 bytes, excluindo espaços em branco e comentários. Quaisquer recursos externos (arquivos de dados, imagens etc.) serão adicionados ao comprimento do código, excluindo os arquivos gerados, como para pontuações mais altas.
    Percebo que essa é uma restrição bastante arbitrária; meu objetivo principal é desencorajar a colagem de cópias de implementações existentes e incentivar a brevidade e a autocontenção.

Critérios Vencedores

Esse desafio será julgado como um concurso de popularidade , o que significa que o envio com mais votos positivos será selecionado como vencedor. Ao votar, incentivo os usuários a votarem todos e quaisquer envios que considerem adequados aos requisitos mencionados acima.

O vencedor será escolhido o mais tardar 2 semanas após a primeira solução válida. Além disso, concederei uma recompensa ao vencedor, aproximadamente proporcional ao número de votos positivos que essa pergunta recebe ( 10 * #votesarredondada para cima para os 50 mais próximos). Se houver empate após o término do período de duas semanas, o período da competição será prorrogado por uma semana. Se ainda houver empate, reservo-me o direito de votar.

Por favor, peça esclarecimentos. Que vença a melhor implementação!

primo
fonte
@ moose Quero dizer código fonte. A pergunta foi atualizada para refletir isso.
Primo
Hey primo, posso colar a versão inicial e (talvez) atualizá-la mais tarde?
Henrik Mühe
@ HenrikMühe Não me importo, mas ele não iniciará o timer, a menos que atenda ao conjunto de recursos necessários.
Primo18
Definitivamente vou querer experimentar isso. Provavelmente o fará com SDL (embora eu não o tenha usado antes, o tornará ainda mais divertido!).
Ben Richards
Se eu pudesse encontrar minha implementação QBASIC do Tetris! :)
Ben Richards

Respostas:

19

Tente: http://tetris.muehe.org

Atualização Há uma pontuação mundial alta. Desfrute de derrotá-lo ou - alternativamente - hackear :-)

Captura de tela: Tetris em ação

A versão CoffeeScript e HTML deve atender aos requisitos da melhor maneira possível (e eu nunca joguei realmente o Tetris).

$ make stats
4095

Github https://github.com/henrik-muehe/tetris

Recursos

  • Pontuação exponencial
  • Indicador da próxima peça
  • Aleatoriedade justa
  • Rotação, mover e soltar para baixo
  • Indicação de fim de jogo
  • Highscore com servidor back-end que não deve ser tão fácil de encontrar.
Henrik Mühe
fonte
Ótimo! Mas um pequeno erro: quando várias linhas são concluídas no fundo do poço, uma delas não desaparece.
manatwork
@manatwork Você quer dizer quando uma das linhas que desaparece é a mais voltada para o fundo? Eu vou olhar para ele, uma imagem seria :) incrível
Henrik Mühe
Essa é uma implementação muito boa. Uma solicitação: você poderia listar quais recursos foram implementados? À medida que mais soluções forem enviadas, a comparação será mais fácil.
Primo20 /
1
@ user2023370, é bytes, embora ...
Henrik Mühe
1
O link não funciona.
Erik o Outgolfer
18

Pascal

Desenvolvido no FreePascal 2.6.2, também deve ser compilado com o Turbo Pascal 6.0. Somente a unidade Crt é usada, sem recursos externos.

program tetris;

  uses
    Crt;

  const
    width = 10;                                  { playing field width }
    height = 20;                                 { playing field height }

  const
    piece: array [1..7, 1..3, 0..1] of ShortInt = ( { piece shapes : piece, element, coordinate }
      (( 1, 0), ( 1, 1), ( 0, 1)),               { O }
      ((-1, 0), ( 1, 0), ( 2, 0)),               { I }
      (( 0, 1), ( 1, 0), ( 2, 0)),               { L }
      (( 0, 1), (-1, 0), (-2, 0)),               { J }
      ((-1, 0), ( 1, 0), ( 0, 1)),               { T }
      (( 1, 0), ( 0, 1), (-1, 1)),               { S }
      ((-1, 0), ( 0, 1), ( 1, 1))                { Z }
    );
    color: array [1..7, 0..1] of Byte = (        { piece colors : foreground, background }
      (Yellow,       Brown),                     { O }
      (LightCyan,    Cyan),                      { I }
      (LightBlue,    Blue),                      { L }
      (White,        LightGray),                 { J }
      (LightMagenta, Magenta),                   { T }
      (LightGreen,   Green),                     { S }
      (LightRed,     Red)                        { Z }
    );

  var
    area: array [1..width + 2, 1..height + 1] of Byte; { playing field }
    coord: array [0..3, 0..1] of Byte;           { precalculated element coordinates }
    played,                                      { played pieces count }
    removed,                                     { completed lines count }
    level,                                       { current level }
    score: LongInt;                              { accumulated score }
    time,                                        { current level delay }
    wait,                                        { current piece delay }
    made,                                        { completed lines in current level }
    multi,                                       { multiline count }
    screen,                                      { original screen size }
    i, j, j2: Word;                              { counters }
    current,                                     { current piece }
    next,                                        { next piece }
    x,                                           { horizontal coordinate }
    y,                                           { vertical coordinate }
    position,                                    { rotation position }
    k: Byte;                                     { pressed key }
    ok: Boolean;                                 { aggregated condition }

{ precalculates the give piece's elements coordinates }
  procedure coordinate(current, x, y, position: Byte);
  begin
    coord[0, 0] := x;
    coord[0, 1] := y;
    for i := 1 to 3 do begin
      coord[i, 0] := x + piece[current, i, position mod 2] * (Ord(position in [0, 1]) * 2 - 1);
      coord[i, 1] := y + piece[current, i, 1 - position mod 2] * (Ord(position in [0, 3]) * 2 - 1);
    end;
  end;

{ draws a piece }
  procedure draw(current, x, y, position: Byte; visible: Boolean);
  begin
    coordinate(current, x, y, position);

    for i := 0 to 3 do begin
      GotoXY(coord[i, 0] * 2 + 1, coord[i, 1]);
      if visible then begin
        TextColor(color[current, 0]);
        TextBackground(color[current, 1]);
        Write('[]');
      end else begin
        TextBackground(Black);
        Write('  ');
      end;
    end;
  end;

{ check whether a piece can be placed in given position }
  function check(x2, y2, position2: Byte): Boolean;
  begin
    coordinate(current, x2, y2, position2);

    ok := True;
    for i := 0 to 3 do if area[coord[i, 0], coord[i, 1]] <> 0 then ok := False;

    if ok then begin
      x := x2;
      y := y2;
      position := position2;
    end;

    check := ok;
  end;

begin

  Randomize;
  TextColor(LightGray);
  TextBackground(Black);
  ClrScr;
  screen := WindMax;

  for i := 0 to width + 1 do for j := 1 to height + 1 do area[i, j] := 9 * Ord(not ((i in [1..width]) and (j in [1..height])));

  Window(1, 1, width * 2 + 4, height + 2);
  for i := 1 to (width + 2) * (height + 1) do Write('##');
  Window(3, 1, width * 2 + 2, height);
  ClrScr;
  Window(1, 1, Lo(screen), Hi(screen));

  Window(width * 2 + 7, 1, width * 2 + 25, 10);
  Writeln('Next');
  Writeln;
  Writeln;
  Writeln('Piece');
  Writeln('Line');
  Writeln('Level');
  Writeln('Score');
  Window(1, 1, Lo(screen), Hi(screen));

  played := 0;
  removed := 0;
  level := 1;
  score := 0;

  current := 9;
  next := 9;
  made := 0;

  repeat

    if current = 9 then begin
      if next = 9 then next := Random(7) + 1 else draw(next, width + 9, 1, 0, False);

      current := next;
      next := Random(7) + 1;
      x := width div 2;
      y := 1;
      position := 0;
      time := 1100 - level * 100;
      wait := time;

      Inc(played);
      Inc(score);
      if made = 25 then begin
        Inc(level);
        made := 0;
      end;

      draw(next, width + 9, 1, 0, True);

      Window(width * 2 + 15, 4, width * 2 + 25, 10);
      TextColor(LightGray);
      TextBackground(Black);
      Writeln(played:5);
      Writeln(removed:5);
      Writeln(level:5);
      Writeln(score:5);
      Window(1, 1, Lo(screen), Hi(screen));

      if not check(x, y, position) then begin
        GotoXY(width * 2 + 7, 10);
        Write('Game Over');
        Break;
      end;
    end;

    draw(current, x, y, position, True);
    GotoXY(1, 1);
    repeat Delay(1);
      Dec(wait);
    until KeyPressed or (wait = 0);
    draw(current, x, y, position, False);

    if KeyPressed then begin
      k := Ord(ReadKey);
      case k of
        75, 77: check(x + Ord(k = 77) * 2 - 1, y, position);
        72, 80: check(x, y, (position + Ord(k = 80) * 2 + 1) mod 4);
        32: begin
          time := 1;
          Inc(score);
        end;
      end;
    end;

    if wait = 0 then begin
      if not check(x, y + 1, position) then begin
        draw(current, x, y, position, True);
        for i := 0 to 3 do area[coord[i, 0], coord[i, 1]] := current;

        multi := 0;
        for j := 1 to height do begin
          ok := True;
          for i := 1 to width do if area[i, j] = 0 then ok := False;

          if ok then begin
            for j2 := j downto 2 do for i := 1 to width do area[i, j2] := area[i, j2 - 1];
            for i := 1 to width do area[i, 1] := 0;

            Inc(score, 10 + multi * 2);
            Inc(removed);
            Inc(multi);

            Window(3, 1, width * 2 + 2, height);
            TextBackground(Black);
            GotoXY(1, j);
            DelLine;
            GotoXY(1, 1);
            InsLine;
            Window(1, 1, Lo(screen), Hi(screen));
          end;

        end;
        if multi <> 0 then Inc(made);

        current := 9;
      end;
      wait := time;
    end;

  until k = 27;

  ReadKey;
  TextColor(LightGray);
  TextBackground(Black);
  ClrScr;

end.

Captura de tela

Tetris em Pascal

(No Linux, na janela XTerm.)

Ao controle

  • Left - vire à esquerda
  • Right - mova para a direita
  • Up - gire no sentido anti-horário
  • Down - rode no sentido dos ponteiros do relógio
  • Space - suspenso
  • Esc - Saída

Pontuação

  • peça tocada - 1 ponto
  • peça derrubada - 1 ponto
  • linha completada - 10 pontos
  • várias linhas - multiplicador * 2 pontos

O nível começa em 1 e aumenta após cada 25 linhas concluídas. (Várias linhas concluídas ao mesmo tempo contam como 1.)

Medição

bash-4.2$ sed '
s/{[^{}]*}//g                  # remove comments
s/^ *\| *$//g                  # trim leading and trailing spaces
/^$/d                          # remove empty lines
s/ *\([:=<>,;+*-]\+\) */\1/g   # no space around operators
' tetris.pas | wc -c
3697
homem a trabalhar
fonte
17

Java (Swing)

Esta é uma implementação da primeira edição histórica do Game Boy da Nintendo (c) de 1989.

Game Boy Tetris Nintendo

Como jogar:

Z= girar para a esquerda
X= girar para a direita
Left= mover para a esquerda
Right= mover para a direita
Down= mover para baixo (lentamente)
Up= girar para a esquerda (apenas para facilitar o uso)
R= reiniciar o jogo

Evitei usar mais de uma classe (porque o aspecto do golfe vem à minha mente). Mas agora não é mais possível jogar golfe, de qualquer forma ... No entanto, codifiquei e codifiquei em Base64 um arquivo de fonte e um arquivo de imagem, para poder usá-lo no primeiro e único arquivo de classe.

Para executá-lo, copie o código Java no seu IDE e inicie. Você não precisa de bibliotecas ou recursos adicionais.

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.xml.bind.DatatypeConverter;

public class JTetris extends javax.swing.JFrame {
    static Image IMAGE;
    static Font FONT;

    static {
        ZipInputStream zipImage = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
            "UEsDBBQAAAAIABmiWkRZ0oOouwAAAJYKAAAFAAAAYy5ibXBz8p3GxQAGZUCsAcQ3gFgCiBkZWMDiCkB5GBCA0iJyLAwl+3oYYpJsGK58u8NAKjBGAgyUgVG7QHYxKUGBAjDiBJUUQUhQAMQGA3xsFL3MMBMNRrpdEABhQxQLCiDYmHZBxCF2QdhAu8CyEHLk2cWkBJSH" +
            "xxcoCgyg5hsbAgWhbBgJUg9Wg24XWDGQHJF2QeOCUruQwEi0S0kRe5qHKCY2zQsgkv0ItAsIKC97EQnecGTYRQUwKNsbNAYAUEsBAj8AFAAAAAgAGaJaRFnSg6i7AAAAlgoAAAUAJAAAAAAAAAAgAAAAAAAAAGMuYm1wCgAgAAAAAAABABgAkJCiSyczzwGQkKJLJzPP" +
            "AZCQoksnM88BUEsFBgAAAAABAAEAVwAAAN4AAAAAAA=="
        )));

        ZipInputStream zipFont = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
            "UEsDBBQAAAAIAEq5UEN0MGyWoggAAHgrAAAFAAAAYS50dGbklsvvC1EUx78zU+/3OxHiEuLd1jMlxKNR70cQCxumY9oOnU5Np6osdIGEDYmwZGElkYh/gI2FBUIsvFdILNgh8Rxft4d6RyxEYvq7PZ97es73nHNv8mthAOiJFiyotRtS0xMnum4Gej0HsNXx7erbI6+7" +
            "ks8CxqNiuVk48qTbVKB3BIy8XXLtbVv6x0cBkwuzS3SYrffvuL8MYGzJj3av3BfP4P4p8yeUA8cGxqWBSau4n+Lbu6tYET8AEoMBqIrtuzuPz33D/Uyg28VqUItgxtTrvRGAAoW4LK7BsHTnb5HAE9qB6E+PidGYhCTmYjP24AxuxTGYR98UpLEIdtsXP4zvxXfjO/HN" +
            "+Gp8Kg6p9O8//8ucRns93ZK6vqXfvBccXLuv7b/QH2LjVtyyYLUAdIfJaMmz6Gc83w3yd9OuSLS0WmcxRLj1xYJY/Gz9Wcyfr79XJ6aO3MEKTEAPWsDE988Y4yQM4RGAsEEeIWyiGzLCFsZjgXACY1EQ7oJeOCzclZnHhbthHs4Ld8cQPBPugT6GKdwT841+wr0wzFgl" +
            "3BtpwxHugxHGMeG+mGycEx6EicbdNhusYFqwYCQ4LTJmL2GDnBU20dfcK2xhjXlIOIFl5i3hLhhqjRLuiow1S7gbdlu+cHdMsh4L98DwRD/hntiTmCjcC8nEaeHe2Jq4IdwHmS7ThPtiUxdPeBDWd7nYZgPo33VANqg2Q69YitQ6Owo9Z4daZdd3uGp6etqMDfWqG6rV" +
            "dugF9Fa2qQlL+T9XLQ6aE9V6t1gv22EuqEQborDuROoX0b/87JPUJjeseUFFTUumdfhUHT71Y/hUHT71Y/jULyp6NWWrKLS3ub4d7lBBQeU2LFc6oBRU1fJK5IYVO6KoXVZL/fyyUhRV56ZSBUbUtETSCfyv5h5+6fWveh1+6ZVq2DWVr3vlSDW8qKQ6/fQU+UajkdQl" +
            "2MTHAj+s+sWW/dVSDG6kMpnMjGlzsqHLpne5Khv4Pj9TiyJ2mK9/DBQtR0KcdkQyCIupsue4lZpbS+WbqRnJdCr3USLvFdXOuu3s8CpFtcetlpphTW0P2Lzf5CS7Vd7dli0uWbStXIqQRYAqmgjhoYgSIiisg41Iexzs4H4V93WSS56ONKZhBjbQU6UnpG81bB0dSGwF" +
            "20gTsJTs66zFCFhjImk990XmlnVODgGjI6qxHr0Orfoz7T/P+66rTVqjprMrUJw3ifQX6lM76mRRJ31WJ4s66cczetRXjFbaazPfha9VeeLMKPA9x5zltB2FEqlKz3K9Zy+0NomdaCpDsQcfeSzTdxkxei5SfBVEo9bpglM59Po/v281HJfw+k/PVWe/oqcBW0+bp47H" +
            "HiPt82hLpB+dT89vum/oV7IzhZyETPD7s/7wUzk/elKi3CBl9GsGb38Osox05aR36SmzWs2XPIVFiOQMOSVE8Zu+nG9UnK80krQhiowr65twtdfVXeXR5PsMxqRpc5+7yJOKtDtZ09a356GiPXvgsm6JeaHubzsCOXkfTbmT3VrB5Q1mmbMEi0hl3TNM86VxFF2QMOXL" +
            "XVsuNnEF8mtAHquDQG7DxvVYSOFbbbfVQovG0NwOVV/kFKj3g4eVJZ5/3dALAzGUIl8/MMTDAvEBAC9oe9Hu/WJ/kD9s78f3gfdvpKrPs6H/A+1VkJtADAMnWXFAqEIVqjhw6KEfzQvan/Ud/cQmJdnRjoyzBLUCFAxeZ8aOHSfgk3aU+Kp6oFqX7/mn2s5L6KF+JPoC" +
            "8QOVOqdFAqiOpICIqxbYo82LiTEcccIZF7zjgyiD14JJ9MrUfmsUWAlrx3nybJXJ/b6v1/yIene+vjHV+OQrxNUsQ5XCpSYvtZBCXZeWD85FbA8CGE3TXXlkQwTIJkI1cvQrGmlXFAOiWYslTyXZJ1XX5JWTeT8IXbEKNaBKky2OmVbG17et7AMet8D6Vlh1kZq8sgdj" +
            "RckB7b9Tnx3ELVCmouKUtBHriQZczDu8dlk1i5w+SwGrFYp5mvtc03aE9NdGqP2rSNu3slZt6Ea4zTmuQc/Zcu1q0XEqj52KR/J9YIIwfN4gZOREc/Xdi+O4W6/anb3a9dGGxBoe1NRf8szqSdveBHqRE8SK4PtAbHr2cXFqh9rORFwMcgVACL6qgdi+WY9mzbjJ1agC" +
            "A2Uh8jgnE1wOHu0lvY4L+RGIZCzMHsnS+bwfbljlmyIz/X2Cyaf8t0jUNzue7CR+ZC32wD0kgyL537VkJY/XUtXl4/f3DWJDnotxhmJ67IyMMD2if9qO9ovOCYvrzqWCIf5Z+I4H4iCiODiKdFob37e6M40fO8Cyqtp4cxjXh5iNz2KXn84f5pI65vQ5nKrUWbPJ+fy7" +
            "nMF/ARx6tzdmc0q43dv7n3IcnZlxcHefezcFAJt3de7FX/bLHQWAEAaiuPe/srhiMcEncWDZMkUKwe/oJM+R+Ip1IqfPB1WCakTEbi7q6Jyb9sZ5oAgoYThNGjp24i9uhItx5j6DbyffdZC4XuWaY4gwHjnb1m/m0cxzUENOUK/VVs+f/S1qh79xC6dP/B+YfgEZgCsT" +
            "XRT0T4OPuu6oXXK5r1OxdrBO2xkcikrZz/l3jjozreH9LYpVi1WLVYtVi1VfjbZVGUfbqqNt1dG26mhbFaBB21blBJIgO+HzavBZNXWgKUQCsK8w58oQKQJ19BhR5mHMyUHYuOfWEHOEmL7+B49bDP3Dc/4Q0DW9aSsURmEYfqGx97+Po31eu4MFYdglBEHw47qIMAiD" +
            "YEEQBAuCYF1AGIZBGIRBGARB0MvBA3N8MMccc3wAkN1Zu2umvfeihkba+ueezEstLSxnogE8JMpk9thQDqGsVIWO8JSq0Bmeo/bwEjTUCV57Mn/ragPvUZkO1iWV+8u5rvDRl/NnRyv4CoqawndQ1Bx+0FhL+G0q1xX+oi6QDOAftTWBStAQqh0VUKtBvaTdDVBLAQI/" +
            "ABQAAAAIAEq5UEN0MGyWoggAAHgrAAAFACQAAAAAAAAAIAAAAAAAAABhLnR0ZgoAIAAAAAAAAQAYAABukYC8ys4BJXTvDjUzzwEldO8ONTPPAVBLBQYAAAAAAQABAFcAAADFCAAAAAA=")
        ));

        try {
            zipImage.getNextEntry();
            zipFont.getNextEntry();
            IMAGE = ImageIO.read(zipImage);
            FONT = Font.createFont(0, zipFont);
            FONT = FONT.deriveFont(20.0f);
        } catch (Exception e) {
            System.exit(1);
        }
    }

    int[] speeds = {887,820,753,686,619,552,468,368,284,184,167,150,133,117,100,100,83,83,66,66,50};
    int[] points = {40,100,300,1200};
    int[][][] stones = {
        {{0,1},{1,1},{2,1},{0,2}},      // L 0
        {{0,1},{1,1},{2,1},{3,1}},      // I 1
        {{0,1},{1,1},{2,1},{2,2}},      // J 2
        {{0,1},{1,1},{2,1},{1,2}},      // T 3
        {{0,1},{1,1},{1,2},{2,2}},      // Z 4
        {{1,1},{2,1},{0,2},{1,2}},      // S 5
        {{1,1},{2,1},{1,2},{2,2}}       // O 6
    };
    Color[] colors = {
        new Color(220, 246, 212), // light
        new Color(140, 190, 116), // green
        new Color(60, 98, 92),    // blue
        new Color(4, 30, 20)      // dark
    };
    int FREE = 0;
    int BORDER = 8;
    int DELTA = 20;
    int WIDTH = 10;
    int HEIGHT = 20;
    int SIZE = 24;

    int[][] field = new int[WIDTH][HEIGHT];
    int[][] stone;
    int deltaX;
    int deltaY;
    int curStone;
    int nextStone = new Random().nextInt(7);
    int score;
    int level;
    int lines;
    int steps;
    int speed = speeds[0];

    Timer tickTimer = new Timer();
    Image dbImage;
    Image staticImage;
    Graphics dbg;

    int anim = -1;
    boolean blink = false;
    ArrayList<Integer> removeLinesRows = new ArrayList<Integer>();
    boolean keyDown = true;
    boolean freezeKeys = false;

    private JTetris() {
        setDefaultCloseOperation(2);
        setResizable(false);
        setSize(475, 480);

        addPropertyChangeListener("isDirty", new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
                repaint();
            }
        });

        reset();

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                super.windowClosed(e);
                tickTimer.cancel();
            }
        });

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                keyDown = true;
            }

            @Override
            public void keyPressed(KeyEvent e) {
                if (freezeKeys) {
                    keyDown = false;
                    return;
                }

                switch (e.getKeyCode()) {
                    case 82: reset(); break;
                    case 37: updateField(-1, 0, false); break;
                    case 39: updateField(1, 0, false); break;
                    case 40: if (keyDown) updateField(0, 1, true); break;
                    case 38:
                    case 90: rotate(false); break;
                    case 88: rotate(true);
                }
            }
        });

        setVisible(true);
    }

    private void startTickTimer() {
        if (tickTimer != null) {
            tickTimer.cancel();
        }

        freezeKeys = false;

        tickTimer = new Timer();
        tickTimer.schedule(new TimerTask() {
            public void run() {
                updateField(0, 1, false);
            }
        }, 0, speed);
    }

    private void startRemoveLineTimer() {
        if (!removeLinesRows.isEmpty()) {
            anim = 7;
        }

        if (tickTimer != null) {
            tickTimer.cancel();
        }

        tickTimer = new Timer();
        tickTimer.schedule(new TimerTask() {
            public void run() {
                if (anim > 0) {
                    blink = !blink;
                    firePropertyChange("isDirty", false, true);
                }

                if (anim-- < 0) {
                    blink = false;
                    tickTimer.cancel();
                    if (removeLinesRows.size() > 0) {
                        int delta = 0;
                        for (int row : removeLinesRows) {
                            for (int r = row + delta++; r > 2; r--) {
                                for (int c = 0; c < WIDTH; c++) {
                                    field[c][r] = field[c][r - 1];
                                }
                            }
                        }

                        score += points[removeLinesRows.size() - 1] * (level + 1);
                        lines += removeLinesRows.size();
                        level = lines / 10;
                        speed = speeds[level > 20 ? 20 : level];
                        removeLinesRows.clear();
                    }

                    createNextStone();

                    startTickTimer();
                }
            }
        }, 50, 160);
    }

    public static void main(String[] a) {
        new JTetris();
    }

    @Override
    public void paint(Graphics g) {
        if (dbImage == null) {
            dbImage = createImage(getWidth(), getHeight());
            dbg = dbImage.getGraphics();
            dbg.setFont(FONT);

            staticImage = createImage(getWidth(), getHeight());
            Graphics sg = staticImage.getGraphics();
            sg.setFont(FONT);

            sg.setColor(colors[3]);
            sg.fillRect(0, 0, getWidth(), getHeight());

            sg.setColor(colors[0]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 70, 170, 58);
            sg.setColor(colors[2]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 73, 170, 52);
            sg.setColor(colors[0]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 95, 170, 27);
            sg.setColor(colors[2]);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 98, 170, 3);

            sg.setColor(colors[1]);
            sg.fillRoundRect(40 + 13 * SIZE - 15, 40 + 13 * SIZE - 15, SIZE * 4 + 30, SIZE * 4 + 30, 20, 20);
            sg.setColor(colors[0]);
            sg.fillRoundRect(40 + 13 * SIZE - 12, 40 + 13 * SIZE - 12, SIZE * 4 + 24, SIZE * 4 + 24, 15, 15);

            sg.fillRoundRect(330 - 6, 60 - 6, 120 + 12, 25 + 12, 15, 15);
            sg.fillRoundRect(330 - 6, 155 - 6, 120 + 12, 60 + 12, 15, 15);
            sg.fillRoundRect(330 - 6, 235 - 6, 120 + 12, 60 + 12, 15, 15);
            sg.setColor(colors[2]);
            sg.fillRoundRect(40 + 13 * SIZE - 9, 40 + 13 * SIZE - 9, SIZE * 4 + 18, SIZE * 4 + 18, 15, 15);

            sg.fillRoundRect(330 - 3, 60 - 3, 120 + 6, 25 + 6, 15, 15);
            sg.fillRoundRect(330 - 3, 155 - 3, 120 + 6, 60 + 6, 15, 15);
            sg.fillRoundRect(330 - 3, 235 - 3, 120 + 6, 60 + 6, 15, 15);
            sg.setColor(colors[3]);
            sg.fillRoundRect(40 + 13 * SIZE - 6, 40 + 13 * SIZE - 6, SIZE * 4 + 12, SIZE * 4 + 12, 15, 15);

            sg.setColor(colors[0]);
            sg.fillRoundRect(330, 60, 120, 25, 5, 5);
            sg.fillRect(40 + WIDTH * SIZE + SIZE, 40, 3, (HEIGHT - 2) * SIZE);
            sg.fillRoundRect(40 + 13 * SIZE - 3, 40 + 13 * SIZE - 3, SIZE * 4 + 6, SIZE * 4 + 6, 5, 5);
            sg.fillRoundRect(330, 155, 120, 60, 15, 15);
            sg.fillRoundRect(330, 235, 120, 60, 15, 15);

            sg.setColor(colors[3]);
            sg.drawString("SCORE", 340, 80);
            sg.drawString("LEVEL", 340, 180);
            sg.drawString("LINES", 340, 260);

            for (int r = 0; r < (HEIGHT - 2) * 24 / 18; r++) {
                sg.drawImage(IMAGE, 16, 40 + r * 18, 16 + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
                sg.drawImage(IMAGE, 40 + WIDTH * SIZE, 40 + r * 18, 40 + WIDTH * SIZE + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
            }
        }

        dbg.drawImage(staticImage, 0, 0, this);

        dbg.setColor(colors[3]);
        dbg.drawString(String.format("%6s", score > 999999 ? 999999 : score), 320, 120);
        dbg.drawString(String.format("%4s", level > 20 ? 20 : level), 340, 205);
        dbg.drawString(String.format("%4s", lines > 9999 ? 9999 : lines), 340, 285);

        for (int x = 0; x < 4; x++) {
            drawField(dbg, stones[nextStone][x][0] + 13, stones[nextStone][x][1] + 13, nextStone + 1);
        }

        for (int r = 2; r < HEIGHT; r++) {
            for (int c = 0; c < WIDTH; c++) {
                drawField(dbg, c, r - 2, field[c][r]);
            }
        }

        if (blink) {
            if (anim == 0)  {
                dbg.setColor(colors[0]);
            } else {
                dbg.setColor(colors[1]);
            }

            for (int row : removeLinesRows) {
                dbg.fillRect(40, 40 + (row - 2) * SIZE, SIZE * WIDTH, SIZE);
            }
        }

        g.drawImage(dbImage, 0, 0, this);
    }

    private void drawField(Graphics g, int x, int y, int idx) {
        if (idx > DELTA) {
            idx -= DELTA;
        }

        g.drawImage(IMAGE, 40 + x * SIZE, 40 + y * SIZE, SIZE + 40 + x * SIZE, SIZE + 40 + y * SIZE, idx * SIZE, 0, (idx + 1) * SIZE, SIZE, this);
    }

    private void createNextStone() {
        curStone = nextStone;
        stone = stones[curStone];
        deltaX = (WIDTH + 1) / 2 - 2;
        deltaY = 1;

        if (!isMoveable(0, 0)) {
            tickTimer.cancel();
            JOptionPane.showMessageDialog(this, "Game over! Press OK for a new game.");
            reset();
            return;
        }

        updateField(0, 0, false);
        nextStone = new Random().nextInt(7);
    }

    private void reset() {
        score = 0;
        steps = 0;
        lines = 0;
        level = 0;
        speed = speeds[0];

        for (int r = 0; r < HEIGHT; r++) {
            for (int c = 0; c < WIDTH; c++) {
                field[c][r] = FREE;
            }
        }

        createNextStone();
        startTickTimer();
    }

    private void updateField(int hor, int ver, boolean player) {
        if (isMoveable(hor, ver)) {
            if (player) {
                steps++;
            }

            for (int x = 0; x < 4; x++) {
                field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
            }

            deltaX += hor;
            deltaY += ver;

            for (int x = 0; x < 4; x++) {
                field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
            }

            firePropertyChange("isDirty", false, true);
        } else if (ver == 1) {
            freezeKeys = true;
            fixStone();
            removeLines();
        }
    }

    private void removeLines() {
        outer:
        for (int r = HEIGHT - 1; r >= 0; r--) {
            for (int c = 0; c < WIDTH; c++) {
                if (field[c][r] == FREE) {
                    continue outer;
                }
            }

            removeLinesRows.add(r);
        }

        startRemoveLineTimer();
    }

    private void fixStone() {
        score += steps;
        steps = 0;

        for (int x = 0; x < 4; x++) {
            field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1 + DELTA;
        }
    }

    private boolean isMoveable(int hor, int ver) {
        for (int x = 0; x < 4; x++) {
            int c = stone[x][0] + deltaX + hor;
            int r = stone[x][1] + deltaY + ver;

            if (c < 0 || c >= WIDTH || r < 0 || r >= HEIGHT) {
                return false;
            }

            if (field[c][r] > DELTA) {
                return false;
            }
        }

        return true;
    }

    private void rotate(boolean r) {
        if (curStone == 6) { // O
            return;
        }

        synchronized (field) {
            int[][] o = new int[4][2];

            for (int x = 0; x < 4; x++) {
                o[x] = new int[] { r ? 3 - stone[x][1] : stone[x][1], r ? stone[x][0] : 3 - stone[x][0] };
            }

            if (canRotate(o)) {
                for (int x = 0; x < 4; x++) {
                    field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
                }

                stone = o;

                for (int x = 0; x < 4; x++) {
                    field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
                }

                firePropertyChange("isDirty", false, true);
            }
        }
    }

    private boolean canRotate(int[][] o) {
        for (int x = 0; x < 4; x++) {
            if (o[x][0] + deltaX < 0 ||
                o[x][0] + deltaX >= WIDTH ||
                o[x][1] + deltaY >= HEIGHT ||
                field[o[x][0] + deltaX][o[x][1] + deltaY] > DELTA) {
                return false;
            }
        }

        return true;
    }
}

TODOs:

  • pedras mais rápidas no nível superior (pronto!)
  • animação original de remoção de linha (concluída!)
  • pontos corretos para as linhas (pronto!)
  • interrompa o movimento para baixo em pedra nova (pronto!)
  • tela de recordes
  • comportamento de rotação original
  • menu para escolher o nível
  • tela de fim de jogo
  • audio

Comentários são bem-vindos :)

bobina
fonte
2
Eu realmente gosto muito deste. Uma sugestão para a usabilidade: quando o usuário mantém ↓ para soltar um bloco, 'libera' programaticamente essa tecla depois que o bloco chega (ou seja, o usuário precisaria soltar a tecla e pressione-a novamente para acelerar o próximo bloco) .
primo
Ótimo! Eu já estava pensando em como lidar com isso. Este será um bom método! Obrigado!
bobbel
1
+1 Por me fazer gritar como uma criança de oito anos com seu primeiro Nintendo GameBoy!
WallyWest 28/02
Não tenho certeza se o código Java horrível é horrível ou retro. +1 de qualquer maneira. ;)
DoisO
Para qualquer classe usada uma vez e apenas uma vez, é garantido que você encurte seu código, não importando-o e, em vez disso, use o nome completo no código. Por exemplo, remova import java.awt.event.KeyAdapter;e mude new KeyAdapterpara new java.awt.event.KeyAdapter.
BenGoldberg
8

Lua - 2876

Tetris em um terminal, funciona na maioria dos sistemas unix, lua pura, sem bibliotecas adicionais necessárias.

Os controles são: wasd ou hjkl, w / k para soltar, s / j para girar, ad / hl para mover

A velocidade aumenta com a pontuação, sempre que várias linhas são removidas, você obtém o quadrado da quantidade de linhas destruídas

Esta não é a solução mais possivelmente golfada, mas decidi jogar um pouco de qualquer maneira. As novas linhas são apenas para caber texto em 80 colunas, não as incluí na contagem de caracteres.

math.randomseed(os.time())if arg[1]then local function s(e)local e=io.popen(
"sleep "..e)e:read"*a"return e:close()end local l={{0,1,1,0,1,1},{-1,1,-1,0,1,0}
,{-1,0,1,0,1,1},{-1,0,0,1,1,0},{-1,1,0,1,1,0},{-1,0,0,1,1,1},{-1,0,1,0,2,0}}
function T(t,e,o)if o>1 then return T(-e,t,o-1)end return t,e end local t 
function fit(i,n,r,o)if n<1 or n>10 or r<1 or t[r][n]~=0 then return end for e=1
,6,2 do local e,o=T(l[i][e],l[i][e+1],o)e,o=e+n,o+r if e<1 or e>10 or o<1 or((t[
o]or{})[e]or 0)~=0 then return end end return i end function put(e,r,i,n)t[i][r]
=e for o=1,6,2 do local n,o=T(l[e][o],l[e][o+1],n)n,o=n+r,o+i;(t[o]or{})[n]=e 
end end while 1 do os.execute"clear"io.write("\27[0;37m+----------++----+\r\n"..
("|          ||    |\r\n"):rep(2)..
"|          |+----+\r\n|          |Score:\r\n"..("|          |\r\n"):rep(16)..
"+----------+\27[20F\27[C")t={}for e=1,20 do t[e]={}for o=1,10 do t[e][o]=0 end 
end local e,o,f,r,a,c,n=20,5,0,math.random(7),math.random(7),0,1 while 1 do f=f+
1 s(.1)local d=io.open(arg[1],"rb")or os.exit(0,io.write"\27[49;39m",io.stdout:
flush(),os.execute"clear")local i=d:read(1)while i do if i=="a"or i=="h"then if 
fit(r,o-1,e,n)then o=o-1 end elseif i=="d"or i=="l"then if fit(r,o+1,e,n)then o=
o+1 end elseif i=="w"or i=="k"then while fit(r,o,e-1,n)do e=e-1 end elseif i==
"s"or i=="j"then if fit(r,o,e,n+1)then n=n%4+1 end end i=d:read(1)end d:close()d
=io.open(arg[1],"w")d:write()d:close()if f%(math.max(1,10-math.floor(c/20)))==0 
then if fit(r,o,e-1,n)then e=e-1 else put(r,o,e,n)r,e,o,a,n=a,20,5,math.random(7
),1 if not fit(r,o,e,n)then break end local e=1 local o=0 while e<21 do local n=
1 for o=1,10 do n=n*t[e][o]end if n~=0 then table.remove(t,e)local e={}for o=1,
10 do e[o]=0 end table.insert(t,e)o=o+1 else e=e+1 end end c=c+o*o end end put(r
,o,e,n)for e=20,1,-1 do for o=1,10 do io.write("\27[3"..t[e][o].."m"..(t[e][o]
==0 and" "or"#"))end io.write"\27[E\27[C"end io.write("\27[20F\27[13C\27[3",a,
"m")for e=1,0,-1 do for n=-1,2 do local o for t=1,6,2 do if l[a][t]==n and l[a][
t+1]==e then io.write"#"o=e end end if e==0 and n==0 then io.write"#"o=e end if 
not o then io.write" "end end io.write"\27[4D\27[B"end io.write(
"\27[2B\27[D\27[49;37m",c,"\27[4F\27[C")t[e][o]=0 for i=1,6,2 do local r,n=T(l[r
][i],l[r][i+1],n)r,n=r+o,n+e;(t[n]or{})[r]=0 end end io.write"\27[49;39m"io.
stdout:flush()os.execute"clear"io.write
"\aGame over. Will automatically start new game in 5 seconds. Use ^C to exit"s(5
)io.stdout:flush()end else local e=os.tmpname()local o=io.open(e,"w")local n=
newproxy(true)getmetatable(n).__gc=function()os.execute"stty -raw echo"end os.
execute"stty raw -echo"local t=""i=0 while arg[i]do t=arg[i].." "..t i=i-1 end 
os.execute(t..e.." &")while 1 do o:close()local t=io.read(1)o=io.open(e,"a")o:
write(t)if t=="\3"then o:close()os.remove(e)os.execute"stty -raw echo"
getmetatable(n).__gc=nil os.execute"sleep 0.1"os.exit()end end end

tetris em um terminal

mniip
fonte
3
Você tem uma versão não-gasta?
precisa saber é o seguinte
7
@ nyuszika7h O que você quer dizer? Ele escreveu assim usando cat.
Camilo Martin
1
@CamiloMartin o que é cat, eu estive usando butterfiles
mniip
3
@mniip Oh sim! Bom Ol ' C-x M-c M-butterfly...
Camilo Martin
6

Mathematica

Este código foi escrito no XHTML Wen no Mathematica e pode ser reproduzido em um navegador da Web aqui: Shape Descender (clique nos gráficos para iniciar as teclas de setas). Abaixo estão a captura de tela e o código completo - o que é bastante breve para um aplicativo Web completo deste jogo.

insira a descrição da imagem aqui

Código

allBlocks = {{{{1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 
      0}}, {{0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 
      0}}}, {{{0, 2, 0, 0}, {2, 2, 2, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 2, 0, 0}, {0, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {2, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}, {{0, 2, 0, 0}, {2, 2, 0, 0}, {0, 2, 0, 0}, {0, 0, 0, 
      0}}}, {{{0, 0, 3, 0}, {3, 3, 3, 0}, {0, 0, 0, 0}, {0, 0, 0, 
      0}}, {{0, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 3, 0}, {0, 0, 0, 
      0}}, {{0, 0, 0, 0}, {3, 3, 3, 0}, {3, 0, 0, 0}, {0, 0, 0, 
      0}}, {{3, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 0}}},
   {{{0, 0, 0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 
      0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 
      0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 
      0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}}, {{{0, 0, 0, 
      0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5, 
      0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}, {{0, 0, 0, 
      0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5, 
      0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}}, {{{6, 6, 0, 
      0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6, 
      0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}, {{6, 6, 0, 
      0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6, 
      0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}}, {{{0, 7, 7, 
      0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0, 
      0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}, {{0, 7, 7, 
      0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0, 
      0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}}};
smallBoard = 
  Table[If[i == 1 || j == 1 || i == 6 || j == 6, 9, 0], {i, 1, 6}, {j,
     1, 6}];

color[v_] := 
  Switch[v, 0, Black, 1, Yellow, 2, Blue, 3, Magenta, 4, Cyan, 5, Red,
    6, Orange, 7, Green, 9, Gray, _, Gray];
showSquare[{i_, j_}, v_] := {color[v], 
   Rectangle[{i + 0.05, j + 0.05}, {i + 0.95, j + 0.95}]};
showHintBlock[n_, p_, x_, y_] := 
  Table[showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], {i, 1, 
    4}, {j, 1, 4}];


initBoard[
   Hold[board_, num_]] := {board = 
    Table[If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 9, 0], {i, 1, 
      16}, {j, 1, 28}], 
   Table[board[[i, 24]] = 0; board[[i, 25]] = 0; 
    board[[i, 26]] = 0, {i, 7, 10}]; 
   Table[board[[i, 25]] = 0, {i, 1, 16}]; board[[6, 25]] = 9, 
   board[[11, 25]] = 9,
   Table[If[Mod[j + i, 2] == 0, board[[j, i + 3]] = 9], {i, 1, 
     num}, {j, 4, 13}]};

bMoveTo[n_, p_, x_, y_, Hold[board_]] := 
  Total[Table[
     allBlocks[[n, p, i, j]]* board[[i + x, j + y]], {i, 1, 4}, {j, 1,
       4}], 2] == 0;
bGameOver[n_, p_, Hold[board_]] := 
  Not[bMoveTo[n, p, 6, 22, Hold[board]]];
bLeft[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x - 1, y, Hold[board]];
bRight[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x + 1, y, Hold[board]];
bDown[n_, p_, x_, y_, Hold[board_]] := 
  bMoveTo[n, p, x, y - 1, Hold[board]];
bRotateAnticlock[n_, p_, x_, y_, Hold[board_]] := 
 Module[{tp}, tp = p - 1; If[tp == 0, tp = 4]; 
  bMoveTo[n, tp, x, y, Hold[board]]]; 
bRotateClockwise[n_, p_, x_, y_, Hold[board_]] := 
 Module[{tp}, tp = p + 1; If[tp == 5, tp = 1]; 
  bMoveTo[n, tp, x, y, Hold[board]]];
showBoard[Hold[board_]] := 
  Table[showSquare[{i, j}, board[[i, j]]], {i, 3, 14}, {j, 3, 25}];
showDropBlock[n_, p_, x_, y_] := 
  Table[If[allBlocks[[n, p, i, j]] != 0, 
    showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], Black], {i, 
    1, 4}, {j, 1, 4}];


updateBoard[n_, p_, x_, y_, 
   Hold[board_, score_]] := {Table[
    board[[i + x, j + y]] += allBlocks[[n, p, i, j]], {i, 1, 4}, {j, 
     1, 4}]; score += 
    10 + 100*(2^
         Sum[updateLine[i, Hold[board]], {i, y + 4, y + 1, -1}] - 
        1)};
updateLine[line_, Hold[board_]] := 
  If[line <= 23 && 
    line >= 4 && (Apply[And, 
       Table[board[[i, line]] != 0, {i, 4, 13}]]) == True, 
   Table[board[[i, j]] = board[[i, j + 1]], {i, 4, 13}, {j, line, 
     22}]; Table[board[[i, 23]] = 0, {i, 4, 13}]; 1, 0];
newGame[Hold[n_, p_, x_, y_, board_, nextItem_, gameOver_, pause_, 
    num_]] := {initBoard[
    Hold[board, num]]; {n, p, x, y} = {nextItem, 2, 6, 22}; 
   nextItem = RandomInteger[6] + 1; gameOver = False; pause = False};
newBlock[Hold[n_, p_, x_, y_, board_, score_, gameOver_, nextItem_, 
    pause_]] := {If[! pause && ! gameOver, 
    updateBoard[n, p, x, y, Hold[board, score]]; 
    gameOver = bGameOver[nextItem, 2, Hold[board]]; 
    If[gameOver == False, {n, p, x, y} = {nextItem, 2, 6, 22}; 
     nextItem = RandomInteger[6] + 1]]};


shapeDescend[Hold[level_, state_, num_]] := 
  DynamicModule[{score = 0, gameOver = False, pause = False, 
    board = Table[
      If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 0, 0], {i, 1, 
       16}, {j, 1, 28}], n, p, x, y, nextItem}, 
   SeedRandom[level*10 + num]; nextItem = RandomInteger[6] + 1; 
   newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause, num]];
   EventHandler[
    Graphics[{Text[Style["Level", Blue, Italic, 24], {18, 12}], 
      Text[Style["Score", Blue, Italic, 24], {18, 9}], 
      Table[showSquare[{i + 16, j + 13}, smallBoard[[i, j]]], {i, 1, 
        6}, {j, 1, 6}], 
      Dynamic[Refresh[
        Switch[state, 1, 
         newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause, 
           num]]; state = 3,
         2, pause = True,
         3, pause = False], UpdateInterval -> Infinity, 
        TrackedSymbols -> {state}]; 
       Refresh[If[! pause && ! gameOver && 
          bDown[n, p, x, y, Hold[board]], y--, 
         newBlock[
          Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
         UpdateInterval -> 3./level, TrackedSymbols -> {}];
       Join[{{Text[
           Style[ToString[level], Green, Italic, 24], {20, 11}], 
          Text[Style[ToString[score], Green, Italic, 24], {20, 8}]}}, 
        showBoard[Hold[board]], showDropBlock[n, p, x, y], 
        showHintBlock[nextItem, 2, 17, 14]], 
       TrackedSymbols -> {x, y, n, p, level}]}, Background -> Black, 
     ImageSize -> {400, 400}], {"LeftArrowKeyDown" :> 
      If[! pause && ! gameOver && bLeft[n, p, x, y, Hold[board]], x--],
     "RightArrowKeyDown" :> 
      If[! pause && ! gameOver && bRight[n, p, x, y, Hold[board]], 
       x++], "DownArrowKeyDown" :> (If[! pause && ! gameOver && 
         bRotateClockwise[n, p, x, y, Hold[board]], p++; 
        If[p == 5, p = 1]]), 
     "UpArrowKeyDown" :> (If[! pause && ! gameOver && 
         bRotateAnticlock[n, p, x, y, Hold[board]], p--; 
        If[p == 0, p = 4]]), {"KeyDown", " "} :> 
      If[! pause && ! gameOver && bDown[n, p, x, y, Hold[board]], y--,
        newBlock[
        Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
     "EscapeKeyDown" :> {newGame[
        Hold[n, p, x, y, board, nextItem, gameOver, pause, num]]}}]];

Manipulate[
 shapeDescend[Hold[level, state, initLine]],
 {{level, 5}, 1, 9, 1},
 {{initLine, 3, "height of bottom level"}, 0, 9, 1},
 {{state, 3, ""}, {1 -> "new game", 2 -> "pause", 3 -> "continue"}},
 ContentSize -> {480, 450}, SynchronousUpdating -> False, 
 SaveDefinitions -> True, Alignment -> Center, TrackedSymbols -> {}]
Vitaliy Kaurov
fonte
4

C

Escrevi isso há muito tempo atrás, sentado entediado na sala de aula no ensino médio. Supostamente, o programador original escreveu a primeira versão do Tetris usando colchetes como blocos, e parecia simples o suficiente para tentar recriar, não? Como eu não conhecia nada de GUI, criei um bom programa de console à moda antiga. Isso foi antes de eu aprender C ++ e aquelas práticas de programação apropriadas, então o código pode ser um pouco confuso. Eu praticamente voei.

TKlone

Ele atende a todos os requisitos do desafio, exceto que a peça gira apenas em uma direção (no sentido horário). Use WASD para tocar, W gira a peça. O código fonte completo e o exe podem ser encontrados aqui: http://sourceforge.net/projects/tklone/files/tklone/tklone-v1.0/

tetris.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include "defines.h"

int array[FIELD_H][FIELD_W];    //field array
int pieceCoords[2];             //current piece coordinates
int x, y;                       //looping variables
int thisPiece, nextPiece;       //current pieces
double score;                   //the players current score
int newpiece=1;                 //the newpiece flag
int init_h=1;                   /*starting coordinates of piece*/
int init_w=4;                   /*these are obsolete variables*/
int h_offset, w_offset;         //piece position offsets from start
int rtTop, rtBot, rtLeft, rtRight;  //rotation thresholds for collision
int b1h, b1w, b2h, b2w, b3h, b3w;   //block draw offsets
int rotation;                   /*0=normal view
                                  1=90 degrees clockwise
                                  2=upside-down
                                  3=270 degrees clockwise */

clock_t speed;          //time before piece falls (in milliseconds)
clock_t gravity;        //temp variable for speed

//clock_t fps;
int piececount;         //number of pieces that have fallen
int oldpiececount;
int level;


void GameInit(void);
int GameStart(void);
int GameMain(void);
void newPiece(void);

void scrollbar(void);           //scrolling marquee bar
void drawField(void);           //graphics rendering function
void setPiece(int piece);       //this sets the individual piece offsets
void drawPiece(void);           //this sets the blocks in the array
void initArray(void);           //this initializes the array
void getMoves(char move);       //input function
void rotate(int coll);          //loops from 1 to 4
int collisionCheck(int side);   //checks to see if piece will collide
int loseGame(void);             //checks to see if game has ended
void eraseLastMove(void);       //clears the array of the last move
void setLastMove(void);         //sets the piece in place for the next piece
void lineClear(void);           //clears filled lines and tabulates the score
void predict(int undo);         //predicts the next piece rotation for collision
//void delay(int duration);

int main()
{
restart:
    GameInit();
    //some test blocks
    /*for(x=FIELD_H;x>3;x--)        //quick lose pattern
        array[x][4]=1;*/

    nextPiece=rand()%7;     //generate a piece to use
    drawField();            //draw a blank field. should be a title screen
    printf("                             / Welcome to TETRIS! \\"); //29 spaces and title

    getch();            //wait to start the game

    while(1)    //main game loop
    {
        if(newpiece==1)         //check to see if a new piece is required
            newPiece();

        //if(gravity<=clock())
            gravity=speed+clock();  //get the end time
        while(gravity>clock())      //count to the end time
        {
            if(kbhit())
            {
                getMoves(getch());  //get input
                if(newpiece==1)     //if piece reaches bottom...
                    break;          //...skip by redraw to save some cycles
                else
                {
                    eraseLastMove();    //clear the last move from the array
                    drawPiece();        //set the piece in the array
                    drawField();        //refresh the screen
                    scrollbar();        //draw the scrollbar
                }
            }
        }
        if(newpiece==0)     /*minor speed optimization*/
            getMoves('s');  //force piece down a line
        eraseLastMove();    //clear the last move from the array
        drawPiece();        //set the piece in the array
        drawField();        //refresh the screen
        scrollbar();        //draw the scrollbar
    }
    return 0;
}

void GameInit()
{
    srand((unsigned)time(NULL));    //seed the random number genreator...
    initArray();                    //...and initialize the array
    //reset some variables
    score=0;
    piececount=-1;      //set to -1 because of newpiece loop on start
    oldpiececount=0;
    speed=1000;         //starting speed of piece
    level=1;
}

void newPiece()
{
    piececount++;       //another piece has fallen
    setLastMove();      //set the last move in place
    lineClear();        //clear any full lines

    newpiece=0;         //reset the newpiece flag
    rotation=0;         //reset the piece rotation
    h_offset=0;         //reset the...
    w_offset=0;         //...piece coordinates
    gravity=0;          //reset the gravity

    if(piececount-oldpiececount==10)    //if 10 more pieces have fallen
        if(speed!=200)                  //200 is the lowest speed it can go
        {
            oldpiececount=piececount;   //set current number of fallen pieces
            speed-=100;                 //make the speed faster
            level++;                    //increment the level counter
        }

    thisPiece=nextPiece;//get the current piece
    nextPiece=rand()%7; //pick the next piece

    setPiece(thisPiece);//get piece data
    if(loseGame())      //did player lose the game?
    {
        drawField();    //refresh the screen
        printf("You lose! Final score: %.0f. Press any key to begin a new game.",score);
        getch();
        newpiece=1;     //reset the newpiece flag /*add more initialization here*/
        //goto restart; //return to start of the program
        exit(0);        //quit the game for now
    }
    drawPiece();        //set the piece in the array
    drawField();        //refresh the screen
    scrollbar();        //draw the scrollbar
}

void scrollbar()
{
    //printf("================================================================================");

    printf("Score: %.0f\t\t      ",score);  //print the score (6 spaces)
    if(nextPiece==5||nextPiece==6)          //print a space if the piece name is short
        printf(" ");

    printf("Next piece: ");
    if(nextPiece==0)            //print the name of the next piece
        printf("straight");
    else if(nextPiece==1)
        printf("t-shaped");
    else if(nextPiece==2)
        printf("s-shaped");
    else if(nextPiece==3)
        printf("z-shaped");
    else if(nextPiece==4)
        printf("l-shaped");
    else if(nextPiece==5)
        printf("back-l");
    else if(nextPiece==6)
        printf("square");

    printf("\t\t\tLevel %i",level); //print the level
}

void drawField()
{
    system("cls");      //clear the screen

    for(x=0;x<FIELD_H;x++)
    {
        printf("                              |");  //thirty spaces

        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==1||array[x][y]==2)
                printf("[]");
            else
                printf("  ");

        printf("|                              ");  //thirty spaces
    }

    //bottom message bar
    //scrollbar();
}

void drawPiece()
{
    /* This sets the initial location of the piece with
       movement offset. Coordinates are based on the
       center of the piece. */
    pieceCoords[0]=init_h+h_offset; //set height position
    pieceCoords[1]=init_w+w_offset; //set width position

    //mark the coordinates to draw the piece
    array[(pieceCoords[0])][(pieceCoords[1])]=2;        //center piece
    array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]=2;    //other...
    array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]=2;    //...three...
    array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]=2;    //...pieces
}

void initArray()
{
    for(x=0;x<FIELD_H;x++)
        for(y=0;y<FIELD_W;y++)
            array[x][y]=0;
        //array equals: 0 for empty
        //              1 for full
        //              2 for moving piece
}

void getMoves(char move)
{
    switch(move)
    {
        case 'a':
        case 'A':
            if(collisionCheck(1)==0)    //if the piece doesnt hit something...
                w_offset--;             //...move the piece
            break;
        case 'd':
        case 'D':
            if(collisionCheck(2)==0)
                w_offset++;
            break;
        case 'w':
        case 'W':
            if(collisionCheck(0)==0)
            {
                rotate(0);              //rotate the piece and...
                setPiece(thisPiece);    //...get new piece data
            }
            break;
        case 's':
        case 'S':
            if(collisionCheck(3)==0)
                h_offset++;
            else                        //if the piece hits the bottom...
                newpiece=1;             //...set the newpiece flag
            break;
        default:
            break;
    }
}

void rotate(int coll)
{
    if(coll==0)     //rotate clockwise
    {
        if(rotation==3)
            rotation=0;
        else
            rotation++;
    }

    if(coll==1)     //rotate counterclockwise
    {
        if(rotation==0)
            rotation=3;
        else
            rotation--;
    }
}

int collisionCheck(int side)
{
    if(side==0) //check rotation collision
    {
        predict(0);

        /* For the first part, we check to see if the
           rotation will collide with the sides. */
        for(x=0;x<rtTop;x++)    //check top side collision
        {
            if(pieceCoords[0]-x==0) //if piece minus top offset hits the top
            {
                predict(1);
                return 1;   //collision detected!
            }
        }

        for(x=0;x<rtLeft;x++)   //check left side collision
        {
            if(pieceCoords[1]-x==0)
            {
                predict(1);
                return 1;
            }
        }

        for(x=0;x<rtRight;x++)  //check right side collision
        {
            if(pieceCoords[1]+x==(FIELD_W-1))
            {
                predict(1);
                return 1;
            }
        }

        for(x=0;x<rtBot;x++)    //check bottom side collision
        {
            if(pieceCoords[0]+x==(FIELD_H-1))
            {
                predict(1);
                return 1;
            }
        }

        /* Then we check to see if any of the
           blocks will hit other blocks on the field. */
        if(array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]==1
    ||array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]==1
    ||array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]==1)
        {
            predict(1);
            return 1;
        }

        predict(1);
        return 0;
    }

    /* Here, we scan the array and check to see whether
       any of the blocks in the current piece are
       adjacent to the field sides or another block. */
    for(x=0;x<FIELD_H;x++)          //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if array location contains a block
            {
                if(side==1) //check left side
                {
                    if(y==0||array[x][y-1]==1)  //if piece is next to border or block...
                        return 1;   //collision detected!
                }

                else if(side==2)    //check right side
                {
                    if(y==FIELD_W-1||array[x][y+1]==1)
                        return 1;
                }

                else if(side==3)    //check bottom
                {
                    if(x==FIELD_H-1||array[x+1][y]==1)
                        return 1;
                }
            }

    return 0;
}

int loseGame()      /*need to modify the conditions of losing*/
{
    if(array[init_h][init_w]==1
||array[(init_h+b1h)][(init_w+b1w)]==1
||array[(init_h+b2h)][(init_w+b2w)]==1
||array[(init_h+b3h)][(init_w+b3w)]==1)
        return 1;

    else
        return 0;
}

void eraseLastMove()    /*need to optimize this*/
{
    for(x=0;x<FIELD_H;x++)      //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if the spot is filled...
                array[x][y]=0;      //...clear the block
}

void setLastMove()      /*also need to optimize this*/
{
    for(x=0;x<FIELD_H;x++)      //scan the array
        for(y=0;y<FIELD_W;y++)
            if(array[x][y]==2)      //if the spot is filled...
                array[x][y]=1;      //...set the block
}

void lineClear()
{
    int x2, y2; //new looping variables
    int empty;  //is the row empty?
    int lines=0;//number of lines cleared

    for(x=0;x<FIELD_H;x++)      //scan the rows
    {
        empty=0;                //reset the empty flag on each row

        for(y=0;y<FIELD_W;y++)  //scan the blocks in each row
            if(array[x][y]==0)  //if the row isn't full...
            {
                empty=1;        //...set the empty flag and...
                break;          //...scan the next row
            }

        if(!empty)
        {
            for(y=0;y<FIELD_W;y++)      //rescan the filled row...
                array[x][y]=0;          //...and clear the blocks

            for(x2=x-1;x2>=0;x2--)      //scan rows from the bottom up starting above cleared row
                for(y2=0;y2<FIELD_W;y2++)   //scan each block in the row
                    if(array[x2][y2]==1)
                    {
                        array[x2][y2]=0;    //clear the block
                        array[x2+1][y2]=1;  //fill in the block below it
                    }

            lines++;            //a line has been cleared
        }
    }

    //tabulate the scores
    if(lines==1)
        score+=10;
    else if(lines==2)
        score+=25;
    else if(lines==3)
        score+=50;
    else if(lines==4)   /* TETRIS! */
        score+=100;
}

void predict(int undo)
{
    if(undo==0)
    {
        rotate(0);              //get next rotation...
        setPiece(thisPiece);    //...and next piece data
    }

    if(undo==1)
    {
        rotate(1);              //un-rotate
        setPiece(thisPiece);    //reset the piece
    }
}

/*void delay(int duration)
{
    clock_t done;

    done = duration + clock();
    while(done>clock())
        ;   //sit and wait
}*/

setPiece.c

#include "defines.h"

extern int rtTop, rtBot, rtLeft, rtRight;   //rotation thresholds for collision
extern int b1h, b1w, b2h, b2w, b3h, b3w;    //block draw offsets
extern int rotation;                    /*0=normal view
                                          1=90 degrees clockwise
                                          2=upside-down
                                          3=270 degrees clockwise */

void setPiece(int piece)
{
    if(piece==0)
    {
        switch(rotation)
        {
            case 0:     //piece looks identical rotated upside-down
            case 2:
                /* The rotation thresholds dictate how far away the piece
                   needs to be from the field edge for the next rotation. */
                rtTop=1;
                rtBot=2;
                rtLeft=0;
                rtRight=0;

                /* These are the coordinates of the
                   blocks relative to the center block. */
                b1h=-1;
                b1w=0;
                b2h=1;
                b2w=0;
                b3h=2;
                b3w=0;
                break;
            case 1:     //piece looks identical side to side
            case 3:
                rtTop=0;
                rtBot=0;
                rtLeft=1;
                rtRight=2;

                b1h=0;
                b1w=-1;
                b2h=0;
                b2w=1;
                b3h=0;
                b3w=2;
                break;
        }
    }
        //draw blue straight piece

    //snip other pieces

    else if(piece==6)
    {
        switch(rotation)
        {
            case 0:     //a cube looks identical when rotated 90 degrees
            case 1:
            case 2:
            case 3:
                rtTop=0;    //piece doesn't rotate so no thresholds needed
                rtBot=0;
                rtLeft=0;
                rtRight=0;

                b1h=-1;
                b1w=0;
                b2h=-1;
                b2w=1;
                b3h=0;
                b3w=1;
                break;
        }
    }
        //draw square piece
}
Bigbio2002
fonte
Oi! Bem-vindo ao PPCG. Recomendamos que as respostas incluam o código-fonte no corpo da resposta, a menos que seja muito grande para o tamanho máximo da postagem. Mesmo assim, é bom fornecer alguns trechos ilustrativos.
Jonathan Van Matre
2
Código adicionado. setPiece.c foi abreviado um pouco e define.h não está listado. O código original pode ser encontrado no link.
Bigbio2002
1
Impressionante! Obrigado por compartilhar.
Jonathan Van Matre
1

Eu construí uma versão javascript https://marchingband.github.io/tetris/

insira a descrição da imagem aqui

<!DOCTYPE html>
<html>
  <body>
      <p>q and w rotate</p>
      <p id='score'>0</p>
      <div id='board' style='position:absolute;top:80px' ></div>
  </body>
</html>
  <script>
    var pieceIndex=0
    nextPiece=()=>{
      pieceIndex=pieceIndex<pieces.length-1?pieceIndex+1:0
      return pieces[pieceIndex]
    }
    const pieces=[
    [
        {x:0,y:-1},
        {x:0,y:0},
        {x:1,y:0},
        {x:-1,y:0},
      ],
      [
        {x:0,y:-2},
        {x:0,y:-1},
        {x:0,y:0},
        {x:0,y:1},
      ],
      [
        {x:1,y:0},
        {x:0,y:0},
        {x:2,y:0},
        {x:0,y:-1},
      ],
      [
        {x:0,y:0},
        {x:1,y:0},
        {x:1,y:1},
        {x:0,y:1}
      ],
      [
        {x:0,y:0},
        {x:-1,y:0},
        {x:0,y:1},
        {x:1,y:1}
      ],
    ]
    const speeds =[1000,800,600,400,300,250,200,150,100,90,80,70,60,50,40,30,20]
    const boardWidth = 10
    const boardHeight = 20
    const pixelSize   = 10
    const pixelBorder = 0
    const boardBorder = 5
    
    var score = 0
    var level = 0
    var fallingBits = []

    const b = document.getElementById('board')
     piece = {
      position:{
        x:boardWidth/2,
        y:1,
      },
      bits:[
        {x:0,y:-1},
        {x:0,y:0},
        {x:1,y:0},
        {x:-1,y:0},
      ],
      reset(){
        piece.position={x:boardWidth/2,y:1}
        piece.bits=nextPiece()
        !piece.coordinates().every((b)=>dom[b.x][b.y]?dom[b.x][b.y].style.background=='red':true) && endGame()
      },
      coordinates(){
        return piece.bits.map((b)=>{
          return {x:b.x+piece.position.x,y:b.y+piece.position.y}
        })
      }
    }
    const dom = []
    for(var x=0;x<boardWidth;x++){
      var row = []
      for(var y=0;y<boardHeight;y++){
        var cell = document.createElement('div')
        cell.style.cssText='outline:thin solid;height:'+pixelSize+'px;width:'+pixelSize+'px;background:red;position:absolute'
        cell.style.left= (x*(pixelSize+pixelBorder)+boardBorder) + 'px'
        cell.style.top = (y*(pixelSize+pixelBorder)+boardBorder) + 'px'
        row.push(cell)
        b.appendChild(cell)
      }
      dom.push(row)
    }
    renderPiece=(color)=>{
      piece.coordinates().forEach((b)=>dom[b.x][b.y] && (dom[b.x][b.y].style.background=color))
    }
    drop=()=>{
      piece.coordinates().every((p)=>p.y+1<boardHeight) && piece.coordinates().every((p)=>dom[p.x][p.y+1].style.background!='blue') ?
        movePiece(0,1) : freeze()
    }
    strafe=(d)=>{
      piece.coordinates().every((p)=>p.x+d<boardWidth && p.x+d >=0) && piece.coordinates().every((p)=>dom[p.x+d][p.y].style.background!='blue') ?
        movePiece(d,0) : null
    }
    freeze=()=>{
      piece.coordinates().forEach((p)=>dom[p.x][p.y].style.background='blue')
      lookForRows()
      piece.reset()
    }
    lookForRows=()=>{
      [...dom[0].keys()].filter((i)=>dom.every((c)=>c[i].style.background=='blue')).forEach((r)=>{
        removeLine(r)
        compressAbove(r)
        addPoint()
      })
    }
    compressAbove=(r)=>{
      // dom.reduce((acc,col,x)=>{return acc.concat(col.map((b,y)=>{return {x:x,y:y}}).filter((b,y)=>y<r && dom[b.x][b.y].style.background=='blue'))},[]).forEach((b)=>dom[b.x][b.y].style.background='red').forEach((b)=>dom[b.x][b.y+1].style.background='blue')
      var bits = []
      dom.forEach((x,i)=>{
        x.forEach((y,j)=>{
          if(j<=r && dom[i][j].style.background=='blue'){bits.push({x:i,y:j})}
        })
      })
      bits.forEach((b)=>{
        dom[b.x][b.y].style.background='red'
      })
      bits.forEach((b)=>{
        dom[b.x][b.y+1].style.background='blue'
      })
    }
    removeLine=(r)=>{
      dom.forEach((x)=>x[r].style.background='red')
    }
    changePiece=(x,y)=>{
      piece.position.x += x
      piece.position.y += y
    }
    movePiece=(x,y)=>{
        renderPiece('red')
        changePiece(x,y)
        renderPiece('yellow')
    }
    turn=(d)=>{
      var newBits = []
      for(let i=0;i<piece.bits.length;i++){
        var x = piece.bits[i].x
        var y = piece.bits[i].y
        var bit={}
        if(d==1){
          bit.x=-y
          bit.y=x
        }else{
          bit.x=y
          bit.y=-x
        }
        newBits.push(bit)
      }
      if(newBits.every((p)=>p.x+piece.position.x<boardWidth && p.x+piece.position.x>=0 && p.y+piece.position.y<boardHeight && dom[p.x+piece.position.x][p.y+piece.position.y].style.background!='blue')){
        renderPiece('red')
        piece.bits=newBits
        renderPiece('yellow')
      }
    }
    addPoint=()=>{
      score++
      document.getElementById('score').innerText=score
      score-level*10 > 10 && levelUp()
    }
    levelUp=()=>{
      clearInterval(timer)
      level++
      timer = window.setInterval(drop,speeds[level])
    }
    endGame=()=>{
      clearInterval(timer)
      dom.forEach((row)=>{
        row.forEach((bit)=>{
          bit.style.background=='red' && (bit.style.background='black')
        })
      })
    }
    document.onkeydown = function(e) {
    switch (e.keyCode) {
        case 37:strafe(-1);break
        case 87:turn(1);break
        case 81:turn(-1);break
        case 39:strafe(1);break
        case 40:drop();break
    }
};
  var timer = window.setInterval(drop,1000)
  </script>
</html>

Andrew March
fonte
Agradável. Infelizmente, há um pequeno bug no Firefox, onde todas as peças caem até o fundo do poço, porque dom[p.x][p.y+1].style.backgroundretornam blue none repeat scroll 0% 0%, portanto nunca serão iguais blue. Sua solução é válida, mas sugiro alterar tudo .style.background.style.backgroundColorpara que funcione bem no Firefox e no Chrome.
manatwork