├── 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 | 
7 |
8 | 
9 |
10 | 
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 |
--------------------------------------------------------------------------------