#include"mx.hpp" #include"argz.hpp" #ifdef __EMSCRIPTEN__ #include <emscripten/emscripten.h> #endif #include"mx.hpp" #include <vector> #include <string> #include <ctime> #include <cstdlib> class Game : public obj::Object { public: Game() : pieceX(0), pieceY(0), isGameOver(false), score(0), lastFallTime(0), fallInterval(1000) {} ~Game() override {} void load(mx::mxWindow* win) override { if(stick.open(0)) { mx::system_out << "Initalized Joystick: " << stick.name() << "\n"; } the_font.loadFont(win->util.getFilePath("data/font.ttf"), 14); bg.loadTexture(win, win->util.getFilePath("data/bg.png")); resetGame(); } void update(mx::mxWindow* win) { if (!isGameOver) { Uint32 currentTime = SDL_GetTicks(); if (currentTime - lastFallTime >= fallInterval) { movePieceDown(); lastFallTime = currentTime; pieceDropped = false; } } } void draw(mx::mxWindow* win) override { SDL_Renderer* renderer = win->renderer; SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); if (isGameOver) { TTF_Font* font = the_font.wrapper().unwrap(); SDL_Color color = {255, 0, 0}; SDL_Surface* surface = TTF_RenderText_Blended(font, "Game Over", color); SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); int textW = 0, textH = 0; SDL_QueryTexture(texture, nullptr, nullptr, &textW, &textH); int windowWidth = 300 * 2; int windowHeight = 630 * 2; SDL_Rect textRect = {(windowWidth - textW) / 2, (windowHeight - textH) / 2, textW, textH}; SDL_RenderCopy(renderer, texture, nullptr, &textRect); SDL_DestroyTexture(texture); SDL_FreeSurface(surface); return; } SDL_RenderCopy(win->renderer, bg.wrapper().unwrap(), nullptr, nullptr); int windowWidth = 300 * 2; int windowHeight = 630 * 2; SDL_Rect gameArea = {50, 50, windowWidth - 100, windowHeight - 100}; int blockSize = gameArea.w / cols; for (int row = 0; row < rows; ++row) { for (int col = 0; col < cols; ++col) { SDL_Rect rect = {gameArea.x + col * blockSize, gameArea.y + row * blockSize, blockSize, blockSize}; if (grid[row][col] == 1) { SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); SDL_RenderFillRect(renderer, &rect); } else { SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderDrawRect(renderer, &rect); } } } for (int row = 0; row < static_cast<int>(currentPiece.size()); ++row) { for (int col = 0; col < static_cast<int>(currentPiece[0].size()); ++col) { if (currentPiece[row][col] == 1) { SDL_Rect rect = {gameArea.x + (pieceX + col) * blockSize, gameArea.y + (pieceY + row) * blockSize, blockSize, blockSize}; SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_RenderFillRect(renderer, &rect); } } } TTF_Font* font = the_font.wrapper().unwrap(); SDL_Color textColor = {255,255,255,255}; std::string scoreText = "Score: " + std::to_string(score); SDL_Surface* textSurface = TTF_RenderText_Blended(font, scoreText.c_str(), textColor); SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); int textW = 0, textH = 0; SDL_QueryTexture(textTexture, nullptr, nullptr, &textW, &textH); SDL_Rect textRect = {gameArea.x + 5, gameArea.y - textH - 5, textW, textH}; SDL_RenderCopy(renderer, textTexture, nullptr, &textRect); SDL_DestroyTexture(textTexture); SDL_FreeSurface(textSurface); update(win); } void event(mx::mxWindow* win, SDL_Event& e) override { if (!isGameOver) { switch (e.type) { case SDL_JOYBUTTONDOWN: switch (e.jbutton.button) { case 0: dropPiece(); break; case 1: rotatePiece(); break; default: break; } break; case SDL_JOYAXISMOTION: if (e.jaxis.axis == 0) { if (e.jaxis.value < -8000) { movePieceLeft(); } else if (e.jaxis.value > 8000) { movePieceRight(); } } else if (e.jaxis.axis == 1) { if (e.jaxis.value > 8000) { movePieceDown(); } } break; case SDL_JOYHATMOTION: if (e.jhat.hat == 0) { switch (e.jhat.value) { case SDL_HAT_LEFT: movePieceLeft(); break; case SDL_HAT_RIGHT: movePieceRight(); break; case SDL_HAT_DOWN: movePieceDown(); break; case SDL_HAT_UP: rotatePiece(); break; case SDL_HAT_CENTERED: break; default: break; } } break; default: break; } } if (e.type == SDL_KEYDOWN && !isGameOver) { switch (e.key.keysym.sym) { case SDLK_DOWN: movePieceDown(); break; case SDLK_LEFT: movePieceLeft(); break; case SDLK_RIGHT: movePieceRight(); break; case SDLK_UP: rotatePiece(); break; case SDLK_SPACE: dropPiece(); break; default: break; } } else if((e.type == SDL_KEYDOWN && isGameOver == true && e.key.keysym.sym == SDLK_RETURN) || (isGameOver == true && e.type == SDL_JOYBUTTONDOWN && e.jbutton.button == 1)) { win->setObject(new Game()); win->object->load(win); return; } if(!isGameOver) { if (e.type == SDL_FINGERDOWN || e.type == SDL_MOUSEBUTTONDOWN) { touchActive = true; accumulatedDeltaX = 0.0f; if (e.type == SDL_FINGERDOWN) { touchStartX = e.tfinger.x; touchStartY = e.tfinger.y; lastTouchX = touchStartX; } else if (e.type == SDL_MOUSEBUTTONDOWN) { int windowWidth, windowHeight; SDL_GetWindowSize(win->window, &windowWidth, &windowHeight); touchStartX = static_cast<float>(e.button.x) / windowWidth; touchStartY = static_cast<float>(e.button.y) / windowHeight; lastTouchX = touchStartX; } } else if (e.type == SDL_FINGERMOTION && touchActive) { float currentTouchX = e.tfinger.x; float deltaX = currentTouchX - lastTouchX; lastTouchX = currentTouchX; accumulatedDeltaX += deltaX * win->width; const float moveThreshold = 30.0f; if (accumulatedDeltaX <= -moveThreshold) { movePieceLeft(); accumulatedDeltaX += moveThreshold; } else if (accumulatedDeltaX >= moveThreshold) { movePieceRight(); accumulatedDeltaX -= moveThreshold; } } else if (e.type == SDL_MOUSEMOTION && touchActive && (e.motion.state & SDL_BUTTON_LMASK)) { int windowWidth, windowHeight; SDL_GetWindowSize(win->window, &windowWidth, &windowHeight); float currentTouchX = static_cast<float>(e.motion.x) / windowWidth; float deltaX = currentTouchX - lastTouchX; lastTouchX = currentTouchX; accumulatedDeltaX += deltaX * windowWidth; const float moveThreshold = 10.0f; if (accumulatedDeltaX <= -moveThreshold) { movePieceLeft(); accumulatedDeltaX += moveThreshold; } else if (accumulatedDeltaX >= moveThreshold) { movePieceRight(); accumulatedDeltaX -= moveThreshold; } } else if (e.type == SDL_FINGERUP || e.type == SDL_MOUSEBUTTONUP) { touchActive = false; float touchEndX = 0.0f, touchEndY = 0.0f; int windowWidth = 0, windowHeight = 0; SDL_GetWindowSize(win->window, &windowWidth, &windowHeight); if (e.type == SDL_FINGERUP) { touchEndX = e.tfinger.x; touchEndY = e.tfinger.y; } else if (e.type == SDL_MOUSEBUTTONUP) { touchEndX = static_cast<float>(e.button.x) / windowWidth; touchEndY = static_cast<float>(e.button.y) / windowHeight; } float deltaX = (touchEndX - touchStartX) * windowWidth; float deltaY = (touchEndY - touchStartY) * windowHeight; float absDeltaX = std::fabs(deltaX); float absDeltaY = std::fabs(deltaY); const float tapThreshold = 10.0f; const float swipeThreshold = 75.0f; Uint32 currentTime = SDL_GetTicks(); if (absDeltaY > swipeThreshold && deltaY > 0 && !pieceDropped) { dropPiece(); tapCount = 0; lastTapTime = 0; pieceDropped = true; } else if (absDeltaX < tapThreshold && absDeltaY < tapThreshold) { if (currentTime - lastTapTime < doubleTapThreshold) { tapCount++; } else { tapCount = 1; } lastTapTime = currentTime; if (tapCount == 2) { rotatePiece(); tapCount = 0; lastTapTime = 0; } } else { tapCount = 0; lastTapTime = 0; } } } else { if ((e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RETURN) || e.type == SDL_FINGERDOWN || e.type == SDL_MOUSEBUTTONDOWN) { win->setObject(new Game()); win->object->load(win); return; } } } private: bool pieceDropped = false; float touchStartX = 0.0f; float touchStartY = 0.0f; bool touchActive = false; float lastTouchX = 0.0f; float accumulatedDeltaX = 0.0f; Uint32 lastTapTime = 0; int tapCount = 0; const Uint32 doubleTapThreshold = 300; static const int rows = 21; static const int cols = 10; int pieceX = 0; int pieceY = 0; std::vector<std::vector<int>> grid; std::vector<std::vector<int>> currentPiece; std::vector<std::vector<int>> nextPiece; bool isGameOver = false; int score = 0; Uint32 lastFallTime = 0; Uint32 fallInterval = 0; void resetGame() { srand(static_cast<unsigned>(time(nullptr))); grid.clear(); grid.resize(rows, std::vector<int>(cols, 0)); currentPiece = getRandomPiece(); nextPiece = getRandomPiece(); pieceX = cols / 2 - static_cast<int>(currentPiece[0].size()) / 2; pieceY = 0; isGameOver = false; score = 0; lastFallTime = SDL_GetTicks(); fallInterval = 1000; } std::vector<std::vector<int>> getRandomPiece() { const std::vector<std::vector<std::vector<int>>> pieces = { {{1, 1, 1, 1}}, {{1, 1}, {1, 1}}, {{0, 1, 0}, {1, 1, 1}}, {{1, 1, 0}, {0, 1, 1}}, {{0, 1, 1}, {1, 1, 0}}, {{1, 1, 1}, {1, 0, 0}}, {{1, 1, 1}, {0, 0, 1}} }; int index = rand() % static_cast<int>(pieces.size()); return pieces[index]; } std::vector<std::vector<int>> rotate(const std::vector<std::vector<int>>& piece) { int rows = static_cast<int>(piece.size()); int cols = static_cast<int>(piece[0].size()); std::vector<std::vector<int>> rotatedPiece(cols, std::vector<int>(rows, 0)); for (int row = 0; row < rows; ++row) { for (int col = 0; col < cols; ++col) { rotatedPiece[col][rows - 1 - row] = piece[row][col]; } } return rotatedPiece; } bool checkCollision(int newX, int newY, const std::vector<std::vector<int>>& piece) { for (int row = 0; row < static_cast<int>(piece.size()); ++row) { for (int col = 0; col < static_cast<int>(piece[0].size()); ++col) { if (piece[row][col] == 1) { int gridX = newX + col; int gridY = newY + row; if (gridX < 0 || gridX >= cols || gridY >= rows || (gridY >= 0 && grid[gridY][gridX] != 0)) { return true; } } } } return false; } void lockPiece() { for (int row = 0; row < static_cast<int>(currentPiece.size()); ++row) { for (int col = 0; col < static_cast<int>(currentPiece[0].size()); ++col) { if (currentPiece[row][col] == 1) { grid[pieceY + row][pieceX + col] = 1; } } } clearLines(); currentPiece = nextPiece; nextPiece = getRandomPiece(); pieceX = cols / 2 - static_cast<int>(currentPiece[0].size()) / 2; pieceY = 0; if (checkCollision(pieceX, pieceY, currentPiece)) { gameOver(); } } void clearLines() { for (int row = rows - 1; row >= 0; --row) { bool isFull = true; for (int col = 0; col < cols; ++col) { if (grid[row][col] == 0) { isFull = false; break; } } if (isFull) { grid.erase(grid.begin() + row); grid.insert(grid.begin(), std::vector<int>(cols, 0)); row++; score += 100; if (fallInterval > 100) { fallInterval -= 10; } } } } void gameOver() { isGameOver = true; } void movePieceDown() { if (!checkCollision(pieceX, pieceY + 1, currentPiece)) { pieceY++; } else { lockPiece(); } } void movePieceLeft() { if (!checkCollision(pieceX - 1, pieceY, currentPiece)) { pieceX--; } } void movePieceRight() { if (!checkCollision(pieceX + 1, pieceY, currentPiece)) { pieceX++; } } void rotatePiece() { auto rotatedPiece = rotate(currentPiece); if (!checkCollision(pieceX, pieceY, rotatedPiece)) { currentPiece = rotatedPiece; } } void dropPiece() { while (!checkCollision(pieceX, pieceY + 1, currentPiece)) { pieceY++; } lockPiece(); } mx::Font the_font; mx::Texture bg; mx::Joystick stick; }; class Intro : public obj::Object { public: Intro() {} ~Intro() override {} virtual void load(mx::mxWindow *win) override { tex.loadTexture(win, win->util.getFilePath("data/logo.png")); bg.loadTexture(win, win->util.getFilePath("data/bg.png")); } virtual void draw(mx::mxWindow *win) override { static Uint32 previous_time = SDL_GetTicks(); Uint32 current_time = SDL_GetTicks(); static int alpha = 255; static bool fading_out = true; static bool done = false; SDL_RenderCopy(win->renderer, bg.wrapper().unwrap(), nullptr, nullptr); SDL_SetTextureAlphaMod(tex.wrapper().unwrap(), alpha); SDL_RenderCopy(win->renderer, tex.wrapper().unwrap(), nullptr, nullptr); if(done == true) { win->setObject(new Game()); win->object->load(win); return; } if (current_time - previous_time >= 15) { previous_time = current_time; if (fading_out) { alpha -= 3; if (alpha <= 0) { alpha = 0; fading_out = false; done = true; } } else { alpha += 3; if (alpha >= 255) { alpha = 255; fading_out = true; previous_time = SDL_GetTicks(); } } } } virtual void event(mx::mxWindow *win, SDL_Event &e) override { } private: mx::Texture tex; mx::Texture bg; }; class MainWindow : public mx::mxWindow { public: MainWindow(std::string path, int tw, int th) : mx::mxWindow("Blocks", tw, th, false) { tex.createTexture(this, 300 * 2, 630 * 2); setPath(path); setObject(new Intro()); object->load(this); } ~MainWindow() override { } virtual void event(SDL_Event &e) override { } virtual void draw(SDL_Renderer *renderer) override { SDL_SetRenderTarget(renderer, tex.wrapper().unwrap()); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); // draw object->draw(this); SDL_SetRenderTarget(renderer,nullptr); SDL_RenderCopy(renderer, tex.wrapper().unwrap(), nullptr, nullptr); SDL_RenderPresent(renderer); } private: mx::Texture tex; }; MainWindow *main_w = nullptr; void eventProc() { main_w->proc(); } int main(int argc, char **argv) { #ifdef __EMSCRIPTEN__ MainWindow main_window("/", 300 * 2 , 630 * 2); main_w =&main_window; emscripten_set_main_loop(eventProc, 0, 1); #else Argz<std::string> parser(argc, argv); parser.addOptionSingle('h', "Display help message") .addOptionSingleValue('p', "assets path") .addOptionDoubleValue('P', "path", "assets path") .addOptionSingleValue('r',"Resolution WidthxHeight") .addOptionDoubleValue('R',"resolution", "Resolution WidthxHeight"); Argument<std::string> arg; std::string path; int value = 0; int tw = 300 * 2, th = 630 * 2; try { while((value = parser.proc(arg)) != -1) { switch(value) { case 'h': case 'v': parser.help(std::cout); exit(EXIT_SUCCESS); break; case 'p': case 'P': path = arg.arg_value; break; case 'r': case 'R': { auto pos = arg.arg_value.find("x"); if(pos == std::string::npos) { mx::system_err << "Error invalid resolution use WidthxHeight\n"; mx::system_err.flush(); exit(EXIT_FAILURE); } std::string left, right; left = arg.arg_value.substr(0, pos); right = arg.arg_value.substr(pos+1); tw = atoi(left.c_str()); th = atoi(right.c_str()); } break; } } } catch (const ArgException<std::string>& e) { mx::system_err << e.text() << "\n"; } if(path.empty()) { mx::system_out << "mx: No path provided trying default current directory.\n"; path = "."; } try { MainWindow main_window(path, tw, th); main_window.loop(); } catch(const mx::Exception &e) { mx::system_err << "mx: Exception: " << e.text() << "\n"; mx::system_err.flush(); exit(EXIT_FAILURE); } #endif return 0; }