From 16e4a766b7b5cdbfbf564f3ca6e6eb87e3b72d26 Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Tue, 13 May 2025 22:28:53 +0300 Subject: [PATCH 1/6] Gems --- Board.cpp | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Board.h | 55 +++++++ Config.h | 56 ++++++++ Game.cpp | 62 ++++++++ Game.h | 22 +++ Gem.cpp | 51 +++++++ Gem.h | 24 ++++ gems.png | Bin 0 -> 3879 bytes main.cpp | 25 ++++ 9 files changed, 716 insertions(+) create mode 100644 Board.cpp create mode 100644 Board.h create mode 100644 Config.h create mode 100644 Game.cpp create mode 100644 Game.h create mode 100644 Gem.cpp create mode 100644 Gem.h create mode 100644 gems.png create mode 100644 main.cpp diff --git a/Board.cpp b/Board.cpp new file mode 100644 index 0000000..c3ab8aa --- /dev/null +++ b/Board.cpp @@ -0,0 +1,421 @@ +#include "Board.h" +#include +#include +#include +#include +#include +#include + +Board::Board(int w, int h, sf::Texture& textureSheet) + : width(w), + height(h), + textureSheetRef(textureSheet), + selectedGemCoords(-1, -1), + gemSelected(false), + rng(static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count())), + gemColorDist(0, NUM_GEM_COLORS - 1), + bonusChanceDist(0.0f, 1.0f), + bonusTypeDist(0, 1), + gridXDist(0, w - 1), + gridYDist(0, h - 1), + needsMatchCheck(true), + needsGravityCheck(false), + needsRefillCheck(false), + needsBonusProcessing(false) +{ + grid.resize(height); + for (int r = 0; r < height; ++r) { + grid[r].resize(width); + } + + initializeBoard(); +} +void Board::initializeBoard() { + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + do { + createRandomGem(r, c); + } while ( + (c >= 2 && + grid[r][c]->getColor() == grid[r][c - 1]->getColor() && + grid[r][c]->getColor() == grid[r][c - 2]->getColor()) + || + (r >= 2 && + grid[r][c]->getColor() == grid[r - 1][c]->getColor() && + grid[r][c]->getColor() == grid[r - 2][c]->getColor()) + ); + } + } + needsMatchCheck = false; + needsGravityCheck = false; + needsRefillCheck = false; + needsBonusProcessing = false; + +} +void Board::createGem(int r, int c, GemColor color) { + if (isValidCoords(r, c)) { + grid[r][c] = std::make_unique(color, c, r, textureSheetRef); + } +} +void Board::createRandomGem(int r, int c) { + if (isValidCoords(r, c)) { + GemColor randomColor = static_cast(gemColorDist(rng)); + grid[r][c] = std::make_unique(randomColor, c, r, textureSheetRef); + grid[r][c]->setGridPosition(c, r); + } +} + + +void Board::draw(sf::RenderWindow& window) { + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + if (grid[r][c]) { + grid[r][c]->draw(window); + } + } + } +} +bool Board::update() { + bool changed = false; + + if (needsBonusProcessing) { + trySpawnBonus(recentlyDestroyedGems); + recentlyDestroyedGems.clear(); + needsBonusProcessing = false; + needsMatchCheck = true; + changed = true; + } + else if (needsMatchCheck) { + std::set matches = findMatches(); + if (!matches.empty()) { + auto destructionResult = destroyGems(matches); + int destroyedCount = destructionResult.first; + recentlyDestroyedGems = destructionResult.second; + + if (destroyedCount > 0) { + needsGravityCheck = true; + needsBonusProcessing = true; + changed = true; + } + needsMatchCheck = false; + } + else { + needsMatchCheck = false; + } + } + else if (needsGravityCheck) { + if (applyGravity()) { + needsRefillCheck = true; + changed = true; + } + needsGravityCheck = false; + } + else if (needsRefillCheck) { + if (refillBoard()) { + needsMatchCheck = true; + changed = true; + } + needsRefillCheck = false; + } + + return changed; +} + + +bool Board::handleMouseClick(sf::Vector2i pixelCoords) { + if (!isIdle()) return false; + + int gridX = pixelCoords.x / GEM_SIZE; + int gridY = pixelCoords.y / GEM_SIZE; + GridCoord clickedCoords(gridX, gridY); + + if (!isValidCoords(clickedCoords)) { + gemSelected = false; + return false; + } + + if (!gemSelected) { + selectedGemCoords = clickedCoords; + gemSelected = true; + return true; + } + else { + GridCoord secondClickCoords = clickedCoords; + + if (secondClickCoords == selectedGemCoords) { + gemSelected = false; + return true; + } + + if (areAdjacent(selectedGemCoords, secondClickCoords)) { + swapGems(selectedGemCoords, secondClickCoords); + std::set matchesAfterSwap = findMatches(); + + if (matchesAfterSwap.empty()) { + swapGems(selectedGemCoords, secondClickCoords); + gemSelected = false; + return false; + } + else { + needsMatchCheck = true; + gemSelected = false; + return true; + } + } + else { + selectedGemCoords = secondClickCoords; + gemSelected = true; + return true; + } + } +} +bool Board::isIdle() const { + return !needsMatchCheck && !needsGravityCheck && !needsRefillCheck && !needsBonusProcessing; +} +bool Board::isValidCoords(int r, int c) const { + return r >= 0 && r < height && c >= 0 && c < width; +} +bool Board::isValidCoords(GridCoord coords) const { + return isValidCoords(coords.y, coords.x); +} +bool Board::areAdjacent(GridCoord p1, GridCoord p2) const { + int dx = std::abs(p1.x - p2.x); + int dy = std::abs(p1.y - p2.y); + return (dx == 1 && dy == 0) || (dx == 0 && dy == 1); +} +void Board::swapGems(GridCoord p1, GridCoord p2) { + if (!isValidCoords(p1) || !isValidCoords(p2)) return; + + auto temp = std::move(grid[p1.y][p1.x]); + grid[p1.y][p1.x] = std::move(grid[p2.y][p2.x]); + grid[p2.y][p2.x] = std::move(temp); + + if (grid[p1.y][p1.x]) { + grid[p1.y][p1.x]->setGridPosition(p1.x, p1.y); + } + if (grid[p2.y][p2.x]) { + grid[p2.y][p2.x]->setGridPosition(p2.x, p2.y); + } +} +std::set Board::findMatches() { + std::set matches; + for (int r = 0; r < height; ++r) { + int consecutive = 1; + GemColor currentColor = GemColor::None; + for (int c = 0; c < width; ++c) { + if (grid[r][c] && grid[r][c]->getColor() == currentColor) { + consecutive++; + } + else { + if (consecutive >= 3) { + for (int i = c - consecutive; i < c; ++i) { + matches.insert({ i, r }); + } + } + currentColor = grid[r][c] ? grid[r][c]->getColor() : GemColor::None; + consecutive = 1; + } + } + if (consecutive >= 3) { + for (int i = width - consecutive; i < width; ++i) { + matches.insert({ i, r }); + } + } + } + + for (int c = 0; c < width; ++c) { + int consecutive = 1; + GemColor currentColor = GemColor::None; + for (int r = 0; r < height; ++r) { + if (grid[r][c] && grid[r][c]->getColor() == currentColor) { + consecutive++; + } + else { + if (consecutive >= 3) { + for (int i = r - consecutive; i < r; ++i) { + matches.insert({ c, i }); + } + } + currentColor = grid[r][c] ? grid[r][c]->getColor() : GemColor::None; + consecutive = 1; + } + } + if (consecutive >= 3) { + for (int i = height - consecutive; i < height; ++i) { + matches.insert({ c, i }); + } + } + } + + return matches; +} +std::pair>> Board::destroyGems(const std::set& coordsToDestroy) { + int count = 0; + std::vector> destroyedInfo; + + for (const auto& coord : coordsToDestroy) { + if (isValidCoords(coord) && grid[coord.y][coord.x]) { + destroyedInfo.push_back({ coord, grid[coord.y][coord.x]->getColor() }); + grid[coord.y][coord.x].reset(); + count++; + } + } + return { count, destroyedInfo }; +} +bool Board::applyGravity() { + bool gemsFell = false; + for (int c = 0; c < width; ++c) { + int emptyRow = height - 1; + for (int r = height - 1; r >= 0; --r) { + if (grid[r][c]) { + if (r != emptyRow) { + grid[emptyRow][c] = std::move(grid[r][c]); + grid[emptyRow][c]->setGridPosition(c, emptyRow); + gemsFell = true; + } + emptyRow--; + } + } + while (emptyRow >= 0) { + grid[emptyRow][c].reset(); + emptyRow--; + } + } + return gemsFell; +} +bool Board::refillBoard() { + bool newGemsAdded = false; + for (int c = 0; c < width; ++c) { + for (int r = 0; r < height; ++r) { + if (!grid[r][c]) { + createRandomGem(r, c); + grid[r][c]->setGridPosition(c, r); + newGemsAdded = true; + } + else { + break; + } + } + } + return newGemsAdded; +} +void Board::trySpawnBonus(const std::vector>& destroyedGemsInfo) { + for (const auto& info : destroyedGemsInfo) { + if (bonusChanceDist(rng) <= BONUS_CHANCE) { + int bonusType = bonusTypeDist(rng); + GridCoord bonusOrigin = info.first; + GemColor bonusOriginColor = info.second; + int targetR, targetC; + int attempts = 0; + const int maxAttempts = 20; + do { + int dr = rng() % (2 * REPAINT_BONUS_RADIUS + 1) - REPAINT_BONUS_RADIUS; + int dc = rng() % (2 * REPAINT_BONUS_RADIUS + 1) - REPAINT_BONUS_RADIUS; + targetR = bonusOrigin.y + dr; + targetC = bonusOrigin.x + dc; + attempts++; + } while (!isValidCoords(targetR, targetC) && attempts < maxAttempts); + + if (isValidCoords(targetR, targetC)) { + GridCoord targetPos = { targetC, targetR }; + if (bonusType == 0) { + applyRepaintBonus(targetPos, bonusOriginColor); + } + else { + applyBombBonus(targetPos); + } + needsMatchCheck = true; + } + } + } +} +void Board::applyRepaintBonus(GridCoord targetPos, GemColor sourceColor) { + if (!isValidCoords(targetPos) || sourceColor == GemColor::None) return; + + if (grid[targetPos.y][targetPos.x]) { + grid[targetPos.y][targetPos.x]->setColor(sourceColor); + } + + std::vector candidates; + for (int dr = -REPAINT_BONUS_RADIUS; dr <= REPAINT_BONUS_RADIUS; ++dr) { + for (int dc = -REPAINT_BONUS_RADIUS; dc <= REPAINT_BONUS_RADIUS; ++dc) { + GridCoord candidatePos(targetPos.x + dc, targetPos.y + dr); + if (isValidCoords(candidatePos) && + grid[candidatePos.y][candidatePos.x] && + !areAdjacent(targetPos, candidatePos)) { + candidates.push_back(candidatePos); + } + } + } + + std::shuffle(candidates.begin(), candidates.end(), rng); + int count = 0; + for (const auto& pos : candidates) { + if (count >= 2) break; + grid[pos.y][pos.x]->setColor(sourceColor); + count++; + } + + needsMatchCheck = true; +} +void Board::applyBombBonus(GridCoord targetPos) { + std::set gemsToDestroy; + + if (isValidCoords(targetPos) && grid[targetPos.y][targetPos.x]) { + gemsToDestroy.insert(targetPos); + } + + std::vector allGems; + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + if (grid[r][c] && !gemsToDestroy.count({ c, r })) { + allGems.push_back({ c, r }); + } + } + } + + std::shuffle(allGems.begin(), allGems.end(), rng); + int needed = BOMB_BONUS_COUNT - gemsToDestroy.size(); + for (int i = 0; i < needed && i < allGems.size(); ++i) { + gemsToDestroy.insert(allGems[i]); + } + + if (!gemsToDestroy.empty()) { + destroyGems(gemsToDestroy); + needsGravityCheck = true; + } +} +bool Board::hasPossibleMoves() const { + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + if (!grid[r][c]) continue; + + GemColor current = grid[r][c]->getColor(); + if (c + 1 < width && grid[r][c + 1]) { + GemColor right = grid[r][c + 1]->getColor(); + if (c + 2 < width && grid[r][c + 2] && grid[r][c + 2]->getColor() == current) return true; + if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() == current && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current) return true; + if (r + 2 < height && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current && grid[r + 2][c + 1] && grid[r + 2][c + 1]->getColor() == current) return true; + if (r - 2 >= 0 && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() == current && grid[r - 2][c + 1] && grid[r - 2][c + 1]->getColor() == current) return true; + if (c - 1 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() == right) return true; + if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c] && grid[r - 1][c]->getColor() == right && grid[r + 1][c] && grid[r + 1][c]->getColor() == right) return true; + if (r + 2 < height && grid[r + 1][c] && grid[r + 1][c]->getColor() == right && grid[r + 2][c] && grid[r + 2][c]->getColor() == right) return true; + if (r - 2 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() == right && grid[r - 2][c] && grid[r - 2][c]->getColor() == right) return true; + + } + if (r + 1 < height && grid[r + 1][c]) { + GemColor down = grid[r + 1][c]->getColor(); + if (r + 2 < height && grid[r + 2][c] && grid[r + 2][c]->getColor() == current) return true; + if (c - 1 >= 0 && c + 1 < width && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() == current && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current) return true; + if (c + 2 < width && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current && grid[r + 1][c + 2] && grid[r + 1][c + 2]->getColor() == current) return true; + if (c - 2 >= 0 && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() == current && grid[r + 1][c - 2] && grid[r + 1][c - 2]->getColor() == current) return true; + + if (r - 1 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() == down) return true; + if (c - 1 >= 0 && c + 1 < width && grid[r][c - 1] && grid[r][c - 1]->getColor() == down && grid[r][c + 1] && grid[r][c + 1]->getColor() == down) return true; + if (c + 2 < width && grid[r][c + 1] && grid[r][c + 1]->getColor() == down && grid[r][c + 2] && grid[r][c + 2]->getColor() == down) return true; + if (c - 2 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() == down && grid[r][c - 2] && grid[r][c - 2]->getColor() == down) return true; + } + } + } + return false; +} \ No newline at end of file diff --git a/Board.h b/Board.h new file mode 100644 index 0000000..e79d1d2 --- /dev/null +++ b/Board.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include "Gem.h" +#include "Config.h" + +class Board { +public: + Board(int width, int height, sf::Texture& textureSheet); + Board(const Board&) = delete; + Board& operator=(const Board&) = delete; + void draw(sf::RenderWindow& window); + bool update(); + bool handleMouseClick(sf::Vector2i pixelCoords); + bool isIdle() const; + + +private: + int width; + int height; + sf::Texture& textureSheetRef; + std::vector>> grid; + GridCoord selectedGemCoords; + bool gemSelected; + std::mt19937 rng; + std::uniform_int_distribution gemColorDist; + std::uniform_real_distribution bonusChanceDist; + std::uniform_int_distribution bonusTypeDist; + std::uniform_int_distribution gridXDist; + std::uniform_int_distribution gridYDist; + bool isValidCoords(int r, int c) const; + bool isValidCoords(GridCoord coords) const; + bool areAdjacent(GridCoord p1, GridCoord p2) const; + void swapGems(GridCoord p1, GridCoord p2); + std::set findMatches(); + std::pair>> destroyGems(const std::set& coordsToDestroy); + bool applyGravity(); + bool refillBoard(); + void trySpawnBonus(const std::vector>& destroyedGemsInfo); + void applyRepaintBonus(GridCoord targetPos, GemColor sourceColor); + void applyBombBonus(GridCoord targetPos); + void initializeBoard(); + bool hasPossibleMoves() const; + void createGem(int r, int c, GemColor color); + void createRandomGem(int r, int c); + bool needsMatchCheck; + bool needsGravityCheck; + bool needsRefillCheck; + bool needsBonusProcessing; + std::vector> recentlyDestroyedGems; + +}; \ No newline at end of file diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..416f3ba --- /dev/null +++ b/Config.h @@ -0,0 +1,56 @@ +#pragma once + +#include +constexpr int GRID_WIDTH = 8; +constexpr int GRID_HEIGHT = 8; +constexpr int GEM_SIZE = 64; +constexpr unsigned int WINDOW_WIDTH = GRID_WIDTH * GEM_SIZE; +constexpr unsigned int WINDOW_HEIGHT = GRID_HEIGHT * GEM_SIZE; +const int NUM_GEM_COLORS = 6; +const float BONUS_CHANCE = 0.1f; +const int REPAINT_BONUS_RADIUS = 3; +const int BOMB_BONUS_COUNT = 5; +enum class GemColor { + Red = 0, + Green, + Blue, + Yellow, + Purple, + Orange, + None +}; +enum class GameState { + Idle, + GemSelected, + Swapping, + Checking, + Destroying, + Falling, + Refilling, + ApplyingBonus +}; + +namespace sf { + inline bool operator<(const Vector2i& a, const Vector2i& b) { + if (a.x == b.x) { + return a.y < b.y; + } + return a.x < b.x; + } +} +struct GridCoord { + int x; + int y; + + GridCoord() : x(0), y(0) {} + + GridCoord(int x, int y) : x(x), y(y) {} + + bool operator<(const GridCoord& other) const { + return (y < other.y) || (y == other.y && x < other.x); + } + + bool operator==(const GridCoord& other) const { + return x == other.x && y == other.y; + } +}; \ No newline at end of file diff --git a/Game.cpp b/Game.cpp new file mode 100644 index 0000000..6886980 --- /dev/null +++ b/Game.cpp @@ -0,0 +1,62 @@ +#include "Game.h" +#include "Config.h" +#include +#include +#include +#include +#include +#include + +Game::Game() + : window(sf::VideoMode({WINDOW_WIDTH, WINDOW_HEIGHT}), "SFML Gems Game", sf::Style::Default) +{ + window.setFramerateLimit(60); + loadResources(); + auto newBoard = std::make_unique(GRID_WIDTH, GRID_HEIGHT, gemTextureSheet); + board = std::move(newBoard); +} + +void Game::loadResources() { + if (!gemTextureSheet.loadFromFile("gems.png")) { + throw std::runtime_error("Failed to load gems.png texture!"); + } +} + +void Game::run() { + sf::Clock clock; + while (window.isOpen()) { + sf::Time deltaTime = clock.restart(); + processEvents(); + update(deltaTime); + render(); + } +} + +void Game::processEvents() { + while (const auto event = window.pollEvent()) { + event->visit([this](auto&& e) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + window.close(); + } + else if constexpr (std::is_same_v) { + if (e.button == sf::Mouse::Button::Left) { + board->handleMouseClick({ e.position.x, e.position.y }); + } + } + }); + } +} + +void Game::update(sf::Time deltaTime) { + while (board->update()) {} +} + +void Game::render() { + window.clear(sf::Color::Black); + if (board) { + board->draw(window); + } + window.display(); +} \ No newline at end of file diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..53f0bf9 --- /dev/null +++ b/Game.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "Board.h" +#include "Config.h" +#include + +class Game { +public: + Game(); + void run(); + +private: + void processEvents(); + void update(sf::Time deltaTime); + void render(); + sf::RenderWindow window; + sf::Texture gemTextureSheet; + std::unique_ptr board; + void loadResources(); + +}; \ No newline at end of file diff --git a/Gem.cpp b/Gem.cpp new file mode 100644 index 0000000..2ae5693 --- /dev/null +++ b/Gem.cpp @@ -0,0 +1,51 @@ +#include "Gem.h" +#include "Config.h" +#include +#include + +Gem::Gem(GemColor c, int x, int y, sf::Texture& textureSheet) + : color(c), gridPosition(x, y), textureSheetRef(textureSheet), sprite(textureSheet) +{ + if (color != GemColor::None) { + updateTextureRect(); + } + setGridPosition(x, y); +} + +void Gem::setGridPosition(int gridX, int gridY) { + gridPosition.x = gridX; + gridPosition.y = gridY; + sprite.setPosition(sf::Vector2f( + static_cast(gridX * GEM_SIZE), + static_cast(gridY * GEM_SIZE) + )); +} + +void Gem::updateTextureRect() { + if (color == GemColor::None) return; + + int colorIndex = static_cast(color); + if (colorIndex < 0 || colorIndex >= NUM_GEM_COLORS) { + throw std::runtime_error("Invalid GemColor index in updateTextureRect"); + } + sprite.setTexture(textureSheetRef); + sprite.setTextureRect(sf::IntRect( + sf::Vector2i(colorIndex * GEM_SIZE, 0), + sf::Vector2i(GEM_SIZE, GEM_SIZE) + )); +} + +GemColor Gem::getColor() const { + return color; +} + +void Gem::draw(sf::RenderWindow& window) { + if (color != GemColor::None) { + window.draw(sprite); + } +} + +void Gem::setColor(GemColor newColor) { + color = newColor; + updateTextureRect(); +} \ No newline at end of file diff --git a/Gem.h b/Gem.h new file mode 100644 index 0000000..69db7fb --- /dev/null +++ b/Gem.h @@ -0,0 +1,24 @@ + +#pragma once + +#include +#include "Config.h" + +class Gem { +public: + Gem(GemColor color, int gridX, int gridY, sf::Texture& textureSheet); + void draw(sf::RenderWindow& window); + void setGridPosition(int gridX, int gridY); + GridCoord getGridPosition() const; + GemColor getColor() const; + void setColor(GemColor newColor); + sf::Vector2f getVisualPosition() const; + void setVisualPosition(float x, float y); + +private: + sf::Sprite sprite; + GemColor color; + GridCoord gridPosition; + sf::Texture& textureSheetRef; + void updateTextureRect(); +}; \ No newline at end of file diff --git a/gems.png b/gems.png new file mode 100644 index 0000000000000000000000000000000000000000..574d6423a0c77c06f86cdd14624c6bafa3cc0199 GIT binary patch literal 3879 zcmds)^ zq+3+dLyI(H#P;mwoFC5fFFe02-n(Ac_p{HvUDpyt0F$AC4iW&UFqFp# zC>`aN-72q@a!2Kd)Yk$k`_Hcc0P~!oj;2L`{d$&(r|sCePBNh-Jy4;px>MXoTo5e+ zi8nIwK<0-I8X+3zv~Sa-#u*gRAdSqeqS~djrc&->3*vrD%`qV@6_K>%q0Z)35z=B> zv}b&Tw`RG6e|=5I6Q(cwcL;R4JatgC3?+1JH1&0TaqA~H?Sv8h;r;OdCVB>W|AIF! zg#FNq>IWX~|E&nKv!2CK9MwKqFUUG_IB)~6Zp(asQBmj{(J6Y7eA_;v~=<2AT)~~KZ?Xo8%qgh{?!2J{T)}tiSQKJ8}WPQY@;;1`GwV4FD zHJRYQ|22g0ZVhk$!{&J?DgVw%tM*!w=?Rq_?X%kXv2;^Xnt-%uLc>qNs!uzXZz3)Z z#Hr3?aRzNhw{g(KsXlB|UMw^lgB^S1YDAbh-%4*Sxyzm;cqHts@P>_L<&W0T&Dzkd zdah7g&vy}$xGQ0|`&<;*Z?+KqPTt^#$$+@`Dm{XusD|{?S4(ZT?oiqyK=FNbRH?N` zQo4CS4bAMuI3yd}Z~#mzGP0dB7mCxO&YbH6;A>3B4=R^g_ZSyj!XXl2 ze09mR2flW+p5oh*6rL|^AUVKK{w~9!$=}Qh^qD>0o=M;D{Nn_>LqjKziMkfO)Iwi6 zZmxI*^3k^BFOOW+J|NvLJ7KpZ49YU;H=kb*8GDl(&XA#c+0IErBsmgZ$K?tSIh*Rv z--n8B2#s%i*<+9r7pO)&V5O<;F{@xc9!B3Mz#KFl&xJ=aPE-oJy&FzZk)OPz=fBah zRiD$QWhcCxa8mX`9>U$n*fsN%%ZjATa-~?D@T6cGD!VHhlS^7)JU9M&B=y+6&fXE) z+WPc-&t8A8^1}%;jmV|YeSRI~VOsJ#l^$Win;{1wqh3D@Y*xM#7wPG|j~xaIay0h+ zw+R@m>(e}9tHrqB2QP%0EU_%YXyRAB%|qd>?OlZ!q|(s}GX@ecH{?YvRiuo79&Z;0 z=OZ&(SssyvxaaVf3wRa&^*g zlgFganGoSI;zr5%OMK--}`@Ah~iSlUn; z@d*>x&5C^Y$`zP_L)81L^))%g99=J%5S0R@$nI89K1ly*?8De6%_)S?h3B0e-{{Ly zDnlQ4P~~w-^j^Ymum(c71$WH`Gnb?O#*>gmyjK-GP8)IMh6E8WUURal2tnwwx;xlE zV^IE$Wt53*Uq2EOOVk|HD>(yTkye9Ka+!5d91HiDomd7P%@w|EaiCOI>u0UhXfd@f z7Q4A2QP=oD!d6Me1c+&8NgaUeb9?T#rgi6`X93)jxq%?~3RLu25?2KNG40Buj7EKh zO))0&_hm&Yp*T#r17xmPfz56|q<*a%h~56ASHcSHJ+qEVoThiqfn$!Buf?jO3P)%# zpHn+tPiZ#9yEwn>xO6&RV)_Lg>1J5W0*aY5?Z~!QKlA|7_M`W#yUlqPE;_m@j}3b^ zB6toeNUjZ8OJLseHqBETeo+26WD=xLs7fEJM7BOT_Z0PX^zmZ$fs#rhU{z+T?cP#zOhx6CDDB@~dHdam9vz22l_d5MQf_uufYjW%T^+G%PyzBHC zFK}2`WjahBwozOSy9Yj9iTlvBzy`j%i_~r2P)~&_@dr$N*=I2ou<(a;<6>)Gg~?P^ zd%{(HZ5hrd57E3-W|??T;+?9{@;X^P>Tt>qr7^3%)D>0!@t)(!63jziAvHq}en1*( zVwQ`kH#1ExGg==U>A%_~zOLrw1l5z$mV>2)Q0eJ`CApR`c z4f38khAtP%s2|)~p`i=_!Tf$Z6uCBGpYpQc-z>d3=v+hh?BWe&c*4ea{bKema7_)h zda%mOndBkY)}YOj;WN&Z)c{l0h4d4B_}JxX*gpiPOk##AaMhX{i$cAy>QEhL}PlhdQ!-0 zf(P{&zmi|7k^hA}S5l+V1$lnN)FGivp_0kM|Esagvs0tAXmtwrAQH+!UK>`|(84yT zK;9C`X3l$bax}`r2=K;dt>0p$$E?R2|4jP{AhZ|u%VMb8_H~Q5dVGMqeBUFB|4^Zh zL`Dwo(*dn#j6cP`u#g1~Oi=pcqO7l}+5jO(9ZL~z;4EM{UC5F~0eQ39uDl>{T@+<` z11eD3{%j!d|LrH3P66H5y@=adO5Hz{xQpgveHh=KZX!_eA$4!VApF-?0GDehhPPoF#`j+X6#1AemKGc2 zaVgvaeA@j4uiJM;X4Wj1w*32mQd$Ou9!F*Ro50UV0R|qdfqgwM&LKNEht$qp@UMml zF8}h1Xx~c7L^JcKZ)ukK?I>-|_MSZNBMqpb6U(2JuRj9E!C9%C2lr~BG(>Y>GzsSo zCM)6@D{t=Y{0KLv69qOBzb;hdZ#BpB(x;^B?WjU>voT!}HNB8}k`~bs3`kkzL4YvA z!-aL_0$pL^T zMO^iM7~wZETmRboim05tffJ`2=-koER*>p4z}xfj{BiRqvDMRf{*cZy`e=7Q%n=1N z?*G1kZA~;~2a36VP2(cvaU>B9%KW_FMA&OQ1D*fsN6-z>PM<*9nC?wmtB>#X*j{K_*1QxttBny>r@TyYI*_~}D zqo-*gX#WE{>cZW3Cdz9m@!h+C{r@Wn%OB*!&jeaoO-iF_H+JR2bs+eLWL3@}_#1o}<)`m!a4EU(ZCLqQ;uIwanrO8#!9q z3rOoQ2gQqV7t1k~VP87nz-Z+b=46C-qlBUNeliKljJK^?$ zRp)7a*$`6OWT}B2dG_ps?jCa1I5BLenX4yV!uZz70R6D4&sRQAKnVQvI W$TV?e;5y}>FJP!^s#B?jiuf;Djsc + +int main() { + try { + Game game; + game.run(); + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; +#ifdef _WIN32 + system("pause"); +#endif + return EXIT_FAILURE; + } + catch (...) { + std::cerr << "An unknown error occurred." << std::endl; +#ifdef _WIN32 + system("pause"); +#endif + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From 2fe901700a9f6a63e01dbd9204d6514658925b2f Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Tue, 13 May 2025 23:23:47 +0300 Subject: [PATCH 2/6] Cmake --- CMakeLists.txt | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6224c96 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.15) +project(GEMS3 LANGUAGES CXX) + +# Настройка стандарта C++17 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Поиск SFML +find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED) + +# Исходные файлы +set(SOURCES + Board.cpp + Game.cpp + Gem.cpp + main.cpp +) + +# Заголовочные файлы +set(HEADERS + Board.h + Config.h + Game.h + Gem.h +) + +# Создание исполняемого файла +add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) + +# Подключение SFML +target_link_libraries(${PROJECT_NAME} PRIVATE + SFML::Graphics + SFML::Window + SFML::System +) + +# Настройки компилятора для MSVC +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /W3 # Уровень предупреждений 3 + /permissive- # Строгое соответствие стандартам + /sdl # Проверки безопасности + ) +else() + target_compile_options(${PROJECT_NAME} PRIVATE + -Wall -Wextra -pedantic + ) +endif() + +# Настройки конфигураций +target_compile_definitions(${PROJECT_NAME} PRIVATE + $<$:_DEBUG> + $<$:NDEBUG> +) + +# Оптимизации для Release +target_compile_options(${PROJECT_NAME} PRIVATE + $<$: + /O2 + /GL + > +) + +# Настройки линковщика +target_link_options(${PROJECT_NAME} PRIVATE + $<$: + /LTCG + /OPT:REF + /OPT:ICF + > +) + +# Рабочая директория для доступа к ресурсам (например, gems.png) +set_target_properties(${PROJECT_NAME} PROPERTIES + VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" +) + +# Для копирования файла gems.png в выходную директорию (опционально) +configure_file( + "${CMAKE_SOURCE_DIR}/gems.png" + "${CMAKE_BINARY_DIR}/gems.png" + COPYONLY +) \ No newline at end of file From 16fa3d0e0a854b0801d2f5e9a4d2aea469064143 Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Sun, 18 May 2025 20:20:01 +0300 Subject: [PATCH 3/6] Update CMakeLists.txt --- CMakeLists.txt | 142 +++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6224c96..7682f7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,84 +1,86 @@ -cmake_minimum_required(VERSION 3.15) -project(GEMS3 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) -# Настройка стандарта C++17 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) -# Поиск SFML -find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED) +project(GEMS_3 CXX) -# Исходные файлы -set(SOURCES - Board.cpp - Game.cpp - Gem.cpp - main.cpp -) - -# Заголовочные файлы -set(HEADERS - Board.h - Config.h - Game.h - Gem.h -) +################################################################################ +# Set target arch type if empty. Visual studio solution generator provides it. +################################################################################ +if(NOT CMAKE_VS_PLATFORM_NAME) + set(CMAKE_VS_PLATFORM_NAME "x64") +endif() +message("${CMAKE_VS_PLATFORM_NAME} architecture in use") -# Создание исполняемого файла -add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) +if(NOT ("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64" + OR "${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86")) + message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} arch is not supported!") +endif() -# Подключение SFML -target_link_libraries(${PROJECT_NAME} PRIVATE - SFML::Graphics - SFML::Window - SFML::System +################################################################################ +# Global configuration types +################################################################################ +set(CMAKE_CONFIGURATION_TYPES + "Debug" + "Release" + CACHE STRING "" FORCE ) -# Настройки компилятора для MSVC +################################################################################ +# Global compiler options +################################################################################ if(MSVC) - target_compile_options(${PROJECT_NAME} PRIVATE - /W3 # Уровень предупреждений 3 - /permissive- # Строгое соответствие стандартам - /sdl # Проверки безопасности - ) -else() - target_compile_options(${PROJECT_NAME} PRIVATE - -Wall -Wextra -pedantic - ) + # remove default flags provided with CMake for MSVC + set(CMAKE_CXX_FLAGS "") + set(CMAKE_CXX_FLAGS_DEBUG "") + set(CMAKE_CXX_FLAGS_RELEASE "") endif() -# Настройки конфигураций -target_compile_definitions(${PROJECT_NAME} PRIVATE - $<$:_DEBUG> - $<$:NDEBUG> -) +################################################################################ +# Global linker options +################################################################################ +if(MSVC) + # remove default flags provided with CMake for MSVC + set(CMAKE_EXE_LINKER_FLAGS "") + set(CMAKE_MODULE_LINKER_FLAGS "") + set(CMAKE_SHARED_LINKER_FLAGS "") + set(CMAKE_STATIC_LINKER_FLAGS "") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS}") + set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "${CMAKE_MODULE_LINKER_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS}") + set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${CMAKE_STATIC_LINKER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS}") + set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS}") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS}") +endif() -# Оптимизации для Release -target_compile_options(${PROJECT_NAME} PRIVATE - $<$: - /O2 - /GL - > -) +################################################################################ +# Nuget packages function stub. +################################################################################ +function(use_package TARGET PACKAGE VERSION) + message(WARNING "No implementation of use_package. Create yours. " + "Package \"${PACKAGE}\" with version \"${VERSION}\" " + "for target \"${TARGET}\" is ignored!") +endfunction() -# Настройки линковщика -target_link_options(${PROJECT_NAME} PRIVATE - $<$: - /LTCG - /OPT:REF - /OPT:ICF - > -) +################################################################################ +# Common utils +################################################################################ +include(CMake/Utils.cmake) -# Рабочая директория для доступа к ресурсам (например, gems.png) -set_target_properties(${PROJECT_NAME} PROPERTIES - VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" -) +################################################################################ +# Additional Global Settings(add specific info there) +################################################################################ +include(CMake/GlobalSettingsInclude.cmake OPTIONAL) + +################################################################################ +# Use solution folders feature +################################################################################ +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +################################################################################ +# Sub-projects +################################################################################ +add_subdirectory(GEMS_3) -# Для копирования файла gems.png в выходную директорию (опционально) -configure_file( - "${CMAKE_SOURCE_DIR}/gems.png" - "${CMAKE_BINARY_DIR}/gems.png" - COPYONLY -) \ No newline at end of file From e73cbefc8923ddb479a55e31c3c0bc821f057b55 Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Sun, 18 May 2025 20:24:55 +0300 Subject: [PATCH 4/6] update cmake --- CMakeLists.txt | 178 ++++++++++++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7682f7b..30acac8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,86 +1,124 @@ -cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) +set(PROJECT_NAME GEMS_3) -set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) +################################################################################ +# Source groups +################################################################################ +set(Source_Files + "Board.cpp" + "Board.h" + "Config.h" + "Game.cpp" + "Game.h" + "Gem.cpp" + "Gem.h" + "main.cpp" +) +source_group("Source Files" FILES ${Source_Files}) -project(GEMS_3 CXX) +set(ALL_FILES + ${Source_Files} +) ################################################################################ -# Set target arch type if empty. Visual studio solution generator provides it. +# Target ################################################################################ -if(NOT CMAKE_VS_PLATFORM_NAME) - set(CMAKE_VS_PLATFORM_NAME "x64") -endif() -message("${CMAKE_VS_PLATFORM_NAME} architecture in use") +add_executable(${PROJECT_NAME} ${ALL_FILES}) -if(NOT ("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64" - OR "${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86")) - message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} arch is not supported!") -endif() +use_props(${PROJECT_NAME} "${CMAKE_CONFIGURATION_TYPES}" "${DEFAULT_CXX_PROPS}") +set(ROOT_NAMESPACE GEMS3) -################################################################################ -# Global configuration types -################################################################################ -set(CMAKE_CONFIGURATION_TYPES - "Debug" - "Release" - CACHE STRING "" FORCE +set_target_properties(${PROJECT_NAME} PROPERTIES + VS_GLOBAL_KEYWORD "Win32Proj" ) - -################################################################################ -# Global compiler options +if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") + set_target_properties(${PROJECT_NAME} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE" + ) +elseif("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86") + set_target_properties(${PROJECT_NAME} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE" + ) +endif() ################################################################################ -if(MSVC) - # remove default flags provided with CMake for MSVC - set(CMAKE_CXX_FLAGS "") - set(CMAKE_CXX_FLAGS_DEBUG "") - set(CMAKE_CXX_FLAGS_RELEASE "") +# Compile definitions +################################################################################ +if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") + target_compile_definitions(${PROJECT_NAME} PRIVATE + "$<$:" + "_DEBUG" + ">" + "$<$:" + "NDEBUG" + ">" + "_CONSOLE;" + "UNICODE;" + "_UNICODE" + ) +elseif("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86") + target_compile_definitions(${PROJECT_NAME} PRIVATE + "$<$:" + "_DEBUG" + ">" + "$<$:" + "NDEBUG" + ">" + "WIN32;" + "_CONSOLE;" + "UNICODE;" + "_UNICODE" + ) endif() ################################################################################ -# Global linker options +# Compile and link options ################################################################################ if(MSVC) - # remove default flags provided with CMake for MSVC - set(CMAKE_EXE_LINKER_FLAGS "") - set(CMAKE_MODULE_LINKER_FLAGS "") - set(CMAKE_SHARED_LINKER_FLAGS "") - set(CMAKE_STATIC_LINKER_FLAGS "") - set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "${CMAKE_MODULE_LINKER_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS}") - set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${CMAKE_STATIC_LINKER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS}") - set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS}") + if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") + target_compile_options(${PROJECT_NAME} PRIVATE + $<$: + /std:c++17 + > + $<$: + /Oi; + /Gy + > + /permissive-; + /sdl; + /W3; + ${DEFAULT_CXX_DEBUG_INFORMATION_FORMAT}; + ${DEFAULT_CXX_EXCEPTION_HANDLING} + ) + elseif("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86") + target_compile_options(${PROJECT_NAME} PRIVATE + $<$: + /Oi; + /Gy + > + /permissive-; + /sdl; + /W3; + ${DEFAULT_CXX_DEBUG_INFORMATION_FORMAT}; + ${DEFAULT_CXX_EXCEPTION_HANDLING} + ) + endif() + if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") + target_link_options(${PROJECT_NAME} PRIVATE + $<$: + /OPT:REF; + /OPT:ICF + > + /DEBUG; + /SUBSYSTEM:CONSOLE + ) + elseif("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86") + target_link_options(${PROJECT_NAME} PRIVATE + $<$: + /OPT:REF; + /OPT:ICF + > + /DEBUG; + /SUBSYSTEM:CONSOLE + ) + endif() endif() -################################################################################ -# Nuget packages function stub. -################################################################################ -function(use_package TARGET PACKAGE VERSION) - message(WARNING "No implementation of use_package. Create yours. " - "Package \"${PACKAGE}\" with version \"${VERSION}\" " - "for target \"${TARGET}\" is ignored!") -endfunction() - -################################################################################ -# Common utils -################################################################################ -include(CMake/Utils.cmake) - -################################################################################ -# Additional Global Settings(add specific info there) -################################################################################ -include(CMake/GlobalSettingsInclude.cmake OPTIONAL) - -################################################################################ -# Use solution folders feature -################################################################################ -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -################################################################################ -# Sub-projects -################################################################################ -add_subdirectory(GEMS_3) - From d8724e1e4c3fbc910549ba7fcea34941884b047a Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Tue, 27 May 2025 01:56:44 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменено решение под реализацию архитектуры C++ --- Board.cpp | 333 +++++++++++++++++++++++++++++++++++------------------- Board.h | 116 +++++++++++++++---- Config.h | 100 +++++++++++----- Game.cpp | 2 +- Gem.cpp | 35 +++--- Gem.h | 10 +- 6 files changed, 409 insertions(+), 187 deletions(-) diff --git a/Board.cpp b/Board.cpp index c3ab8aa..c9bd8d8 100644 --- a/Board.cpp +++ b/Board.cpp @@ -5,6 +5,90 @@ #include #include #include +#include +// IdleState +void IdleState::enter(Board& board) { /* ... */ } +GameStateBase* IdleState::update(Board& board) { + return nullptr; +} +void IdleState::exit(Board& board) { /* ... */ } +bool IdleState::isIdle() const { return true; } + +// GemSelectedState +void GemSelectedState::enter(Board& board) { /* ... */ } +GameStateBase* GemSelectedState::update(Board& board) { + return nullptr; +} +void GemSelectedState::exit(Board& board) { /* ... */ } +// SwappingState +void SwappingState::enter(Board& board) { /* ... */ } +GameStateBase* SwappingState::update(Board& board) { + std::set matchesAfterSwap = board.findMatches(); + if (matchesAfterSwap.empty()) { + board.swapGems(board.getSelectedGemCoords(), board.getSelectedGemCoords()); + return new IdleState(); + } + else { + return new CheckingState(); + } +} +void SwappingState::exit(Board& board) { /* ... */ } + +// CheckingState +void CheckingState::enter(Board& board) { /* ... */ } +GameStateBase* CheckingState::update(Board& board) { + std::set matches = board.findMatches(); + if (!matches.empty()) { + auto destructionResult = board.destroyGems(matches); + board.recentlyDestroyedGems = destructionResult.second; + return new DestroyingState(); + } + else { + return new IdleState(); + } +} +void CheckingState::exit(Board& board) { /* ... */ } + +// DestroyingState +void DestroyingState::enter(Board& board) { /* ... */ } +GameStateBase* DestroyingState::update(Board& board) { + return new ApplyingBonusState(); +} +void DestroyingState::exit(Board& board) { /* ... */ } + +// FallingState +void FallingState::enter(Board& board) { /* ... */ } +GameStateBase* FallingState::update(Board& board) { + if (board.applyGravity()) { + return new RefillingState(); + } + else { + return new CheckingState(); + } +} +void FallingState::exit(Board& board) { /* ... */ } + +// RefillingState +void RefillingState::enter(Board& board) { /* ... */ } +GameStateBase* RefillingState::update(Board& board) { + if (board.refillBoard()) { + return new CheckingState(); + } + else { + return new IdleState(); + } +} +void RefillingState::exit(Board& board) { /* ... */ } + +// ApplyingBonusState +void ApplyingBonusState::enter(Board& board) { /* ... */ } +GameStateBase* ApplyingBonusState::update(Board& board) { + board.trySpawnBonus(board.recentlyDestroyedGems); + board.recentlyDestroyedGems.clear(); + return new FallingState(); +} +void ApplyingBonusState::exit(Board& board) { /* ... */ } + Board::Board(int w, int h, sf::Texture& textureSheet) : width(w), @@ -18,10 +102,7 @@ Board::Board(int w, int h, sf::Texture& textureSheet) bonusTypeDist(0, 1), gridXDist(0, w - 1), gridYDist(0, h - 1), - needsMatchCheck(true), - needsGravityCheck(false), - needsRefillCheck(false), - needsBonusProcessing(false) + currentState(std::make_unique()) { grid.resize(height); for (int r = 0; r < height; ++r) { @@ -30,6 +111,17 @@ Board::Board(int w, int h, sf::Texture& textureSheet) initializeBoard(); } + +void Board::setState(std::unique_ptr newState) { + if (currentState) { + currentState->exit(*this); + } + currentState = std::move(newState); + if (currentState) { + currentState->enter(*this); + } +} + void Board::initializeBoard() { for (int r = 0; r < height; ++r) { for (int c = 0; c < width; ++c) { @@ -37,30 +129,37 @@ void Board::initializeBoard() { createRandomGem(r, c); } while ( (c >= 2 && - grid[r][c]->getColor() == grid[r][c - 1]->getColor() && - grid[r][c]->getColor() == grid[r][c - 2]->getColor()) + grid[r][c]->getColor()->getIndex() == grid[r][c - 1]->getColor()->getIndex() && + grid[r][c]->getColor()->getIndex() == grid[r][c - 2]->getColor()->getIndex()) || (r >= 2 && - grid[r][c]->getColor() == grid[r - 1][c]->getColor() && - grid[r][c]->getColor() == grid[r - 2][c]->getColor()) + grid[r][c]->getColor()->getIndex() == grid[r - 1][c]->getColor()->getIndex() && + grid[r][c]->getColor()->getIndex() == grid[r - 2][c]->getColor()->getIndex()) ); } } - needsMatchCheck = false; - needsGravityCheck = false; - needsRefillCheck = false; - needsBonusProcessing = false; - + setState(std::make_unique()); } -void Board::createGem(int r, int c, GemColor color) { +void Board::createGem(int r, int c, std::unique_ptr color) { if (isValidCoords(r, c)) { - grid[r][c] = std::make_unique(color, c, r, textureSheetRef); + grid[r][c] = std::make_unique(std::move(color), c, r, textureSheetRef); } } + void Board::createRandomGem(int r, int c) { if (isValidCoords(r, c)) { - GemColor randomColor = static_cast(gemColorDist(rng)); - grid[r][c] = std::make_unique(randomColor, c, r, textureSheetRef); + int randomColorIndex = gemColorDist(rng); + std::unique_ptr randomColor; + switch (randomColorIndex) { + case 0: randomColor = std::make_unique(); break; + case 1: randomColor = std::make_unique(); break; + case 2: randomColor = std::make_unique(); break; + case 3: randomColor = std::make_unique(); break; + case 4: randomColor = std::make_unique(); break; + case 5: randomColor = std::make_unique(); break; + default: randomColor = std::make_unique(); break; + } + grid[r][c] = std::make_unique(std::move(randomColor), c, r, textureSheetRef); grid[r][c]->setGridPosition(c, r); } } @@ -75,68 +174,34 @@ void Board::draw(sf::RenderWindow& window) { } } } + bool Board::update() { - bool changed = false; - - if (needsBonusProcessing) { - trySpawnBonus(recentlyDestroyedGems); - recentlyDestroyedGems.clear(); - needsBonusProcessing = false; - needsMatchCheck = true; - changed = true; - } - else if (needsMatchCheck) { - std::set matches = findMatches(); - if (!matches.empty()) { - auto destructionResult = destroyGems(matches); - int destroyedCount = destructionResult.first; - recentlyDestroyedGems = destructionResult.second; - - if (destroyedCount > 0) { - needsGravityCheck = true; - needsBonusProcessing = true; - changed = true; - } - needsMatchCheck = false; - } - else { - needsMatchCheck = false; - } - } - else if (needsGravityCheck) { - if (applyGravity()) { - needsRefillCheck = true; - changed = true; - } - needsGravityCheck = false; - } - else if (needsRefillCheck) { - if (refillBoard()) { - needsMatchCheck = true; - changed = true; - } - needsRefillCheck = false; + GameStateBase* nextState = currentState->update(*this); + if (nextState != nullptr) { + setState(std::unique_ptr(nextState)); + return true; } - - return changed; + return false; } bool Board::handleMouseClick(sf::Vector2i pixelCoords) { - if (!isIdle()) return false; + if (!isIdle() && !gemSelected) + return false; int gridX = pixelCoords.x / GEM_SIZE; int gridY = pixelCoords.y / GEM_SIZE; GridCoord clickedCoords(gridX, gridY); if (!isValidCoords(clickedCoords)) { - gemSelected = false; + setState(std::make_unique()); return false; } if (!gemSelected) { selectedGemCoords = clickedCoords; gemSelected = true; + setState(std::make_unique()); return true; } else { @@ -144,45 +209,42 @@ bool Board::handleMouseClick(sf::Vector2i pixelCoords) { if (secondClickCoords == selectedGemCoords) { gemSelected = false; + setState(std::make_unique()); return true; } if (areAdjacent(selectedGemCoords, secondClickCoords)) { swapGems(selectedGemCoords, secondClickCoords); - std::set matchesAfterSwap = findMatches(); - - if (matchesAfterSwap.empty()) { - swapGems(selectedGemCoords, secondClickCoords); - gemSelected = false; - return false; - } - else { - needsMatchCheck = true; - gemSelected = false; - return true; - } + gemSelected = false; + setState(std::make_unique()); + return true; } else { selectedGemCoords = secondClickCoords; - gemSelected = true; + setState(std::make_unique()); return true; } } } + bool Board::isIdle() const { - return !needsMatchCheck && !needsGravityCheck && !needsRefillCheck && !needsBonusProcessing; + return currentState->isIdle(); } + bool Board::isValidCoords(int r, int c) const { return r >= 0 && r < height && c >= 0 && c < width; } + bool Board::isValidCoords(GridCoord coords) const { return isValidCoords(coords.y, coords.x); } + bool Board::areAdjacent(GridCoord p1, GridCoord p2) const { int dx = std::abs(p1.x - p2.x); int dy = std::abs(p1.y - p2.y); return (dx == 1 && dy == 0) || (dx == 0 && dy == 1); } + void Board::swapGems(GridCoord p1, GridCoord p2) { if (!isValidCoords(p1) || !isValidCoords(p2)) return; @@ -197,13 +259,14 @@ void Board::swapGems(GridCoord p1, GridCoord p2) { grid[p2.y][p2.x]->setGridPosition(p2.x, p2.y); } } + std::set Board::findMatches() { std::set matches; for (int r = 0; r < height; ++r) { int consecutive = 1; - GemColor currentColor = GemColor::None; + const GemColorBase* currentColor = nullptr; for (int c = 0; c < width; ++c) { - if (grid[r][c] && grid[r][c]->getColor() == currentColor) { + if (grid[r][c] && grid[r][c]->getColor() && grid[r][c]->getColor()->getIndex() == (currentColor ? currentColor->getIndex() : -2)) { consecutive++; } else { @@ -212,7 +275,7 @@ std::set Board::findMatches() { matches.insert({ i, r }); } } - currentColor = grid[r][c] ? grid[r][c]->getColor() : GemColor::None; + currentColor = grid[r][c] ? grid[r][c]->getColor() : nullptr; consecutive = 1; } } @@ -225,9 +288,9 @@ std::set Board::findMatches() { for (int c = 0; c < width; ++c) { int consecutive = 1; - GemColor currentColor = GemColor::None; + const GemColorBase* currentColor = nullptr; for (int r = 0; r < height; ++r) { - if (grid[r][c] && grid[r][c]->getColor() == currentColor) { + if (grid[r][c] && grid[r][c]->getColor() && grid[r][c]->getColor()->getIndex() == (currentColor ? currentColor->getIndex() : -2)) { consecutive++; } else { @@ -236,7 +299,7 @@ std::set Board::findMatches() { matches.insert({ c, i }); } } - currentColor = grid[r][c] ? grid[r][c]->getColor() : GemColor::None; + currentColor = grid[r][c] ? grid[r][c]->getColor() : nullptr; consecutive = 1; } } @@ -249,9 +312,10 @@ std::set Board::findMatches() { return matches; } -std::pair>> Board::destroyGems(const std::set& coordsToDestroy) { + +std::pair>> Board::destroyGems(const std::set& coordsToDestroy) { int count = 0; - std::vector> destroyedInfo; + std::vector> destroyedInfo; for (const auto& coord : coordsToDestroy) { if (isValidCoords(coord) && grid[coord.y][coord.x]) { @@ -262,6 +326,7 @@ std::pair>> Board::destroyGems(c } return { count, destroyedInfo }; } + bool Board::applyGravity() { bool gemsFell = false; for (int c = 0; c < width; ++c) { @@ -283,6 +348,7 @@ bool Board::applyGravity() { } return gemsFell; } + bool Board::refillBoard() { bool newGemsAdded = false; for (int c = 0; c < width; ++c) { @@ -299,12 +365,13 @@ bool Board::refillBoard() { } return newGemsAdded; } -void Board::trySpawnBonus(const std::vector>& destroyedGemsInfo) { + +void Board::trySpawnBonus(const std::vector>& destroyedGemsInfo) { for (const auto& info : destroyedGemsInfo) { if (bonusChanceDist(rng) <= BONUS_CHANCE) { int bonusType = bonusTypeDist(rng); GridCoord bonusOrigin = info.first; - GemColor bonusOriginColor = info.second; + const GemColorBase* bonusOriginColor = info.second; int targetR, targetC; int attempts = 0; const int maxAttempts = 20; @@ -324,16 +391,26 @@ void Board::trySpawnBonus(const std::vector>& des else { applyBombBonus(targetPos); } - needsMatchCheck = true; } } } } -void Board::applyRepaintBonus(GridCoord targetPos, GemColor sourceColor) { - if (!isValidCoords(targetPos) || sourceColor == GemColor::None) return; + +void Board::applyRepaintBonus(GridCoord targetPos, const GemColorBase* sourceColor) { + if (!isValidCoords(targetPos) || !sourceColor || sourceColor->getIndex() == -1) return; if (grid[targetPos.y][targetPos.x]) { - grid[targetPos.y][targetPos.x]->setColor(sourceColor); + std::unique_ptr newColor; + switch (sourceColor->getIndex()) { + case 0: newColor = std::make_unique(); break; + case 1: newColor = std::make_unique(); break; + case 2: newColor = std::make_unique(); break; + case 3: newColor = std::make_unique(); break; + case 4: newColor = std::make_unique(); break; + case 5: newColor = std::make_unique(); break; + default: newColor = std::make_unique(); break; + } + grid[targetPos.y][targetPos.x]->setColor(std::move(newColor)); } std::vector candidates; @@ -352,12 +429,21 @@ void Board::applyRepaintBonus(GridCoord targetPos, GemColor sourceColor) { int count = 0; for (const auto& pos : candidates) { if (count >= 2) break; - grid[pos.y][pos.x]->setColor(sourceColor); + std::unique_ptr newColor; + switch (sourceColor->getIndex()) { + case 0: newColor = std::make_unique(); break; + case 1: newColor = std::make_unique(); break; + case 2: newColor = std::make_unique(); break; + case 3: newColor = std::make_unique(); break; + case 4: newColor = std::make_unique(); break; + case 5: newColor = std::make_unique(); break; + default: newColor = std::make_unique(); break; + } + grid[pos.y][pos.x]->setColor(std::move(newColor)); count++; } - - needsMatchCheck = true; } + void Board::applyBombBonus(GridCoord targetPos) { std::set gemsToDestroy; @@ -382,38 +468,53 @@ void Board::applyBombBonus(GridCoord targetPos) { if (!gemsToDestroy.empty()) { destroyGems(gemsToDestroy); - needsGravityCheck = true; } } + bool Board::hasPossibleMoves() const { for (int r = 0; r < height; ++r) { for (int c = 0; c < width; ++c) { if (!grid[r][c]) continue; - GemColor current = grid[r][c]->getColor(); - if (c + 1 < width && grid[r][c + 1]) { - GemColor right = grid[r][c + 1]->getColor(); - if (c + 2 < width && grid[r][c + 2] && grid[r][c + 2]->getColor() == current) return true; - if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() == current && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current) return true; - if (r + 2 < height && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current && grid[r + 2][c + 1] && grid[r + 2][c + 1]->getColor() == current) return true; - if (r - 2 >= 0 && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() == current && grid[r - 2][c + 1] && grid[r - 2][c + 1]->getColor() == current) return true; - if (c - 1 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() == right) return true; - if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c] && grid[r - 1][c]->getColor() == right && grid[r + 1][c] && grid[r + 1][c]->getColor() == right) return true; - if (r + 2 < height && grid[r + 1][c] && grid[r + 1][c]->getColor() == right && grid[r + 2][c] && grid[r + 2][c]->getColor() == right) return true; - if (r - 2 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() == right && grid[r - 2][c] && grid[r - 2][c]->getColor() == right) return true; - + const GemColorBase* current = grid[r][c]->getColor(); + if (!current || current->getIndex() == -1) continue; + if (c + 1 < width && grid[r][c + 1] && grid[r][c + 1]->getColor() && grid[r][c + 1]->getColor()->getIndex() != -1) { + const GemColorBase* right = grid[r][c + 1]->getColor(); + if (c + 2 < width && grid[r][c + 2] && grid[r][c + 2]->getColor() && grid[r][c + 2]->getColor()->getIndex() == current->getIndex()) return true; + if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() && grid[r - 1][c + 1]->getColor()->getIndex() == current->getIndex() && + grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() && grid[r + 1][c + 1]->getColor()->getIndex() == current->getIndex()) return true; + if (r + 2 < height && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() && grid[r + 1][c + 1]->getColor()->getIndex() == current->getIndex() && + grid[r + 2][c + 1] && grid[r + 2][c + 1]->getColor() && grid[r + 2][c + 1]->getColor()->getIndex() == current->getIndex()) return true; + + if (r - 2 >= 0 && grid[r - 1][c + 1] && grid[r - 1][c + 1]->getColor() && grid[r - 1][c + 1]->getColor()->getIndex() == current->getIndex() && + grid[r - 2][c + 1] && grid[r - 2][c + 1]->getColor() && grid[r - 2][c + 1]->getColor()->getIndex() == current->getIndex()) return true; + if (c - 1 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() && grid[r][c - 1]->getColor()->getIndex() == right->getIndex()) return true; + if (r - 1 >= 0 && r + 1 < height && grid[r - 1][c] && grid[r - 1][c]->getColor() && grid[r - 1][c]->getColor()->getIndex() == right->getIndex() && + grid[r + 1][c] && grid[r + 1][c]->getColor() && grid[r + 1][c]->getColor()->getIndex() == right->getIndex()) return true; + if (r + 2 < height && grid[r + 1][c] && grid[r + 1][c]->getColor() && grid[r + 1][c]->getColor()->getIndex() == right->getIndex() && + grid[r + 2][c] && grid[r + 2][c]->getColor() && grid[r + 2][c]->getColor()->getIndex() == right->getIndex()) return true; + + if (r - 2 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() && grid[r - 1][c]->getColor()->getIndex() == right->getIndex() && + grid[r - 2][c] && grid[r - 2][c]->getColor() && grid[r - 2][c]->getColor()->getIndex() == right->getIndex()) return true; } - if (r + 1 < height && grid[r + 1][c]) { - GemColor down = grid[r + 1][c]->getColor(); - if (r + 2 < height && grid[r + 2][c] && grid[r + 2][c]->getColor() == current) return true; - if (c - 1 >= 0 && c + 1 < width && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() == current && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current) return true; - if (c + 2 < width && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() == current && grid[r + 1][c + 2] && grid[r + 1][c + 2]->getColor() == current) return true; - if (c - 2 >= 0 && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() == current && grid[r + 1][c - 2] && grid[r + 1][c - 2]->getColor() == current) return true; - - if (r - 1 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() == down) return true; - if (c - 1 >= 0 && c + 1 < width && grid[r][c - 1] && grid[r][c - 1]->getColor() == down && grid[r][c + 1] && grid[r][c + 1]->getColor() == down) return true; - if (c + 2 < width && grid[r][c + 1] && grid[r][c + 1]->getColor() == down && grid[r][c + 2] && grid[r][c + 2]->getColor() == down) return true; - if (c - 2 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() == down && grid[r][c - 2] && grid[r][c - 2]->getColor() == down) return true; + if (r + 1 < height && grid[r + 1][c] && grid[r + 1][c]->getColor() && grid[r + 1][c]->getColor()->getIndex() != -1) { + const GemColorBase* down = grid[r + 1][c]->getColor(); + if (r + 2 < height && grid[r + 2][c] && grid[r + 2][c]->getColor() && grid[r + 2][c]->getColor()->getIndex() == current->getIndex()) return true; + if (c - 1 >= 0 && c + 1 < width && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() && grid[r + 1][c - 1]->getColor()->getIndex() == current->getIndex() && + grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() && grid[r + 1][c + 1]->getColor()->getIndex() == current->getIndex()) return true; + if (c + 2 < width && grid[r + 1][c + 1] && grid[r + 1][c + 1]->getColor() && grid[r + 1][c + 1]->getColor()->getIndex() == current->getIndex() && + grid[r + 1][c + 2] && grid[r + 1][c + 2]->getColor() && grid[r + 1][c + 2]->getColor()->getIndex() == current->getIndex()) return true; + + if (c - 2 >= 0 && grid[r + 1][c - 1] && grid[r + 1][c - 1]->getColor() && grid[r + 1][c - 1]->getColor()->getIndex() == current->getIndex() && + grid[r + 1][c - 2] && grid[r + 1][c - 2]->getColor() && grid[r + 1][c - 2]->getColor()->getIndex() == current->getIndex()) return true; + if (r - 1 >= 0 && grid[r - 1][c] && grid[r - 1][c]->getColor() && grid[r - 1][c]->getColor()->getIndex() == down->getIndex()) return true; + if (c - 1 >= 0 && c + 1 < width && grid[r][c - 1] && grid[r][c - 1]->getColor() && grid[r][c - 1]->getColor()->getIndex() == down->getIndex() && + grid[r][c + 1] && grid[r][c + 1]->getColor() && grid[r][c + 1]->getColor()->getIndex() == down->getIndex()) return true; + if (c + 2 < width && grid[r][c + 1] && grid[r][c + 1]->getColor() && grid[r][c + 1]->getColor()->getIndex() == down->getIndex() && + grid[r][c + 2] && grid[r][c + 2]->getColor() && grid[r][c + 2]->getColor()->getIndex() == down->getIndex()) return true; + + if (c - 2 >= 0 && grid[r][c - 1] && grid[r][c - 1]->getColor() && grid[r][c - 1]->getColor()->getIndex() == down->getIndex() && + grid[r][c - 2] && grid[r][c - 2]->getColor() && grid[r][c - 2]->getColor()->getIndex() == down->getIndex()) return true; } } } diff --git a/Board.h b/Board.h index e79d1d2..d4ecf78 100644 --- a/Board.h +++ b/Board.h @@ -6,16 +6,108 @@ #include #include "Gem.h" #include "Config.h" +#include +class IdleState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; + bool isIdle() const override; +}; + +class GemSelectedState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class SwappingState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class CheckingState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class DestroyingState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class FallingState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class RefillingState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + +class ApplyingBonusState : public GameStateBase { +public: + void enter(Board& board) override; + GameStateBase* update(Board& board) override; + void exit(Board& board) override; +}; + class Board { public: Board(int width, int height, sf::Texture& textureSheet); Board(const Board&) = delete; Board& operator=(const Board&) = delete; + void draw(sf::RenderWindow& window); bool update(); bool handleMouseClick(sf::Vector2i pixelCoords); bool isIdle() const; + void setState(std::unique_ptr newState); + GridCoord getSelectedGemCoords() const { return selectedGemCoords; } + void setSelectedGemCoords(GridCoord coords) { selectedGemCoords = coords; } + bool isGemSelected() const { return gemSelected; } + void setGemSelected(bool selected) { gemSelected = selected; } + + bool isValidCoords(int r, int c) const; + bool isValidCoords(GridCoord coords) const; + bool areAdjacent(GridCoord p1, GridCoord p2) const; + void swapGems(GridCoord p1, GridCoord p2); + std::set findMatches(); + std::pair>> destroyGems(const std::set& coordsToDestroy); + bool applyGravity(); + bool refillBoard(); + void trySpawnBonus(const std::vector>& destroyedGemsInfo); + void applyRepaintBonus(GridCoord targetPos, const GemColorBase* sourceColor); + void applyBombBonus(GridCoord targetPos); + void createGem(int r, int c, std::unique_ptr color); + void createRandomGem(int r, int c); + std::vector>>& getGrid() { return grid; } + const std::vector>>& getGrid() const { return grid; } + sf::Texture& getTextureSheet() { return textureSheetRef; } + std::mt19937& getRNG() { return rng; } + std::uniform_int_distribution& getGemColorDist() { return gemColorDist; } + std::uniform_real_distribution& getBonusChanceDist() { return bonusChanceDist; } + std::uniform_int_distribution& getBonusTypeDist() { return bonusTypeDist; } + std::uniform_int_distribution& getGridXDist() { return gridXDist; } + std::uniform_int_distribution& getGridYDist() { return gridYDist; } + int getWidth() const { return width; } + int getHeight() const { return height; } + std::vector> recentlyDestroyedGems; + + bool hasPossibleMoves() const; private: @@ -25,31 +117,15 @@ class Board { std::vector>> grid; GridCoord selectedGemCoords; bool gemSelected; + std::mt19937 rng; std::uniform_int_distribution gemColorDist; std::uniform_real_distribution bonusChanceDist; std::uniform_int_distribution bonusTypeDist; std::uniform_int_distribution gridXDist; std::uniform_int_distribution gridYDist; - bool isValidCoords(int r, int c) const; - bool isValidCoords(GridCoord coords) const; - bool areAdjacent(GridCoord p1, GridCoord p2) const; - void swapGems(GridCoord p1, GridCoord p2); - std::set findMatches(); - std::pair>> destroyGems(const std::set& coordsToDestroy); - bool applyGravity(); - bool refillBoard(); - void trySpawnBonus(const std::vector>& destroyedGemsInfo); - void applyRepaintBonus(GridCoord targetPos, GemColor sourceColor); - void applyBombBonus(GridCoord targetPos); - void initializeBoard(); - bool hasPossibleMoves() const; - void createGem(int r, int c, GemColor color); - void createRandomGem(int r, int c); - bool needsMatchCheck; - bool needsGravityCheck; - bool needsRefillCheck; - bool needsBonusProcessing; - std::vector> recentlyDestroyedGems; + std::unique_ptr currentState; + + void initializeBoard(); }; \ No newline at end of file diff --git a/Config.h b/Config.h index 416f3ba..fc40d7f 100644 --- a/Config.h +++ b/Config.h @@ -1,6 +1,10 @@ #pragma once #include +#include +#include +#include + constexpr int GRID_WIDTH = 8; constexpr int GRID_HEIGHT = 8; constexpr int GEM_SIZE = 64; @@ -10,47 +14,83 @@ const int NUM_GEM_COLORS = 6; const float BONUS_CHANCE = 0.1f; const int REPAINT_BONUS_RADIUS = 3; const int BOMB_BONUS_COUNT = 5; -enum class GemColor { - Red = 0, - Green, - Blue, - Yellow, - Purple, - Orange, - None -}; -enum class GameState { - Idle, - GemSelected, - Swapping, - Checking, - Destroying, - Falling, - Refilling, - ApplyingBonus +class GemColorBase { +public: + virtual ~GemColorBase() = default; + virtual int getIndex() const = 0; + virtual sf::Color getSFMLColor() const = 0; +}; +class RedGemColor : public GemColorBase { +public: + int getIndex() const override { return 0; } + sf::Color getSFMLColor() const override { return sf::Color::Red; } }; -namespace sf { - inline bool operator<(const Vector2i& a, const Vector2i& b) { - if (a.x == b.x) { - return a.y < b.y; - } - return a.x < b.x; - } -} +class GreenGemColor : public GemColorBase { +public: + int getIndex() const override { return 1; } + sf::Color getSFMLColor() const override { return sf::Color::Green; } +}; + +class BlueGemColor : public GemColorBase { +public: + int getIndex() const override { return 2; } + sf::Color getSFMLColor() const override { return sf::Color::Blue; } +}; + +class YellowGemColor : public GemColorBase { +public: + int getIndex() const override { return 3; } + sf::Color getSFMLColor() const override { return sf::Color::Yellow; } +}; + +class PurpleGemColor : public GemColorBase { +public: + int getIndex() const override { return 4; } + sf::Color getSFMLColor() const override { return sf::Color::Magenta; } +}; + +class OrangeGemColor : public GemColorBase { +public: + int getIndex() const override { return 5; } + sf::Color getSFMLColor() const override { return sf::Color(255, 165, 0); } +}; + +class NoneGemColor : public GemColorBase { +public: + int getIndex() const override { return -1; } + sf::Color getSFMLColor() const override { return sf::Color::Black; } +}; +class GameStateBase { +public: + virtual ~GameStateBase() = default; + virtual void enter(class Board& board) = 0; + virtual GameStateBase* update(class Board& board) = 0; + virtual void exit(class Board& board) = 0; + virtual bool isIdle() const { return false; } +}; struct GridCoord { int x; int y; GridCoord() : x(0), y(0) {} - GridCoord(int x, int y) : x(x), y(y) {} - bool operator<(const GridCoord& other) const { - return (y < other.y) || (y == other.y && x < other.x); + if (y == other.y) { + return x < other.x; + } + return y < other.y; } bool operator==(const GridCoord& other) const { return x == other.x && y == other.y; } -}; \ No newline at end of file +}; +namespace sf { + inline bool operator<(const Vector2i& a, const Vector2i& b) { + if (a.x == b.x) { + return a.y < b.y; + } + return a.x < b.x; + } +} \ No newline at end of file diff --git a/Game.cpp b/Game.cpp index 6886980..fadad52 100644 --- a/Game.cpp +++ b/Game.cpp @@ -8,7 +8,7 @@ #include Game::Game() - : window(sf::VideoMode({WINDOW_WIDTH, WINDOW_HEIGHT}), "SFML Gems Game", sf::Style::Default) + : window(sf::VideoMode({ WINDOW_WIDTH, WINDOW_HEIGHT }), "SFML Gems Game", sf::Style::Default) { window.setFramerateLimit(60); loadResources(); diff --git a/Gem.cpp b/Gem.cpp index 2ae5693..0c7bffb 100644 --- a/Gem.cpp +++ b/Gem.cpp @@ -2,11 +2,10 @@ #include "Config.h" #include #include - -Gem::Gem(GemColor c, int x, int y, sf::Texture& textureSheet) - : color(c), gridPosition(x, y), textureSheetRef(textureSheet), sprite(textureSheet) +Gem::Gem(std::unique_ptr c, int x, int y, sf::Texture& textureSheet) + : color(std::move(c)), gridPosition(x, y), textureSheetRef(textureSheet), sprite(textureSheet) { - if (color != GemColor::None) { + if (color && color->getIndex() != -1) { updateTextureRect(); } setGridPosition(x, y); @@ -22,30 +21,36 @@ void Gem::setGridPosition(int gridX, int gridY) { } void Gem::updateTextureRect() { - if (color == GemColor::None) return; + if (!color || color->getIndex() == -1) return; - int colorIndex = static_cast(color); + int colorIndex = color->getIndex(); if (colorIndex < 0 || colorIndex >= NUM_GEM_COLORS) { throw std::runtime_error("Invalid GemColor index in updateTextureRect"); } sprite.setTexture(textureSheetRef); sprite.setTextureRect(sf::IntRect( - sf::Vector2i(colorIndex * GEM_SIZE, 0), - sf::Vector2i(GEM_SIZE, GEM_SIZE) + sf::Vector2i(colorIndex * GEM_SIZE, 0), + sf::Vector2i(GEM_SIZE, GEM_SIZE) )); } - -GemColor Gem::getColor() const { - return color; +const GemColorBase* Gem::getColor() const { + return color.get(); } void Gem::draw(sf::RenderWindow& window) { - if (color != GemColor::None) { + if (color && color->getIndex() != -1) { window.draw(sprite); } } - -void Gem::setColor(GemColor newColor) { - color = newColor; +void Gem::setColor(std::unique_ptr newColor) { + color = std::move(newColor); updateTextureRect(); +} + +sf::Vector2f Gem::getVisualPosition() const { + return sprite.getPosition(); +} + +void Gem::setVisualPosition(float x, float y) { + sprite.setPosition(sf::Vector2f(x, y)); } \ No newline at end of file diff --git a/Gem.h b/Gem.h index 69db7fb..89ef38e 100644 --- a/Gem.h +++ b/Gem.h @@ -1,23 +1,23 @@ - #pragma once #include #include "Config.h" +#include class Gem { public: - Gem(GemColor color, int gridX, int gridY, sf::Texture& textureSheet); + Gem(std::unique_ptr c, int gridX, int gridY, sf::Texture& textureSheet); void draw(sf::RenderWindow& window); void setGridPosition(int gridX, int gridY); GridCoord getGridPosition() const; - GemColor getColor() const; - void setColor(GemColor newColor); + const GemColorBase* getColor() const; + void setColor(std::unique_ptr newColor); sf::Vector2f getVisualPosition() const; void setVisualPosition(float x, float y); private: sf::Sprite sprite; - GemColor color; + std::unique_ptr color; GridCoord gridPosition; sf::Texture& textureSheetRef; void updateTextureRect(); From 690df034f3254827a62dc2cccafd90661c3b5cdb Mon Sep 17 00:00:00 2001 From: TToJlkoBHuK <28arhalex@gmail.com> Date: Mon, 29 Sep 2025 22:32:39 +0300 Subject: [PATCH 6/6] work version --- Board.cpp | 22 ++++++++++++---------- Board.h | 8 ++++---- Config.h | 2 +- Game.h | 2 ++ 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Board.cpp b/Board.cpp index c9bd8d8..9530b49 100644 --- a/Board.cpp +++ b/Board.cpp @@ -1,3 +1,5 @@ +// This is a personal academic project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com #include "Board.h" #include #include @@ -313,13 +315,13 @@ std::set Board::findMatches() { return matches; } -std::pair>> Board::destroyGems(const std::set& coordsToDestroy) { +std::pair>> Board::destroyGems(const std::set& coordsToDestroy) { int count = 0; - std::vector> destroyedInfo; + std::vector> destroyedInfo; for (const auto& coord : coordsToDestroy) { if (isValidCoords(coord) && grid[coord.y][coord.x]) { - destroyedInfo.push_back({ coord, grid[coord.y][coord.x]->getColor() }); + destroyedInfo.push_back({ coord, grid[coord.y][coord.x]->getColor()->getIndex() }); grid[coord.y][coord.x].reset(); count++; } @@ -366,12 +368,12 @@ bool Board::refillBoard() { return newGemsAdded; } -void Board::trySpawnBonus(const std::vector>& destroyedGemsInfo) { +void Board::trySpawnBonus(const std::vector>& destroyedGemsInfo) { for (const auto& info : destroyedGemsInfo) { if (bonusChanceDist(rng) <= BONUS_CHANCE) { int bonusType = bonusTypeDist(rng); GridCoord bonusOrigin = info.first; - const GemColorBase* bonusOriginColor = info.second; + int bonusOriginColorIndex = info.second; int targetR, targetC; int attempts = 0; const int maxAttempts = 20; @@ -386,7 +388,7 @@ void Board::trySpawnBonus(const std::vectorgetIndex() == -1) return; +void Board::applyRepaintBonus(GridCoord targetPos, int sourceColorIndex) { + if (!isValidCoords(targetPos) || sourceColorIndex == -1) return; if (grid[targetPos.y][targetPos.x]) { std::unique_ptr newColor; - switch (sourceColor->getIndex()) { + switch (sourceColorIndex) { case 0: newColor = std::make_unique(); break; case 1: newColor = std::make_unique(); break; case 2: newColor = std::make_unique(); break; @@ -430,7 +432,7 @@ void Board::applyRepaintBonus(GridCoord targetPos, const GemColorBase* sourceCol for (const auto& pos : candidates) { if (count >= 2) break; std::unique_ptr newColor; - switch (sourceColor->getIndex()) { + switch (sourceColorIndex) { case 0: newColor = std::make_unique(); break; case 1: newColor = std::make_unique(); break; case 2: newColor = std::make_unique(); break; diff --git a/Board.h b/Board.h index d4ecf78..571bfbf 100644 --- a/Board.h +++ b/Board.h @@ -86,11 +86,11 @@ class Board { bool areAdjacent(GridCoord p1, GridCoord p2) const; void swapGems(GridCoord p1, GridCoord p2); std::set findMatches(); - std::pair>> destroyGems(const std::set& coordsToDestroy); + std::pair>> destroyGems(const std::set& coordsToDestroy); bool applyGravity(); bool refillBoard(); - void trySpawnBonus(const std::vector>& destroyedGemsInfo); - void applyRepaintBonus(GridCoord targetPos, const GemColorBase* sourceColor); + void trySpawnBonus(const std::vector>& destroyedGemsInfo); + void applyRepaintBonus(GridCoord targetPos, int sourceColorIndex); void applyBombBonus(GridCoord targetPos); void createGem(int r, int c, std::unique_ptr color); void createRandomGem(int r, int c); @@ -105,7 +105,7 @@ class Board { std::uniform_int_distribution& getGridYDist() { return gridYDist; } int getWidth() const { return width; } int getHeight() const { return height; } - std::vector> recentlyDestroyedGems; + std::vector> recentlyDestroyedGems; bool hasPossibleMoves() const; diff --git a/Config.h b/Config.h index fc40d7f..56bf035 100644 --- a/Config.h +++ b/Config.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include diff --git a/Game.h b/Game.h index 53f0bf9..cbb5936 100644 --- a/Game.h +++ b/Game.h @@ -1,3 +1,5 @@ +// This is a personal academic project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com #pragma once #include