mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-16 03:22:59 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23cabb8b8a | |||
| b3edaf3cd0 | |||
| 9afd3a9dca |
@@ -223,9 +223,9 @@ void AISRecentEntry::update(const ais::Packet& packet) {
|
||||
break;
|
||||
|
||||
case 5:
|
||||
call_sign = packet.text(70, 7);
|
||||
name = packet.text(112, 20);
|
||||
destination = packet.text(302, 20);
|
||||
call_sign = trim(packet.text(70, 7));
|
||||
name = trim(packet.text(112, 20));
|
||||
destination = trim(packet.text(302, 20));
|
||||
break;
|
||||
|
||||
case 18:
|
||||
@@ -238,7 +238,7 @@ void AISRecentEntry::update(const ais::Packet& packet) {
|
||||
break;
|
||||
|
||||
case 21:
|
||||
name = packet.text(43, 20);
|
||||
name = trim(packet.text(43, 20));
|
||||
last_position.timestamp = packet.received_at();
|
||||
last_position.latitude = packet.latitude(192);
|
||||
last_position.longitude = packet.longitude(164);
|
||||
@@ -247,11 +247,11 @@ void AISRecentEntry::update(const ais::Packet& packet) {
|
||||
case 24:
|
||||
switch (packet.read(38, 2)) {
|
||||
case 0:
|
||||
name = packet.text(40, 20);
|
||||
name = trim(packet.text(40, 20));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
call_sign = packet.text(90, 7);
|
||||
call_sign = trim(packet.text(90, 7));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -233,6 +233,9 @@ standalone_application_api_t api = {
|
||||
.draw_pixels = &ext_draw_pixels,
|
||||
.draw_pixel = &ext_draw_pixel,
|
||||
.exit_app = &exit_app,
|
||||
// version 4
|
||||
.screen_height = &screen_height,
|
||||
.screen_width = &screen_width,
|
||||
};
|
||||
|
||||
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
|
||||
|
||||
+141
-245
@@ -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();
|
||||
|
||||
+79
-70
@@ -17,6 +17,7 @@
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include <vector>
|
||||
|
||||
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<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{
|
||||
{screen_width, 0, 0, 0},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "ui.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
#define CURRENT_STANDALONE_APPLICATION_API_VERSION 3
|
||||
#define CURRENT_STANDALONE_APPLICATION_API_VERSION 4
|
||||
|
||||
struct standalone_application_api_t {
|
||||
// Version 1
|
||||
@@ -105,6 +105,11 @@ struct standalone_application_api_t {
|
||||
void (*draw_pixels)(const ui::Rect r, const ui::Color* const colors, const size_t count);
|
||||
void (*draw_pixel)(const ui::Point p, const ui::Color color);
|
||||
void (*exit_app)();
|
||||
|
||||
// Version 4
|
||||
uint16_t* screen_height;
|
||||
uint16_t* screen_width;
|
||||
|
||||
// TODO: add baseband access functions
|
||||
|
||||
// HOW TO extend this interface:
|
||||
|
||||
@@ -86,15 +86,17 @@ set(LDSCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/external.ld)
|
||||
# C sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
FILE(GLOB_RECURSE Sources_C ${CMAKE_CURRENT_LIST_DIR}/*.c)
|
||||
FILE(GLOB_RECURSE Sources_C_COMMON ${CMAKE_CURRENT_LIST_DIR}/../common/*.c)
|
||||
set(CSRC
|
||||
${Sources_C}
|
||||
${Sources_C} ${Sources_C_COMMON}
|
||||
)
|
||||
|
||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
FILE(GLOB_RECURSE Sources_CPP ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
||||
FILE(GLOB_RECURSE Sources_CPP_COMMON ${CMAKE_CURRENT_LIST_DIR}/../common/*.cpp)
|
||||
set(CPPSRC
|
||||
${Sources_CPP}
|
||||
${Sources_CPP} ${Sources_CPP_COMMON}
|
||||
)
|
||||
|
||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||
@@ -122,6 +124,8 @@ set(ASMSRC)
|
||||
|
||||
set(INCDIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../common
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../common/ui
|
||||
${COMMON}
|
||||
${COMMON}/../application
|
||||
${COMMON}/../application/hw
|
||||
|
||||
@@ -31,7 +31,9 @@ void initialize(const standalone_application_api_t& api) {
|
||||
_api = &api;
|
||||
|
||||
context = new ui::Context();
|
||||
standaloneViewMirror = new StandaloneViewMirror(*context, {0, 16, 240, 304});
|
||||
screen_height = *(_api->screen_height);
|
||||
screen_width = *(_api->screen_width);
|
||||
standaloneViewMirror = new StandaloneViewMirror(*context, {0, 16, screen_width, screen_height - 16});
|
||||
}
|
||||
|
||||
// event 1 == frame sync. called each 1/60th of second, so 6 = 100ms
|
||||
@@ -78,7 +80,7 @@ ui::Widget* captured_widget{nullptr};
|
||||
void OnTouchEvent(int, int, uint32_t) {
|
||||
if (standaloneViewMirror) {
|
||||
_api->exit_app();
|
||||
/*
|
||||
/* //left here for example, but not used in digital rain
|
||||
ui::TouchEvent event{{x, y}, static_cast<ui::TouchEvent::Type>(type)};
|
||||
|
||||
if (event.type == ui::TouchEvent::Type::Start) {
|
||||
@@ -105,7 +107,8 @@ bool OnKeyEvent(uint8_t) {
|
||||
// ui::KeyEvent key = (ui::KeyEvent)key_val;
|
||||
if (context) {
|
||||
_api->exit_app();
|
||||
/* auto focus_widget = context->focus_manager().focus_widget();
|
||||
/* left here for example, but not used in digital rain
|
||||
auto focus_widget = context->focus_manager().focus_widget();
|
||||
|
||||
if (focus_widget) {
|
||||
if (focus_widget->on_key(key))
|
||||
@@ -130,6 +133,7 @@ bool OnEncoder(int32_t) {
|
||||
if (context) {
|
||||
_api->exit_app();
|
||||
/*
|
||||
left here for example, but not used in digital rain
|
||||
auto focus_widget = context->focus_manager().focus_widget();
|
||||
|
||||
if (focus_widget) return focus_widget->on_encoder((ui::EncoderEvent)delta);
|
||||
@@ -143,6 +147,7 @@ bool OnKeyboad(uint8_t) {
|
||||
if (context) {
|
||||
_api->exit_app();
|
||||
/*
|
||||
left here for example, but not used in digital rain
|
||||
auto focus_widget = context->focus_manager().focus_widget();
|
||||
|
||||
if (focus_widget)
|
||||
|
||||
@@ -45,17 +45,19 @@ bool OnKeyboad(uint8_t);
|
||||
void PaintViewMirror();
|
||||
|
||||
extern const standalone_application_api_t* _api;
|
||||
extern uint16_t screen_height;
|
||||
extern uint16_t screen_width;
|
||||
|
||||
class DigitalRain {
|
||||
private:
|
||||
ui::Painter painter{};
|
||||
static const int WIDTH = 240;
|
||||
static const int HEIGHT = 325;
|
||||
static const int MARGIN_TOP = 20;
|
||||
static const int CHAR_WIDTH = 5;
|
||||
static const int CHAR_HEIGHT = 8;
|
||||
static const int COLS = WIDTH / CHAR_WIDTH;
|
||||
static const int ROWS = (HEIGHT - MARGIN_TOP) / CHAR_HEIGHT;
|
||||
int WIDTH = 0; // 240;
|
||||
int HEIGHT = 0; // 325;
|
||||
int MARGIN_TOP = 20;
|
||||
int CHAR_WIDTH = 5;
|
||||
int CHAR_HEIGHT = 8;
|
||||
int COLS = 0; // WIDTH / CHAR_WIDTH;
|
||||
int ROWS = 0; //(HEIGHT - MARGIN_TOP) / CHAR_HEIGHT;
|
||||
static const int MAX_DROPS = 36;
|
||||
|
||||
const ui::Font& font = ui::font::fixed_5x8();
|
||||
@@ -124,7 +126,10 @@ class DigitalRain {
|
||||
public:
|
||||
DigitalRain() {
|
||||
std::srand(0);
|
||||
|
||||
WIDTH = screen_width;
|
||||
HEIGHT = screen_height + 5;
|
||||
COLS = WIDTH / CHAR_WIDTH;
|
||||
ROWS = (HEIGHT - MARGIN_TOP) / CHAR_HEIGHT;
|
||||
for (uint8_t i = 0; i < MAX_DROPS; ++i) {
|
||||
init_drop(i, true);
|
||||
}
|
||||
|
||||
@@ -176,3 +176,6 @@ extern "C" int f_printf(FIL* fp, const TCHAR* str, ...) {
|
||||
extern "C" TCHAR* f_gets(TCHAR* buff, int len, FIL* fp) {
|
||||
return _api->f_gets(buff, len, fp);
|
||||
}
|
||||
|
||||
uint16_t screen_height = 320;
|
||||
uint16_t screen_width = 240;
|
||||
|
||||
Reference in New Issue
Block a user