Mystify Demo, TwoEleven updates & Snake (#21)

* **New Features**
  * Added Mystify screensaver demo with animated polygons and trails
  * Added Snake game with multiple difficulties, high-score persistence, and multi-input (touch/keyboard) support
  * Added CLI tool for building, packaging, and deploying apps (end-to-end build/install/run workflow)
  * Per-grid-size high-score persistence added to 2048 app; expanded keyboard controls (WASD and device-specific mappings)

* **Documentation**
  * Added Snake README with gameplay, controls, and usage instructions
This commit is contained in:
Shadowtrance
2026-02-07 08:35:13 +10:00
committed by GitHub
parent d31b6b48a4
commit 46cf00d92e
41 changed files with 4025 additions and 131 deletions
+56 -2
View File
@@ -3,9 +3,11 @@
#include "TwoElevenHelpers.h"
#include <stdlib.h>
#include <string.h>
#include <tt_lvgl_keyboard.h>
static void game_play_event(lv_event_t * e);
static void btnm_event_cb(lv_event_t * e);
static void focus_event(lv_event_t* e);
/**
* @brief Free all resources for the 2048 game object
@@ -15,6 +17,13 @@ static void delete_event(lv_event_t * e)
lv_obj_t * obj = lv_event_get_target_obj(e);
twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(obj);
if (game_2048) {
// Reset group editing mode if we enabled it
if (tt_lvgl_hardware_keyboard_is_available()) {
lv_group_t* group = lv_group_get_default();
if (group) {
lv_group_set_editing(group, false);
}
}
for (uint16_t index = 0; index < game_2048->map_count; index++) {
if (game_2048->btnm_map[index]) {
lv_free(game_2048->btnm_map[index]);
@@ -32,6 +41,24 @@ static void delete_event(lv_event_t * e)
}
}
/**
* @brief Handle focus/defocus to manage edit mode for keyboard input
*/
static void focus_event(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
lv_group_t* group = lv_group_get_default();
if (!group) return;
if (code == LV_EVENT_FOCUSED) {
// Enable edit mode so arrow keys control the game
lv_group_set_editing(group, true);
} else if (code == LV_EVENT_DEFOCUSED) {
// Restore normal focus navigation
lv_group_set_editing(group, false);
}
}
/**
* @brief Create a new 2048 game object
*/
@@ -113,10 +140,23 @@ lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size)
lv_btnmatrix_set_map(game_2048->btnm, (const char **)game_2048->btnm_map);
lv_btnmatrix_set_btn_ctrl_all(game_2048->btnm, LV_BTNMATRIX_CTRL_DISABLED);
lv_obj_add_event_cb(game_2048->btnm, game_play_event, LV_EVENT_ALL, obj);
lv_obj_add_event_cb(game_2048->btnm, game_play_event, LV_EVENT_GESTURE, obj);
lv_obj_add_event_cb(game_2048->btnm, game_play_event, LV_EVENT_KEY, obj);
lv_obj_add_event_cb(game_2048->btnm, btnm_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
lv_obj_add_event_cb(obj, delete_event, LV_EVENT_DELETE, NULL);
if (tt_lvgl_hardware_keyboard_is_available()) {
lv_group_t* group = lv_group_get_default();
if (group) {
lv_group_add_obj(group, game_2048->btnm);
// Register focus handlers to manage edit mode lifecycle
lv_obj_add_event_cb(game_2048->btnm, focus_event, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(game_2048->btnm, focus_event, LV_EVENT_DEFOCUSED, NULL);
// Focus the container (will trigger FOCUSED event and enable edit mode)
lv_group_focus_obj(game_2048->btnm);
}
}
return obj;
}
@@ -175,17 +215,31 @@ static void game_play_event(lv_event_t * e)
} else if (code == LV_EVENT_KEY) {
game_2048->game_over = game_over(game_2048->matrix_size, (const uint16_t **)game_2048->matrix);
if (!game_2048->game_over) {
switch (*((const uint8_t *) lv_event_get_param(e))) {
uint32_t key = lv_event_get_key(e);
switch (key) {
// Arrow keys, WASD, and punctuation keys for cardputer
case LV_KEY_UP:
case 'w':
case 'W':
case ';':
success = move_right(&(game_2048->score), game_2048->matrix_size, game_2048->matrix);
break;
case LV_KEY_DOWN:
case 's':
case 'S':
case '.':
success = move_left(&(game_2048->score), game_2048->matrix_size, game_2048->matrix);
break;
case LV_KEY_LEFT:
case 'a':
case 'A':
case ',':
success = move_up(&(game_2048->score), game_2048->matrix_size, game_2048->matrix);
break;
case LV_KEY_RIGHT:
case 'd':
case 'D':
case '/':
success = move_down(&(game_2048->score), game_2048->matrix_size, game_2048->matrix);
break;
default: