#include "SDL.h" #include "argz.hpp" #include "input.hpp" #include "loadpng.hpp" #include <climits> #include <cmath> #include <ctime> #include <random> #include <sstream> #include <unistd.h> #include <vk.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; constexpr int SHADER_LEVEL_LINES = 10; constexpr int SHADER_LEVEL_COUNT = 4; 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 { int score; std::string name; Score(std::string name_, int score_) : score(score_), name(name_) {} Score() : name{}, score{0} {} auto operator<=>(const Score &s) const { return s.score <=> score; } }; 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()); } 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 shaderEffectsEnabled = true; 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[SHADER_LEVEL_COUNT] = {nullptr}; int shaderLevel = 0; mx::VKSprite *startScreen = nullptr; mx::VKSprite *introScreen = nullptr; mx::Input controller; static constexpr Sint16 JOYSTICK_DEAD_ZONE = 12000; Uint32 joyRepeatLeft = 0; Uint32 joyRepeatRight = 0; Uint32 joyRepeatDown = 0; Uint32 joyRepeatUp = 0; static constexpr Uint32 JOY_REPEAT_DELAY = 150; public: MasterPieceWindow(const std::string &path, int wx, int wy, bool full) : mx::VKWindow("-[ Acid Drop - Vulkan ]-", wx, wy, full) { setPath(path); enableControllerInput(); updateFontSize(); srand((unsigned int)time(0)); std::string ico_path = path; if (!ico_path.empty() && ico_path.back() != '/' && ico_path.back() != '\\') { ico_path += "/"; } ico_path += "data/icon.png"; SDL_Surface *ico = png::LoadPNG(ico_path.c_str()); if (!ico) { std::cerr << "Error loading icon.\n"; } else { SDL_SetWindowIcon(window, ico); SDL_FreeSurface(ico); } 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"; const char *fragShaders[SHADER_LEVEL_COUNT] = { "sprite_kaleidoscope.spv", "sprite_kaleidoscope2.spv", "sprite_kaleidoscope3.spv", "sprite_kaleidoscope4.spv"}; for (int i = 0; i < SHADER_LEVEL_COUNT; i++) { gamebg[i] = createSprite(util.path + "/data/gamebg.png", vertShader, util.path + "/data/" + fragShaders[i]); } 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")); } applyShaderEffectsToggle(); } 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(); } } void applyShaderEffectsToggle() { for (int i = 0; i < SHADER_LEVEL_COUNT; i++) { if (gamebg[i]) gamebg[i]->setEffectsEnabled(shaderEffectsEnabled); } if (introScreen) introScreen->setEffectsEnabled(shaderEffectsEnabled); if (startScreen) startScreen->setEffectsEnabled(shaderEffectsEnabled); if (creditScreen) creditScreen->setEffectsEnabled(shaderEffectsEnabled); if (scoresBackground) scoresBackground->setEffectsEnabled(shaderEffectsEnabled); for (auto *block : grid_blocks) { if (block) block->setEffectsEnabled(shaderEffectsEnabled); } } 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 { pollController(); 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); } Uint32 currentTick = SDL_GetTicks(); if (currentTick - lastTick > 5000) { 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[shaderLevel]) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; gamebg[shaderLevel]->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); gamebg[shaderLevel]->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}); } std::string fxText = std::string("< Shader Effects: ") + (shaderEffectsEnabled ? "On" : "Off") + " >"; SDL_Color fxColor = (optionsCursor == 1) ? SDL_Color{255, 255, 0, 255} : SDL_Color{255, 255, 255, 255}; printText(fxText.c_str(), centerX(fxText.c_str()), optStartY + spacing, fxColor); if (optionsCursor == 1) { int fxWidth, fxHeight; getTextDimensions(fxText.c_str(), fxWidth, fxHeight); printText(">>", w / 2 - fxWidth / 2 - scaleY(30), optStartY + spacing, {255, 255, 0, 255}); } const char *backText = "Back"; SDL_Color backColor = (optionsCursor == 2) ? SDL_Color{255, 255, 0, 255} : SDL_Color{255, 255, 255, 255}; printText(backText, centerX(backText), optStartY + spacing * 2, backColor); if (optionsCursor == 2) { int backWidth, backHeight; getTextDimensions(backText, backWidth, backHeight); printText(">>", w / 2 - backWidth / 2 - scaleY(30), optStartY + spacing * 2, {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); } const char *credReturn = "Press Return to return"; printText(credReturn, centerX(credReturn), scaleY(360), {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, 0, 150, 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 or Escape/Back"; printText(instructions, centerX(instructions), entryY + lineHeight, {200, 0, 150, 255}); } else { const char *returnText = "Press ENTER to return to menu"; printText(returnText, centerX(returnText), scaleY(440), {255, 0, 0, 255}); } } void drawGame() { float scaleX = (float)w / 640.0f; float scaleY = (float)h / 480.0f; if (gamebg[shaderLevel]) { float time_f = static_cast<float>(SDL_GetTicks()) / 1000.0f; gamebg[shaderLevel]->setShaderParams(time_f, 0.0f, 0.0f, 0.0f); gamebg[shaderLevel]->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) { const char *pausedText = "PAUSED - Press P to Continue"; int pausedWidth, pausedHeight; getTextDimensions(pausedText, pausedWidth, pausedHeight); printText(pausedText, w / 2 - pausedWidth / 2, 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; int newLevel = matrix.Game.lines / SHADER_LEVEL_LINES; if (newLevel >= SHADER_LEVEL_COUNT) newLevel = SHADER_LEVEL_COUNT - 1; if (newLevel != shaderLevel) { shaderLevel = newLevel; mx::system_out << "level increased to: " << shaderLevel << std::endl; } 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 (controller.connectEvent(e)) { mx::system_out << "Controller connected: " << controller.name() << " (index " << controller.controllerIndex() << ")\n"; } if (e.type == SDL_CONTROLLERDEVICEREMOVED) { mx::system_out << "Controller disconnected.\n"; } 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); } if (e.type == SDL_CONTROLLERBUTTONDOWN) { handleControllerButton(e.cbutton.button); } } void handleControllerButton(Uint8 button) { switch (currentScreen) { case SCREEN_INTRO: if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_START) { currentScreen = SCREEN_START; } break; case SCREEN_START: if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) { cursorPos = (cursorPos - 1 + 5) % 5; } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { cursorPos = (cursorPos + 1) % 5; } else if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_START) { if (cursorPos == 0) { matrix.init_matrix(); shaderLevel = 0; 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(); } } else if (button == SDL_CONTROLLER_BUTTON_B) { quit(); } break; case SCREEN_GAME: if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { moveBlockLeft(); } else if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { moveBlockRight(); } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { moveBlockDown(); } else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) { matrix.block.color.shiftcolor(true); } else if (button == SDL_CONTROLLER_BUTTON_A) { rotateBlock(); } else if (button == SDL_CONTROLLER_BUTTON_B) { matrix.block.color.shiftcolor(false); } else if (button == SDL_CONTROLLER_BUTTON_Y) { dropBlock(); } else if (button == SDL_CONTROLLER_BUTTON_START) { paused = !paused; } else if (button == SDL_CONTROLLER_BUTTON_BACK) { currentScreen = SCREEN_START; } break; case SCREEN_GAMEOVER: if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_START) { goToScoresScreen(); } else if (button == SDL_CONTROLLER_BUTTON_B) { currentScreen = SCREEN_START; } break; case SCREEN_OPTIONS: if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) { optionsCursor = (optionsCursor - 1 + 3) % 3; } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { optionsCursor = (optionsCursor + 1) % 3; } else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { if (optionsCursor == 0) difficultySetting = (difficultySetting - 1 + 3) % 3; if (optionsCursor == 1) { shaderEffectsEnabled = !shaderEffectsEnabled; applyShaderEffectsToggle(); } } else if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { if (optionsCursor == 0) difficultySetting = (difficultySetting + 1) % 3; if (optionsCursor == 1) { shaderEffectsEnabled = !shaderEffectsEnabled; applyShaderEffectsToggle(); } } else if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_START) { if (optionsCursor == 2) currentScreen = SCREEN_START; } else if (button == SDL_CONTROLLER_BUTTON_B) { currentScreen = SCREEN_START; } break; case SCREEN_CREDITS: if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_B || button == SDL_CONTROLLER_BUTTON_START) { currentScreen = SCREEN_START; } break; case SCREEN_SCORES: if (!enteringName) { if (button == SDL_CONTROLLER_BUTTON_A || button == SDL_CONTROLLER_BUTTON_B || button == SDL_CONTROLLER_BUTTON_START) { currentScreen = SCREEN_START; } } else if (enteringName) { if (button == SDL_CONTROLLER_BUTTON_BACK) currentScreen = SCREEN_START; } break; } } void pollController() { if (!controller.active()) return; Uint32 now = SDL_GetTicks(); if (currentScreen == SCREEN_GAME && !paused) { Sint16 lx = controller.getAxis(SDL_CONTROLLER_AXIS_LEFTX); Sint16 ly = controller.getAxis(SDL_CONTROLLER_AXIS_LEFTY); if (lx < -JOYSTICK_DEAD_ZONE) { if (now - joyRepeatLeft > JOY_REPEAT_DELAY) { moveBlockLeft(); joyRepeatLeft = now; } } else { joyRepeatLeft = 0; } if (lx > JOYSTICK_DEAD_ZONE) { if (now - joyRepeatRight > JOY_REPEAT_DELAY) { moveBlockRight(); joyRepeatRight = now; } } else { joyRepeatRight = 0; } if (ly > JOYSTICK_DEAD_ZONE) { if (now - joyRepeatDown > JOY_REPEAT_DELAY) { moveBlockDown(); joyRepeatDown = now; } } else { joyRepeatDown = 0; } if (ly < -JOYSTICK_DEAD_ZONE) { if (now - joyRepeatUp > JOY_REPEAT_DELAY) { matrix.block.color.shiftcolor(true); joyRepeatUp = now; } } else { joyRepeatUp = 0; } } } 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(); shaderLevel = 0; 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_l) { /*if (shaderLevel < SHADER_LEVEL_COUNT - 1) { shaderLevel++; mx::system_out << "level increased to: " << shaderLevel << std::endl; }*/ } 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 + 3) % 3; } else if (key == SDLK_DOWN) { optionsCursor = (optionsCursor + 1) % 3; } else if (key == SDLK_LEFT) { if (optionsCursor == 0) { difficultySetting = (difficultySetting - 1 + 3) % 3; } else if (optionsCursor == 1) { shaderEffectsEnabled = !shaderEffectsEnabled; applyShaderEffectsToggle(); } } else if (key == SDLK_RIGHT) { if (optionsCursor == 0) { difficultySetting = (difficultySetting + 1) % 3; } else if (optionsCursor == 1) { shaderEffectsEnabled = !shaderEffectsEnabled; applyShaderEffectsToggle(); } } else if (key == SDLK_RETURN) { if (optionsCursor == 2) { 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; }