Tetris, game development in SFML C++

Short description

Tetris is a classic game created in 1984 by the Russian programmer Oleksiy Pazhitnov. The objective of the game is to fill a horizontal row with different geometric shapes, known as tetrominoes, and create complete rows for points. With the speed of the game increasing over time, the challenge also increases. Despite its simple design and rules, Tetris has a strong effect on players. The game creates opportunities to improve concentration and make quick decisions. The Tetris project consists of five classes, including GameEngine, GameSound, Tetramino, Button, and AssetManager. The versatile tetromino shapes offer a bright intellectual stimulus.

Tetris, game development in SFML C++

Previous topic

“Tetris” is a legendary game that everyone knows. It was created in 1984 by the Russian programmer Oleksiy Pazhitnov and since then has won the hearts of millions of players around the world.

The gameplay consists in filling a horizontal row at the bottom of the screen with figures from different geometric shapes, known as tetraminoes. It is quite difficult to describe exactly what shapes are in the game, so it is easier to show an example: these can be shapes consisting of four squares (square, “letter T”, “letter L”, “letter S”, “letter Z” and ” the letter J”).

There are several ways to rotate and move the tetramino around the grid, but the main goal is to fill in the empty spaces and create complete rows. As soon as the entire row is filled, it disappears, and the remaining segments are reset on the vacated space, while points are awarded for each row collected.

The difficulty of the game gradually increases, as the speed of movement of the tetramino increases over time, and the amount of free space decreases. Theoretically, the game has no end, but many players cannot even reach the 10th row.

Although the game is quite simple in design and rules, it has quite a strong effect on the players. The versatility of tetramino – an almost unlimited number of combinations with a relatively small number of pieces – gives the game a bright intellectual stimulus. In addition, due to the high-speed and dynamic gameplay, players often feel the limit of their concentration, which makes the game even more exciting.

Tetris is not just a game, it is a legend that lives on and delights new generations of players. More than 35 years have passed since its release, and it still does not lose its popularity. Whether it’s on consoles, mobile apps, or online, there’s more to choose from than ever before. It is characterized by a simple and bright design, unique gameplay and incredible cultural relevance, and also creates unique opportunities to improve your concentration and learn to make complex decisions quickly.

Tetris

The Tetris project consists of five classes: GameEngine, GameSound, Tetramino, Button, AssetManager.

AssetManager – manages game assets, which include textures, music, fonts.

Button – creates game interface button objects.

Tetramino – creates game logic for Tetris game.

GameSound – creates and manages sound effects.

GameEngine – game engine.

The Button class

#pragma once
#include <SFML/Graphics.hpp>
#include <iostream>

class Button {
public:
    // координаты кнопки          текстура нормальной кнопки   текстура нажатой кнопки       
    Button(sf::Vector2f location, const sf::Texture& normal, const sf::Texture& clicked);
    // метод проверки нажатия на кнопку в параметрах передаются координаты курсора мышки
    bool checkClick(sf::Vector2i mousePos = sf::Vector2i(-1,-1));
    // метод возвращающий текущее состояние отображения кнопки 
    sf::Sprite* getSprite();
    
private:
    // объект хранит нормальное отображение кнопки
    sf::Sprite normal;
    // объект хранит отображение нажатой кнопки
    sf::Sprite clicked;
    // указатель на Sprite
    sf::Sprite* currentSpr;
    // свойство состояния кнопки
    bool current=false; 
    // метод меняющий отображение кнопки
    void setState(bool);
    
};
Button::Button(sf::Vector2f location, const sf::Texture& normal, const sf::Texture& clicked)
{
    // устанавливаем текстуры для спрайтов 
    this->normal.setTexture(normal);   // нормальная кнопка
    this->clicked.setTexture(clicked); // кнопка нажата
    // устанавливаем координаты расположения кнопок
    this->normal.setPosition(location);
    this->clicked.setPosition(location);
    // присваиваем указателю нормальное состояние кнопки
    currentSpr = &this->normal;
}

The constructor creates a button in the graphics window. Accepts the button coordinates, a texture with a normal display, and a pressed button texture in the parameters.

bool Button::checkClick(sf::Vector2i mousePos) 
{
    // если передаются координаты курсора мышки делаем проверку, 
    // что курсор находится в пределах границ кнопки
    if (mousePos.x>=0)
    {
    if ((static_cast<float>(mousePos.x) > currentSpr->getPosition().x && 
        static_cast<float>(mousePos.x) < (currentSpr->getPosition().x + 
        currentSpr->getGlobalBounds().width))
        && (static_cast<float>(mousePos.y) > currentSpr->getPosition().y && 
            static_cast<float>(mousePos.y) < (currentSpr->getPosition().y + 
        currentSpr->getGlobalBounds().height)) )
        {
        // меняем состояние кнопки на противоположное
        setState(!current); return true;
        }
      }
    else
        // если кнопка нажата меняем её вид в нормальное положение
        if (current) 
        { 
            setState(!current); return true; 
        } 
    return false;
}

The checkClick() method handles button click events with the mouse cursor.

In the parameters, the coordinates of the mouse cursor are transferred to check whether the mouse cursor is within the button field.

void Button::setState(bool which) 
{
    current = which;
    if (current) 
    {
        currentSpr = &clicked;
        return;
    }
    currentSpr = &normal;  
}
sf::Sprite* Button::getSprite() 
{
    return currentSpr;
}

The setState() method sets the value of the current button state property and changes the value of the pointer to the currentSpr button display sprite to the appropriate value.

GameSound class

#pragma once
#include<array>
#include<SFML/Audio.hpp>
#include "AssetManager.h"

class GameSound
{   
	// количество звуковых эффектов
	static const int n = 5;
	// массив объектов звуковых эффектов
    std::array<sf::Sound, n> GSound; 	
		
public:
  GameSound()
	{
		// массив названий файлов и путей расположения звуковых эффектов
		std::array<std::string, n> namefilebuf{ "sound/fon.ogg" ,"sound/deadline.ogg","sound/game_over.ogg",
		"sound/movetetramino.ogg","sound/svist.ogg"};
		// цикл присвоения звуковым объектам звуковых эффектов
		for (int i = 0; i < n; i++) GSound[i].setBuffer(AssetManager::GetSoundBuffer(namefilebuf[i]));
		// звуковой объект с нулевым индексом воспроизводится циклично
		GSound[0].setLoop(true);
	};
	// метод включения звукового эффекта согласно установленного в параметрах индекса
	void play(int index);
	// метод выключения звукового эффекта согласно установленного в параметрах индекса
	void stop(int index);
	// метод выключения всех звуковых эффектов
	void AllStop();
};

We create a constant whose value will be the number of sound effects in the array namefilebuf and GSound. We create an array of Sound objects that will play the downloaded audio data. In the constructor, we create an array of paths for loading audio data and load sound effects into GSound sound effects playback objects using the setBuffer() method, similar to how we load textures for sprites. Then we set the object with index zero using the setLoop() method to true to loop the playback, since this object will play the background music.

#include "GameSound.h"
void GameSound::play(int index) 
{
	if (GSound[index].getStatus() == sf::SoundSource::Status::Stopped ) GSound[index].play();
}
void GameSound::stop(int index) 
{
	if (GSound[index].getStatus() == sf::SoundSource::Status::Playing) GSound[index].stop();
}
void GameSound::AllStop()
{
	for (int i = 0; i < n; i++) if (GSound[i].getStatus() == sf::SoundSource::Status::Playing) GSound[i].stop();
}

The play() method starts playing the sound effect according to the specified index parameter, provided that the effect is not playing.

The stop() method stops playing the sound effect according to the specified index parameter, provided that the effect is playing.

The AllStop() method disables the playback of all sound effects.

Tetramino class

An open area of ​​the Tetramino class

#pragma once
#include"AssetManager.h"
#include "GameSound.h"
#include <array>
#include <vector>
#include <chrono>
#include <random>
#include <math.h>

class Tetramino
{
public:
	// перечисление направлений движения тетрамино по горизонтали
	enum class direction { left = -1, nuLL, right };
	// перечисление проверки координат на столкновение с установлеными границами 
	// при перемещении и вращении
	enum class ch { x, y, rotation };
	// конструктор тетрамино
	explicit Tetramino(sf::RenderWindow&, sf::Vector2f, sf::Vector2i, float);
	// метод устанавливающий вектор движения тетрамино
	void tetDirection(direction);
	// метод рисующий тетрамино в графическом окне
	void draw();
	// метод обновления игровой логики тетрамино
	void update(sf::Time const&);
	// метод вращения тетрамино
	void rotate();
	// метод возвращающий координаты центра тетрамино
	sf::Vector2f getPositio();
	// метод ускоряющий падение тетрамино
	void speed();
	// метод сбрасывающий все свойства в начальные значения - рестарт игры
	void restart();
	// метод возвращающий количество выигранных очков
	int getscore() const;
	// метод включающий и выключающий фоновую музыку
	void mustet(bool);
	// метод отображения макета следующего тетрамино
	void maket(sf::Vector2f);

A closed area of ​​the Tetramino class

private:
	const int height;               // высота игрового поля 
	const int width;                // ширина игрового поля 
	const  float click_dy = 1.0f;   // шаг перемещения тетрамино по y  
	// массив игрового поля
	std::vector<std::vector<sf::Color>> square;
	// массив локальных координат фигурок тетрамино 
	std::array<std::array<int, 4>, 7> figures
	{ {{1,3,5,7},{2,4,5,7},{3,4,5,6},{3,4,5,7},{2,3,5,7},{3,5,6,7},{2,3,4,5}} };
	// положение прямоугольника в построении тетрамино 
	std::array<sf::Vector2f, 4> t;
	// массив цвета для тетрамино
	std::array<sf::Color, 7> tetcolor{ {sf::Color::Blue,sf::Color::Cyan,sf::Color::Yellow,
		sf::Color::Green,sf::Color::Magenta,sf::Color::Red,sf::Color::White} };
	// прямоугольник тетрамино
	std::unique_ptr<sf::RectangleShape> cube = std::make_unique<sf::RectangleShape>();
	// момент системного времени
	long long seed = std::chrono::system_clock::now().time_since_epoch().count();
	// запуск генератора случайных чисел
	std::default_random_engine rnd = std::default_random_engine(static_cast<long>(seed));
	// установка диапазона случайных чисел
	std::uniform_int_distribution<int> d = std::uniform_int_distribution<int>(0, 6);
	// ссылка на графическое окно
	sf::RenderWindow& window;
	// начальные координаты тетрамино
	const sf::Vector2f tet;
	sf::Time frameRate;          // интервал обновления игровой логики 
	sf::Vector2i typeTet;	     // тип тетрамино 
	sf::Vector2i colTet;         // цвет тетрамино 
	void newFigrois();	         // новый тетрамино  
	void lineDead(int);	         // уничтожение полоски тетрамино при заполнении поля по горизонтали 
	bool check(ch);	             // проверка положения тетрамино 
	sf::Int32 delay;             // интервал обработки игровой логики 
	float click_dx;              // шаг перемещения тетрамино по x  
	int score;                   // очки выигрыша 
	bool playMus = false;        // включение музыки 
	GameSound mus;				 // объект музыкальных эффектов 
	float scale;                 // масштаб тетрамино 
	// свойство координат макета тетрамино
	sf::Vector2f positionmaket= sf::Vector2f(-1, -1);
};
Tetramino::Tetramino(sf::RenderWindow& window, sf::Vector2f pos, sf::Vector2i square_size, float scale)
: height(square_size.y), width(square_size.x), window(window), tet(pos), scale(scale)
{
	cube->setOutlineColor(sf::Color(78, 87, 84));
	cube->setOutlineThickness(-1);
	cube->setSize(sf::Vector2f(scale,scale));
	for (int i = 0; i < width; i++)
	{
		std::vector<sf::Color> v;
		for (int j = 0; j < height; j++) {
			v.push_back(sf::Color::Black);
		}
		square.push_back(v);
	}
	restart();
}

The constructor in the parameters accepts a reference to the window graphic window, the coordinates of the playing field pos, the size of the playing field square_size, the scale of the playing field and the tetramino figures scale. In the definition of the constructor, we set the contour for the rectangle shape with the size of one unit setOutlineThickness(-1), minus denotes the inner contour of the rectangle, without a sign, the outer one. The setOutlineColor(sf::Color(78, 87, 84)) method sets the gray outline color. With the help of a loop, we create elements of the array of the playing field square, filling each with the value Color:: Black (black color is an empty field). Method restart(), we set the initial values ​​of all tetramino properties.

void Tetramino::restart()
{
	for (int i = 0; i < width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			square[i][j] = sf::Color::Black;
		}
	}
	typeTet.y = d(rnd);
	colTet.y = d(rnd);
	score = 0;
	newFigrois();
}

In the restart() method, we fill the game field array with empty values, i.e. black color We set a random value for the type typeTet.y and the color colTet.y of the tetramino. Initialize the number of scored points to zero. Using the newFigrois() method, we build a tetramino shape.

void Tetramino::newFigrois()
{
	typeTet.x = typeTet.y;
	colTet.x = colTet.y;
	for (int i = 0; i < 4; i++)
	{
		t[i].x = figures[typeTet.x][i] % 2+ static_cast<float>(floor(width/2));
		t[i].y = static_cast<float>(figures[typeTet.x][i] / 2);
	}
	typeTet.y = d(rnd);
	colTet.y = d(rnd);
   delay = 250;
}

In the newFigrois() method, we assign the previous values ​​of the type and color of the tetramine to the new tetramine in the playing field.

With the help of formulas, we calculate the global coordinates by X and G of each element that makes up the tetramino.

In the figure array, each figure is represented in local coordinates. To display the figure in global coordinates, we use special formulas, in which to find the x-value, we take the remainder from dividing the local coordinates by two and add the width of the playing field, divided by two, thus the figure starts moving along the center of the playing field on the x-axis. To obtain the Greek value, we divide the local coordinates by two and round down.

Using a random number generator, we find new values ​​for the type and color of the tetramino shape and assign these values ​​to the variables of the tetramino layout.

We set the delay processing interval of the game logic.

void Tetramino::update(sf::Time const& deltaTime)
{	
	if (playMus)  mus.play(0); else mus.stop(0);
	frameRate += deltaTime;
	if (frameRate > sf::milliseconds(delay))
	{
		frameRate = sf::milliseconds(0);
		if (check(ch::x) && click_dx !=0)
		{
			for (int i = 0; i < 4; i++) t[i].x += click_dx; mus.play(3); click_dx = 0;
		}
		if (check(ch::y)) { for (int i = 0; i < 4; i++)  t[i].y += click_dy; }
		else 
		{   
			for (int i = 0; i < 4; i++)
			{
				if (static_cast<int>(t[i].y) == 2) { restart(); mus.play(2); return; }
				square[static_cast<size_t>(t[i].x)][static_cast<size_t>(t[i].y)] = sf::Color(tetcolor[colTet.x]);
			}
			int numLine = 0;
				for (int j = 0; j < height; j++)
				{
				int line = 0;
				for (int i = 0; i < width; i++)
			    {	
					if (square[i][j] != sf::Color::Black) line++;
					if (line == width)
					{
					lineDead(j);
					mus.play(1);
					numLine++;
					}
				}
				}
				if (numLine != 0) 
				{
					score += 5*(numLine * numLine);
				}
			newFigrois();
		}
	}
}

In the body of the update() method, we measure the frameRate elapsed time interval, if the interval exceeds the set limits, we reset the time interval count variable to zero. If the x-axis motion vector was specified, we check for a possible collision of the tetramino figure with obstacles using the check(ch::x) method. If there are no obstacles, move the tetramino figure, voice the movement of mus.play(3) and zero out the movement vector along x.

We make a similar check while moving the tetramino behind the jack and moving the tetramino. If movement along the board is not possible, then we check the exit from the playing field and if so, we start the game again, otherwise we fill the array of the playing field with the color of the current tetramino figure, using the coordinates of the tetramino as indices of the two-dimensional array.

The following code identifies a filled horizontal playing field and removes it. With the last line, we create a new figure on the playing field newFigrois().

void Tetramino::tetDirection(direction dir)
{
	click_dx =static_cast<float> (dir); 
}

Using the tetDirection method, we change the horizontal movement vector of the figure.

void Tetramino::rotate()
{
	if (check(ch::rotation))
	{
	sf::Vector2f centerRotation = t[1];
	for (int i = 0; i < 4; i++) 
	{
		float x = t[i].y - centerRotation.y;
		float y = t[i].x - centerRotation.x;
		t[i].x = centerRotation.x - x;
		t[i].y = centerRotation.y + y;
	}
	mus.play(3);
	}
}

In the shape rotation method rotate(), we do a preliminary check for collisions of the shape with check(ch::rotation), if we do not rotate the shape.

To calculate the new coordinates of the figure during rotation, we use a formula from linear algebra.

void Tetramino::speed()
{
	mus.play(4);
	delay = 10;
}

void Tetramino::lineDead(int g) 
{
	for (int i = g; i > 0; i--)
	{
		for (int j = 0; j < width; j++)
		{
			square[j][i] = square[j][static_cast<size_t>(i-1)];
		}
	}
}

The speed() method changes the processing interval of the game logic to ten, thus speeding up the movement of the tetramino.

The lineDead method destroys the filled horizontal line, overwriting the previous tetramino elements in its place in the playing field. In the parameters, the method receives the index of the filled horizontal array of the playing field.

bool Tetramino::check(ch ch)
{
	switch (ch)
	{	case Tetramino::ch::x:
			{	for (int i = 0; i < 4; i++)
				{if ((t[i].x + click_dx < 0) || 
				(t[i].x + click_dx >static_cast<float>(width-1))) return false;	
				if ((static_cast<int>(t[i].y) >= 0) && 
				(square[static_cast<size_t>(t[i].x + click_dx)][static_cast<size_t>(t[i].y)]
				!= sf::Color::Black))  return false;}
    	break;}
		case Tetramino::ch::y:
	        {	for (int i = 0; i < 4; i++)
				{if ((t[i].y+ click_dy) > static_cast<float>(height-1))  return false;
				if ((static_cast<int>(t[i].y + click_dy) >= 0) && 
				(square[static_cast<size_t>(t[i].x )][static_cast<size_t>(t[i].y + click_dy)] 
				!= sf::Color::Black))  return false;}
		break;}
		case Tetramino::ch::rotation:
			{ sf::Vector2f centerRotation = t[1];
				for (int i = 0; i < 4; i++)
				{
				float x = t[i].y - centerRotation.y;
				float y = t[i].x - centerRotation.x;
			    if (((centerRotation.x - x)<0) || ((centerRotation.x - x)  > static_cast<float>(width-1)) ||
				((centerRotation.y + y)> static_cast<float>(height-1))) return false;
				if ((static_cast<int>(centerRotation.y + y) >= 0) &&
				(square[static_cast<size_t>(centerRotation.x - x)][static_cast<size_t>(centerRotation.y + y)]
				!= sf::Color::Black))  return false;
				}
		break;}
	default:
		break;
	}	
	return true;
}

The check() method, depending on the set parameter, checks for out of bounds of the playing field or collision with the elements of the tetramino figures in the playing field, if there is a collision or out of bounds, it returns the value false otherwise true.

void Tetramino::draw()
{
	if (positionmaket.x >= 0) 
	{
	cube->setFillColor(tetcolor[colTet.y]);
	for (int i = 0; i < 4; i++)
	{	
		cube->setPosition((figures[typeTet.y][i] % 2)*scale, (static_cast<float>(figures[typeTet.y][i] / 2))* scale);
		cube->move(positionmaket);
		window.draw(*cube);	
	}
    }
	for (int i = 0; i < width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			cube->setFillColor(square[i][j]);
			cube->setPosition(static_cast<float>(i)*scale,static_cast<float>(j)*scale);
			cube->move(tet);
			window.draw(*cube);
		}
	}
	cube->setFillColor(tetcolor[colTet.x]);
	for (int i = 0; i < 4; i++)
	{   
	    cube->setPosition(t[i].x * scale, t[i].y * scale);
		cube->move(tet);
		window.draw(*cube);
	}
}

The draw() method, if the coordinates of the tetramino layout are given, draws them in the graphics window. Draws a playing field and a tetramino figure.

void Tetramino::mustet(bool m)
{
	playMus = m;
}

int Tetramino::getscore() const
{
	return score;
}

sf::Vector2f Tetramino::getPositio()
{
	sf::Vector2f pos;
	pos.x = t[1].x * scale + tet.x;
	pos.y =  t[1].y * scale + tet.y;
	return pos;
}

void Tetramino::maket(sf::Vector2f posmak)
{
	positionmaket = posmak;
}

The mustet() method includes turns off the background music by changing the playMus property.

The getscore() method returns the scored game points.

The getPositio() method returns the global coordinates of the tetramino’s center of rotation.

The maket() method determines the coordinates of the tetramino layout.

GameEngine class

#pragma once
#include"Button.h";
#include"Tetramino.h";

class GameEngine
{
public:
	GameEngine();          
	void run();            
private:
	// объект игровых ассетов
	AssetManager manager;
	// графическое окно
	std::unique_ptr<sf::RenderWindow> window = std::make_unique<sf::RenderWindow>
		(sf::VideoMode(640, 640), L"Тетрис", sf::Style::Close);
	// иконка графического окна
	sf::Image icon;
	// игровой фон
	sf::RectangleShape background = sf::RectangleShape(sf::Vector2f(640, 640));
	// кнопки игрового интерфейса
	Button pause = Button(sf::Vector2f(13, 140), 
	AssetManager::GetTexture("image/play1.png"), AssetManager::GetTexture("image/pause2.png"));
	Button restart = Button(sf::Vector2f(13, 220), 
	AssetManager::GetTexture("image/restart1.png"), AssetManager::GetTexture("image/restart2.png"));
	Button sound = Button(sf::Vector2f(13, 300), 
	AssetManager::GetTexture("image/nosound.png"), AssetManager::GetTexture("image/sound.png"));
	Button exit = Button(sf::Vector2f(13, 380), 
	AssetManager::GetTexture("image/exit1.png"), AssetManager::GetTexture("image/exit2.png"));
	// объект текста
	sf::Text text;
	// игра тетрис
	Tetramino tetramino = Tetramino(*window, sf::Vector2f(210, -42), sf::Vector2i(20,33), 20);
	void input();         
	void update(sf::Time const& deltaTime);
	void draw();          
	bool myexit = false;  
	bool mypause = false; 
	bool mus = false;     
	sf::Time tm;          
};
GameEngine::GameEngine()
{
	background.setTexture(&AssetManager::GetTexture("image/Tetris.png"));
	if (!icon.loadFromFile("image/game.png")) window->close();
	window->setIcon(256, 256, icon.getPixelsPtr());
	text.setFont(AssetManager::GetFont("font/Godzilla.ttf"));
	text.setFillColor(sf::Color::Green);
	tetramino.maket(sf::Vector2f(70,20));
}

In the designer, we set the texture for the game background, the font and color of the text, the coordinates of the tetramino layout.

