#include #include #include #include #ifdef MIYOO_BUILD #include #include #include #include #include #endif #include "Game.hpp" #include "Synth.hpp" #include "Renderer.hpp" // Crash signal handler to print diagnostic trace to cybermatris.log void crashHandler(int signum) { std::cerr << "\n\n==================================================" << std::endl; std::cerr << "[CRASH DETECTED] CyberMatris caught fatal signal " << signum; switch (signum) { case SIGSEGV: std::cerr << " (SIGSEGV: Segmentation Fault)"; break; case SIGABRT: std::cerr << " (SIGABRT: Abort / Assertion failed)"; break; case SIGFPE: std::cerr << " (SIGFPE: Floating Point Exception)"; break; case SIGILL: std::cerr << " (SIGILL: Illegal Instruction)"; break; case SIGBUS: std::cerr << " (SIGBUS: Bus Error)"; break; default: std::cerr << " (Unknown Signal)"; break; } std::cerr << "\n==================================================" << std::endl; std::cerr.flush(); // Restore default handler and re-raise to complete execution termination std::signal(signum, SIG_DFL); std::raise(signum); } int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { // Register crash signal handlers std::signal(SIGSEGV, crashHandler); std::signal(SIGABRT, crashHandler); std::signal(SIGFPE, crashHandler); std::signal(SIGILL, crashHandler); std::signal(SIGBUS, crashHandler); // 1. Initialize SDL2 subsystems if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; return -1; } // 2. Create display window int win_w = 800; int win_h = 600; Uint32 win_flags = SDL_WINDOW_SHOWN; #ifdef MIYOO_BUILD win_flags |= SDL_WINDOW_FULLSCREEN; // Default fallback win_w = 640; win_h = 480; // 1. Try environment variables first const char* env_w = std::getenv("MIYOO_SCREEN_WIDTH"); const char* env_h = std::getenv("MIYOO_SCREEN_HEIGHT"); if (env_w && env_h) { int parsed_w = std::atoi(env_w); int parsed_h = std::atoi(env_h); if (parsed_w > 0 && parsed_h > 0) { win_w = parsed_w; win_h = parsed_h; std::cout << "[INFO] Detected screen resolution from environment: " << win_w << "x" << win_h << std::endl; } } else { // 2. Fallback to /dev/fb0 ioctl int fd = open("/dev/fb0", O_RDONLY); if (fd >= 0) { struct fb_var_screeninfo vinfo; if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) >= 0) { if (vinfo.xres > 0 && vinfo.yres > 0) { win_w = vinfo.xres; win_h = vinfo.yres; std::cout << "[INFO] Detected screen resolution from /dev/fb0: " << win_w << "x" << win_h << std::endl; } } close(fd); } else { std::cerr << "[WARNING] Failed to open /dev/fb0 for resolution query, using default 640x480" << std::endl; } } #endif SDL_Window* window = SDL_CreateWindow( "CyberMatris - Programmatic Retro Tetris", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, win_w, win_h, win_flags ); if (window == nullptr) { std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; SDL_Quit(); return -1; } // 3. Create GPU Hardware Accelerated Renderer with VSync SDL_Renderer* renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC ); #ifdef MIYOO_BUILD if (renderer != nullptr) { std::cerr << "[MIYOO] Hardware accelerated renderer OK" << std::endl; } #endif if (renderer == nullptr) { std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl; SDL_DestroyWindow(window); SDL_Quit(); return -1; } // 4. Initialize Procedural Audio Synthesizer Synth synth; if (!synth.init()) { std::cerr << "Warning: Could not initialize programmatic audio synthesizer! SDL_Error: " << SDL_GetError() << std::endl; } // 5. Initialize Game Logic and Renderer Game game; Renderer gameRenderer; if (!gameRenderer.init(renderer, win_w, win_h)) { std::cerr << "Failed to build procedural graphics textures!" << std::endl; SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return -1; } bool quit = false; SDL_Event event; // Track frame timing using high-precision performance counters uint64_t lastTime = SDL_GetPerformanceCounter(); double freq = static_cast(SDL_GetPerformanceFrequency()); float softDropTimer = 0.0f; // Flush initial queued events (e.g., from OnionOS menu launch/button presses) std::cout << "[INFO] Flushing initial event queue..." << std::endl; SDL_Event trashEvent; int flushedCount = 0; while (SDL_PollEvent(&trashEvent)) { flushedCount++; } std::cout << "[INFO] Flushed " << flushedCount << " initial events." << std::endl; // 6. Execution Loop while (!quit) { // A. Calculate frame delta-time (dt) uint64_t currentTime = SDL_GetPerformanceCounter(); float dt = static_cast((currentTime - lastTime) / freq); lastTime = currentTime; // Cap dt to prevent massive jumps during window drags or lags if (dt > 0.1f) dt = 0.1f; // B. Handle Inputs & Events while (SDL_PollEvent(&event) != 0) { if (event.type == SDL_QUIT) { std::cout << "[EVENT] Received SDL_QUIT signal." << std::endl; quit = true; } else if (event.type == SDL_KEYDOWN) { SDL_Keycode sym = event.key.keysym.sym; std::cout << "[EVENT] Key Down: " << sym << " (" << SDL_GetKeyName(sym) << ")" << std::endl; if (sym == SDLK_s) { gameRenderer.takeScreenshot("screenshot.bmp"); std::cout << "[INFO] Screenshot captured to screenshot.bmp" << std::endl; } // START Screen Inputs if (game.getState() == GameState::START) { if (sym == SDLK_RETURN || sym == SDLK_KP_ENTER || sym == SDLK_LALT) { std::cout << "[INPUT] Start/Confirm game triggered." << std::endl; game.setState(GameState::PLAYING); game.reset(); synth.playBGM(true); } else if (sym == SDLK_ESCAPE) { // Cooldown check (1000ms) to prevent immediate exit due to leftover events on boot Uint32 currentTicks = SDL_GetTicks(); if (currentTicks > 1000) { std::cout << "[INPUT] Quit game requested via ESCAPE/SELECT (ticks: " << currentTicks << ")" << std::endl; quit = true; } else { std::cout << "[INPUT] Blocked early ESCAPE/SELECT quit request (cooldown ticks: " << currentTicks << ")" << std::endl; } } } // PLAYING State Inputs else if (game.getState() == GameState::PLAYING) { switch (sym) { case SDLK_LEFT: game.moveLeft(); break; case SDLK_RIGHT: game.moveRight(); break; case SDLK_UP: case SDLK_x: case SDLK_LALT: // Gamepad A (Rotate CW) game.rotate(1); // Clockwise rotation break; case SDLK_z: case SDLK_LCTRL: // Gamepad B (Rotate CCW) game.rotate(-1); // Counter-Clockwise rotation break; case SDLK_SPACE: game.hardDrop(); break; case SDLK_c: case SDLK_LSHIFT: game.holdPiece(); break; case SDLK_p: game.setState(GameState::PAUSED); synth.playBGM(false); break; case SDLK_r: synth.stopAllSFX(); game.reset(); synth.playBGM(true); break; case SDLK_ESCAPE: synth.stopAllSFX(); game.setState(GameState::START); synth.playBGM(false); break; default: break; } } // PAUSED State Inputs else if (game.getState() == GameState::PAUSED) { if (sym == SDLK_p) { game.setState(GameState::PLAYING); synth.playBGM(true); } else if (sym == SDLK_ESCAPE) { synth.stopAllSFX(); game.setState(GameState::START); synth.playBGM(false); } } // GAME OVER State Inputs else if (game.getState() == GameState::GAME_OVER) { if (sym == SDLK_r) { synth.stopAllSFX(); game.setState(GameState::PLAYING); game.reset(); synth.playBGM(true); } else if (sym == SDLK_ESCAPE || sym == SDLK_RETURN || sym == SDLK_KP_ENTER || sym == SDLK_LALT) { synth.stopAllSFX(); game.setState(GameState::START); synth.playBGM(false); } } } } // C. Continuous Keyboard Polling for Smooth Soft-Drop sliding if (game.getState() == GameState::PLAYING) { const Uint8* keyboardState = SDL_GetKeyboardState(nullptr); if (keyboardState[SDL_SCANCODE_DOWN]) { softDropTimer += dt; if (softDropTimer >= 0.04f) { // Soft drop tick speed (every 40ms) softDropTimer = 0.0f; game.softDrop(); } } else { softDropTimer = 0.0f; } } // D. Update Game Logic (gravity timing) bool lineCleared = game.update(dt); if (lineCleared) { // Trigger glorious pixel explosion of sparks on cleared rows gameRenderer.spawnLineClearParticles(game); } // E. Bridge Game Events to Synth Sound Triggers and Particle Emitters if (game.flagMoveSFX) { synth.triggerSFX(SFXType::MOVE); game.flagMoveSFX = false; } if (game.flagRotateSFX) { synth.triggerSFX(SFXType::ROTATE); game.flagRotateSFX = false; } if (game.flagLandSFX) { synth.triggerSFX(SFXType::LAND); // Spawn subtle white-gray impact smoke clouds at bottom row of contact auto lockedCells = game.mLastLockedCells; if (!lockedCells.empty()) { int lowestY = -100; for (const auto& p : lockedCells) { if (p.y > lowestY) lowestY = p.y; } for (const auto& p : lockedCells) { if (p.y == lowestY) { gameRenderer.spawnLandDustParticles(p.x, p.y, game.getActivePieceType()); } } } game.flagLandSFX = false; } if (game.flagLineClearSFX) { synth.triggerSFX(SFXType::LINE_CLEAR); game.flagLineClearSFX = false; } if (game.flagTetrisClearSFX) { synth.triggerSFX(SFXType::TETRIS_CLEAR); game.flagTetrisClearSFX = false; } if (game.flagLevelUpSFX) { synth.triggerSFX(SFXType::LEVEL_UP); game.flagLevelUpSFX = false; } if (game.flagGameOverSFX) { synth.triggerSFX(SFXType::GAME_OVER); synth.playBGM(false); game.flagGameOverSFX = false; } // F. Update Parallax Background and Particle physics gameRenderer.update(dt); // G. Render active frame (automatically uploads backbuffer and calls SDL_RenderPresent) gameRenderer.render(game, synth); // H. Automatic periodic screenshot disabled on Miyoo (FAT32 write-during-render issues) #ifndef MIYOO_BUILD static float autoScreenshotTimer = 0.0f; autoScreenshotTimer += dt; if (autoScreenshotTimer >= 2.0f) { autoScreenshotTimer = 0.0f; gameRenderer.takeScreenshot("screenshot.bmp"); } #endif } // 7. Cleanup and close resources synth.playBGM(false); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; }