mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
Merge pull request #23 from Calvario/joysticktest
Add GPS altitude support, add channel “reply to” targeting, fix snake…
This commit is contained in:
@@ -147,6 +147,9 @@ static bool adv_stop_for_interval_change;
|
||||
* (SerialBLEInterface.cpp:343). */
|
||||
static bool adv_running;
|
||||
|
||||
/* Administrative BLE state */
|
||||
static bool ble_enabled = true;
|
||||
|
||||
|
||||
/* Runtime BLE passkey */
|
||||
static uint32_t ble_passkey = CONFIG_ZEPHCORE_BLE_PASSKEY;
|
||||
@@ -781,6 +784,10 @@ static void adv_slow_work_fn(struct k_work *work)
|
||||
|
||||
static void start_adv(void)
|
||||
{
|
||||
if (!ble_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t interval = fast_adv_active ? BT_ADV_FAST_INTERVAL : BT_ADV_INTERVAL;
|
||||
|
||||
struct bt_le_adv_param adv_param = {
|
||||
@@ -908,6 +915,7 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len)
|
||||
|
||||
void zephcore_ble_set_enabled(bool enable)
|
||||
{
|
||||
ble_enabled = enable;
|
||||
if (!enable) {
|
||||
/* Disconnect current connection if any */
|
||||
if (current_conn) {
|
||||
@@ -917,6 +925,10 @@ void zephcore_ble_set_enabled(bool enable)
|
||||
/* Stop advertising */
|
||||
bt_le_adv_stop();
|
||||
adv_running = false;
|
||||
|
||||
/* No future fast->slow transition needed */
|
||||
k_work_cancel_delayable(&adv_slow_work);
|
||||
|
||||
LOG_INF("BLE disabled");
|
||||
} else {
|
||||
/* Re-enable advertising — start fast window */
|
||||
@@ -926,6 +938,10 @@ void zephcore_ble_set_enabled(bool enable)
|
||||
LOG_INF("BLE enabled");
|
||||
}
|
||||
}
|
||||
bool zephcore_ble_is_enabled(void)
|
||||
{
|
||||
return ble_enabled;
|
||||
}
|
||||
|
||||
bool zephcore_ble_is_active(void)
|
||||
{
|
||||
|
||||
@@ -43,6 +43,9 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len);
|
||||
/** Enable/disable BLE. Disabling disconnects and stops advertising. */
|
||||
void zephcore_ble_set_enabled(bool enable);
|
||||
|
||||
/** True if BLE is enabled */
|
||||
bool zephcore_ble_is_enabled(void);
|
||||
|
||||
/** True if BLE is the active transport and ready to send. */
|
||||
bool zephcore_ble_is_active(void);
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ class GPSSettingsScreen : public UIScreen {
|
||||
float _speed_kmh, _heading_deg;
|
||||
bool _heading_valid;
|
||||
uint32_t _heading_hold_until;
|
||||
bool _show_alt;
|
||||
uint32_t _alt_cycle_ms;
|
||||
struct k_timer _sample_timer;
|
||||
static void sampleTimerCb(struct k_timer *t);
|
||||
void sampleGPS();
|
||||
@@ -235,6 +237,13 @@ class T9InputScreen : public UIScreen {
|
||||
public:
|
||||
T9InputScreen(JoystickUITask *task);
|
||||
void clearInput() { memset(_input, 0, sizeof(_input)); _cursor = 0; }
|
||||
void setInitialInput(const char *text) {
|
||||
if (!text || !text[0]) return;
|
||||
strncpy(_input, text, sizeof(_input) - 1);
|
||||
_input[sizeof(_input) - 1] = '\0';
|
||||
_cursor = (int)strlen(_input);
|
||||
_last_key = -1; _letter_index = 0; _last_press_time = 0;
|
||||
}
|
||||
const char *getInput() const { return _input; }
|
||||
int render(JoystickDisplay &display) override;
|
||||
bool handleInput(char c) override;
|
||||
@@ -313,17 +322,24 @@ public:
|
||||
/* ===== SnakeScreen ===== */
|
||||
class SnakeScreen : public UIScreen {
|
||||
JoystickUITask *_task;
|
||||
static const int GRID_W = 21;
|
||||
static const int GRID_H = 5;
|
||||
int8_t _snake_x[GRID_W * GRID_H];
|
||||
int8_t _snake_y[GRID_W * GRID_H];
|
||||
/* runtime grid */
|
||||
int _grid_w;
|
||||
int _grid_h;
|
||||
int _max_len;
|
||||
/* snake storage (safe upper bound) */
|
||||
static constexpr int MAX_SNAKE_LEN = 1024;
|
||||
int8_t _snake_x[MAX_SNAKE_LEN];
|
||||
int8_t _snake_y[MAX_SNAKE_LEN];
|
||||
int _snake_len;
|
||||
int8_t _food_x, _food_y;
|
||||
uint32_t _next_move;
|
||||
int _score;
|
||||
bool _grid_ready;
|
||||
struct k_timer _tick_timer;
|
||||
volatile bool _tick_due; /* set in timer ISR, consumed in render */
|
||||
static void tickTimerCb(struct k_timer *t);
|
||||
|
||||
void updateGrid(JoystickDisplay &display);
|
||||
void placeFood();
|
||||
void reset();
|
||||
void advanceGame();
|
||||
|
||||
@@ -197,13 +197,6 @@ extern "C" void ui_notify_packet_sent(void)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ui_set_msg_count(uint16_t count)
|
||||
{
|
||||
if (s_task) {
|
||||
s_task->msgRead((int)count);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ui_set_ble_status(bool connected, const char *name)
|
||||
{
|
||||
(void)name;
|
||||
@@ -232,21 +225,6 @@ extern "C" void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, uint
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Pull-model no-ops =====
|
||||
* The joystick UI reads all hardware state on demand at render time, so these
|
||||
* push-model hooks from the old button UI have no work to do. */
|
||||
extern "C" void ui_set_gps_data(bool, uint8_t, int32_t, int32_t, int32_t) {}
|
||||
extern "C" void ui_set_clock(uint32_t) {}
|
||||
extern "C" void ui_add_recent(const char *, int16_t, uint32_t) {}
|
||||
extern "C" void ui_set_node_name(const char *) {}
|
||||
extern "C" void ui_clear_recent(void) {}
|
||||
extern "C" void ui_set_sensor_data(int16_t, uint32_t, uint16_t, uint16_t) {}
|
||||
extern "C" void ui_set_gps_available(bool) {}
|
||||
extern "C" void ui_set_gps_enabled(bool) {}
|
||||
extern "C" void ui_set_gps_state(uint8_t, uint32_t, uint32_t) {}
|
||||
extern "C" void ui_set_buzzer_quiet(bool) {}
|
||||
extern "C" void ui_set_offgrid_mode(bool) {}
|
||||
|
||||
extern "C" void ui_set_battery(uint16_t mv, uint8_t /*pct*/)
|
||||
{
|
||||
if (s_task) {
|
||||
@@ -267,3 +245,20 @@ extern "C" void ui_refresh_display(void)
|
||||
s_task->forceRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===== Pull-model no-ops =====
|
||||
* The joystick UI reads all hardware state on demand at render time, so these
|
||||
* push-model hooks from the old button UI have no work to do. */
|
||||
extern "C" void ui_set_gps_data(bool, uint8_t, int32_t, int32_t, int32_t) {}
|
||||
extern "C" void ui_set_clock(uint32_t) {}
|
||||
extern "C" void ui_add_recent(const char *, int16_t, uint32_t) {}
|
||||
extern "C" void ui_set_node_name(const char *) {}
|
||||
extern "C" void ui_clear_recent(void) {}
|
||||
extern "C" void ui_set_sensor_data(int16_t, uint32_t, uint16_t, uint16_t) {}
|
||||
extern "C" void ui_set_gps_available(bool) {}
|
||||
extern "C" void ui_set_gps_enabled(bool) {}
|
||||
extern "C" void ui_set_gps_state(uint8_t, uint32_t, uint32_t) {}
|
||||
extern "C" void ui_set_buzzer_quiet(bool) {}
|
||||
extern "C" void ui_set_offgrid_mode(bool) {}
|
||||
extern "C" void ui_set_msg_count(uint16_t count) {}
|
||||
|
||||
@@ -300,6 +300,16 @@ void JoystickUITask::gotoRadioStatsScreen() { setCurrScreen(_radio_stats); }
|
||||
void JoystickUITask::gotoRepeatersScreen() { setCurrScreen(_repeaters); }
|
||||
void JoystickUITask::gotoChannelsScreen() { setCurrScreen(_channels); }
|
||||
void JoystickUITask::gotoT9InputScreen() { setCurrScreen(_t9_input); }
|
||||
|
||||
void JoystickUITask::gotoT9InputScreenWithPrefix(const char *prefix)
|
||||
{
|
||||
if (!_t9_input) return;
|
||||
auto *t9 = static_cast<T9InputScreen *>(_t9_input);
|
||||
t9->clearInput();
|
||||
t9->setInitialInput(prefix);
|
||||
setCurrScreen(_t9_input);
|
||||
}
|
||||
|
||||
void JoystickUITask::gotoRenameNodeScreen() { setCurrScreen(_rename_node); }
|
||||
void JoystickUITask::gotoBLECodeScreen() { setCurrScreen(_ble_code); }
|
||||
void JoystickUITask::gotoStatsScreen() { setCurrScreen(_stats); }
|
||||
@@ -670,7 +680,7 @@ void JoystickUITask::toggleWakeOnMsg()
|
||||
/* ===== Notifications from mesh ===== */
|
||||
void JoystickUITask::newMsg(uint8_t path_len, const char *from_name, const char *text, int msgcount)
|
||||
{
|
||||
(void)msgcount; /* tracked by CompanionMesh; we mirror via msgRead() side-effect only */
|
||||
(void)msgcount; /* tracked by CompanionMesh */
|
||||
if (_unread) {
|
||||
/* When BLE phone is connected it pulls offline queue and marks read;
|
||||
* keep the message in our local history but don't count as unread. */
|
||||
@@ -723,16 +733,6 @@ void JoystickUITask::newChannelMsg(const char *channel_name, const char *text,
|
||||
#endif
|
||||
}
|
||||
|
||||
void JoystickUITask::msgRead(int msgcount)
|
||||
{
|
||||
/* Called from CompanionMesh when the BLE-offline-queue drains to 0
|
||||
* (phone synced); auto-navigate home if the user is sitting on the
|
||||
* Unread list looking at what just got cleared. */
|
||||
if (msgcount == 0 && _curr == _unread) {
|
||||
gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void JoystickUITask::notify()
|
||||
{
|
||||
_next_refresh = 0;
|
||||
|
||||
@@ -51,7 +51,6 @@ public:
|
||||
/* Called from CompanionMesh callbacks (mesh thread context) */
|
||||
void newMsg(uint8_t path_len, const char *from_name, const char *text, int msgcount);
|
||||
void newChannelMsg(const char *channel_name, const char *text, uint32_t ts, uint8_t path_len);
|
||||
void msgRead(int msgcount);
|
||||
void notify(); /* BLE connect/disconnect, ACK, etc. */
|
||||
|
||||
/* Alert overlay */
|
||||
@@ -70,6 +69,7 @@ public:
|
||||
void gotoRepeatersScreen();
|
||||
void gotoChannelsScreen();
|
||||
void gotoT9InputScreen();
|
||||
void gotoT9InputScreenWithPrefix(const char *prefix);
|
||||
void gotoRenameNodeScreen();
|
||||
void gotoBLECodeScreen();
|
||||
void gotoStatsScreen();
|
||||
|
||||
@@ -307,22 +307,22 @@ bool UnreadScreen::handleInput(char key)
|
||||
return true;
|
||||
}
|
||||
if (key == KEY_ENTER_LONG) {
|
||||
/* Reply to channel message if applicable */
|
||||
if (entry && strchr(entry->msg, '#') != nullptr) {
|
||||
const char *hash_pos = strchr(entry->msg, '#');
|
||||
char channel_name[32] = {};
|
||||
const char *space_after = hash_pos ? strchr(hash_pos, ' ') : nullptr;
|
||||
if (space_after) {
|
||||
size_t len = space_after - hash_pos;
|
||||
if (len < sizeof(channel_name)) {
|
||||
memcpy(channel_name, hash_pos, len);
|
||||
channel_name[len] = '\0';
|
||||
}
|
||||
}
|
||||
if (channel_name[0]) {
|
||||
_task->setComposeChannel(-1, channel_name);
|
||||
if (!entry) return true;
|
||||
/* Determine if this is a channel message by checking origin for '#' */
|
||||
char origin_sender[32] = {};
|
||||
uint8_t path_len_origin = OUT_PATH_UNKNOWN;
|
||||
parseMessageOriginAndPath(entry->origin, origin_sender, sizeof(origin_sender),
|
||||
&path_len_origin);
|
||||
if (origin_sender[0] == '#') {
|
||||
/* Channel message: channel name follows '#' in origin_sender */
|
||||
const char *ch_name = origin_sender + 1;
|
||||
_task->setComposeChannel(-1, ch_name);
|
||||
char at_buf[38];
|
||||
if (buildChannelReplyPrefix(entry->msg, path_len_origin,
|
||||
at_buf, sizeof(at_buf))) {
|
||||
_task->gotoT9InputScreenWithPrefix(at_buf);
|
||||
} else {
|
||||
_task->gotoT9InputScreen();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -479,8 +479,9 @@ bool ChannelsScreen::handleInput(char key)
|
||||
if (_msg_details) {
|
||||
const char *msg = "";
|
||||
uint32_t ts = 0;
|
||||
uint8_t path_len = OUT_PATH_UNKNOWN;
|
||||
int max_scroll = 0;
|
||||
if (_task->getChannelPreviewFor(_msg_channel, _msg_scroll, msg, ts)) {
|
||||
if (_task->getChannelPreviewFor(_msg_channel, _msg_scroll, msg, ts, &path_len)) {
|
||||
max_scroll = getMessageDetailMaxScrollSanitized(
|
||||
_task->getDisplay(), msg, 20, 5, 5);
|
||||
}
|
||||
@@ -492,7 +493,13 @@ bool ChannelsScreen::handleInput(char key)
|
||||
}
|
||||
if (key == KEY_ENTER_LONG) {
|
||||
_task->setComposeChannel(_msg_channel_idx, _msg_channel);
|
||||
_task->gotoT9InputScreen();
|
||||
char at_buf[38];
|
||||
if (buildChannelReplyPrefix(msg, path_len,
|
||||
at_buf, sizeof(at_buf))) {
|
||||
_task->gotoT9InputScreenWithPrefix(at_buf);
|
||||
} else {
|
||||
_task->gotoT9InputScreen();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -531,6 +531,29 @@ static inline void renderT9Keypad(JoystickDisplay &display, const char * const *
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== buildChannelReplyPrefix ===== */
|
||||
/* Builds "@[SenderName] " from a "SenderName: text" channel message.
|
||||
* Returns false for outbound messages (OUT_PATH_SENT) or if no ": " separator
|
||||
* is found. out_buf must be at least (name_len + 5) bytes; 38 is always safe. */
|
||||
static inline bool buildChannelReplyPrefix(
|
||||
const char *msg, uint8_t path_len,
|
||||
char *out_buf, size_t out_size
|
||||
){
|
||||
if (path_len == OUT_PATH_SENT) return false;
|
||||
const char *sep = strstr(msg, ": ");
|
||||
if (!sep || sep == msg) return false;
|
||||
size_t nlen = (size_t)(sep - msg);
|
||||
if (nlen > 32) nlen = 32;
|
||||
if (out_size < nlen + 5) return false; /* '@' '[' name ']' ' ' '\0' */
|
||||
out_buf[0] = '@';
|
||||
out_buf[1] = '[';
|
||||
memcpy(out_buf + 2, msg, nlen);
|
||||
out_buf[2 + nlen] = ']';
|
||||
out_buf[3 + nlen] = ' ';
|
||||
out_buf[4 + nlen] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Helper: leave compose input and return to appropriate screen */
|
||||
static inline void leaveComposeInput(class JoystickUITask *task);
|
||||
|
||||
|
||||
@@ -621,7 +621,8 @@ GPSSettingsScreen::GPSSettingsScreen(JoystickUITask *task, mesh::RTCClock *rtc)
|
||||
: _task(task), _rtc(rtc), _selected(0),
|
||||
_have_prev_fix(false), _prev_lat_e6(0), _prev_lon_e6(0),
|
||||
_last_sample_ms(0), _speed_kmh(0.0f), _heading_deg(0.0f),
|
||||
_heading_valid(false), _heading_hold_until(0)
|
||||
_heading_valid(false), _heading_hold_until(0),
|
||||
_show_alt(false), _alt_cycle_ms(0)
|
||||
{
|
||||
k_timer_init(&_sample_timer, sampleTimerCb, NULL);
|
||||
k_timer_user_data_set(&_sample_timer, this);
|
||||
@@ -660,12 +661,20 @@ void GPSSettingsScreen::onDisplayOn()
|
||||
|
||||
void GPSSettingsScreen::sampleGPS()
|
||||
{
|
||||
/* Cycle between position and altitude view every 4 seconds */
|
||||
uint32_t now = k_uptime_get_32();
|
||||
if (_alt_cycle_ms == 0) {
|
||||
_alt_cycle_ms = now + GPS_HEADING_HOLD_MS;
|
||||
} else if (now >= _alt_cycle_ms) {
|
||||
_show_alt = !_show_alt;
|
||||
_alt_cycle_ms = now + GPS_HEADING_HOLD_MS;
|
||||
}
|
||||
|
||||
if (!_task->isGPSAvailable() || !_task->getGPSState()) return;
|
||||
struct gps_position pos;
|
||||
gps_get_position(&pos);
|
||||
if (!pos.valid) return;
|
||||
|
||||
uint32_t now = k_uptime_get_32();
|
||||
if (now - _last_sample_ms < 1000) return;
|
||||
|
||||
int32_t lat = (int32_t)(pos.latitude_ndeg / 1000LL);
|
||||
@@ -737,11 +746,16 @@ int GPSSettingsScreen::render(JoystickDisplay &display)
|
||||
snprintf(satellites_line, sizeof(satellites_line), "Sat: %d (%s)", sat, fix_str);
|
||||
|
||||
if (has_fix) {
|
||||
double lat = (double)(pos.latitude_ndeg / 1000000LL) / 1000.0;
|
||||
double lon = (double)(pos.longitude_ndeg / 1000000LL) / 1000.0;
|
||||
snprintf(lat_lon_line, sizeof(lat_lon_line), "%.4f%c %.4f%c",
|
||||
fabs(lat), lat >= 0 ? 'N' : 'S',
|
||||
fabs(lon), lon >= 0 ? 'E' : 'W');
|
||||
if (_show_alt) {
|
||||
float alt_m = (float)pos.altitude_mm / 1000.0f;
|
||||
snprintf(lat_lon_line, sizeof(lat_lon_line), "Alt: %.1f m", (double)alt_m);
|
||||
} else {
|
||||
double lat = (double)(pos.latitude_ndeg / 1000000LL) / 1000.0;
|
||||
double lon = (double)(pos.longitude_ndeg / 1000000LL) / 1000.0;
|
||||
snprintf(lat_lon_line, sizeof(lat_lon_line), "%.4f%c %.4f%c",
|
||||
fabs(lat), lat >= 0 ? 'N' : 'S',
|
||||
fabs(lon), lon >= 0 ? 'E' : 'W');
|
||||
}
|
||||
|
||||
if (_heading_valid) {
|
||||
snprintf(speed_line, sizeof(speed_line), "%.1f km/h | %s %.0f",
|
||||
|
||||
@@ -456,9 +456,11 @@ static int8_t s_ndir_y = 0;
|
||||
static int s_state = STATE_READY;
|
||||
|
||||
SnakeScreen::SnakeScreen(JoystickUITask *task)
|
||||
: _task(task), _snake_len(0),
|
||||
_food_x(0), _food_y(0), _score(0),
|
||||
_tick_due(false)
|
||||
: _task(task), _grid_w(0), _grid_h(0),
|
||||
_max_len(0), _snake_len(0),
|
||||
_food_x(0), _food_y(0),
|
||||
_next_move(0), _score(0),
|
||||
_grid_ready(false), _tick_due(false)
|
||||
{
|
||||
memset(_snake_x, 0, sizeof(_snake_x));
|
||||
memset(_snake_y, 0, sizeof(_snake_y));
|
||||
@@ -505,58 +507,93 @@ void SnakeScreen::onDisplayOn()
|
||||
if (s_state == STATE_PLAYING) startTicking();
|
||||
}
|
||||
|
||||
void SnakeScreen::updateGrid(JoystickDisplay &display)
|
||||
{
|
||||
_grid_w = display.width() / SNAKE_CELL;
|
||||
_grid_h = (display.height() - SNAKE_HEADER) / SNAKE_CELL;
|
||||
|
||||
if (_grid_w < 5) _grid_w = 5;
|
||||
if (_grid_h < 3) _grid_h = 3;
|
||||
|
||||
_max_len = _grid_w * _grid_h;
|
||||
|
||||
if (_max_len > MAX_SNAKE_LEN)
|
||||
_max_len = MAX_SNAKE_LEN;
|
||||
|
||||
_grid_ready = true;
|
||||
}
|
||||
|
||||
void SnakeScreen::reset()
|
||||
{
|
||||
int cx = GRID_W / 2, cy = GRID_H / 2;
|
||||
_snake_len = 3;
|
||||
_snake_x[0] = cx; _snake_y[0] = cy;
|
||||
_snake_x[1] = cx - 1; _snake_y[1] = cy;
|
||||
_snake_x[2] = cx - 2; _snake_y[2] = cy;
|
||||
s_dir_x = 1; s_dir_y = 0;
|
||||
s_ndir_x = 1; s_ndir_y = 0;
|
||||
_score = 0;
|
||||
s_state = STATE_READY;
|
||||
_tick_due = false;
|
||||
placeFood();
|
||||
_next_move = 0;
|
||||
if (_grid_ready) {
|
||||
int cx = _grid_w / 2;
|
||||
int cy = _grid_h / 2;
|
||||
_snake_x[0] = cx; _snake_y[0] = cy;
|
||||
_snake_x[1] = cx - 1; _snake_y[1] = cy;
|
||||
_snake_x[2] = cx - 2; _snake_y[2] = cy;
|
||||
placeFood();
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeScreen::placeFood()
|
||||
{
|
||||
for (int tries = 0; tries < 200; tries++) {
|
||||
int8_t x = (int8_t)(sys_rand32_get() % GRID_W);
|
||||
int8_t y = (int8_t)(sys_rand32_get() % GRID_H);
|
||||
int8_t x = sys_rand32_get() % _grid_w;
|
||||
int8_t y = sys_rand32_get() % _grid_h;
|
||||
bool hit = false;
|
||||
for (int i = 0; i < _snake_len; i++) {
|
||||
if (_snake_x[i] == x && _snake_y[i] == y) { hit = true; break; }
|
||||
if (_snake_x[i] == x && _snake_y[i] == y) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit) {
|
||||
_food_x = x; _food_y = y;
|
||||
return;
|
||||
}
|
||||
if (!hit) { _food_x = x; _food_y = y; return; }
|
||||
}
|
||||
_food_x = 0; _food_y = 0;
|
||||
}
|
||||
|
||||
static bool headHitsBody(const int8_t *sx, const int8_t *sy, int len)
|
||||
{
|
||||
for (int i = 1; i < len; i++) {
|
||||
if (sx[0] == sx[i] && sy[0] == sy[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SnakeScreen::advanceGame()
|
||||
{
|
||||
if (s_state != STATE_PLAYING) return;
|
||||
if (!_grid_ready) return;
|
||||
uint32_t now = k_uptime_get_32();
|
||||
if (now < _next_move) return;
|
||||
_next_move = now + SNAKE_TICK_MS;
|
||||
|
||||
s_dir_x = s_ndir_x; s_dir_y = s_ndir_y;
|
||||
|
||||
int8_t nx = _snake_x[0] + s_dir_x;
|
||||
int8_t ny = _snake_y[0] + s_dir_y;
|
||||
int nx = _snake_x[0] + s_dir_x;
|
||||
int ny = _snake_y[0] + s_dir_y;
|
||||
|
||||
if (nx < 0 || ny < 0 || nx >= GRID_W || ny >= GRID_H) {
|
||||
/* WALL CHECK */
|
||||
if (nx < 0 || ny < 0 || nx >= _grid_w || ny >= _grid_h) {
|
||||
s_state = STATE_OVER;
|
||||
k_timer_stop(&_tick_timer);
|
||||
return;
|
||||
}
|
||||
|
||||
bool eat = (nx == _food_x && ny == _food_y);
|
||||
if (eat && _snake_len < SNAKE_MAX_LEN) _snake_len++;
|
||||
|
||||
int body_to_check = eat ? _snake_len : (_snake_len - 1);
|
||||
for (int i = 1; i < body_to_check; i++) {
|
||||
if (_snake_x[i] == nx && _snake_y[i] == ny) {
|
||||
s_state = STATE_OVER;
|
||||
k_timer_stop(&_tick_timer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (eat && _snake_len < _max_len) _snake_len++;
|
||||
|
||||
for (int i = _snake_len - 1; i > 0; i--) {
|
||||
_snake_x[i] = _snake_x[i - 1];
|
||||
@@ -564,16 +601,17 @@ void SnakeScreen::advanceGame()
|
||||
}
|
||||
_snake_x[0] = nx; _snake_y[0] = ny;
|
||||
|
||||
if (headHitsBody(_snake_x, _snake_y, _snake_len)) {
|
||||
s_state = STATE_OVER;
|
||||
k_timer_stop(&_tick_timer);
|
||||
return;
|
||||
if (eat) {
|
||||
_score++;
|
||||
placeFood();
|
||||
}
|
||||
if (eat) { _score++; placeFood(); }
|
||||
}
|
||||
|
||||
int SnakeScreen::render(JoystickDisplay &display)
|
||||
{
|
||||
// Use the correct size based on the display
|
||||
updateGrid(display);
|
||||
|
||||
/* Advance game on tick fire (set by k_timer ISR). */
|
||||
if (_tick_due) {
|
||||
_tick_due = false;
|
||||
@@ -614,10 +652,17 @@ int SnakeScreen::render(JoystickDisplay &display)
|
||||
|
||||
bool SnakeScreen::handleInput(char c)
|
||||
{
|
||||
if (c == KEY_UP && s_dir_y == 0) { s_ndir_x = 0; s_ndir_y = -1; return true; }
|
||||
if (c == KEY_DOWN && s_dir_y == 0) { s_ndir_x = 0; s_ndir_y = 1; return true; }
|
||||
if (c == KEY_LEFT && s_dir_x == 0) { s_ndir_x = -1; s_ndir_y = 0; return true; }
|
||||
if (c == KEY_RIGHT && s_dir_x == 0) { s_ndir_x = 1; s_ndir_y = 0; return true; }
|
||||
/* One turn per tick: if a direction change is already queued for the
|
||||
* upcoming tick, ignore further direction input. Also block the 180°
|
||||
* reverse (would move head straight into body[1]).
|
||||
* Once the tick fires, s_dir == s_ndir again and new input is accepted. */
|
||||
bool turn_queued = (s_ndir_x != s_dir_x || s_ndir_y != s_dir_y);
|
||||
if (!turn_queued) {
|
||||
if (c == KEY_UP && s_dir_y != 1) { s_ndir_x = 0; s_ndir_y = -1; return true; }
|
||||
if (c == KEY_DOWN && s_dir_y != -1) { s_ndir_x = 0; s_ndir_y = 1; return true; }
|
||||
if (c == KEY_LEFT && s_dir_x != 1) { s_ndir_x = -1; s_ndir_y = 0; return true; }
|
||||
if (c == KEY_RIGHT && s_dir_x != -1) { s_ndir_x = 1; s_ndir_y = 0; return true; }
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
if (s_state == STATE_READY || s_state == STATE_OVER) {
|
||||
reset(); s_state = STATE_PLAYING;
|
||||
|
||||
@@ -334,7 +334,7 @@ static void mesh_event_loop(void)
|
||||
* transiently (HCI timeout, controller pacing) the device
|
||||
* would silently stop advertising and be undiscoverable
|
||||
* until next reboot. Cheap to nudge it back here. */
|
||||
if (!zephcore_ble_is_connected() && !zephcore_ble_is_advertising()) {
|
||||
if (zephcore_ble_is_enabled && !zephcore_ble_is_connected() && !zephcore_ble_is_advertising()) {
|
||||
LOG_WRN("BLE adv watchdog: not advertising, re-enabling");
|
||||
zephcore_ble_set_enabled(true);
|
||||
}
|
||||
@@ -675,7 +675,7 @@ int main(void)
|
||||
ui_set_battery(zephyr_board.getBattMilliVolts(), 0);
|
||||
ui_set_gps_available(gps_is_available());
|
||||
ui_set_gps_enabled(companion_mesh.prefs.gps_enabled != 0);
|
||||
ui_set_ble_enabled(true); /* BLE starts advertising at boot */
|
||||
ui_set_ble_enabled(companion_mesh.prefs.ble_disabled != 1); /* BLE starts advertising at boot */
|
||||
|
||||
/* Restore offgrid mode (client repeat) state from persisted prefs */
|
||||
ui_set_offgrid_mode(companion_mesh.prefs.client_repeat != 0);
|
||||
|
||||
Reference in New Issue
Block a user