Tetris, game development in SFML C++

Previous topic

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

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

There are several ways to rotate and move the Tetriminos 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 dropped into the vacant place, while points are awarded for each completed row.

The difficulty of the game gradually increases as the speed of the Tetrimino’s movement 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.

Even though the game is quite simple in structure and rules, it has quite a strong impact on the players. The versatility of the tetramino – an almost limitless 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 experience 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 now than ever before. It features a simple and vibrant design, unique gameplay and incredible cultural relevance, and creates unique opportunities to improve your concentration and learn how to quickly make difficult decisions.

Tetris

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

asset manager – manages game resources, which include textures, music, fonts.

Button – creates game interface button objects.

Tetramino – creates game logic for playing Tetris.

GameSound – Creates and manages sound effects.

GameEngine is a game engine.

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. In the parameters, it takes the coordinates of the button, the texture with a normal display, and the texture of the pressed button.

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 mouse click events on the button.

The parameters pass the coordinates of the mouse cursor to check if 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 with the corresponding 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 namefilebuf and GSound array. We create an array of Sound objects that will play the loaded audio data. In the constructor, we create an array of paths to load the audio data and load the sound effects into the GSound sound effect playback objects using the setBuffer() method, similar to how we load textures for sprites. We then set the object at index zero with the setLoop() method to true to loop the playback as 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 given index parameter, provided that the effect is not playing.

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

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

Tetramino class

Tetramino class open area

#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);

Enclosed 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 takes a reference to the graphical window window, the playing field coordinates pos, the playing field size square_size, the playing field scale, and the tetramino figures scale. In the constructor definition, set the outline for the rectangle shape to one unit setOutlineThickness(-1), minus denotes the inner outline of the rectangle, unsigned outer. The setOutlineColor(sf::Color(78, 87, 84)) method sets the outline color to gray. With the help of a loop, we create the elements of the array of the playing field square, filling each with the value Color::Black (black color is an empty field). The restart() method, we set the initial values ​​of all Tetrimino 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 playing field array with empty values, i.e. black. Set a random value for typeTet.y and color colTet.y for tetraminos. The number of points scored, initialize to zero. Using the newFigrois() method, we build a tetramino figure.

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 preliminary values ​​of the tetramin type and color to the new tetramin in the playing field.

Using the formulas, we calculate the global x and y coordinates of each element that makes up the tetramino.

In the figures array, each figure is represented in local coordinates. To display a figure in global coordinates, we use special formulas in which, to find the x-value, we take the remainder of dividing the local coordinates by two and add the width of the playing field divided by two, so the figure starts moving in the center of the playing field on the x-axis. To get the value for the player, 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 Tetrimino shape and assign these values ​​to Tetrimino layout variables.

Set the game logic processing interval delay.

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 elapsed time interval frameRate, if the interval exceeds the set limits, we reset the value of the variable for calculating the time interval. If a motion vector along x 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 mus.play(3) and reset the movement vector by x.

We make a similar check when moving the tetramino over the game and move the tetramino. If it is impossible to move on the player, then we check if the playing field is out of bounds, and if so, we start the game again, otherwise we fill the playing field array with the color of the current tetramino figure, using tetramino coordinates as indices of a two-dimensional array.

The following code determines the filled horizontal of the playing field and removes it. The last line is to 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 rotate() method of the figure rotation, we do a preliminary check for the collision of the figure with obstacles check(ch::rotation), if there are none, we rotate the figure.

To calculate the new coordinates of a 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 by ten, thus speeding up the movement of the Tetriminos.

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 in the 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 going beyond the boundaries of the playing field or colliding with elements of tetramino figures in the playing field, if there is a collision or going beyond the boundaries, it returns 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 temino layout coordinates are given, draws it 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 turns background music on and off by changing the playMus property.

The getscore() method returns the accumulated game scores.

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

The maket() method sets the Tetrimino layout coordinates.

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 constructor, we set the texture for the game background, the font and color of the text, and 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 the handling of events for controlling the tetramino figure with the arrows on the keyboard: movement to the left, movement to the right, accelerated fall of the figure down, rotation of the tetramino figure.

When the mouse wheel is rotated, event.type == sf::Event::MouseButtonPressed, the Tetrimino shape accelerates its fall.

In the mouse button click processing section event.type == sf::Event::MouseButtonPressed, when the left button is pressed, we check the position of the mouse cursor. If the cursor is in the area where the game menu button is located, 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 in the direction on which side of the figure the mouse cursor is located.

When the left mouse button is released, if the restart or exit keys were pressed, their position returns to their original position.

When you press the right button, if the cursor is in the playing 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 the pause is disabled, the Tetrimino game logic is played. When the logout property is enabled, the application exits 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: a playing field with tetromino elements, game background, game interface buttons, scoring text.

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 “C++/C# game programming

Previous topic

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *