Предыдущая тема

"Тетрис" - легендарная игра, которую знает каждый. Она была создана в 1984 году российским программистом Алексеем Пажитновым и с тех пор завоевала сердца миллионов игроков по всему миру.

Игровой процесс заключается в заполнении горизонтального ряда в нижней части экрана фигурами из различных геометрических форм, известных как тетрамино. Довольно сложно описать, какие именно фигуры есть в игре, поэтому проще показать пример: это могут быть фигуры, состоящие из четырех квадратов (квадрат, "буква T", "буква L", "буква S", "буква Z", "буква I" и "буква J").

Существует несколько способов вращать и перемещать тетрамино по сетке, но главная цель - заполнить пустые места и создать полные ряды. Как только весь ряд заполнен, он исчезает, а оставшиеся сегменты сбрасываются на освободившееся место, при этом за каждый собранный ряд начисляются очки.

Сложность игры постепенно возрастает, поскольку скорость перемещения тетрамино со временем увеличивается, а количество свободного пространства уменьшается. Теоретически игра не имеет конца, но очень многие игроки не могут дойти даже до 10-го ряда.

Несмотря на то, что игра довольно проста по конструкции и правилам, она оказывает довольно сильное влияние на игроков. Универсальность тетрамино - практически безграничное количество комбинаций при относительно небольшом количестве фигур - придает игре яркий интеллектуальный стимул. Кроме того, благодаря высокоскоростному и динамичному игровому процессу, игроки часто испытывают предел своей концентрации, что делает игру еще более захватывающей.

Тетрис - это не просто игра, это легенда, которая живет и радует новые поколения игроков. Прошло уже более 35 лет с момента ее выхода, а она все еще не теряет своей популярности. Будь то на консолях, в мобильных приложениях или в онлайн-версиях, сейчас выбор игр больше, чем когда-либо прежде. Она отличается простым и ярким дизайном, уникальным геймплеем и невероятной культурной актуальностью, а также создает уникальные возможности для того, чтобы улучшить свою концентрацию и научиться быстро принимать сложные решения.

Тетрис

Проект Тетрис состоит из пяти классов: GameEngine, GameSound, Tetramino, Button, AssetManager.

AssetManager – управляет игровыми ресурсами, в которые входят текстуры, музыка, шрифты.

Button – создаёт объекты кнопок игрового интерфейса.

Tetramino – создаёт игровую логику для игры тетрис.

GameSound – создаёт и управляет звуковыми эффектами.

GameEngine – игровой движок.

Класс Button

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

Конструктор создаёт кнопку в графическом окне.  В параметрах принимает координаты кнопки, текстуру с нормальным отображением и текстуру нажатой кнопки.

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;
}

Метод checkClick() обрабатывает события нажатия на кнопку курсором мышки.

В параметрах передаются координаты курсора мышки для проверки, находится ли курсор мышки в пределах поля кнопки.

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

Метод setState(), устанавливает значение свойства состояния кнопки current и меняет значение указателя на спрайт отображение кнопки currentSpr соответствующим значением.

Класс GameSound

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

Создаём константу, значением которой будет количество звуковых эффектов в массиве namefilebuf и GSound. Создаём массив объектов Sound, которые будут воспроизводить загруженные аудио данные. В конструкторе создаем массив путей для загрузки аудио данных и загружаем звуковые эффекты в объекты воспроизведения звуковых эффектов GSound с помощью метода setBuffer(), аналогично тому, как мы загружаем текстуры для спрайтов. Затем устанавливаем объект с нулевым индексом с помощью метода setLoop() в положение true, чтобы зациклить воспроизведение, поскольку этот объект будет воспроизводить фоновую музыку.

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

Метод play() запускает воспроизведение звукового эффекта в соответствии с заданным параметром индекса, при условии, что эффект не воспроизводится.

Метод stop() останавливает воспроизведение звукового эффекта в соответствии с заданным параметром индекса, при условии, что эффект воспроизводится.

Метод AllStop() отключает воспроизведение всех звуковых эффектов.

Класс Tetramino

Открытая область класса Tetramino

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

Закрытая область класса Tetramino

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();
}

Конструктор в параметрах принимает ссылку на графическое окно window, координаты игрового поля pos, размер игрового поля square_size, масштаб игрового поля и фигур тетрамино scale. В определении конструктора устанавливаем контур для фигуры прямоугольника размером в одну единицу setOutlineThickness(-1), минус обозначает внутренний контур прямоугольника, без знака внешний.  Метод setOutlineColor(sf::Color(78, 87, 84)) устанавливает цвет контура серый. С помощью цикла создаём элементы массива игрового поля square, заполняя каждый значением Color::Black (чёрный цвет – пустое поле). Метод restart(), задаём начальные значения всех свойств тетрамино. 

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();
}

В методе restart(), заполняем массив  игровое поле пустыми значениями т.е. чёрным цветом. Устанавливаем случайное значение для типа typeTet.y и цвета colTet.y тетрамино. Количество набранных очков score, инициализируем значением ноль. Методом newFigrois() строим фигуру тетрамино.

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;
}

В методе newFigrois(), присваиваем предварительные значения типа и цвета тетрамина, новому тетрамину в игровом поле.

С помощью формул высчитываем глобальные координаты по иксу и по игреку каждого элемента, из которого состоит тетрамино.

В массиве figures каждая фигура представлена в локальных координатах. Чтобы отобразить фигуру в глобальных координатах используем специальные формулы, в которых для нахождения значение по иксу берём остаток от деления локальных координат на два и добавляем ширину игрового поля, делённую на два, таким образом фигура начинает движение по центру игрового поля на оси икс.  Для получения значения по игреку локальные координаты делим на два и округляем в меньшую сторону.

Используя генератор случайных чисел, находим новые значения типа и цвета фигуры тетрамино и присваиваем эти значения переменным макета тетрамино.   

Задаём интервал обработки игровой логики 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();
		}
	}
}

В теле метода update(), измеряем интервал прошедшего времени frameRate, если интервал превышает установленные границы, обнуляем значение переменной подсчёта интервала времени. Если был задан вектор движения по иксу, проверяем на возможное столкновение фигуры тетрамино с препятствиями используя метод check(ch::x). Если препятствий нет перемещаем фигуру тетрамино, озвучиваем перемещение mus.play(3) и обнуляем вектор перемещения по иксу.

Аналогичную проверку делаем при движении тетрамино по игреку и перемещаем тетрамино. Если же движение по игреку невозможно, тогда проверяем выход за границы игрового поля и если это так, начинаем игру заново, иначе заполняем массив игрового поля цветом текущей фигуры тетрамино, используя координаты тетрамино, как индексы двумерного массива.

Следующий код определяет заполненную горизонталь игрового поля и удаляет её. Последней строчкой создаём новую фигуру на игровом поле newFigrois().

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

Методом tetDirection, меняем вектор движения фигуры по горизонтали.

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

В методе вращения фигуры rotate(), делаем предварительную проверку на столкновение фигуры с препятствиями check(ch::rotation), если таковых нет вращаем фигуру. 

Для вычисления новых координат фигуры при вращении используем формулу из линейной алгебры.

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)];
		}
	}
}

Метод speed(), изменяет интервал обработки игровой логики на десять, таким образом ускоряя перемещение тетрамино.

Метод  lineDead, уничтожает заполненную горизонталь, перезаписывая на её место в игровом поле предыдущие элементы тетрамино. В параметрах метод получает индекс заполненной горизонтали в массиве игрового поля.

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;
}

Метод check(), в зависимости от установленного параметра, делает проверку на  выход за границы игрового поля или столкновение с элементами фигур тетрамино в игровом поле, если столкновение или выход за границы присутствуют, возвращает значение false иначе 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);
	}
}

Метод draw(), если заданы координаты макета тетрамино рисует его в  графическом окне. Рисует игровое поле и фигуру тетрамино.

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;
}

Метод mustet() включает выключает фоновую музыку изменяя свойство playMus.

Метод getscore(), возвращает набранные игровые очки.

Метод getPositio() возвращает глобальные координаты центра вращения тетрамино.

Метод maket(), задаёт координаты макета тетрамино.

Класс GameEngine

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

В конструкторе задаём текстуру для игрового фона, шрифт и цвет текста, координаты макета тетрамино.

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();
			}

		}}}

В методе input(),  создаём обработку событий управления фигурой тетрамино стрелками на клавиатуре: движение влево, движение вправо, ускоренное падение фигуры вниз, поворот фигуры тетрамино.

При вращении колёсика мышки event.type == sf::Event::MouseButtonPressed фигура тетрамино ускоряет падение.  

В разделе обработки нажатия кнопок мыши event.type == sf::Event::MouseButtonPressed, при нажатии левой кнопки, проверяем положение курсора мыши. Если курсор находится в зоне расположения кнопки игрового меню, выполняется код её нажатия и соответствующий этой кнопке алгоритм действия. Если курсор находится в пределах игрового поля, перемещаем фигуру тетрамино по горизонтали в ту сторону с какой стороны от фигуры находится курсор мыши.

При отпускании левой кнопки мыши, если клавиши рестарт или выход были нажаты, их положение возвращается в исходное.

При нажатии правой кнопки, если курсор находится в игровом поле происходит вращение тетрамино. 

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();
		}
	}
}

В методе update(), если пауза выключена проигрывается игровая логика тетрамино.   При включении свойства выход, выход с приложения происходит с небольшой задержкой. 

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();
}

Метод draw() рисует в графическом окне: игровое поле с элементами тетрамино, фон игры, кнопки игрового интерфейса, текст подсчёта очков. 

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

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

Метод run() создаёт игровой цикл, включая в него выше описанные методы.

Более подробную инструкцию вы можете получить, посмотрев видео «SFML C++ Тетрис»

Клонировать репозиторий Тетрис

Телеграмм канал "Программирование игр С++/С#

Предыдущая тема

Комментарии (2)


  1. klimkinMD
    13.04.2023 07:55
    +1

    квадрат, "буква T", "буква L", "буква S", "буква Z" и "буква J"

    А где "буква "I"?

    И, ох уж эти "3d" кнопки и блоки...


  1. dyadyaSerezha
    13.04.2023 07:55

    Да простит меня автор, но тут плохо всё. И названия методов, и переменных, и эти бесконечные циклы от 0 до 4 вместо нормального для std::array, и почему-то два поинтера - на cube и window, и полное отсутствие стиля (форматирования), и т.д.