diff --git a/firmware/application/external/breakout/ui_breakout.cpp b/firmware/application/external/breakout/ui_breakout.cpp index 2287a457f..b71ec57d6 100644 --- a/firmware/application/external/breakout/ui_breakout.cpp +++ b/firmware/application/external/breakout/ui_breakout.cpp @@ -10,91 +10,41 @@ namespace ui::external_app::breakout { -Ticker game_timer; - -int paddle_x = 0; -float ball_x = 0; -float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1; -float ball_dx = 1.5f; -float ball_dy = -2.0f; -int score = 0; -int lives = 3; -int level = 1; -int game_state = STATE_MENU; -bool initialized = false; -bool ball_attached = true; -unsigned int brick_count = 0; - -bool menu_initialized = false; -bool blink_state = true; -uint32_t blink_counter = 0; -int16_t prompt_x = 0; - -bool gameover_initialized = false; -bool gameover_blink_state = true; -uint32_t gameover_blink_counter = 0; -int16_t restart_x = 0; - -bool bricks[BRICK_ROWS][BRICK_COLS]; -int brick_colors[BRICK_ROWS]; - -const Color pp_colors[] = { - Color::white(), - Color::blue(), - Color::yellow(), - Color::purple(), - Color::green(), - Color::red(), - Color::magenta(), - Color::orange(), - Color::black(), -}; - -Painter painter; - -bool but_RIGHT = false; -bool but_LEFT = false; -bool but_SELECT = false; - -static Callback game_update_callback = nullptr; -static uint32_t game_update_timeout = 0; -static uint32_t game_update_counter = 0; - -void cls() { +void BreakoutView::cls() { painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black()); } -void background(int color) { +void BreakoutView::background(int color) { (void)color; } -void fillrect(int x1, int y1, int x2, int y2, int color) { +void BreakoutView::fillrect(int x1, int y1, int x2, int y2, int color) { painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]); } -void rect(int x1, int y1, int x2, int y2, int color) { +void BreakoutView::rect(int x1, int y1, int x2, int y2, int color) { painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]); } -void check_game_timer() { +void BreakoutView::check_game_timer() { if (game_update_callback) { if (++game_update_counter >= game_update_timeout) { game_update_counter = 0; - game_update_callback(); + game_timer_check(); } } } -void Ticker::attach(Callback func, double delay_sec) { - game_update_callback = func; +void BreakoutView::attach(double delay_sec) { + game_update_callback = true; game_update_timeout = delay_sec * 60; } -void Ticker::detach() { - game_update_callback = nullptr; +void BreakoutView::detach() { + game_update_callback = false; } -void game_timer_check() { +void BreakoutView::game_timer_check() { if (game_state == STATE_PLAYING) { update_game(); } else if (game_state == STATE_MENU) { @@ -104,36 +54,57 @@ void game_timer_check() { } } -void init_game() { - paddle_x = (screen_width - PADDLE_WIDTH) / 2; +void BreakoutView::init_game() { + SCREEN_WIDTH = screen_width; + SCREEN_HEIGHT = screen_height; + GAME_AREA_BOTTOM = SCREEN_HEIGHT - 10; + PADDLE_Y = GAME_AREA_BOTTOM - PADDLE_HEIGHT; + PADDLE_SPEED = 10; + + // Calculate brick layout + int available_width = SCREEN_WIDTH - 4; + BRICK_COLS = available_width / 22; + if (BRICK_COLS > 12) BRICK_COLS = 12; + if (BRICK_COLS < 5) BRICK_COLS = 5; + BRICK_WIDTH = (available_width - (BRICK_COLS - 1) * BRICK_GAP) / BRICK_COLS; + + // Initialize paddle position + paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2; score = 0; lives = 3; level = 1; + // Set brick colors brick_colors[0] = Red; brick_colors[1] = Orange; brick_colors[2] = Yellow; brick_colors[3] = Green; brick_colors[4] = Purple; + // Initialize bricks array + bricks.resize(BRICK_ROWS); + for (int i = 0; i < BRICK_ROWS; i++) { + bricks[i].resize(BRICK_COLS); + } + init_level(); game_state = STATE_MENU; - menu_initialized = false; - blink_state = true; - blink_counter = 0; + menu_blink_state = true; + menu_blink_counter = 0; } -void init_level() { +void BreakoutView::init_level() { ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2); ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1; - float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE); - ball_dx = (ball_dx > 0 ? 1.5f : -1.5f) * speed_multiplier; + float speed_multiplier = 1.0f + ((level - 1) * BALL_SPEED_INCREASE); + ball_dx = 1.5f * speed_multiplier; ball_dy = -2.0f * speed_multiplier; ball_attached = true; + // Fill all bricks brick_count = 0; for (int row = 0; row < BRICK_ROWS; row++) { for (int col = 0; col < BRICK_COLS; col++) { @@ -143,10 +114,9 @@ void init_level() { } } -void draw_screen() { +void BreakoutView::draw_screen() { cls(); background(COLOR_BACKGROUND); - draw_borders(); draw_bricks(); draw_paddle(); @@ -156,11 +126,11 @@ void draw_screen() { draw_level(); } -void draw_borders() { - rect(0, GAME_AREA_TOP - 1, screen_width, GAME_AREA_TOP, COLOR_BORDER); +void BreakoutView::draw_borders() { + rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER); } -void draw_bricks() { +void BreakoutView::draw_bricks() { for (int row = 0; row < BRICK_ROWS; row++) { for (int col = 0; col < BRICK_COLS; col++) { if (bricks[row][col]) { @@ -173,37 +143,37 @@ void draw_bricks() { } } -void draw_paddle() { +void BreakoutView::draw_paddle() { fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_PADDLE); } -void draw_ball() { +void BreakoutView::draw_ball() { fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BALL); } -void draw_score() { +void BreakoutView::draw_score() { auto style = *ui::Theme::getInstance()->fg_green; painter.draw_string({5, 10}, style, "Score: " + std::to_string(score)); } -void draw_lives() { +void BreakoutView::draw_lives() { auto style = *ui::Theme::getInstance()->fg_red; painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives)); } -void draw_level() { +void BreakoutView::draw_level() { auto style = *ui::Theme::getInstance()->fg_yellow; painter.draw_string({80, 30}, style, "Level: " + std::to_string(level)); } -void move_paddle_left() { +void BreakoutView::move_paddle_left() { if (paddle_x > 0) { fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND); if (ball_attached) { fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND); } - paddle_x -= 10; + paddle_x -= PADDLE_SPEED; if (paddle_x < 0) paddle_x = 0; if (ball_attached) { @@ -217,15 +187,15 @@ void move_paddle_left() { } } -void move_paddle_right() { - if (paddle_x < screen_width - PADDLE_WIDTH) { +void BreakoutView::move_paddle_right() { + if (paddle_x < SCREEN_WIDTH - PADDLE_WIDTH) { fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND); if (ball_attached) { fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND); } - paddle_x += 10; - if (paddle_x > screen_width - PADDLE_WIDTH) paddle_x = screen_width - PADDLE_WIDTH; + paddle_x += PADDLE_SPEED; + if (paddle_x > SCREEN_WIDTH - PADDLE_WIDTH) paddle_x = SCREEN_WIDTH - PADDLE_WIDTH; if (ball_attached) { ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2); @@ -238,26 +208,41 @@ void move_paddle_right() { } } -void launch_ball() { +void BreakoutView::launch_ball() { if (ball_attached) { ball_attached = false; - ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2); - ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1; - float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE); + float speed_multiplier = 1.0f + ((level - 1) * BALL_SPEED_INCREASE); ball_dx = 1.5f * speed_multiplier; ball_dy = -2.0f * speed_multiplier; } } -void update_game() { +void BreakoutView::update_game() { if (ball_attached) { return; } fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND); - float next_ball_y = ball_y + ball_dy; - if (next_ball_y > GAME_AREA_BOTTOM) { + ball_x += ball_dx; + ball_y += ball_dy; + + // Wall collisions + if (ball_x < 0) { + ball_x = 0; + ball_dx = -ball_dx; + } else if (ball_x > SCREEN_WIDTH - BALL_SIZE) { + ball_x = SCREEN_WIDTH - BALL_SIZE; + ball_dx = -ball_dx; + } + + if (ball_y < GAME_AREA_TOP) { + ball_y = GAME_AREA_TOP; + ball_dy = -ball_dy; + } + + // Bottom edge - lose life + if (ball_y > GAME_AREA_BOTTOM) { lives--; draw_lives(); if (lives <= 0) { @@ -271,37 +256,20 @@ void update_game() { return; } - ball_x += ball_dx; - ball_y = next_ball_y; - - if (ball_x < 0) { - ball_x = 0; - ball_dx = -ball_dx; - } else if (ball_x > screen_width - BALL_SIZE) { - ball_x = screen_width - BALL_SIZE; - ball_dx = -ball_dx; - } - - if (ball_y < GAME_AREA_TOP) { - ball_y = GAME_AREA_TOP; - ball_dy = -ball_dy; - } - + // Paddle collision if (ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_HEIGHT) { if (ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_WIDTH) { ball_y = PADDLE_Y - BALL_SIZE; + ball_dy = -ball_dy; + + // Add some angle based on hit position float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x; float angle = (hit_position / PADDLE_WIDTH) - 0.5f; ball_dx = angle * 4.0f; - if (ball_dx > -0.5f && ball_dx < 0.5f) { - ball_dx = (ball_dx > 0) ? 0.5f : -0.5f; - } - ball_dy = -ball_dy; } } check_collisions(); - draw_ball(); if (check_level_complete()) { @@ -309,151 +277,93 @@ void update_game() { } } -void check_collisions() { +void BreakoutView::check_collisions() { int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP); int grid_y = (ball_y - GAME_AREA_TOP - 5) / (BRICK_HEIGHT + BRICK_GAP); - for (int row = grid_y - 1; row <= grid_y + 1; row++) { - for (int col = grid_x - 1; col <= grid_x + 1; col++) { - if (row >= 0 && row < BRICK_ROWS && col >= 0 && col < BRICK_COLS) { - if (bricks[row][col] && check_brick_collision(row, col)) { - return; - } - } - } + if (grid_x >= 0 && grid_x < BRICK_COLS && grid_y >= 0 && grid_y < BRICK_ROWS) { + check_brick_collision(grid_y, grid_x); } } -bool check_brick_collision(int row, int col) { +bool BreakoutView::check_brick_collision(int row, int col) { + if (!bricks[row][col]) return false; + int brick_x = col * (BRICK_WIDTH + BRICK_GAP); int brick_y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5; if (ball_x + BALL_SIZE >= brick_x && ball_x <= brick_x + BRICK_WIDTH && ball_y + BALL_SIZE >= brick_y && ball_y <= brick_y + BRICK_HEIGHT) { fillrect(brick_x, brick_y, brick_x + BRICK_WIDTH, brick_y + BRICK_HEIGHT, COLOR_BACKGROUND); - bricks[row][col] = false; brick_count--; score += (5 - row) * 10; draw_score(); - float center_x = brick_x + BRICK_WIDTH / 2; - float center_y = brick_y + BRICK_HEIGHT / 2; - float ball_center_x = ball_x + BALL_SIZE / 2; - float ball_center_y = ball_y + BALL_SIZE / 2; - float dx = std::abs(ball_center_x - center_x); - float dy = std::abs(ball_center_y - center_y); - - if (dx * BRICK_HEIGHT > dy * BRICK_WIDTH) { - ball_dx = -ball_dx; - } else { - ball_dy = -ball_dy; - } - + ball_dy = -ball_dy; return true; } return false; } -bool check_level_complete() { +bool BreakoutView::check_level_complete() { return brick_count == 0; } -void next_level() { +void BreakoutView::next_level() { level++; init_level(); draw_screen(); } -void handle_game_over() { +void BreakoutView::handle_game_over() { game_state = STATE_GAME_OVER; - gameover_initialized = false; - show_game_over(); -} - -void init_menu() { - cls(); - background(COLOR_BACKGROUND); - - auto style_yellow = *ui::Theme::getInstance()->fg_yellow; - auto style_blue = *ui::Theme::getInstance()->fg_blue; - auto style_cyan = *ui::Theme::getInstance()->fg_cyan; - - int16_t screen_width = ui::screen_width; - int16_t title_x = (screen_width - 17 * 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 = 16 * 8; - prompt_x = (screen_width - prompt_width) / 2; - - painter.fill_rectangle({0, 30, screen_width, 30}, Color::black()); - painter.draw_string({title_x + 2, 42}, style_yellow, "*** BREAKOUT ***"); - painter.draw_string({divider_x, 70}, style_blue, "========================"); - painter.fill_rectangle({instruction_x - 5, 110, instruction_width + 10, 70}, Color::black()); - painter.draw_rectangle({instruction_x - 5, 110, instruction_width + 10, 70}, Color::white()); - painter.draw_string({instruction_x, 120}, style_cyan, " ROTARY: MOVE PADDLE"); - painter.draw_string({instruction_x, 150}, style_cyan, " SELECT: START/LAUNCH"); - painter.draw_string({divider_x, 190}, style_blue, "========================"); - - menu_initialized = true; -} - -void show_menu() { - if (!menu_initialized) { - init_menu(); - } - - auto style_red = *ui::Theme::getInstance()->fg_red; - - if (++blink_counter >= 30) { - blink_counter = 0; - blink_state = !blink_state; - - painter.fill_rectangle({prompt_x - 2, 228, 16 * 8 + 4, 20}, Color::black()); - - if (blink_state) { - painter.draw_string({prompt_x, 230}, style_red, "* PRESS SELECT *"); - } - } -} - -void init_game_over() { - cls(); - background(COLOR_BACKGROUND); - - auto style_red = *ui::Theme::getInstance()->fg_red; - auto style_yellow = *ui::Theme::getInstance()->fg_yellow; - - // int16_t screen_width = screen_width; - int16_t title_width = 9 * 8; - int16_t title_x = (screen_width - title_width) / 2; - int16_t score_text_width = (16 + std::to_string(score).length()) * 8; - int16_t score_x = (screen_width - score_text_width) / 2; - - painter.draw_rectangle({20, 80, screen_width - 40, 160}, Color::red()); - painter.draw_rectangle({22, 82, screen_width - 44, 156}, Color::white()); - - painter.draw_string({title_x, 100}, style_red, "GAME OVER"); - - painter.fill_rectangle({40, 140, screen_width - 80, 30}, Color::black()); - painter.draw_rectangle({40, 140, screen_width - 80, 30}, Color::yellow()); - painter.draw_string({score_x, 150}, style_yellow, " FINAL SCORE: " + std::to_string(score)); - - int16_t restart_width = 12 * 8; - restart_x = (screen_width - restart_width) / 2; - - gameover_initialized = true; gameover_blink_state = true; gameover_blink_counter = 0; } -void show_game_over() { - if (!gameover_initialized) { - init_game_over(); +void BreakoutView::show_menu() { + if (menu_blink_counter == 0) { + cls(); + background(COLOR_BACKGROUND); + + auto style_yellow = *ui::Theme::getInstance()->fg_yellow; + auto style_blue = *ui::Theme::getInstance()->fg_blue; + auto style_cyan = *ui::Theme::getInstance()->fg_cyan; + + painter.draw_string({50, 40}, style_yellow, "*** BREAKOUT ***"); + painter.draw_string({20, 100}, style_blue, "========================"); + painter.draw_string({30, 130}, style_cyan, "ROTARY: MOVE PADDLE"); + painter.draw_string({30, 160}, style_cyan, "SELECT: START/LAUNCH"); + painter.draw_string({20, 190}, style_blue, "========================"); + } + + auto style_red = *ui::Theme::getInstance()->fg_red; + + if (++menu_blink_counter >= 30) { + menu_blink_counter = 0; + menu_blink_state = !menu_blink_state; + + painter.fill_rectangle({56, 228, 128, 20}, Color::black()); + + if (menu_blink_state) { + painter.draw_string({56, 230}, style_red, "* PRESS SELECT *"); + } + } +} + +void BreakoutView::show_game_over() { + if (gameover_blink_counter == 0) { + cls(); + background(COLOR_BACKGROUND); + + auto style_red = *ui::Theme::getInstance()->fg_red; + auto style_yellow = *ui::Theme::getInstance()->fg_yellow; + + painter.draw_string({90, 100}, style_red, "GAME OVER"); + painter.draw_string({50, 150}, style_yellow, "SCORE: " + std::to_string(score)); } auto style_green = *ui::Theme::getInstance()->fg_green; @@ -462,32 +372,27 @@ void show_game_over() { gameover_blink_counter = 0; gameover_blink_state = !gameover_blink_state; - painter.fill_rectangle({restart_x - 2, 198, 12 * 8 + 4, 20}, Color::black()); + painter.fill_rectangle({72, 198, 96, 20}, Color::black()); if (gameover_blink_state) { - painter.draw_string({restart_x, 200}, style_green, "PRESS SELECT"); + painter.draw_string({72, 200}, style_green, "PRESS SELECT"); } } } -void reset_game() { +void BreakoutView::reset_game() { level = 1; score = 0; lives = 3; game_state = STATE_PLAYING; init_level(); draw_screen(); - gameover_initialized = false; - gameover_blink_state = true; - gameover_blink_counter = 0; } BreakoutView::BreakoutView(NavigationView& nav) - : nav_{nav} { - paddle_x = (screen_width - PADDLE_WIDTH) / 2; - ball_x = screen_width / 2; + : nav_{nav}, bricks{} { // Add bricks{} here add_children({&dummy}); - game_timer.attach(&game_timer_check, 1.0 / 60.0); + attach(1.0 / 60.0); } void BreakoutView::on_show() { @@ -512,20 +417,15 @@ bool BreakoutView::on_encoder(const EncoderEvent delta) { if (game_state == STATE_PLAYING) { if (delta > 0) { move_paddle_right(); - set_dirty(); } else if (delta < 0) { move_paddle_left(); - set_dirty(); } + set_dirty(); } return true; } bool BreakoutView::on_key(const KeyEvent key) { - but_SELECT = (key == KeyEvent::Select); - but_LEFT = (key == KeyEvent::Left); - but_RIGHT = (key == KeyEvent::Right); - if (key == KeyEvent::Select) { if (game_state == STATE_MENU) { game_state = STATE_PLAYING; @@ -535,14 +435,10 @@ bool BreakoutView::on_key(const KeyEvent key) { } else if (game_state == STATE_GAME_OVER) { reset_game(); } - } else if (key == KeyEvent::Left) { - if (game_state == STATE_PLAYING) { - move_paddle_left(); - } - } else if (key == KeyEvent::Right) { - if (game_state == STATE_PLAYING) { - move_paddle_right(); - } + } else if (key == KeyEvent::Left && game_state == STATE_PLAYING) { + move_paddle_left(); + } else if (key == KeyEvent::Right && game_state == STATE_PLAYING) { + move_paddle_right(); } set_dirty(); diff --git a/firmware/application/external/breakout/ui_breakout.hpp b/firmware/application/external/breakout/ui_breakout.hpp index 1ff303595..022fe7790 100644 --- a/firmware/application/external/breakout/ui_breakout.hpp +++ b/firmware/application/external/breakout/ui_breakout.hpp @@ -17,6 +17,7 @@ #include "random.hpp" #include "lpc43xx_cpp.hpp" #include "ui_widget.hpp" +#include namespace ui::external_app::breakout { @@ -32,41 +33,15 @@ enum { Black, }; -extern const Color pp_colors[]; -extern Painter painter; -extern bool but_RIGHT; -extern bool but_LEFT; -extern bool but_SELECT; - -void cls(); -void background(int color); -void fillrect(int x1, int y1, int x2, int y2, int color); -void rect(int x1, int y1, int x2, int y2, int color); - #define wait(x) chThdSleepMilliseconds(x * 1000) -using Callback = void (*)(void); - -void check_game_timer(); - -class Ticker { - public: - Ticker() = default; - void attach(Callback func, double delay_sec); - void detach(); -}; - #define PADDLE_WIDTH 40 #define PADDLE_HEIGHT 10 #define BALL_SIZE 8 -#define BRICK_WIDTH 20 #define BRICK_HEIGHT 10 #define BRICK_ROWS 5 -#define BRICK_COLS 10 #define BRICK_GAP 2 #define GAME_AREA_TOP 50 -#define GAME_AREA_BOTTOM 310 -#define PADDLE_Y (GAME_AREA_BOTTOM - PADDLE_HEIGHT) #define BALL_SPEED_INCREASE 0.1f #define STATE_MENU 0 @@ -77,49 +52,6 @@ class Ticker { #define COLOR_PADDLE Blue #define COLOR_BALL White #define COLOR_BORDER White -#define COLOR_BRICK_COLORS \ - { Red, Orange, Yellow, Green, Purple } - -extern Ticker game_timer; - -extern int paddle_x; -extern float ball_x; -extern float ball_y; -extern float ball_dx; -extern float ball_dy; -extern int score; -extern int lives; -extern int level; -extern int game_state; -extern bool initialized; -extern bool ball_attached; -extern unsigned int brick_count; -extern bool bricks[BRICK_ROWS][BRICK_COLS]; -extern int brick_colors[BRICK_ROWS]; - -void game_timer_check(); -void init_game(); -void init_level(); -void draw_screen(); -void draw_bricks(); -void draw_paddle(); -void draw_ball(); -void draw_score(); -void draw_lives(); -void draw_level(); -void draw_borders(); -void move_paddle_left(); -void move_paddle_right(); -void launch_ball(); -void update_game(); -void check_collisions(); -bool check_brick_collision(int row, int col); -void handle_game_over(); -void show_menu(); -void show_game_over(); -bool check_level_complete(); -void next_level(); -void reset_game(); class BreakoutView : public View { public: @@ -134,9 +66,86 @@ class BreakoutView : public View { bool on_encoder(const EncoderEvent event) override; bool on_key(KeyEvent key) override; + void cls(); + void background(int color); + void fillrect(int x1, int y1, int x2, int y2, int color); + void rect(int x1, int y1, int x2, int y2, int color); + void game_timer_check(); + void init_game(); + void init_level(); + void draw_screen(); + void draw_bricks(); + void draw_paddle(); + void draw_ball(); + void draw_score(); + void draw_lives(); + void draw_level(); + void draw_borders(); + void move_paddle_left(); + void move_paddle_right(); + void launch_ball(); + void update_game(); + void check_collisions(); + bool check_brick_collision(int row, int col); + void handle_game_over(); + void show_menu(); + void show_game_over(); + bool check_level_complete(); + void next_level(); + void reset_game(); + void check_game_timer(); + void attach(double delay_sec); + void detach(); + private: - bool initialized = false; + const Color pp_colors[9] = { + Color::white(), + Color::blue(), + Color::yellow(), + Color::purple(), + Color::green(), + Color::red(), + Color::magenta(), + Color::orange(), + Color::black(), + }; NavigationView& nav_; + Painter painter{}; + + // Dynamic screen-dependent values + int SCREEN_WIDTH = 0; + int SCREEN_HEIGHT = 0; + int GAME_AREA_BOTTOM = 0; + int PADDLE_Y = 0; + int BRICK_WIDTH = 0; + int BRICK_COLS = 0; + int PADDLE_SPEED = 0; + + int paddle_x = 0; + float ball_x = 0; + float ball_y = 0; + float ball_dx = 1.5f; + float ball_dy = -2.0f; + int score = 0; + int lives = 3; + int level = 1; + int game_state = STATE_MENU; + bool initialized = false; + bool ball_attached = true; + unsigned int brick_count = 0; + + bool menu_blink_state = true; + uint32_t menu_blink_counter = 0; + + bool gameover_blink_state = true; + uint32_t gameover_blink_counter = 0; + + std::vector> bricks; + int brick_colors[BRICK_ROWS]; + + bool game_update_callback = false; + double game_update_timeout = 0; + uint32_t game_update_counter = 0; Button dummy{ {screen_width, 0, 0, 0},