zsmb's prog site

Tetris

  2015.10.08.

Introduction

It's a classic. Everybody has to write a Tetris at some point, right? So here it is. It's Tetris. There's music. There's even a sound effect when you clear rows. It's pretty cool.

Description

Not much to say about the gameplay. Pieces come from the top, you can move them left and right, rotate, and drop them. When you complete rows, the completed row disappears, everything else above it goes one block down. Each completed row earns you 100 points, you can see your best achieved scores in the High Scores menu. The game uses the  standardized colours for the pieces.

Sound credits

The music used in the game is by Octopus58, who was kind enough to give me permission to use it. You can check out their website with all their music  here, and the remix I used on their YouTube channel  here.

The sound for clearing a row is from  freesound.org, and was uploaded there by Cabeeno Rossley. It's used under a  Creative Commons license.

Usage

The game has a few dependencies that have to be in the same folder as the executable for it to run.

  • about.txt
  • help.txt
  • scores.dat (gets auto-generated if missing)
  • SourceSansPro-Regular.ttf
  • OctoNote - Korobeiniki Tetris Remix.ogg
  • OctoNote - Korobeiniki Tetris Remix (V2).ogg
  • 126426__cabeeno-rossley__timer-ends-time-up.wav

Other than having these in the right place, the game itself is fairly self explainatory. You can use the indicated letters on the keyboard to quickly navigate the menu.

Development

C++, SDL2, same old methods. This time properly separated into header and source files. I also generated the doxygen documentation for the project, which you can find  here. The most exciting part was getting SDL_Mixer to work and then finding free to use music and sound effects for the projects.

If you want to self-compile this project, you'll need SDL2, SDL_TTF, and SDL_Mixer, as well as have C++11 enabled. If compiling for Windows, use the sources.zip below, otherwise get the prepared Linux project. The main difference is in the includes for SDL, as those are under SDL2/ on Linux.

Rotation, rotation, rotation

The way I rotate the pieces deserves a bit of an explaination, even though I hope I managed to document it in a sensible way in the source files. I didn't bother to make a generic rotation algorithm, I just hardcoded how each piece is to be rotated. Not very pretty, but plenty fast.

The Piece class has a virtual  Piece::rotate() function, this is used for all but the I and O pieces. The override of the I piece is very similar to how all the others are rotated, and the rotation of the O piece probably doesn't warrant an explaination.

Let's take a J piece for example. Here's the code that constructs it when it spawns. The constructor is given an x position (middle of the TetrisWindow that contains it), the y position by default is going to be 0, marking the top of the window.


blocks.push_back(new Block(x_pos-1, 0, colour, window));
blocks.push_back(new Block(x_pos, 0, colour, window));
blocks.push_back(new Block(x_pos+1, 0, colour, window));
blocks.push_back(new Block(x_pos+1, 1, colour, window));
				

After the above code runs, we have a  Piece_J object that looks like this (number is the index of each block withing the vector):

Piece_J after construction

Note that it spawns a block higher than this, I just took screenshots after one step so that it rotates nicer.

Now, let's see what the  Piece::rotate() function does.


int x = blocks[1]->get_x();
int y = blocks[1]->get_y();

int end_x = blocks[0]->get_x();
int end_y = blocks[0]->get_y();
				

So we have the coordinates of the first two blocks, and the following if-else structure will determine which way the piece faces based on these. Since the x coordinate of the Block at index 0 is smaller, the following case wil run:


else if(end_x < x) { /* Piece faces left */
    if(is_clear_to_rotate(x, blocks[1], x+r[6], y+r[7], x+r[8], y+r[9], x+r[10], y+r[11])) {
        blocks[0]->set(x+r[6], y+r[7]);
        blocks[2]->set(x+r[8], y+r[9]);
        blocks[3]->set(x+r[10], y+r[11]);
    }
}
				

Let's take at the look at the values of the  "rotation matrix" that we're using here. Indexes 6 to 11, the second row below (rows stand for right, left, up, down, in the order the if-else clauses are).


const char Piece_J::rotation_matrix[24] = {
    0, -1, 0, 1, -1, 1,
    0, 1, 0, -1, 1, -1,
    -1, 0, 1, 0, 1, 1,
    1, 0, -1, 0, -1, -1
};
				

So this row tells us where the Blocks that are indexed 0,2 and 3 are to end up after rotation. The Block at index 0 is the pivot Block, which doesn't rotate.

Reading the row, the 0th Block will be in the same column (x offset 0) as the pivot, in the row below it (y offset 1). The 2nd Block is also in the same column (x offset 0), but in the row above (y offset -1). Finally, the 3rd Block is in the next column (x offset 1), and a row above (y offset -1).

Here's what the Piece looks like after rotation:

Piece_J after rotation

The  is_clear_to_rotate() function that I won't go into such detail on checks with the TetrisWindow to see if the positions specified by the offsets are available, and if not, it tries moving the whole piece left or right a block or two, and rotating it there. It does so by taking a reference to the x variable and modifying it, as well as taking the pointer to the pivot Block.

General purpose components

There are some components that I tried to make reusable, these include MusicPlayer, SoundPlayer, and the text.cpp and text.h files (I didn't put these in a class, because I wanted to use them with the same syntax as the SDL_gfx library functions).

  •  MusicPlayer is a class that opens a music file (wav, ogg, and some mp3s) and has functions for play, pause, stop, and adjusting volume.
  •  SoundPlayer opens a sound file (wav) and only has a play function, along with volume adjustment options. Both of these classes use SDL_Mixer.
  •  text.cpp and text.h provide easy font rendering using SDL_ttf, with a fixed font that can be changed in the source file.

The sources for these are available for download below separately.

Downloads

Downloading the zip file that contains the executable results in a warning (at least in Chrome) as a security measure, but you can choose to "Keep" it.

Name Description Size Date
tetris.zip Windows executable with required files 16.2 MB 2015.10.08.
tetris.tar.gz Linux project with Makefile, SDL_gfx and assets 14.4 MB 2015.10.08.
tetris_sources.zip All source files of the project 21.5 KB 2015.10.08.
tetris_assets.zip Required text and audio files 14.3 MB 2015.10.08.
MusicPlayer.zip Independent source files 1.64 KB 2015.10.08.
SoundPlayer.zip Independent source files 1.35 KB 2015.10.08.
text.zip Independent source files with font 1.34 KB 2015.10.08.