#include <vk.hpp> #include "SDL.h" #include <random> #include <cmath> #include <sstream> #include <ctime> #include <unistd.h> #include <climits> #include "argz.hpp" constexpr int STARTX = 184; constexpr int STARTY = 61; constexpr int GRID_WIDTH = 8; constexpr int GRID_HEIGHT = 18; constexpr int BLOCK_SIZE = 31; constexpr int BLOCK_HEIGHT = 15; constexpr int BLOCK_SPACING = 1; constexpr int NEXT_PANEL_X = 450; constexpr int NEXT_PANEL_Y = 180; constexpr int FLASH_DURATION = 300; enum { BLOCK_BLACK = 0, BLOCK_YELLOW, BLOCK_ORANGE, BLOCK_LTBLUE, BLOCK_DBLUE, BLOCK_PURPLE, BLOCK_PINK, BLOCK_GRAY, BLOCK_RED, BLOCK_GREEN, BLOCK_CLEAR, BLOCK_COUNT }; enum ScreenState { SCREEN_INTRO, SCREEN_START, SCREEN_GAME, SCREEN_GAMEOVER, SCREEN_OPTIONS, SCREEN_CREDITS, SCREEN_SCORES }; struct GameData { unsigned long score; int lines; int speed; int lineamt; GameData() { newgame(); } void newgame() { score = 0; lines = 0; speed = 20; lineamt = 0; } void addline() { lines++; score += 6; lineamt++; if (lineamt >= 10) { lineamt = 0; speed = std::max(5, speed - 4); } } }; struct BlockColor { int c1, c2, c3; void randcolor() { c1 = 1 + rand() % 9; c2 = 1 + rand() % 9; c3 = 1 + rand() % 9; if (c1 == c2 && c1 == c3) randcolor(); } void shiftcolor(bool dir) { int t1 = c1, t2 = c2, t3 = c3; if (dir) { c1 = t3; c2 = t1; c3 = t2; } else { c1 = t2; c2 = t3; c3 = t1; } } }; struct GameBlock { BlockColor color; BlockColor nextcolor; int x, y; int rotation; bool horizontal; GameBlock() : x(0), y(0), rotation(0), horizontal(false) {} }; struct TileMatrix { int grid[GRID_WIDTH][GRID_HEIGHT]; GameBlock block, nextblock; GameData Game; TileMatrix() { init_matrix(); } void init_matrix() { memset(grid, 0, sizeof(grid)); Game.newgame(); block.color.randcolor(); nextblock.color.randcolor(); } }; struct Score { std::string name; int score; Score(std::string name_, int score_) : name(name_), score(score_) {} Score() : name{}, score{0} {} }; class HighScores { public: HighScores() { read(); } ~HighScores() { write(); } void addScore(const std::string &name, int score) { scores.push_back({name, score}); sort(); if (scores.size() > 10) { scores.resize(10); } } void sort() { std::sort(scores.begin(), scores.end(), [](const Score &one, const Score &two) { return one.score > two.score; }); } bool qualifiesForHighScore(int score) { if (scores.size() < 10) return true; return score > scores.back().score; } const std::vector<Score>& getScores() const { return scores; } void init() { scores.clear(); for(int i = 0; i < 10; ++i) scores.push_back(Score("Anonymous", (10 - i) * 100)); } void read() { std::fstream file; file.open("./scores.dat", std::ios::in); if(!file.is_open()) { mx::system_out << "No scores file.\n"; init(); return; } std::string input; int count = 0; while(std::getline(file, input)) { if(count >= 10) break; if(input.length() > 0) { auto pos = input.find(":"); if (pos != std::string::npos) { std::string left = input.substr(0, pos); std::string right = input.substr(pos + 1); scores.push_back(Score(left, std::strtol(right.c_str(), 0, 10))); count++; } } } file.close(); sort(); } void write() { std::fstream file; file.open("./scores.dat", std::ios::out); if(!file.is_open()) { mx::system_err << "Could not open score file..\n"; return; } for(auto &s : scores) { file << s.name << ":" << s.score << std::endl; } file.close(); } private: std::vector<Score> scores; }; class MasterPieceWindow : public mx::VKWindow { private: HighScores scores; TileMatrix matrix; ScreenState currentScreen = SCREEN_INTRO; Uint32 lastTick = 0; int cursorPos = 0; bool paused = false; int optionsCursor = 0; int difficultySetting = 1; bool enteringName = false; std::string playerName; int finalScore = 0; mx::VKSprite* scoresBackground = nullptr; mx::VKSprite* creditScreen = nullptr; int matchBonus = 0; int lastFontSize = 0; bool flashActive = false; Uint32 flashStartTime = 0; bool flashGrid[GRID_WIDTH][GRID_HEIGHT] = {{false}}; std::vector<mx::VKSprite*> grid_blocks; mx::VKSprite* gamebg = nullptr; mx::VKSprite* startScreen = nullptr; mx::VKSprite* introScreen = nullptr; public: MasterPieceWindow(const std::string& path, int wx, int wy, bool full) : mx::VKWindow("-[ Acid Drop - Vulkan ]-", wx, wy, full) { setPath(path); updateFontSize(); srand((unsigned int)time(0)); lastTick = SDL_GetTicks(); } virtual ~MasterPieceWindow() {} void initVulkan() override { mx::VKWindow::initVulkan(); initGfx(); } void onResize() override { vkDeviceWaitIdle(device); clearTextQueue(); lastFontSize = 0; updateFontSize(); } void initGfx() { std::string vertShader = util.path + "/data/sprite_vert.spv"; std::string fragShader = util.path + "/data/sprite_kaleidoscope.spv"; gamebg = createSprite(util.path + "/data/gamebg.png", util.path + "/data/sprite_vert.spv", fragShader); introScreen = createSprite(util.path + "/data/intro.png", util.path + "/data/sprite_vert.spv", util.path + "/data/sprite_bubble.spv"); startScreen = createSprite(util.path + "/data/universe.png", util.path + "/data/sprite_vert.spv", fragShader); creditScreen = createSprite(util.path + "/data/logo.png", util.path + "/data/sprite_vert.spv", util.path + "/data/sprite_time.spv"); scoresBackground = createSprite(util.path + "/data/bg.png", util.path + "/data/sprite_vert.spv", util.path + "/data/sprite_time.spv"); const char* blockFiles[] = { "block_black.png", "block_yellow.png", "block_orange.png", "block_ltblue.png", "block_dblue.png", "block_purple.png", "block_pink.png", "block_gray.png", "block_red.png", "block_green.png", "block_clear.png" }; for (int i = 0; i < BLOCK_COUNT; i++) { grid_blocks.push_back(createSprite(util.path + "/data/" + blockFiles[i], util.path + "/data/sprite_vert.spv", util.path + "/data/sprite_frag.spv")); } } void updateFontSize() { int fontSize = (int)(20.0f * ((float)h / 480.0f)); fontSize = std::max(16, std::min(128, fontSize)); if (fontSize != lastFontSize) { lastFontSize = fontSize; setFont("font.ttf", fontSize); clearTextQueue(); } } int getCharWidth() { return (int)(lastFontSize * 0.5f); } int getMenuSpacing() { return (int)(40.0f * ((float)h / 480.0f)); } int scaleY(int baseY) { return (int)(baseY * ((float)h / 480.0f)); } int centerX(const char* text) { int width, height; if (getTextDimensions(text, width, height)) { return w/2 - width/2; } return w/2 - (int)(strlen(text) * getCharWidth() / 2); } void proc() override { switch (currentScreen) { case SCREEN_INTRO: updateIntro(); break; case SCREEN_START: updateStart(); break; case SCREEN_GAME: updateGame(); break; case SCREEN_GAMEOVER: updateGameOver(); break; case SCREEN_OPTIONS: updateOptions(); break; case SCREEN_CREDITS: updateCredits(); break; case SCREEN_SCORES: updateScores(); break; } } void updateIntro() { if (introScreen) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; introScreen->setShaderParams(time_f, 1.0f, 0.8f, 1.2f); introScreen->drawSpriteRect(0, 0, w, h); } const char* introText = "Press ENTER to Start"; printText(introText, centerX(introText), h/2, {255, 255, 255, 255}); Uint32 currentTick = SDL_GetTicks(); if (currentTick - lastTick > 3000) { currentScreen = SCREEN_START; lastTick = currentTick; } } void updateStart() { if (startScreen) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; startScreen->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); startScreen->drawSpriteRect(0, 0, w, h); } int titleY = scaleY(100); int menuStartY = scaleY(180); int spacing = getMenuSpacing(); const char* title = "Liquid Acid Drop"; printText(title, centerX(title), titleY, {255, 255, 0, 255}); const char* menuItems[] = {"New Game", "High Scores", "Options", "Credits", "Quit"}; for (int i = 0; i < 5; i++) { SDL_Color col = (i == cursorPos) ? SDL_Color{255, 255, 0, 255} : SDL_Color{255, 255, 255, 255}; printText(menuItems[i], centerX(menuItems[i]), menuStartY + i * spacing, col); } printText(">>", centerX(menuItems[cursorPos]) - scaleY(30), menuStartY + cursorPos * spacing, {255, 255, 0, 255}); } void updateGame() { if (!paused) { logic(); } drawGame(); } void updateGameOver() { if (gamebg) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; gamebg->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); gamebg->drawSpriteRect(0, 0, w, h); } const char* gameOverText = "GAME OVER!"; printText(gameOverText, centerX(gameOverText), h/2 - 80, {255, 0, 0, 255}); std::ostringstream stream; stream << "Final Score: " << matrix.Game.score; std::string scoreStr = stream.str(); printText(scoreStr.c_str(), centerX(scoreStr.c_str()), h/2 - 20, {255, 255, 255, 255}); stream.str(""); stream << "Tabs: " << matrix.Game.lines; std::string linesStr = stream.str(); printText(linesStr.c_str(), centerX(linesStr.c_str()), h/2 + 40, {255, 255, 255, 255}); const char* returnText = "Press ENTER to view High Scores"; printText(returnText, centerX(returnText), h/2 + 120, {255, 255, 0, 255}); } void goToScoresScreen() { finalScore = matrix.Game.score; playerName = ""; enteringName = scores.qualifiesForHighScore(finalScore); currentScreen = SCREEN_SCORES; } void updateOptions() { if (startScreen) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; startScreen->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); startScreen->drawSpriteRect(0, 0, w, h); } int titleY = scaleY(100); int optStartY = scaleY(180); int spacing = getMenuSpacing(); const char* optTitle = "OPTIONS"; printText(optTitle, centerX(optTitle), titleY, {255, 255, 0, 255}); const char* difficultyLabels[] = {"Easy", "Normal", "Hard"}; std::string diffText = std::string("< Difficulty: ") + difficultyLabels[difficultySetting] + " >"; SDL_Color diffColor = (optionsCursor == 0) ? SDL_Color{255, 255, 0, 255} : SDL_Color{255, 255, 255, 255}; printText(diffText.c_str(), centerX(diffText.c_str()), optStartY, diffColor); if (optionsCursor == 0) { int diffWidth, diffHeight; getTextDimensions(diffText.c_str(), diffWidth, diffHeight); printText(">>", w/2 - diffWidth/2 - scaleY(30), optStartY, {255, 255, 0, 255}); } const char* backText = "Back"; SDL_Color backColor = (optionsCursor == 1) ? SDL_Color{255, 255, 0, 255} : SDL_Color{255, 255, 255, 255}; printText(backText, centerX(backText), optStartY + spacing, backColor); if (optionsCursor == 1) { int backWidth, backHeight; getTextDimensions(backText, backWidth, backHeight); printText(">>", w/2 - backWidth/2 - scaleY(30), optStartY + spacing, {255, 255, 0, 255}); } const char* instructions = "UP/DOWN: Select LEFT/RIGHT: Change ENTER: Confirm"; printText(instructions, centerX(instructions), scaleY(350), {200, 200, 200, 255}); } void updateCredits() { if (creditScreen) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; creditScreen->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); creditScreen->drawSpriteRect(0, 0, w, h); } int titleY = scaleY(100); int contentY = scaleY(180); int spacing = getMenuSpacing(); const char* credTitle = "CREDITS"; const char* cred1 = "Acid Drop Clone"; const char* cred2 = "MX2 Engine by Jared Bruni"; const char* credReturn = "Press Return to return"; printText(credTitle, centerX(credTitle), titleY, {255, 255, 0, 255}); printText(cred1, centerX(cred1), contentY, {255, 255, 255, 255}); printText(cred2, centerX(cred2), contentY + spacing, {255, 255, 255, 255}); printText(credReturn, centerX(credReturn), scaleY(320), {255, 255, 0, 255}); } void updateScores() { if (scoresBackground) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; scoresBackground->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); scoresBackground->drawSpriteRect(0, 0, w, h); } int titleY = scaleY(40); int listStartY = scaleY(90); int lineHeight = scaleY(32); const char* title = "HIGH SCORES"; printText(title, centerX(title), titleY, {255, 255, 0, 255}); const auto& scoreList = scores.getScores(); for (size_t i = 0; i < scoreList.size() && i < 10; ++i) { std::ostringstream stream; stream << (i + 1) << ". " << scoreList[i].name << " " << scoreList[i].score; std::string scoreStr = stream.str(); SDL_Color color = {255, 255, 255, 255}; if (enteringName && finalScore > 0) { int rank = 0; for (size_t j = 0; j < scoreList.size(); ++j) { if (finalScore > scoreList[j].score) { rank = j; break; } rank = j + 1; } if ((int)i == rank) { color = {0, 255, 0, 255}; } } printText(scoreStr.c_str(), scaleY(100), listStartY + (int)i * lineHeight, color); } if (enteringName) { int entryY = scaleY(420); std::ostringstream stream; stream << "Your Score: " << finalScore; std::string yourScore = stream.str(); printText(yourScore.c_str(), centerX(yourScore.c_str()), entryY - lineHeight * 2, {255, 255, 0, 255}); const char* prompt = "Enter your name:"; printText(prompt, centerX(prompt), entryY - lineHeight, {255, 255, 255, 255}); std::string displayName = playerName + "_"; printText(displayName.c_str(), centerX(displayName.c_str()), entryY, {0, 255, 255, 255}); const char* instructions = "Type name, ENTER to confirm, BACKSPACE to delete"; printText(instructions, centerX(instructions), entryY + lineHeight, {200, 200, 200, 255}); } else { const char* returnText = "Press ENTER to return to menu"; printText(returnText, centerX(returnText), scaleY(440), {255, 255, 0, 255}); } } void drawGame() { float scaleX = (float)w / 640.0f; float scaleY = (float)h / 480.0f; if (gamebg) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; gamebg->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); gamebg->drawSpriteRect(0, 0, w, h); } drawmatrix(scaleX, scaleY); drawblock(scaleX, scaleY); drawnext(scaleX, scaleY); std::ostringstream stream; stream << "Score:" << matrix.Game.score; printText(stream.str().c_str(), (int)(200 * scaleX), (int)(80 * scaleY) - (lastFontSize+(lastFontSize/4)), {255, 255, 255, 255}); stream.str(""); stream << "Tabs:" << matrix.Game.lines; printText(stream.str().c_str(), (int)(310 * scaleX), (int)(80 * scaleY) - (lastFontSize+(lastFontSize/4)), {255, 255, 255, 255}); if (paused) { printText("PAUSED - Press P to Continue", w/2 - 120, h/2, {255, 255, 0, 255}); } } void drawmatrix(float scaleX, float scaleY) { Uint32 now = SDL_GetTicks(); bool showFlash = flashActive && ((now - flashStartTime) / 50 % 2 == 0); for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { int blockType = matrix.grid[i][j]; if (blockType > 0 && blockType < BLOCK_COUNT && static_cast<size_t>(blockType) < grid_blocks.size()) { int x = STARTX + i * (BLOCK_SIZE + BLOCK_SPACING); int y = STARTY + j * (BLOCK_HEIGHT + BLOCK_SPACING) + 10; if (flashActive && flashGrid[i][j] && !showFlash) { continue; } if (grid_blocks[blockType]) { grid_blocks[blockType]->drawSpriteRect( (int)(x * scaleX), (int)(y * scaleY) + 10, (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } } } } } void drawblock(float scaleX, float scaleY) { int c1 = matrix.block.color.c1; int c2 = matrix.block.color.c2; int c3 = matrix.block.color.c3; if (matrix.block.horizontal) { int screenY = STARTY + matrix.block.y * (BLOCK_HEIGHT + BLOCK_SPACING) + 10; for (int i = 0; i < 3; i++) { int color = (i == 0) ? c1 : (i == 1) ? c2 : c3; int bx = STARTX + (matrix.block.x + i) * (BLOCK_SIZE + BLOCK_SPACING); if (matrix.block.x + i >= 0 && matrix.block.x + i < GRID_WIDTH && color > 0 && color < BLOCK_COUNT && grid_blocks[color]) { grid_blocks[color]->drawSpriteRect( (int)(bx * scaleX), (int)(screenY * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } } } else { int bx = STARTX + matrix.block.x * (BLOCK_SIZE + BLOCK_SPACING); int y0 = matrix.block.y; int y1 = matrix.block.y + 1; int y2 = matrix.block.y + 2; if (y0 >= 0 && c1 > 0 && c1 < BLOCK_COUNT && grid_blocks[c1]) { int screenY = STARTY + y0 * (BLOCK_HEIGHT + BLOCK_SPACING) + 10; grid_blocks[c1]->drawSpriteRect( (int)(bx * scaleX), (int)(screenY * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } if (y1 >= 0 && c2 > 0 && c2 < BLOCK_COUNT && grid_blocks[c2]) { int screenY = STARTY + y1 * (BLOCK_HEIGHT + BLOCK_SPACING) + 10; grid_blocks[c2]->drawSpriteRect( (int)(bx * scaleX), (int)(screenY * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } if (y2 >= 0 && c3 > 0 && c3 < BLOCK_COUNT && grid_blocks[c3]) { int screenY = STARTY + y2 * (BLOCK_HEIGHT + BLOCK_SPACING) + 10; grid_blocks[c3]->drawSpriteRect( (int)(bx * scaleX), (int)(screenY * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } } } void drawnext(float scaleX, float scaleY) { int bx = NEXT_PANEL_X + 70; int by = NEXT_PANEL_Y + 15; int c1 = matrix.nextblock.color.c1; int c2 = matrix.nextblock.color.c2; int c3 = matrix.nextblock.color.c3; if (c1 > 0 && c1 < BLOCK_COUNT && grid_blocks[c1]) { grid_blocks[c1]->drawSpriteRect( (int)(bx * scaleX), (int)(by * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } if (c2 > 0 && c2 < BLOCK_COUNT && grid_blocks[c2]) { grid_blocks[c2]->drawSpriteRect( (int)(bx * scaleX), (int)((by + BLOCK_HEIGHT + BLOCK_SPACING) * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } if (c3 > 0 && c3 < BLOCK_COUNT && grid_blocks[c3]) { grid_blocks[c3]->drawSpriteRect( (int)(bx * scaleX), (int)((by + (BLOCK_HEIGHT + BLOCK_SPACING) * 2) * scaleY), (int)(BLOCK_SIZE * scaleX), (int)(BLOCK_HEIGHT * scaleY) ); } } void logic() { static Uint32 lastMove = 0; Uint32 now = SDL_GetTicks(); if (flashActive) { if (now - flashStartTime >= FLASH_DURATION) { clearFlashedBlocks(); flashActive = false; applyGravity(); if (findMatches()) { startFlash(); } } return; } if (now - lastMove > (Uint32)(matrix.Game.speed * 50)) { lastMove = now; if (canMoveDown()) { matrix.block.y++; } else { placeBlock(); applyGravity(); if (findMatches()) { startFlash(); } else { spawnNewBlock(); if (checkGameOver()) { currentScreen = SCREEN_GAMEOVER; } } } } } bool canMoveDown() { int bx = matrix.block.x; int by = matrix.block.y; if (matrix.block.horizontal) { if (by + 1 >= GRID_HEIGHT) return false; for (int i = 0; i < 3; i++) { if (bx + i >= 0 && bx + i < GRID_WIDTH && matrix.grid[bx + i][by + 1] > 0) return false; } } else { if (by + 3 >= GRID_HEIGHT) return false; if (matrix.grid[bx][by + 3] > 0) return false; } return true; } bool canMoveLeft() { if (matrix.block.x <= 0) return false; int bx = matrix.block.x - 1; int by = matrix.block.y; if (matrix.block.horizontal) { if (by >= 0 && by < GRID_HEIGHT && matrix.grid[bx][by] > 0) return false; } else { if (by >= 0 && by < GRID_HEIGHT && matrix.grid[bx][by] > 0) return false; if (by + 1 >= 0 && by + 1 < GRID_HEIGHT && matrix.grid[bx][by + 1] > 0) return false; if (by + 2 >= 0 && by + 2 < GRID_HEIGHT && matrix.grid[bx][by + 2] > 0) return false; } return true; } bool canMoveRight() { int by = matrix.block.y; if (matrix.block.horizontal) { if (matrix.block.x + 3 >= GRID_WIDTH) return false; int bx = matrix.block.x + 3; if (by >= 0 && by < GRID_HEIGHT && matrix.grid[bx][by] > 0) return false; } else { if (matrix.block.x >= GRID_WIDTH - 1) return false; int bx = matrix.block.x + 1; if (by >= 0 && by < GRID_HEIGHT && matrix.grid[bx][by] > 0) return false; if (by + 1 >= 0 && by + 1 < GRID_HEIGHT && matrix.grid[bx][by + 1] > 0) return false; if (by + 2 >= 0 && by + 2 < GRID_HEIGHT && matrix.grid[bx][by + 2] > 0) return false; } return true; } void placeBlock() { int bx = matrix.block.x; int by = matrix.block.y; if (matrix.block.horizontal) { if (by >= 0 && by < GRID_HEIGHT) { if (bx >= 0 && bx < GRID_WIDTH) matrix.grid[bx][by] = matrix.block.color.c1; if (bx + 1 >= 0 && bx + 1 < GRID_WIDTH) matrix.grid[bx + 1][by] = matrix.block.color.c2; if (bx + 2 >= 0 && bx + 2 < GRID_WIDTH) matrix.grid[bx + 2][by] = matrix.block.color.c3; } } else { if (by >= 0 && by < GRID_HEIGHT) matrix.grid[bx][by] = matrix.block.color.c1; if (by + 1 >= 0 && by + 1 < GRID_HEIGHT) matrix.grid[bx][by + 1] = matrix.block.color.c2; if (by + 2 >= 0 && by + 2 < GRID_HEIGHT) matrix.grid[bx][by + 2] = matrix.block.color.c3; } } bool findMatches() { memset(flashGrid, false, sizeof(flashGrid)); bool foundMatch = false; matchBonus = 0; for (int j = 0; j < GRID_HEIGHT; j++) { for (int i = 0; i < GRID_WIDTH; i++) { if (matrix.grid[i][j] > 0) { int color = matrix.grid[i][j]; int count = 1; while (i + count < GRID_WIDTH && matrix.grid[i + count][j] == color) { count++; } if (count >= 3) { for (int k = 0; k < count; k++) { flashGrid[i + k][j] = true; } foundMatch = true; if (count == 4) matchBonus += 25; else if (count >= 5) matchBonus += 50; } } } } for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { if (matrix.grid[i][j] > 0) { int color = matrix.grid[i][j]; int count = 1; while (j + count < GRID_HEIGHT && matrix.grid[i][j + count] == color) { count++; } if (count >= 3) { for (int k = 0; k < count; k++) { flashGrid[i][j + k] = true; } foundMatch = true; if (count == 4) matchBonus += 25; else if (count >= 5) matchBonus += 50; } } } } for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { if (matrix.grid[i][j] > 0) { int color = matrix.grid[i][j]; int count = 1; while (i + count < GRID_WIDTH && j + count < GRID_HEIGHT && matrix.grid[i + count][j + count] == color) { count++; } if (count >= 3) { for (int k = 0; k < count; k++) { flashGrid[i + k][j + k] = true; } foundMatch = true; if (count == 4) matchBonus += 35; else if (count >= 5) matchBonus += 75; } } } } for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { if (matrix.grid[i][j] > 0) { int color = matrix.grid[i][j]; int count = 1; while (i - count >= 0 && j + count < GRID_HEIGHT && matrix.grid[i - count][j + count] == color) { count++; } if (count >= 3) { for (int k = 0; k < count; k++) { flashGrid[i - k][j + k] = true; } foundMatch = true; if (count == 4) matchBonus += 35; else if (count >= 5) matchBonus += 75; } } } } return foundMatch; } void startFlash() { flashActive = true; flashStartTime = SDL_GetTicks(); } void clearFlashedBlocks() { for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { if (flashGrid[i][j]) { matrix.grid[i][j] = 0; matrix.Game.addline(); } } } matrix.Game.score += matchBonus; matchBonus = 0; memset(flashGrid, false, sizeof(flashGrid)); spawnNewBlock(); if (checkGameOver()) { currentScreen = SCREEN_GAMEOVER; } } void applyGravity() { bool moved; do { moved = false; for (int i = 0; i < GRID_WIDTH; i++) { for (int j = GRID_HEIGHT - 2; j >= 0; j--) { if (matrix.grid[i][j] > 0 && matrix.grid[i][j + 1] == 0) { matrix.grid[i][j + 1] = matrix.grid[i][j]; matrix.grid[i][j] = 0; moved = true; } } } } while (moved); } void spawnNewBlock() { matrix.block.color = matrix.nextblock.color; matrix.nextblock.color.randcolor(); matrix.block.x = GRID_WIDTH / 2; matrix.block.y = 1; matrix.block.horizontal = false; } bool checkGameOver() { for (int i = 0; i < GRID_WIDTH; i++) { if (matrix.grid[i][1] > 0) { return true; } } for (int i = 0; i < GRID_WIDTH; i++) { for (int j = 0; j < GRID_HEIGHT; j++) { if (matrix.grid[i][j] == 0) { return false; } } } return true; } void applyDifficulty() { int baseSpeeds[] = {30, 20, 10}; matrix.Game.speed = baseSpeeds[difficultySetting]; } void moveBlockLeft() { if (canMoveLeft()) { matrix.block.x--; } } void moveBlockRight() { if (canMoveRight()) { matrix.block.x++; } } void moveBlockDown() { if (canMoveDown()) { matrix.block.y++; } } void dropBlock() { while (canMoveDown()) { matrix.block.y++; } } bool canRotate() { int bx = matrix.block.x; int by = matrix.block.y; if (matrix.block.horizontal) { if (by + 2 >= GRID_HEIGHT) return false; if (by + 1 < GRID_HEIGHT && matrix.grid[bx][by + 1] > 0) return false; if (by + 2 < GRID_HEIGHT && matrix.grid[bx][by + 2] > 0) return false; } else { if (bx + 2 >= GRID_WIDTH) return false; if (bx + 1 < GRID_WIDTH && by >= 0 && by < GRID_HEIGHT && matrix.grid[bx + 1][by] > 0) return false; if (bx + 2 < GRID_WIDTH && by >= 0 && by < GRID_HEIGHT && matrix.grid[bx + 2][by] > 0) return false; } return true; } void rotateBlock() { if (canRotate()) { matrix.block.horizontal = !matrix.block.horizontal; } } void event(SDL_Event& e) override { if (e.type == SDL_WINDOWEVENT) { if (e.window.event == SDL_WINDOWEVENT_RESIZED || e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { recreateSwapChain(); } } if(e.type == SDL_QUIT || (currentScreen == SCREEN_START && e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) { quit(); return; } if (e.type == SDL_KEYDOWN) { gameKeypress(e.key.keysym.sym); } else if (e.type == SDL_TEXTINPUT) { handleTextInput(e.text.text); } } void gameKeypress(SDL_Keycode key) { switch (currentScreen) { case SCREEN_INTRO: if (key == SDLK_RETURN) { currentScreen = SCREEN_START; } break; case SCREEN_START: if (key == SDLK_UP) { cursorPos = (cursorPos - 1 + 5) % 5; } else if (key == SDLK_DOWN) { cursorPos = (cursorPos + 1) % 5; } else if (key == SDLK_RETURN) { if (cursorPos == 0) { matrix.init_matrix(); applyDifficulty(); matrix.block.x = GRID_WIDTH / 2; matrix.block.y = 1; matrix.block.horizontal = false; currentScreen = SCREEN_GAME; } else if (cursorPos == 1) { finalScore = 0; enteringName = false; currentScreen = SCREEN_SCORES; } else if (cursorPos == 2) { currentScreen = SCREEN_OPTIONS; } else if (cursorPos == 3) { currentScreen = SCREEN_CREDITS; } else if (cursorPos == 4) { quit(); } } break; case SCREEN_GAME: if (key == SDLK_LEFT) { moveBlockLeft(); } else if (key == SDLK_RIGHT) { moveBlockRight(); } else if (key == SDLK_DOWN) { moveBlockDown(); } else if (key == SDLK_UP) { matrix.block.color.shiftcolor(true); } else if (key == SDLK_SPACE) { rotateBlock(); } else if (key == SDLK_z) { matrix.block.color.shiftcolor(false); } else if (key == SDLK_p) { paused = !paused; } else if (key == SDLK_ESCAPE) { currentScreen = SCREEN_START; } break; case SCREEN_GAMEOVER: if (key == SDLK_RETURN) { goToScoresScreen(); } else if (key == SDLK_ESCAPE) { currentScreen = SCREEN_START; } break; case SCREEN_OPTIONS: if (key == SDLK_UP) { optionsCursor = (optionsCursor - 1 + 2) % 2; } else if (key == SDLK_DOWN) { optionsCursor = (optionsCursor + 1) % 2; } else if (key == SDLK_LEFT) { if (optionsCursor == 0) { difficultySetting = (difficultySetting - 1 + 3) % 3; } } else if (key == SDLK_RIGHT) { if (optionsCursor == 0) { difficultySetting = (difficultySetting + 1) % 3; } } else if (key == SDLK_RETURN) { if (optionsCursor == 1) { currentScreen = SCREEN_START; } } break; case SCREEN_CREDITS: if (key == SDLK_RETURN) { currentScreen = SCREEN_START; } break; case SCREEN_SCORES: if (enteringName) { if (key == SDLK_RETURN && !playerName.empty()) { scores.addScore(playerName, finalScore); enteringName = false; } else if (key == SDLK_BACKSPACE && !playerName.empty()) { playerName.pop_back(); } else if (key == SDLK_ESCAPE) { enteringName = false; } } else { if (key == SDLK_RETURN || key == SDLK_ESCAPE) { currentScreen = SCREEN_START; } } break; } } void handleTextInput(const char* text) { if (currentScreen == SCREEN_SCORES && enteringName) { if (playerName.length() < 15) { playerName += text; } } } }; int main(int argc, char **argv) { Arguments args = proc_args(argc, argv); try { MasterPieceWindow window(args.path, args.width, args.height, args.fullscreen); window.initVulkan(); window.loop(); window.cleanup(); } catch (mx::Exception &e) { SDL_Log("mx: Exception: %s\n", e.text().c_str()); } return EXIT_SUCCESS; }