Skip to content

Instantly share code, notes, and snippets.

@Ruthenus
Last active May 22, 2025 21:45
Show Gist options
  • Select an option

  • Save Ruthenus/296de071dcf6f4fe6ae2c55f3d69725b to your computer and use it in GitHub Desktop.

Select an option

Save Ruthenus/296de071dcf6f4fe6ae2c55f3d69725b to your computer and use it in GitHub Desktop.
Final Project for Procedural C++ Course DRAFT
/*
ПРАКТИЧНИЙ ПРОЄКТ (чернетка)
з дисципліни "Програмування з використанням мови C++"
(процедурна частина)
Двовимірна гра "ТЕТРІС"
Виконав студент гр. СПР411
Комп'ютерної Академії IT STEP
Качуровський Р.Р.
Одеса 2025
НЕМАЄ РОБОТИ З ФАЙЛАМИ В ПРОЦЕДУРНОМУ СТИЛІ
БЕЗ ВИКОРИСТАННЯ ВЛАСНИХ БІБЛІОТЕК (тому що Gist)
РОЗПОЧАВ РОБОТУ НАД ФУНКЦІЄЮ main()
23 функції на чистову
*/
#include <windows.h>
#include <iostream>
#include <conio.h> // _getch(), _kbhit() — для роботи з клавіатурою
#include <string> // to_string, string()
#include <cstdlib> // srand(), rand()
#include <ctime> // time()
using namespace std;
// **ЗАГОЛОВКОВИЙ ФАЙЛ tetrograd_config.h**
/**
* Нижче подано "магічні" константи для налаштування гри, відомої під
* назвою "Стакан", що задаються на етапі компіляції. Числові літерали,
* такі як розміри ігрового поля в ретро-стилі, є достатньо зрозумілими
* з контексту, тому не винесені окремо. Використання константних
* змінних в іменованому просторі спрощує налаштування користувацького
* інтерфейсу та ігрової механіки, що є стандартною практикою для
* C++-програмістів, які працюють із низькорівневим кодом або уникають
* об'єктно-орієнтованого програмування (ООП).
*/
namespace TetrisConstants {
// Символи для відображення тетраміно та ігрового поля
// Як складовий ASCII-символ тетраміно вибрано малий квадрат,
// що забезпечує пропорційність фігури по горизонталі та вертикалі:
constexpr char TETROMINO_CHAR = static_cast<char>(254);
// Використання static_cast є безпечнішим, аніж (char), оскільки
// воно виконує явне приведення типів із гарантією сумісності
constexpr char GLASS_BOTTOM_CHAR = '='; // символ дна стакана
constexpr char GLASS_EDGE_CHAR = '|'; // символ бокової грані
// Параметри швидкості гри у мілісекундах
constexpr DWORD INITIAL_TICK = 500; // початковий інтервал падіння
constexpr DWORD MIN_TICK = 100; // мінімальний інтервал падіння
constexpr DWORD TICK_STEP = 50; // крок зменшення інтервалу на рівень
constexpr DWORD SLEEP_DELAY = 50; // затримка в ігровому циклі
constexpr DWORD ABOUT_AUTHOR = 37000;
// Загальна тривалість миготіння бонусного повідомлення, мс
constexpr DWORD TETRIS_FLASH_MS = 500; // повний цикл миготіння
// Відступи та позиції елементів інтерфейсу
constexpr short MARGIN_RIGHT = 8; // відступ від правого краю стакана
constexpr short MARGIN_LEFT = 32; // відступ від лівого краю стакана
constexpr short TETRIS_Y = 2; // Y-координата бонусного повідомлення
constexpr short NEXT_Y = 4; // Y-координата наступного тетраміно
constexpr short STATS_Y = 9; // Y-координата статистики гри
constexpr short CONTROLS_Y = 14; // Y-координата довідки з керування
}
// https://pvs-studio.com/en/blog/posts/cpp/0909/
// **ЗАГОЛОВКОВИЙ ФАЙЛ tetrograd_core.h**
// Перелік класичних тетраміно (пентаміно – в наступних версіях гри)
enum struct Type { I, O, T, J, L, S, Z };
// https://en.cppreference.com/w/cpp/language/enum
// Перелік кольорів для тетраміно та інтерфейсу (Week 16 Homework.cpp):
enum struct Color : unsigned char {
BLACK = 0,
BLUE = 1,
GREEN = 2,
AQUA = 3,
RED = 4,
PURPLE = 5,
YELLOW = 6,
WHITE = 7,
GRAY = 8,
LIGHT_BLUE = 9,
LIGHT_GREEN = 10,
LIGHT_AQUA = 11,
LIGHT_RED = 12,
LIGHT_PURPLE = 13,
LIGHT_YELLOW = 14,
BRIGHT_WHITE = 15
}; // номери кольорів задано явно для зручності запам'ятовування
// Коди зчитування клавіш керування:
enum struct Key : unsigned char {
// Стрілка вправо: рух праворуч
RIGHT = 77, // scan-код dec 77 / hex 0x4D, перед ним 224 / E0
// Стрілка вліво: рух ліворуч
LEFT = 75, // scan-код dec 75 / hex 0x4B, перед ним 224 / E0
// Стрілка вгору: обертання за годинниковою стрілкою
ROTATE = 72, // scan-код dec 72 / hex 48, перед ним 224 / E0
// Стрілка вниз: м'яке падіння
SOFT_DROP = 80, // scan-код dec 80 / hex 50, перед ним 224 / E0
// Space / пробіл: жорстке падіння
HARD_DROP = 32, // ASCII-код dec 32 / hex 0x20
// Escape / ESC: пауза гри
PAUSE = 27, // ASCII-код dec 27 / hex 0x1B
// F1: перезапуск гри
RESTART = 59, // код _getch() після F1: спочатку 0, потім 59 / 3B
};
// https://www.asciitable.com/ тільки видимі символи та службові коди!
// https://kbdlayout.info/kbdusx/scancodes апаратні коди від клавіатури
// Властивості тетраміно, доступних у версії гри "Тетріс" - "Тетроград":
struct Tetromino {
Type type; // тип тетраміно (I, O, T, J, L, S, Z)
COORD squares[4]; // координати 4 квадратів тетраміно
Color color; // колір тетраміно, у класичній грі невипадковий
COORD offset; // зсув позиції
};
/**
* typedef struct _COORD {
* SHORT X;
* SHORT Y;
* } COORD, *PCOORD;
*/
// Властивості ігрової структури
struct Glass {
short height; // висота стакана
short width; // ширина стакана
short startX; // X-координата початку стакана в консолі
short startY; // Y-координата початку стакана в консолі
};
// У цьому проєкті замість терміна "ігрове поле" використано поняття
// "ігрова структура" та "ігрова мозаїка".
// Структура ігрових досягнень
struct GameStats {
int score; // поточний рахунок
int highScore; // рекордний рахунок
short level; // поточний рівень гри
};
// **ЗАГОЛОВКОВИЙ ФАЙЛ tetrograd_functions.h**
// ПРОТОТИПИ ФУНКЦІЙ
// 1. Налаштування консолі та ініціалізація генератора випадкових чисел
void util_SetConsoleOptions(HANDLE h);
// 2. Отримання розмірів вікна консолі для перевірки мінімальних вимог
void util_GetConsoleSize(HANDLE h, short& outWidth, short& outHeight);
// 3. Створення двовимірного масиву кольорів ігрової мозаїки
Color** v4_CreateMosaic(const Glass& cup);
// 4. Створення тетраміно із заданим типом, кольором та зсувом позиції
Tetromino v4_CreateTetromino(Type type, const Glass& cup);
// 5. Завантаження рекордного рахунку з файлу
int util_LoadHighScore();
// 6. Ініціалізація ігрової статистики для обробника сигналів консолі
void util_InitHandlerStats(GameStats* stats, GameStats** activeStats);
// 7. Обробник сигналів консолі Windows (Ctrl+C, закриття вікна тощо)
BOOL WINAPI util_ConsoleHandler(DWORD signal);
// 8. Відображення текстової довідки про клавіші керування грою
void v4_DrawControls(HANDLE h, COORD ctrlPos);
// 9. Малювання в консолі ASCII-зображення ігрового "стакана"
void v4_DrawGlass(HANDLE h, const Glass& cup);
// 10. Малювання кольорової мозаїки гри
void v4_DrawMosaic(HANDLE h, Color** mosaic, const Glass& cup);
// 11. Малювання наступного тетраміно в заданій області
void v4_DrawNextTetromino(HANDLE h, const Tetromino& next, COORD nextPos);
// 12. Відображення статистики гри в консолі (рахунок, рівень, рекорд)
void v4_DrawStatistics(HANDLE h, const GameStats& stats, COORD statsPos);
// 13. Виведення заданого тексту у вказаній позиції з заданим кольором
void util_ShowText(HANDLE h, short x, short y, const string& text,
Color color);
// 14. Ефект стирання тетраміно пробілами для оновлення позиції
void v4_EraseTetromino(HANDLE h, const Tetromino& tm);
// 15. Перевірка можливості руху тетраміно без виходу за межі чи колізій
bool v4_CanMove(const Tetromino& tm, COORD delta, Color** mosaic,
const Glass& cup);
// 16. Обертання тетраміно на 90 градусів із механізмом "wall kicks"
void v4_RotateTetromino(Tetromino& tm, Color** mosaic, const Glass& cup);
// 17. Додавання тетраміно до ігрової мозаїки, його знерухомлення
void v4_FixTetromino(const Tetromino& tm, Color** mosaic, const Glass& cup);
// 18. Виявлення та очищення заповнених ліній, оновлення статистики
void v4_CheckLines(HANDLE h, Color** mosaic, const Glass& cup,
GameStats& stats, COORD bonusPos);
// 19. Перевірка, чи не настав кінець гри: заповнено верхній ряд стакана
// або неможливо розмістити нове тетраміно
bool v4_CheckGameOver(Color** mosaic, const Glass& cup,
const Tetromino& current);
// 20. Малювання поточного тетраміно в його поточній позиції
void v4_DrawCurrentTetromino(HANDLE h, const Tetromino& tm);
// 21. Виведення тетраміно в заданій позиції, ігноруючи його зміщення
void v4_ShowTetrominoAt(HANDLE h, const Tetromino& tm, COORD position);
// 22. Збереження рекордного рахунку у файл
void util_SaveHighScore(const GameStats& stats);
// 23. Виведення повідомлення про завершення гри з можливістю рестарту
void v4_DrawGameOver(HANDLE h, const Glass& cup, const GameStats& stats,
bool& restart);
// 24. Очищення динамічної пам'яті, виділеної для ігрової мозаїки
void v4_DeleteMosaic(Color** mosaic, short height);
// 25. Відновлення початкових налаштувань консолі наприкінці гри
void util_RestoreConsoleState(HANDLE h);
/**
* Порядок прототипів точно відображає послідовність викликів у main():
*
* Ініціалізація: util_SetConsoleOptions, util_GetConsoleSize,
* v4_CreateMosaic, v4_CreateTetromino, util_LoadHighScore,
* util_InitHandlerStats, util_ConsoleHandler, v4_DrawControls.
*
* Малювання початкового стану: v4_DrawGlass, v4_DrawMosaic,
* v4_DrawNextTetromino, v4_DrawStatistics.
*
* Ігровий цикл: util_ShowText, v4_EraseTetromino, v4_CanMove,
* v4_RotateTetromino, v4_FixTetromino, v4_CheckLines,
* v4_CheckGameOver, v4_DrawCurrentTetromino, v4_ShowTetrominoAt.
*
* Завершення гри: util_SaveHighScore, v4_DrawGameOver,
* v4_DeleteMosaic, util_RestoreConsoleState.
*/
// **ФАЙЛИ РЕАЛІЗАЦІЇ (.cpp)**
// Файл 1: tetrograd_game.cpp (для ігрових функцій v4_ версії 4.5)
// Файл 2: tetrograd_utils.cpp (для утиліт util_)
/**
* Функція створення нової фігури тетраміно, ініціалізації її
* властивостей та позиціювання в ігровій структурі.
* Параметри функції:
* – type: значення типу Type (перелік форм тетраміно);
* - cup: константне посилання на структуру Glass для центрування
* тетраміно.
* Return: Tetromino. Повертає структуру Tetromino, ініціалізовану з
* типом, кольором, координатами квадратів і зсувом.
* У структурі Tetromino є масив квадратів squares[4] типу COORD
* (статичний масив фіксованого розміру).
*/
Tetromino v4_CreateTetromino(Type type, const Glass& cup)
{ // Промодельовано взаємодію з функцією v4_RotateTetromino()
// Створення фігури структурного типу тетраміно:
Tetromino tm;
tm.type = type;
// Ініціалізація кольору та локальних координат квадратів
switch (type) { // залежно від типу тетраміно
case Type::I:
tm.color = Color::AQUA; // ретро-ефекти вимкнено ? LIGHT_AQUA
tm.squares[0] = { 0, 0 };
tm.squares[1] = { 1, 0 }; // центр обертання
tm.squares[2] = { 2, 0 };
tm.squares[3] = { 3, 0 };
break;
/*
| 0 1 2 3 4
-------------
0| 0 1 2 3
1|
2|
3|
4|
*/
case Type::O:
tm.color = Color::YELLOW; // ретро-ефекти вимкн. ? LIGHT_YELLOW
tm.squares[0] = { 0, 1 };
tm.squares[1] = { 1, 1 };
tm.squares[2] = { 0, 2 };
tm.squares[3] = { 1, 2 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 0 1
2| 2 3
3|
4|
*/
case Type::T:
tm.color = Color::PURPLE; // ретро-ефекти вимкн.? LIGHT_PURPLE
tm.squares[0] = { 0, 1 };
tm.squares[1] = { 1, 1 }; // центр обертання
tm.squares[2] = { 2, 1 };
tm.squares[3] = { 1, 2 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 0 1 2
2| 3
3|
4|
*/
case Type::J:
tm.color = Color::BLUE;
tm.squares[0] = { 0, 1 };
tm.squares[1] = { 0, 2 }; // центр обертання
tm.squares[2] = { 1, 2 };
tm.squares[3] = { 2, 2 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 0
2| 1 2 3
3|
4|
*/
case Type::L:
tm.color = Color::LIGHT_RED; // у класичній грі помаранчевий!
tm.squares[0] = { 2, 1 };
tm.squares[1] = { 2, 2 }; // центр обертання
tm.squares[2] = { 1, 2 };
tm.squares[3] = { 0, 2 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 0
2| 3 2 1
3|
4|
*/
case Type::S:
tm.color = Color::GREEN; // ретро-ефекти вимкнено? LIGHT_GREEN
tm.squares[0] = { 1, 2 };
tm.squares[1] = { 0, 2 }; // центр обертання
tm.squares[2] = { 0, 3 };
tm.squares[3] = { 1, 1 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 3
2| 1 0
3| 2
4|
*/
case Type::Z:
tm.color = Color::RED; // ретро-ефекти вимкнено ? LIGHT_RED
tm.squares[0] = { 0, 1 };
tm.squares[1] = { 1, 1 }; // центр обертання
tm.squares[2] = { 1, 2 };
tm.squares[3] = { 2, 2 };
break;
/*
| 0 1 2 3 4
-------------
0|
1| 0 1
2| 2 3
3|
4|
*/
}
// Обчислення ширини тетраміно по осі X
short minX = tm.squares[0].X; // найлівіша X-координата
short maxX = tm.squares[0].X; // найправіша X-координата
for (short i = 1; i < 4; i++) {
minX = min(minX, tm.squares[i].X);
maxX = max(maxX, tm.squares[i].X);
}
short tmWidth = maxX - minX + 1; // ширина тетраміно
// Центруємо тетраміно по горизонталі в ігровій структурі
tm.offset.X = cup.startX + (cup.width - tmWidth) / 2;
// Задаємо початкову позицію по вертикалі –
tm.offset.Y = 0; // початок падіння з верхнього краю консолі!
// Повертаємо ініціалізоване тетраміно
return tm;
}
/**
* Функція створення ігрової мозаїки для гри "Тетроград" – двовимірного
* масиву, який представляє стан т.зв. ігрового поля, де кожна клітинка
* зберігає колір блоку (чорний для порожньої клітинки).
* Параметри функції:
* - cup: константне посилання на структуру Glass (height, width,
* startX, startY) для визначення розмірів мозаїки.
* Return: Color**. Повертає покажчик на масив покажчиків (Color**),
* де кожен елемент — це динамічно виділений масив типу Color
* (рядок мозаїки).
* Динамічна пам'ять виділяється оператором new і потребує
* звільнення через v4_DeleteMosaic.
*/
Color** v4_CreateMosaic(const Glass& cup)
{
// Створюємо масив покажчиків для рядків ігрової структури
Color** mosaic = new Color * [cup.height];
// Ітеруємо по рядках двовимірного масиву mosaic:
for (short i = 0; i < cup.height; i++) {
// Виділяємо пам'ять для стовпців у поточному рядку
mosaic[i] = new Color[cup.width];
// Перевіряємо, чи вдало виділена пам'ять
if (!mosaic[i]) {
// Очищаємо пам’ять для всіх попередніх рядків перед виходом
for (short j = 0; j < i; j++) delete[] mosaic[j];
// Очищаємо масив покажчиків, щоб уникнути витоків пам'яті
delete[] mosaic;
return nullptr;
}
// Ініціалізуємо всі клітинки рядка як порожні:
for (short j = 0; j < cup.width; j++)
mosaic[i][j] = Color::BLACK;
}
// Повертаємо покажчик на створений двовимірний масив
return mosaic; // покажчик на покажчик кольорів
}
/**
* Функція звільнення пам'яті від ігрової мозаїки.
* Параметри функції:
* - mosaic: покажчик на двовимірний масив типу Color**
* (ігрова мозаїка).
* - height: значення типу short (висота мозаїки, збігається з
* cup.height) для ітерації по рядках.
* Return: немає (void). Функція звільняє пам'ять, виділену для мозаїки.
* Функція використовує delete[] для звільнення кожного рядка,
* а потім delete[] для масиву покажчиків.
*/
void v4_DeleteMosaic(Color** mosaic, short height)
{
// Ітеруємо по всіх рядках двовимірного масиву mosaic:
for (short i = 0; i < height; i++) // height = cup.height
// Звільняємо пам’ять, виділену для стовпців у поточному рядку
delete[] mosaic[i];
// Звільняємо пам’ять, виділену для масиву покажчиків на рядки
delete[] mosaic;
}
/**
* Функція обертання тетраміно на 90 градусів за годинниковою стрілкою
* навколо центру обертання. Використовує механізм "wall kicks" для
* уникнення колізій з боковими гранями, дном або іншими тетраміно.
* Якщо обертання неможливе через колізії, тетраміно залишається в
* початковому стані.
* Параметри функції:
* - tm: посилання на структуру Tetromino, що модифікується
* під час обертання;
* - mosaic: двовимірний масив Color** для перевірки колізій;
* - cup: структура Glass для перевірки меж стакана.
* Return: немає (void). Функція змінює координати та зсув tm в разі
* успішного обертання або залишає без змін, якщо обертання неможливе.
* Використовує посилання (Tetromino&) для зміни структури tm.
* Використовує локальний статичний масив kicks[7][2] для зсувів
* ("wall kicks") і тимчасовий масив newSquares[4] для нових координат.
*/
void v4_RotateTetromino(Tetromino& tm, Color** mosaic, const Glass& cup)
{ // Промодельовано взаємодію з функцією v4_CreateTetromino()
// Тетраміно O не обертається через свою симетрію (квадрат 2x2):
if (tm.type == Type::O) return;
// Визначаємо центр обертання (другий квадрат у масиві tm.squares):
COORD center = tm.squares[1]; // для всіх тетраміно (крім O)
// Для перевірки колізій створюємо масив для тимчасового зберігання
// нових координат квадратів тетраміно після обертання
COORD newSquares[4];
// Обчислюємо нові координати квадратів після обертання
// https://uk.wikipedia.org/wiki/Матриця_повороту
// [ 0 -1
// 1 0 ]
// на 90 градусів за годинниковою стрілкою:
for (short i = 0; i < 4; i++) {
short dx = tm.squares[i].X - center.X; // відносний зсув по X
short dy = tm.squares[i].Y - center.Y; // відносний зсув по Y
newSquares[i].X = center.X + dy; // нова координата X
newSquares[i].Y = center.Y - dx; // нова координата Y
}
// Застосовуємо масив зсувів ("wall kicks") для уникнення колізій.
// Кожна пара {dx, dy} визначає спробу зсуву тетраміно після обертання:
short kicks[7][2] = { // "wall kicks" оптимізовано для тетраміно I
{ 0, 0 }, // без зсуву
{ -1, 0 }, // зсув на 1 клітинку ліворуч
{ 1, 0 }, // зсув на 1 клітинку праворуч
{ -2, 0 }, // зсуви на 2 клітинку ліворуч
{ 2, 0 }, // зсуви на 2 клітинку праворуч
{ 0, 1 }, // зсув на 1 клітинку вгору
{ 0, -1 } // зсув на 1 клітинку вниз
};
// Перебираємо всі можливі зсуви з масиву kicks:
for (auto& k : kicks) {
// Ставимо прапорець для перевірки,
bool ok = true; // чи можливе обертання з поточним зсувом
// Перевіряємо кожен квадрат тетраміно після обертання:
for (short j = 0; j < 4; j++) {
// Абсолютні координати нового квадрата в ігровій структурі
short x = newSquares[j].X + tm.offset.X + k[0];
short y = newSquares[j].Y + tm.offset.Y + k[1];
// Перевіряємо, чи виходить квадрат за межі структури:
if (x < cup.startX || x >= cup.startX + cup.width ||
y >= cup.startY + cup.height) {
ok = false; // якщо поза межею, обертання неможливе
break;
}
// Перевіряємо, чи не перетинається квадрат із зайнятими
// клітинками в ігровій мозаїці. Якщо квадрат знаходиться в
// межах видимої структури і клітинка не порожня, є колізія.
if (y >= cup.startY &&
mosaic[y - cup.startY][x - cup.startX] != Color::BLACK) {
ok = false; // А якщо є колізія, обертання неможливе
break;
}
}
// Якщо обертання можливе з поточним зсувом (немає колізій),
// застосовуємо нові координати квадратів та оновлюємо зсуви:
if (ok) {
for (short j = 0; j < 4; j++) {
// Оновлюємо локальні координати квадратів
tm.squares[j] = newSquares[j];
}
tm.offset.X += k[0]; // оновлюємо зсув по X
tm.offset.Y += k[1]; // оновлюємо зсув по Y
return; // завершуємо функцію після успішного обертання
}
}
// Якщо жоден зсув не дозволив обертання (усі спроби призвели до
// колізій), функція завершується без змін у тетраміно
// (tm залишається в початковому стані).
}
/**
* Функція перевірки, чи може тетраміно переміститися на задану відстань
* {delta.X, delta.Y} без виходу за межі ігрової структури та без
* колізій з іншими тетраміно.
* Параметри функції:
* - tm: константне посилання на структуру Tetromino (тип, координати
* квадратів, колір, зсув);
* - delta: структура COORD із зсувами по X та Y (delta.X, delta.Y)
* для руху;
* - mosaic: покажчик на двовимірний масив типу Color**
* (ігрова мозаїка);
* - cup: константне посилання на структуру Glass для перевірки меж.
* Return: bool. Повертає true, якщо переміщення можливе без колізій,
* і false, якщо є колізії або вихід за межі стакана.
* У структурі Tetromino є статичний масив squares[4].
*/
bool v4_CanMove(const Tetromino& tm, COORD delta, Color** mosaic,
const Glass& cup)
{ // Пор. коментарі до функції v4_RotateTetromino()
for (short i = 0; i < 4; i++) {
// Нові абсолютні координати квадрата після переміщення:
short x = tm.squares[i].X + tm.offset.X + delta.X;
short y = tm.squares[i].Y + tm.offset.Y + delta.Y;
if (x < cup.startX || x >= cup.startX + cup.width ||
y >= cup.startY + cup.height) // Виходить за межі?
return false; // Тоді рух неможливий
// До речі, mosaic – видима частина ігрової структури cup
if (y >= cup.startY && mosaic[y - cup.startY][x - cup.startX]
!= Color::BLACK) // Є колізія?
return false; // Отже, рух неможливий
}
// Якщо жоден квадрат не викликав колізію або не вийшов за межі,
return true; // рух можливий!
}
/**
* Функція закріплення тетраміно в ігровій мозаїці, яка додає квадрати
* тетраміно до мозаїки. Після фіксації тетраміно стає частиною мозаїки
* та більше не може рухатися. Функція не повертає значення, але
* змінює масив ігрової мозаїки!
* Параметри функції:
* - tm: константне посилання на структуру Tetromino, квадрати якої
* фіксуються;
* - mosaic: покажчик на двовимірний масив типу Color** для додавання
* квадратів тетраміно;
* - cup: константне посилання на структуру Glass для координат у
* мозаїці.
* Return: немає (void). Модифікує mosaic, додаючи квадрати тетраміно.
* У структурі Tetromino є статичний масив squares[4].
*/
void v4_FixTetromino(const Tetromino& tm, Color** mosaic, const Glass& cup)
{
for (short i = 0; i < 4; i++) {
// Координати квадрата в системі координат масиву mosaic:
short x = tm.squares[i].X + tm.offset.X - cup.startX;
short y = tm.squares[i].Y + tm.offset.Y - cup.startY;
if (y >= 0 && y < cup.height && x >= 0 && x < cup.width)
// фіксація квадрата в мозаїці кольором тетраміно
mosaic[y][x] = tm.color;
}
}
/**
* Функція для стирання тетраміно з консолі й підготовки до
* перемалювання.
* Параметри функції:
* - h: дескриптор консолі для стирання тетраміно;
* - tm: константне посилання на структуру Tetromino, чиї квадрати
* замінюються пробілами.
* Return: немає (void). Стирає тетраміно з консолі.
* У структурі Tetromino є статичний масив squares[4].
*/
void v4_EraseTetromino(HANDLE h, const Tetromino& tm)
{
// Встановлення кодової сторінки 437 (OEM US) для коректного
// відображення псевдографічних символів
SetConsoleOutputCP(437); // додано під час тестування гри
// Встановлення чорного кольору тексту для "стирання"
SetConsoleTextAttribute(h, static_cast<WORD>(Color::BLACK));
// (збігається з фоном консолі)
for (short i = 0; i < 4; i++) {
COORD pos; // абсолютні координати квадрата тетраміно
pos.X = tm.squares[i].X + tm.offset.X;
pos.Y = tm.squares[i].Y + tm.offset.Y;
// Переміщення курсору в обчислену позицію
SetConsoleCursorPosition(h, pos);
// Виведення пробілу для заміни квадрата тетраміно
cout << ' '; // (ефект стирання)
}
}
/**
* Функція для відображення тетраміно на консолі у заданій позиції,
* ігноруючи його поточне зміщення. Функція не повертає значення, але
* виводить символи тетраміно на консоль.
* Параметри функції:
* - h: дескриптор консолі для виведення тетраміно;
* - tm: константне посилання на структуру Tetromino (форма, колір,
* координати);
* - position: структура COORD для базової позиції малювання;
* Return: немає (void). Малює тетраміно в заданій позиції.
* У структурі Tetromino є статичний масив squares[4].
*/
void v4_ShowTetrominoAt(HANDLE h, const Tetromino& tm, COORD position)
{
SetConsoleOutputCP(437); // додано під час тестування гри
// Встановлення кольору тексту для виведення тетраміно
SetConsoleTextAttribute(h, static_cast<WORD>(tm.color));
for (short i = 0; i < 4; i++) {
// Обчислення позиції символу тетраміно на консолі:
COORD coutPosChar;
coutPosChar.X = position.X + tm.squares[i].X;
coutPosChar.Y = position.Y + tm.squares[i].Y;
SetConsoleCursorPosition(h, coutPosChar);
cout << TetrisConstants::TETROMINO_CHAR; // символ тетраміно
}
}
/**
* Функція для малювання тетраміно на консолі в його поточній позиції,
* використовуючи зміщення. Функція викликає v4_ShowTetrominoAt(), що є
* частиною механізму малювання, із позицією, яка відповідає tm.offset.
* Параметри функції:
* - h: дескриптор консолі для виведення поточного тетраміно;
* - tm: константне посилання на структуру Tetromino із координатами
* та зсувом.
* Return: немає (void). Малює поточне тетраміно в його позиції.
*/
void v4_DrawCurrentTetromino(HANDLE h, const Tetromino& tm)
{
COORD position = { tm.offset.X, tm.offset.Y };
v4_ShowTetrominoAt(h, tm, position);
}
/**
* Допоміжна функція для виведення тексту в заданій позиції на консолі.
* Передає Color за значенням без константного посилання, оскільки
* базовий тип unsigned char (8 біт) достатній для всіх значень (0–15).
* Параметри функції:
* - h: дескриптор консолі для виведення тексту в консоль;
* - x: значення типу short (X-координата позиції);
* - y: значення типу short (Y-координата позиції);
* - text: константне посилання на об'єкт std::string для
* виведення тексту;
* - color: значення типу Color (перелік для кольору тексту).
* Return: немає (void). Функція виводить текст у консоль.
* Використовує константне посилання (const string&) для ефективної
* передачі рядка без копіювання.
*/
void util_ShowText(HANDLE h, short x, short y, const string& text,
Color color)
{
SetConsoleOutputCP(1251); // для коректного відображення кирилиці
// Приведення Color до WORD виконується тут, щоб спростити виклики
// функції.
SetConsoleTextAttribute(h, static_cast<WORD>(color));
// SetConsoleCursorPosition використовує COORD {x, y} для
// позиціонування курсора:
SetConsoleCursorPosition(h, { x, y });
cout << text; // виводимо заданий текст у заданій позиції {x, y}
}
/**
* Функція для малювання наступного тетраміно в заданій позиції на
* консолі. Очищає область, де буде намальовано тетраміно, і викликає
* функцію v4_ShowTetrominoAt() для малювання наступного тетраміно.
* Параметри функції:
* - h: дескриптор консолі для виведення наступного тетраміно;
* - next: константне посилання на структуру Tetromino
* (наступне тетраміно);
* - nextPos: структура COORD для позиції малювання.
* Return: Немає (void). Очищає область і малює наступне тетраміно.
* У next є статичний масив squares[4].
*/
void v4_DrawNextTetromino(HANDLE h, const Tetromino& next, COORD nextPos)
{
// Очищення області 5x5, де буде відображатися наступне тетраміно.
// Це необхідно, щоб видалити попереднє тетраміно або залишки символів.
for (short y = 0; y < 5; y++) {
for (short x = 0; x < 5; x++) {
// Виведення пробілу в заданій позиції з кольором BLACK,
// щоб забезпечити чистий фон
util_ShowText(h, nextPos.X + x, nextPos.Y + y, " ", Color::BLACK);
}
}
// Малювання наступного тетраміно у зазначеній позиції на консолі
v4_ShowTetrominoAt(h, next, nextPos);
}
/**
* Функція перевірки та очищення заповнених ліній, оновлення статистики
* (рахунок, рівень, рекорд), а також відображення бонусного привітання
* з ефектом миготіння на заданій позиції у разі очищення 4 ліній.
* Параметри функції:
* - h: дескриптор консолі для виведення бонусного повідомлення;
* - mosaic: покажчик на двовимірний масив типу Color** для
* очищення ліній;
* - cup: константне посилання на структуру Glass для розмірів мозаїки;
* - stats: посилання на структуру GameStats для оновлення статистики;
* - bonusPos: структура COORD для позиції повідомлення "TETRIS!".
* Return: немає (void). Модифікує mosaic (очищає лінії) і stats.
* Використовує динамічно виділений масив fullLines типу bool* для
* позначення заповнених ліній (звільняється через delete[]).
*/
void v4_CheckLines(HANDLE h, Color** mosaic, const Glass& cup,
GameStats& stats, COORD bonusPos)
{
short cleared = 0; // лічильник очищених ліній
// Масив для позначення заповнених ліній (ініціалізовано false):
bool* fullLines = new bool[cup.height](); // +1 динамічний масив
// Крок 1: Виявлення всіх заповнених ліній
for (short y = 0; y < cup.height; y++) {
bool full = true; // припустимо, що лінія заповнена
for (short x = 0; x < cup.width; x++) {
if (mosaic[y][x] == Color::BLACK) {
full = false;
break; // якщо є чорна клітинка, то лінія не заповнена
}
}
if (full) {
// позначити лінію в масиві як заповнену:
fullLines[y] = true; // (усі клітинки не чорні)
cleared++; // збільшити лічильник
}
}
// Крок 2: Зсув незнищених ліній вниз одним проходом
// Видаляємо заповнені лінії (fullLines[y] == true), зсуваючи
// незнищені вниз. Позиція для запису наступної незнищеної лінії:
short writeY = cup.height - 1;
// Починаємо з нижнього рядка, куди зсуваються незнищені лінії.
for (short readY = cup.height - 1; readY >= 0; readY--) {
// Перечитуємо рядки знизу вгору для зсуву ліній вниз
if (!fullLines[readY]) { // Якщо лінія не заповнена,
// копіюємо незнищену лінію до позиції writeY, але
// уникаємо копіювання, якщо позиції збігаються
if (writeY != readY) {
// Такий збіг можливий, приміром, для нижньої незнищеної
// лінії на першій ітерації або коли після заповнених
// ліній іде незнищена на потрібній позиції
for (short x = 0; x < cup.width; x++) {
// Копіюємо клітинки рядка readY у рядок writeY
mosaic[writeY][x] = mosaic[readY][x];
}
}
writeY--; // переходимо до наступної позиції для запису вище
}
// Заповнені лінії пропускаємо, видаляючи їх.
}
/*
У коді очищення заповнених ліній реалізується так:
а) пропуск заповнених ліній під час копіювання (вони не потрапляють
у нижню частину mosaic);
б) перезапис їх позицій незнищеними лініями (за допомогою зсуву);
в) очищення верхньої частини поля на кроці 3, що завершує видалення.
*/
// Крок 3: Очищення верхніх ліній (ті, що вище writeY)
if (writeY >= 0 && writeY < cup.height) {
for (short y = 0; y <= writeY; y++) {
for (short x = 0; x < cup.width; x++) {
mosaic[y][x] = Color::BLACK;
}
}
}
// Обов'язкове звільнення пам'яті
delete[] fullLines;
// Крок 4: Оновлення рахунку залежно від кількості очищених ліній
switch (cleared) {
case 1:
stats.score += 100;
break;
case 2:
stats.score += 300;
break;
case 3:
stats.score += 700;
break;
case 4:
stats.score += 1500;
// Початкове очищення області для бонусного повідомлення
util_ShowText(h, bonusPos.X, bonusPos.Y, " ", Color::BLACK);
// Тетрісом в багатьох версіях гри також називається дія, після
// якої зникає одразу 4 лінії. Нижче реалізовано відображення
// бонусного повідомлення "TETRIS!" за очищення 4 ліній.
for (short i = 0; i < 4; i++) { // миготіння чотири рази
util_ShowText(h, bonusPos.X, bonusPos.Y, "TETRIS!", i % 2 == 0 ?
Color::YELLOW : Color::BLUE);
Sleep(TetrisConstants::TETRIS_FLASH_MS / 2);
// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep
util_ShowText(h, bonusPos.X, bonusPos.Y, " ", Color::BLACK);
// Ще одна затримка на 250 мс перед наступною ітерацією
Sleep(TetrisConstants::TETRIS_FLASH_MS / 2);
}
// Виведення статичної емблеми після завершення миготіння
util_ShowText(h, bonusPos.X, bonusPos.Y, "TETROGRAD", Color::AQUA);
// видима до наступного очищення 4 ліній або рестарту гри
break;
default:
// Нічого не робимо, якщо не очищено жодної лінії
break;
}
// Крок 5: Оновлення рекордного рахунку
if (stats.score > stats.highScore) {
stats.highScore = stats.score;
}
// Крок 6: Оновлення рівня гри (наприклад, кожні 1000 очок)
stats.level = min(stats.score / 1000 + 1, 15);
// Додано обмеження максимального рівня
}
/**
* Функція порядкового відображення текстового інтерфейсу керування
* грою в консолі, використовуючи задану позицію.
* Параметри функції:
* - h: дескриптор консолі для виведення тексту керування;
* - ctrlPos: структура COORD для позиції інтерфейсу керування.
* Return: немає (void). Виводить текстовий інтерфейс керування.
* Масиви та посилання не використовуються, крім викликів util_ShowText.
*/
void v4_DrawControls(HANDLE h, COORD ctrlPos)
{
util_ShowText(h, ctrlPos.X, ctrlPos.Y,
" KACZUROWSKI TetraFly Studio", Color::BRIGHT_WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 2, " ^ Обертання",
Color::WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 3,
"< > Рух ліворуч / праворуч", Color::WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 4, " v М’яке падіння",
Color::WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 6, "Пробіл Жорстке падіння",
Color::WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 8, "Escape ПАВЗА",
Color::WHITE);
util_ShowText(h, ctrlPos.X, ctrlPos.Y + 10, "F1: Перезапуск гри",
Color::WHITE);
}
/**
* Функція порядкового відображення статистики гри (рахунок, рівень,
* рекорд) у консолі на заданій позиції.
* Параметри функції:
* - h: дескриптор консолі для виведення статистики;
* - stats: константне посилання на структуру GameStats;
* - statsPos: структура COORD для позиції статистики.
* Return: немає (void). Виводить статистику гри.
*/
void v4_DrawStatistics(HANDLE h, const GameStats& stats, COORD statsPos)
{
util_ShowText(h, statsPos.X, statsPos.Y,
"Рахунок: " + to_string(stats.score), Color::BRIGHT_WHITE);
util_ShowText(h, statsPos.X, statsPos.Y + 1,
"Рівень: " + to_string(stats.level), Color::BRIGHT_WHITE);
util_ShowText(h, statsPos.X, statsPos.Y + 2,
"Рекорд: " + to_string(stats.highScore), Color::BRIGHT_WHITE);
// https://habr.com/ru/articles/318962/
}
/**
* Функція малювання ASCII-зображення "стакана" в консолі гри "Тетріс",
* використовуючи символи для відображення бокових граней та дна.
* Параметри функції:
* - h: дескриптор консолі для виведення ASCII-символів граней і дна;
* - cup: константне посилання на структуру Glass для розмірів і
* позиції стакана.
* Return: немає(void). Малює стакан у консолі.
*/
void v4_DrawGlass(HANDLE h, const Glass& cup)
{
SetConsoleOutputCP(437); // додано під час тестування гри
// Малювання вертикальних стінок (бокових граней) стакана
// Ініціалізація рядка з двох ASCII-символів граней стакана
// та нуля-термінатора для коректної роботи з рядками
char line[] = { TetrisConstants::GLASS_EDGE_CHAR,
TetrisConstants::GLASS_EDGE_CHAR,
'\0'
};
for (short y = cup.startY; y < cup.startY + cup.height; y++) {
// Виведення лівої грані
util_ShowText(h, cup.startX - 2, y, line, Color::BRIGHT_WHITE);
// Виведення правої грані
util_ShowText(h, cup.startX + cup.width, y, line, Color::BRIGHT_WHITE);
}
// Оновлення рядка для двох ASCII-символів дна стакана
line[0] = TetrisConstants::GLASS_BOTTOM_CHAR;
line[1] = TetrisConstants::GLASS_BOTTOM_CHAR;
// Малювання нижньої частини (дна) стакана
for (short x = cup.startX - 2; x < cup.startX + cup.width + 1; x++) {
util_ShowText(h, x, cup.startY + cup.height, line,
Color::BRIGHT_WHITE);
}
}
/**
* Функція малювання мозаїки гри "Тетроград" всередині "стакана" в
* консолі, використовуючи ASCII-символи для заповнених і порожніх
* клітинок із відповідними кольорами, визначеними двовимірним масивом
* Параметри функції:
* - h: дескриптор консолі для виведення мозаїки;
* - mosaic: покажчик на двовимірний масив типу Color** із кольорами
* клітинок;
* - cup: константне посилання на структуру Glass для розмірів і
* позиції мозаїки.
* Return: немає (void).
*/
void v4_DrawMosaic(HANDLE h, Color** mosaic, const Glass& cup)
{
SetConsoleOutputCP(437); // додано під час тестування гри
// Створення динамічного рядка для буферизації ігрової мозаїки
char* row = new char[cup.width + 1]; // +1 для нуля-термінатора
if (row == nullptr) return; // перевірка на успішне виділення пам'яті
// Заповнення рядка пробілами
memset(row, ' ', cup.width); // установлює буфер на вказаний символ
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/memset-wmemset?view=msvc-170
row[cup.width] = '\0'; // додаємо нуль-термінатор
// Формування набору рядків висотою cup.height
for (short y = 0; y < cup.height; y++) {
// Формування поточного рядка мозаїки
for (short x = 0; x < cup.width; x++) {
// Заповнення рядка ASCII-символами: TETROMINO_CHAR для
// заповнених клітинок в мозаїці, пробіл для порожніх
if (mosaic[y][x] == Color::BLACK) row[x] = ' ';
else row[x] = TetrisConstants::TETROMINO_CHAR;
}
// Встановлення позиції для виведення рядка
COORD pos = { cup.startX, cup.startY + y };
SetConsoleCursorPosition(h, pos);
// Виведення рядка, зважаючи на кольори ігрової мозаїки
for (short x = 0; x < cup.width; x++) {
SetConsoleTextAttribute(h, static_cast<WORD>(mosaic[y][x]));
cout << row[x];
}
}
delete[] row; // звільнення пам'яті після використання рядка
}
/**
* Функція ініціалізації налаштувань консолі.
* Допоміжна функція, яка забезпечує коректну роботу консольної
* програми, зокрема різновиду гри "Тетріс" – "Тетроград". Функція
* відповідає за встановлення кодової сторінки для підтримки кирилиці,
* ініціалізацію генератора випадкових чисел, встановлення заголовка
* вікна консолі, приховування курсору для чистоти інтерфейсу та
* реєстрацію обробника системних сигналів.
* Параметри функції:
* - h - покажчик типу HANDLE (дескриптор консолі), отриманий через
* GetStdHandle(STD_OUTPUT_HANDLE).
* Return: немає (void). Функція змінює стан консолі через покажчик h
* та не повертає значення.
* Функція викликається на початку програми для підготовки консолі.
*/
void util_SetConsoleOptions(HANDLE h)
{
// Установлення кодової сторінки 1251 для підтримки кирилиці
SetConsoleOutputCP(1251);
// Ініціалізація генератора випадкових чисел на основі поточного часу
srand(static_cast<unsigned>(time(nullptr)));
// nullptr – початок епохи UNIX 1 січня 1970 року, 00:00:00 UTC
// Установлення заголовка вікна консолі
SetConsoleTitleA("TETROGRAD");
// якщо запускаєте .exe, а не з VS, працюватиме як очікується
// https://learn.microsoft.com/en-us/windows/console/setconsoletitle
// Налаштування параметрів курсору
CONSOLE_CURSOR_INFO cursor = { 100, FALSE };
// cursor.dwSize = 100; // розмір 100 (повне заповнення клітинки)
// cursor.bVisible = false; // видимість false (прихований)
// https://learn.microsoft.com/en-us/windows/console/console-cursor-info-str
// Застосування налаштувань для приховування курсору
SetConsoleCursorInfo(h, &cursor);
// https://learn.microsoft.com/en-us/windows/console/setconsolecursorinfo
//// Установлення обробника сигналів консолі
//SetConsoleCtrlHandler(util_ConsoleHandler, TRUE);
//// https://learn.microsoft.com/en-us/windows/console/setconsolectrlhandler
}
/**
* Обробник сигналів консолі - допоміжна функція гри "Тетроград", яка
* забезпечує коректне завершення програми в разі виникненні подій,
* таких як Ctrl+C, штатне закриття консолі або вимкнення системи.
* Дублює логіку збереження прогресу з main(): перевіряє, чи поточний
* рахунок (stats.score) перевищує найвищий рахунок (stats.highScore),
* і якщо так, оновлює highScore та зберігає рекорд у файл за допомогою
* util_SaveHighScore. Для системних подій (CTRL_LOGOFF_EVENT,
* CTRL_SHUTDOWN_EVENT) зберігає прогрес без перевірки, щоб гарантувати
* збереження. Відновлює стан консолі перед завершенням та повертає
* TRUE, якщо сигнал оброблено, або FALSE для невідомих сигналів.
* Параметри функції:
* - signal: код події (DWORD), що вказує на тип сигналу.
* Return: BOOL (TRUE або FALSE).
*/
BOOL WINAPI util_ConsoleHandler(DWORD signal)
// https://learn.microsoft.com/en-en/windows/console/handlerroutine
{
// Статичний покажчик на GameStats, оголошений усередині функції
static GameStats* activeStats = nullptr;
// Отримуємо дескриптор стандартного виводу консолі для відновлення
// стану консолі перед завершенням програми
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE) {
return FALSE; // якщо консоль недоступна
}
// Перевірка на nullptr перед використанням
if (!activeStats) {
util_RestoreConsoleState(hConsole);
return FALSE;
}
// Аналізуємо тип сигналу для вибору відповідної логіки обробки:
switch (signal) {
// Обробка сигналів, викликаних користувачем у консолі
case CTRL_C_EVENT: // сигнал Ctrl+C переривання поточної задачі
case CTRL_BREAK_EVENT: // сигнал Ctrl+Break, якщо є клавіша Break
// Дублюємо логіку збереження рекорду з main():
if (activeStats->score > activeStats->highScore) {
activeStats->highScore = activeStats->score;
util_SaveHighScore(*activeStats);
}
SetConsoleOutputCP(1251);
// Відновлюємо початковий стан консолі
util_RestoreConsoleState(hConsole);
// Завершуємо процес із кодом 0 (успішне завершення)
ExitProcess(0);
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess
break;
// Обробка сигналу закриття вікна консолі натисканням кнопки "X"
case CTRL_CLOSE_EVENT: // штатне закриття консолі користувачем
if (activeStats->score > activeStats->highScore) {
activeStats->highScore = activeStats->score;
util_SaveHighScore(*activeStats);
}
SetConsoleOutputCP(1251);
util_RestoreConsoleState(hConsole);
// Додаємо мінімальну затримку (0.5 секунди) для надійного
// завершення операцій збереження у файл. Відомо: Windows
// обмежує обробку CTRL_CLOSE_EVENT до ~5 секунд.
Sleep(500);
ExitProcess(0); // відтак завершуємо процес
break;
// Обробка системних подій: вихід, вимкнення, перезавантаження
case CTRL_LOGOFF_EVENT: // користувач виходить із системи
case CTRL_SHUTDOWN_EVENT: // вимкнення або перезавантаження системи
// Для системних подій зберігаємо прогрес без перевірки,
// щоб гарантувати збереження даних, навіть якщо рахунок
// не перевищує найвищий. На жаль, досягнений рекорд пропаде.
util_SaveHighScore(*activeStats);
SetConsoleOutputCP(1251);
util_RestoreConsoleState(hConsole);
ExitProcess(0);
break;
// Обробка невідомих сигналів
default:
// Повертаємо FALSE, щоб система могла передати сигнал іншим
// обробникам або виконати дію за замовчуванням
return FALSE;
}
// Повертаємо TRUE, щоб повідомити систему, що сигнал успішно
// оброблено, запобігаючи подальшій обробці іншими обробниками
return TRUE;
}
/**
* Допоміжна функція ініціалізації статистики для обробника.
* Ініціалізує покажчик на структуру GameStats, присвоюючи йому адресу
* переданої структури stats. Запобігає витокам пам'яті.
* Параметри функції:
* - stats: покажчик на структуру GameStats, що містить статистику гри;
* - activeStats: подвійний покажчик на структуру GameStats, що
* буде оновлено, зберігаючи адресу stats для подальшого використання.
*/
void util_InitHandlerStats(GameStats* stats, GameStats** activeStats)
{
// Запобігання використанню нульових покажчиків
if (!stats || !activeStats) return;
// Пряме присвоєння через покажчик на покажчик
*activeStats = stats;
// Тепер activeStats вказує на ту ж область пам’яті, що і stats.
// Операція дозволяє змінити значення покажчика поза межами функції,
// що корисно для керування станом гри.
}
/**
* Допоміжна функція відновлення стану консолі.
* Функція скидає налаштування консолі, встановлені раніше, наприклад,
* у util_SetConsoleOptions. Вона очищає екран, відновлює стандартний
* колір тексту, параметри курсора, кодову сторінку й заголовок консолі.
* Використовується для повернення консолі до початкового стану після
* виконання програми або певного режиму.
* Параметри функції:
* - h: дескриптор консолі, отриманий, наприклад, за допомогою
* GetStdHandle(STD_OUTPUT_HANDLE).
* Return: немає (void).
*/
void util_RestoreConsoleState(HANDLE h)
{
// Відновлення стандартного кольору тексту (яскраво-білий)
SetConsoleTextAttribute(h, static_cast<WORD>(Color::BRIGHT_WHITE));
// Відновлення параметрів курсора -
CONSOLE_CURSOR_INFO cursor = { 25, TRUE };
// стандартний розмір 25, видимий курсор
SetConsoleCursorInfo(h, &cursor);
// Отримання інформації про буфер екрана консолі
CONSOLE_SCREEN_BUFFER_INFO csbi;
// https://learn.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
GetConsoleScreenBufferInfo(h, &csbi);
// https://learn.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
// Обчислення розміру буфера (ширина * висота)
DWORD size = csbi.dwSize.X * csbi.dwSize.Y;
// Очищення екрана: заповнення пробілами
DWORD written; // lpNumberOfCharsWritten, lpNumberOfAttrsWritten
FillConsoleOutputCharacter(h, ' ', size, { 0, 0 }, &written);
// https://learn.microsoft.com/en-us/windows/console/fillconsoleoutputcharacter
// Відновлення атрибутів (кольорів) для всіх символів у буфері
FillConsoleOutputAttribute(h, csbi.wAttributes, size, { 0, 0 }, &written);
// https://learn.microsoft.com/en-us/windows/console/fillconsoleoutputattribute
// Переміщення курсора в початкову позицію
SetConsoleCursorPosition(h, { 0, 0 });
// Відновлення кодової сторінки до стандартної системної
SetConsoleOutputCP(GetOEMCP());
SetConsoleCP(GetOEMCP());
// https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getoemcp
// Відновлення стандартного заголовка консолі
SetConsoleTitleA("");
}
/**
* Допоміжна функція, що отримує розміри вікна консолі.
* Параметри функції:
* - h: дескриптор консолі;
* - outWidth: посилання на змінну типу short, в яку буде записана
* ширина консолі в символах;
* - outHeight: посилання на змінну типу short, в яку буде записана
* висота консолі в символах.
* Return: немає (void).
* Записує результат в параметри outWidth та outHeight.
*/
void util_GetConsoleSize(HANDLE h, short& outWidth, short& outHeight)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
// Отримуємо інформацію про буфер екрана консолі
GetConsoleScreenBufferInfo(h, &csbi);
// Обчислюємо ширину за правим і лівим краями вікна
outWidth = csbi.srWindow.Right - csbi.srWindow.Left + 1;
// Обчислюємо висоту за нижнім і верхнім краями вікна
outHeight = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
/**
* Перевірка завершення гри
* Функція перевіряє, чи закінчилася гра, аналізуючи два умови:
* 1. Чи є заповнені клітинки у верхньому рядку мозаїки
* (блоки досягли верху).
* 2. Чи може нове тетраміно бути розміщене в початковій позиції
* без колізій.
* Додано параметр current: поточне тетраміно, яке перевіряється
* на можливість розміщення в початковій позиції.
* Повертає true, якщо гра закінчилася (верхній ряд заповнений або
* нове тетраміно не може бути розміщене).
* Повертає false, якщо гра може продовжуватися.
*/
bool v4_CheckGameOver(Color** mosaic, const Glass& cup,
const Tetromino& current)
{
// Чи є заповнені клітинки у верхньому рядку?
for (short x = 0; x < cup.width; x++) {
if (mosaic[0][x] != Color::BLACK) {
return true; // блоки досягли верхнього краю стакана
}
}
// Чи може поточне тетраміно бути розміщене в початковій позиції?
if (!v4_CanMove(current, { 0, 0 }, mosaic, cup)) {
return true; // неможливо розмістити нове тетраміно
}
// Якщо обидві перевірки пройшли, гра продовжується
return false;
}
/**
* Функція відображення повідомлення про завершення гри з можливістю
* вибору дії.
* Параметри функції:
* - h: дескриптор консолі для виведення повідомлення;
* - cup: константне посилання на структуру Glass для позиціонування;
* - stats: константне посилання на структуру GameStats для
* відображення рахунку;
* - restart: посилання на bool, яке встановлюється в true для
* перезапуску гри (F1) або false для виходу з гри (ESC/інше).
* Return: немає (void). Модифікує restart для вказівки дії.
*/
void v4_DrawGameOver(HANDLE h, const Glass& cup, const GameStats& stats,
bool& restart)
{
// Розташування повідомлення "GAME OVER" над стаканом
short msgY = cup.startY * 2 / 3; // визначення координат
// Ліворуч від стакана з відступом
short msgX = cup.startX - TetrisConstants::MARGIN_LEFT;
if (msgX < 0) msgX = 0; // запобігаємо від’ємним координатам
// Очищення області перед виведенням повідомлення
for (short y = msgY; y <= msgY + 10; y++) {
util_ShowText(h, msgX, y, string(24, ' '), Color::BLACK);
}
// Відображення повідомлення про завершення гри та статистики
util_ShowText(h, msgX, msgY, " GAME OVER ", Color::RED);
util_ShowText(h, msgX, msgY + 2, "Рахунок: " + to_string(stats.score),
Color::YELLOW);
util_ShowText(h, msgX, msgY + 3, "Рекорд: " + to_string(stats.highScore),
Color::YELLOW);
util_ShowText(h, msgX, msgY + 5, "F1: Рестарт | ESC: Вихід", Color::GREEN);
// Очікування вибору гравця
restart = false; // за замовчуванням не перезапуск, а вихід з гри
while (true) {
if (_kbhit()) { // перевірка, чи була натиснута клавіша
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/kbhit?view=msvc-170
int key = _getch(); // отримання коду натиснутої клавіші
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getch-getwch?view=msvc-170
// Обробка спеціальних клавіш
if (key == 0 || key == 224) key = _getch();
switch (static_cast<Key>(key)) {
case Key::RESTART: // якщо натиснуто F1,
restart = true; // встановлюємо перезапуск гри
return;
case Key::PAUSE: // ESC для виходу (тривалої паузи)
restart = false;
return;
default: // будь-яка інша клавіша також для виходу
// Виведення інформації про автора
util_ShowText(h, msgX, msgY + 8,
"Розробив студент IT STEP Academy", Color::RED);
util_ShowText(h, msgX, msgY + 10,
" Руслан КАЧУРОВСЬКИЙ", Color::RED);
Sleep(TetrisConstants::ABOUT_AUTHOR);
restart = false; // встановлюємо вихід із гри
return;
}
}
// Затримка перед наступною перевіркою вводу
Sleep(TetrisConstants::SLEEP_DELAY);
}
}
// Завантаження рекордного рахунку
int util_LoadHighScore() // тема наступного домашнього завдання
{
// Функція залишена для сумісності, але не виконує жодних дій
return 0;
}
// Збереження рекордного рахунку
void util_SaveHighScore(const GameStats& stats) // тема наступного ДЗ
{
// Функція залишена для сумісності, але не виконує жодних дій
}
// **ОСНОВНИЙ ФАЙЛ main.cpp**
/**
* Основна функція гри "Тетріс" - "Тетроград". Ініціалізує консоль,
* перевіряє її розмір, створює ігрові об’єкти (стакан, мозаїку,
* статистику, тетраміно), виконує основний ігровий цикл із обробкою
* введення, руху тетраміно, паузи, перезапуску та завершення гри.
* Уся логіка, включаючи паузу та рестарт, реалізована в межах цієї
* функції без використання додаткових функцій для ігрового циклу.
* Return: int. Повертає 0 у разі успішного виконання, 1 у разі помилок.
*/
int main()
{
// Отримання дескриптора консолі для виведення графіки та тексту
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
if (h == INVALID_HANDLE_VALUE) {
cerr << "Помилка отримання дескриптора консолі!\n";
return 1;
}
// Налаштування консолі, ініціалізація генератора випадкових чисел
util_SetConsoleOptions(h);
// Перевірка розмірів консолі
short consoleW, consoleH;
util_GetConsoleSize(h, consoleW, consoleH);
// cout << consoleW << "\n";
// cout << consoleH;
if (consoleW < 100 || consoleH < 30) {
util_RestoreConsoleState(h);
// cout << consoleW << "\n";
// cout << consoleH;
cout << "\n Recommended parameters for Your Console\n\n"
<< " Startup\n"
<< " Launch size\n"
<< " Columns 150\n"
<< " Rows 50\n"
<< " Launch parameters\n"
<< " Launch mode Maximized\n"
<< " Launch position Let Windows decide\n"
<< " Center on launch On\n\n"
<< " Defaults > Appearance\n"
<< " Color scheme Campbell\n"
<< " Font face Lucida Console\n"
<< " Font size 18\n"
<< " Font weight Normal\n"
<< " Retro terminal effects On\n";
Sleep(120000); // 2 хвилини, щоб переписати
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment