├── game15_10px.png ├── images ├── Game15.png ├── Game15Popup.png └── Game15Restore.png ├── README.md ├── application.fam ├── sandbox.h ├── sandbox.c └── game15.c /game15_10px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x27/flipperzero-game15/HEAD/game15_10px.png -------------------------------------------------------------------------------- /images/Game15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x27/flipperzero-game15/HEAD/images/Game15.png -------------------------------------------------------------------------------- /images/Game15Popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x27/flipperzero-game15/HEAD/images/Game15Popup.png -------------------------------------------------------------------------------- /images/Game15Restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x27/flipperzero-game15/HEAD/images/Game15Restore.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Game "15" for Flipper Zero 3 | 4 | Logic game [Wikipedia](https://en.wikipedia.org/wiki/15_puzzle) 5 | 6 | ![Game screen](images/Game15.png) 7 | 8 | ![Restore game](images/Game15Restore.png) 9 | 10 | ![Popoup](images/Game15Popup.png) 11 | 12 | FAP file for firmware 0.69.1 13 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="game15", 3 | name="Game 15", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="game15_app", 6 | cdefines=["APP_GAME15"], 7 | requires=["gui"], 8 | stack_size=1 * 1024, 9 | fap_icon="game15_10px.png", 10 | order=30, 11 | fap_category="Games", 12 | ) 13 | -------------------------------------------------------------------------------- /sandbox.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef enum { 6 | EventTypeTick, 7 | EventTypeKey, 8 | } EventType; 9 | 10 | typedef struct { 11 | EventType type; 12 | InputEvent input; 13 | } GameEvent; 14 | 15 | typedef void (*SandboxRenderCallback)(Canvas* canvas); 16 | typedef void (*SandboxEventHandler)(GameEvent event); 17 | 18 | void sandbox_init( 19 | uint8_t fps, 20 | SandboxRenderCallback render_callback, 21 | SandboxEventHandler event_handler); 22 | void sandbox_loop(); 23 | void sandbox_loop_exit(); 24 | void sandbox_free(); 25 | -------------------------------------------------------------------------------- /sandbox.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sandbox.h" 4 | 5 | FuriMessageQueue* sandbox_event_queue; 6 | FuriMutex** sandbox_mutex; 7 | ViewPort* sandbox_view_port; 8 | Gui* sandbox_gui; 9 | FuriTimer* sandbox_timer; 10 | bool sandbox_loop_processing; 11 | SandboxRenderCallback sandbox_user_render_callback; 12 | SandboxEventHandler sandbox_user_event_handler; 13 | 14 | static void sandbox_render_callback(Canvas* const canvas, void* context) { 15 | UNUSED(context); 16 | if (furi_mutex_acquire(sandbox_mutex, 25) != FuriStatusOk) 17 | return; 18 | 19 | if (sandbox_user_render_callback) 20 | sandbox_user_render_callback(canvas); 21 | 22 | furi_mutex_release(sandbox_mutex); 23 | } 24 | 25 | static void sandbox_input_callback(InputEvent* input_event, void* context) { 26 | UNUSED(context); 27 | GameEvent event = {.type = EventTypeKey, .input = *input_event}; 28 | furi_message_queue_put(sandbox_event_queue, &event, FuriWaitForever); 29 | } 30 | 31 | static void sandbox_timer_callback(void* context ) { 32 | UNUSED(context); 33 | GameEvent event = {.type = EventTypeTick}; 34 | furi_message_queue_put(sandbox_event_queue, &event, 0); 35 | } 36 | 37 | void sandbox_loop() { 38 | sandbox_loop_processing = true; 39 | while( sandbox_loop_processing ) { 40 | GameEvent event; 41 | FuriStatus event_status = furi_message_queue_get(sandbox_event_queue, &event, 100); 42 | if (event_status != FuriStatusOk) { 43 | // timeout 44 | continue; 45 | } 46 | 47 | furi_mutex_acquire(sandbox_mutex, FuriWaitForever); 48 | 49 | if (sandbox_user_event_handler) 50 | sandbox_user_event_handler(event); 51 | 52 | view_port_update(sandbox_view_port); 53 | furi_mutex_release(sandbox_mutex); 54 | } 55 | } 56 | 57 | void sandbox_loop_exit() { 58 | sandbox_loop_processing = false; 59 | } 60 | 61 | void sandbox_init(uint8_t fps, SandboxRenderCallback u_render_callback, SandboxEventHandler u_event_handler) 62 | { 63 | sandbox_user_render_callback = u_render_callback; 64 | sandbox_user_event_handler = u_event_handler; 65 | 66 | sandbox_event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); 67 | sandbox_mutex = furi_mutex_alloc(FuriMutexTypeNormal); 68 | 69 | sandbox_view_port = view_port_alloc(); 70 | view_port_draw_callback_set(sandbox_view_port, sandbox_render_callback, NULL); 71 | view_port_input_callback_set(sandbox_view_port, sandbox_input_callback, NULL); 72 | 73 | sandbox_gui = furi_record_open(RECORD_GUI); 74 | gui_add_view_port(sandbox_gui, sandbox_view_port, GuiLayerFullscreen); 75 | 76 | if (fps > 0) { 77 | sandbox_timer = furi_timer_alloc(sandbox_timer_callback, FuriTimerTypePeriodic, NULL); 78 | furi_timer_start(sandbox_timer, furi_kernel_get_tick_frequency() / fps); 79 | } else 80 | sandbox_timer = NULL; 81 | } 82 | 83 | void sandbox_free() 84 | { 85 | if (sandbox_timer) 86 | furi_timer_free(sandbox_timer); 87 | 88 | gui_remove_view_port(sandbox_gui, sandbox_view_port); 89 | view_port_enabled_set(sandbox_view_port, false); 90 | view_port_free(sandbox_view_port); 91 | 92 | if(furi_mutex_acquire(sandbox_mutex, FuriWaitForever) == FuriStatusOk) { 93 | furi_mutex_free(sandbox_mutex); 94 | } 95 | furi_message_queue_free(sandbox_event_queue); 96 | } 97 | -------------------------------------------------------------------------------- /game15.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sandbox.h" 8 | 9 | #define FPS 20 10 | #define CELL_WIDTH 10 11 | #define CELL_HEIGHT 8 12 | #define MOVE_TICKS 5 13 | #define KEY_STACK_SIZE 16 14 | #define SAVING_DIRECTORY "/ext/apps/Games" 15 | #define SAVING_FILENAME SAVING_DIRECTORY "/game15.save" 16 | #define POPUP_MENU_ITEMS 2 17 | 18 | typedef enum { 19 | DirectionNone, 20 | DirectionUp, 21 | DirectionDown, 22 | DirectionLeft, 23 | DirectionRight 24 | } direction_e; 25 | 26 | typedef enum { ScenePlay, SceneWin, ScenePopup } scene_e; 27 | 28 | typedef struct { 29 | uint8_t cell_index; 30 | uint8_t zero_index; 31 | uint8_t move_direction; 32 | uint8_t move_ticks; 33 | } moving_cell_t; 34 | 35 | typedef struct { 36 | uint16_t top_record; 37 | scene_e scene; 38 | uint16_t move_count; 39 | uint32_t tick_count; 40 | uint8_t board[16]; 41 | } game_state_t; 42 | 43 | static game_state_t game_state; 44 | static NotificationApp* notification; 45 | static moving_cell_t moving_cell; 46 | static uint8_t loaded_saving_ticks; 47 | static uint8_t popup_menu_selected_item; 48 | 49 | static const char* popup_menu_strings[] = { 50 | "Continue", 51 | "Reset" 52 | }; 53 | 54 | static uint8_t keys[KEY_STACK_SIZE]; 55 | static uint8_t key_stack_head = 0; 56 | 57 | static const uint8_t pic_cells[] = { 58 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 59 | 0x30, 0xfc, 0x38, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 60 | 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0x30, 0xfc, 0x18, 0xfc, 0x0c, 0xfc, 0xfc, 0xfc, 61 | 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 62 | 0x70, 0xfc, 0x78, 0xfc, 0x68, 0xfc, 0x6c, 0xfc, 0x6c, 0xfc, 0xec, 0xfc, 0xfc, 0xfc, 0x60, 0xfc, 63 | 0xfc, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 64 | 0x78, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 65 | 0xfc, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 66 | 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 67 | 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xf8, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0x78, 0xfc, 68 | 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, 69 | 0x8c, 0xfd, 0xce, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 70 | 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0xc6, 0xfc, 0x66, 0xfc, 0x36, 0xfc, 0xf6, 0xff, 71 | 0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, 72 | 0xc6, 0xfd, 0xe7, 0xfd, 0xa6, 0xfd, 0xb6, 0xfd, 0xb6, 0xfd, 0xb6, 0xff, 0xf6, 0xff, 0x86, 0xfd, 73 | 0xf6, 0xff, 0x37, 0xfc, 0x36, 0xfc, 0xf6, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd, 74 | }; 75 | 76 | static const uint8_t pic_digits[] = { 77 | 0xf0, 0xf2, 0xf2, 0xf2, 0xf2, 0xf0, 0xf9, 0xf8, 0xf9, 0xf9, 0xf9, 0xf0, 0xf0, 0xf2, 0xf3, 78 | 0xf1, 0xfc, 0xf0, 0xf0, 0xf3, 0xf1, 0xf3, 0xf2, 0xf0, 0xf3, 0xf1, 0xf2, 0xf2, 0xf0, 0xf3, 79 | 0xf0, 0xfc, 0xf0, 0xf3, 0xf2, 0xf0, 0x00, 0x0c, 0x00, 0x02, 0x02, 0x00, 0x00, 0x03, 0x03, 80 | 0x03, 0x03, 0x03, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x03, 0x03, 0x00, 81 | }; 82 | 83 | static const uint8_t pic_top[] = {11, 4, 0x88, 0xf8, 0xad, 0xfa, 0xad, 0xf8, 0x8d, 0xfe}; 84 | static const uint8_t pic_move[] = 85 | {17, 4, 0x2e, 0x2a, 0xfe, 0xa4, 0xaa, 0xff, 0xaa, 0x2a, 0xff, 0x2e, 0x36, 0xfe}; 86 | static const uint8_t pic_time[] = {15, 4, 0xa8, 0x8b, 0x2d, 0xe9, 0xad, 0xca, 0xad, 0x8b}; 87 | 88 | static const uint8_t pic_puzzled[] = { 89 | 0xff, 0xcf, 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0xff, 0xcf, 90 | 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0x03, 0xcc, 0x00, 0x03, 91 | 0x38, 0x00, 0x0e, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0x03, 0xcc, 0x00, 0x03, 0x1c, 0x00, 92 | 0x07, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x0e, 0x80, 0x03, 0x03, 93 | 0xc0, 0xff, 0x33, 0xc0, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x07, 0xc0, 0x01, 0x03, 0xc0, 0xff, 94 | 0x33, 0xc0, 0xdc, 0x03, 0xc0, 0x00, 0x83, 0x03, 0xe0, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, 95 | 0xd0, 0x03, 0xc0, 0x00, 0xc3, 0x01, 0x70, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, 0xd0, 0x03, 96 | 0xc0, 0xff, 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc, 0x03, 0xc0, 0xff, 97 | 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc}; 98 | 99 | static void key_stack_init() { 100 | key_stack_head = 0; 101 | } 102 | 103 | static uint8_t key_stack_pop() { 104 | return keys[--key_stack_head]; 105 | } 106 | 107 | static bool key_stack_is_empty() { 108 | return key_stack_head == 0; 109 | } 110 | 111 | static int key_stack_push(uint8_t value) { 112 | if(key_stack_head != KEY_STACK_SIZE) { 113 | keys[key_stack_head] = value; 114 | key_stack_head++; 115 | return key_stack_head; 116 | } else 117 | return -1; 118 | } 119 | 120 | static bool storage_game_state_load() { 121 | Storage* storage = furi_record_open(RECORD_STORAGE); 122 | File* file = storage_file_alloc(storage); 123 | 124 | uint16_t bytes_readed = 0; 125 | if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) 126 | bytes_readed = storage_file_read(file, &game_state, sizeof(game_state_t)); 127 | storage_file_close(file); 128 | storage_file_free(file); 129 | furi_record_close(RECORD_STORAGE); 130 | return bytes_readed == sizeof(game_state_t); 131 | } 132 | 133 | static void storage_game_state_save() { 134 | Storage* storage = furi_record_open(RECORD_STORAGE); 135 | 136 | if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { 137 | if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { 138 | return; 139 | } 140 | } 141 | 142 | File* file = storage_file_alloc(storage); 143 | if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { 144 | storage_file_write(file, &game_state, sizeof(game_state_t)); 145 | } 146 | storage_file_close(file); 147 | storage_file_free(file); 148 | furi_record_close(RECORD_STORAGE); 149 | } 150 | 151 | static void set_moving_cell_by_direction(direction_e direction) { 152 | moving_cell.move_direction = DirectionNone; 153 | moving_cell.zero_index = 0xff; 154 | 155 | for(int i = 0; i < 16; i++) { 156 | if(!game_state.board[i]) { 157 | moving_cell.zero_index = i; 158 | break; 159 | } 160 | } 161 | if(moving_cell.zero_index == 0xff) return; 162 | 163 | uint8_t x = moving_cell.zero_index % 4; 164 | uint8_t y = moving_cell.zero_index / 4; 165 | 166 | moving_cell.cell_index = moving_cell.zero_index; 167 | 168 | if(direction == DirectionUp && y < 3) 169 | moving_cell.cell_index += 4; 170 | else if(direction == DirectionDown && y > 0) 171 | moving_cell.cell_index -= 4; 172 | else if(direction == DirectionLeft && x < 3) 173 | moving_cell.cell_index++; 174 | else if(direction == DirectionRight && x > 0) 175 | moving_cell.cell_index--; 176 | else 177 | return; 178 | 179 | moving_cell.move_ticks = 0; 180 | moving_cell.move_direction = direction; 181 | } 182 | 183 | static bool is_board_has_solution() { 184 | uint8_t i, j, inv = 0; 185 | for(i = 0; i < 16; ++i) 186 | if(game_state.board[i]) 187 | for(j = 0; j < i; ++j) 188 | if(game_state.board[j] > game_state.board[i]) ++inv; 189 | for(i = 0; i < 16; ++i) 190 | if(game_state.board[i] == 0) inv += 1 + i / 4; 191 | 192 | return inv % 2 == 0; 193 | } 194 | 195 | static void board_init() { 196 | for(int i = 0; i < 16; i++) { 197 | game_state.board[i] = (i + 1) % 16; 198 | } 199 | 200 | do { 201 | for(int i = 15; i >= 1; i--) { 202 | int j = rand() % (i + 1); 203 | uint8_t tmp = game_state.board[j]; 204 | game_state.board[j] = game_state.board[i]; 205 | game_state.board[i] = tmp; 206 | } 207 | } while(!is_board_has_solution()); 208 | } 209 | 210 | static void game_init() { 211 | game_state.scene = ScenePlay; 212 | game_state.move_count = 0; 213 | game_state.tick_count = 0; 214 | moving_cell.move_direction = DirectionNone; 215 | board_init(); 216 | key_stack_init(); 217 | popup_menu_selected_item = 0; 218 | } 219 | 220 | static bool is_board_solved() { 221 | for(int i = 0; i < 16; i++) 222 | if(((i + 1) % 16) != game_state.board[i]) return false; 223 | return true; 224 | } 225 | 226 | static void game_tick() { 227 | switch(game_state.scene) { 228 | case ScenePlay: 229 | if (game_state.move_count >= 1) 230 | game_state.tick_count++; 231 | if (loaded_saving_ticks) 232 | loaded_saving_ticks--; 233 | if(moving_cell.move_direction == DirectionNone && !key_stack_is_empty()) { 234 | set_moving_cell_by_direction(key_stack_pop()); 235 | if(moving_cell.move_direction == DirectionNone) { 236 | notification_message(notification, &sequence_single_vibro); 237 | key_stack_init(); 238 | } 239 | } 240 | 241 | if(moving_cell.move_direction != DirectionNone) { 242 | moving_cell.move_ticks++; 243 | if(moving_cell.move_ticks == MOVE_TICKS) { 244 | game_state.board[moving_cell.zero_index] = 245 | game_state.board[moving_cell.cell_index]; 246 | game_state.board[moving_cell.cell_index] = 0; 247 | moving_cell.move_direction = DirectionNone; 248 | game_state.move_count++; 249 | } 250 | if(is_board_solved()) { 251 | notification_message(notification, &sequence_double_vibro); 252 | if(game_state.move_count < game_state.top_record || game_state.top_record == 0) { 253 | game_state.top_record = game_state.move_count; 254 | storage_game_state_save(); 255 | } 256 | game_state.scene = SceneWin; 257 | } 258 | } 259 | break; 260 | 261 | case SceneWin: 262 | if(!key_stack_is_empty()) game_init(); 263 | break; 264 | 265 | case ScenePopup: 266 | if (!key_stack_is_empty()) { 267 | switch(key_stack_pop()) 268 | { 269 | case DirectionDown: 270 | popup_menu_selected_item++; 271 | popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; 272 | break; 273 | case DirectionUp: 274 | popup_menu_selected_item--; 275 | popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS; 276 | break; 277 | case DirectionNone: 278 | if (popup_menu_selected_item == 0) { 279 | game_state.scene = ScenePlay; 280 | notification_message(notification, &sequence_single_vibro); 281 | } 282 | else if (popup_menu_selected_item == 1) { 283 | notification_message(notification, &sequence_single_vibro); 284 | game_init(); 285 | } 286 | break; 287 | } 288 | } 289 | break; 290 | } 291 | } 292 | 293 | static void draw_cell(Canvas* canvas, uint8_t x, uint8_t y, uint8_t cell_number) { 294 | canvas_set_color(canvas, ColorBlack); 295 | canvas_draw_rframe(canvas, x, y, 18, 14, 1); 296 | canvas_set_color(canvas, ColorBlack); 297 | canvas_draw_xbm(canvas, x + 4, y + 3, CELL_WIDTH, CELL_HEIGHT, pic_cells + cell_number * 16); 298 | } 299 | 300 | static void board_draw(Canvas* canvas) { 301 | for(int i = 0; i < 16; i++) { 302 | if(game_state.board[i]) { 303 | if(moving_cell.move_direction == DirectionNone || moving_cell.cell_index != i) 304 | draw_cell(canvas, (i % 4) * 20 + 7, (i / 4) * 16 + 1, game_state.board[i]); 305 | if(moving_cell.move_direction != DirectionNone && moving_cell.cell_index == i) { 306 | uint8_t from_x = (moving_cell.cell_index % 4) * 20 + 7; 307 | uint8_t from_y = (moving_cell.cell_index / 4) * 16 + 1; 308 | uint8_t to_x = (moving_cell.zero_index % 4) * 20 + 7; 309 | uint8_t to_y = (moving_cell.zero_index / 4) * 16 + 1; 310 | int now_x = from_x + (to_x - from_x) * moving_cell.move_ticks / MOVE_TICKS; 311 | int now_y = from_y + (to_y - from_y) * moving_cell.move_ticks / MOVE_TICKS; 312 | draw_cell(canvas, now_x, now_y, game_state.board[i]); 313 | } 314 | } 315 | } 316 | } 317 | 318 | static void number_draw(Canvas* canvas, uint8_t y, uint32_t value) { 319 | uint8_t x = 121; 320 | while(true) { 321 | uint8_t digit = value % 10; 322 | canvas_draw_xbm(canvas, x, y, 4, 6, pic_digits + digit * 6); 323 | x -= 5; 324 | value = value / 10; 325 | if(!value) break; 326 | } 327 | } 328 | 329 | static void plate_draw( 330 | Canvas* canvas, 331 | uint8_t y, 332 | const uint8_t* header, 333 | uint32_t value, 334 | bool dont_draw_zero_value) { 335 | canvas_set_color(canvas, ColorBlack); 336 | canvas_draw_rbox(canvas, 92, y, 35, 19, 2); 337 | canvas_set_color(canvas, ColorBlack); 338 | canvas_draw_xbm(canvas, 95, y + 3, header[0], header[1], &header[2]); 339 | if((!value && !dont_draw_zero_value) || value) number_draw(canvas, y + 10, value); 340 | } 341 | 342 | static void info_draw(Canvas* canvas) { 343 | plate_draw(canvas, 1, pic_top, game_state.top_record, true); 344 | plate_draw(canvas, 22, pic_move, game_state.move_count, false); 345 | plate_draw(canvas, 43, pic_time, game_state.tick_count / FPS, false); 346 | } 347 | 348 | static void gray_screen(Canvas* const canvas) { 349 | canvas_set_color(canvas, ColorWhite); 350 | for(int x = 0; x < 128; x += 2) { 351 | for(int y = 0; y < 64; y++) { 352 | canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y); 353 | } 354 | } 355 | } 356 | 357 | static void render_callback(Canvas* const canvas) { 358 | canvas_set_color(canvas, ColorWhite); 359 | canvas_draw_box(canvas, 0, 0, 128, 64); 360 | 361 | if(game_state.scene == ScenePlay || game_state.scene == SceneWin || game_state.scene == ScenePopup) { 362 | canvas_set_color(canvas, ColorBlack); 363 | board_draw(canvas); 364 | info_draw(canvas); 365 | 366 | if (loaded_saving_ticks && game_state.scene != ScenePopup) { 367 | canvas_set_color(canvas, ColorWhite); 368 | canvas_draw_rbox(canvas, 20, 24, 88, 16, 4); 369 | canvas_set_color(canvas, ColorBlack); 370 | canvas_draw_rframe(canvas, 20, 24, 88, 16, 4); 371 | canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "Restoring game ..."); 372 | } 373 | } 374 | 375 | if(game_state.scene == SceneWin) { 376 | gray_screen(canvas); 377 | canvas_draw_box(canvas, 7, 20, 114, 24); 378 | canvas_set_color(canvas, ColorBlack); 379 | canvas_draw_box(canvas, 8, 21, 112, 22); 380 | canvas_set_color(canvas, ColorWhite); 381 | canvas_draw_box(canvas, 10, 23, 108, 18); 382 | canvas_set_color(canvas, ColorBlack); 383 | canvas_draw_xbm(canvas, 14, 27, 100, 10, pic_puzzled); 384 | } 385 | else if (game_state.scene == ScenePopup) { 386 | gray_screen(canvas); 387 | canvas_set_color(canvas, ColorWhite); 388 | canvas_draw_rbox(canvas, 28, 16, 72, 32, 4); 389 | canvas_set_color(canvas, ColorBlack); 390 | canvas_draw_rframe(canvas, 28, 16, 72, 32, 4); 391 | 392 | for(int i=0; i < POPUP_MENU_ITEMS; i++) { 393 | if ( i == popup_menu_selected_item) { 394 | canvas_set_color(canvas, ColorBlack); 395 | canvas_draw_box(canvas, 34, 20 + 12 * i, 60, 12); 396 | } 397 | 398 | canvas_set_color(canvas, i == popup_menu_selected_item ? ColorWhite : ColorBlack); 399 | canvas_draw_str_aligned(canvas, 64, 26 + 12 * i, AlignCenter, AlignCenter, popup_menu_strings[i]); 400 | } 401 | } 402 | } 403 | 404 | static void game_event_handler(GameEvent const event) { 405 | if(event.type == EventTypeKey) { 406 | if(event.input.type == InputTypePress) { 407 | switch(event.input.key) { 408 | case InputKeyUp: 409 | key_stack_push(DirectionUp); 410 | break; 411 | case InputKeyDown: 412 | key_stack_push(DirectionDown); 413 | break; 414 | case InputKeyRight: 415 | key_stack_push(DirectionRight); 416 | break; 417 | case InputKeyLeft: 418 | key_stack_push(DirectionLeft); 419 | break; 420 | case InputKeyOk: 421 | if (game_state.scene == ScenePlay) { 422 | game_state.scene = ScenePopup; 423 | key_stack_init(); 424 | } 425 | else 426 | key_stack_push(DirectionNone); 427 | break; 428 | case InputKeyBack: 429 | if (game_state.scene == ScenePopup) { 430 | game_state.scene = ScenePlay; 431 | } 432 | else { 433 | storage_game_state_save(); 434 | sandbox_loop_exit(); 435 | } 436 | break; 437 | } 438 | } 439 | } else if(event.type == EventTypeTick) { 440 | game_tick(); 441 | } 442 | } 443 | 444 | static void game_alloc() { 445 | srand(DWT->CYCCNT); 446 | key_stack_init(); 447 | notification = furi_record_open(RECORD_NOTIFICATION); 448 | notification_message_block(notification, &sequence_display_backlight_enforce_on); 449 | } 450 | 451 | static void game_free() { 452 | notification_message_block(notification, &sequence_display_backlight_enforce_auto); 453 | furi_record_close(RECORD_NOTIFICATION); 454 | } 455 | 456 | int32_t game15_app() { 457 | game_alloc(); 458 | game_init(); 459 | 460 | loaded_saving_ticks = 0; 461 | if(storage_game_state_load()) { 462 | if (game_state.scene != ScenePlay) 463 | game_init(); 464 | else 465 | loaded_saving_ticks = FPS; 466 | } 467 | else 468 | game_init(); 469 | 470 | sandbox_init( 471 | FPS, (SandboxRenderCallback)render_callback, (SandboxEventHandler)game_event_handler); 472 | sandbox_loop(); 473 | sandbox_free(); 474 | game_free(); 475 | return 0; 476 | } 477 | --------------------------------------------------------------------------------