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. |