Tetris in C++ with the SFML2 library
Introduction
This page holds a complete Tetris game in C++ with the SFML 2 library, written in one source file. The whole game is a single program: a class describes one falling tetromino, and the main loop draws the field, moves the piece down and handles the keyboard.
The playing field is an array of 15 by 25 integers, where zero marks an empty cell and any other value is the color of a filled block. Seven tetromino shapes, I, S, Z, T, L, J and O, are stored as four block positions each. A piece moves down, left and right and rotates around its second block; a move that would push it off the field or into an occupied cell is cancelled, and a piece that can no longer move down is fixed in the field.
A clock drives the fall: the piece steps down after 0.3 seconds, and the delay shrinks to 0.05 seconds while the down arrow is held. Each fully filled line is cleared by shifting the rows above it down and adds one point to the score drawn above the field. The right and left arrows move the piece sideways, the up arrow rotates it and the down arrow speeds up the fall.
Code
#include <SFML/Graphics.hpp>
#include <iostream>
#include <time.h>
#include <string>
const int field_height = 25;
// field height
const int field_width = 15;
// field width
int field[field_height][field_width] { }; // the playing field
// Array of tetromino shapes
int figures[7][4] =
{
1,3,5,7, // I
2,4,5,7, // S
3,5,4,6, // Z
3,5,4,7, // T
2,3,5,7, // L
3,5,7,6, // J
2,3,4,5, // O
};
int checkLine(int& userPoints)
{
//----LINE CHECK----//
int k = field_height - 1; // row k is the lowest one
for (int i = field_height - 1; i > 0; i--)
// i runs through every row from bottom to top.
// at the start k and i are equal
{
int count = 0;
for (int j = 0; j < field_width; j++)
{
if (field[i][j])
{
count++;
}
field[k][j] = field[i][j];
}
if (count < field_width)
{
k--;
}
else
{
userPoints += 1;
}
// k decreases only if the line is not completely filled
// if the line is completely filled, the next i
// already corresponds to a higher row
// while k is still on the full row
// row k will be overwritten by the next row i
}
return userPoints;
}
class Tetramino
{
public:
int color { };
int type { };
int blockPositions[4] { };
struct Coordinates
{
int x;
int y;
};
Coordinates coordinates[4]{ };
int figures[7][4] =
{
1,3,5,7, // I
2,4,5,7, // S
3,5,4,6, // Z
3,5,4,7, // T
2,3,5,7, // L
3,5,7,6, // J
2,3,4,5, // O
};
void create()
{
std::cout << "new tetramino created" << std::endl;
color = 1 + rand() % 7;
type = rand() % 7;
for (int i = 0; i < 4; i++)
{
blockPositions[i] = figures[type][i];
}
for (int i = 0; i < 4; i++)
{
coordinates[i].x = blockPositions[i] % 2 + 7;
// shift the starting position closer to the center with + 7
coordinates[i].y = blockPositions[i] / 2;
}
};
void draw(sf::RenderWindow& thatWindow,
sf::Sprite& thatSprite,
int marginLeft,
int marginTop)
{
// Draw the tetromino from the Tetramino class
for (int i = 0; i < 4; i++)
{
// Color the tetromino
thatSprite.setTextureRect(sf::IntRect(color * 18, 0, 18, 18));
// Set the position of every block of the tetromino
thatSprite.setPosition(coordinates[i].x * 18, coordinates[i].y * 18);
thatSprite.move(marginLeft, marginTop); // offset
// Drawing the sprite
thatWindow.draw(thatSprite);
}
};
bool check()
{
for (int i = 0; i < 4; i++)
{
if (coordinates[i].x < 0 || coordinates[i].x >= field_width || coordinates[i].y >= field_height)
{
std::cout << std::endl << "field X" << coordinates[i].x << "Y " << coordinates[i].y << "is out of board" << std::endl;
return false;
}
else if (field[coordinates[i].y][coordinates[i].x])
{
std::cout << std::endl << "field X" << coordinates[i].x << "Y " << coordinates[i].y << "is occupied" << std::endl;
return false;
}
}
return true;
}
void moveDown(sf::RenderWindow& thatWindow)
{
int x[4]{};
int y[4]{};
for (int i = 0; i < 4; i++)
{
y[i] = coordinates[i].y;
x[i] = coordinates[i].x;
}
for (int i = 0; i < 4; i++)
{
coordinates[i].y += 1;
}
if (check() == false)
{
std::cout << "stop here" << std::endl;
for (int i = 0; i < 4; i++)
{
coordinates[i].y = y[i];
std::cout << "coloring the field" << std::endl;
field[y[i]][x[i]] = color;
}
create();
if (check() == false)
{
std::cout << "GAME OVER " << std::endl;
thatWindow.close();
}
}
else
{
;
}
}
void moveHoriz(sf::RenderWindow& thatWindow, int direction)
{
int x[4]{};
int y[4]{};
for (int i = 0; i < 4; i++)
{
y[i] = coordinates[i].y;
x[i] = coordinates[i].x;
}
for (int i = 0; i < 4; i++)
{
coordinates[i].x += direction;
}
if (check() == false)
{
for (int i = 0; i < 4; i++)
{
coordinates[i].x = x[i];
}
}
else
{
;
}
}
void rotate()
{
int x[4]{};
int y[4]{};
for (int i = 0; i < 4; i++)
{
y[i] = coordinates[i].y;
x[i] = coordinates[i].x;
}
int originX = coordinates[1].x;
// set the X of the rotation center
int originY = coordinates[1].y;
// set the Y of the rotation center
for (int i = 0; i < 4; i++)
{
int dy = coordinates[i].y - originY; // y - y0
int dx = coordinates[i].x - originX; // x - x0
coordinates[i].x = originX - dy; // x_2 = x_0 - dy
coordinates[i].y = originY + dx; // y_2 = y_0 + dx
}
if (check() == false)
{
for (int i = 0; i < 4; i++)
{
coordinates[i].x = x[i];
}
}
else
{
;
}
}
};
int main()
{
srand(time(0));
int window_width{ 640 };
int window_height{ 960 };
int userPoints{ 0 };
Tetramino tetramino;
tetramino.create();
sf::RenderWindow window(sf::VideoMode(window_width, window_height), "HH Tetris!");
// Creating and loading the texture
sf::Texture texture, texture_background, texture_frame;
texture.loadFromFile("C:\Users\andrei\source\repos\tetris\images\corona_tiles.png");
texture_background.loadFromFile("C:\Users\andrei\source\repos\tetris\images\background.png");
texture_frame.loadFromFile("C:\Users\andrei\source\repos\tetris\images\frame.png");
// Creating the sprite
sf::Sprite sprite(texture), sprite_background(texture_background), sprite_frame(texture_frame);
// Cut a single square of 18x18 pixels out of the sprite
sprite.setTextureRect(sf::IntRect(0, 0, 18, 18));
// Variables for the timer and the delay
float timer = 0;
float delay = 0.3;
// Clock (timer)
sf::Clock clock;
// Main application loop: runs while the window is open
while (window.isOpen())
{
// Get the time elapsed since the last restart and convert it to seconds
float time = clock.getElapsedTime().asSeconds();
clock.restart();
timer += time;
// Handle events in the loop
sf::Event event;
while (window.pollEvent(event))
{
// Did the user click the cross and want to close the window?
if (event.type == sf::Event::Closed)
// then close it
window.close();
// Was a key pressed on the keyboard?
if (event.type == sf::Event::KeyPressed)
{
// Is this key the left arrow?
if (event.key.code == sf::Keyboard::Left)
{
tetramino.moveHoriz(window, -1);
}
// Or the right arrow?
else if (event.key.code == sf::Keyboard::Right)
{
tetramino.moveHoriz(window, 1);
}
// Or maybe the up arrow?
else if (event.key.code == sf::Keyboard::Up)
{
tetramino.rotate();
}
}
// Is the "Down" key held? Speed up the falling tetromino
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) delay = 0.05;
}
// Move the tetromino down (a timer tick)
if (timer > delay)
{
tetramino.moveDown(window);
timer = 0;
}
checkLine(userPoints);
delay = 0.3;
//----DRAWING----//
// Set the background color to white
window.clear(sf::Color::White);
// Drawing the background
//window.draw(sprite_background);
int tetrisFeildTop{ 150 };
int tetrisFeildLeft{ 185 };
for (int varHeight = 0; varHeight < field_height; varHeight++)
{
for (int j = 0; j < field_width; j++)
{
if (field[varHeight][j] == 0)
{
//std::cout << "empty field" << std::endl;
sprite.setTextureRect(sf::IntRect(8 * 18, 0, 18, 18));
sprite.setPosition(tetrisFeildLeft + j * 18, tetrisFeildTop + varHeight * 18);
// Drawing the sprite
window.draw(sprite);
}
else
{
sprite.setTextureRect(sf::IntRect(field[varHeight][j] * 18, 0, 18, 18));
// the value of every field[varHeight][j] cell is a color, we use it
// to pick a colored square from the sprite
}
sprite.setPosition(tetrisFeildLeft + j * 18, tetrisFeildTop + varHeight * 18);
//sprite.move(28, 31); // offset
window.draw(sprite);
}
}
// Draw the tetromino with the draw method of the Tetramino class
tetramino.draw(window, sprite, tetrisFeildLeft, tetrisFeildTop);
// Drawing the frame
//window.draw(sprite_frame);
sf::Font font;
if (!font.loadFromFile("C:\Development\Fonts\Roboto\Roboto-Regular.ttf"))
{
// error...
}
sf::Text text;
// select the font
text.setFont(font); // font is a sf::Font
std::string userPointsString = "Score: " + std::to_string(userPoints);
// set the string to display
text.setString(userPointsString);
// set the character size
text.setCharacterSize(24); // in pixels, not points!
// set the color
text.setFillColor(sf::Color::Blue);
// set the text style
text.setStyle(sf::Text::Bold | sf::Text::Underlined);
text.setPosition(190.f, 10.f);
window.draw(text);
// Drawing the window
window.display();
}
return 0;
}
Article author: Andrei Olegovich