void GameEngine::input()
{
	sf::Event event;
	while (window->pollEvent(event))
	{
		if (event.type == sf::Event::Closed) window->close();
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
		{tetramino.tetDirection(Tetramino::direction::left);}
		else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
		{tetramino.tetDirection(Tetramino::direction::right);}
		else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
		{tetramino.speed();	}
		else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
		{tetramino.rotate();}
		if (event.type == sf::Event::MouseWheelMoved)
		{
			if ((event.mouseWheel.delta == -1) || (event.mouseWheel.delta == 1))
			{
				tetramino.speed();
			}
		}
		if (event.type == sf::Event::MouseButtonPressed)
		{
			if (event.mouseButton.button == sf::Mouse::Left)
			{
				if (pause.checkClick(sf::Mouse::getPosition(*window)))
				{   mypause = !mypause;}
				if (sound.checkClick(sf::Mouse::getPosition(*window)))
				{   
					if (mus) mus = false; else mus = true;
					tetramino.mustet(mus);
				}
				if (restart.checkClick(sf::Mouse::getPosition(*window)))
				{	tetramino.restart();}
				if (exit.checkClick(sf::Mouse::getPosition(*window)))
				{	myexit = true;		}
				if ((sf::Mouse::getPosition(*window).x < tetramino.getPositio().x)
					&& (sf::Mouse::getPosition(*window).x > 208) && (sf::Mouse::getPosition(*window).x < 609))
				{	tetramino.tetDirection(Tetramino::direction::left);	}
				if (sf::Mouse::getPosition(*window).x >= tetramino.getPositio().x
					&& sf::Mouse::getPosition(*window).x > 208 && sf::Mouse::getPosition(*window).x < 609)
				{	tetramino.tetDirection(Tetramino::direction::right);}
			}
			if (event.mouseButton.button == sf::Mouse::Right)
			{
				if (sf::Mouse::getPosition(*window).x > 208 && sf::Mouse::getPosition(*window).x < 609)
				{tetramino.rotate();}
			}
		}
		if (event.type == sf::Event::MouseButtonReleased)
		{
			if (event.mouseButton.button == sf::Mouse::Left)
			{
				restart.checkClick();
				exit.checkClick();
			}

		}}}

In the input() method, we create event processing for controlling the tetramino figure with arrows on the keyboard: movement to the left, movement to the right, accelerated falling of the figure down, rotation of the tetramino figure.

When rotating the mouse wheel event.type == sf::Event::MouseButtonPressed, the tetramino shape accelerates its fall.

In the mouse button pressing event.type == sf::Event::MouseButtonPressed section, we check the position of the mouse cursor when the left button is pressed. If the cursor is in the area of ​​the game menu button, the code for pressing it and the action algorithm corresponding to this button are executed. If the cursor is within the playing field, move the tetramino figure horizontally to the side on which the mouse cursor is located.

When you release the left mouse button, if the restart or exit buttons were pressed, their position returns to their original position.

When you press the right button, if the cursor is in the game field, the tetramino rotates.

void GameEngine::update(sf::Time const& deltaTime)
{
	if (!mypause) tetramino.update(deltaTime);

	if (myexit) {
		tm += deltaTime;
		if (tm > sf::seconds(1))
		{
			if (myexit) window->close();
		}
	}
}

In the update() method, if pause is disabled, the tetramino game logic is played. When the exit property is enabled, exiting the program occurs with a slight delay.

void GameEngine::draw()
{
	window->clear(sf::Color::Black);
	tetramino.draw();
	window->draw(background);
	window->draw(*pause.getSprite());
	window->draw(*restart.getSprite());
	window->draw(*sound.getSprite());
	window->draw(*exit.getSprite());
	text.setPosition(15, 515);
	text.setString(" < score > ");
	window->draw(text);
	text.setString(std::to_string(tetramino.getscore()));
	text.setPosition(100 - text.getGlobalBounds().width / 2, 555);
	window->draw(text);
	window->display();
}

The draw() method draws in the graphics window: the playing field with tetramino elements, the game background, the game interface buttons, the text for counting points.

void GameEngine::run()
{
	sf::Clock clock;

	while (window->isOpen())
	{
		sf::Time dt = clock.restart();
		input();
		update(dt);
		draw();
	}
}

The run() method creates a game loop, including the methods described above.

You can get more detailed instructions by watching the video SFML C++ Tetris»

Clone the Tetris repository

Telegram channel “Programming C++/C# games

Previous topic

Related posts