0% acharam este documento útil (0 voto)
598 visualizações31 páginas

Tetris Tutorial para Iniciantes

Este documento apresenta um tutorial em C++ para criar um clone do jogo Tetris de forma independente da plataforma. O tutorial ensina a lógica por trás do jogo, como armazenar as peças e tabuleiro usando matrizes, resolver rotações das peças e verificar colisões. O código fonte completo é fornecido para compilar no Windows ou Linux.

Enviado por

leaors
Direitos autorais
© Attribution Non-Commercial (BY-NC)
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
598 visualizações31 páginas

Tetris Tutorial para Iniciantes

Este documento apresenta um tutorial em C++ para criar um clone do jogo Tetris de forma independente da plataforma. O tutorial ensina a lógica por trás do jogo, como armazenar as peças e tabuleiro usando matrizes, resolver rotações das peças e verificar colisões. O código fonte completo é fornecido para compilar no Windows ou Linux.

Enviado por

leaors
Direitos autorais
© Attribution Non-Commercial (BY-NC)
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 31

Tutorial tetris em C++ com

plataforma independente, focada na


lgica do jogo para iniciantes.
Autor Original:
Javier Lpez Lpez em GameTuto
Traduzido por:
Samuel Leonardo (Sam L.)
Por Favor Acesse o Original Em Ingls:
http://gametuto.com/tetris-tutorial-in-c-render-independent/

Ns iremos aprender como criar um clone do Tetris do inicio usando


simples e limpo C++. E isto levar menos de uma hora! Este o perfeito
tutorial para iniciantes. S se divertir e deixar um comentrio, se quiser
explicar-me a algo melhor.
Sei que meu Ingls uma porcaria, por isso, se voc observar alguns erros,
por favor, me diga. Vamos l!

Baixar codigo fonte


Aqui est ele o codigo fonte completo.
Plataforma Windows.
O codigo fonte vem com os cabealhos da SDL e bibliotecas, pronto
para compilar no Visual C++ Express Edition 2008.
Na pasta Realease l existe um arquivo executvel, apenas no caso de
voc querer experiment-lo diretamente.

Outras plataformas(para usuario avanados).


Obrigado ao Imelior e Javier Santana, por existir uma verso Linux deste
tutorial.O cdigo fonte independete de plataforma e vem com um
makefile.
Contudo, sob Linux, voc precisar da libsdl-gfx1.2-dev e libsdl1.2-dev, se
voc est usando Ubuntu voc pode obt-la por este meio:
$ sudo apt-get install libsdl1.2-dev libsdlgfx1.2-dev
Teclas:

ESC = Terminar o jogo.

z = Rotacionar a pea.

x = deixar cair a pea

seta Esquerda, Direita e Baixo = Eu no ofenderei sua inteligncia.

Passo 0: Introduo
Ns vamos focalizar na lgica do jogo, utilizando apenas rectngulo
primitivas (SDL_Rect), para a renderizao.
Toda a lgica do jogo isolada do desenho, ento voc pode expandir o
tutorial facilmente.
Estou fazendo um planejamento do segundo tutorial de como melhorar
este Colne do Tetris usando sprites, background, efeitos, etc.
Mas agora, vamos nos concentrar na lgica do jogo. Esta a forma como o
seu prottipo ficar depois de terminar o tutorial:

Neste tutorial voc vai aprender:

Como conservar as peas e tabuleiro utilizando matrizes (arrays


multidimensionais).
Como resolver o problema de rotao no Tetris, em uma maneira muito
fcil, sem usar a matemtica complexa ou nada difcil, basta usar um
truque inteligente.
Como verificar colises entre as peas e o tabuleiro.
Como o loop principal de um jogo Tetris trabalha(ou funciona).

O que suponho que voc j conhece:

C++
Um pouco de programao grfica, se quiser ampliar o tutorial com
grficos melhorados.No se preocupe com o que se v, s queira saber
a lgica do jogo Tetris.

O que voc precisa?

Um compilador ou uma IDE de programao. Eu usei Visual C++ Express


Edition para este tutorial, que uma IDE livre. Mas voc pode usar uma
de sua escolha claro.
Vontade de aprender :D

Qual a licena do cdigo fonte?


O cdigo fonte est sob a Creatives Commons Attribution 3.0
Unported. Isso significa que voc pode copiar, distribuir e transmitir o
trabalho e modific-lo. Mas voc deve atribuir o trabalho (mas no de
qualquer forma que sugira que endossa voc ou o seu uso do trabalho). A
forma de atribuio at voc. Voc pode apenas referir-me (Javier Lpez).
Um backlink seria igualmente apreciado.

Passo 1: As peas.
Primeiro, vamos criar uma classe para armazenar todas as peas. H 7
tipos diferentes de peas: quadrado, I, L, L-espelhado, N, N-espelhados e T.
Mas, como podemos definir cada pea? Apenas verifique a figura:

Como voc pode ver, esta pea definida em uma matriz de 5 5


clulas. 0 significa "nenhum bloco", 1 significa "normal bloco" e 2 significa
"centro bloco". O centro bloco o ponto de rotao: sim, o original Tetris
jogo tem um ponto de rotao para cada pea:)
E como podemos armazen-la usando C++? Fcil: utilizando uma matriz
bidimensional de 5 5 Ints (ou bytes, se voc um fantico por otimizao).
A pea armazenado como o anterior, que:
1.
2.
3.
4.
5.

{0, 0, 0, 0, 0},
{0, 0, 0, 1, 0},
{0, 0, 2, 1, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, 0, 0}

Agora que temos a forma de armazenar cadapea vamos pensar sobre


rotaes. Podemos resolver o problema da rotao em um lote de diferentes
maneiras. Em outros tutoriais, vi que eles utilizam uma complexa lgebra de
rotao, a fim de girar a pea... mas ns podemos resolver este problema
facilmente. Se podemos armazenar cada pea...
por que no guardamos cada pea rotacionadatambm? Existem quatro
possveis rotaes para cada pea:

Tetris Tutorial C++ - Peas e suas rotaes


Como voc pode ver, a pea mais longa tem apenas 4 blocos de largura.
Mas estamos usando 5 blocos de matrizes, a fim de ser capaz de armazenar
todas as rotaes respeitando o centro do bloco.Em uma verso anterior
deste tutorial, eu estava usando o 4 blocos de matrizes, mas, depois, foi
necessrio armazenar tradues do centro para a origem. Desta forma,
estamos utilizando alguns bytes a mais, mas o cdigo fonte est limpo. No
total, s utilizaremos 448 bytes para armazenar todas as peas. Isso no
nada. :)
Ento, a fim de armazenar todaesta informao, precisamos de um
array de 4 dimenses (uau!), A fim de conservar as 4 possveis rotaes
(matrizes de 5 5) de cada pea:

1. // Pieces definition
2. char mPieces [7 /*kind */ ][4 /* rotation */ ][5 /* horizontal blocks */ ][5 /* vertical
blocks */ ] =
3. {
4. // Square
5. {
6. {
7.
{0, 0, 0, 0, 0},
8.
{0, 0, 0, 0, 0},
9.
{0, 0, 2, 1, 0},
10.
{0, 0, 1, 1, 0},
11.
{0, 0, 0, 0, 0}
12.
},
13. {
14.
{0, 0, 0, 0, 0},
15.
{0, 0, 0, 0, 0},
16.
{0, 0, 2, 1, 0},
17.
{0, 0, 1, 1, 0},
18.
{0, 0, 0, 0, 0}
19.
},
20. {
21.
{0, 0, 0, 0, 0},
22.
{0, 0, 0, 0, 0},
23.
{0, 0, 2, 1, 0},
24.
{0, 0, 1, 1, 0},
25.
{0, 0, 0, 0, 0}
26.
},
27. {
28.
{0, 0, 0, 0, 0},
29.
{0, 0, 0, 0, 0},
30.
{0, 0, 2, 1, 0},
31.
{0, 0, 1, 1, 0},
32.
{0, 0, 0, 0, 0}
33.
}
34. },
35.
36. // I
37. {
38. {
39.
{0, 0, 0, 0, 0},
40.
{0, 0, 0, 0, 0},
41.
{0, 1, 2, 1, 1},
42.
{0, 0, 0, 0, 0},
43.
{0, 0, 0, 0, 0}
44.
},
45. {
46.
{0, 0, 0, 0, 0},
47.
{0, 0, 1, 0, 0},
48.
{0, 0, 2, 0, 0},
49.
{0, 0, 1, 0, 0},
50.
{0, 0, 1, 0, 0}
51.
},
52. {
53.
{0, 0, 0, 0, 0},
54.
{0, 0, 0, 0, 0},

55.
{1, 1, 2, 1, 0},
56.
{0, 0, 0, 0, 0},
57.
{0, 0, 0, 0, 0}
58.
},
59. {
60.
{0, 0, 1, 0, 0},
61.
{0, 0, 1, 0, 0},
62.
{0, 0, 2, 0, 0},
63.
{0, 0, 1, 0, 0},
64.
{0, 0, 0, 0, 0}
65.
}
66. }
67. ,
68. // L
69. {
70. {
71.
{0, 0, 0, 0, 0},
72.
{0, 0, 1, 0, 0},
73.
{0, 0, 2, 0, 0},
74.
{0, 0, 1, 1, 0},
75.
{0, 0, 0, 0, 0}
76.
},
77. {
78.
{0, 0, 0, 0, 0},
79.
{0, 0, 0, 0, 0},
80.
{0, 1, 2, 1, 0},
81.
{0, 1, 0, 0, 0},
82.
{0, 0, 0, 0, 0}
83.
},
84. {
85.
{0, 0, 0, 0, 0},
86.
{0, 1, 1, 0, 0},
87.
{0, 0, 2, 0, 0},
88.
{0, 0, 1, 0, 0},
89.
{0, 0, 0, 0, 0}
90.
},
91. {
92.
{0, 0, 0, 0, 0},
93.
{0, 0, 0, 1, 0},
94.
{0, 1, 2, 1, 0},
95.
{0, 0, 0, 0, 0},
96.
{0, 0, 0, 0, 0}
97.
}
98. },
99. // L mirrored
100. {
101. {
102.
{0, 0, 0, 0, 0},
103.
{0, 0, 1, 0, 0},
104.
{0, 0, 2, 0, 0},
105.
{0, 1, 1, 0, 0},
106.
{0, 0, 0, 0, 0}
107.
},
108. {
109.
{0, 0, 0, 0, 0},

110.
{0,
111.
{0,
112.
{0,
113.
{0,
114.
},
115. {
116.
{0,
117.
{0,
118.
{0,
119.
{0,
120.
{0,
121.
},
122. {
123.
{0,
124.
{0,
125.
{0,
126.
{0,
127.
{0,
128.
}
129. },
130. // N
131. {
132. {
133.
{0,
134.
{0,
135.
{0,
136.
{0,
137.
{0,
138.
},
139. {
140.
{0,
141.
{0,
142.
{0,
143.
{0,
144.
{0,
145.
},
146. {
147.
{0,
148.
{0,
149.
{0,
150.
{0,
151.
{0,
152.
},
153. {
154.
{0,
155.
{0,
156.
{0,
157.
{0,
158.
{0,
159.
}
160. },

1, 0, 0,
1, 2, 1,
0, 0, 0,
0, 0, 0,

0},
0},
0},
0}

0, 0, 0,
0, 1, 1,
0, 2, 0,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 0, 0,
1, 2, 1,
0, 0, 1,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 0, 1,
0, 2, 1,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 0, 0,
1, 2, 0,
0, 1, 1,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 1, 0,
1, 2, 0,
1, 0, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
1, 1, 0,
0, 2, 1,
0, 0, 0,
0, 0, 0,

0},
0},
0},
0},
0}

161. // N mirrored
162. {

163. {
164.
{0,
165.
{0,
166.
{0,
167.
{0,
168.
{0,
169.
},
170. {
171.
{0,
172.
{0,
173.
{0,
174.
{0,
175.
{0,
176.
},
177. {
178.
{0,
179.
{0,
180.
{0,
181.
{0,
182.
{0,
183.
},
184. {
185.
{0,
186.
{0,
187.
{0,
188.
{0,
189.
{0,
190.
}
191. },
192. // T
193. {
194. {
195.
{0,
196.
{0,
197.
{0,
198.
{0,
199.
{0,
200.
},
201. {
202.
{0,
203.
{0,
204.
{0,
205.
{0,
206.
{0,
207.
},
208. {
209.
{0,
210.
{0,
211.
{0,
212.
{0,
213.
{0,
214.
},

0, 0, 0,
0, 1, 0,
0, 2, 1,
0, 0, 1,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 0, 0,
0, 2, 1,
1, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
1, 0, 0,
1, 2, 0,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 1, 1,
1, 2, 0,
0, 0, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 1, 0,
0, 2, 1,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 0, 0,
1, 2, 1,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

0, 0, 0,
0, 1, 0,
1, 2, 0,
0, 1, 0,
0, 0, 0,

0},
0},
0},
0},
0}

215. {
216.
{0,
217.
{0,
218.
{0,
219.
{0,
220.
{0,
221.
}
222. }
223. };

0, 0, 0,
0, 1, 0,
1, 2, 1,
0, 0, 0,
0, 0, 0,

0},
0},
0},
0},
0}

timo! Agora, para rodar uma pea, s temos de escolher as seguintes


peas rotacionadas armazenadas.
H algo importante que temos de ter em conta. Cada pea diferente
deve ser corretamente posicionada cada vez que criada no topo do
ecr(tela do seu jogo). Em outras palavras, ele precisa ser traduzido para a
posio correta (a fim de mostrar apenas uma linha de blocos no tabuleiro e
ser centrado, blocos superior devem estar fora do tabuleiro).
Como cada pea diferente (algumas so mais baixas ou menor do que
outras na matriz), cada uma tem uma traduo diferente cada vez que ela
criada. Vamos guardar essas tradues em outra matriz, uma traduo por
pea rotacionada. Tome o seu tempo para entender isso.

Tetris Tutorial C++ - Tetris peas em boas posies e errado

As tradues so dois nmeros (tradues horizontais, tradues


verticais) que temos de armazenar, para cada pea. Ns iremos usar esses
nmeros mais tarde, na classe Jogo ao criar as peas de cada vez que um
elemento novo aparece, ento ele ser inicializado na posio correta. Esta
a matriz que armazena estes deslocamentos:
1. // Displacement of the piece to the position where it is first drawn in the board
when it is created
2. int mPiecesInitialPosition [7 /*kind */ ][4 /* r2otation */ ][2 /* position */] =
3. {
4. /* Square */
5. {
6.
{-2, -3},
7.
{-2, -3},
8.
{-2, -3},
9.
{-2, -3}
10. },
11. /* I */
12. {
13.
{-2, -2},
14.
{-2, -3},
15.
{-2, -2},
16.
{-2, -3}
17. },
18. /* L */
19. {
20.
{-2, -3},
21.
{-2, -3},
22.
{-2, -3},
23.
{-2, -2}
24. },
25. /* L mirrored */
26. {
27.
{-2, -3},
28.
{-2, -2},
29.
{-2, -3},
30.
{-2, -3}
31. },
32. /* N */
33. {
34.
{-2, -3},
35.
{-2, -3},
36.
{-2, -3},
37.
{-2, -2}
38. },
39. /* N mirrored */
40. {
41.
{-2, -3},
42.
{-2, -3},
43.
{-2, -3},
44.
{-2, -2}
45. },

46. /* T */
47. {
48.
{-2,
49.
{-2,
50.
{-2,
51.
{-2,
52. },
53. };

-3},
-3},
-3},
-2}

E temos como resolvido uma das mais complicadas partes deste


tutorial.
Agora podemos criar a nossa classe Peas, esse arquivo chamado
"Pieces.h"://Pecas.h
1. #ifndef _PIECES_
2. #define _PIECES_
3.
4. // -------------------------------------------------------------------------------5. //
Pieces
6. // -------------------------------------------------------------------------------7.
8. class Pieces
9. {
10. public:
11.
12.
int GetBlockType
(int pPiece, int pRotation, int pX, int pY);
13.
int GetXInitialPosition (int pPiece, int pRotation);
14.
int GetYInitialPosition (int pPiece, int pRotation);
15. };
16.
17. #endif // _PIECES_
Os 3 mtodos que voc pode ver no cabealho retorna algumas
informaes que precisaremos mais tarde. A aplicao deles trivial:
1. /*
2. ======================================
3. Return the type of a block (0 = no-block, 1 = normal block, 2 = pivot block)
4.
5. Parameters:
6.
7. >> pPiece:
Piece to draw
8. >> pRotation: 1 of the 4 possible rotations
9. >> pX:
Horizontal position in blocks
10. >> pY:
Vertical position in blocks
11. ======================================
12. */
13. int Pieces::GetBlockType (int pPiece, int pRotation, int pX, int pY)
14. {
15.
return mPieces [pPiece][pRotation][pX][pY];
16. }

17.
18. /*
19. ======================================
20. Returns the horizontal displacement of the piece that has to be applied in order
to create it in the
21. correct position.
22.
23. Parameters:
24.
25. >> pPiece: Piece to draw
26. >> pRotation: 1 of the 4 possible rotations
27. ======================================
28. */
29. int Pieces::GetXInitialPosition (int pPiece, int pRotation)
30. {
31.
return mPiecesInitialPosition [pPiece][pRotation][0];
32. }
33.
34. /*
35. ======================================
36. Returns the vertical displacement of the piece that has to be applied in order to
create it in the
37. correct position.
38.
39. Parameters:
40.
41. >> pPiece: Piece to draw
42. >> pRotation: 1 of the 4 possible rotations
43. ======================================
44. */
45. int Pieces::GetYInitialPosition (int pPiece, int pRotation)
46. {
47.
return mPiecesInitialPosition [pPiece][pRotation][1];
48. }

Passo 2: O tabuleiro.
Agora vamos aprender a guardar as peas no tabuleiro e verificar
colises. Esta classe armazena um array bidimensional de N x N blocos que
so inicializados para POS_FREE. As peas sero armazenadaspor preencher
estes blocos quando cair atualizando o bloco para POS_FILLED.
Este o cabealho da classe (Board.h):
1. #ifndef _BOARD_
2. #define _BOARD_
3.
4. // ------ Includes ----5.
6. #include "Pieces.h"
7.
8. // ------ Defines ----9.
10. #define BOARD_LINE_WIDTH 6
// Width of each of the two lines that delimit
the board
11. #define BLOCK_SIZE 16
// Width and Height of each block of a piece
12. #define BOARD_POSITION 320
// Center position of the board from the left
of the screen
13. #define BOARD_WIDTH 10
// Board width in blocks
14. #define BOARD_HEIGHT 20
// Board height in blocks
15. #define MIN_VERTICAL_MARGIN 20
// Minimum vertical margin for the board
limit
16. #define MIN_HORIZONTAL_MARGIN 20 // Minimum horizontal margin for the
board limit
17. #define PIECE_BLOCKS 5
// Number of horizontal and vertical blocks of a
matrix piece
18.
19. // -------------------------------------------------------------------------------20. //
Board
21. // -------------------------------------------------------------------------------22.
23. class Board
24. {
25. public:
26.
27.
Board
(Pieces *pPieces, int pScreenHeight);
28.
29.
int GetXPosInPixels
(int pPos);
30.
int GetYPosInPixels
(int pPos);
31.
bool IsFreeBlock
(int pX, int pY);
32.
bool IsPossibleMovement (int pX, int pY, int pPiece, int pRotation);
33.
void StorePiece
(int pX, int pY, int pPiece, int pRotation);
34.
void DeletePossibleLines ();
35.
bool IsGameOver
();
36.
37. private:
38.
enum { POS_FREE, POS_FILLED };
// POS_FREE = free position of the
board; POS_FILLED = filled position of the board

39.
int mBoard [BOARD_WIDTH][BOARD_HEIGHT]; // Board that contains the pieces
40.
Pieces *mPieces;
41.
int mScreenHeight;
42.
43.
void InitBoard();
44.
void DeleteLine (int pY);
45. };
46.
47. #endif // _BOARD_
Agora, vamos ver cada mtodo diferente.
Mtodo InitBoard apenas um loop aninhado que inicializa todos os blocos
do tabuleiro POS_FREE.
1. /*
2. ======================================
3. Init the board blocks with free positions
4. ======================================
5. */
6. void Board::InitBoard()
7. {
8.
for (int i = 0; i < BOARD_WIDTH; i++)
9.
for (int j = 0; j < BOARD_HEIGHT; j++)
10.
mBoard[i][j] = POS_FREE;
11. }
1. /*
2. ======================================
3. Init the board blocks with free positions
4. ======================================
5. */
6. void Board::InitBoard()
7. {
8.
for (int i = 0; i < BOARD_WIDTH; i++)
9.
for (int j = 0; j < BOARD_HEIGHT; j++)
10.
mBoard[i][j] = POS_FREE;
11. }
/*
======================================
Init the board blocks with free positions
======================================
*/
void Board::InitBoard()
{
for (int i = 0; i < BOARD_WIDTH; i++)
for (int j = 0; j < BOARD_HEIGHT; j++)
mBoard[i][j] = POS_FREE;
}

Mtodo StorePiece, armazena apenas uma pea no tabuleiro, enchendo os


blocos como POS_FILLED. Existe um loop aninhado que itera atravs da pea
matriz e armazenar os blocos do tabuleiro.
1. /*
2.
3. ======================================
4. Store a piece in the board by filling the blocks
5.
6. Parameters:
7.
8. >> pX:
Horizontal position in blocks
9. >> pY:
Vertical position in blocks
10. >> pPiece: Piece to draw
11. >> pRotation: 1 of the 4 possible rotations
12. ======================================
13. */
14. void Board::StorePiece (int pX, int pY, int pPiece, int pRotation)
15. {
16.
// Store each block of the piece into the board
17.
for (int i1 = pX, i2 = 0; i1 < pX + PIECE_BLOCKS; i1++, i2++)
18.
{
19.
for (int j1 = pY, j2 = 0; j1 < pY + PIECE_BLOCKS; j1++, j2++)
20.
{
21.
// Store only the blocks of the piece that are not holes
22.
if (mPieces->GetBlockType (pPiece, pRotation, j2, i2) != 0)
23.
mBoard[i1][j1] = POS_FILLED;
24.
}
25.
}
26. }
Mtodo IsGameOver verifica se existem blocos na primeira fila. Isso significa
que o jogo acaba.
1. /*
2. ======================================
3. Check if the game is over becase a piece have achived the upper position
4.
5. Returns true or false
6. ======================================
7. */
8. bool Board::IsGameOver()
9. {
10.
//If the first line has blocks, then, game over
11.
for (int i = 0; i < BOARD_WIDTH; i++)
12.
{
13.
if (mBoard[i][0] == POS_FILLED) return true;
14.
}
15.
16.
return false;
17. }

Mtodo DeleteLine o mtodo que apaga uma linha e move todos os blocos
de posies superiores uma linha para baixo. Ela s comea a partir da linha
que tem de ser removida e, em seguida, iterao atravs da placa em um
loop aninhado, move todos os blocos das linhas superiores de uma linha
feita.
1. /*
2. ======================================
3. Delete a line of the board by moving all above lines down
4.
5. Parameters:
6.
7. >> pY:
Vertical position in blocks of the line to delete
8. ======================================
9. */
10. void Board::DeleteLine (int pY)
11. {
12.
// Moves all the upper lines one row down
13.
for (int j = pY; j > 0; j--)
14.
{
15.
for (int i = 0; i < BOARD_WIDTH; i++)
16.
{
17.
mBoard[i][j] = mBoard[i][j-1];
18.
}
19.
}
20. }
DeletePossibleLines um mtodo que remove todas as linhas que devem ser
apagadas do tabuleiro. Ele funciona em primeiro lugar, verificando quais
linhas devem ser removidas (os que tm todos os seus blocos horizontais
cheia). Ento, ele usa o mtodo DeleteLine a fim de apagar essa linha e
mover todas as linhas de uma linha superior para baixo.
1. /*
2. ======================================
3. Delete all the lines that should be removed
4. ======================================
5. */
6. void Board::DeletePossibleLines ()
7. {
8.
for (int j = 0; j < BOARD_HEIGHT; j++)
9.
{
10.
int i = 0;
11.
while (i < BOARD_WIDTH)
12.
{
13.
if (mBoard[i][j] != POS_FILLED) break;
14.
i++;
15.
}
16.
17.
if (i == BOARD_WIDTH) DeleteLine (j);
18.
}
19. }

IsFreeBlock um mtodo trivial que verifica se um bloco do tabuleiro


preenchido ou no.
1. /*
2. ======================================
3. Returns 1 (true) if the this block of the board is empty, 0 if it is filled
4.
5. Parameters:
6.
7. >> pX:
Horizontal position in blocks
8. >> pY:
Vertical position in blocks
9. ======================================
10. */
11. bool Board::IsFreeBlock (int pX, int pY)
12. {
13.
if (mBoard [pX][pY] == POS_FREE) return true; else return false;
14. }
At agora, temos sempre a falado de "blocos". Mas, a fim de chamarlhes a tela, precisamos determinar a posio empixels. Ento, temos dois
mtodos (GetXPosInPixels e GetYPosInPixels), a fim de obter a posio
horizontal e vertical em pixels de um determinado bloco.
1. /*
2. ======================================
3. Returns the horizontal position (in pixels) of the block given like parameter
4.
5. Parameters:
6.
7. >> pPos: Horizontal position of the block in the board
8. ======================================
9. */
10. int Board::GetXPosInPixels (int pPos)
11. {
12.
return ( ( BOARD_POSITION - (BLOCK_SIZE * (BOARD_WIDTH / 2)) ) + (pPos *
BLOCK_SIZE) );
13. }
14.
15. /*
16. ======================================
17. Returns the vertical position (in pixels) of the block given like parameter
18.
19. Parameters:
20.
21. >> pPos: Horizontal position of the block in the board
22. ======================================
23. */
24. int Board::GetYPosInPixels (int pPos)
25. {
26.
return ( (mScreenHeight - (BLOCK_SIZE * BOARD_HEIGHT)) + (pPos *
BLOCK_SIZE) );
27. }

IsPossibleMovement o ltimo e mais complexo mtodo da classe tabuleiro.


Este mtodo ser utilizado mais tarde no loop principal para verificar se o
movimento de uma pea possvel ou no. O mtodo compara todos os
blocos de uma pea com os blocos j armazenados no tabuleiro e com o
limite do tabuleiro. Esta comparao feita por meio de iterao da pea
matriz e comparando com os 5 5 espao no tabuleiro. Se houver uma
coliso que significa que o movimento no possvel, ento ele retorna
false. Se no houver coliso, o movimento possvel, e ela retorna true.

Tetris Tutorial C++ - Colises com os blocos armazenados e os limites do tabuleiro

1. /*
2. ======================================
3. Check if the piece can be stored at this position without any collision
4. Returns true if the movement is possible, false if it not possible
5.
6. Parameters:
7.
8. >> pX:
Horizontal position in blocks
9. >> pY:
Vertical position in blocks
10. >> pPiece: Piece to draw
11. >> pRotation: 1 of the 4 possible rotations
12. ======================================
13. */
14. bool Board::IsPossibleMovement (int pX, int pY, int pPiece, int pRotation)
15. {
16.
// Checks collision with pieces already stored in the board or the board limits
17.
// This is just to check the 5x5 blocks of a piece with the appropriate area in
the board
18.
for (int i1 = pX, i2 = 0; i1 < pX + PIECE_BLOCKS; i1++, i2++)
19.
{
20.
for (int j1 = pY, j2 = 0; j1 < pY + PIECE_BLOCKS; j1++, j2++)
21.
{
22.
// Check if the piece is outside the limits of the board
23.
if ( i1 < 0
||
24.
i1 > BOARD_WIDTH - 1 ||
25.
j1 > BOARD_HEIGHT - 1)
26.
{
27.
if (mPieces->GetBlockType (pPiece, pRotation, j2, i2) != 0)
28.
return 0;
29.
}
30.
31.
// Check if the piece have collisioned with a block already stored in the
map
32.
if (j1 >= 0)
33.
{
34.
if ((mPieces->GetBlockType (pPiece, pRotation, j2, i2) != 0) &&
35.
(!IsFreeBlock (i1, j1)) )
36.
return false;
37.
}
38.
}
39.
}
40.
41.
// No collision
42.
return true;
43.}

Passo 3: O jogo.
Agora vamos implementar uma classe geral, chamada "Game", que
inicialize o jogo, desenha o tabuleiro e as peas, aproveitando cada bloco
como um retngulo (usando uma outra classe que veremos mais tarde
chamada "IO", que usa SDL) e cria novas peas aleatrias.
Este o cabealho, Game.h:
1. #ifndef _GAME_
2. #define _GAME_
3.
4. // ------ Includes ----5.
6. #include "Board.h"
7. #include "Pieces.h"
8. #include "IO.h"
9. #include <time.h>
10.
11. // ------ Defines ----12.
13. #define WAIT_TIME 700
// Number of milliseconds that the piece remains
before going 1 block down */
14.
15. // -------------------------------------------------------------------------------16. //
Game
17. // -------------------------------------------------------------------------------18.
19. class Game
20. {
21. public:
22.
23.
Game
(Board *pBoard, Pieces *pPieces, IO *pIO, int pScreenHeight);
24.
25.
void DrawScene ();
26.
void CreateNewPiece ();
27.
28.
int mPosX, mPosY;
// Position of the piece that is falling down
29.
int mPiece, mRotation;
// Kind and rotation the piece that is falling down
30.
31. private:
32.
33.
int mScreenHeight;
// Screen height in pixels
34.
int mNextPosX, mNextPosY;
// Position of the next piece
35.
int mNextPiece, mNextRotation; // Kind and rotation of the next piece
36.
37.
Board *mBoard;
38.
Pieces *mPieces;
39.
IO *mIO;
40.
41.
int GetRand (int pA, int pB);

42.
void InitGame();
43.
void DrawPiece (int pX, int pY, int pPiece, int pRotation);
44.
void DrawBoard ();
45. };
46. #endif // _GAME_
Como voc pode ver, a pea atual definida usando 4 variaveis:
mPosX, mPosY (a posio da pea no bloco), mPiece (o tipo de pea),
mRotation (a matriz atual que define a pea, como vimos, cada pea tem
quatro matrizes, uma para cada rotao).
Vamos ver a implementao dos mtodos.
GetRand trivial um mtodo que retorna um nmero aleatrio entre dois
limites.
1. /*
2. ======================================
3. Get a random int between to integers
4.
5. Parameters:
6. >> pA: First number
7. >> pB: Second number
8. ======================================
9. */
10. int Game::GetRand (int pA, int pB)
11. {
12.
return rand () % (pB - pA + 1) + pA;
13. }
InitGame, cuidada da inicializao do jogo, selecionando a primeira e a
prxima pea aleatoriamente.A prxima pea apresentada por isso o
jogador pode ver o que vai aparecer prxima pea. Este mtodo tambm
apresenta a posio em blocos de peas.
Usamos dois mtodos que vimos antes na classe "Pieces":
GetXInitialPosition e GetYInitialPosition em ordem para inicializar a pea na
posio correta.
1. /*
2. ======================================
3. Initial parameters of the game
4. ======================================
5. */
6. void Game::InitGame()
7. {
8.
// Init random numbers
9.
srand ((unsigned int) time(NULL));
10.
11.
// First piece
12.
mPiece
= GetRand (0, 6);
13.
mRotation
= GetRand (0, 3);
14.
mPosX
= (BOARD_WIDTH / 2) + mPieces->GetXInitialPosition (mPiece,
mRotation);
15.
mPosY
= mPieces->GetYInitialPosition (mPiece, mRotation);
16.

17.
18.
19.
20.
21.
22. }

// Next piece
mNextPiece
mNextRotation
mNextPosX
mNextPosY

= GetRand (0, 6);


= GetRand (0, 3);
= BOARD_WIDTH + 5;
= 5;

CreateNewPiece mtodo que define a "prxima pea" como a atual e um


redefine a sua posio, em seguida, escolhe uma nova "prxima pea".
1. /*
2. ======================================
3. Create a random piece
4. ======================================
5. */
6. void Game::CreateNewPiece()
7. {
8.
// The new piece
9.
mPiece
= mNextPiece;
10.
mRotation
= mNextRotation;
11.
mPosX
= (BOARD_WIDTH / 2) + mPieces->GetXInitialPosition (mPiece,
mRotation);
12.
mPosY
= mPieces->GetYInitialPosition (mPiece, mRotation);
13.
14.
// Random next piece
15.
mNextPiece
= GetRand (0, 6);
16.
mNextRotation = GetRand (0, 3);
17. }
DrawPiece realmente um mtodo fcil que itera por meio da pea matriz e
chama cada bloco da pea. Ele usa a cor verde para os blocos normais e azul
para o centro bloco. Para desenhar rectngulos chamada DrawRectangle,
mtodo da classe "IO", que veremos mais tarde.
1. /*
2. ======================================
3. Draw piece
4.
5. Parameters:
6.
7. >> pX:
Horizontal position in blocks
8. >> pY:
Vertical position in blocks
9. >> pPiece: Piece to draw
10. >> pRotation: 1 of the 4 possible rotations
11. ======================================
12. */
13. void Game::DrawPiece (int pX, int pY, int pPiece, int pRotation)
14. {
15.
color mColor;
// Color of the block
16.
17.
// Obtain the position in pixel in the screen of the block we want to draw
18.
int mPixelsX = mBoard->GetXPosInPixels (pX);

19.
int mPixelsY = mBoard->GetYPosInPixels (pY);
20.
21.
// Travel the matrix of blocks of the piece and draw the blocks that are filled
22.
for (int i = 0; i < PIECE_BLOCKS; i++)
23.
{
24.
for (int j = 0; j < PIECE_BLOCKS; j++)
25.
{
26.
// Get the type of the block and draw it with the correct color
27.
switch (mPieces->GetBlockType (pPiece, pRotation, j, i))
28.
{
29.
case 1: mColor = GREEN; break; // For each block of the piece except
the pivot
30.
case 2: mColor = BLUE; break; // For the pivot
31.
}
32.
33.
if (mPieces->GetBlockType (pPiece, pRotation, j, i) != 0)
34.
mIO->DrawRectangle (mPixelsX + i * BLOCK_SIZE,
35.
mPixelsY + j * BLOCK_SIZE,
36.
(mPixelsX + i * BLOCK_SIZE) + BLOCK_SIZE - 1,
37.
(mPixelsY + j * BLOCK_SIZE) + BLOCK_SIZE - 1,
38.
mColor);
39.
}
40.
}
41. }
DrawBoard semelhante ao mtodo anterior. Ela chama duas colunas azuis,
que so utilizadas como os limites do tabuleiro. Em seguida, desenham-se os
blocos do tabuleiro que so sinalizados como POS_FILLED em um loop
aninhado.
1. /*
2. ======================================
3. Draw board
4.
5. Draw the two lines that delimit the board
6. ======================================
7. */
8. void Game::DrawBoard ()
9. {
10.
11.
// Calculate the limits of the board in pixels
12.
int mX1 = BOARD_POSITION - (BLOCK_SIZE * (BOARD_WIDTH / 2)) - 1;
13.
int mX2 = BOARD_POSITION + (BLOCK_SIZE * (BOARD_WIDTH / 2));
14.
int mY = mScreenHeight - (BLOCK_SIZE * BOARD_HEIGHT);
15.
16.
// Check that the vertical margin is not to small
17.
//assert (mY > MIN_VERTICAL_MARGIN);
18.
19.
// Rectangles that delimits the board
20.
mIO->DrawRectangle (mX1 - BOARD_LINE_WIDTH, mY, mX1, mScreenHeight 1, BLUE);
21.
22.
mIO->DrawRectangle (mX2, mY, mX2 + BOARD_LINE_WIDTH, mScreenHeight 1, BLUE);

23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42. }

// Check that the horizontal margin is not to small


//assert (mX1 > MIN_HORIZONTAL_MARGIN);
// Drawing the blocks that are already stored in the board
mX1 += 1;
for (int i = 0; i < BOARD_WIDTH; i++)
{
for (int j = 0; j < BOARD_HEIGHT; j++)
{
// Check if the block is filled, if so, draw it
if (!mBoard->IsFreeBlock(i, j))
mIO->DrawRectangle ( mX1 + i * BLOCK_SIZE,
mY + j * BLOCK_SIZE,
(mX1 + i * BLOCK_SIZE) + BLOCK_SIZE - 1,
(mY + j * BLOCK_SIZE) + BLOCK_SIZE - 1,
RED);
}
}

DrawScene, apenas solicita os mtodos anteriores, em ordem para desenhar


tudo.
1. /*
2. ======================================
3. Draw scene
4.
5. Draw all the objects of the scene
6. ======================================
7. */
8. void Game::DrawScene ()
9. {
10.
DrawBoard ();
// Draw the delimitation lines and
blocks stored in the board
11.
DrawPiece (mPosX, mPosY, mPiece, mRotation);
// Draw the playing
piece
12.
DrawPiece (mNextPosX, mNextPosY, mNextPiece, mNextRotation); // Draw
the next piece
13. }

Passo 4: desenhando fcil, gerenciamento de


janela e entradas do teclado usando SDL, isolado
da lgica do jogo.
"IO.cpp" e "IO.h" so os arquivos que implementam a classe "IO". Ele
usa SDL, a fim de criar a janela, claro, atualizar a tela e ter cuidado com as
entradas do teclado.
Voc pode verificar que "IO.cpp" e "IO.h" so arquivos a fim de ver a
sua execuo. Eu no vou para explicar os mtodos que esto relacionados
com SDL. Voc pode alterar esta classe, a fim de usar um renderizador
diferentes (como IndieLib, Allegro, OpenGL, Direct3D, etc.)
Este o cabealho IO.h:
1. #ifndef _IO_
2. #define _IO_
4. // ------ Includes ----5.
6. #ifndef LINUX
7. #include "SDL/include/SDL.h"
8. #include "SDL/SDL_GfxPrimitives/SDL_gfxPrimitives.h"
9. #else
10. #include <SDL/SDL.h>
11. #include "SDL/SDL_GfxPrimitives/sdl_gfxprimitives.h"
12. #endif
13. #pragma comment (lib, "SDL/lib/SDL.lib")
14. #pragma comment (lib, "SDL/SDL_GfxPrimitives/SDL_GfxPrimitives_Static.lib")
15.
16. // ------ Enums ----17.
18. enum color {BLACK, RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW, WHITE,
COLOR_MAX}; // Colors
19.
20. // -------------------------------------------------------------------------------21. //
IO
22. // -------------------------------------------------------------------------------23.
24. class IO
25. {
26. public:
27.
28.
IO
();
29.
30.
void DrawRectangle
(int pX1, int pY1, int pX2, int pY2, enum color pC);
31.
void ClearScreen
();
32.
int GetScreenHeight
();
33.
int InitGraph
();
34.
int Pollkey
();
35.
int Getkey
();
36.
int IsKeyDown
(int pKey);

37.
void UpdateScreen
38.
39. };
40. #endif // _IO_

();

Passo 5: O loop Principal


O loop principal bastante simples. Em cada frame ns desenhamos
tudo. Depois, ns usamos as entradas do teclado a fim de mover a pea.
Antes de cada movimento, primeiro verifique se o movimento possvel. Ns
tambm medimos o tempo, a fim de mover a pea para baixo a cada n
milissegundos. Quando a pea descer um bloco, verificamos se esse
movimento possvel, se no, guarde a pea no tabuleiro. Tambm verifique
se h blocos na linha superior, em caso afirmativo, o jogo acaba.
Vamos ver o Main.cpp passo-a-passo:
Primeiro ns inicializamos todas as classes. Ento, ns obtemos o atual
milissegundos, que sero utilizados para determinar quando a pea deve
mover para baixo.
1. #include "Game.h"
2. #ifndef LINUX
3. #include <windows.h>
4. #endif
5.
6. /*
7. ==================
8. Main
9. ==================
10. */
11. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
12. {
13.
// ----- Vars ----14.
15.
// Class for drawing staff, it uses SDL for the rendering. Change the methods of
this class
16.
// in order to use a different renderer
17.
IO mIO;
18.
int mScreenHeight = mIO.GetScreenHeight();
19.
20.
// Pieces
21.
Pieces mPieces;
22.
23.
// Board
24.
Board mBoard (&mPieces, mScreenHeight);
25.
26.
// Game
27.
Game mGame (&mBoard, &mPieces, &mIO, mScreenHeight);
28.
29.
// Get the actual clock milliseconds (SDL)
30.
unsigned long mTime1 = SDL_GetTicks();

Este o loop principal. Ns podemos sair apertando ESC. Em cada


frame (loop) ns limpamos e atualizamos a tela e desenhamos tudo de novo.
1. // ----- Main Loop ----2.
3. while (!mIO.IsKeyDown (SDLK_ESCAPE))
4. {
5.
// ----- Draw ----6.
7.
mIO.ClearScreen ();
// Clear screen
8.
mGame.DrawScene ();
// Draw staff
9.
mIO.UpdateScreen ();
// Put the graphic context in the screen
Ns comeamos com a entrada do teclado. Se ns pressionarmos seta
para esquerda, seta para baixo ou seta para direita, ns movemos a pea
naquela direo. Ns s moveremos a pea se o movimento for possivel.
1. // ----- Input ----2.
3. int mKey = mIO.Pollkey();
4.
5. switch (mKey)
6. {
7.
case (SDLK_RIGHT):
8.
{
9.
if (mBoard.IsPossibleMovement (mGame.mPosX + 1, mGame.mPosY,
mGame.mPiece, mGame.mRotation))
10.
mGame.mPosX++;
11.
break;
12.
}
13.
14.
case (SDLK_LEFT):
15.
{
16.
if (mBoard.IsPossibleMovement (mGame.mPosX - 1, mGame.mPosY,
mGame.mPiece, mGame.mRotation))
17.
mGame.mPosX--;
18.
break;
19.
}
20.
21.
case (SDLK_DOWN):
22.
{
23.
if (mBoard.IsPossibleMovement (mGame.mPosX, mGame.mPosY + 1,
mGame.mPiece, mGame.mRotation))
24.
mGame.mPosY++;
25.
break;
26.
}

Pressionando "x", a pea vai cair diretamente no cho. Isto realmente


fcil de implementar, tentar mover a pea para baixo at que o movimento
no seja possvel. Ento armazene a pea, delete possveis linhas e verifique
se o jogo acabou, se no, ns criamos uma nova pea.
1. case (SDLK_x):
2. {
3.
// Check collision from up to down
4.
while (mBoard.IsPossibleMovement(mGame.mPosX, mGame.mPosY,
mGame.mPiece, mGame.mRotation)) { mGame.mPosY++; }
5.
6.
mBoard.StorePiece (mGame.mPosX, mGame.mPosY - 1, mGame.mPiece,
mGame.mRotation);
7.
8.
mBoard.DeletePossibleLines ();
9.
10.
if (mBoard.IsGameOver())
11.
{
12.
mIO.Getkey();
13.
exit(0);
14.
}
15.
16.
mGame.CreateNewPiece();
17.
18.
break;
19. }
Pressionando "z" rotacionamos a pea. Com os mtodos que j
implementamos, essa uma tarefa fcil. A rotao de fato uma mudana
para a prxima pea armazenada. Ns primeiro, devemos verificar se a pea
rodada ser desenhada sem colidir, em caso afirmativo, definimos esta
rotao como a atual.
1.
case (SDLK_z):
2.
{
3.
if (mBoard.IsPossibleMovement (mGame.mPosX, mGame.mPosY,
mGame.mPiece, (mGame.mRotation + 1) % 4))
4.
mGame.mRotation = (mGame.mRotation + 1) % 4;
5.
6.
break;
7.
}
8. }

Se WAIT_TIME for passado, a pea deve cair um bloco. Temos de


verificar se o movimento possvel, se no, a pea deve ser armazenada e
temos de verificar se podemos excluir linhas. Ns tambm devemos ver se o
jogo acabou, se no, ns criamos uma nova pea.
1.
// ----- Vertical movement ----2.
3.
unsigned long mTime2 = SDL_GetTicks();
4.
5.
if ((mTime2 - mTime1) > WAIT_TIME)
6.
{
7.
if (mBoard.IsPossibleMovement (mGame.mPosX, mGame.mPosY + 1,
mGame.mPiece, mGame.mRotation))
8.
{
9.
mGame.mPosY++;
10.
}
11.
else
12.
{
13.
mBoard.StorePiece (mGame.mPosX, mGame.mPosY, mGame.mPiece,
mGame.mRotation);
14.
15.
mBoard.DeletePossibleLines ();
16.
17.
if (mBoard.IsGameOver())
18.
{
19.
mIO.Getkey();
20.
exit(0);
21.
}
22.
23.
mGame.CreateNewPiece();
24.
}
25.
26.
mTime1 = SDL_GetTicks();
27.
}
28. }
29.
30. return 0;
E isso tudo! Por favor, deixe um comentrio, aqui:
http://gametuto.com/tetris-tutorial-in-c-render-independent/
se voc observar alguns erros, erros lingusticos ou se voc tiver quaisquer
dvidas ... ou simplesmente para dizer muito obrigado! :)
Crditos:

Javier Lpez Lpez (criador do tutorial)


* Um agradecimento especial: Imelior, quem corrigu erros de ingls e
compilou o tutorial em Linux.
* Um agradecimento especial: Javier Santana, que acrescentou
setenas #ifndef ponteiros que era necessrio utilizar libsdlgfx1.2-dev e

libsdl1.2-dev em Linux.
Se voc gostou deste tutorial, por favor, ponha um backlink de seu blog e v
ao ttulo e clique no Digg, Reddit e Dzone. Isso me d energia, a fim de criar
mais tutoriais.
Bnus
No se esquea de brincar com o "define".Exemplo maluco:
1. #define BLOCK_SIZE 5
2. #define BOARD_WIDTH 90
3. #define BOARD_HEIGHT 90

// Width and Height of each block of a


// Board width in blocks
// Board height in blocks

Esta postagem foi escrita por Javier Lpez, publicada em 14 de dezembro de


2008 s 5:33, arquivada sob Uncategorized. Bookmark o permalink. Siga
quaisquer comentrios aqui com o feed RSS para este post. Postar um
comentrio ou deixar um trackback: Trackback URL.

Você também pode gostar