From 1d8f53b49e83e9af8a04dffc80ca59321b75b483 Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:30:30 -0700 Subject: [PATCH] Space invaders UI improvements for all device screens including new PortaRF (#2810) * Dynamic screen for PortaRF Dynamic screen size for new PortaRF device. * Format code * Space Invaders improvements for all devices including new PortaRF Space Invaders improvements for all devices including new PortaRF --- .../external/spaceinv/ui_spaceinv.cpp | 315 +++++++++--------- .../external/spaceinv/ui_spaceinv.hpp | 4 +- 2 files changed, 168 insertions(+), 151 deletions(-) diff --git a/firmware/application/external/spaceinv/ui_spaceinv.cpp b/firmware/application/external/spaceinv/ui_spaceinv.cpp index 9c02d9dea..0f96c153e 100644 --- a/firmware/application/external/spaceinv/ui_spaceinv.cpp +++ b/firmware/application/external/spaceinv/ui_spaceinv.cpp @@ -10,22 +10,27 @@ namespace ui::external_app::spaceinv { -// Game constants -#define PLAYER_WIDTH 26 -#define PLAYER_HEIGHT 16 -#define PLAYER_Y 280 -#define BULLET_WIDTH 3 -#define BULLET_HEIGHT 10 -#define BULLET_SPEED 8 -#define MAX_BULLETS 3 -#define INVADER_WIDTH 20 -#define INVADER_HEIGHT 16 -#define INVADER_ROWS 5 // Classic 5 rows -#define INVADER_COLS 6 // Reduced for more movement space on narrow PP screen -#define INVADER_GAP_X 10 -#define INVADER_GAP_Y 8 // Gap between invaders -#define MAX_ENEMY_BULLETS 3 -#define ENEMY_BULLET_SPEED 3 +// Dynamic game constants +static int SCREEN_WIDTH = 240; +static int SCREEN_HEIGHT = 320; +static int PLAYER_WIDTH = 26; +static int PLAYER_HEIGHT = 16; +static int PLAYER_Y = 280; +static int BULLET_WIDTH = 3; +static int BULLET_HEIGHT = 10; +static int BULLET_SPEED = 8; +static int MAX_BULLETS = 3; +static int INVADER_WIDTH = 20; +static int INVADER_HEIGHT = 16; +static int INVADER_ROWS = 5; +static int INVADER_COLS = 6; +static int INVADER_GAP_X = 10; +static int INVADER_GAP_Y = 8; +static int MAX_ENEMY_BULLETS = 3; +static int ENEMY_BULLET_SPEED = 3; +static int INFO_BAR_HEIGHT = 50; +static int INVADER_MOVE_AMOUNT = 8; +static int INVADER_DROP_AMOUNT = 15; // Game state static int game_state = 0; // 0=menu, 1=playing, 2=game over, 3=wave_complete @@ -38,18 +43,18 @@ static uint32_t speed_bonus = 0; static uint32_t wave_complete_timer = 0; // Cannon Bullets -static int bullet_x[MAX_BULLETS] = {0, 0, 0}; -static int bullet_y[MAX_BULLETS] = {0, 0, 0}; -static bool bullet_active[MAX_BULLETS] = {false, false, false}; +static int bullet_x[3] = {0, 0, 0}; +static int bullet_y[3] = {0, 0, 0}; +static bool bullet_active[3] = {false, false, false}; // Enemy bullets -static int enemy_bullet_x[MAX_ENEMY_BULLETS] = {0, 0, 0}; -static int enemy_bullet_y[MAX_ENEMY_BULLETS] = {0, 0, 0}; -static bool enemy_bullet_active[MAX_ENEMY_BULLETS] = {false, false, false}; +static int enemy_bullet_x[3] = {0, 0, 0}; +static int enemy_bullet_y[3] = {0, 0, 0}; +static bool enemy_bullet_active[3] = {false, false, false}; static uint32_t enemy_fire_counter = 0; // Invaders -static bool invaders[INVADER_ROWS][INVADER_COLS]; +static bool invaders[5][11]; // Max possible columns static int invaders_x = 40; static int invaders_y = 60; static int invader_direction = 1; @@ -94,15 +99,47 @@ void Ticker::detach() { game_update_callback = nullptr; } +static void init_dimensions() { + SCREEN_WIDTH = ui::screen_width; + SCREEN_HEIGHT = ui::screen_height; + + // Scale player based on screen size + PLAYER_WIDTH = SCREEN_WIDTH / 9; + PLAYER_HEIGHT = SCREEN_HEIGHT / 20; + PLAYER_Y = SCREEN_HEIGHT - 40; + + // Keep invader size fixed for proper pixel art + INVADER_WIDTH = 20; + INVADER_HEIGHT = 16; + + // Calculate columns based on available width + int available_width = SCREEN_WIDTH - 40; // 20px margins on each side + INVADER_COLS = available_width / (INVADER_WIDTH + INVADER_GAP_X); + if (INVADER_COLS > 11) INVADER_COLS = 11; + if (INVADER_COLS < 6) INVADER_COLS = 6; + + // Center the invader formation + int formation_width = INVADER_COLS * INVADER_WIDTH + (INVADER_COLS - 1) * INVADER_GAP_X; + invaders_x = (SCREEN_WIDTH - formation_width) / 2; + invaders_y = INFO_BAR_HEIGHT + 10; + + // Scale movement + INVADER_MOVE_AMOUNT = SCREEN_WIDTH / 30; + INVADER_DROP_AMOUNT = SCREEN_HEIGHT / 21; +} + static void init_invaders() { - // Initialize invader array + // Initialize invader array based on dynamic columns for (int row = 0; row < INVADER_ROWS; row++) { for (int col = 0; col < INVADER_COLS; col++) { invaders[row][col] = true; } } - invaders_x = 40; - invaders_y = 60; + + // Reset position to center + int formation_width = INVADER_COLS * INVADER_WIDTH + (INVADER_COLS - 1) * INVADER_GAP_X; + invaders_x = (SCREEN_WIDTH - formation_width) / 2; + invaders_y = INFO_BAR_HEIGHT + 10; invader_direction = 1; invader_move_counter = 0; @@ -125,138 +162,114 @@ static void save_high_score() { } static void init_menu() { - painter.fill_rectangle({0, 0, 240, 320}, Color::black()); + painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, Color::black()); auto style_green = *ui::Theme::getInstance()->fg_green; auto style_yellow = *ui::Theme::getInstance()->fg_yellow; auto style_cyan = *ui::Theme::getInstance()->fg_cyan; + auto style_red = *ui::Theme::getInstance()->fg_red; - int16_t screen_width = 240; - int16_t title_x = (screen_width - 15 * 8) / 2; - int16_t divider_width = 24 * 8; - int16_t divider_x = (screen_width - divider_width) / 2; - int16_t instruction_width = 22 * 8; - int16_t instruction_x = (screen_width - instruction_width) / 2; - int16_t prompt_width = 12 * 8; - prompt_x = (screen_width - prompt_width) / 2; + // Title section + painter.draw_string({UI_POS_X_CENTER(15), 30}, style_green, "SPACE INVADERS"); + painter.draw_string({UI_POS_X_CENTER(24), 55}, style_yellow, "========================"); - painter.fill_rectangle({0, 30, screen_width, 30}, Color::black()); - painter.draw_string({title_x, 42}, style_green, "SPACE INVADERS"); + // Instructions box + int box_width = 22 * 8; + int box_x = UI_POS_X_CENTER(22); + painter.fill_rectangle({box_x - 5, 85, box_width + 10, 80}, Color::black()); + painter.draw_rectangle({box_x - 5, 85, box_width + 10, 80}, Color::white()); - painter.draw_string({divider_x, 70}, style_yellow, "========================"); + painter.draw_string({box_x, 95}, style_cyan, " ROTARY: MOVE SHIP"); + painter.draw_string({box_x, 115}, style_cyan, " SELECT: FIRE"); + painter.draw_string({box_x, 135}, style_cyan, " DEFEND EARTH!"); - painter.fill_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::black()); - painter.draw_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::white()); + // Point values + painter.draw_string({UI_POS_X_CENTER(16), 175}, style_red, "TOP ROW = 30 PTS"); + painter.draw_string({UI_POS_X_CENTER(16), 190}, style_yellow, "MID ROW = 20 PTS"); + painter.draw_string({UI_POS_X_CENTER(16), 205}, style_green, "BOT ROW = 10 PTS"); - painter.draw_string({instruction_x, 110}, style_cyan, " ROTARY: MOVE SHIP"); - painter.draw_string({instruction_x, 130}, style_cyan, " SELECT: FIRE"); - painter.draw_string({instruction_x, 150}, style_cyan, " DEFEND EARTH!"); - - // Draw point values - auto style_purple = *ui::Theme::getInstance()->fg_magenta; - painter.draw_string({50, 190}, style_purple, "TOP ROW = 30 PTS"); - painter.draw_string({50, 205}, style_yellow, "MID ROW = 20 PTS"); - painter.draw_string({50, 220}, style_green, "BOT ROW = 10 PTS"); - - // Draw high score + // High score if (current_instance) { auto style_white = *ui::Theme::getInstance()->fg_light; std::string high_score_text = "HIGH SCORE: " + std::to_string(current_instance->highScore); - int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2; - painter.draw_string({high_score_x, 240}, style_white, high_score_text); + painter.draw_string({UI_POS_X_CENTER(high_score_text.length()), 225}, style_white, high_score_text); } + // Set prompt position (will be used for blinking text) + prompt_x = UI_POS_X_CENTER(12); + menu_initialized = true; } static void init_game_over() { - painter.fill_rectangle({0, 0, 240, 320}, Color::black()); + painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, Color::black()); auto style_red = *ui::Theme::getInstance()->fg_red; auto style_yellow = *ui::Theme::getInstance()->fg_yellow; auto style_cyan = *ui::Theme::getInstance()->fg_cyan; auto style_white = *ui::Theme::getInstance()->fg_light; - int16_t screen_width = 240; - // Game Over title - int16_t title_x = (screen_width - 9 * 8) / 2; - painter.draw_string({title_x, 42}, style_red, "GAME OVER"); - - // Divider - int16_t divider_width = 24 * 8; - int16_t divider_x = (screen_width - divider_width) / 2; - painter.draw_string({divider_x, 70}, style_yellow, "========================"); + painter.draw_string({UI_POS_X_CENTER(9), 40}, style_red, "GAME OVER"); + painter.draw_string({UI_POS_X_CENTER(24), 65}, style_yellow, "========================"); // Score box - int16_t box_width = 22 * 8; - int16_t box_x = (screen_width - box_width) / 2; - painter.fill_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::black()); - painter.draw_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::white()); + int box_width = 22 * 8; + int box_x = UI_POS_X_CENTER(22); + painter.fill_rectangle({box_x - 5, 90, box_width + 10, 80}, Color::black()); + painter.draw_rectangle({box_x - 5, 90, box_width + 10, 80}, Color::white()); // Display scores std::string score_text = "SCORE: " + std::to_string(score); - int16_t score_x = (screen_width - score_text.length() * 8) / 2; - painter.draw_string({score_x, 120}, style_cyan, score_text); + painter.draw_string({UI_POS_X_CENTER(score_text.length()), 110}, style_cyan, score_text); std::string wave_text = "WAVE: " + std::to_string(wave); - int16_t wave_x = (screen_width - wave_text.length() * 8) / 2; - painter.draw_string({wave_x, 140}, style_cyan, wave_text); + painter.draw_string({UI_POS_X_CENTER(wave_text.length()), 130}, style_cyan, wave_text); // High score section if (current_instance) { if (score > current_instance->highScore) { // New high score! - std::string new_high = "NEW HIGH SCORE!"; - int16_t new_high_x = (screen_width - new_high.length() * 8) / 2; - painter.draw_string({new_high_x, 200}, style_yellow, new_high); + painter.draw_string({UI_POS_X_CENTER(15), 190}, style_yellow, "NEW HIGH SCORE!"); std::string high_score_text = std::to_string(score); - int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2; - painter.draw_string({high_score_x, 220}, style_yellow, high_score_text); + painter.draw_string({UI_POS_X_CENTER(high_score_text.length()), 210}, style_yellow, high_score_text); } else { // Show existing high score - std::string high_label = "HIGH SCORE:"; - int16_t label_x = (screen_width - high_label.length() * 8) / 2; - painter.draw_string({label_x, 200}, style_white, high_label); + painter.draw_string({UI_POS_X_CENTER(11), 190}, style_white, "HIGH SCORE:"); std::string high_score_text = std::to_string(current_instance->highScore); - int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2; - painter.draw_string({high_score_x, 220}, style_white, high_score_text); + painter.draw_string({UI_POS_X_CENTER(high_score_text.length()), 210}, style_white, high_score_text); } } + // Set prompt position for blinking text + prompt_x = UI_POS_X_CENTER(12); game_over_initialized = true; } static void draw_player() { // Clear the entire player area - painter.fill_rectangle({0, PLAYER_Y - 4, 240, PLAYER_HEIGHT + 8}, Color::black()); + painter.fill_rectangle({0, PLAYER_Y - 4, SCREEN_WIDTH, PLAYER_HEIGHT + 8}, Color::black()); // Draw the classic Space Invaders cannon Color green = Color::green(); + // Scale the drawing based on player size + int unit_x = PLAYER_WIDTH / 26; + int unit_y = PLAYER_HEIGHT / 16; + // Top part - the cannon barrel - painter.draw_hline({player_x + 12, PLAYER_Y}, 2, green); - painter.draw_hline({player_x + 12, PLAYER_Y + 1}, 2, green); + painter.fill_rectangle({player_x + 12 * unit_x, PLAYER_Y, 2 * unit_x, 2 * unit_y}, green); // Upper body - painter.draw_hline({player_x + 11, PLAYER_Y + 2}, 4, green); - painter.draw_hline({player_x + 11, PLAYER_Y + 3}, 4, green); - painter.draw_hline({player_x + 10, PLAYER_Y + 4}, 6, green); - painter.draw_hline({player_x + 10, PLAYER_Y + 5}, 6, green); + painter.fill_rectangle({player_x + 11 * unit_x, PLAYER_Y + 2 * unit_y, 4 * unit_x, 2 * unit_y}, green); + painter.fill_rectangle({player_x + 10 * unit_x, PLAYER_Y + 4 * unit_y, 6 * unit_x, 2 * unit_y}, green); // Main body - painter.draw_hline({player_x + 2, PLAYER_Y + 6}, 22, green); - painter.draw_hline({player_x + 2, PLAYER_Y + 7}, 22, green); - painter.draw_hline({player_x + 1, PLAYER_Y + 8}, 24, green); - painter.draw_hline({player_x + 1, PLAYER_Y + 9}, 24, green); - painter.draw_hline({player_x, PLAYER_Y + 10}, 26, green); - painter.draw_hline({player_x, PLAYER_Y + 11}, 26, green); - painter.draw_hline({player_x, PLAYER_Y + 12}, 26, green); - painter.draw_hline({player_x, PLAYER_Y + 13}, 26, green); - painter.draw_hline({player_x, PLAYER_Y + 14}, 26, green); - painter.draw_hline({player_x, PLAYER_Y + 15}, 26, green); + painter.fill_rectangle({player_x + 2 * unit_x, PLAYER_Y + 6 * unit_y, 22 * unit_x, 10 * unit_y}, green); + painter.fill_rectangle({player_x + unit_x, PLAYER_Y + 8 * unit_y, 24 * unit_x, 8 * unit_y}, green); + painter.fill_rectangle({player_x, PLAYER_Y + 10 * unit_y, 26 * unit_x, 6 * unit_y}, green); } static void draw_invader(int row, int col) { @@ -452,14 +465,14 @@ static void fire_enemy_bullet() { static void update_enemy_bullets() { for (int i = 0; i < MAX_ENEMY_BULLETS; i++) { if (enemy_bullet_active[i]) { - // Clear old position - but protect the border line - if (enemy_bullet_y[i] != 49) { // Don't clear if we're exactly on the border line + // Clear old position + if (enemy_bullet_y[i] != INFO_BAR_HEIGHT - 1) { painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black()); } enemy_bullet_y[i] += ENEMY_BULLET_SPEED; - if (enemy_bullet_y[i] > 320) { + if (enemy_bullet_y[i] > SCREEN_HEIGHT) { enemy_bullet_active[i] = false; } else { // Draw at new position @@ -498,7 +511,7 @@ static void update_invaders() { // Clear old positions clear_all_invaders(); - // Check bounds - find the actual leftmost and rightmost invaders + // Check bounds int leftmost = INVADER_COLS; int rightmost = -1; @@ -513,25 +526,25 @@ static void update_invaders() { bool hit_edge = false; if (invader_direction > 0) { - // Moving right - check rightmost invader + // Moving right - check rightmost invader with margin int right_edge = invaders_x + rightmost * (INVADER_WIDTH + INVADER_GAP_X) + INVADER_WIDTH; - if (right_edge >= 240) { + if (right_edge >= SCREEN_WIDTH - 5) { // 5px margin hit_edge = true; } } else { - // Moving left - check leftmost invader + // Moving left - check leftmost invader with margin int left_edge = invaders_x + leftmost * (INVADER_WIDTH + INVADER_GAP_X); - if (left_edge <= 0) { + if (left_edge <= 5) { // 5px margin hit_edge = true; } } // Move invaders if (hit_edge) { - invaders_y += 15; // Move down + invaders_y += INVADER_DROP_AMOUNT; invader_direction = -invader_direction; } else { - invaders_x += invader_direction * 8; // Horizontal movement + invaders_x += invader_direction * INVADER_MOVE_AMOUNT; } // Toggle animation frame @@ -541,7 +554,7 @@ static void update_invaders() { draw_all_invaders(); } - // Enemy firing logic - only in hard mode or always with lower frequency + // Enemy firing logic if (!current_instance || !current_instance->easy_mode) { if (++enemy_fire_counter >= 120) { // Fire every 2 seconds at 60fps enemy_fire_counter = 0; @@ -568,7 +581,7 @@ static void update_bullets() { bullet_y[i] -= BULLET_SPEED; - if (bullet_y[i] < 50) { + if (bullet_y[i] < INFO_BAR_HEIGHT) { bullet_active[i] = false; } else { painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::white()); @@ -600,7 +613,7 @@ static void check_collisions() { invaders[row][col] = false; painter.fill_rectangle({inv_x, inv_y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black()); - // Score based on row: top=30, middle=20, bottom=10 + // Score based on row if (row == 0) { score += 30; } else if (row == 1 || row == 2) { @@ -635,23 +648,20 @@ static void check_wave_complete() { speed_bonus = (wave - 1) * 3; // Each wave is slightly faster if (speed_bonus > 15) speed_bonus = 15; // Cap the speed increase - // Clear any enemy bullets before transitioning + // Clear any enemy bullets clear_all_enemy_bullets(); - // Set state to wave complete and start timer + // Set state to wave complete game_state = 3; - wave_complete_timer = 60; // 1 second at 60fps + wave_complete_timer = 60; // 1 second - // Clear screen and show wave message - but preserve the border line - painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line at 49 - - // Redraw the border line to ensure it's intact - stupid bullet clearing wants to damage it - painter.draw_hline({0, 49}, 240, Color::white()); + // Clear screen and show wave message + painter.fill_rectangle({0, INFO_BAR_HEIGHT + 1, SCREEN_WIDTH, SCREEN_HEIGHT - INFO_BAR_HEIGHT - 1}, Color::black()); + painter.draw_hline({0, INFO_BAR_HEIGHT - 1}, SCREEN_WIDTH, Color::white()); auto style = *ui::Theme::getInstance()->fg_green; std::string wave_text = "WAVE " + std::to_string(wave); - int wave_x = (240 - wave_text.length() * 8) / 2; - painter.draw_string({wave_x, 150}, style, wave_text); + painter.draw_string({UI_POS_X_CENTER(wave_text.length()), SCREEN_HEIGHT / 2}, style, wave_text); return; } @@ -661,8 +671,8 @@ static void check_wave_complete() { for (int col = 0; col < INVADER_COLS; col++) { if (invaders[row][col]) { int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y); - if (y + INVADER_HEIGHT >= PLAYER_Y) { // Actual collision with player - game_state = 2; // Game over + if (y + INVADER_HEIGHT >= PLAYER_Y) { + game_state = 2; // Game over save_high_score(); return; } @@ -683,10 +693,12 @@ void game_timer_check() { blink_state = !blink_state; auto style = *ui::Theme::getInstance()->fg_red; + int blink_y = 250; // Position below the high score + if (blink_state) { - painter.draw_string({prompt_x, 260}, style, "PRESS SELECT"); + painter.draw_string({prompt_x, blink_y}, style, "PRESS SELECT"); } else { - painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black()); + painter.fill_rectangle({prompt_x, blink_y, 12 * 8, 20}, Color::black()); } } } else if (game_state == 1) { @@ -704,16 +716,14 @@ void game_timer_check() { auto style = *ui::Theme::getInstance()->fg_green; painter.fill_rectangle({5, 10, 100, 20}, Color::black()); painter.draw_string({5, 10}, style, "Score: " + std::to_string(score)); - - // Redraw border line after score update (in case it was damaged) - stupid bullet clearing wants to damage it - painter.draw_hline({0, 49}, 240, Color::white()); + painter.draw_hline({0, INFO_BAR_HEIGHT - 1}, SCREEN_WIDTH, Color::white()); } - // Periodically redraw the border line to fix any damage because it's a pain in the ass + // Periodically redraw the border line static uint32_t border_redraw_counter = 0; - if (++border_redraw_counter >= 60) { // Once per second - might make less if causing flickering + if (++border_redraw_counter >= 60) { border_redraw_counter = 0; - painter.draw_hline({0, 49}, 240, Color::white()); + painter.draw_hline({0, INFO_BAR_HEIGHT - 1}, SCREEN_WIDTH, Color::white()); } } else if (game_state == 2) { // Game over state @@ -726,30 +736,29 @@ void game_timer_check() { blink_state = !blink_state; auto style = *ui::Theme::getInstance()->fg_red; + int blink_y = 250; // Position below high score section + if (blink_state) { - painter.draw_string({prompt_x, 260}, style, "PRESS SELECT"); + painter.draw_string({prompt_x, blink_y}, style, "PRESS SELECT"); } else { - painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black()); + painter.fill_rectangle({prompt_x, blink_y, 12 * 8, 20}, Color::black()); } } } else if (game_state == 3) { // Wave complete state - wait for timer if (--wave_complete_timer <= 0) { - // Timer expired, start next wave game_state = 1; - // Clear and redraw game area - preserve border line - painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line + // Clear and redraw game area + painter.fill_rectangle({0, INFO_BAR_HEIGHT + 1, SCREEN_WIDTH, SCREEN_HEIGHT - INFO_BAR_HEIGHT - 1}, Color::black()); // Restore UI auto style = *ui::Theme::getInstance()->fg_green; painter.draw_string({5, 10}, style, "Score: " + std::to_string(score)); painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives)); + painter.draw_hline({0, INFO_BAR_HEIGHT - 1}, SCREEN_WIDTH, Color::white()); - // Redraw the border line in case it was damaged again - painter.draw_hline({0, 49}, 240, Color::white()); - - // Initialize new wave of invaders + // Initialize new wave init_invaders(); draw_all_invaders(); draw_player(); @@ -760,7 +769,16 @@ void game_timer_check() { SpaceInvadersView::SpaceInvadersView(NavigationView& nav) : nav_{nav} { add_children({&dummy, &button_difficulty}); + + // Initialize dimensions first current_instance = this; + init_dimensions(); + + // Now reposition button with proper centering + int button_y = SCREEN_HEIGHT - 45; // 45px from bottom + int button_x = (SCREEN_WIDTH - 100) / 2; // Center horizontally + button_difficulty.set_parent_rect({button_x, button_y, 100, 20}); + game_timer.attach(&game_timer_check, 1.0 / 60.0); // Update button text based on loaded setting @@ -769,12 +787,10 @@ SpaceInvadersView::SpaceInvadersView(NavigationView& nav) button_difficulty.on_select = [this](Button&) { easy_mode = !easy_mode; button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD"); - // Settings will be saved when the view is destroyed }; } SpaceInvadersView::~SpaceInvadersView() { - // Settings are automatically saved when destroyed current_instance = nullptr; } @@ -791,7 +807,7 @@ void SpaceInvadersView::paint(Painter& painter) { game_over_initialized = false; blink_state = true; blink_counter = 0; - player_x = 107; + player_x = SCREEN_WIDTH / 2 - PLAYER_WIDTH / 2; score = 0; lives = 3; wave = 1; @@ -821,12 +837,13 @@ void SpaceInvadersView::frame_sync() { bool SpaceInvadersView::on_encoder(const EncoderEvent delta) { if (game_state == 1) { + int move_speed = SCREEN_WIDTH / 48; // Scale movement speed if (delta > 0) { - player_x += 5; - if (player_x > 214) player_x = 214; + player_x += move_speed; + if (player_x > SCREEN_WIDTH - PLAYER_WIDTH) player_x = SCREEN_WIDTH - PLAYER_WIDTH; draw_player(); } else if (delta < 0) { - player_x -= 5; + player_x -= move_speed; if (player_x < 0) player_x = 0; draw_player(); } @@ -850,8 +867,8 @@ bool SpaceInvadersView::on_key(const KeyEvent key) { enemy_fire_counter = 0; init_invaders(); - painter.fill_rectangle({0, 0, 240, 320}, Color::black()); - painter.draw_hline({0, 49}, 240, Color::white()); + painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, Color::black()); + painter.draw_hline({0, INFO_BAR_HEIGHT - 1}, SCREEN_WIDTH, Color::white()); auto style = *ui::Theme::getInstance()->fg_green; painter.draw_string({5, 10}, style, "Score: 0"); @@ -876,4 +893,4 @@ bool SpaceInvadersView::on_key(const KeyEvent key) { return true; } -} // namespace ui::external_app::spaceinv +} // namespace ui::external_app::spaceinv \ No newline at end of file diff --git a/firmware/application/external/spaceinv/ui_spaceinv.hpp b/firmware/application/external/spaceinv/ui_spaceinv.hpp index 0aee4c0c9..73b74903d 100644 --- a/firmware/application/external/spaceinv/ui_spaceinv.hpp +++ b/firmware/application/external/spaceinv/ui_spaceinv.hpp @@ -54,7 +54,7 @@ class SpaceInvadersView : public View { NavigationView& nav_; Button button_difficulty{ - {70, 285, 100, 20}, + {70, 275, 100, 20}, "Mode: HARD"}; app_settings::SettingsManager settings_{ @@ -76,4 +76,4 @@ class SpaceInvadersView : public View { } // namespace ui::external_app::spaceinv -#endif /* __UI_SPACEINV_H__ */ +#endif /* __UI_SPACEINV_H__ */ \ No newline at end of file