From f85678dc9e450e90542d28f22b8cdfbfa434fd9a Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 11:44:40 +0300 Subject: [PATCH 01/12] some init commit renderer with color palette --- lab1/CMakeLists.txt | 21 +++++++ lab1/include/BonusType.hpp | 5 ++ lab1/include/Colors.hpp | 12 ++++ lab1/include/Game.hpp | 30 +++++++++ lab1/include/Gem.hpp | 15 +++++ lab1/src/Game.cpp | 121 +++++++++++++++++++++++++++++++++++++ lab1/src/main.cpp | 11 ++++ 7 files changed, 215 insertions(+) create mode 100644 lab1/CMakeLists.txt create mode 100644 lab1/include/BonusType.hpp create mode 100644 lab1/include/Colors.hpp create mode 100644 lab1/include/Game.hpp create mode 100644 lab1/include/Gem.hpp create mode 100644 lab1/src/Game.cpp create mode 100644 lab1/src/main.cpp diff --git a/lab1/CMakeLists.txt b/lab1/CMakeLists.txt new file mode 100644 index 0000000..fd0e90f --- /dev/null +++ b/lab1/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.15) +project(gems) + +set(CMAKE_CXX_STANDARD 17) + +include(FetchContent) +FetchContent_Declare( + SDL3 + GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" + GIT_TAG "main" + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(SDL3) + +add_executable(gems + src/main.cpp + src/Game.cpp +) + +target_include_directories(gems PRIVATE include) +target_link_libraries(gems PRIVATE SDL3::SDL3) \ No newline at end of file diff --git a/lab1/include/BonusType.hpp b/lab1/include/BonusType.hpp new file mode 100644 index 0000000..7feed9f --- /dev/null +++ b/lab1/include/BonusType.hpp @@ -0,0 +1,5 @@ +#pragma once + +enum BonusType { + VOID, RECOLOR, BOMB +}; diff --git a/lab1/include/Colors.hpp b/lab1/include/Colors.hpp new file mode 100644 index 0000000..f1729fa --- /dev/null +++ b/lab1/include/Colors.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "SDL3/SDL_pixels.h" + +constexpr SDL_Color WHEAT{245, 222, 179, 255}; +constexpr SDL_Color MAROON{128, 0, 0, 255}; +constexpr SDL_Color ORANGE{255, 140, 0, 255}; +constexpr SDL_Color OLIVE{128, 128, 0, 255}; +constexpr SDL_Color LIGHT_PINK{255, 182, 193, 255}; +constexpr SDL_Color SLATE_GRAY{112, 128, 144, 255}; + +constexpr SDL_Color PALETTE[] = {WHEAT, MAROON, ORANGE, OLIVE, LIGHT_PINK, SLATE_GRAY}; +constexpr int PALETTE_SIZE = sizeof(PALETTE) / sizeof(SDL_Color); diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp new file mode 100644 index 0000000..4658238 --- /dev/null +++ b/lab1/include/Game.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +#include "Gem.hpp" +#include "SDL3/SDL_render.h" + +class Game { +public: + Game(); + + bool Initialize(); + void Run(); + ~Game() = default; + +private: + SDL_Window* window = nullptr; + SDL_Renderer* renderer = nullptr; + std::vector field; + bool shouldExit = false; + SDL_FRect selectedGem = {0,0,0,0}; + + void InitField(); + + void Render() const; + void Update(); + void CheckGems(); + void SpawnBonuses(); + void ProcessInput(); + void HandleClick(const SDL_MouseButtonEvent& e); +}; diff --git a/lab1/include/Gem.hpp b/lab1/include/Gem.hpp new file mode 100644 index 0000000..66ec901 --- /dev/null +++ b/lab1/include/Gem.hpp @@ -0,0 +1,15 @@ +#ifndef GEM_HPP +#define GEM_HPP +#include "BonusType.hpp" +#include "SDL3/SDL_pixels.h" + +struct Gem { + explicit Gem(SDL_Color c) : color(c) {}; + Gem() = default; + + BonusType bonus = VOID; + SDL_Color color{255, 255, 255, 255}; +}; + + +#endif //GEM_HPP diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp new file mode 100644 index 0000000..1f31ff7 --- /dev/null +++ b/lab1/src/Game.cpp @@ -0,0 +1,121 @@ +#include "Game.hpp" + +#include + +#include "Colors.hpp" +#include "SDL3/SDL_init.h" +#include "SDL3/SDL_log.h" + +constexpr int SCREEN_WIDTH = 800; +constexpr int SCREEN_HEIGHT = 600; +constexpr int GEM_WIDTH = 40; +constexpr int GEM_HEIGHT = 40; +constexpr int TOTAL_GEMS = SCREEN_WIDTH * SCREEN_HEIGHT / GEM_WIDTH / GEM_HEIGHT; + +Game::Game() { + field.reserve(TOTAL_GEMS); +} + +bool Game::Initialize() { + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Создание окна + window = SDL_CreateWindow("Gems", + SCREEN_WIDTH, + SCREEN_HEIGHT, + SDL_WINDOW_RESIZABLE); + if (!window) { + SDL_Log("Failed to create window: %s", SDL_GetError()); + SDL_Quit(); + return false; + } + + // Создание рендерера + renderer = SDL_CreateRenderer(window, + nullptr); + if (!renderer) { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return false; + } + + InitField(); + + return true; +} + +void Game::InitField() { + for (int i = 0; i < TOTAL_GEMS; i++) { + Gem gem; + + gem.color = PALETTE[i % PALETTE_SIZE]; + + field.emplace_back(gem); + } +} + + +void Game::Run() { + while (!shouldExit) { + ProcessInput(); + Update(); + Render(); + } +} + +void Game::ProcessInput() { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + shouldExit = true; + } + else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + HandleClick(e.button); + } + } +} + +void Game::HandleClick(const SDL_MouseButtonEvent& e) { + if (e.button != SDL_BUTTON_LEFT || !e.down) return; + + const float x = e.x; + const float y = e.y; + + +} + + +void Game::Render() const { + //count of gems in 1 row + constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + for (int i = 0; i < field.size(); i++) { + int gemX = i % LINE_LENGTH; + int gemY = i / LINE_LENGTH; + + SDL_FRect gem{ + static_cast(gemX * GEM_WIDTH), static_cast(gemY * GEM_HEIGHT), + GEM_WIDTH, GEM_HEIGHT + }; + + const SDL_Color& color = field[i].color; + + SDL_SetRenderDrawColor(renderer, + color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer, &gem); + } + + SDL_RenderPresent(renderer); +} + +void Game::CheckGems() {} + +void Game::Update() {} + +void Game::SpawnBonuses() {} diff --git a/lab1/src/main.cpp b/lab1/src/main.cpp new file mode 100644 index 0000000..7f23f68 --- /dev/null +++ b/lab1/src/main.cpp @@ -0,0 +1,11 @@ +#include "Game.hpp" + +int main() { + Game gems{}; + + if (gems.Initialize()) { + gems.Run(); + } + + return 0; +} From 7914c88da925e98a2b9241f91730ebb4011cb3a1 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 12:02:34 +0300 Subject: [PATCH 02/12] added gem selection logic added random field generation --- lab1/include/Game.hpp | 2 +- lab1/src/Game.cpp | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 4658238..5799659 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -17,7 +17,7 @@ class Game { SDL_Renderer* renderer = nullptr; std::vector field; bool shouldExit = false; - SDL_FRect selectedGem = {0,0,0,0}; + size_t selectedGem = -1; void InitField(); diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 1f31ff7..afce38f 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -1,6 +1,8 @@ #include "Game.hpp" #include +#include +#include #include "Colors.hpp" #include "SDL3/SDL_init.h" @@ -11,6 +13,8 @@ constexpr int SCREEN_HEIGHT = 600; constexpr int GEM_WIDTH = 40; constexpr int GEM_HEIGHT = 40; constexpr int TOTAL_GEMS = SCREEN_WIDTH * SCREEN_HEIGHT / GEM_WIDTH / GEM_HEIGHT; +constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; + Game::Game() { field.reserve(TOTAL_GEMS); @@ -44,15 +48,25 @@ bool Game::Initialize() { } InitField(); + SDL_SetRenderDrawBlendMode(renderer,SDL_BLENDMODE_BLEND); return true; } +template +int getRandomInt() { + static std::random_device rd; // Seed for random number engine + static std::mt19937 gen(rd()); // Mersenne Twister engine + + static std::uniform_int_distribution dist(start, end); + return dist(gen); +} + void Game::InitField() { for (int i = 0; i < TOTAL_GEMS; i++) { Gem gem; - gem.color = PALETTE[i % PALETTE_SIZE]; + gem.color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; field.emplace_back(gem); } @@ -72,8 +86,7 @@ void Game::ProcessInput() { while (SDL_PollEvent(&e)) { if (e.type == SDL_EVENT_QUIT) { shouldExit = true; - } - else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { HandleClick(e.button); } } @@ -82,16 +95,14 @@ void Game::ProcessInput() { void Game::HandleClick(const SDL_MouseButtonEvent& e) { if (e.button != SDL_BUTTON_LEFT || !e.down) return; - const float x = e.x; - const float y = e.y; - + const int x = e.x / GEM_WIDTH; + const int y = e.y / GEM_HEIGHT; + selectedGem = x + y * LINE_LENGTH; } void Game::Render() const { - //count of gems in 1 row - constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); @@ -111,6 +122,21 @@ void Game::Render() const { SDL_RenderFillRect(renderer, &gem); } + //outline selected gem + if (selectedGem != -1) { + size_t gemX = selectedGem % LINE_LENGTH; + size_t gemY = selectedGem / LINE_LENGTH; + SDL_FRect gem{ + static_cast(gemX * GEM_WIDTH), static_cast(gemY * GEM_HEIGHT), + GEM_WIDTH, GEM_HEIGHT + }; + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 96); + SDL_RenderFillRect(renderer, &gem); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderRect(renderer, &gem); + } + SDL_RenderPresent(renderer); } From 55172b988b747034b17cd39966fc27274856c64e Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 12:10:36 +0300 Subject: [PATCH 03/12] Added neighbourhood check && gem change logic --- lab1/src/Game.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index afce38f..7613cc7 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -86,19 +86,40 @@ void Game::ProcessInput() { while (SDL_PollEvent(&e)) { if (e.type == SDL_EVENT_QUIT) { shouldExit = true; - } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + } + else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { HandleClick(e.button); } } } +bool isNeighbours(size_t a, size_t b) { + if (a - b == 1 || a - b == -1) return true; + if (a - b == LINE_LENGTH || a - b == -LINE_LENGTH) return true; + return false; +} + void Game::HandleClick(const SDL_MouseButtonEvent& e) { - if (e.button != SDL_BUTTON_LEFT || !e.down) return; + if (e.button != SDL_BUTTON_LEFT || !e.down) { + selectedGem = -1; + return; + } const int x = e.x / GEM_WIDTH; const int y = e.y / GEM_HEIGHT; - selectedGem = x + y * LINE_LENGTH; + size_t clicked = x + y * LINE_LENGTH; + if (selectedGem != -1 && isNeighbours(selectedGem, clicked)) { + //change gems + auto tmp = field[selectedGem]; + field[selectedGem] = field[clicked]; + field[clicked] = tmp; + + //unselect + selectedGem = -1; + } + //otherwise, change selection to clicked + else selectedGem = clicked; } From b62be698662ef7e4a4d1e0c7018b01ed620cc435 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 12:37:53 +0300 Subject: [PATCH 04/12] changed initialization logic to prevent triplet spawning --- lab1/include/Game.hpp | 2 ++ lab1/src/Game.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 5799659..a4a5d65 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -20,6 +20,8 @@ class Game { size_t selectedGem = -1; void InitField(); + bool CheckTriplets() const; + bool canPlace(size_t i, SDL_Color color); void Render() const; void Update(); diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 7613cc7..2b4c448 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -62,12 +62,48 @@ int getRandomInt() { return dist(gen); } +bool operator==(const SDL_Color& a, const SDL_Color& b) { + if (a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a) return false; + return true; +} + +bool Game::CheckTriplets() const { + for (int i = 2; i < TOTAL_GEMS; i++) { + const auto& src = field[i].color; + + if (i >= 2 * LINE_LENGTH) + if (field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) + return true; + + if (i % LINE_LENGTH >= 2) + if (field[i - 1].color == src && field[i - 2].color == src) + return true; + } + + return false; +} + +bool Game::canPlace(size_t i, SDL_Color src) { + if (i >= 2 * LINE_LENGTH) + if (field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) + return false; + + if (i % LINE_LENGTH >= 2) + if (field[i - 1].color == src && field[i - 2].color == src) + return false; + return true; +} + + void Game::InitField() { for (int i = 0; i < TOTAL_GEMS; i++) { Gem gem; + auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + while (!canPlace(i, color)) { + color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + } - gem.color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; - + gem.color = color; field.emplace_back(gem); } } @@ -118,7 +154,7 @@ void Game::HandleClick(const SDL_MouseButtonEvent& e) { //unselect selectedGem = -1; } - //otherwise, change selection to clicked + //otherwise, change selection to clicked gem else selectedGem = clicked; } From 582b8f1554811412045d0590f87aaa4978b2034f Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 12:57:24 +0300 Subject: [PATCH 05/12] added triplet destruction logic --- lab1/include/Game.hpp | 5 ++-- lab1/src/Game.cpp | 65 ++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index a4a5d65..354b7c0 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -20,12 +20,11 @@ class Game { size_t selectedGem = -1; void InitField(); - bool CheckTriplets() const; - bool canPlace(size_t i, SDL_Color color); + bool canPlace(size_t i, SDL_Color color) const; void Render() const; void Update(); - void CheckGems(); + void CheckTriplets(); void SpawnBonuses(); void ProcessInput(); void HandleClick(const SDL_MouseButtonEvent& e); diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 2b4c448..11c73e6 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -67,23 +67,7 @@ bool operator==(const SDL_Color& a, const SDL_Color& b) { return true; } -bool Game::CheckTriplets() const { - for (int i = 2; i < TOTAL_GEMS; i++) { - const auto& src = field[i].color; - - if (i >= 2 * LINE_LENGTH) - if (field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) - return true; - - if (i % LINE_LENGTH >= 2) - if (field[i - 1].color == src && field[i - 2].color == src) - return true; - } - - return false; -} - -bool Game::canPlace(size_t i, SDL_Color src) { +bool Game::canPlace(size_t i, SDL_Color src) const { if (i >= 2 * LINE_LENGTH) if (field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) return false; @@ -197,8 +181,51 @@ void Game::Render() const { SDL_RenderPresent(renderer); } -void Game::CheckGems() {} +void Game::CheckTriplets() { + for (int i = 2; i < TOTAL_GEMS; i++) { + const auto& src = field[i].color; -void Game::Update() {} + if (i >= 2 * LINE_LENGTH && + field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) { + //located vertical triplet + + //move all gems in vertical downwards + int j = i; + for (; j > 2 * LINE_LENGTH; j -= LINE_LENGTH) { + field[j] = field[j - 3 * LINE_LENGTH]; + } + + for (; j > 0; j -= LINE_LENGTH) { + //generate new gems + Gem gem; + auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + + gem.color = color; + field[j] = gem; + } + } + + if (i % LINE_LENGTH >= 2 && + field[i - 1].color == src && field[i - 2].color == src) { + //located horizontal triplet + //move all gems from upwards + for (int x = i; x > i - 3; x--) { + int j = x; + for (; j > LINE_LENGTH; j -= LINE_LENGTH) { + field[j] = field[j - LINE_LENGTH]; + } + Gem gem; + auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + + gem.color = color; + field[j] = gem; + } + } + } +} + +void Game::Update() { + CheckTriplets(); +} void Game::SpawnBonuses() {} From 2790cfa8310801bb3c3683a7922f85c362b0d366 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 13:09:56 +0300 Subject: [PATCH 06/12] fixed out of range on palette --- lab1/include/Game.hpp | 2 +- lab1/src/Game.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 354b7c0..164ecb0 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -24,7 +24,7 @@ class Game { void Render() const; void Update(); - void CheckTriplets(); + bool CheckTriplets(); void SpawnBonuses(); void ProcessInput(); void HandleClick(const SDL_MouseButtonEvent& e); diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 11c73e6..1158aac 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -58,7 +58,7 @@ int getRandomInt() { static std::random_device rd; // Seed for random number engine static std::mt19937 gen(rd()); // Mersenne Twister engine - static std::uniform_int_distribution dist(start, end); + static std::uniform_int_distribution dist(start, end-1); return dist(gen); } @@ -181,17 +181,19 @@ void Game::Render() const { SDL_RenderPresent(renderer); } -void Game::CheckTriplets() { +bool Game::CheckTriplets() { + bool located = false; for (int i = 2; i < TOTAL_GEMS; i++) { const auto& src = field[i].color; if (i >= 2 * LINE_LENGTH && field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) { //located vertical triplet + located = true; //move all gems in vertical downwards int j = i; - for (; j > 2 * LINE_LENGTH; j -= LINE_LENGTH) { + for (; j > 3 * LINE_LENGTH; j -= LINE_LENGTH) { field[j] = field[j - 3 * LINE_LENGTH]; } @@ -208,6 +210,7 @@ void Game::CheckTriplets() { if (i % LINE_LENGTH >= 2 && field[i - 1].color == src && field[i - 2].color == src) { //located horizontal triplet + located = true; //move all gems from upwards for (int x = i; x > i - 3; x--) { int j = x; @@ -222,10 +225,11 @@ void Game::CheckTriplets() { } } } + return located; } void Game::Update() { - CheckTriplets(); + while (CheckTriplets()) {} } void Game::SpawnBonuses() {} From f8bc455f803f8545e8e6f9a31470863cb7567352 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 13:39:05 +0300 Subject: [PATCH 07/12] added fourplets, fiveplets, etc --- lab1/src/Game.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 1158aac..9ea4b59 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -7,6 +7,7 @@ #include "Colors.hpp" #include "SDL3/SDL_init.h" #include "SDL3/SDL_log.h" +#include "SDL3/SDL_timer.h" constexpr int SCREEN_WIDTH = 800; constexpr int SCREEN_HEIGHT = 600; @@ -58,7 +59,7 @@ int getRandomInt() { static std::random_device rd; // Seed for random number engine static std::mt19937 gen(rd()); // Mersenne Twister engine - static std::uniform_int_distribution dist(start, end-1); + static std::uniform_int_distribution dist(start, end - 1); return dist(gen); } @@ -98,6 +99,7 @@ void Game::Run() { ProcessInput(); Update(); Render(); + SDL_Delay(100); } } @@ -191,10 +193,15 @@ bool Game::CheckTriplets() { //located vertical triplet located = true; + int additional = 1; + while (i + additional * LINE_LENGTH < TOTAL_GEMS && field[i + additional * LINE_LENGTH].color == src) + additional++; + additional--; + //move all gems in vertical downwards - int j = i; + int j = i + additional * LINE_LENGTH; for (; j > 3 * LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - 3 * LINE_LENGTH]; + field[j] = field[j - (3+additional) * LINE_LENGTH]; } for (; j > 0; j -= LINE_LENGTH) { @@ -211,8 +218,16 @@ bool Game::CheckTriplets() { field[i - 1].color == src && field[i - 2].color == src) { //located horizontal triplet located = true; + + int additional = 1; + int endLineIndex = (i / LINE_LENGTH + 1) * LINE_LENGTH; + while (i + additional < endLineIndex && field[i + additional].color == src) + additional++; + + additional--; + //move all gems from upwards - for (int x = i; x > i - 3; x--) { + for (int x = i + additional; x > i - 3; x--) { int j = x; for (; j > LINE_LENGTH; j -= LINE_LENGTH) { field[j] = field[j - LINE_LENGTH]; @@ -229,7 +244,7 @@ bool Game::CheckTriplets() { } void Game::Update() { - while (CheckTriplets()) {} + CheckTriplets(); } void Game::SpawnBonuses() {} From fb8517070442d05f4607a77763202168e5b27369 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 13:45:02 +0300 Subject: [PATCH 08/12] added little documentation --- lab1/README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lab1/README.md b/lab1/README.md index bbaf8aa..48eb86e 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -1,2 +1,20 @@ CPP laboratories -sheesh \ No newline at end of file +sheesh + +# Gems + +## How to play: +Use your mouse to change neighbour gems, LMB to select, other buttons to remove selection + +In total game has six colors: +1. Wheat +2. Maroon +3. Orange +4. Olive +5. Light pink +6. Slate gray + +Bonuses can spawn in reward after gem elimination in radius of 3 gems +Bonuses are rendered as squares inside a gem: +1. Recolor (blue) - changes colors randomly of gem where the bonus is and his 2 random non-neighboring gems to color of gem with bonus. +2. Bomb (red) - eliminates 5 random gems in the field including source gem \ No newline at end of file From e0bd172a86544b8217602a1aecb4618bd55e9492 Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 16:25:55 +0300 Subject: [PATCH 09/12] bugfix --- lab1/src/Game.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 9ea4b59..1da2a5d 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -15,6 +15,7 @@ constexpr int GEM_WIDTH = 40; constexpr int GEM_HEIGHT = 40; constexpr int TOTAL_GEMS = SCREEN_WIDTH * SCREEN_HEIGHT / GEM_WIDTH / GEM_HEIGHT; constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; +constexpr int ROWS = SCREEN_HEIGHT / GEM_HEIGHT; Game::Game() { @@ -54,12 +55,11 @@ bool Game::Initialize() { return true; } -template -int getRandomInt() { +int getRandomInt(int start, int end) { static std::random_device rd; // Seed for random number engine static std::mt19937 gen(rd()); // Mersenne Twister engine - static std::uniform_int_distribution dist(start, end - 1); + std::uniform_int_distribution dist(start, end - 1); return dist(gen); } @@ -83,9 +83,9 @@ bool Game::canPlace(size_t i, SDL_Color src) const { void Game::InitField() { for (int i = 0; i < TOTAL_GEMS; i++) { Gem gem; - auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; while (!canPlace(i, color)) { - color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; } gem.color = color; @@ -202,16 +202,19 @@ bool Game::CheckTriplets() { int j = i + additional * LINE_LENGTH; for (; j > 3 * LINE_LENGTH; j -= LINE_LENGTH) { field[j] = field[j - (3+additional) * LINE_LENGTH]; + for (; j > (3 + additional) * LINE_LENGTH; j -= LINE_LENGTH) { + field[j] = field[j - (3 + additional) * LINE_LENGTH]; } for (; j > 0; j -= LINE_LENGTH) { //generate new gems Gem gem; - auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; gem.color = color; field[j] = gem; } + break; } if (i % LINE_LENGTH >= 2 && @@ -233,11 +236,12 @@ bool Game::CheckTriplets() { field[j] = field[j - LINE_LENGTH]; } Gem gem; - auto color = PALETTE[getRandomInt<0, PALETTE_SIZE>()]; + auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; gem.color = color; field[j] = gem; } + break; } } return located; From b6873a79f978c02af18850cd6d626e21122bc61c Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 16:26:17 +0300 Subject: [PATCH 10/12] added bomb bonus --- lab1/include/Bonus.hpp | 19 ++++++++++ lab1/include/BonusType.hpp | 5 --- lab1/include/Game.hpp | 5 ++- lab1/include/Gem.hpp | 2 - lab1/src/Game.cpp | 77 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 lab1/include/Bonus.hpp delete mode 100644 lab1/include/BonusType.hpp diff --git a/lab1/include/Bonus.hpp b/lab1/include/Bonus.hpp new file mode 100644 index 0000000..3b95a1b --- /dev/null +++ b/lab1/include/Bonus.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "SDL3/SDL_pixels.h" + +struct Bonus { + enum BonusType { + RECOLOR, BOMB, SIZE + }; + + size_t i; + BonusType t; + Bonus() = default; + Bonus(int i, BonusType bonus) : i(i), t(bonus) {} + + static SDL_Color getColor(BonusType t) { + if (t == RECOLOR) return SDL_Color{0, 0, 255, 255}; + if (t == BOMB) return SDL_Color{255, 0, 0, 255}; + return {0, 0, 0, 0}; + } +}; diff --git a/lab1/include/BonusType.hpp b/lab1/include/BonusType.hpp deleted file mode 100644 index 7feed9f..0000000 --- a/lab1/include/BonusType.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -enum BonusType { - VOID, RECOLOR, BOMB -}; diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 164ecb0..7b2586b 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "Bonus.hpp" #include "Gem.hpp" #include "SDL3/SDL_render.h" @@ -16,6 +17,7 @@ class Game { SDL_Window* window = nullptr; SDL_Renderer* renderer = nullptr; std::vector field; + std::vector bonuses; bool shouldExit = false; size_t selectedGem = -1; @@ -25,7 +27,8 @@ class Game { void Render() const; void Update(); bool CheckTriplets(); - void SpawnBonuses(); + void SpawnBonus(size_t destroyed); + void RunBonuses(); void ProcessInput(); void HandleClick(const SDL_MouseButtonEvent& e); }; diff --git a/lab1/include/Gem.hpp b/lab1/include/Gem.hpp index 66ec901..02e5cae 100644 --- a/lab1/include/Gem.hpp +++ b/lab1/include/Gem.hpp @@ -1,13 +1,11 @@ #ifndef GEM_HPP #define GEM_HPP -#include "BonusType.hpp" #include "SDL3/SDL_pixels.h" struct Gem { explicit Gem(SDL_Color c) : color(c) {}; Gem() = default; - BonusType bonus = VOID; SDL_Color color{255, 255, 255, 255}; }; diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 1da2a5d..86074e2 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -165,6 +165,24 @@ void Game::Render() const { SDL_RenderFillRect(renderer, &gem); } + //render bonuses + for (const auto& bonus : bonuses) { + int x = bonus.i % LINE_LENGTH; + int y = bonus.i / LINE_LENGTH; + const SDL_Color& color = Bonus::getColor(bonus.t); + + SDL_SetRenderDrawColor(renderer, + color.r, color.g, color.b, color.a); + + SDL_FRect f{ + float(x * GEM_WIDTH + GEM_WIDTH / 8 * 3), + float(y * GEM_HEIGHT + GEM_HEIGHT / 8 * 3), + GEM_WIDTH / 4, GEM_HEIGHT / 4 + }; + + SDL_RenderFillRect(renderer, &f); + } + //outline selected gem if (selectedGem != -1) { size_t gemX = selectedGem % LINE_LENGTH; @@ -200,8 +218,9 @@ bool Game::CheckTriplets() { //move all gems in vertical downwards int j = i + additional * LINE_LENGTH; - for (; j > 3 * LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - (3+additional) * LINE_LENGTH]; + for (int dj = 0; dj < 3 + additional; dj++) { + SpawnBonus(j - dj * LINE_LENGTH); + } for (; j > (3 + additional) * LINE_LENGTH; j -= LINE_LENGTH) { field[j] = field[j - (3 + additional) * LINE_LENGTH]; } @@ -232,6 +251,7 @@ bool Game::CheckTriplets() { //move all gems from upwards for (int x = i + additional; x > i - 3; x--) { int j = x; + SpawnBonus(j); for (; j > LINE_LENGTH; j -= LINE_LENGTH) { field[j] = field[j - LINE_LENGTH]; } @@ -248,7 +268,58 @@ bool Game::CheckTriplets() { } void Game::Update() { + RunBonuses(); CheckTriplets(); } -void Game::SpawnBonuses() {} +void Game::RunBonuses() { + if (bonuses.empty()) return; + //run only one bonus at a time + const auto& bonus = bonuses.back(); + + if (bonus.t == Bonus::BOMB) { + int x = bonus.i % LINE_LENGTH; + int y = bonus.i / LINE_LENGTH; + + for (int i = 0; i < 4; i++) { + //destroy gem at (x,y) + int j = x + y * LINE_LENGTH; + for (; j > LINE_LENGTH; j -= LINE_LENGTH) { + field[j] = field[j - LINE_LENGTH]; + } + Gem n; + n.color = PALETTE[getRandomInt(0,PALETTE_SIZE)]; + field[j]=n; + + + //generate new x,y + x = getRandomInt(0, LINE_LENGTH); + y = getRandomInt(0, ROWS); + } + } + else if (bonus.t == Bonus::RECOLOR) {} + + bonuses.pop_back(); +} + + +void Game::SpawnBonus(size_t destroyed) { + constexpr float SPAWN_CHANCE = 0.5; + constexpr int SPAWN_RADIUS = 3; + if (static_cast(getRandomInt(0, 101)) / 100 > SPAWN_CHANCE) { + //do spawn + + int x = destroyed % LINE_LENGTH; + int y = destroyed / LINE_LENGTH; + + int xNew = getRandomInt(std::max(0, x - SPAWN_RADIUS), + std::min(LINE_LENGTH, x + SPAWN_RADIUS)); + int yNew = getRandomInt(std::max(0, y - SPAWN_RADIUS), + std::min(ROWS, y + SPAWN_RADIUS)); + + bonuses.emplace_back( + xNew + yNew * LINE_LENGTH, + static_cast(getRandomInt(0, Bonus::BonusType::SIZE)) + ); + } +} From fdf87a553af7a4d7eb504607ef3c16bc6f0034df Mon Sep 17 00:00:00 2001 From: Fromant Date: Sat, 17 May 2025 16:32:10 +0300 Subject: [PATCH 11/12] added recolor bonus --- lab1/src/Game.cpp | 58 +++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 86074e2..a685ae4 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -273,33 +273,53 @@ void Game::Update() { } void Game::RunBonuses() { - if (bonuses.empty()) return; - //run only one bonus at a time - const auto& bonus = bonuses.back(); - if (bonus.t == Bonus::BOMB) { - int x = bonus.i % LINE_LENGTH; - int y = bonus.i / LINE_LENGTH; + for ( const auto& bonus : bonuses) { + if (bonus.t == Bonus::BOMB) { + int x = bonus.i % LINE_LENGTH; + int y = bonus.i / LINE_LENGTH; + + for (int i = 0; i < 4; i++) { + //destroy gem at (x,y) + int j = x + y * LINE_LENGTH; + for (; j > LINE_LENGTH; j -= LINE_LENGTH) { + field[j] = field[j - LINE_LENGTH]; + } + Gem n; + n.color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; + field[j] = n; + - for (int i = 0; i < 4; i++) { - //destroy gem at (x,y) - int j = x + y * LINE_LENGTH; - for (; j > LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - LINE_LENGTH]; + //generate new x,y + x = getRandomInt(0, LINE_LENGTH); + y = getRandomInt(0, ROWS); } - Gem n; - n.color = PALETTE[getRandomInt(0,PALETTE_SIZE)]; - field[j]=n; + } + else if (bonus.t == Bonus::RECOLOR) { + constexpr int RECOLOR_RADIUS = 2; + auto src_color = field[bonus.i].color; + field[bonus.i].color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; + + int x = bonus.i % LINE_LENGTH; + int y = bonus.i / LINE_LENGTH; + + int x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); + int y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); + size_t i1 = x1 + y1 * LINE_LENGTH; - //generate new x,y - x = getRandomInt(0, LINE_LENGTH); - y = getRandomInt(0, ROWS); + while (isNeighbours(bonus.i, i1)) { + x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); + y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); + i1 = x1 + y1 * LINE_LENGTH; + } + + field[i1].color = src_color; } + } - else if (bonus.t == Bonus::RECOLOR) {} - bonuses.pop_back(); + bonuses.clear(); } From 7b7215726d2656c3cce30a724515aab5990c8ae7 Mon Sep 17 00:00:00 2001 From: Fromant Date: Thu, 29 May 2025 15:30:19 +0300 Subject: [PATCH 12/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20C++=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab1/CMakeLists.txt | 2 + lab1/include/Bonus.hpp | 32 ++-- lab1/include/Colors.hpp | 2 +- lab1/include/Game.hpp | 49 +++-- lab1/include/GameObject.hpp | 10 + lab1/include/Gem.hpp | 24 ++- lab1/src/Bonus.cpp | 91 +++++++++ lab1/src/Game.cpp | 355 ++++++++++++++++-------------------- lab1/src/Gem.cpp | 28 +++ 9 files changed, 352 insertions(+), 241 deletions(-) create mode 100644 lab1/include/GameObject.hpp create mode 100644 lab1/src/Bonus.cpp create mode 100644 lab1/src/Gem.cpp diff --git a/lab1/CMakeLists.txt b/lab1/CMakeLists.txt index fd0e90f..908d0ba 100644 --- a/lab1/CMakeLists.txt +++ b/lab1/CMakeLists.txt @@ -15,6 +15,8 @@ FetchContent_MakeAvailable(SDL3) add_executable(gems src/main.cpp src/Game.cpp + src/Gem.cpp + src/Bonus.cpp ) target_include_directories(gems PRIVATE include) diff --git a/lab1/include/Bonus.hpp b/lab1/include/Bonus.hpp index 3b95a1b..009d149 100644 --- a/lab1/include/Bonus.hpp +++ b/lab1/include/Bonus.hpp @@ -1,19 +1,23 @@ #pragma once -#include "SDL3/SDL_pixels.h" +#include "GameObject.hpp" +#include -struct Bonus { - enum BonusType { - RECOLOR, BOMB, SIZE - }; +enum class BonusType { RECOLOR, BOMB }; - size_t i; - BonusType t; - Bonus() = default; - Bonus(int i, BonusType bonus) : i(i), t(bonus) {} +class Bonus : public GameObject { +public: + virtual void Activate(class Game* game, size_t index) = 0; + virtual ~Bonus() = default; +}; - static SDL_Color getColor(BonusType t) { - if (t == RECOLOR) return SDL_Color{0, 0, 255, 255}; - if (t == BOMB) return SDL_Color{255, 0, 0, 255}; - return {0, 0, 0, 0}; - } +class RecolorBonus : public Bonus { +public: + void Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const override; + void Activate(class Game* game, size_t index) override; }; + +class BombBonus : public Bonus { +public: + void Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const override; + void Activate(class Game* game, size_t index) override; +}; \ No newline at end of file diff --git a/lab1/include/Colors.hpp b/lab1/include/Colors.hpp index f1729fa..f9b5266 100644 --- a/lab1/include/Colors.hpp +++ b/lab1/include/Colors.hpp @@ -9,4 +9,4 @@ constexpr SDL_Color LIGHT_PINK{255, 182, 193, 255}; constexpr SDL_Color SLATE_GRAY{112, 128, 144, 255}; constexpr SDL_Color PALETTE[] = {WHEAT, MAROON, ORANGE, OLIVE, LIGHT_PINK, SLATE_GRAY}; -constexpr int PALETTE_SIZE = sizeof(PALETTE) / sizeof(SDL_Color); +constexpr int PALETTE_SIZE = sizeof(PALETTE) / sizeof(SDL_Color); \ No newline at end of file diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 7b2586b..39c7d1b 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -1,34 +1,45 @@ #pragma once +#include "SDL3/SDL.h" #include +#include +#include "Colors.hpp" +#include "GameObject.hpp" -#include "Bonus.hpp" -#include "Gem.hpp" -#include "SDL3/SDL_render.h" +constexpr int SCREEN_WIDTH = 800; +constexpr int SCREEN_HEIGHT = 600; +constexpr int GEM_WIDTH = 40; +constexpr int GEM_HEIGHT = 40; +constexpr int TOTAL_GEMS = SCREEN_WIDTH * SCREEN_HEIGHT / GEM_WIDTH / GEM_HEIGHT; +constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; +constexpr int ROWS = SCREEN_HEIGHT / GEM_HEIGHT; class Game { public: Game(); - bool Initialize(); void Run(); - ~Game() = default; + void ProcessInput(); + void Update(); + void Render() const; + + void HandleClick(const SDL_MouseButtonEvent& e); + bool CheckTriplets(); + void ActivateBonuses(); + void SpawnBonus(size_t destroyed); + + // Для доступа бонусов + std::vector>& GetField() { return field; } + friend class BombBonus; + friend class RecolorBonus; private: + bool shouldExit = false; + int selectedGem = -1; SDL_Window* window = nullptr; SDL_Renderer* renderer = nullptr; - std::vector field; - std::vector bonuses; - bool shouldExit = false; - size_t selectedGem = -1; - void InitField(); - bool canPlace(size_t i, SDL_Color color) const; + std::vector> field; - void Render() const; - void Update(); - bool CheckTriplets(); - void SpawnBonus(size_t destroyed); - void RunBonuses(); - void ProcessInput(); - void HandleClick(const SDL_MouseButtonEvent& e); -}; + void InitField(); + bool canPlace(size_t i, SDL_Color src) const; +}; \ No newline at end of file diff --git a/lab1/include/GameObject.hpp b/lab1/include/GameObject.hpp new file mode 100644 index 0000000..9873d00 --- /dev/null +++ b/lab1/include/GameObject.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "SDL3/SDL_rect.h" +#include "SDL3/SDL_render.h" + +class GameObject { +public: + virtual ~GameObject() = default; + virtual void Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const = 0; + virtual void Update() {} +}; \ No newline at end of file diff --git a/lab1/include/Gem.hpp b/lab1/include/Gem.hpp index 02e5cae..6fa2d26 100644 --- a/lab1/include/Gem.hpp +++ b/lab1/include/Gem.hpp @@ -1,13 +1,23 @@ -#ifndef GEM_HPP -#define GEM_HPP +#pragma once +#include "GameObject.hpp" #include "SDL3/SDL_pixels.h" +#include -struct Gem { - explicit Gem(SDL_Color c) : color(c) {}; +class Bonus; + +class Gem : public GameObject { +public: + explicit Gem(SDL_Color c); Gem() = default; - SDL_Color color{255, 255, 255, 255}; -}; + void Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const override; + void SetBonus(std::unique_ptr b); + bool HasBonus() const; + void ActivateBonus(class Game* game, size_t index); + + SDL_Color color{255, 255, 255, 255}; -#endif //GEM_HPP +private: + std::unique_ptr bonus; +}; \ No newline at end of file diff --git a/lab1/src/Bonus.cpp b/lab1/src/Bonus.cpp new file mode 100644 index 0000000..fdd167c --- /dev/null +++ b/lab1/src/Bonus.cpp @@ -0,0 +1,91 @@ +#include "Bonus.hpp" +#include "Game.hpp" +#include "Gem.hpp" +#include "Colors.hpp" +#include +#include + +namespace { + int getRandomInt(int start, int end) { + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_int_distribution dist(start, end - 1); + return dist(gen); + } + + bool isNeighbours(size_t a, size_t b) { + if (a == b) return false; + if (a > b) std::swap(a, b); + if (b - a == 1 && a % LINE_LENGTH != LINE_LENGTH-1) return true; + if (b - a == LINE_LENGTH) return true; + return false; + } +} + +void RecolorBonus::Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const { + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + SDL_FRect bonusRect = { + rect.x + rect.w * 3/8, + rect.y + rect.h * 3/8, + rect.w / 4, + rect.h / 4 + }; + SDL_RenderFillRect(renderer, &bonusRect); +} + +void RecolorBonus::Activate(Game* game, size_t index) { + auto& field = game->GetField(); + auto gem = std::dynamic_pointer_cast(field[index]); + if (!gem) return; + + auto src_color = gem->color; + gem->color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; + + constexpr int RECOLOR_RADIUS = 2; + int x = index % LINE_LENGTH; + int y = index / LINE_LENGTH; + + int x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); + int y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); + + size_t i1 = x1 + y1 * LINE_LENGTH; + + while (isNeighbours(index, i1)) { + x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); + y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); + i1 = x1 + y1 * LINE_LENGTH; + } + + if (auto other = std::dynamic_pointer_cast(field[i1])) { + other->color = src_color; + } +} + +void BombBonus::Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + SDL_FRect bonusRect = { + rect.x + rect.w * 3/8, + rect.y + rect.h * 3/8, + rect.w / 4, + rect.h / 4 + }; + SDL_RenderFillRect(renderer, &bonusRect); +} + +void BombBonus::Activate(Game* game, size_t index) { + auto& field = game->GetField(); + for (int i = 0; i < 4; i++) { + int x = getRandomInt(0, LINE_LENGTH); + int y = getRandomInt(0, ROWS); + size_t j = x + y * LINE_LENGTH; + + if (j < field.size()) { + for (int row = y; row > 0; row--) { + size_t current = x + row * LINE_LENGTH; + size_t above = x + (row-1) * LINE_LENGTH; + field[current] = field[above]; + } + field[x] = std::make_shared(PALETTE[getRandomInt(0, PALETTE_SIZE)]); + } + } +} \ No newline at end of file diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index a685ae4..36bae73 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -1,22 +1,9 @@ #include "Game.hpp" - +#include "Gem.hpp" +#include "Bonus.hpp" #include #include -#include - -#include "Colors.hpp" -#include "SDL3/SDL_init.h" -#include "SDL3/SDL_log.h" -#include "SDL3/SDL_timer.h" - -constexpr int SCREEN_WIDTH = 800; -constexpr int SCREEN_HEIGHT = 600; -constexpr int GEM_WIDTH = 40; -constexpr int GEM_HEIGHT = 40; -constexpr int TOTAL_GEMS = SCREEN_WIDTH * SCREEN_HEIGHT / GEM_WIDTH / GEM_HEIGHT; -constexpr int LINE_LENGTH = SCREEN_WIDTH / GEM_WIDTH; -constexpr int ROWS = SCREEN_HEIGHT / GEM_HEIGHT; - +#include Game::Game() { field.reserve(TOTAL_GEMS); @@ -28,20 +15,14 @@ bool Game::Initialize() { return false; } - // Создание окна - window = SDL_CreateWindow("Gems", - SCREEN_WIDTH, - SCREEN_HEIGHT, - SDL_WINDOW_RESIZABLE); + window = SDL_CreateWindow("Gems", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_RESIZABLE); if (!window) { SDL_Log("Failed to create window: %s", SDL_GetError()); SDL_Quit(); return false; } - // Создание рендерера - renderer = SDL_CreateRenderer(window, - nullptr); + renderer = SDL_CreateRenderer(window, nullptr); if (!renderer) { SDL_Log("Failed to create renderer: %s", SDL_GetError()); SDL_DestroyWindow(window); @@ -50,50 +31,48 @@ bool Game::Initialize() { } InitField(); - SDL_SetRenderDrawBlendMode(renderer,SDL_BLENDMODE_BLEND); - + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); return true; } int getRandomInt(int start, int end) { - static std::random_device rd; // Seed for random number engine - static std::mt19937 gen(rd()); // Mersenne Twister engine - + static std::random_device rd; + static std::mt19937 gen(rd()); std::uniform_int_distribution dist(start, end - 1); return dist(gen); } bool operator==(const SDL_Color& a, const SDL_Color& b) { - if (a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a) return false; - return true; + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } bool Game::canPlace(size_t i, SDL_Color src) const { - if (i >= 2 * LINE_LENGTH) - if (field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) + if (i >= 2 * LINE_LENGTH) { + auto gem1 = std::dynamic_pointer_cast(field[i - LINE_LENGTH]); + auto gem2 = std::dynamic_pointer_cast(field[i - 2 * LINE_LENGTH]); + if (gem1 && gem2 && gem1->color == src && gem2->color == src) return false; + } - if (i % LINE_LENGTH >= 2) - if (field[i - 1].color == src && field[i - 2].color == src) + if (i % LINE_LENGTH >= 2) { + auto gem1 = std::dynamic_pointer_cast(field[i - 1]); + auto gem2 = std::dynamic_pointer_cast(field[i - 2]); + if (gem1 && gem2 && gem1->color == src && gem2->color == src) return false; + } return true; } - void Game::InitField() { for (int i = 0; i < TOTAL_GEMS; i++) { - Gem gem; auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; while (!canPlace(i, color)) { color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; } - - gem.color = color; - field.emplace_back(gem); + field.push_back(std::make_shared(color)); } } - void Game::Run() { while (!shouldExit) { ProcessInput(); @@ -116,8 +95,10 @@ void Game::ProcessInput() { } bool isNeighbours(size_t a, size_t b) { - if (a - b == 1 || a - b == -1) return true; - if (a - b == LINE_LENGTH || a - b == -LINE_LENGTH) return true; + if (a == b) return false; + if (a > b) std::swap(a, b); + if (b - a == 1 && a % LINE_LENGTH != LINE_LENGTH-1) return true; + if (b - a == LINE_LENGTH) return true; return false; } @@ -132,19 +113,14 @@ void Game::HandleClick(const SDL_MouseButtonEvent& e) { size_t clicked = x + y * LINE_LENGTH; if (selectedGem != -1 && isNeighbours(selectedGem, clicked)) { - //change gems - auto tmp = field[selectedGem]; - field[selectedGem] = field[clicked]; - field[clicked] = tmp; - - //unselect + std::swap(field[selectedGem], field[clicked]); selectedGem = -1; } - //otherwise, change selection to clicked gem - else selectedGem = clicked; + else { + selectedGem = clicked; + } } - void Game::Render() const { SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); @@ -153,49 +129,29 @@ void Game::Render() const { int gemX = i % LINE_LENGTH; int gemY = i / LINE_LENGTH; - SDL_FRect gem{ - static_cast(gemX * GEM_WIDTH), static_cast(gemY * GEM_HEIGHT), - GEM_WIDTH, GEM_HEIGHT - }; + SDL_FRect gemRect{ + static_cast(gemX * GEM_WIDTH), + static_cast(gemY * GEM_HEIGHT), + static_cast(GEM_WIDTH), + static_cast(GEM_HEIGHT) + }; - const SDL_Color& color = field[i].color; - - SDL_SetRenderDrawColor(renderer, - color.r, color.g, color.b, color.a); - SDL_RenderFillRect(renderer, &gem); + field[i]->Draw(renderer, gemRect); } - //render bonuses - for (const auto& bonus : bonuses) { - int x = bonus.i % LINE_LENGTH; - int y = bonus.i / LINE_LENGTH; - const SDL_Color& color = Bonus::getColor(bonus.t); - - SDL_SetRenderDrawColor(renderer, - color.r, color.g, color.b, color.a); - - SDL_FRect f{ - float(x * GEM_WIDTH + GEM_WIDTH / 8 * 3), - float(y * GEM_HEIGHT + GEM_HEIGHT / 8 * 3), - GEM_WIDTH / 4, GEM_HEIGHT / 4 - }; - - SDL_RenderFillRect(renderer, &f); - } - - //outline selected gem - if (selectedGem != -1) { - size_t gemX = selectedGem % LINE_LENGTH; - size_t gemY = selectedGem / LINE_LENGTH; - SDL_FRect gem{ - static_cast(gemX * GEM_WIDTH), static_cast(gemY * GEM_HEIGHT), - GEM_WIDTH, GEM_HEIGHT - }; + if (selectedGem != -1 && selectedGem < field.size()) { + int gemX = selectedGem % LINE_LENGTH; + int gemY = selectedGem / LINE_LENGTH; + SDL_FRect gemRect{ + static_cast(gemX * GEM_WIDTH), + static_cast(gemY * GEM_HEIGHT), + static_cast(GEM_WIDTH), + static_cast(GEM_HEIGHT) + }; SDL_SetRenderDrawColor(renderer, 255, 255, 255, 96); - SDL_RenderFillRect(renderer, &gem); - + SDL_RenderFillRect(renderer, &gemRect); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderRect(renderer, &gem); + SDL_RenderRect(renderer, &gemRect); } SDL_RenderPresent(renderer); @@ -204,142 +160,141 @@ void Game::Render() const { bool Game::CheckTriplets() { bool located = false; for (int i = 2; i < TOTAL_GEMS; i++) { - const auto& src = field[i].color; - - if (i >= 2 * LINE_LENGTH && - field[i - LINE_LENGTH].color == src && field[i - 2 * LINE_LENGTH].color == src) { - //located vertical triplet - located = true; - - int additional = 1; - while (i + additional * LINE_LENGTH < TOTAL_GEMS && field[i + additional * LINE_LENGTH].color == src) - additional++; - additional--; - - //move all gems in vertical downwards - int j = i + additional * LINE_LENGTH; - for (int dj = 0; dj < 3 + additional; dj++) { - SpawnBonus(j - dj * LINE_LENGTH); - } - for (; j > (3 + additional) * LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - (3 + additional) * LINE_LENGTH]; - } + auto gem_i = std::dynamic_pointer_cast(field[i]); + if (!gem_i) continue; + + const auto& src = gem_i->color; + + // Проверка вертикальных троек + if (i >= 2 * LINE_LENGTH) { + auto gem_up1 = std::dynamic_pointer_cast(field[i - LINE_LENGTH]); + auto gem_up2 = std::dynamic_pointer_cast(field[i - 2 * LINE_LENGTH]); + if (gem_up1 && gem_up2 && gem_up1->color == src && gem_up2->color == src) { + located = true; + int additional = 1; + while (i + additional * LINE_LENGTH < TOTAL_GEMS) { + auto gem_next = std::dynamic_pointer_cast(field[i + additional * LINE_LENGTH]); + if (gem_next && gem_next->color == src) + additional++; + else + break; + } + additional--; - for (; j > 0; j -= LINE_LENGTH) { - //generate new gems - Gem gem; - auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; + // Определяем столбец + int col = i % LINE_LENGTH; + int startRow = i / LINE_LENGTH - 2; // начальная строка тройки + int endRow = startRow + 2 + additional; // последняя строка в цепочке - gem.color = color; - field[j] = gem; + // Спавним бонусы для всех гемов в цепочке + for (int row = startRow; row <= endRow; row++) { + int idx = col + row * LINE_LENGTH; + SpawnBonus(idx); + } + + // Количество удаляемых строк + int rowsToRemove = 3 + additional; + + // Сдвигаем гемы в столбце сверху вниз + for (int row = endRow; row >= rowsToRemove; row--) { + int idx = col + row * LINE_LENGTH; + field[idx] = field[idx - rowsToRemove * LINE_LENGTH]; + } + + // Генерируем новые гемы наверху + for (int row = 0; row < rowsToRemove; row++) { + int idx = col + row * LINE_LENGTH; + field[idx] = std::make_shared(PALETTE[getRandomInt(0, PALETTE_SIZE)]); + } + + break; } - break; } - if (i % LINE_LENGTH >= 2 && - field[i - 1].color == src && field[i - 2].color == src) { - //located horizontal triplet - located = true; + // Проверка горизонтальных троек + if (i % LINE_LENGTH >= 2) { + auto gem_left1 = std::dynamic_pointer_cast(field[i - 1]); + auto gem_left2 = std::dynamic_pointer_cast(field[i - 2]); + if (gem_left1 && gem_left2 && gem_left1->color == src && gem_left2->color == src) { + located = true; + int additional = 1; + int row = i / LINE_LENGTH; + int endLineIndex = (row + 1) * LINE_LENGTH; + while (i + additional < endLineIndex) { + auto gem_next = std::dynamic_pointer_cast(field[i + additional]); + if (gem_next && gem_next->color == src) + additional++; + else + break; + } + additional--; + + int startCol = i % LINE_LENGTH - 2; + int endCol = startCol + 2 + additional; + + // Спавним бонусы для всех гемов в цепочке + for (int col = startCol; col <= endCol; col++) { + int idx = col + row * LINE_LENGTH; + SpawnBonus(idx); + } - int additional = 1; - int endLineIndex = (i / LINE_LENGTH + 1) * LINE_LENGTH; - while (i + additional < endLineIndex && field[i + additional].color == src) - additional++; + // Для каждого столбца в цепочке сдвигаем гемы сверху вниз + for (int col = startCol; col <= endCol; col++) { + int idx = col + row * LINE_LENGTH; - additional--; + // Сдвигаем все гемы выше вниз на одну позицию + for (int r = row; r > 0; r--) { + int currentIdx = col + r * LINE_LENGTH; + int aboveIdx = col + (r-1) * LINE_LENGTH; + field[currentIdx] = field[aboveIdx]; + } - //move all gems from upwards - for (int x = i + additional; x > i - 3; x--) { - int j = x; - SpawnBonus(j); - for (; j > LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - LINE_LENGTH]; + // Генерируем новый гем наверху + field[col] = std::make_shared(PALETTE[getRandomInt(0, PALETTE_SIZE)]); } - Gem gem; - auto color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; - gem.color = color; - field[j] = gem; + break; } - break; } } return located; } void Game::Update() { - RunBonuses(); + ActivateBonuses(); CheckTriplets(); } -void Game::RunBonuses() { - - for ( const auto& bonus : bonuses) { - if (bonus.t == Bonus::BOMB) { - int x = bonus.i % LINE_LENGTH; - int y = bonus.i / LINE_LENGTH; - - for (int i = 0; i < 4; i++) { - //destroy gem at (x,y) - int j = x + y * LINE_LENGTH; - for (; j > LINE_LENGTH; j -= LINE_LENGTH) { - field[j] = field[j - LINE_LENGTH]; - } - Gem n; - n.color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; - field[j] = n; - - - //generate new x,y - x = getRandomInt(0, LINE_LENGTH); - y = getRandomInt(0, ROWS); - } - } - else if (bonus.t == Bonus::RECOLOR) { - constexpr int RECOLOR_RADIUS = 2; - auto src_color = field[bonus.i].color; - field[bonus.i].color = PALETTE[getRandomInt(0, PALETTE_SIZE)]; - - int x = bonus.i % LINE_LENGTH; - int y = bonus.i / LINE_LENGTH; - - int x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); - int y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); - - size_t i1 = x1 + y1 * LINE_LENGTH; - - while (isNeighbours(bonus.i, i1)) { - x1 = getRandomInt(std::max(0, x - RECOLOR_RADIUS), std::min(x + RECOLOR_RADIUS, LINE_LENGTH)); - y1 = getRandomInt(std::max(0, y - RECOLOR_RADIUS), std::min(y + RECOLOR_RADIUS, ROWS)); - i1 = x1 + y1 * LINE_LENGTH; +void Game::ActivateBonuses() { + for (size_t i = 0; i < field.size(); i++) { + if (auto gem = std::dynamic_pointer_cast(field[i])) { + if (gem->HasBonus()) { + gem->ActivateBonus(this, i); } - - field[i1].color = src_color; } - } - - bonuses.clear(); } - void Game::SpawnBonus(size_t destroyed) { constexpr float SPAWN_CHANCE = 0.5; - constexpr int SPAWN_RADIUS = 3; - if (static_cast(getRandomInt(0, 101)) / 100 > SPAWN_CHANCE) { - //do spawn - - int x = destroyed % LINE_LENGTH; - int y = destroyed / LINE_LENGTH; - - int xNew = getRandomInt(std::max(0, x - SPAWN_RADIUS), - std::min(LINE_LENGTH, x + SPAWN_RADIUS)); - int yNew = getRandomInt(std::max(0, y - SPAWN_RADIUS), - std::min(ROWS, y + SPAWN_RADIUS)); + if (static_cast(getRandomInt(0, 101)) / 100 > SPAWN_CHANCE) return; - bonuses.emplace_back( - xNew + yNew * LINE_LENGTH, - static_cast(getRandomInt(0, Bonus::BonusType::SIZE)) - ); + constexpr int SPAWN_RADIUS = 3; + int x = destroyed % LINE_LENGTH; + int y = destroyed / LINE_LENGTH; + + int xNew = getRandomInt(std::max(0, x - SPAWN_RADIUS), std::min(LINE_LENGTH, x + SPAWN_RADIUS)); + int yNew = getRandomInt(std::max(0, y - SPAWN_RADIUS), std::min(ROWS, y + SPAWN_RADIUS)); + size_t idx = xNew + yNew * LINE_LENGTH; + + if (idx >= field.size()) return; + if (auto gem = std::dynamic_pointer_cast(field[idx])) { + std::unique_ptr bonus; + if (getRandomInt(0, 2) == 0) { + bonus = std::make_unique(); + } else { + bonus = std::make_unique(); + } + gem->SetBonus(std::move(bonus)); } -} +} \ No newline at end of file diff --git a/lab1/src/Gem.cpp b/lab1/src/Gem.cpp new file mode 100644 index 0000000..8411a13 --- /dev/null +++ b/lab1/src/Gem.cpp @@ -0,0 +1,28 @@ +#include "Gem.hpp" +#include "Bonus.hpp" + +Gem::Gem(SDL_Color c) : color(c) {} + +void Gem::Draw(SDL_Renderer* renderer, const SDL_FRect& rect) const { + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer, &rect); + + if (bonus) { + bonus->Draw(renderer, rect); + } +} + +void Gem::SetBonus(std::unique_ptr b) { + bonus = std::move(b); +} + +bool Gem::HasBonus() const { + return bonus != nullptr; +} + +void Gem::ActivateBonus(Game* game, size_t index) { + if (bonus) { + bonus->Activate(game, index); + bonus.reset(); + } +} \ No newline at end of file