Last active
May 22, 2025 21:45
-
-
Save Ruthenus/296de071dcf6f4fe6ae2c55f3d69725b to your computer and use it in GitHub Desktop.
Final Project for Procedural C++ Course DRAFT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| ПРАКТИЧНИЙ ПРОЄКТ (чернетка) | |
| з дисципліни "Програмування з використанням мови 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