Tetris passo-a-passo
Parece ser muito fácil fazer um. E é!


Programar um jogo não é um bicho de sete cabeças e vou mostrar como seria possível fazer um Tetris, no Delphi, com um pé nas costas, uma mão amarrada na cadeira e com o mouse de cabeça para baixo, à lá trackball.

Dedos à postos, vocês verão como é simples. Mas, antes que alguém pergunte: "por que Delphi e não C++, Turbo Assembler ou Sânscrito arcaico?" já adianto que a ferramenta que usamos na construção de um jogo não é importante mas sim o que executamos com ela e, no caso, escolhi o pacote RAD da Borland porque é de longe o melhor sistema de programação dos tempos modernos. Aviso ainda que não vou usar nada escabroso: apenas um formulário e um componente TTimer.

Obviamente que no tempo das interfaces gráficas, o barato seria mostrar como fazer um jogo amplamente ilustrado, cheio de figuras e multi colorido. Ledo engano: vou mostrar a programação, ou seja o lado "nerd" do jogo. O visual fica por conta de cada um, adaptando o jogo de modo a personalizá-lo.

Então lá vamos nós... (dispensando os inicialmentes, referentes ao Delphi e à inicialização de um projeto novo).

Estabelecemos que o jogo terá 14 peças, definidas por uma configuração matricial de 3 x 3 pontos, ou seja, num array 3 x 3, o valor 1 indica um quadrado e o valor 0 indica posição vazia. Então, a primeira providência é montar uma constante global para a forma, de modo a conter a definição de cada uma dessas peças:

const
  Peca: array[1..14,0..8] of byte = (
         (1,0,0,0,0,0,0,0,0),
         (1,1,0,0,0,0,0,0,0),
         (1,0,0,1,0,0,0,0,0),
         (1,1,1,0,0,0,0,0,0),
         (1,0,0,1,0,0,1,0,0),
         (1,1,0,1,0,0,0,0,0),
         (1,1,0,0,1,0,0,0,0),
         (0,1,0,1,1,0,0,0,0),
         (1,0,0,1,1,0,0,0,0),
         (1,1,0,1,1,0,0,0,0),
         (1,1,1,0,1,0,0,0,0),
         (0,1,0,1,1,0,0,1,0),
         (0,1,0,1,1,1,0,0,0),
         (1,0,0,1,1,0,1,0,0));

No embalo, declaramos todas as variáveis globais necessárias ao jogo:

var
  Pan: array[0..9,0..20] of byte;
  Tip: byte;
  Px,Py: integer;

As variáveis Px e Py conterão as coordenadas do canto superior esquerdo da peça, em relação à posição de impressão da mesma. Tip define que peça está sendo usada.

"Pan" é uma matriz com 21 linhas, cada linha contendo 10 posições. Essas posições serão zeradas, para indicar que elas estão "vazias". Se uma posição contém o valor "1" é porque alí foi colocado um "bloco" (não uma peça, mas apenas um bloco de peça). Por definição, um bloco de peça será desenhado no canvas do Form1 com as dimensões de 20 x 20 pixels, tendo portanto o campo de peças (onde as peças aparecerão) 200 pixels de largura por 400 pixels de altura.

O primeiro trabalho a ser realizado por nosso programa é a inicialização do jogo e portanto usaremos o evento OnCreate, do Form1. Basta clicar a aba Events, no Object Incpector e dar um duplo clique em OnCreate. Depois basta acrescentar a programação a seguir:

procedure TForm1.FormCreate (Sender: TObject);
var
  Tc,Tl: integer;
begin
  Randomize;

  //Redefine o formulário
  ClientWidth:= 200;
  ClientHeight:= 400;

  //Zera as posições
  for Tl:= 0 to 19 do
    for Tc:= 0 to 9 do
      Pan[Tc,Tl]:= 0;

  //Última linha +1
  for Tc:= 0 to 9 do Pan[Tc,20]:= 1;
  Py:= 0; Px:= 60;

  //Escolhe uma peça
  Tip:= Random(14)+1;
end;

O destaque fica por conta da linha 20. Os mais atenciosos irão coçar a cabeça: tem uma linha a mais! Se cada figura tem 20 x 20 pixels e a área de descida das peças tem 400 pixels, só caberão 20 linhas e de 0 a 20 são 21 linhas. Ocorre que a última linha, portanto uma linha fora da visão ou da área do jogo, será toda preenchida com o valor 1. Assim, ela fica sendo uma espécie de linha de barreira, para o momento em que se testar a presença ou não de blocos já posicionados.

A variável Tip contém o número da peça "sorteada" e varia de 1 a 14.

Para montar uma peça na área do jogo, criaremos uma procedure independente:

procedure MostraPeca;
var
  Tc,Tl: integer;
begin
  for Tl:= 0 to 2 do for Tc:= 0 to 2 do
    if Peca[Tip,Tl*3+Tc] = 1 then
    Form1.Canvas.FillRect(bounds(Tc*20+1+Px,
                                 Tl*20+1+Py,18,18));
end;

Ou seja, ela percorre a matriz de forma (apontada por Tip) e verifica se é para desenhar um bloco (1) ou não (0). No caso de desenhar, a cor definida por Canvas.Brush.Color será a cor usada e portanto, antes de chamar essa procedure, temos que definir a cor desejada. Neste jogo usaremos apenas duas cores: clNavy, que é a cor azul marinho para mostrar um bloco e clSilver, que é o cinza de fundo do formulário, usado no caso para "apagar" uma peça, antes de movê-la para baixo.

O passo seguinte é colocar no Form1 um componente TTimer: aba System (o reloginho). No evento OnTimer, colocamos a seguinte programação:

procedure TForm1.Timer1Timer (Sender: TObject);
var
  Tc,Tl,Pc,Pl: integer;
begin
  //Apaga a peça
  Canvas.Brush.Color:= clSilver;
  MostraPeca;
  Canvas.Brush.Color:= clNavy;

Apagamos a peça e já deixamos a cor pronta para colocar a peça em sua nova posição (se houver).

  //Ver se para onde ela vai, é possível ir
  for Pl:= 0 to 2 do for Pc:= 0 to 2 do
  if (Peca[Tip,Pl*3+Pc] = 1) and
    (Pan[Px div 20+Pc,Py div 20+Pl+1] = 1) then begin

Ou seja, percorremos a área correspondente a uma nova possível posição da peça (uma linha à frente), para verificar se está completamente livre. Se não estiver, a peça não poderá seguir adiante e deverá ser "colada" na sua posição atual.

    //Não, não pode mover... cola a peça
    for Tl:= 0 to 2 do for Tc:= 0 to 2 do
    if Peca[Tip,Tl*3+Tc] = 1 then begin
      Canvas.FillRect(bounds(Tc*20+1+Px,Tl*20+1+Py,18,18));
      Pan[Px div 20 + Tc,Py div 20 + Tl]:= 1;
    end;

Se colou uma peça na primeira linha (linha zero), então o jogo acabou.

    //Acabou o jogo
    if Py = 0 then begin
      Timer1.Enabled:= false;
      Canvas.Font.Size:= 16;
      Canvas.Brush.Style:= bsClear;
      Canvas.TextOut(0,170,'F I M');
      exit;
    end;

Se não, então escolhe uma nova peça e siga em frente.

    //Escolhe uma nova peça
    Py:= -20; Px:= 60;
    Tip:= Random(14)+1;
  end;
  Py:= Py+20; MostraPeca;
end;

Só falta agora os controles de movimento do jogo e para isso usaremos o evento OnKeyDown do Form1.

procedure TForm1.FormKeyDown(Sender:TObject;
           var Key: Word; Shift: TShiftState);
var
  Tm,Tc,Tl: integer;
begin
  Canvas.Brush.Color:= clSilver;

Pronto para apagar uma peça, se ela for deslocada para os lados ou rotacionada.

  //Tecla para a esquerda
  if Key = VK_Left then begin
    //No limite esquerdo
    if Px = 0 then exit;
    MostraPeca;
    Px:= Px - 20;
  end;

Se pressionar a tecla seta para a esquerda, verificamos a variável Px. Caso seja zero, então significa que a peça já está na lateral esquerda. Se não, move-a 20 pixels para a esquerda.

  //Tecla para a direita
  if Key = VK_Right then begin
    Tm:= 180;
    //Define o limite em função da peça
    if (Peca[Tip,1] = 1) or
       (Peca[Tip,4] = 1) or
       (Peca[Tip,7] = 1) then Tm:= 160;
    if (Peca[Tip,2] = 1) or
       (Peca[Tip,5] = 1) or
       (Peca[Tip,8] = 1) then Tm:= 140;
    if Px = Tm then exit;
    MostraPeca;
    Px:= Px + 20;
  end;

A verificação à direita é mais complexa, pois ela depende também da largura da peça. Uma peça pode possuir um, dois ou três blocos de largura e isso deve ser levado em consideração.

  //Rotação da peça
  if Key = VK_Control then begin
    MostraPeca;
    case Tip of
         2: Tip:= 3;
         3: Tip:= 2;
         4: Tip:= 5;
         5: Tip:= 4;
         6: Tip:= 7;
         7: Tip:= 8;
         8: Tip:= 9;
         9: Tip:= 6;
         11: Tip:= 12;
         12: Tip:= 13;
         13: Tip:= 14;
         14: Tip:= 11;
    end;

Para fazer a rotação da peça, basta olhar para a variável Tip e alterá-la para a forma correspondente. A tecla Control provoca essa rotação.

    //Reajusta após a rotação
    Tm:= 180;
    if (Peca[Tip,1] = 1) or
       (Peca[Tip,4] = 1) or
       (Peca[Tip,7] = 1) then Tm:= 160;
    if (Peca[Tip,2] = 1) or
       (Peca[Tip,5] = 1) or
       (Peca[Tip,8] = 1) then Tm:= 140;
    if Px > Tm then Px:= Tm;
  end;

Se uma peça não simétrica for rotacionada perto da lateral direita da área de descida, será preciso então reposicioná-la para que não fique nenhum bloco "sobrando" para o lado de fora da lateral.

  Canvas.Brush.Color:= clNavy;
  MostraPeca;
end;

Fim. Acabou. É só isso. Aqui está o que poderíamos chamar de uma "engine" tetris, ou seja, o processamento de um jogo deste modelo. Cada designer pode então adaptá-la para conter um fundo ilustrativo, o logotipo do seu site, variar a cor dos blocos, etc. A criatividade será portanto o único limite.

 
online