GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 86 / 2 / 88
Functions: 100.0% 8 / 0 / 8
Branches: 100.0% 19 / 1 / 20

explosion/src/explosion.c
Line Branch Exec Source
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright (c) 2026 Manuel Hernández Méndez
5 *
6 * Authors:
7 * Manuel Hernández Méndez <maherme.dev@gmail.com>
8 */
9
10 #include "explosion.h"
11 #include "graph.h"
12 #include "graphGlutCallbacks.h"
13 #include "list.h"
14 #include "utils.h"
15 #include <assert.h>
16 #include <stdbool.h>
17
18 #define EXPLOSION_BULLET_SPACESHIP_WIDTH 8
19 #define EXPLOSION_BULLET_SPACESHIP_HEIGHT 8
20 #define EXPLOSION_BULLET_ALIEN_WIDTH 6
21 #define EXPLOSION_BULLET_ALIEN_HEIGHT 8
22 #define EXPLOSION_SPACESHIP_WIDTH 16
23 #define EXPLOSION_SPACESHIP_HEIGHT 8
24 #define EXPLOSION_SPACESHIP_NUM_FRAMES 2
25 #define EXPLOSION_UFO_WIDTH 24
26 #define EXPLOSION_UFO_HEIGHT 8
27 #define EXPLOSION_ALIEN_NUM_IMGS 4
28 #define EXPLOSION_ALIEN_WIDTH 14
29 #define EXPLOSION_ALIEN_HEIGHT 8
30
31 static const unsigned int explosion_heights[] = {
32 [EXPLOSION_BULLET_SPACESHIP] = EXPLOSION_BULLET_SPACESHIP_HEIGHT,
33 [EXPLOSION_BULLET_ALIEN] = EXPLOSION_BULLET_ALIEN_HEIGHT,
34 [EXPLOSION_SPACESHIP] = EXPLOSION_SPACESHIP_HEIGHT,
35 [EXPLOSION_UFO] = EXPLOSION_UFO_HEIGHT,
36 [EXPLOSION_ALIEN] = EXPLOSION_ALIEN_HEIGHT,
37 };
38
39 struct explosion_instance
40 {
41 sprite_t sprite;
42 struct timespec creation_time;
43 long long explosion_time;
44 struct timespec update_frame_timer;
45 long long update_frame_time;
46 explosion_type_t type;
47 void (*callback)(void);
48 };
49
50 typedef struct explosion_node
51 {
52 explosion_t explosion;
53 struct list_head node;
54 } explosion_node_t;
55
56 static LIST_HEAD(explosions);
57
58 static const struct
59 {
60 const char image[EXPLOSION_BULLET_SPACESHIP_HEIGHT][EXPLOSION_BULLET_SPACESHIP_WIDTH][NUM_RGBA_CHANNELS];
61 const int image_width;
62 const int image_height;
63 const long long explosion_time;
64 } explosion_bullet_spaceship = {.image = {{B, B, R, B, R, B, B, B},
65 {B, R, R, R, B, B, B, B},
66 {B, B, R, R, R, B, R, B},
67 {B, R, R, R, R, R, B, B},
68 {B, B, R, R, R, B, B, B},
69 {B, B, B, R, B, B, R, B},
70 {B, B, R, B, B, R, B, B},
71 {R, B, B, B, R, B, B, R}},
72 .image_width = EXPLOSION_BULLET_SPACESHIP_WIDTH,
73 .image_height = EXPLOSION_BULLET_SPACESHIP_HEIGHT,
74 .explosion_time = 500 * NS_PER_MS};
75
76 static const struct
77 {
78 const char image[EXPLOSION_BULLET_ALIEN_HEIGHT][EXPLOSION_BULLET_ALIEN_WIDTH][NUM_RGBA_CHANNELS];
79 const int image_width;
80 const int image_height;
81 const long long explosion_time;
82 } explosion_bullet_alien = {.image = {{B, B, G, B, B, G},
83 {B, G, B, G, B, B},
84 {B, B, G, G, G, G},
85 {G, G, G, G, B, B},
86 {B, G, G, G, G, B},
87 {G, G, G, G, G, G},
88 {B, B, G, G, B, G},
89 {B, G, B, B, G, B}},
90 .image_width = EXPLOSION_BULLET_ALIEN_WIDTH,
91 .image_height = EXPLOSION_BULLET_ALIEN_HEIGHT,
92 .explosion_time = 500 * NS_PER_MS};
93
94 static const struct
95 {
96 const char image[EXPLOSION_SPACESHIP_NUM_FRAMES][EXPLOSION_SPACESHIP_HEIGHT][EXPLOSION_SPACESHIP_WIDTH][NUM_RGBA_CHANNELS];
97 const int image_width;
98 const int image_height;
99 const long long explosion_time;
100 const long long update_frame_time;
101 } explosion_spaceship = {.image =
102 {/* frame 1 */
103 {{B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B},
104 {B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B},
105 {B, B, B, G, B, B, B, B, B, B, G, B, B, B, B, B},
106 {B, B, B, G, B, G, B, B, B, G, G, B, G, B, B, B},
107 {B, B, G, B, G, B, G, B, B, B, B, G, B, G, B, B},
108 {B, B, B, G, B, G, G, B, B, G, B, G, G, B, B, B},
109 {B, B, B, G, G, B, B, B, B, B, B, G, G, B, B, B},
110 {B, B, G, G, G, G, B, B, B, B, G, G, G, G, B, B}},
111 /* frame 2 */
112 {{G, B, B, G, B, B, G, B, B, G, B, B, G, B, B, G},
113 {B, B, G, B, G, B, B, B, B, G, B, G, B, B, G, B},
114 {B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B},
115 {B, B, B, B, B, B, G, B, B, G, B, B, G, B, B, B},
116 {B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B},
117 {B, B, B, G, B, B, B, B, B, B, B, G, B, B, B, B},
118 {B, B, B, G, G, B, B, B, B, B, B, G, G, B, B, B},
119 {B, B, G, G, G, G, B, B, B, B, G, G, G, G, B, B}}},
120 .image_width = EXPLOSION_SPACESHIP_WIDTH,
121 .image_height = EXPLOSION_SPACESHIP_HEIGHT,
122 .explosion_time = 2000 * NS_PER_MS,
123 .update_frame_time = 125 * NS_PER_MS};
124
125 static const struct
126 {
127 const char image[EXPLOSION_UFO_HEIGHT][EXPLOSION_UFO_WIDTH][NUM_RGBA_CHANNELS];
128 const int image_width;
129 const int image_height;
130 const long long explosion_time;
131 } explosion_ufo = {.image = {{B, B, B, B, B, B, B, B, B, B, B, R, B, R, B, B, B, B, B, B, B, B, B, B},
132 {B, B, B, R, R, R, R, R, B, B, B, B, B, B, B, B, R, R, R, R, R, B, B, B},
133 {B, B, R, R, R, R, R, R, R, B, B, B, R, B, B, R, R, R, R, R, R, R, B, B},
134 {B, B, R, B, R, B, R, R, B, B, R, B, B, R, B, B, R, R, B, R, B, R, B, B},
135 {B, R, R, R, R, R, R, R, B, R, B, B, B, B, R, B, R, R, R, R, R, R, R, B},
136 {R, R, R, R, R, R, R, B, B, R, B, B, B, B, B, B, R, R, R, R, R, R, R, R},
137 {B, B, B, B, R, R, R, R, B, B, B, R, R, B, B, B, R, R, R, R, B, B, B, B},
138 {B, B, B, B, B, B, R, B, B, B, B, B, B, B, R, B, B, R, B, B, B, B, B, B}},
139 .image_width = EXPLOSION_UFO_WIDTH,
140 .image_height = EXPLOSION_UFO_HEIGHT,
141 .explosion_time = 500 * NS_PER_MS};
142
143 static const struct
144 {
145 const char image[EXPLOSION_ALIEN_NUM_IMGS][EXPLOSION_ALIEN_HEIGHT][EXPLOSION_ALIEN_WIDTH][NUM_RGBA_CHANNELS];
146 const int image_width;
147 const int image_height;
148 const long long explosion_time;
149 } explosion_alien = {.image =
150 {/* image 1 */
151 {{B, B, B, B, B, B, B, B, B, B, W, B, B, B},
152 {B, W, B, B, W, B, B, B, B, B, B, B, W, B},
153 {B, B, B, B, B, W, B, B, W, B, B, B, B, B},
154 {B, B, B, B, B, B, W, W, B, B, B, B, B, W},
155 {B, B, W, B, B, B, W, W, B, W, B, B, B, B},
156 {B, B, B, B, W, W, B, B, W, B, B, W, B, B},
157 {W, B, B, B, B, B, B, B, B, B, B, B, B, B},
158 {B, B, W, B, B, B, W, B, B, B, W, B, B, W}},
159 /* image 2 */
160 {{B, B, B, B, B, B, B, B, B, B, W, B, B, B},
161 {B, B, B, B, W, B, B, W, B, B, B, B, B, B},
162 {B, B, B, B, B, W, B, B, B, B, B, B, B, B},
163 {B, B, B, B, B, B, W, W, B, B, W, B, B, B},
164 {B, B, B, B, B, B, W, W, B, B, B, B, B, B},
165 {B, B, B, B, W, W, B, B, W, B, B, W, B, B},
166 {B, B, B, B, B, B, B, B, B, B, B, B, B, B},
167 {B, B, B, W, B, B, W, B, B, B, W, B, B, B}},
168 /* image 3 */
169 {{B, B, B, B, B, B, B, B, B, B, B, B, B, B},
170 {B, W, B, W, B, B, B, B, B, B, B, B, B, B},
171 {B, B, B, B, B, W, B, B, W, B, B, B, B, B},
172 {B, B, B, B, W, B, W, W, B, B, B, B, B, B},
173 {B, B, W, B, B, B, W, W, B, B, B, B, B, B},
174 {B, B, B, B, B, W, B, B, W, B, B, W, B, B},
175 {B, B, B, B, B, B, B, B, B, B, B, B, B, B},
176 {B, B, B, W, B, B, W, B, B, B, B, B, B, B}},
177 /* image 4 */
178 {{B, B, B, B, B, B, B, B, B, B, B, B, W, B},
179 {B, B, B, B, B, B, W, B, B, B, W, B, B, B},
180 {W, B, B, W, B, W, B, B, W, B, B, B, B, B},
181 {B, B, B, B, B, B, W, W, B, B, B, B, B, B},
182 {B, B, B, B, B, B, W, W, B, W, B, W, B, B},
183 {B, B, B, B, W, W, B, B, W, B, B, B, B, B},
184 {B, W, B, B, B, B, B, B, B, B, B, B, B, B},
185 {B, B, B, B, B, B, B, W, B, B, B, W, B, B}}},
186 .image_width = EXPLOSION_ALIEN_WIDTH,
187 .image_height = EXPLOSION_ALIEN_HEIGHT,
188 .explosion_time = 500 * NS_PER_MS};
189
190 static void
191 13 setExplosionType(explosion_t instance, explosion_type_t type)
192 {
193 static int explosion_alien_next = 0;
194
195 13 instance->type = type;
196
5/6
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 2 → 6 taken 3 times.
✓ Branch 2 → 8 taken 1 time.
13 switch (type)
197 {
198 7 case EXPLOSION_BULLET_SPACESHIP:
199 7 instance->sprite.width = explosion_bullet_spaceship.image_width;
200 7 instance->sprite.height = explosion_bullet_spaceship.image_height;
201 7 instance->sprite.image = (const char *)explosion_bullet_spaceship.image;
202 7 instance->explosion_time = explosion_bullet_spaceship.explosion_time;
203 7 break;
204 1 case EXPLOSION_BULLET_ALIEN:
205 1 instance->sprite.width = explosion_bullet_alien.image_width;
206 1 instance->sprite.height = explosion_bullet_alien.image_height;
207 1 instance->sprite.image = (const char *)explosion_bullet_alien.image;
208 1 instance->explosion_time = explosion_bullet_alien.explosion_time;
209 1 break;
210 1 case EXPLOSION_UFO:
211 1 instance->sprite.width = explosion_ufo.image_width;
212 1 instance->sprite.height = explosion_ufo.image_height;
213 1 instance->sprite.image = (const char *)explosion_ufo.image;
214 1 instance->explosion_time = explosion_ufo.explosion_time;
215 1 break;
216 3 case EXPLOSION_SPACESHIP:
217 3 instance->sprite.width = explosion_spaceship.image_width;
218 3 instance->sprite.height = explosion_spaceship.image_height;
219 3 instance->sprite.image_base = (const char *)explosion_spaceship.image;
220 3 instance->sprite.image = instance->sprite.image_base;
221 3 instance->sprite.num_frames = EXPLOSION_SPACESHIP_NUM_FRAMES;
222 3 instance->explosion_time = explosion_spaceship.explosion_time;
223 3 instance->update_frame_time = explosion_spaceship.update_frame_time;
224 3 clock_gettime(CLOCK_MONOTONIC, &instance->update_frame_timer);
225 3 break;
226 1 case EXPLOSION_ALIEN:
227 1 instance->sprite.width = explosion_alien.image_width;
228 1 instance->sprite.height = explosion_alien.image_height;
229 1 instance->sprite.image = (const char *)explosion_alien.image[explosion_alien_next];
230 1 explosion_alien_next = (explosion_alien_next + 1) % EXPLOSION_ALIEN_NUM_IMGS;
231 1 instance->explosion_time = explosion_alien.explosion_time;
232 1 break;
233 /* GCOVR_EXCL_START */
234 default:
235 assert(!"invalid explosion type");
236 UNREACHABLE();
237 break; /* GCOVR_EXCL_BR_SOURCE */
238 /* GCOVR_EXCL_STOP */
239 }
240 13 }
241
242 explosion_t
243 13 explosionCreate(int x, int y, explosion_type_t type, void (*callback)(void))
244 {
245 13 explosion_node_t *new_node = utilsCalloc(1, sizeof(explosion_node_t));
246 13 explosion_t new_explosion = utilsCalloc(1, sizeof(struct explosion_instance));
247 13 setExplosionType(new_explosion, type);
248 13 new_explosion->sprite.x = x - new_explosion->sprite.width / 2;
249 13 new_explosion->sprite.y = y;
250 13 new_explosion->sprite.pixels_to_move = 0;
251 13 new_explosion->sprite.time_to_move = 0;
252 13 new_explosion->sprite.layer = LAYER_EFFECTS;
253 13 new_explosion->sprite.visible = true;
254 13 clock_gettime(CLOCK_MONOTONIC, &new_explosion->creation_time);
255 13 graphRegisterPrint(graphGetSprite((base_t *)new_explosion));
256 13 new_explosion->callback = callback;
257 13 new_node->explosion = new_explosion;
258 13 list_add(&new_node->node, &explosions);
259
260 13 return new_explosion;
261 }
262
263 static bool
264 7 explosionTimeout(explosion_t explosion)
265 {
266 assert(explosion); /* GCOVR_EXCL_LINE */
267
268
2/2
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 9 taken 4 times.
7 if (utilsCheckTimeout(explosion->creation_time, explosion->explosion_time))
269 {
270 3 sprite_t *explosion_sprite = graphGetSprite((base_t *)explosion);
271 3 graphUnregisterPrint(explosion_sprite);
272
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 2 times.
3 if (explosion->callback)
273 {
274 1 explosion->callback();
275 }
276 3 UTILS_FREE(explosion);
277 3 return true;
278 }
279
280 4 return false;
281 }
282
283 static void
284 7 explosionUpdateImageToPrint(explosion_t explosion)
285 {
286 assert(explosion); /* GCOVR_EXCL_LINE */
287
288
4/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 7 taken 5 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 1 time.
7 if (explosion->sprite.num_frames > 1 && utilsCheckTimeout(explosion->update_frame_timer, explosion->update_frame_time))
289 {
290 1 graphNextImageToPrint(&explosion->sprite);
291 1 clock_gettime(CLOCK_MONOTONIC, &explosion->update_frame_timer);
292 }
293 7 }
294
295 void
296 7 explosionsDestroy(void)
297 {
298 explosion_node_t *n, *tmp;
299
300
2/2
✓ Branch 9 → 3 taken 7 times.
✓ Branch 9 → 10 taken 7 times.
14 list_for_each_entry_safe(n, tmp, &explosions, node)
301 {
302 7 explosionUpdateImageToPrint(n->explosion);
303
2/2
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 8 taken 4 times.
7 if (explosionTimeout(n->explosion))
304 {
305 3 list_del(&n->node);
306 3 UTILS_FREE(n);
307 }
308 }
309 7 }
310
311 bool
312 2 explosionsAllFinished(void)
313 {
314 2 return list_empty(&explosions);
315 }
316
317 unsigned int
318 5 explosionsGetExplosionHeight(explosion_type_t type)
319 {
320 5 return explosion_heights[type];
321 }
322
323 #ifdef UNIT_TESTING
324 void
325 11 helperUT_explosionResetList(void)
326 {
327 explosion_node_t *n, *tmp;
328
329
2/2
✓ Branch 5 → 3 taken 10 times.
✓ Branch 5 → 6 taken 11 times.
21 list_for_each_entry_safe(n, tmp, &explosions, node)
330 {
331 10 list_del(&n->node);
332 10 free((void *)n->explosion);
333 10 free((void *)n);
334 }
335 11 }
336 #endif /* UNIT_TESTING */
337