mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-11-24 05:43:11 -05:00
Make dynamic screen for PortaRF (#2800)
This commit is contained in:
parent
b3edaf3cd0
commit
23cabb8b8a
2 changed files with 220 additions and 315 deletions
|
|
@ -10,91 +10,41 @@
|
||||||
|
|
||||||
namespace ui::external_app::breakout {
|
namespace ui::external_app::breakout {
|
||||||
|
|
||||||
Ticker game_timer;
|
void BreakoutView::cls() {
|
||||||
|
|
||||||
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() {
|
|
||||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
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)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]);
|
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]);
|
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_callback) {
|
||||||
if (++game_update_counter >= game_update_timeout) {
|
if (++game_update_counter >= game_update_timeout) {
|
||||||
game_update_counter = 0;
|
game_update_counter = 0;
|
||||||
game_update_callback();
|
game_timer_check();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ticker::attach(Callback func, double delay_sec) {
|
void BreakoutView::attach(double delay_sec) {
|
||||||
game_update_callback = func;
|
game_update_callback = true;
|
||||||
game_update_timeout = delay_sec * 60;
|
game_update_timeout = delay_sec * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ticker::detach() {
|
void BreakoutView::detach() {
|
||||||
game_update_callback = nullptr;
|
game_update_callback = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_timer_check() {
|
void BreakoutView::game_timer_check() {
|
||||||
if (game_state == STATE_PLAYING) {
|
if (game_state == STATE_PLAYING) {
|
||||||
update_game();
|
update_game();
|
||||||
} else if (game_state == STATE_MENU) {
|
} else if (game_state == STATE_MENU) {
|
||||||
|
|
@ -104,36 +54,57 @@ void game_timer_check() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_game() {
|
void BreakoutView::init_game() {
|
||||||
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
|
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;
|
score = 0;
|
||||||
lives = 3;
|
lives = 3;
|
||||||
level = 1;
|
level = 1;
|
||||||
|
|
||||||
|
// Set brick colors
|
||||||
brick_colors[0] = Red;
|
brick_colors[0] = Red;
|
||||||
brick_colors[1] = Orange;
|
brick_colors[1] = Orange;
|
||||||
brick_colors[2] = Yellow;
|
brick_colors[2] = Yellow;
|
||||||
brick_colors[3] = Green;
|
brick_colors[3] = Green;
|
||||||
brick_colors[4] = Purple;
|
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();
|
init_level();
|
||||||
|
|
||||||
game_state = STATE_MENU;
|
game_state = STATE_MENU;
|
||||||
menu_initialized = false;
|
menu_blink_state = true;
|
||||||
blink_state = true;
|
menu_blink_counter = 0;
|
||||||
blink_counter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_level() {
|
void BreakoutView::init_level() {
|
||||||
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
|
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
|
||||||
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
|
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 = (ball_dx > 0 ? 1.5f : -1.5f) * speed_multiplier;
|
ball_dx = 1.5f * speed_multiplier;
|
||||||
ball_dy = -2.0f * speed_multiplier;
|
ball_dy = -2.0f * speed_multiplier;
|
||||||
|
|
||||||
ball_attached = true;
|
ball_attached = true;
|
||||||
|
|
||||||
|
// Fill all bricks
|
||||||
brick_count = 0;
|
brick_count = 0;
|
||||||
for (int row = 0; row < BRICK_ROWS; row++) {
|
for (int row = 0; row < BRICK_ROWS; row++) {
|
||||||
for (int col = 0; col < BRICK_COLS; col++) {
|
for (int col = 0; col < BRICK_COLS; col++) {
|
||||||
|
|
@ -143,10 +114,9 @@ void init_level() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_screen() {
|
void BreakoutView::draw_screen() {
|
||||||
cls();
|
cls();
|
||||||
background(COLOR_BACKGROUND);
|
background(COLOR_BACKGROUND);
|
||||||
|
|
||||||
draw_borders();
|
draw_borders();
|
||||||
draw_bricks();
|
draw_bricks();
|
||||||
draw_paddle();
|
draw_paddle();
|
||||||
|
|
@ -156,11 +126,11 @@ void draw_screen() {
|
||||||
draw_level();
|
draw_level();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_borders() {
|
void BreakoutView::draw_borders() {
|
||||||
rect(0, GAME_AREA_TOP - 1, screen_width, GAME_AREA_TOP, COLOR_BORDER);
|
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 row = 0; row < BRICK_ROWS; row++) {
|
||||||
for (int col = 0; col < BRICK_COLS; col++) {
|
for (int col = 0; col < BRICK_COLS; col++) {
|
||||||
if (bricks[row][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);
|
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);
|
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;
|
auto style = *ui::Theme::getInstance()->fg_green;
|
||||||
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
|
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;
|
auto style = *ui::Theme::getInstance()->fg_red;
|
||||||
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
|
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;
|
auto style = *ui::Theme::getInstance()->fg_yellow;
|
||||||
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
|
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
|
||||||
}
|
}
|
||||||
|
|
||||||
void move_paddle_left() {
|
void BreakoutView::move_paddle_left() {
|
||||||
if (paddle_x > 0) {
|
if (paddle_x > 0) {
|
||||||
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
||||||
if (ball_attached) {
|
if (ball_attached) {
|
||||||
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
|
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 (paddle_x < 0) paddle_x = 0;
|
||||||
|
|
||||||
if (ball_attached) {
|
if (ball_attached) {
|
||||||
|
|
@ -217,15 +187,15 @@ void move_paddle_left() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void move_paddle_right() {
|
void BreakoutView::move_paddle_right() {
|
||||||
if (paddle_x < screen_width - PADDLE_WIDTH) {
|
if (paddle_x < SCREEN_WIDTH - PADDLE_WIDTH) {
|
||||||
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
|
||||||
if (ball_attached) {
|
if (ball_attached) {
|
||||||
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
|
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 > screen_width - PADDLE_WIDTH) paddle_x = screen_width - PADDLE_WIDTH;
|
if (paddle_x > SCREEN_WIDTH - PADDLE_WIDTH) paddle_x = SCREEN_WIDTH - PADDLE_WIDTH;
|
||||||
|
|
||||||
if (ball_attached) {
|
if (ball_attached) {
|
||||||
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
|
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) {
|
if (ball_attached) {
|
||||||
ball_attached = false;
|
ball_attached = false;
|
||||||
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
|
float speed_multiplier = 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
|
||||||
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 = 1.5f * speed_multiplier;
|
ball_dx = 1.5f * speed_multiplier;
|
||||||
ball_dy = -2.0f * speed_multiplier;
|
ball_dy = -2.0f * speed_multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_game() {
|
void BreakoutView::update_game() {
|
||||||
if (ball_attached) {
|
if (ball_attached) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
|
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
|
||||||
|
|
||||||
float next_ball_y = ball_y + ball_dy;
|
ball_x += ball_dx;
|
||||||
if (next_ball_y > GAME_AREA_BOTTOM) {
|
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--;
|
lives--;
|
||||||
draw_lives();
|
draw_lives();
|
||||||
if (lives <= 0) {
|
if (lives <= 0) {
|
||||||
|
|
@ -271,37 +256,20 @@ void update_game() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ball_x += ball_dx;
|
// Paddle collision
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_HEIGHT) {
|
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) {
|
if (ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_WIDTH) {
|
||||||
ball_y = PADDLE_Y - BALL_SIZE;
|
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 hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
|
||||||
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
|
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
|
||||||
ball_dx = angle * 4.0f;
|
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();
|
check_collisions();
|
||||||
|
|
||||||
draw_ball();
|
draw_ball();
|
||||||
|
|
||||||
if (check_level_complete()) {
|
if (check_level_complete()) {
|
||||||
|
|
@ -309,71 +277,55 @@ void update_game() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_collisions() {
|
void BreakoutView::check_collisions() {
|
||||||
int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP);
|
int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP);
|
||||||
int grid_y = (ball_y - GAME_AREA_TOP - 5) / (BRICK_HEIGHT + 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++) {
|
if (grid_x >= 0 && grid_x < BRICK_COLS && grid_y >= 0 && grid_y < BRICK_ROWS) {
|
||||||
for (int col = grid_x - 1; col <= grid_x + 1; col++) {
|
check_brick_collision(grid_y, grid_x);
|
||||||
if (row >= 0 && row < BRICK_ROWS && col >= 0 && col < BRICK_COLS) {
|
|
||||||
if (bricks[row][col] && check_brick_collision(row, col)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_x = col * (BRICK_WIDTH + BRICK_GAP);
|
||||||
int brick_y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
|
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 &&
|
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) {
|
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);
|
fillrect(brick_x, brick_y, brick_x + BRICK_WIDTH, brick_y + BRICK_HEIGHT, COLOR_BACKGROUND);
|
||||||
|
|
||||||
bricks[row][col] = false;
|
bricks[row][col] = false;
|
||||||
brick_count--;
|
brick_count--;
|
||||||
|
|
||||||
score += (5 - row) * 10;
|
score += (5 - row) * 10;
|
||||||
draw_score();
|
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 true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_level_complete() {
|
bool BreakoutView::check_level_complete() {
|
||||||
return brick_count == 0;
|
return brick_count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void next_level() {
|
void BreakoutView::next_level() {
|
||||||
level++;
|
level++;
|
||||||
init_level();
|
init_level();
|
||||||
draw_screen();
|
draw_screen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_game_over() {
|
void BreakoutView::handle_game_over() {
|
||||||
game_state = STATE_GAME_OVER;
|
game_state = STATE_GAME_OVER;
|
||||||
gameover_initialized = false;
|
gameover_blink_state = true;
|
||||||
show_game_over();
|
gameover_blink_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_menu() {
|
void BreakoutView::show_menu() {
|
||||||
|
if (menu_blink_counter == 0) {
|
||||||
cls();
|
cls();
|
||||||
background(COLOR_BACKGROUND);
|
background(COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
|
@ -381,79 +333,37 @@ void init_menu() {
|
||||||
auto style_blue = *ui::Theme::getInstance()->fg_blue;
|
auto style_blue = *ui::Theme::getInstance()->fg_blue;
|
||||||
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
||||||
|
|
||||||
int16_t screen_width = ui::screen_width;
|
painter.draw_string({50, 40}, style_yellow, "*** BREAKOUT ***");
|
||||||
int16_t title_x = (screen_width - 17 * 8) / 2;
|
painter.draw_string({20, 100}, style_blue, "========================");
|
||||||
int16_t divider_width = 24 * 8;
|
painter.draw_string({30, 130}, style_cyan, "ROTARY: MOVE PADDLE");
|
||||||
int16_t divider_x = (screen_width - divider_width) / 2;
|
painter.draw_string({30, 160}, style_cyan, "SELECT: START/LAUNCH");
|
||||||
int16_t instruction_width = 22 * 8;
|
painter.draw_string({20, 190}, style_blue, "========================");
|
||||||
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;
|
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||||
|
|
||||||
if (++blink_counter >= 30) {
|
if (++menu_blink_counter >= 30) {
|
||||||
blink_counter = 0;
|
menu_blink_counter = 0;
|
||||||
blink_state = !blink_state;
|
menu_blink_state = !menu_blink_state;
|
||||||
|
|
||||||
painter.fill_rectangle({prompt_x - 2, 228, 16 * 8 + 4, 20}, Color::black());
|
painter.fill_rectangle({56, 228, 128, 20}, Color::black());
|
||||||
|
|
||||||
if (blink_state) {
|
if (menu_blink_state) {
|
||||||
painter.draw_string({prompt_x, 230}, style_red, "* PRESS SELECT *");
|
painter.draw_string({56, 230}, style_red, "* PRESS SELECT *");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_game_over() {
|
void BreakoutView::show_game_over() {
|
||||||
|
if (gameover_blink_counter == 0) {
|
||||||
cls();
|
cls();
|
||||||
background(COLOR_BACKGROUND);
|
background(COLOR_BACKGROUND);
|
||||||
|
|
||||||
auto style_red = *ui::Theme::getInstance()->fg_red;
|
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||||
|
|
||||||
// int16_t screen_width = screen_width;
|
painter.draw_string({90, 100}, style_red, "GAME OVER");
|
||||||
int16_t title_width = 9 * 8;
|
painter.draw_string({50, 150}, style_yellow, "SCORE: " + std::to_string(score));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||||
|
|
@ -462,32 +372,27 @@ void show_game_over() {
|
||||||
gameover_blink_counter = 0;
|
gameover_blink_counter = 0;
|
||||||
gameover_blink_state = !gameover_blink_state;
|
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) {
|
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;
|
level = 1;
|
||||||
score = 0;
|
score = 0;
|
||||||
lives = 3;
|
lives = 3;
|
||||||
game_state = STATE_PLAYING;
|
game_state = STATE_PLAYING;
|
||||||
init_level();
|
init_level();
|
||||||
draw_screen();
|
draw_screen();
|
||||||
gameover_initialized = false;
|
|
||||||
gameover_blink_state = true;
|
|
||||||
gameover_blink_counter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BreakoutView::BreakoutView(NavigationView& nav)
|
BreakoutView::BreakoutView(NavigationView& nav)
|
||||||
: nav_{nav} {
|
: nav_{nav}, bricks{} { // Add bricks{} here
|
||||||
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
|
|
||||||
ball_x = screen_width / 2;
|
|
||||||
add_children({&dummy});
|
add_children({&dummy});
|
||||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
attach(1.0 / 60.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreakoutView::on_show() {
|
void BreakoutView::on_show() {
|
||||||
|
|
@ -512,20 +417,15 @@ bool BreakoutView::on_encoder(const EncoderEvent delta) {
|
||||||
if (game_state == STATE_PLAYING) {
|
if (game_state == STATE_PLAYING) {
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
move_paddle_right();
|
move_paddle_right();
|
||||||
set_dirty();
|
|
||||||
} else if (delta < 0) {
|
} else if (delta < 0) {
|
||||||
move_paddle_left();
|
move_paddle_left();
|
||||||
set_dirty();
|
|
||||||
}
|
}
|
||||||
|
set_dirty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BreakoutView::on_key(const KeyEvent key) {
|
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 (key == KeyEvent::Select) {
|
||||||
if (game_state == STATE_MENU) {
|
if (game_state == STATE_MENU) {
|
||||||
game_state = STATE_PLAYING;
|
game_state = STATE_PLAYING;
|
||||||
|
|
@ -535,15 +435,11 @@ bool BreakoutView::on_key(const KeyEvent key) {
|
||||||
} else if (game_state == STATE_GAME_OVER) {
|
} else if (game_state == STATE_GAME_OVER) {
|
||||||
reset_game();
|
reset_game();
|
||||||
}
|
}
|
||||||
} else if (key == KeyEvent::Left) {
|
} else if (key == KeyEvent::Left && game_state == STATE_PLAYING) {
|
||||||
if (game_state == STATE_PLAYING) {
|
|
||||||
move_paddle_left();
|
move_paddle_left();
|
||||||
}
|
} else if (key == KeyEvent::Right && game_state == STATE_PLAYING) {
|
||||||
} else if (key == KeyEvent::Right) {
|
|
||||||
if (game_state == STATE_PLAYING) {
|
|
||||||
move_paddle_right();
|
move_paddle_right();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
set_dirty();
|
set_dirty();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "random.hpp"
|
#include "random.hpp"
|
||||||
#include "lpc43xx_cpp.hpp"
|
#include "lpc43xx_cpp.hpp"
|
||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ui::external_app::breakout {
|
namespace ui::external_app::breakout {
|
||||||
|
|
||||||
|
|
@ -32,41 +33,15 @@ enum {
|
||||||
Black,
|
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)
|
#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_WIDTH 40
|
||||||
#define PADDLE_HEIGHT 10
|
#define PADDLE_HEIGHT 10
|
||||||
#define BALL_SIZE 8
|
#define BALL_SIZE 8
|
||||||
#define BRICK_WIDTH 20
|
|
||||||
#define BRICK_HEIGHT 10
|
#define BRICK_HEIGHT 10
|
||||||
#define BRICK_ROWS 5
|
#define BRICK_ROWS 5
|
||||||
#define BRICK_COLS 10
|
|
||||||
#define BRICK_GAP 2
|
#define BRICK_GAP 2
|
||||||
#define GAME_AREA_TOP 50
|
#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 BALL_SPEED_INCREASE 0.1f
|
||||||
|
|
||||||
#define STATE_MENU 0
|
#define STATE_MENU 0
|
||||||
|
|
@ -77,26 +52,24 @@ class Ticker {
|
||||||
#define COLOR_PADDLE Blue
|
#define COLOR_PADDLE Blue
|
||||||
#define COLOR_BALL White
|
#define COLOR_BALL White
|
||||||
#define COLOR_BORDER White
|
#define COLOR_BORDER White
|
||||||
#define COLOR_BRICK_COLORS \
|
|
||||||
{ Red, Orange, Yellow, Green, Purple }
|
|
||||||
|
|
||||||
extern Ticker game_timer;
|
class BreakoutView : public View {
|
||||||
|
public:
|
||||||
|
BreakoutView(NavigationView& nav);
|
||||||
|
void on_show() override;
|
||||||
|
|
||||||
extern int paddle_x;
|
std::string title() const override { return "Breakout"; }
|
||||||
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 focus() override { dummy.focus(); }
|
||||||
|
void paint(Painter& painter) override;
|
||||||
|
void frame_sync();
|
||||||
|
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 game_timer_check();
|
||||||
void init_game();
|
void init_game();
|
||||||
void init_level();
|
void init_level();
|
||||||
|
|
@ -120,23 +93,59 @@ void show_game_over();
|
||||||
bool check_level_complete();
|
bool check_level_complete();
|
||||||
void next_level();
|
void next_level();
|
||||||
void reset_game();
|
void reset_game();
|
||||||
|
void check_game_timer();
|
||||||
class BreakoutView : public View {
|
void attach(double delay_sec);
|
||||||
public:
|
void detach();
|
||||||
BreakoutView(NavigationView& nav);
|
|
||||||
void on_show() override;
|
|
||||||
|
|
||||||
std::string title() const override { return "Breakout"; }
|
|
||||||
|
|
||||||
void focus() override { dummy.focus(); }
|
|
||||||
void paint(Painter& painter) override;
|
|
||||||
void frame_sync();
|
|
||||||
bool on_encoder(const EncoderEvent event) override;
|
|
||||||
bool on_key(KeyEvent key) override;
|
|
||||||
|
|
||||||
private:
|
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_;
|
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<std::vector<bool>> bricks;
|
||||||
|
int brick_colors[BRICK_ROWS];
|
||||||
|
|
||||||
|
bool game_update_callback = false;
|
||||||
|
double game_update_timeout = 0;
|
||||||
|
uint32_t game_update_counter = 0;
|
||||||
|
|
||||||
Button dummy{
|
Button dummy{
|
||||||
{screen_width, 0, 0, 0},
|
{screen_width, 0, 0, 0},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue