The wxWidgets programming tutorial (12. The tetris game in wxWidgets)
Translations of this material:
- into Russian: Руководство по программированию с wxWidgets (12. Тетрис в wxWidgets). Translation complete.
-
Submitted for translation by ber113 23.03.2010
Published 2 years, 2 months ago.
Text
The tetris game in wxWidgets
Tetris
The tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a russian programmer Alexey Pajitnov in 1985. Since then, tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the tetris game.
Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the tetris game is to move and rotate the shapes, so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the tetris game until we top out.
wxWidgets is a toolkit designed to create applications. There are other libraries which are targeted at creating computer games. Nevertheless, wxWidgets and other application toolkits can be used to create games.
The development
We do not have images for our tetris game, we draw the tetrominoes using the drawing API available in the wxWidgets programming toolkit. Behind every computer game, there is a mathematical model. So it is in tetris.
Some ideas behind the game.
* We use wxTimer to create a game cycle
* The tetrominoes are drawn
* The shapes move on a square by square basis (not pixel by pixel)
* Mathematically a board is a simple list of numbers
I have simplified the game a bit, so that it is easier to understand. The game starts immediately, after it is launched. We can pause the game by pressing the p key. The space key will drop the tetris piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The score is the number of lines, that we have removed.
...
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
Before we start the game, we initialize some important variables. The isFallingFinished variable determines, it the tetris shape has finished falling and we then need to create a new shape. The numLinesRemoved counts the number of lines, we have removed so far. The curX and curY variables determine the actual position of the falling tetris shape.
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = ShapeAt(j, BoardHeight - i - 1);
if (shape != NoShape)
DrawSquare(dc, 0 + j * SquareWidth(),
boardTop + i * SquareHeight(), shape);
}
}
The painting of the game is divided into two steps. In the first step, we draw all the shapes, or remains of the shapes, that have been dropped to the bottom of the board. All the squares are rememberd in the board array. We access it using the ShapeAt() method.
if (curPiece.GetShape() != NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
DrawSquare(dc, 0 + x * SquareWidth(),
boardTop + (BoardHeight - y - 1) * SquareHeight(),
curPiece.GetShape());
}
}
The next step is drawing of the actual piece, that is falling down.
...
switch (keycode) {
case WXK_LEFT:
TryMove(curPiece, curX - 1, curY);
break;
...
In the Board::OnKeyDown() method we check for pressed keys. If we press the left arrow key, we try to move the piece to the left. We say try, because the piece might not be able to move.
void Board::OnTimer(wxCommandEvent& event)
{
if (isFallingFinished) {
isFallingFinished = false;
NewPiece();
} else {
OneLineDown();
}
}
In the Board::OnTimer() method we either create a new piece, after the previous one was dropped to the bottom, or we move a falling piece one line down.
void Board::DropDown()
{
int newY = curY;
while (newY > 0) {
if (!TryMove(curPiece, curX, newY - 1))
break;
--newY;
}
PieceDropped();
}
The Board::DropDown() method drops the falling shape immediately to the bottom of the board. It happens, when we press the space key.
void Board::PieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
ShapeAt(x, y) = curPiece.GetShape();
}
RemoveFullLines();
if (!isFallingFinished)
NewPiece();
}
In the Board::PieceDropped() method we set the current shape at it's final position. We call the RemoveFullLines() method to check, if we have at least one full line. And we create a new tetris shape, if it wasn't already created in the Board::PieceDropped() method in the meantime.
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
ShapeAt(j, k) = ShapeAt(j, k + 1);
}
}
This code removes the full lines. After finding a full line we increase the counter. We move all the lines above the full row one line down. This way we destroy the full line. Notice, that in our tetris game, we use so called naive gravity. This means, that the squares may be left floating above empty gaps.
void Board::NewPiece()
{
curPiece.SetRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.MinY();
if (!TryMove(curPiece, curX, curY)) {
curPiece.SetShape(NoShape);
timer->Stop();
isStarted = false;
m_stsbar->SetStatusText(wxT("game over"));
}
