-
Notifications
You must be signed in to change notification settings - Fork 3
/
app.c
689 lines (606 loc) · 24.2 KB
/
app.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license. */
#include <furi.h>
#include <furi_hal.h>
#include <input/input.h>
#include <gui/gui.h>
#include <stdlib.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <math.h>
#ifndef PI
#define PI 3.14159265358979f
#endif
#define TAG "Asteroids" // Used for logging
#define DEBUG_MSG 1
#define SCREEN_XRES 128
#define SCREEN_YRES 64
#define GAME_START_LIVES 3
/* The game uses the OK button both to fire and to accelerate the ship.
* This makes it a lot more playable since the finger does not have to
* move between two keys. However it is important that the extra time the
* player needs to press the button to accelerate instead of just firing
* is precisely selected to provide a smooth experience. After a few
* attempts, it looks like 70 milliseconds is the right spot. */
#define SHIP_ACCELERATION_KEYPRESS_TIME 70
/* ============================ Data structures ============================= */
typedef struct Ship {
float x, /* Ship x position. */
y, /* Ship y position. */
vx, /* x velocity. */
vy, /* y velocity. */
rot; /* Current rotation. 2*PI full rotation. */
} Ship;
typedef struct Bullet {
float x, y, vx, vy; /* Fields like in ship. */
uint32_t ttl; /* Time to live, in ticks. */
} Bullet;
typedef struct Asteroid {
float x, y, vx, vy, rot, /* Fields like ship. */
rot_speed, /* Angular velocity (rot speed and sense). */
size; /* Asteroid size. */
uint8_t shape_seed; /* Seed to give random shape. */
} Asteroid;
#define MAXBUL 10 /* Max bullets on the screen. */
#define MAXAST 32 /* Max asteroids on the screen. */
#define SHIP_HIT_ANIMATION_LEN 15
typedef struct AsteroidsApp {
/* GUI */
Gui *gui;
ViewPort *view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */
FuriMessageQueue *event_queue; /* Key press events go here. */
/* Game state. */
int running; /* Once false exists the app. */
bool gameover; /* Game over status. */
uint32_t ticks; /* Game ticks. Increments at each refresh. */
uint32_t score; /* Game score. */
uint32_t lives; /* Number of lives in the current game. */
uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid
and we need to show an animation as long as
its value is non-zero (and decrease it's value
at each tick of animation). */
/* Ship state. */
struct Ship ship;
/* Bullets state. */
struct Bullet bullets[MAXBUL]; /* Each bullet state. */
int bullets_num; /* Active bullets. */
uint32_t last_bullet_tick; /* Tick the last bullet was fired. */
/* Asteroids state. */
Asteroid asteroids[MAXAST]; /* Each asteroid state. */
int asteroids_num; /* Active asteroids. */
uint32_t pressed[InputKeyMAX]; /* pressed[id] is true if pressed.
Each array item contains the time
in milliseconds the key was pressed. */
bool fire; /* Short press detected: fire a bullet. */
} AsteroidsApp;
/* ============================== Prototypes ================================ */
// Only functions called before their definition are here.
void restart_game_after_gameover(AsteroidsApp *app);
uint32_t key_pressed_time(AsteroidsApp *app, InputKey key);
/* ============================ 2D drawing ================================== */
/* This structure represents a polygon of at most POLY_MAX points.
* The function draw_poly() is able to render it on the screen, rotated
* by the amount specified. */
#define POLY_MAX 8
typedef struct Poly {
float x[POLY_MAX];
float y[POLY_MAX];
uint32_t points; /* Number of points actually populated. */
} Poly;
/* Define the polygons we use. */
Poly ShipPoly = {
{-3, 0, 3},
{-3, 6, -3},
3
};
Poly ShipFirePoly = {
{-1.5, 0, 1.5},
{-3, -6, -3},
3
};
/* Rotate the point of the polygon 'poly' and store the new rotated
* polygon in 'rot'. The polygon is rotated by an angle 'a', with
* center at 0,0. */
void rotate_poly(Poly *rot, Poly *poly, float a) {
/* We want to compute sin(a) and cos(a) only one time
* for every point to rotate. It's a slow operation. */
float sin_a = (float)sin(a);
float cos_a = (float)cos(a);
for (uint32_t j = 0; j < poly->points; j++) {
rot->x[j] = poly->x[j]*cos_a - poly->y[j]*sin_a;
rot->y[j] = poly->y[j]*cos_a + poly->x[j]*sin_a;
}
rot->points = poly->points;
}
/* This is an 8 bit LFSR we use to generate a predictable and fast
* pseudorandom sequence of numbers, to give a different shape to
* each asteroid. */
void lfsr_next(unsigned char *prev) {
unsigned char lsb = *prev & 1;
*prev = *prev >> 1;
if (lsb == 1) *prev ^= 0b11000111;
*prev ^= *prev<<7; /* Mix things a bit more. */
}
/* Render the polygon 'poly' at x,y, rotated by the specified angle. */
void draw_poly(Canvas *const canvas, Poly *poly, uint8_t x, uint8_t y, float a)
{
Poly rot;
rotate_poly(&rot,poly,a);
canvas_set_color(canvas, ColorBlack);
for (uint32_t j = 0; j < rot.points; j++) {
uint32_t a = j;
uint32_t b = j+1;
if (b == rot.points) b = 0;
canvas_draw_line(canvas,x+rot.x[a],y+rot.y[a],
x+rot.x[b],y+rot.y[b]);
}
}
/* A bullet is just a + pixels pattern. A single pixel is not
* visible enough. */
void draw_bullet(Canvas *const canvas, Bullet *b) {
canvas_draw_dot(canvas,b->x-1,b->y);
canvas_draw_dot(canvas,b->x+1,b->y);
canvas_draw_dot(canvas,b->x,b->y);
canvas_draw_dot(canvas,b->x,b->y-1);
canvas_draw_dot(canvas,b->x,b->y+1);
}
/* Draw an asteroid. The asteroid shapes is computed on the fly and
* is not stored in a permanent shape structure. In order to generate
* the shape, we use an initial fixed shape that we resize according
* to the asteroid size, perturbed according to the asteroid shape
* seed, and finally draw it rotated of the right amount. */
void draw_asteroid(Canvas *const canvas, Asteroid *ast) {
Poly ap;
/* Start with what is kinda of a circle. Note that this could be
* stored into a template and copied here, to avoid computing
* sin() / cos(). But the Flipper can handle it without problems. */
uint8_t r = ast->shape_seed;
for (int j = 0; j < 8; j++) {
float a = (PI*2)/8*j;
/* Before generating the point, to make the shape unique generate
* a random factor between .7 and 1.3 to scale the distance from
* the center. However this asteroid should have its unique shape
* that remains always the same, so we use a predictable PRNG
* implemented by an 8 bit shift register. */
lfsr_next(&r);
float scaling = .7+((float)r/255*.6);
ap.x[j] = (float)sin(a) * ast->size * scaling;
ap.y[j] = (float)cos(a) * ast->size * scaling;
}
ap.points = 8;
draw_poly(canvas,&ap,ast->x,ast->y,ast->rot);
}
/* Draw small ships in the top-right part of the screen, one for
* each left live. */
void draw_left_lives(Canvas *const canvas, AsteroidsApp *app) {
int lives = app->lives;
int x = SCREEN_XRES-5;
Poly mini_ship = {
{-2, 0, 2},
{-2, 4, -2},
3
};
while(lives--) {
draw_poly(canvas,&mini_ship,x,6,PI);
x -= 6;
}
}
/* Given the current position, update it according to the velocity and
* wrap it back to the other side if the object went over the screen. */
void update_pos_by_velocity(float *x, float *y, float vx, float vy) {
/* Return back from one side to the other of the screen. */
*x += vx;
*y += vy;
if (*x >= SCREEN_XRES) *x = 0;
else if (*x < 0) *x = SCREEN_XRES-1;
if (*y >= SCREEN_YRES) *y = 0;
else if (*y < 0) *y = SCREEN_YRES-1;
}
/* Render the current game screen. */
void render_callback(Canvas *const canvas, void *ctx) {
AsteroidsApp *app = ctx;
/* Clear screen. */
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, 0, SCREEN_XRES-1, SCREEN_YRES-1);
/* Draw score. */
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
char score[32];
snprintf(score,sizeof(score),"%lu",app->score);
canvas_draw_str(canvas, 0, 8, score);
/* Draw left ships. */
draw_left_lives(canvas,app);
/* Draw ship, asteroids, bullets. */
draw_poly(canvas,&ShipPoly,app->ship.x,app->ship.y,app->ship.rot);
if (key_pressed_time(app,InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME)
draw_poly(canvas,&ShipFirePoly,app->ship.x,app->ship.y,app->ship.rot);
for (int j = 0; j < app->bullets_num; j++)
draw_bullet(canvas,&app->bullets[j]);
for (int j = 0; j < app->asteroids_num; j++)
draw_asteroid(canvas,&app->asteroids[j]);
/* Game over text. */
if (app->gameover) {
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 28, 35, "GAME OVER");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 25, 50, "Press OK to restart");
}
}
/* ============================ Game logic ================================== */
float distance(float x1, float y1, float x2, float y2) {
float dx = x1-x2;
float dy = y1-y2;
return sqrt(dx*dx+dy*dy);
}
/* Detect a collision between the object at x1,y1 of radius r1 and
* the object at x2, y2 of radius r2. A factor < 1 will make the
* function detect the collision even if the objects are yet not
* relly touching, while a factor > 1 will make it detect the collision
* only after they are a bit overlapping. It basically is used to
* rescale the distance.
*
* Note that in this simplified 2D world, objects are all considered
* spheres (this is why this function only takes the radius). This
* is, after all, kinda accurate for asteroids, for bullets, and
* even for the ship "core" itself. */
bool objects_are_colliding(float x1, float y1, float r1,
float x2, float y2, float r2,
float factor)
{
/* The objects are colliding if the distance between object 1 and 2
* is smaller than the sum of the two radiuses r1 and r2.
* So it would be like: sqrt((x1-x2)^2+(y1-y2)^2) < r1+r2.
* However we can avoid computing the sqrt (which is slow) by
* squaring the second term and removing the square root, making
* the comparison like this:
*
* (x1-x2)^2+(y1-y2)^2 < (r1+r2)^2. */
float dx = (x1-x2)*factor;
float dy = (y1-y2)*factor;
float rsum = r1+r2;
return dx*dx+dy*dy < rsum*rsum;
}
/* Create a new bullet headed in the same direction of the ship. */
void ship_fire_bullet(AsteroidsApp *app) {
if (app->bullets_num == MAXBUL) return;
Bullet *b = &app->bullets[app->bullets_num];
b->x = app->ship.x;
b->y = app->ship.y;
b->vx = -sin(app->ship.rot);
b->vy = cos(app->ship.rot);
/* Ship should fire from its head, not in the middle. */
b->x += b->vx*5;
b->y += b->vy*5;
/* Give the bullet some velocity (for now the vector is just
* normalized to 1). */
b->vx *= 3;
b->vy *= 3;
/* It's more realistic if we add the velocity vector of the
* ship, too. Otherwise if the ship is going fast the bullets
* will be slower, which is not how the world works. */
b->vx += app->ship.vx;
b->vy += app->ship.vy;
b->ttl = 50; /* The bullet will disappear after N ticks. */
app->bullets_num++;
}
/* Remove the specified bullet by id (index in the array). */
void remove_bullet(AsteroidsApp *app, int bid) {
/* Replace the top bullet with the empty space left
* by the removal of this bullet. This way we always take the
* array dense, which is an advantage when looping. */
int n = --app->bullets_num;
if (n && bid != n) app->bullets[bid] = app->bullets[n];
}
/* Create a new asteroid, away from the ship. Return the
* pointer to the asteroid object, so that the caller can change
* certain things of the asteroid if needed. */
Asteroid *add_asteroid(AsteroidsApp *app) {
if (app->asteroids_num == MAXAST) return NULL;
float size = 4+rand()%15;
float min_distance = 20;
float x,y;
do {
x = rand() % SCREEN_XRES;
y = rand() % SCREEN_YRES;
} while(distance(app->ship.x,app->ship.y,x,y) < min_distance+size);
Asteroid *a = &app->asteroids[app->asteroids_num++];
a->x = x;
a->y = y;
a->vx = 2*(-.5 + ((float)rand()/RAND_MAX));
a->vy = 2*(-.5 + ((float)rand()/RAND_MAX));
a->size = size;
a->rot = 0;
a->rot_speed = ((float)rand()/RAND_MAX)/10;
if (app->ticks & 1) a->rot_speed = -(a->rot_speed);
a->shape_seed = rand() & 255;
return a;
}
/* Remove the specified asteroid by id (index in the array). */
void remove_asteroid(AsteroidsApp *app, int id) {
/* Replace the top asteroid with the empty space left
* by the removal of this one. This way we always take the
* array dense, which is an advantage when looping. */
int n = --app->asteroids_num;
if (n && id != n) app->asteroids[id] = app->asteroids[n];
}
/* Called when an asteroid was reached by a bullet. The asteroid
* hit is the one with the specified 'id'. */
void asteroid_was_hit(AsteroidsApp *app, int id) {
float sizelimit = 6; // Smaller than that, they disappear in one shot.
Asteroid *a = &app->asteroids[id];
/* Asteroid is large enough to break into fragments. */
float size = a->size;
float x = a->x, y = a->y;
remove_asteroid(app,id);
if (size > sizelimit) {
int max_fragments = size / sizelimit;
int fragments = 2+rand()%max_fragments;
float newsize = size/fragments;
if (newsize < 2) newsize = 2;
for (int j = 0; j < fragments; j++) {
a = add_asteroid(app);
if (a == NULL) break; // Too many asteroids on screen.
a->x = x + -(size/2) + rand() % (int)newsize;
a->y = y + -(size/2) + rand() % (int)newsize;
a->size = newsize;
}
} else {
app->score++;
}
}
/* Set game over state. When in game-over mode, the game displays a
* game over text with a background of many asteroids floating around. */
void game_over(AsteroidsApp *app) {
restart_game_after_gameover(app);
app->gameover = true;
int asteroids = 8;
while(asteroids-- && add_asteroid(app) != NULL);
}
/* Function called when a collision between the asteroid and the
* ship is detected. */
void ship_was_hit(AsteroidsApp *app) {
app->ship_hit = SHIP_HIT_ANIMATION_LEN;
if (app->lives) {
app->lives--;
} else {
game_over(app);
}
}
/* Restart game after the ship is hit. Will reset the ship position, bullets
* and asteroids to restart the game. */
void restart_game(AsteroidsApp *app) {
app->ship.x = SCREEN_XRES / 2;
app->ship.y = SCREEN_YRES / 2;
app->ship.rot = PI; /* Start headed towards top. */
app->ship.vx = 0;
app->ship.vy = 0;
app->bullets_num = 0;
app->last_bullet_tick = 0;
app->asteroids_num = 0;
}
/* Called after game over to restart the game. This function
* also calls restart_game(). */
void restart_game_after_gameover(AsteroidsApp *app) {
app->gameover = false;
app->ticks = 0;
app->score = 0;
app->ship_hit = 0;
app->lives = GAME_START_LIVES-1; /* -1 to account for current one. */
restart_game(app);
}
/* Move bullets. */
void update_bullets_position(AsteroidsApp *app) {
for (int j = 0; j < app->bullets_num; j++) {
update_pos_by_velocity(&app->bullets[j].x,&app->bullets[j].y,
app->bullets[j].vx,app->bullets[j].vy);
if (--app->bullets[j].ttl == 0) {
remove_bullet(app,j);
j--; /* Process this bullet index again: the removal will
fill it with the top bullet to take the array dense. */
}
}
}
/* Move asteroids. */
void update_asteroids_position(AsteroidsApp *app) {
for (int j = 0; j < app->asteroids_num; j++) {
update_pos_by_velocity(&app->asteroids[j].x,&app->asteroids[j].y,
app->asteroids[j].vx,app->asteroids[j].vy);
app->asteroids[j].rot += app->asteroids[j].rot_speed;
if (app->asteroids[j].rot < 0) app->asteroids[j].rot = 2*PI;
else if (app->asteroids[j].rot > 2*PI) app->asteroids[j].rot = 0;
}
}
/* Collision detection and game state update based on collisions. */
void detect_collisions(AsteroidsApp *app) {
/* Detect collision between bullet and asteroid. */
for (int j = 0; j < app->bullets_num; j++) {
Bullet *b = &app->bullets[j];
for (int i = 0; i < app->asteroids_num; i++) {
Asteroid *a = &app->asteroids[i];
if (objects_are_colliding(a->x, a->y, a->size,
b->x, b->y, 1.5, 1))
{
asteroid_was_hit(app,i);
remove_bullet(app,j);
/* The bullet no longer exist. Break the loop.
* However we want to start processing from the
* same bullet index, since now it is used by
* another bullet (see remove_bullet()). */
j--; /* Scan this j value again. */
break;
}
}
}
/* Detect collision between ship and asteroid. */
for (int j = 0; j < app->asteroids_num; j++) {
Asteroid *a = &app->asteroids[j];
if (objects_are_colliding(a->x, a->y, a->size,
app->ship.x, app->ship.y, 4, 1))
{
ship_was_hit(app);
break;
}
}
}
/* This is the main game execution function, called 10 times for
* second (with the Flipper screen latency, an higher FPS does not
* make sense). In this function we update the position of objects based
* on velocity. Detect collisions. Update the score and so forth.
*
* Each time this function is called, app->tick is incremented. */
void game_tick(void *ctx) {
AsteroidsApp *app = ctx;
/* There are two special screens:
*
* 1. Ship was hit, we frozen the game as long as ship_hit isn't zero
* again, and show an animation of a rotating ship. */
if (app->ship_hit) {
app->ship.rot += 0.5;
app->ship_hit--;
view_port_update(app->view_port);
if (app->ship_hit == 0) {
restart_game(app);
}
return;
} else if (app->gameover) {
/* 2. Game over. We need to update only background asteroids. In this
* state the game just displays a GAME OVER text with the floating
* asteroids in background. */
if (key_pressed_time(app,InputKeyOk) > 100) {
restart_game_after_gameover(app);
}
update_asteroids_position(app);
view_port_update(app->view_port);
return;
}
/* Handle key presses. */
if (app->pressed[InputKeyLeft]) app->ship.rot -= .35;
if (app->pressed[InputKeyRight]) app->ship.rot += .35;
if (key_pressed_time(app,InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME) {
app->ship.vx -= 0.5*(float)sin(app->ship.rot);
app->ship.vy += 0.5*(float)cos(app->ship.rot);
} else if (app->pressed[InputKeyDown]) {
app->ship.vx *= 0.75;
app->ship.vy *= 0.75;
}
/* Fire a bullet if needed. app->fire is set in
* asteroids_update_keypress_state() since depends on exact
* pressure timing. */
if (app->fire) {
uint32_t bullet_min_period = 200; // In milliseconds
uint32_t now = furi_get_tick();
if (now - app->last_bullet_tick >= bullet_min_period) {
ship_fire_bullet(app);
app->last_bullet_tick = now;
}
app->fire = false;
}
/* Update positions and detect collisions. */
update_pos_by_velocity(&app->ship.x,&app->ship.y,app->ship.vx,app->ship.vy);
update_bullets_position(app);
update_asteroids_position(app);
detect_collisions(app);
/* From time to time, create a new asteroid. The more asteroids
* already on the screen, the smaller probability of creating
* a new one. */
if (app->asteroids_num == 0 ||
(random() % 5000) < (30/(1+app->asteroids_num)))
{
add_asteroid(app);
}
app->ticks++;
view_port_update(app->view_port);
}
/* ======================== Flipper specific code =========================== */
/* Here all we do is putting the events into the queue that will be handled
* in the while() loop of the app entry point function. */
void input_callback(InputEvent* input_event, void* ctx)
{
AsteroidsApp *app = ctx;
furi_message_queue_put(app->event_queue,input_event,FuriWaitForever);
}
/* Allocate the application state and initialize a number of stuff.
* This is called in the entry point to create the application state. */
AsteroidsApp* asteroids_app_alloc() {
AsteroidsApp *app = malloc(sizeof(AsteroidsApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, render_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->running = 1; /* Turns 0 when back is pressed. */
restart_game_after_gameover(app);
memset(app->pressed,0,sizeof(app->pressed));
return app;
}
/* Free what the application allocated. It is not clear to me if the
* Flipper OS, once the application exits, will be able to reclaim space
* even if we forget to free something here. */
void asteroids_app_free(AsteroidsApp *app) {
furi_assert(app);
// View related.
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(app->event_queue);
app->gui = NULL;
free(app);
}
/* Return the time in milliseconds the specified key is continuously
* pressed. Or 0 if it is not pressed. */
uint32_t key_pressed_time(AsteroidsApp *app, InputKey key) {
return app->pressed[key] == 0 ? 0 :
furi_get_tick() - app->pressed[key];
}
/* Handle keys interaction. */
void asteroids_update_keypress_state(AsteroidsApp *app, InputEvent input) {
if (input.type == InputTypePress) {
app->pressed[input.key] = furi_get_tick();
} else if (input.type == InputTypeRelease) {
uint32_t dur = key_pressed_time(app,input.key);
app->pressed[input.key] = 0;
if (dur < 200 && input.key == InputKeyOk) app->fire = true;
}
}
int32_t asteroids_app_entry(void* p) {
UNUSED(p);
AsteroidsApp *app = asteroids_app_alloc();
/* Create a timer. We do data analysis in the callback. */
FuriTimer *timer = furi_timer_alloc(game_tick, FuriTimerTypePeriodic, app);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10);
/* This is the main event loop: here we get the events that are pushed
* in the queue by input_callback(), and process them one after the
* other. */
InputEvent input;
while(app->running) {
FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
if (qstat == FuriStatusOk) {
if (DEBUG_MSG) FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u",
input.type, input.key);
/* Handle navigation here. Then handle view-specific inputs
* in the view specific handling function. */
if (input.type == InputTypeShort &&
input.key == InputKeyBack)
{
app->running = 0;
} else {
asteroids_update_keypress_state(app,input);
}
} else {
/* Useful to understand if the app is still alive when it
* does not respond because of bugs. */
if (DEBUG_MSG) {
static int c = 0; c++;
if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
}
}
}
furi_timer_free(timer);
asteroids_app_free(app);
return 0;
}