From b12321caa9bbaf0247435b0d90b9e3f2a47e0d23 Mon Sep 17 00:00:00 2001 From: RogueMaster Date: Sun, 23 Oct 2022 21:51:03 -0400 Subject: [PATCH] 15 --- ReadMe.md | 3 + applications/plugins/game15/README.md | 8 + applications/plugins/game15/application.fam | 12 + applications/plugins/game15/game15.c | 385 ++++++++++++++++++ applications/plugins/game15/game15.fap | Bin 0 -> 9580 bytes applications/plugins/game15/game15_10px.png | Bin 0 -> 152 bytes applications/plugins/game15/images/Game15.png | Bin 0 -> 2078 bytes applications/plugins/game15/sandbox.c | 96 +++++ applications/plugins/game15/sandbox.h | 24 ++ 9 files changed, 528 insertions(+) create mode 100644 applications/plugins/game15/README.md create mode 100644 applications/plugins/game15/application.fam create mode 100644 applications/plugins/game15/game15.c create mode 100644 applications/plugins/game15/game15.fap create mode 100644 applications/plugins/game15/game15_10px.png create mode 100644 applications/plugins/game15/images/Game15.png create mode 100644 applications/plugins/game15/sandbox.c create mode 100644 applications/plugins/game15/sandbox.h diff --git a/ReadMe.md b/ReadMe.md index bb75a9fea..39b534e2e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -17,6 +17,8 @@ - Many apps renamed, delete /ext/apps before install - Added apps for SAM YES and SAM NO - [UniRFRemix - Cleaned up error checking + Bug fix #373 (By Esurge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/373) +- [UniRFRemix into FAP #374 (By Esurge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/374) +- Added: [15 (By x27)](https://github.com/x27/flipperzero-game15)
TO DO / REMOVED
@@ -180,6 +182,7 @@ $ ./fbt plugin_dist
GAMES
+- [15 (By x27)](https://github.com/x27/flipperzero-game15) - [2048 (By OlegSchwann)](https://github.com/OlegSchwann/flipperzero-firmware/tree/hackaton/game_2048/applications/game-2048) [(Score By DevMilanIan)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/186) - [Arkanoid (By gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins) [(Score By DevMilanIan)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/188) - [BlackJack (By teeebor)](https://github.com/teeebor/flipper_games) diff --git a/applications/plugins/game15/README.md b/applications/plugins/game15/README.md new file mode 100644 index 000000000..ec6b08634 --- /dev/null +++ b/applications/plugins/game15/README.md @@ -0,0 +1,8 @@ +#Game "15" for Flipper Zero + +Logic game [Wikipedia](https://en.wikipedia.org/wiki/15_puzzle) + +![Game screen](images/Game15.png) + +FAP file for firmware 0.69.1 + diff --git a/applications/plugins/game15/application.fam b/applications/plugins/game15/application.fam new file mode 100644 index 000000000..dc3a0da0b --- /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..939b49e6e --- /dev/null +++ b/applications/plugins/game15/game15.c @@ -0,0 +1,385 @@ +#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 TOP_RECORD_DIRECTORY "/ext/apps/Games" +#define TOP_RECORD_FILENAME TOP_RECORD_DIRECTORY "/game15.top" + +typedef enum { + DirectionNone, + DirectionUp, + DirectionDown, + DirectionLeft, + DirectionRight +} direction_e; + +typedef enum { ScenePlay, SceneWin } scene_e; + +typedef struct { + uint8_t cell_index; + uint8_t zero_index; + uint8_t move_direction; + uint8_t move_ticks; +} moving_cell_t; + +static scene_e scene; +static uint8_t board[16]; +static uint16_t top_record; +static uint16_t move_count; +static uint32_t tick_count; +static NotificationApp* notification; +static moving_cell_t moving_cell; + +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 void storage_top_record_load() { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + if(storage_file_open(file, TOP_RECORD_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) + storage_file_read(file, &top_record, 2); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +static void storage_top_record_save() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_stat(storage, TOP_RECORD_DIRECTORY, NULL) == FSE_NOT_EXIST) { + if(!storage_simply_mkdir(storage, TOP_RECORD_DIRECTORY)) { + return; + } + } + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, TOP_RECORD_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) + storage_file_write(file, &top_record, 2); + 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(!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(board[i]) + for(j = 0; j < i; ++j) + if(board[j] > board[i]) ++inv; + for(i = 0; i < 16; ++i) + if(board[i] == 0) inv += 1 + i / 4; + + return inv % 2 == 0; +} + +static void board_init() { + for(int i = 0; i < 16; i++) { + board[i] = (i + 1) % 16; + } + + do { + for(int i = 15; i >= 1; i--) { + int j = rand() % (i + 1); + uint8_t tmp = board[j]; + board[j] = board[i]; + board[i] = tmp; + } + } while(!is_board_has_solution()); +} + +static void game_init() { + scene = ScenePlay; + move_count = 0; + tick_count = 0; + storage_top_record_load(); + moving_cell.move_direction = DirectionNone; + board_init(); + key_stack_init(); +} + +static bool is_board_solved() { + for(int i = 0; i < 16; i++) + if(((i + 1) % 16) != board[i]) return false; + return true; +} + +static void game_tick() { + switch(scene) { + case ScenePlay: + tick_count++; + + 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) { + board[moving_cell.zero_index] = board[moving_cell.cell_index]; + board[moving_cell.cell_index] = 0; + moving_cell.move_direction = DirectionNone; + move_count++; + } + if(is_board_solved()) { + notification_message(notification, &sequence_double_vibro); + if(move_count < top_record || top_record == 0) { + top_record = move_count; + storage_top_record_save(); + } + scene = SceneWin; + } + } + break; + + case SceneWin: + if(!key_stack_is_empty()) game_init(); + 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(board[i]) { + if(moving_cell.move_direction == DirectionNone || moving_cell.cell_index != i) + draw_cell(canvas, (i % 4) * 20 + 7, (i / 4) * 16 + 1, 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, 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, top_record, true); + plate_draw(canvas, 22, pic_move, move_count, false); + plate_draw(canvas, 43, pic_time, tick_count / FPS, false); +} + +static void render_callback(Canvas* const canvas) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 128, 64); + + if(scene == ScenePlay || scene == SceneWin) { + canvas_set_color(canvas, ColorBlack); + board_draw(canvas); + info_draw(canvas); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, 128, 64); + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, 10, 10, "0123456789"); + } + if(scene == SceneWin) { + 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); + } + } + 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); + } +} + +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: + key_stack_push(DirectionNone); + break; + case InputKeyBack: + sandbox_loop_exit(); + 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(); + 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.fap b/applications/plugins/game15/game15.fap new file mode 100644 index 0000000000000000000000000000000000000000..2348c1fbb4e6d63c7f385d31cd318a6b22aa567e GIT binary patch literal 9580 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 literal 0 HcmV?d00001 diff --git a/applications/plugins/game15/game15_10px.png b/applications/plugins/game15/game15_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..16c4f103806d90d1b8b2c6467487398774db40a6 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVk{1FcVbv~PUa<$!;&U>c zv7h@-A}f&37T^=&3Z(!4|G)I+{JTIFXMsm#F#`kNVGw3Kp1&dmC@Age;us=vIhmp9 q7h78cTU&vFB)gjfTfl@yMuv<^cBUmM%QAt=7(8A5T-G@yGywn)A1I#y literal 0 HcmV?d00001 diff --git a/applications/plugins/game15/images/Game15.png b/applications/plugins/game15/images/Game15.png new file mode 100644 index 0000000000000000000000000000000000000000..f13c2907b531fa5441541ac53a96347f7684ae72 GIT binary patch literal 2078 zcmaJ?X;4#F7`+dqh_X0ztW`v?j-!=P+GYf?grJ}-Do{lzm_SiW1f-zs2@%F+T!Fd) zk+4{*gs@f#2mu0a86hN!VN;US5hMi2Lm+uVBxz6_E_T`3_;ZqOSZN7<&5%IS4 z*LF(`o=#Zvo=hG;$FLnRx+6=oFtPJ>d3NwEx5oHrO>2AKsr}~4w^BmNG~%2W?45#u zyww0~Hv*|XDdie{u11rf%ulo!zk;@>M+G+Eyr#%W^)&z%vJZbT`a(S< zvG3x&-!$^>b_ZvCV4S*hpmHg2UXOq!o&cOS2jJ#{xyQi9Wlt9sMx=3q3v){;(}}!8 z_!voEtiXSWG(C0LZ!VbCX6!6fICeCuhK4x9LYyv&A~3^l1ytJZ%Npn9W7S0#b)(+t& z4;#$&%m;~=BHq6Wfa-iUP74h(LC1go&kXCS$WmhMDq;hz zI4eC=;PE*{SLG6NeS7e}s9^b8I>b%j2`ljX>$sf?>u&#a&imPy&RKFX-Ld_y>$3I__Z!llU*Q*@2`Iu?(j!6A^!GKIi=>PUr%T3tnP z$bovDsw5aiFT-7#ruDSSno*?b)a(+q1UZAKy}7l1`AI<%a`8ksNdL68D!mO%r-Z5> zl_#?NY%!KP~q$IWQ@km1MSxDkD+0D8`s^Lwj&Ou5g z+Pc=b-mo|^+e|3nJ|K1djS|-o)MJhk6UCECh>rt&&?5Yck^SXx^vH;NAgM&k$>HP~ zwk~H4xG3pJma29GO@FRsU3fglE|V&bOU_*vTl3P94+$sodwu4O9{bRmh`!)7O0O7e zDsGpa#o%%q$-tjDScG=k6XI;GkKgk@Bn^ah8I)j0!<6Md;-RoAw@w*1c#_jWcFU*q!kW6W+?j{p9m@O=YWJLH zMIS^n8V~5f>nt7zfW^X0F7wfj{VH_gp+= z?@vjQkL+p?vNW0wto$A0Dx}rV9Ght;)Mo~jyzHlp{6s25rPB%b_M#UWi1E?kq={c4 z4OizOzUtScUiR51*BzQo)!j0hc34zr&gaWnXj$I6ELFe;+Bbj)?R=4EVyCP`C;69r ze(0lm@*C+js97Cj*|m=|8@=#~qRu!Lof_fSn}v(9Jvl!{cE`|^OTJxEH-3!3-XuBn zNOA;HGOOP>_3kaa;U82|J?{dZ4e_ose&^=v^fqQNVO%0lxCg@+iP!&HdB0ud?X9-- zD#Ju|?Yr@(u$!1EVO-_Cpuk{OR60`{m5Wv#VV5hoE`beeiaK}L!o}N+LAUe*eu!?L zoIaJqH3T`Irtin4Ryh?=gqeP*F}8J{QPO13k;AYSwfc#!rUn_$?r+ik@PWq;FW2(z HznuIBq;E*m literal 0 HcmV?d00001 diff --git a/applications/plugins/game15/sandbox.c b/applications/plugins/game15/sandbox.c new file mode 100644 index 000000000..ac4bffeec --- /dev/null +++ b/applications/plugins/game15/sandbox.c @@ -0,0 +1,96 @@ +#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();