CyberMatris port for Miyoo Mini Plus
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

359 lines
14 KiB

#include <SDL2/SDL.h>
#include <csignal>
#include <iostream>
#include <chrono>
#ifdef MIYOO_BUILD
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#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<double>(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<float>((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;
}