diff --git a/applications/plugins/game15/README.md b/applications/plugins/game15/README.md new file mode 100644 index 000000000..b1710c919 --- /dev/null +++ b/applications/plugins/game15/README.md @@ -0,0 +1,13 @@ + +# Game "15" for Flipper Zero + +[Original link](https://github.com/x27/flipperzero-game15) + +Logic game [Wikipedia](https://en.wikipedia.org/wiki/15_puzzle) + +![Game screen](images/Game15.png) + +![Restore game](images/Game15Restore.png) + +![Popoup](images/Game15Popup.png) + diff --git a/applications/plugins/game15/application.fam b/applications/plugins/game15/application.fam new file mode 100644 index 000000000..ab00316c1 --- /dev/null +++ b/applications/plugins/game15/application.fam @@ -0,0 +1,12 @@ +App( + appid="game15", + name="Game 15", + apptype=FlipperAppType.EXTERNAL, + entry_point="game15_app", + cdefines=["APP_GAME15"], + requires=["gui"], + stack_size=1 * 1024, + fap_icon="game15_10px.png", + order=30, + fap_category="Games", +) diff --git a/applications/plugins/game15/game15.c b/applications/plugins/game15/game15.c new file mode 100644 index 000000000..aad5f1330 --- /dev/null +++ b/applications/plugins/game15/game15.c @@ -0,0 +1,469 @@ +#include +#include +#include +#include +#include + +#include "sandbox.h" + +#define FPS 20 +#define CELL_WIDTH 10 +#define CELL_HEIGHT 8 +#define MOVE_TICKS 5 +#define KEY_STACK_SIZE 16 +#define SAVING_DIRECTORY "/ext/apps/Games" +#define SAVING_FILENAME SAVING_DIRECTORY "/game15.save" +#define POPUP_MENU_ITEMS 2 + +typedef enum { + DirectionNone, + DirectionUp, + DirectionDown, + DirectionLeft, + DirectionRight +} direction_e; + +typedef enum { ScenePlay, SceneWin, ScenePopup } scene_e; + +typedef struct { + uint8_t cell_index; + uint8_t zero_index; + uint8_t move_direction; + uint8_t move_ticks; +} moving_cell_t; + +typedef struct { + uint16_t top_record; + scene_e scene; + uint16_t move_count; + uint32_t tick_count; + uint8_t board[16]; +} game_state_t; + +static game_state_t game_state; +static NotificationApp* notification; +static moving_cell_t moving_cell; +static uint8_t loaded_saving_ticks; +static uint8_t popup_menu_selected_item; + +static const char* popup_menu_strings[] = {"Continue", "Reset"}; + +static uint8_t keys[KEY_STACK_SIZE]; +static uint8_t key_stack_head = 0; + +static const uint8_t pic_cells[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x30, 0xfc, 0x38, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0x30, 0xfc, 0x18, 0xfc, 0x0c, 0xfc, 0xfc, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x70, 0xfc, 0x78, 0xfc, 0x68, 0xfc, 0x6c, 0xfc, 0x6c, 0xfc, 0xec, 0xfc, 0xfc, 0xfc, 0x60, 0xfc, + 0xfc, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x78, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0xfc, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, + 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xf8, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0x78, 0xfc, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, + 0x8c, 0xfd, 0xce, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0xc6, 0xfc, 0x66, 0xfc, 0x36, 0xfc, 0xf6, 0xff, + 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, + 0xc6, 0xfd, 0xe7, 0xfd, 0xa6, 0xfd, 0xb6, 0xfd, 0xb6, 0xfd, 0xb6, 0xff, 0xf6, 0xff, 0x86, 0xfd, + 0xf6, 0xff, 0x37, 0xfc, 0x36, 0xfc, 0xf6, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, +}; + +static const uint8_t pic_digits[] = { + 0xf0, 0xf2, 0xf2, 0xf2, 0xf2, 0xf0, 0xf9, 0xf8, 0xf9, 0xf9, 0xf9, 0xf0, 0xf0, 0xf2, 0xf3, + 0xf1, 0xfc, 0xf0, 0xf0, 0xf3, 0xf1, 0xf3, 0xf2, 0xf0, 0xf3, 0xf1, 0xf2, 0xf2, 0xf0, 0xf3, + 0xf0, 0xfc, 0xf0, 0xf3, 0xf2, 0xf0, 0x00, 0x0c, 0x00, 0x02, 0x02, 0x00, 0x00, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x03, 0x03, 0x00, +}; + +static const uint8_t pic_top[] = {11, 4, 0x88, 0xf8, 0xad, 0xfa, 0xad, 0xf8, 0x8d, 0xfe}; +static const uint8_t pic_move[] = + {17, 4, 0x2e, 0x2a, 0xfe, 0xa4, 0xaa, 0xff, 0xaa, 0x2a, 0xff, 0x2e, 0x36, 0xfe}; +static const uint8_t pic_time[] = {15, 4, 0xa8, 0x8b, 0x2d, 0xe9, 0xad, 0xca, 0xad, 0x8b}; + +static const uint8_t pic_puzzled[] = { + 0xff, 0xcf, 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0xff, 0xcf, + 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0x03, 0xcc, 0x00, 0x03, + 0x38, 0x00, 0x0e, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0x03, 0xcc, 0x00, 0x03, 0x1c, 0x00, + 0x07, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x0e, 0x80, 0x03, 0x03, + 0xc0, 0xff, 0x33, 0xc0, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x07, 0xc0, 0x01, 0x03, 0xc0, 0xff, + 0x33, 0xc0, 0xdc, 0x03, 0xc0, 0x00, 0x83, 0x03, 0xe0, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, + 0xd0, 0x03, 0xc0, 0x00, 0xc3, 0x01, 0x70, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, 0xd0, 0x03, + 0xc0, 0xff, 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc, 0x03, 0xc0, 0xff, + 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc}; + +static void key_stack_init() { + key_stack_head = 0; +} + +static uint8_t key_stack_pop() { + return keys[--key_stack_head]; +} + +static bool key_stack_is_empty() { + return key_stack_head == 0; +} + +static int key_stack_push(uint8_t value) { + if(key_stack_head != KEY_STACK_SIZE) { + keys[key_stack_head] = value; + key_stack_head++; + return key_stack_head; + } else + return -1; +} + +static bool storage_game_state_load() { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + uint16_t bytes_readed = 0; + if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) + bytes_readed = storage_file_read(file, &game_state, sizeof(game_state_t)); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return bytes_readed == sizeof(game_state_t); +} + +static void storage_game_state_save() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { + if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { + return; + } + } + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, &game_state, sizeof(game_state_t)); + } + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +static void set_moving_cell_by_direction(direction_e direction) { + moving_cell.move_direction = DirectionNone; + moving_cell.zero_index = 0xff; + + for(int i = 0; i < 16; i++) { + if(!game_state.board[i]) { + moving_cell.zero_index = i; + break; + } + } + if(moving_cell.zero_index == 0xff) return; + + uint8_t x = moving_cell.zero_index % 4; + uint8_t y = moving_cell.zero_index / 4; + + moving_cell.cell_index = moving_cell.zero_index; + + if(direction == DirectionUp && y < 3) + moving_cell.cell_index += 4; + else if(direction == DirectionDown && y > 0) + moving_cell.cell_index -= 4; + else if(direction == DirectionLeft && x < 3) + moving_cell.cell_index++; + else if(direction == DirectionRight && x > 0) + moving_cell.cell_index--; + else + return; + + moving_cell.move_ticks = 0; + moving_cell.move_direction = direction; +} + +static bool is_board_has_solution() { + uint8_t i, j, inv = 0; + for(i = 0; i < 16; ++i) + if(game_state.board[i]) + for(j = 0; j < i; ++j) + if(game_state.board[j] > game_state.board[i]) ++inv; + for(i = 0; i < 16; ++i) + if(game_state.board[i] == 0) inv += 1 + i / 4; + + return inv % 2 == 0; +} + +static void board_init() { + for(int i = 0; i < 16; i++) { + game_state.board[i] = (i + 1) % 16; + } + + do { + for(int i = 15; i >= 1; i--) { + int j = rand() % (i + 1); + uint8_t tmp = game_state.board[j]; + game_state.board[j] = game_state.board[i]; + game_state.board[i] = tmp; + } + } while(!is_board_has_solution()); +} + +static void game_init() { + game_state.scene = ScenePlay; + game_state.move_count = 0; + game_state.tick_count = 0; + moving_cell.move_direction = DirectionNone; + board_init(); + key_stack_init(); + popup_menu_selected_item = 0; +} + +static bool is_board_solved() { + for(int i = 0; i < 16; i++) + if(((i + 1) % 16) != game_state.board[i]) return false; + return true; +} + +static void game_tick() { + switch(game_state.scene) { + case ScenePlay: + game_state.tick_count++; + if(loaded_saving_ticks) loaded_saving_ticks--; + if(moving_cell.move_direction == DirectionNone && !key_stack_is_empty()) { + set_moving_cell_by_direction(key_stack_pop()); + if(moving_cell.move_direction == DirectionNone) { + notification_message(notification, &sequence_single_vibro); + key_stack_init(); + } + } + + if(moving_cell.move_direction != DirectionNone) { + moving_cell.move_ticks++; + if(moving_cell.move_ticks == MOVE_TICKS) { + game_state.board[moving_cell.zero_index] = + game_state.board[moving_cell.cell_index]; + game_state.board[moving_cell.cell_index] = 0; + moving_cell.move_direction = DirectionNone; + game_state.move_count++; + } + if(is_board_solved()) { + notification_message(notification, &sequence_double_vibro); + if(game_state.move_count < game_state.top_record || game_state.top_record == 0) { + game_state.top_record = game_state.move_count; + storage_game_state_save(); + } + game_state.scene = SceneWin; + } + } + break; + + case SceneWin: + if(!key_stack_is_empty()) game_init(); + break; + + case ScenePopup: + if(!key_stack_is_empty()) { + switch(key_stack_pop()) { + case DirectionDown: + popup_menu_selected_item++; + popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; + break; + case DirectionUp: + popup_menu_selected_item--; + popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; + break; + case DirectionNone: + if(popup_menu_selected_item == 0) { + game_state.scene = ScenePlay; + notification_message(notification, &sequence_single_vibro); + } else if(popup_menu_selected_item == 1) { + notification_message(notification, &sequence_single_vibro); + game_init(); + } + break; + } + } + break; + } +} + +static void draw_cell(Canvas* canvas, uint8_t x, uint8_t y, uint8_t cell_number) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, x, y, 18, 14, 1); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, x + 4, y + 3, CELL_WIDTH, CELL_HEIGHT, pic_cells + cell_number * 16); +} + +static void board_draw(Canvas* canvas) { + for(int i = 0; i < 16; i++) { + if(game_state.board[i]) { + if(moving_cell.move_direction == DirectionNone || moving_cell.cell_index != i) + draw_cell(canvas, (i % 4) * 20 + 7, (i / 4) * 16 + 1, game_state.board[i]); + if(moving_cell.move_direction != DirectionNone && moving_cell.cell_index == i) { + uint8_t from_x = (moving_cell.cell_index % 4) * 20 + 7; + uint8_t from_y = (moving_cell.cell_index / 4) * 16 + 1; + uint8_t to_x = (moving_cell.zero_index % 4) * 20 + 7; + uint8_t to_y = (moving_cell.zero_index / 4) * 16 + 1; + int now_x = from_x + (to_x - from_x) * moving_cell.move_ticks / MOVE_TICKS; + int now_y = from_y + (to_y - from_y) * moving_cell.move_ticks / MOVE_TICKS; + draw_cell(canvas, now_x, now_y, game_state.board[i]); + } + } + } +} + +static void number_draw(Canvas* canvas, uint8_t y, uint32_t value) { + uint8_t x = 121; + while(true) { + uint8_t digit = value % 10; + canvas_draw_xbm(canvas, x, y, 4, 6, pic_digits + digit * 6); + x -= 5; + value = value / 10; + if(!value) break; + } +} + +static void plate_draw( + Canvas* canvas, + uint8_t y, + const uint8_t* header, + uint32_t value, + bool dont_draw_zero_value) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_rbox(canvas, 92, y, 35, 19, 2); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, 95, y + 3, header[0], header[1], &header[2]); + if((!value && !dont_draw_zero_value) || value) number_draw(canvas, y + 10, value); +} + +static void info_draw(Canvas* canvas) { + plate_draw(canvas, 1, pic_top, game_state.top_record, true); + plate_draw(canvas, 22, pic_move, game_state.move_count, false); + plate_draw(canvas, 43, pic_time, game_state.tick_count / FPS, false); +} + +static void gray_screen(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + for(int x = 0; x < 128; x += 2) { + for(int y = 0; y < 64; y++) { + canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y); + } + } +} + +static void render_callback(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 128, 64); + + if(game_state.scene == ScenePlay || game_state.scene == SceneWin || + game_state.scene == ScenePopup) { + canvas_set_color(canvas, ColorBlack); + board_draw(canvas); + info_draw(canvas); + + if(loaded_saving_ticks && game_state.scene != ScenePopup) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_rbox(canvas, 20, 24, 88, 16, 4); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 20, 24, 88, 16, 4); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "Restore game ..."); + } + } + + if(game_state.scene == SceneWin) { + gray_screen(canvas); + canvas_draw_box(canvas, 7, 20, 114, 24); + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 8, 21, 112, 22); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 10, 23, 108, 18); + canvas_set_color(canvas, ColorBlack); + canvas_draw_xbm(canvas, 14, 27, 100, 10, pic_puzzled); + } else if(game_state.scene == ScenePopup) { + gray_screen(canvas); + canvas_set_color(canvas, ColorWhite); + canvas_draw_rbox(canvas, 28, 16, 72, 32, 4); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 28, 16, 72, 32, 4); + + for(int i = 0; i < POPUP_MENU_ITEMS; i++) { + if(i == popup_menu_selected_item) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 34, 20 + 12 * i, 60, 12); + } + + canvas_set_color(canvas, i == popup_menu_selected_item ? ColorWhite : ColorBlack); + canvas_draw_str_aligned( + canvas, 64, 26 + 12 * i, AlignCenter, AlignCenter, popup_menu_strings[i]); + } + } +} + +static void game_event_handler(GameEvent const event) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + key_stack_push(DirectionUp); + break; + case InputKeyDown: + key_stack_push(DirectionDown); + break; + case InputKeyRight: + key_stack_push(DirectionRight); + break; + case InputKeyLeft: + key_stack_push(DirectionLeft); + break; + case InputKeyOk: + if(game_state.scene == ScenePlay) { + game_state.scene = ScenePopup; + key_stack_init(); + } else + key_stack_push(DirectionNone); + break; + case InputKeyBack: + if(game_state.scene == ScenePopup) { + game_state.scene = ScenePlay; + } else { + storage_game_state_save(); + sandbox_loop_exit(); + } + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + game_tick(); + } +} + +static void game_alloc() { + srand(DWT->CYCCNT); + key_stack_init(); + notification = furi_record_open(RECORD_NOTIFICATION); + notification_message_block(notification, &sequence_display_backlight_enforce_on); +} + +static void game_free() { + notification_message_block(notification, &sequence_display_backlight_enforce_auto); + furi_record_close(RECORD_NOTIFICATION); +} + +int32_t game15_app() { + game_alloc(); + game_init(); + + loaded_saving_ticks = 0; + if(storage_game_state_load()) { + if(game_state.scene != ScenePlay) + game_init(); + else + loaded_saving_ticks = FPS; + } else + game_init(); + + sandbox_init( + FPS, (SandboxRenderCallback)render_callback, (SandboxEventHandler)game_event_handler); + sandbox_loop(); + sandbox_free(); + game_free(); + return 0; +} diff --git a/applications/plugins/game15/game15_10px.png b/applications/plugins/game15/game15_10px.png new file mode 100644 index 000000000..16c4f1038 Binary files /dev/null and b/applications/plugins/game15/game15_10px.png differ diff --git a/applications/plugins/game15/images/Game15.png b/applications/plugins/game15/images/Game15.png new file mode 100644 index 000000000..f13c2907b Binary files /dev/null and b/applications/plugins/game15/images/Game15.png differ diff --git a/applications/plugins/game15/images/Game15Popup.png b/applications/plugins/game15/images/Game15Popup.png new file mode 100644 index 000000000..1df14729f Binary files /dev/null and b/applications/plugins/game15/images/Game15Popup.png differ diff --git a/applications/plugins/game15/images/Game15Restore.png b/applications/plugins/game15/images/Game15Restore.png new file mode 100644 index 000000000..05aac27f6 Binary files /dev/null and b/applications/plugins/game15/images/Game15Restore.png differ diff --git a/applications/plugins/game15/sandbox.c b/applications/plugins/game15/sandbox.c new file mode 100644 index 000000000..e3b759fc8 --- /dev/null +++ b/applications/plugins/game15/sandbox.c @@ -0,0 +1,93 @@ +#include +#include +#include "sandbox.h" + +FuriMessageQueue* sandbox_event_queue; +FuriMutex** sandbox_mutex; +ViewPort* sandbox_view_port; +Gui* sandbox_gui; +FuriTimer* sandbox_timer; +bool sandbox_loop_processing; +SandboxRenderCallback sandbox_user_render_callback; +SandboxEventHandler sandbox_user_event_handler; + +static void sandbox_render_callback(Canvas* const canvas, void* context) { + UNUSED(context); + if(furi_mutex_acquire(sandbox_mutex, 25) != FuriStatusOk) return; + + if(sandbox_user_render_callback) sandbox_user_render_callback(canvas); + + furi_mutex_release(sandbox_mutex); +} + +static void sandbox_input_callback(InputEvent* input_event, void* context) { + UNUSED(context); + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(sandbox_event_queue, &event, FuriWaitForever); +} + +static void sandbox_timer_callback(void* context) { + UNUSED(context); + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(sandbox_event_queue, &event, 0); +} + +void sandbox_loop() { + sandbox_loop_processing = true; + while(sandbox_loop_processing) { + GameEvent event; + FuriStatus event_status = furi_message_queue_get(sandbox_event_queue, &event, 100); + if(event_status != FuriStatusOk) { + // timeout + continue; + } + + furi_mutex_acquire(sandbox_mutex, FuriWaitForever); + + if(sandbox_user_event_handler) sandbox_user_event_handler(event); + + view_port_update(sandbox_view_port); + furi_mutex_release(sandbox_mutex); + } +} + +void sandbox_loop_exit() { + sandbox_loop_processing = false; +} + +void sandbox_init( + uint8_t fps, + SandboxRenderCallback u_render_callback, + SandboxEventHandler u_event_handler) { + sandbox_user_render_callback = u_render_callback; + sandbox_user_event_handler = u_event_handler; + + sandbox_event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + sandbox_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + sandbox_view_port = view_port_alloc(); + view_port_draw_callback_set(sandbox_view_port, sandbox_render_callback, NULL); + view_port_input_callback_set(sandbox_view_port, sandbox_input_callback, NULL); + + sandbox_gui = furi_record_open(RECORD_GUI); + gui_add_view_port(sandbox_gui, sandbox_view_port, GuiLayerFullscreen); + + if(fps > 0) { + sandbox_timer = furi_timer_alloc(sandbox_timer_callback, FuriTimerTypePeriodic, NULL); + furi_timer_start(sandbox_timer, furi_kernel_get_tick_frequency() / fps); + } else + sandbox_timer = NULL; +} + +void sandbox_free() { + if(sandbox_timer) furi_timer_free(sandbox_timer); + + gui_remove_view_port(sandbox_gui, sandbox_view_port); + view_port_enabled_set(sandbox_view_port, false); + view_port_free(sandbox_view_port); + + if(furi_mutex_acquire(sandbox_mutex, FuriWaitForever) == FuriStatusOk) { + furi_mutex_free(sandbox_mutex); + } + furi_message_queue_free(sandbox_event_queue); +} diff --git a/applications/plugins/game15/sandbox.h b/applications/plugins/game15/sandbox.h new file mode 100644 index 000000000..ea7dff37b --- /dev/null +++ b/applications/plugins/game15/sandbox.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +typedef void (*SandboxRenderCallback)(Canvas* canvas); +typedef void (*SandboxEventHandler)(GameEvent event); + +void sandbox_init( + uint8_t fps, + SandboxRenderCallback render_callback, + SandboxEventHandler event_handler); +void sandbox_loop(); +void sandbox_loop_exit(); +void sandbox_free(); diff --git a/applications/plugins/gps_nmea_uart/README.md b/applications/plugins/gps_nmea_uart/README.md index 5e36dabda..7af0fa3b3 100644 --- a/applications/plugins/gps_nmea_uart/README.md +++ b/applications/plugins/gps_nmea_uart/README.md @@ -1,6 +1,7 @@ # GPS for Flipper Zero [Original link](https://github.com/ezod/flipperzero-gps) + [Adafruit Ultimate GPS Breakout]. ![ui](ui.png) diff --git a/applications/plugins/morse_code/morse_code.c b/applications/plugins/morse_code/morse_code.c index 0b4790721..a29454371 100644 --- a/applications/plugins/morse_code/morse_code.c +++ b/applications/plugins/morse_code/morse_code.c @@ -27,7 +27,6 @@ typedef struct { MorseCodeWorker* worker; } MorseCode; - static void render_callback(Canvas* const canvas, void* ctx) { MorseCode* morse_code = ctx; furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); @@ -35,20 +34,26 @@ static void render_callback(Canvas* const canvas, void* ctx) { canvas_set_font(canvas, FontPrimary); //write words - elements_multiline_text_aligned(canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words)); - - // volume view_port + elements_multiline_text_aligned( + canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words)); + + // volume view_port uint8_t vol_bar_x_pos = 124; uint8_t vol_bar_y_pos = 0; - const uint8_t volume_h = - (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume; + const uint8_t volume_h = (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume; canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64); canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h); //dit bpm canvas_draw_str_aligned( - canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta))); - + canvas, + 0, + 10, + AlignLeft, + AlignCenter, + furi_string_get_cstr( + furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta))); + //button info elements_button_center(canvas, "Press/Hold"); furi_mutex_release(morse_code->model_mutex); @@ -59,9 +64,7 @@ static void input_callback(InputEvent* input_event, void* ctx) { furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever); } -static void morse_code_worker_callback( - FuriString* words, - void* context) { +static void morse_code_worker_callback(FuriString* words, void* context) { MorseCode* morse_code = context; furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); morse_code->model->words = words; @@ -115,45 +118,45 @@ int32_t morse_code_app() { InputEvent input; morse_code_worker_start(morse_code->worker); morse_code_worker_set_volume( - morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); - morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); - while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == FuriStatusOk){ + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk); if(input.key == InputKeyBack) { - furi_mutex_release(morse_code->model_mutex); - break; - }else if(input.key == InputKeyOk){ - if(input.type == InputTypePress) - morse_code_worker_play(morse_code->worker, true); - else if(input.type == InputTypeRelease) - morse_code_worker_play(morse_code->worker, false); - }else if(input.key == InputKeyUp && input.type == InputTypePress){ - if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1) - morse_code->model->volume++; - morse_code_worker_set_volume( - morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); - }else if(input.key == InputKeyDown && input.type == InputTypePress){ - if(morse_code->model->volume > 0) - morse_code->model->volume--; - morse_code_worker_set_volume( - morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); - }else if(input.key == InputKeyLeft && input.type == InputTypePress){ - if(morse_code->model->dit_delta > 10) - morse_code->model->dit_delta-=10; - morse_code_worker_set_dit_delta( - morse_code->worker, morse_code->model->dit_delta); - } - else if(input.key == InputKeyRight && input.type == InputTypePress){ - if(morse_code->model->dit_delta >= 10) - morse_code->model->dit_delta+=10; - morse_code_worker_set_dit_delta( - morse_code->worker, morse_code->model->dit_delta); - } - - FURI_LOG_D("Input", "%s %s %ld", input_get_key_name(input.key), input_get_type_name(input.type), input.sequence); - + furi_mutex_release(morse_code->model_mutex); + break; + } else if(input.key == InputKeyOk) { + if(input.type == InputTypePress) + morse_code_worker_play(morse_code->worker, true); + else if(input.type == InputTypeRelease) + morse_code_worker_play(morse_code->worker, false); + } else if(input.key == InputKeyUp && input.type == InputTypePress) { + if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1) + morse_code->model->volume++; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + } else if(input.key == InputKeyDown && input.type == InputTypePress) { + if(morse_code->model->volume > 0) morse_code->model->volume--; + morse_code_worker_set_volume( + morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]); + } else if(input.key == InputKeyLeft && input.type == InputTypePress) { + if(morse_code->model->dit_delta > 10) morse_code->model->dit_delta -= 10; + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + } else if(input.key == InputKeyRight && input.type == InputTypePress) { + if(morse_code->model->dit_delta >= 10) morse_code->model->dit_delta += 10; + morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta); + } + + FURI_LOG_D( + "Input", + "%s %s %ld", + input_get_key_name(input.key), + input_get_type_name(input.type), + input.sequence); + furi_mutex_release(morse_code->model_mutex); - view_port_update(morse_code->view_port); + view_port_update(morse_code->view_port); } morse_code_worker_stop(morse_code->worker); morse_code_free(morse_code); diff --git a/applications/plugins/morse_code/morse_code_worker.c b/applications/plugins/morse_code/morse_code_worker.c index 54ee747c5..b465abc5b 100644 --- a/applications/plugins/morse_code/morse_code_worker.c +++ b/applications/plugins/morse_code/morse_code_worker.c @@ -2,19 +2,20 @@ #include #include - #define TAG "MorseCodeWorker" #define MORSE_CODE_VERSION 0 //A-Z0-1 -const char morse_array[36][6] ={ - ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", - "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", ".----", "..---", "...--", "....-", ".....", - "-....", "--...", "---..", "----.", "-----" - }; -const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; +const char morse_array[36][6] = {".-", "-...", "-.-.", "-..", ".", "..-.", + "--.", "....", "..", ".---", "-.-", ".-..", + "--", "-.", "---", ".--.", "--.-", ".-.", + "...", "-", "..-", "...-", ".--", "-..-", + "-.--", "--..", ".----", "..---", "...--", "....-", + ".....", "-....", "--...", "---..", "----.", "-----"}; +const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; struct MorseCodeWorker { FuriThread* thread; @@ -28,31 +29,28 @@ struct MorseCodeWorker { FuriString* words; }; -void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration){ +void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration) { FURI_LOG_D("MorseCode: Duration", "%ld", duration); - if( duration <= instance->dit_delta) + if(duration <= instance->dit_delta) furi_string_push_back(instance->buffer, *DOT); else if(duration <= (instance->dit_delta * 3)) furi_string_push_back(instance->buffer, *LINE); - if(furi_string_size(instance->buffer) > 5) - furi_string_reset(instance->buffer); + if(furi_string_size(instance->buffer) > 5) furi_string_reset(instance->buffer); FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer)); } -void morse_code_worker_fill_letter(MorseCodeWorker* instance){ - if(furi_string_size(instance->words) > 63) - furi_string_reset(instance->words); - for (size_t i = 0; i < sizeof(morse_array); i++){ - if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0){ - furi_string_push_back(instance->words, symbol_array[i]); - furi_string_reset(instance->buffer); - break; - } +void morse_code_worker_fill_letter(MorseCodeWorker* instance) { + if(furi_string_size(instance->words) > 63) furi_string_reset(instance->words); + for(size_t i = 0; i < sizeof(morse_array); i++) { + if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0) { + furi_string_push_back(instance->words, symbol_array[i]); + furi_string_reset(instance->buffer); + break; } + } FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words)); } - static int32_t morse_code_worker_thread_callback(void* context) { furi_assert(context); MorseCodeWorker* instance = context; @@ -61,16 +59,16 @@ static int32_t morse_code_worker_thread_callback(void* context) { uint32_t end_tick = 0; bool pushed = true; bool spaced = true; - while(instance->is_running){ + while(instance->is_running) { furi_delay_ms(SLEEP); - if(instance->play){ - if(!was_playing){ + if(instance->play) { + if(!was_playing) { start_tick = furi_get_tick(); furi_hal_speaker_start(FREQUENCY, instance->volume); was_playing = true; } - }else{ - if(was_playing){ + } else { + if(was_playing) { pushed = false; spaced = false; furi_hal_speaker_stop(); @@ -80,21 +78,21 @@ static int32_t morse_code_worker_thread_callback(void* context) { start_tick = 0; } } - if(!pushed){ - if(end_tick + (instance->dit_delta * 3) < furi_get_tick()){ + if(!pushed) { + if(end_tick + (instance->dit_delta * 3) < furi_get_tick()) { //NEW LETTER morse_code_worker_fill_letter(instance); if(instance->callback) - instance->callback(instance->words, instance->callback_context); + instance->callback(instance->words, instance->callback_context); pushed = true; } } - if(!spaced){ - if(end_tick + (instance->dit_delta * 7) < furi_get_tick()){ + if(!spaced) { + if(end_tick + (instance->dit_delta * 7) < furi_get_tick()) { //NEW WORD furi_string_push_back(instance->words, *SPACE); if(instance->callback) - instance->callback(instance->words, instance->callback_context); + instance->callback(instance->words, instance->callback_context); spaced = true; } } @@ -132,17 +130,17 @@ void morse_code_worker_set_callback( instance->callback_context = context; } -void morse_code_worker_play(MorseCodeWorker* instance, bool play){ +void morse_code_worker_play(MorseCodeWorker* instance, bool play) { furi_assert(instance); instance->play = play; } -void morse_code_worker_set_volume(MorseCodeWorker* instance, float level){ +void morse_code_worker_set_volume(MorseCodeWorker* instance, float level) { furi_assert(instance); instance->volume = level; } -void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta){ +void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta) { furi_assert(instance); instance->dit_delta = delta; } diff --git a/applications/plugins/morse_code/morse_code_worker.h b/applications/plugins/morse_code/morse_code_worker.h index cc84a2674..524876476 100644 --- a/applications/plugins/morse_code/morse_code_worker.h +++ b/applications/plugins/morse_code/morse_code_worker.h @@ -10,9 +10,7 @@ #define LINE "-" #define SPACE " " -typedef void (*MorseCodeWorkerCallback)( - FuriString* buffer, - void* context); +typedef void (*MorseCodeWorkerCallback)(FuriString* buffer, void* context); typedef struct MorseCodeWorker MorseCodeWorker; @@ -34,9 +32,3 @@ void morse_code_worker_play(MorseCodeWorker* instance, bool play); void morse_code_worker_set_volume(MorseCodeWorker* instance, float level); void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta); - - - - - - diff --git a/applications/plugins/totp/totp_app.c b/applications/plugins/totp/totp_app.c index 3bc85e211..f296a734b 100644 --- a/applications/plugins/totp/totp_app.c +++ b/applications/plugins/totp/totp_app.c @@ -24,7 +24,7 @@ static void render_callback(Canvas* const canvas, void* ctx) { PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); - if (plugin_state != NULL && !plugin_state->changing_scene) { + if(plugin_state != NULL && !plugin_state->changing_scene) { totp_scene_director_render(canvas, plugin_state); } @@ -49,29 +49,43 @@ static bool totp_plugin_state_init(PluginState* const plugin_state) { totp_scene_director_init_scenes(plugin_state); - if (plugin_state->crypto_verify_data == NULL) { + if(plugin_state->crypto_verify_data == NULL) { DialogMessage* message = dialog_message_alloc(); dialog_message_set_buttons(message, "No", NULL, "Yes"); - dialog_message_set_text(message, "Would you like to setup PIN?", SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); + dialog_message_set_text( + message, + "Would you like to setup PIN?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); DialogMessageButton dialog_result = dialog_message_show(plugin_state->dialogs, message); dialog_message_free(message); - if (dialog_result == DialogMessageButtonRight) { + if(dialog_result == DialogMessageButtonRight) { totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); } else { totp_crypto_seed_iv(plugin_state, NULL, 0); totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); } - } else if (plugin_state->pin_set) { + } else if(plugin_state->pin_set) { totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); } else { totp_crypto_seed_iv(plugin_state, NULL, 0); - if (totp_crypto_verify_key(plugin_state)) { + if(totp_crypto_verify_key(plugin_state)) { totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); } else { - FURI_LOG_E(LOGGING_TAG, "Digital signature verification failed. Looks like conf file was created on another flipper and can't be used on any other"); + FURI_LOG_E( + LOGGING_TAG, + "Digital signature verification failed. Looks like conf file was created on another flipper and can't be used on any other"); DialogMessage* message = dialog_message_alloc(); dialog_message_set_buttons(message, "Exit", NULL, NULL); - dialog_message_set_text(message, "Digital signature verification failed", SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); + dialog_message_set_text( + message, + "Digital signature verification failed", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); dialog_message_show(plugin_state->dialogs, message); dialog_message_free(message); return false; @@ -94,7 +108,7 @@ static void totp_plugin_state_free(PluginState* plugin_state) { ListNode* node = plugin_state->tokens_list; ListNode* tmp; - while (node != NULL) { + while(node != NULL) { tmp = node->next; TokenInfo* tokenInfo = node->data; token_info_free(tokenInfo); @@ -102,7 +116,7 @@ static void totp_plugin_state_free(PluginState* plugin_state) { node = tmp; } - if (plugin_state->crypto_verify_data != NULL) { + if(plugin_state->crypto_verify_data != NULL) { free(plugin_state->crypto_verify_data); } free(plugin_state); @@ -112,7 +126,7 @@ int32_t totp_app() { FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); PluginState* plugin_state = malloc(sizeof(PluginState)); - if (!totp_plugin_state_init(plugin_state)) { + if(!totp_plugin_state_init(plugin_state)) { FURI_LOG_E(LOGGING_TAG, "App state initialization failed\r\n"); totp_plugin_state_free(plugin_state); return 254; @@ -137,18 +151,20 @@ int32_t totp_app() { bool processing = true; uint32_t last_user_interaction_time = furi_get_tick(); while(processing) { - if (plugin_state->changing_scene) continue; + if(plugin_state->changing_scene) continue; FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); PluginState* plugin_state_m = acquire_mutex_block(&state_mutex); if(event_status == FuriStatusOk) { - if (event.type == EventTypeKey) { + if(event.type == EventTypeKey) { last_user_interaction_time = furi_get_tick(); } processing = totp_scene_director_handle_event(&event, plugin_state_m); - } else if (plugin_state_m->pin_set && plugin_state_m->current_scene != TotpSceneAuthentication && furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + } else if( + plugin_state_m->pin_set && plugin_state_m->current_scene != TotpSceneAuthentication && + furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { totp_scene_director_activate_scene(plugin_state_m, TotpSceneAuthentication, NULL); }