mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-08-12 11:07:37 +00:00
Battleship (#2720)
* Made the Battleship 2P 2PP game - FSK is wip * Using POCSAG
This commit is contained in:
71
firmware/application/external/battleship/main.cpp
vendored
Normal file
71
firmware/application/external/battleship/main.cpp
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_battleship.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BattleshipView>();
|
||||
}
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Battleship",
|
||||
/*.bitmap_data = */ {
|
||||
// Ship icon
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1C,
|
||||
0x38,
|
||||
0x3E,
|
||||
0x7C,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x7F,
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFC,
|
||||
0x1F,
|
||||
0xF8,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x07,
|
||||
0xE0,
|
||||
0x03,
|
||||
0xC0,
|
||||
0x01,
|
||||
0x80,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::blue().v,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
822
firmware/application/external/battleship/ui_battleship.cpp
vendored
Normal file
822
firmware/application/external/battleship/ui_battleship.cpp
vendored
Normal file
@@ -0,0 +1,822 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_battleship.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "modems.hpp"
|
||||
#include "bch_code.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
|
||||
// POCSAG address for battleship game messages
|
||||
constexpr uint32_t BATTLESHIP_BASE_ADDRESS = 1000000;
|
||||
constexpr uint32_t RED_TEAM_ADDRESS = BATTLESHIP_BASE_ADDRESS + 1;
|
||||
constexpr uint32_t BLUE_TEAM_ADDRESS = BATTLESHIP_BASE_ADDRESS + 2;
|
||||
|
||||
BattleshipView::BattleshipView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
|
||||
|
||||
add_children({&rssi,
|
||||
&field_frequency,
|
||||
&text_status,
|
||||
&text_score,
|
||||
&button_red_team,
|
||||
&button_blue_team,
|
||||
&button_rotate,
|
||||
&button_place,
|
||||
&button_fire,
|
||||
&button_menu});
|
||||
|
||||
text_score.hidden(true);
|
||||
button_rotate.hidden(true);
|
||||
button_place.hidden(true);
|
||||
button_fire.hidden(true);
|
||||
button_menu.hidden(true);
|
||||
|
||||
field_frequency.set_value(DEFAULT_FREQUENCY);
|
||||
field_frequency.on_change = [this](rf::Frequency freq) {
|
||||
tx_frequency = freq;
|
||||
rx_frequency = freq;
|
||||
if (!is_transmitting) {
|
||||
receiver_model.set_target_frequency(rx_frequency);
|
||||
}
|
||||
};
|
||||
|
||||
button_red_team.on_select = [this](Button&) {
|
||||
start_team(true);
|
||||
};
|
||||
|
||||
button_blue_team.on_select = [this](Button&) {
|
||||
start_team(false);
|
||||
};
|
||||
|
||||
button_rotate.on_select = [this](Button&) {
|
||||
placing_horizontal = !placing_horizontal;
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
button_place.on_select = [this](Button&) {
|
||||
place_ship();
|
||||
};
|
||||
|
||||
button_fire.on_select = [this](Button&) {
|
||||
fire_at_position();
|
||||
};
|
||||
|
||||
button_menu.on_select = [this](Button&) {
|
||||
reset_game();
|
||||
};
|
||||
|
||||
set_focusable(true);
|
||||
init_game();
|
||||
}
|
||||
|
||||
BattleshipView::~BattleshipView() {
|
||||
transmitter_model.disable();
|
||||
receiver_model.disable();
|
||||
audio::output::stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void BattleshipView::focus() {
|
||||
if (game_state == GameState::MENU) {
|
||||
button_red_team.focus();
|
||||
} else {
|
||||
View::focus();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::init_game() {
|
||||
for (uint8_t y = 0; y < GRID_SIZE; y++) {
|
||||
for (uint8_t x = 0; x < GRID_SIZE; x++) {
|
||||
my_grid[y][x] = CellState::EMPTY;
|
||||
enemy_grid[y][x] = CellState::EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
setup_ships();
|
||||
update_score();
|
||||
}
|
||||
|
||||
void BattleshipView::reset_game() {
|
||||
transmitter_model.disable();
|
||||
receiver_model.disable();
|
||||
audio::output::stop();
|
||||
|
||||
game_state = GameState::MENU;
|
||||
is_red_team = false;
|
||||
opponent_ready = false;
|
||||
current_ship_index = 0;
|
||||
placing_horizontal = true;
|
||||
ships_remaining = 5;
|
||||
enemy_ships_remaining = 5;
|
||||
cursor_x = 0;
|
||||
cursor_y = 0;
|
||||
target_x = 0;
|
||||
target_y = 0;
|
||||
is_transmitting = false;
|
||||
last_address = 0;
|
||||
|
||||
init_game();
|
||||
|
||||
current_status = "Choose your team!";
|
||||
update_score();
|
||||
text_status.hidden(false);
|
||||
text_score.hidden(true);
|
||||
field_frequency.hidden(false);
|
||||
button_red_team.hidden(false);
|
||||
button_blue_team.hidden(false);
|
||||
button_rotate.hidden(true);
|
||||
button_place.hidden(true);
|
||||
button_fire.hidden(true);
|
||||
button_menu.hidden(true);
|
||||
|
||||
button_red_team.set_focusable(true);
|
||||
button_blue_team.set_focusable(true);
|
||||
button_red_team.focus();
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::setup_ships() {
|
||||
static const ShipType types[] = {ShipType::CARRIER, ShipType::BATTLESHIP,
|
||||
ShipType::CRUISER, ShipType::SUBMARINE, ShipType::DESTROYER};
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
my_ships[i] = {types[i], 0, 0, true, 0, false};
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::start_team(bool red) {
|
||||
is_red_team = red;
|
||||
game_state = GameState::PLACING_SHIPS;
|
||||
|
||||
field_frequency.hidden(true);
|
||||
button_red_team.hidden(true);
|
||||
button_blue_team.hidden(true);
|
||||
button_rotate.hidden(false);
|
||||
button_place.hidden(false);
|
||||
button_menu.hidden(false);
|
||||
|
||||
text_status.hidden(true);
|
||||
text_score.hidden(true);
|
||||
|
||||
current_status = "Place carrier (5)";
|
||||
|
||||
button_rotate.set_focusable(false);
|
||||
button_place.set_focusable(false);
|
||||
button_menu.set_focusable(false);
|
||||
|
||||
focus();
|
||||
|
||||
is_transmitting = true;
|
||||
configure_radio_rx();
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::configure_radio_tx() {
|
||||
if (is_transmitting) return;
|
||||
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_fsktx);
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
transmitter_model.set_target_frequency(tx_frequency);
|
||||
transmitter_model.set_sampling_rate(2280000);
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
transmitter_model.set_rf_amp(false);
|
||||
transmitter_model.set_tx_gain(35);
|
||||
|
||||
is_transmitting = true;
|
||||
}
|
||||
|
||||
void BattleshipView::configure_radio_rx() {
|
||||
if (is_transmitting) {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
}
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
|
||||
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
receiver_model.set_target_frequency(rx_frequency);
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
receiver_model.set_baseband_bandwidth(1750000);
|
||||
receiver_model.set_rf_amp(false);
|
||||
receiver_model.set_lna(24);
|
||||
receiver_model.set_vga(24);
|
||||
|
||||
baseband::set_pocsag();
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
is_transmitting = false;
|
||||
|
||||
current_status = "RX Ready";
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
|
||||
if (game_state == GameState::MENU) {
|
||||
auto style_title = *ui::Theme::getInstance()->fg_light;
|
||||
painter.draw_string({60, 20}, style_title, "BATTLESHIP");
|
||||
painter.draw_string({40, 80}, style_title, "Choose your team:");
|
||||
painter.draw_string({10, 180}, *ui::Theme::getInstance()->fg_medium, "Set same freq on both!");
|
||||
return;
|
||||
}
|
||||
|
||||
Color team_color = is_red_team ? Color::red() : Color::blue();
|
||||
painter.fill_rectangle({0, 5, 240, 16}, team_color);
|
||||
auto style_white = Style{
|
||||
.font = ui::font::fixed_8x16,
|
||||
.background = team_color,
|
||||
.foreground = Color::white()};
|
||||
painter.draw_string({85, 5}, style_white, is_red_team ? "RED TEAM" : "BLUE TEAM");
|
||||
|
||||
auto style_status = *ui::Theme::getInstance()->fg_light;
|
||||
painter.fill_rectangle({0, 21, 240, 16}, Color::black());
|
||||
painter.draw_string({10, 21}, style_status, current_status);
|
||||
|
||||
if (game_state != GameState::MENU) {
|
||||
painter.draw_string({170, 21}, style_status, current_score);
|
||||
}
|
||||
|
||||
if (game_state == GameState::PLACING_SHIPS) {
|
||||
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
|
||||
if (current_ship_index < 5) {
|
||||
draw_ship_preview(painter);
|
||||
}
|
||||
} else if (game_state == GameState::MY_TURN) {
|
||||
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, enemy_grid, false, true);
|
||||
painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
|
||||
"Enemy ships: " + to_string_dec_uint(enemy_ships_remaining));
|
||||
} else if (game_state == GameState::OPPONENT_TURN || game_state == GameState::WAITING_FOR_OPPONENT) {
|
||||
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
|
||||
painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
|
||||
"Your ships: " + to_string_dec_uint(ships_remaining));
|
||||
} else if (game_state == GameState::GAME_OVER) {
|
||||
painter.draw_string({50, 150}, style_status, "Game Over!");
|
||||
painter.draw_string({30, 170}, style_status, current_status);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid) {
|
||||
painter.fill_rectangle({grid_x, grid_y, GRID_SIZE * CELL_SIZE, GRID_SIZE * CELL_SIZE},
|
||||
Color::dark_blue());
|
||||
|
||||
for (uint8_t i = 0; i <= GRID_SIZE; i++) {
|
||||
painter.draw_vline({grid_x + i * CELL_SIZE, grid_y},
|
||||
GRID_SIZE * CELL_SIZE, Color::grey());
|
||||
painter.draw_hline({grid_x, grid_y + i * CELL_SIZE},
|
||||
GRID_SIZE * CELL_SIZE, Color::grey());
|
||||
}
|
||||
|
||||
for (uint8_t y = 0; y < GRID_SIZE; y++) {
|
||||
for (uint8_t x = 0; x < GRID_SIZE; x++) {
|
||||
draw_cell(painter, grid_x + x * CELL_SIZE + 1, grid_y + y * CELL_SIZE + 1,
|
||||
grid[y][x], show_ships, is_offense_grid,
|
||||
is_cursor_at(x, y, is_offense_grid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor) {
|
||||
Color cell_color = Color::dark_blue();
|
||||
bool should_fill = false;
|
||||
|
||||
if (game_state == GameState::PLACING_SHIPS && !is_offense_grid && current_ship_index < 5) {
|
||||
uint8_t ship_size = my_ships[current_ship_index].size();
|
||||
for (uint8_t i = 0; i < ship_size; i++) {
|
||||
uint8_t preview_x = placing_horizontal ? cursor_x + i : cursor_x;
|
||||
uint8_t preview_y = placing_horizontal ? cursor_y : cursor_y + i;
|
||||
uint8_t grid_x = (cell_x - 1) / CELL_SIZE;
|
||||
uint8_t grid_y = (cell_y - GRID_OFFSET_Y - 6) / CELL_SIZE;
|
||||
if (grid_x == preview_x && grid_y == preview_y && preview_x < GRID_SIZE && preview_y < GRID_SIZE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case CellState::SHIP:
|
||||
if (show_ships) {
|
||||
cell_color = Color::grey();
|
||||
should_fill = true;
|
||||
}
|
||||
break;
|
||||
case CellState::HIT:
|
||||
cell_color = Color::red();
|
||||
should_fill = true;
|
||||
break;
|
||||
case CellState::MISS:
|
||||
cell_color = Color::light_grey();
|
||||
should_fill = true;
|
||||
break;
|
||||
case CellState::SUNK:
|
||||
cell_color = Color::dark_red();
|
||||
should_fill = true;
|
||||
break;
|
||||
default:
|
||||
if (is_offense_grid && state == CellState::EMPTY) {
|
||||
cell_color = Color::dark_grey();
|
||||
should_fill = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (should_fill) {
|
||||
painter.fill_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, cell_color);
|
||||
}
|
||||
|
||||
if (state == CellState::HIT || state == CellState::SUNK) {
|
||||
painter.draw_hline({cell_x + 4, cell_y + 4}, CELL_SIZE - 10, Color::white());
|
||||
painter.draw_hline({cell_x + 4, cell_y + CELL_SIZE - 6}, CELL_SIZE - 10, Color::white());
|
||||
painter.draw_vline({cell_x + 4, cell_y + 4}, CELL_SIZE - 10, Color::white());
|
||||
painter.draw_vline({cell_x + CELL_SIZE - 6, cell_y + 4}, CELL_SIZE - 10, Color::white());
|
||||
} else if (state == CellState::MISS) {
|
||||
painter.draw_hline({cell_x + 8, cell_y + 4}, 8, Color::white());
|
||||
painter.draw_hline({cell_x + 8, cell_y + CELL_SIZE - 6}, 8, Color::white());
|
||||
painter.draw_vline({cell_x + 4, cell_y + 8}, 8, Color::white());
|
||||
painter.draw_vline({cell_x + CELL_SIZE - 6, cell_y + 8}, 8, Color::white());
|
||||
}
|
||||
|
||||
if (is_cursor) {
|
||||
painter.draw_rectangle({cell_x - 1, cell_y - 1, CELL_SIZE, CELL_SIZE},
|
||||
is_offense_grid && game_state == GameState::MY_TURN ? Color::yellow() : Color::cyan());
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleshipView::is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid) {
|
||||
if (game_state == GameState::PLACING_SHIPS && !is_offense_grid) {
|
||||
return x == cursor_x && y == cursor_y;
|
||||
} else if (is_offense_grid && game_state == GameState::MY_TURN) {
|
||||
return x == target_x && y == target_y;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleshipView::draw_ship_preview(Painter& painter) {
|
||||
if (current_ship_index >= 5) return;
|
||||
|
||||
const Ship& ship = my_ships[current_ship_index];
|
||||
uint8_t size = ship.size();
|
||||
bool can_place = can_place_ship(cursor_x, cursor_y, size, placing_horizontal);
|
||||
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
uint8_t x = placing_horizontal ? cursor_x + i : cursor_x;
|
||||
uint8_t y = placing_horizontal ? cursor_y : cursor_y + i;
|
||||
|
||||
if (x < GRID_SIZE && y < GRID_SIZE) {
|
||||
uint8_t cell_x = GRID_OFFSET_X + x * CELL_SIZE + 1;
|
||||
uint8_t cell_y = GRID_OFFSET_Y + 5 + y * CELL_SIZE + 1;
|
||||
|
||||
Color preview_color = can_place ? Color::green() : Color::red();
|
||||
|
||||
painter.fill_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, preview_color);
|
||||
painter.draw_rectangle({cell_x, cell_y, CELL_SIZE - 2, CELL_SIZE - 2}, Color::white());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleshipView::can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal) {
|
||||
if ((horizontal && x + size > GRID_SIZE) || (!horizontal && y + size > GRID_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
uint8_t check_x = horizontal ? x + i : x;
|
||||
uint8_t check_y = horizontal ? y : y + i;
|
||||
|
||||
if (my_grid[check_y][check_x] != CellState::EMPTY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
int adj_x = check_x + dx;
|
||||
int adj_y = check_y + dy;
|
||||
|
||||
if (adj_x >= 0 && adj_x < GRID_SIZE &&
|
||||
adj_y >= 0 && adj_y < GRID_SIZE) {
|
||||
if (my_grid[adj_y][adj_x] == CellState::SHIP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BattleshipView::place_ship() {
|
||||
if (current_ship_index >= 5) return;
|
||||
|
||||
Ship& ship = my_ships[current_ship_index];
|
||||
uint8_t size = ship.size();
|
||||
|
||||
if (!can_place_ship(cursor_x, cursor_y, size, placing_horizontal)) {
|
||||
current_status = "Invalid placement!";
|
||||
set_dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
ship.x = cursor_x;
|
||||
ship.y = cursor_y;
|
||||
ship.horizontal = placing_horizontal;
|
||||
ship.placed = true;
|
||||
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
uint8_t x = placing_horizontal ? cursor_x + i : cursor_x;
|
||||
uint8_t y = placing_horizontal ? cursor_y : cursor_y + i;
|
||||
my_grid[y][x] = CellState::SHIP;
|
||||
}
|
||||
|
||||
current_ship_index++;
|
||||
|
||||
if (current_ship_index >= 5) {
|
||||
button_rotate.hidden(true);
|
||||
button_place.hidden(true);
|
||||
|
||||
send_message({MessageType::READY, 0, 0});
|
||||
|
||||
if (is_red_team) {
|
||||
game_state = GameState::MY_TURN;
|
||||
current_status = "Your turn! Fire!";
|
||||
button_fire.hidden(false);
|
||||
button_fire.set_focusable(false);
|
||||
touch_enabled = true;
|
||||
} else {
|
||||
game_state = GameState::WAITING_FOR_OPPONENT;
|
||||
current_status = "Waiting for Red...";
|
||||
touch_enabled = false;
|
||||
}
|
||||
|
||||
focus();
|
||||
} else {
|
||||
static const char* ship_names[] = {"carrier (5)", "battleship (4)", "cruiser (3)",
|
||||
"submarine (3)", "destroyer (2)"};
|
||||
current_status = "Place ";
|
||||
current_status += ship_names[current_ship_index];
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::send_message(const GameMessage& msg) {
|
||||
static const char* msg_strings[] = {"READY", "SHOT:", "HIT:", "MISS:", "SUNK:", "WIN"};
|
||||
|
||||
std::string message = msg_strings[static_cast<int>(msg.type)];
|
||||
if (msg.type != MessageType::READY && msg.type != MessageType::WIN) {
|
||||
message += to_string_dec_uint(msg.x) + "," + to_string_dec_uint(msg.y);
|
||||
}
|
||||
|
||||
configure_radio_tx();
|
||||
|
||||
// Use POCSAG encoding
|
||||
uint32_t target_address = is_red_team ? BLUE_TEAM_ADDRESS : RED_TEAM_ADDRESS;
|
||||
|
||||
std::vector<uint32_t> codewords;
|
||||
BCHCode BCH_code{{1, 0, 1, 0, 0, 1}, 5, 31, 21, 2};
|
||||
|
||||
// Use the pocsag namespace to access ALPHANUMERIC
|
||||
pocsag::pocsag_encode(pocsag::MessageType::ALPHANUMERIC, BCH_code, 0, message, target_address, codewords);
|
||||
|
||||
// Copy codewords to shared memory
|
||||
uint8_t* data_ptr = shared_memory.bb_data.data;
|
||||
size_t bi = 0;
|
||||
|
||||
for (size_t i = 0; i < codewords.size(); i++) {
|
||||
uint32_t codeword = codewords[i];
|
||||
data_ptr[bi++] = (codeword >> 24) & 0xFF;
|
||||
data_ptr[bi++] = (codeword >> 16) & 0xFF;
|
||||
data_ptr[bi++] = (codeword >> 8) & 0xFF;
|
||||
data_ptr[bi++] = codeword & 0xFF;
|
||||
}
|
||||
|
||||
// Set baseband FSK data
|
||||
baseband::set_fsk_data(
|
||||
codewords.size() * 32, // Total bits
|
||||
2280000 / 1200, // Bit duration (1200 baud)
|
||||
4500, // Deviation
|
||||
64); // Packet repeat
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
transmitter_model.enable();
|
||||
|
||||
current_status = "TX: " + message;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::on_pocsag_packet(const POCSAGPacketMessage* message) {
|
||||
if (message->packet.flag() != pocsag::NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode POCSAG message
|
||||
pocsag_state.codeword_index = 0;
|
||||
pocsag_state.errors = 0;
|
||||
|
||||
while (pocsag::pocsag_decode_batch(message->packet, pocsag_state)) {
|
||||
if (pocsag_state.out_type == pocsag::MESSAGE) {
|
||||
// Check if message is for our team
|
||||
uint32_t expected_address = is_red_team ? RED_TEAM_ADDRESS : BLUE_TEAM_ADDRESS;
|
||||
if (pocsag_state.address == expected_address) {
|
||||
process_message(pocsag_state.output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
(void)progress;
|
||||
|
||||
if (done) {
|
||||
transmitter_model.disable();
|
||||
chThdSleepMilliseconds(200);
|
||||
configure_radio_rx();
|
||||
|
||||
if (game_state == GameState::MY_TURN) {
|
||||
current_status = "Waiting for response";
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleshipView::parse_coords(const std::string& coords, uint8_t& x, uint8_t& y) {
|
||||
size_t comma_pos = coords.find(',');
|
||||
if (comma_pos == std::string::npos) return false;
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
for (size_t i = 0; i < comma_pos; i++) {
|
||||
char c = coords[i];
|
||||
if (c >= '0' && c <= '9') {
|
||||
x = x * 10 + (c - '0');
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = comma_pos + 1; i < coords.length(); i++) {
|
||||
char c = coords[i];
|
||||
if (c >= '0' && c <= '9') {
|
||||
y = y * 10 + (c - '0');
|
||||
}
|
||||
}
|
||||
|
||||
return x < GRID_SIZE && y < GRID_SIZE;
|
||||
}
|
||||
|
||||
void BattleshipView::mark_ship_sunk(uint8_t x, uint8_t y, std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid) {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
int check_x = x + dx;
|
||||
int check_y = y + dy;
|
||||
if (check_x >= 0 && check_x < GRID_SIZE &&
|
||||
check_y >= 0 && check_y < GRID_SIZE) {
|
||||
if (grid[check_y][check_x] == CellState::HIT) {
|
||||
grid[check_y][check_x] = CellState::SUNK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleshipView::process_message(const std::string& message) {
|
||||
if (message.empty()) return;
|
||||
|
||||
size_t colon_pos = message.find(':');
|
||||
std::string msg_type = (colon_pos != std::string::npos)
|
||||
? message.substr(0, colon_pos)
|
||||
: message;
|
||||
|
||||
if (msg_type == "READY") {
|
||||
opponent_ready = true;
|
||||
if (!is_red_team && game_state == GameState::WAITING_FOR_OPPONENT) {
|
||||
current_status = "Red ready! Waiting...";
|
||||
set_dirty();
|
||||
}
|
||||
} else if (msg_type == "SHOT" && colon_pos != std::string::npos) {
|
||||
if (game_state == GameState::OPPONENT_TURN ||
|
||||
(game_state == GameState::WAITING_FOR_OPPONENT && !is_red_team)) {
|
||||
uint8_t x, y;
|
||||
if (parse_coords(message.substr(colon_pos + 1), x, y)) {
|
||||
process_shot(x, y);
|
||||
}
|
||||
}
|
||||
} else if ((msg_type == "HIT" || msg_type == "MISS" || msg_type == "SUNK") && colon_pos != std::string::npos) {
|
||||
uint8_t x, y;
|
||||
if (parse_coords(message.substr(colon_pos + 1), x, y)) {
|
||||
if (msg_type == "HIT") {
|
||||
enemy_grid[y][x] = CellState::HIT;
|
||||
current_status = "Hit! Fire again!";
|
||||
} else if (msg_type == "MISS") {
|
||||
enemy_grid[y][x] = CellState::MISS;
|
||||
current_status = "Miss! Enemy turn";
|
||||
game_state = GameState::OPPONENT_TURN;
|
||||
button_fire.hidden(true);
|
||||
touch_enabled = false;
|
||||
} else if (msg_type == "SUNK") {
|
||||
enemy_grid[y][x] = CellState::SUNK;
|
||||
enemy_ships_remaining--;
|
||||
current_status = "Ship sunk! Fire!";
|
||||
mark_ship_sunk(x, y, enemy_grid);
|
||||
}
|
||||
|
||||
if (game_state == GameState::MY_TURN) {
|
||||
touch_enabled = true;
|
||||
}
|
||||
}
|
||||
} else if (msg_type == "WIN") {
|
||||
game_state = GameState::GAME_OVER;
|
||||
current_status = (is_red_team ? "BLUE" : "RED") + std::string(" TEAM WINS!");
|
||||
button_fire.hidden(true);
|
||||
touch_enabled = false;
|
||||
losses++;
|
||||
update_score();
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::fire_at_position() {
|
||||
if (game_state != GameState::MY_TURN) return;
|
||||
|
||||
if (enemy_grid[target_y][target_x] != CellState::EMPTY) {
|
||||
current_status = "Already fired!";
|
||||
set_dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
send_message({MessageType::SHOT, target_x, target_y});
|
||||
current_status = "Firing...";
|
||||
touch_enabled = false;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::process_shot(uint8_t x, uint8_t y) {
|
||||
if (my_grid[y][x] == CellState::SHIP) {
|
||||
my_grid[y][x] = CellState::HIT;
|
||||
|
||||
bool sunk = false;
|
||||
|
||||
for (auto& ship : my_ships) {
|
||||
if (!ship.placed) continue;
|
||||
|
||||
bool hit_this_ship = false;
|
||||
for (uint8_t i = 0; i < ship.size(); i++) {
|
||||
uint8_t check_x = ship.horizontal ? ship.x + i : ship.x;
|
||||
uint8_t check_y = ship.horizontal ? ship.y : ship.y + i;
|
||||
|
||||
if (check_x == x && check_y == y) {
|
||||
hit_this_ship = true;
|
||||
ship.hits++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit_this_ship && ship.is_sunk()) {
|
||||
sunk = true;
|
||||
ships_remaining--;
|
||||
|
||||
for (uint8_t i = 0; i < ship.size(); i++) {
|
||||
uint8_t sunk_x = ship.horizontal ? ship.x + i : ship.x;
|
||||
uint8_t sunk_y = ship.horizontal ? ship.y : ship.y + i;
|
||||
my_grid[sunk_y][sunk_x] = CellState::SUNK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sunk) {
|
||||
send_message({MessageType::SUNK, x, y});
|
||||
|
||||
if (ships_remaining == 0) {
|
||||
send_message({MessageType::WIN, 0, 0});
|
||||
game_state = GameState::GAME_OVER;
|
||||
current_status = (is_red_team ? "RED" : "BLUE") + std::string(" TEAM WINS!");
|
||||
wins++;
|
||||
update_score();
|
||||
}
|
||||
} else {
|
||||
send_message({MessageType::HIT, x, y});
|
||||
}
|
||||
} else {
|
||||
my_grid[y][x] = CellState::MISS;
|
||||
send_message({MessageType::MISS, x, y});
|
||||
|
||||
game_state = GameState::MY_TURN;
|
||||
button_fire.hidden(false);
|
||||
touch_enabled = true;
|
||||
current_status = "Your turn! Fire!";
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BattleshipView::update_score() {
|
||||
current_score = "W:" + to_string_dec_uint(wins) + " L:" + to_string_dec_uint(losses);
|
||||
}
|
||||
|
||||
bool BattleshipView::on_touch(const TouchEvent event) {
|
||||
if (event.type != TouchEvent::Type::Start || !touch_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t x = event.point.x();
|
||||
uint16_t y = event.point.y();
|
||||
|
||||
if (x >= GRID_OFFSET_X && x < GRID_OFFSET_X + GRID_SIZE * CELL_SIZE &&
|
||||
y >= GRID_OFFSET_Y + 5 && y < GRID_OFFSET_Y + 5 + GRID_SIZE * CELL_SIZE) {
|
||||
uint8_t grid_x = (x - GRID_OFFSET_X) / CELL_SIZE;
|
||||
uint8_t grid_y = (y - GRID_OFFSET_Y - 5) / CELL_SIZE;
|
||||
|
||||
if (game_state == GameState::PLACING_SHIPS) {
|
||||
cursor_x = grid_x;
|
||||
cursor_y = grid_y;
|
||||
} else if (game_state == GameState::MY_TURN) {
|
||||
target_x = grid_x;
|
||||
target_y = grid_y;
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BattleshipView::on_encoder(const EncoderEvent delta) {
|
||||
if (delta == 0) return false;
|
||||
|
||||
if (game_state == GameState::PLACING_SHIPS) {
|
||||
placing_horizontal = !placing_horizontal;
|
||||
} else if (game_state == GameState::MY_TURN) {
|
||||
target_x = (delta > 0) ? (target_x + 1) % GRID_SIZE : (target_x == 0) ? GRID_SIZE - 1
|
||||
: target_x - 1;
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BattleshipView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == GameState::PLACING_SHIPS) {
|
||||
place_ship();
|
||||
return true;
|
||||
} else if (game_state == GameState::MY_TURN) {
|
||||
fire_at_position();
|
||||
return true;
|
||||
}
|
||||
} else if (key == KeyEvent::Back) {
|
||||
if (game_state != GameState::MENU) {
|
||||
reset_game();
|
||||
return true;
|
||||
}
|
||||
} else if (key == KeyEvent::Up || key == KeyEvent::Down) {
|
||||
uint8_t* coord_y = (game_state == GameState::PLACING_SHIPS) ? &cursor_y : &target_y;
|
||||
if (key == KeyEvent::Up) {
|
||||
*coord_y = (*coord_y == 0) ? GRID_SIZE - 1 : *coord_y - 1;
|
||||
} else {
|
||||
*coord_y = (*coord_y + 1) % GRID_SIZE;
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
} else if (key == KeyEvent::Left || key == KeyEvent::Right) {
|
||||
uint8_t* coord_x = (game_state == GameState::PLACING_SHIPS) ? &cursor_x : &target_x;
|
||||
if (key == KeyEvent::Left) {
|
||||
*coord_x = (*coord_x == 0) ? GRID_SIZE - 1 : *coord_x - 1;
|
||||
} else {
|
||||
*coord_x = (*coord_x + 1) % GRID_SIZE;
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::battleship
|
247
firmware/application/external/battleship/ui_battleship.hpp
vendored
Normal file
247
firmware/application/external/battleship/ui_battleship.hpp
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_BATTLESHIP_H__
|
||||
#define __UI_BATTLESHIP_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
#include "pocsag.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
constexpr uint8_t GRID_SIZE = 10;
|
||||
constexpr uint8_t CELL_SIZE = 24;
|
||||
constexpr uint8_t GRID_OFFSET_X = 0;
|
||||
constexpr uint8_t GRID_OFFSET_Y = 32;
|
||||
constexpr uint16_t BUTTON_Y = 280;
|
||||
constexpr uint32_t DEFAULT_FREQUENCY = 433920000;
|
||||
|
||||
enum class ShipType : uint8_t {
|
||||
CARRIER = 5,
|
||||
BATTLESHIP = 4,
|
||||
CRUISER = 3,
|
||||
SUBMARINE = 3,
|
||||
DESTROYER = 2
|
||||
};
|
||||
|
||||
enum class GameState : uint8_t {
|
||||
MENU,
|
||||
PLACING_SHIPS,
|
||||
WAITING_FOR_OPPONENT,
|
||||
MY_TURN,
|
||||
OPPONENT_TURN,
|
||||
GAME_OVER
|
||||
};
|
||||
|
||||
enum class CellState : uint8_t {
|
||||
EMPTY,
|
||||
SHIP,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK
|
||||
};
|
||||
|
||||
enum class MessageType : uint8_t {
|
||||
READY,
|
||||
SHOT,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK,
|
||||
WIN
|
||||
};
|
||||
|
||||
struct Ship {
|
||||
ShipType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
bool horizontal;
|
||||
uint8_t hits;
|
||||
bool placed;
|
||||
|
||||
uint8_t size() const {
|
||||
return static_cast<uint8_t>(type);
|
||||
}
|
||||
|
||||
bool is_sunk() const {
|
||||
return hits >= size();
|
||||
}
|
||||
};
|
||||
|
||||
struct GameMessage {
|
||||
MessageType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
};
|
||||
|
||||
class BattleshipView : public View {
|
||||
public:
|
||||
BattleshipView(NavigationView& nav);
|
||||
~BattleshipView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
|
||||
std::string title() const override { return "Battleship"; }
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState rx_radio_state_{
|
||||
DEFAULT_FREQUENCY /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
TxRadioState tx_radio_state_{
|
||||
DEFAULT_FREQUENCY /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"battleship",
|
||||
app_settings::Mode::RX_TX,
|
||||
{{"rx_freq"sv, &rx_frequency},
|
||||
{"tx_freq"sv, &tx_frequency},
|
||||
{"wins"sv, &wins},
|
||||
{"losses"sv, &losses}}};
|
||||
|
||||
GameState game_state{GameState::MENU};
|
||||
bool is_red_team{false};
|
||||
bool opponent_ready{false};
|
||||
uint32_t wins{0};
|
||||
uint32_t losses{0};
|
||||
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> my_grid{};
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> enemy_grid{};
|
||||
|
||||
std::string current_status{"Choose your team!"};
|
||||
std::string current_score{"W:0 L:0"};
|
||||
|
||||
std::array<Ship, 5> my_ships{};
|
||||
uint8_t current_ship_index{0};
|
||||
bool placing_horizontal{true};
|
||||
uint8_t ships_remaining{5};
|
||||
uint8_t enemy_ships_remaining{5};
|
||||
|
||||
uint8_t cursor_x{0};
|
||||
uint8_t cursor_y{0};
|
||||
uint8_t target_x{0};
|
||||
uint8_t target_y{0};
|
||||
bool touch_enabled{true};
|
||||
|
||||
uint32_t tx_frequency{DEFAULT_FREQUENCY};
|
||||
uint32_t rx_frequency{DEFAULT_FREQUENCY};
|
||||
bool is_transmitting{false};
|
||||
|
||||
// POCSAG decoding state
|
||||
pocsag::EccContainer ecc{};
|
||||
pocsag::POCSAGState pocsag_state{&ecc};
|
||||
uint32_t last_address{0};
|
||||
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{10, 50}};
|
||||
|
||||
Text text_status{
|
||||
{10, 16, 220, 16},
|
||||
"Choose your team!"};
|
||||
|
||||
Text text_score{
|
||||
{170, 16, 60, 16},
|
||||
"W:0 L:0"};
|
||||
|
||||
Button button_red_team{
|
||||
{20, 100, 90, 40},
|
||||
"RED TEAM"};
|
||||
|
||||
Button button_blue_team{
|
||||
{130, 100, 90, 40},
|
||||
"BLUE TEAM"};
|
||||
|
||||
Button button_rotate{
|
||||
{10, BUTTON_Y, 60, 32},
|
||||
"Rotate"};
|
||||
|
||||
Button button_place{
|
||||
{80, BUTTON_Y, 60, 32},
|
||||
"Place"};
|
||||
|
||||
Button button_fire{
|
||||
{80, BUTTON_Y, 60, 32},
|
||||
"Fire!"};
|
||||
|
||||
Button button_menu{
|
||||
{150, BUTTON_Y, 60, 32},
|
||||
"Menu"};
|
||||
|
||||
void init_game();
|
||||
void reset_game();
|
||||
void start_team(bool red);
|
||||
void setup_ships();
|
||||
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
|
||||
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
|
||||
void draw_ship_preview(Painter& painter);
|
||||
void place_ship();
|
||||
bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
|
||||
void fire_at_position();
|
||||
void process_shot(uint8_t x, uint8_t y);
|
||||
void update_score();
|
||||
bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
|
||||
|
||||
void send_message(const GameMessage& msg);
|
||||
void process_message(const std::string& message);
|
||||
bool parse_coords(const std::string& coords, uint8_t& x, uint8_t& y);
|
||||
void mark_ship_sunk(uint8_t x, uint8_t y, std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid);
|
||||
|
||||
void configure_radio_tx();
|
||||
void configure_radio_rx();
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::POCSAGPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const POCSAGPacketMessage*>(p);
|
||||
on_pocsag_packet(message);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const TXProgressMessage*>(p);
|
||||
on_tx_progress(message->progress, message->done);
|
||||
}};
|
||||
|
||||
void on_pocsag_packet(const POCSAGPacketMessage* message);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
#endif
|
5
firmware/application/external/external.cmake
vendored
5
firmware/application/external/external.cmake
vendored
@@ -228,6 +228,10 @@ set(EXTCPPSRC
|
||||
#blackjack
|
||||
external/blackjack/main.cpp
|
||||
external/blackjack/ui_blackjack.cpp
|
||||
|
||||
#battleship
|
||||
external/battleship/main.cpp
|
||||
external/battleship/ui_battleship.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -286,4 +290,5 @@ set(EXTAPPLIST
|
||||
detector_rx
|
||||
spaceinv
|
||||
blackjack
|
||||
battleship
|
||||
)
|
||||
|
7
firmware/application/external/external.ld
vendored
7
firmware/application/external/external.ld
vendored
@@ -78,6 +78,7 @@ MEMORY
|
||||
ram_external_app_dinogame (rwx) : org = 0xADE50000, len = 32k
|
||||
ram_external_app_spaceinv (rwx) : org = 0xADE60000, len = 32k
|
||||
ram_external_app_blackjack (rwx) : org = 0xADE70000, len = 32k
|
||||
ram_external_app_battleship (rwx) : org = 0xADE80000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -412,5 +413,11 @@ SECTIONS
|
||||
*(*ui*external_app*blackjack*);
|
||||
} > ram_external_app_blackjack
|
||||
|
||||
.external_app_battleship : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_battleship.application_information));
|
||||
*(*ui*external_app*battleship*);
|
||||
} > ram_external_app_battleship
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user