GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 140 / 5 / 145
Functions: 100.0% 15 / 0 / 15
Branches: 100.0% 50 / 2 / 52

aliens/src/aliens.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 "aliens.h"
11 #include "events.h"
12 #include "graph.h"
13 #include "graphGlutCallbacks.h"
14 #include "physic.h"
15 #include "utils.h"
16 #include <assert.h>
17 #include <stdbool.h>
18
19 #define ALIEN_CELL_WIDTH 16
20 #define ALIEN_CELL_HEIGHT 16
21 #define ALIEN_NUM_FRAMES 2
22 #define SQUID_WIDTH 8
23 #define SQUID_HEIGHT 8
24 #define CRAB_WIDTH 11
25 #define CRAB_HEIGHT 8
26 #define OCTOPUS_WIDTH 12
27 #define OCTOPUS_HEIGHT 8
28
29 #define INDEX_ALIENS(row, col) ((row) * ALIENS_COLS + (col))
30
31 struct alien_instance
32 {
33 sprite_t sprite;
34 bool alive;
35 };
36
37 static struct
38 {
39 struct alien_instance aliens[ALIENS_INITIAL_NUMBER];
40 int origin_x;
41 int origin_y;
42 direction_t current_dir;
43 bool descend_pending;
44 max_movement_t max_movement;
45 int pixels_to_move;
46 unsigned int aliens_alive;
47 } alienPool;
48
49 static alien_type_t alienTypeByRow[ALIENS_ROWS] = {
50 OCTOPUS,
51 OCTOPUS,
52 CRAB,
53 CRAB,
54 SQUID,
55 };
56
57 static const char squidImage[ALIEN_NUM_FRAMES][SQUID_HEIGHT][SQUID_WIDTH][NUM_RGBA_CHANNELS] = {
58 /* frame 1 */
59 {{B, B, W, W, W, W, B, B},
60 {B, W, W, W, W, W, W, B},
61 {W, W, B, W, W, B, W, W},
62 {W, W, W, W, W, W, W, W},
63 {B, W, W, W, W, W, W, B},
64 {B, B, W, B, B, W, B, B},
65 {B, W, B, B, B, B, W, B},
66 {W, B, B, B, B, B, B, W}},
67 /* frame 2 */
68 {{B, B, W, W, W, W, B, B},
69 {B, W, W, W, W, W, W, B},
70 {W, W, B, W, W, B, W, W},
71 {W, W, W, W, W, W, W, W},
72 {B, W, W, B, B, W, W, B},
73 {B, B, W, W, W, W, B, B},
74 {B, B, W, B, B, W, B, B},
75 {B, W, W, B, B, W, W, B}}};
76
77 static const char crabImage[ALIEN_NUM_FRAMES][CRAB_HEIGHT][CRAB_WIDTH][NUM_RGBA_CHANNELS] = {
78 /* frame 1 */
79 {{B, B, B, W, B, B, B, W, B, B, B},
80 {B, B, B, B, W, B, W, B, B, B, B},
81 {B, B, B, W, W, W, W, W, B, B, B},
82 {W, B, W, W, B, W, B, W, W, B, W},
83 {B, W, W, W, W, W, W, W, W, W, B},
84 {W, B, W, W, W, W, W, W, W, B, W},
85 {B, B, B, W, B, B, B, W, B, B, B},
86 {B, W, W, B, B, B, B, B, W, W, B}},
87 /* frame 2 */
88 {{B, B, B, B, W, B, W, B, B, B, B},
89 {B, B, B, B, W, B, W, B, B, B, B},
90 {B, B, B, W, W, W, W, W, B, B, B},
91 {W, B, W, W, B, W, B, W, W, B, W},
92 {W, W, W, W, W, W, W, W, W, W, W},
93 {B, B, W, W, W, W, W, W, W, B, B},
94 {B, B, B, W, B, B, B, W, B, B, B},
95 {B, B, W, W, B, B, B, W, W, B, B}}};
96
97 static const char octopusImage[ALIEN_NUM_FRAMES][OCTOPUS_HEIGHT][OCTOPUS_WIDTH][NUM_RGBA_CHANNELS] = {
98 /* frame 1 */
99 {{B, B, W, W, W, W, W, W, W, W, B, B},
100 {B, W, W, W, W, W, W, W, W, W, W, B},
101 {W, W, W, B, W, W, W, W, B, W, W, W},
102 {B, W, W, W, W, B, B, W, W, W, W, B},
103 {B, B, W, W, W, W, W, W, W, W, B, B},
104 {B, B, W, B, W, B, B, W, B, W, B, B},
105 {B, B, W, B, W, B, B, W, B, W, B, B},
106 {W, W, W, B, W, B, B, W, B, W, W, W}},
107 /* frame 2 */
108 {{B, B, W, W, W, W, W, W, W, W, B, B},
109 {B, W, W, W, W, W, W, W, W, W, W, B},
110 {W, W, W, B, W, W, W, W, B, W, W, W},
111 {B, W, W, W, W, W, W, W, W, W, W, B},
112 {B, B, W, W, W, W, W, W, W, W, B, B},
113 {B, B, W, B, W, B, B, W, B, W, B, B},
114 {B, B, W, B, B, B, B, B, B, W, B, B},
115 {B, B, W, W, W, B, B, W, W, W, B, B}}};
116
117 static void
118 55 setAlienType(alien_t inst, alien_type_t type)
119 {
120
3/4
✓ Branch 2 → 3 taken 11 times.
✓ Branch 2 → 4 taken 22 times.
✓ Branch 2 → 5 taken 22 times.
55 switch (type)
121 {
122 11 case SQUID:
123 11 inst->sprite.width = SQUID_WIDTH;
124 11 inst->sprite.height = SQUID_HEIGHT;
125 11 inst->sprite.image_base = (const char *)squidImage;
126 11 break;
127 22 case CRAB:
128 22 inst->sprite.width = CRAB_WIDTH;
129 22 inst->sprite.height = CRAB_HEIGHT;
130 22 inst->sprite.image_base = (const char *)crabImage;
131 22 break;
132 22 case OCTOPUS:
133 22 inst->sprite.width = OCTOPUS_WIDTH;
134 22 inst->sprite.height = OCTOPUS_HEIGHT;
135 22 inst->sprite.image_base = (const char *)octopusImage;
136 22 break;
137 /* GCOVR_EXCL_START */
138 default:
139 assert(!"invalid alien type");
140 UNREACHABLE();
141 break; /* GCOVR_EXCL_BR_SOURCE */
142 /* GCOVR_EXCL_STOP */
143 }
144
145 55 inst->sprite.image = inst->sprite.image_base;
146 55 }
147
148 static void
149 55 alienCreate(int x, int y, alien_type_t type, int index)
150 {
151 55 struct alien_instance *inst = &alienPool.aliens[index];
152 55 setAlienType(inst, type);
153 55 inst->sprite.x = x;
154 55 inst->sprite.y = y;
155 55 inst->sprite.time_to_move = 0;
156 55 inst->sprite.pixels_to_move = alienPool.pixels_to_move;
157 55 inst->sprite.max_movement.right = GAME_WIDTH;
158 55 inst->sprite.max_movement.left = 0;
159 55 inst->sprite.max_movement.down = 0;
160 55 inst->sprite.num_frames = ALIEN_NUM_FRAMES;
161 55 inst->sprite.layer = LAYER_GAME_OBJECTS;
162 55 inst->sprite.visible = true;
163 55 graphUpdateTimeSprite(&inst->sprite);
164 55 graphRegisterPrint(&inst->sprite);
165 55 inst->alive = true;
166 55 }
167
168 void
169 2 alienDestroy(alien_t alien)
170 {
171
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
2 if (!alien)
172 {
173 1 return;
174 }
175
176 1 sprite_t *alien_sprite = graphGetSprite((base_t *)alien);
177 1 graphUnregisterPrint(alien_sprite);
178 1 alien->alive = false;
179 1 alienPool.aliens_alive--;
180 }
181
182 static int
183 55 alienWidth(alien_type_t type)
184 {
185
3/4
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 4 taken 22 times.
✓ Branch 2 → 5 taken 11 times.
55 switch (type)
186 {
187 22 case OCTOPUS:
188 22 return OCTOPUS_WIDTH;
189 22 case CRAB:
190 22 return CRAB_WIDTH;
191 11 case SQUID:
192 11 return SQUID_WIDTH;
193 /* GCOVR_EXCL_START */
194 default:
195 break; /* GCOVR_EXCL_BR_SOURCE */
196 /* GCOVR_EXCL_STOP */
197 }
198
199 /* GCOVR_EXCL_START */
200 assert(!"invalid alien type");
201 UNREACHABLE();
202 return 0;
203 /* GCOVR_EXCL_STOP */
204 }
205
206 void
207 1 aliensCreate(void)
208 {
209 1 int formation_width = ALIENS_COLS * ALIEN_CELL_WIDTH;
210 1 alienPool.origin_x = (GAME_WIDTH - formation_width) / 2;
211 1 alienPool.origin_y = GAME_HEIGHT / 2;
212 1 alienPool.current_dir = RIGHT;
213 1 alienPool.descend_pending = false;
214 1 alienPool.pixels_to_move = ALIEN_CELL_WIDTH / 4;
215 1 alienPool.aliens_alive = ALIENS_INITIAL_NUMBER;
216
217
2/2
✓ Branch 6 → 3 taken 55 times.
✓ Branch 6 → 7 taken 1 time.
56 for (int i = 0; i < ALIENS_INITIAL_NUMBER; i++)
218 {
219 55 int row = i / ALIENS_COLS;
220 55 int col = i % ALIENS_COLS;
221
222 55 alien_type_t type = alienTypeByRow[row];
223 55 int rel_x = col * ALIEN_CELL_WIDTH + (ALIEN_CELL_WIDTH - alienWidth(type)) / 2;
224 55 int rel_y = row * ALIEN_CELL_HEIGHT;
225
226 55 alienCreate(alienPool.origin_x + rel_x, alienPool.origin_y + rel_y, type, i);
227 }
228 1 }
229
230 bool
231 3 alienAlive(alien_t alien)
232 {
233
234
4/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
3 return alien && alien->alive;
235 }
236
237 unsigned int
238 55 aliensGetAlives(void)
239 {
240 55 return alienPool.aliens_alive;
241 }
242
243 alien_t
244 56 aliensGetAlienInstance(int alien_index)
245 {
246
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 55 times.
56 if (ALIENS_INITIAL_NUMBER <= alien_index)
247 1 return NULL;
248
249 55 return &alienPool.aliens[alien_index];
250 }
251
252 alien_t
253 3 aliensGetShooter(void)
254 {
255 3 int start_col = rand() % ALIENS_COLS;
256
257
2/2
✓ Branch 10 → 4 taken 33 times.
✓ Branch 10 → 11 taken 1 time.
34 for (int i = 0; i < ALIENS_COLS; i++)
258 {
259 33 int col = (start_col + i) % ALIENS_COLS;
260
261
2/2
✓ Branch 8 → 5 taken 157 times.
✓ Branch 8 → 9 taken 31 times.
188 for (int row = 0; row < ALIENS_ROWS; row++)
262 {
263 157 alien_t a = &alienPool.aliens[row * ALIENS_COLS + col];
264
2/2
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 155 times.
157 if (a->alive)
265 {
266 2 return a;
267 }
268 }
269 }
270
271 1 return NULL;
272 }
273 static void
274 5 getFormationBounds(int *left, int *right, int *down)
275 {
276 5 *left = GAME_WIDTH;
277 5 *right = 0;
278 5 *down = GAME_HEIGHT;
279
280
2/2
✓ Branch 12 → 3 taken 275 times.
✓ Branch 12 → 13 taken 5 times.
280 for (int i = 0; i < ALIENS_INITIAL_NUMBER; i++)
281 {
282 275 alien_t a = &alienPool.aliens[i];
283
2/2
✓ Branch 3 → 4 taken 269 times.
✓ Branch 3 → 5 taken 6 times.
275 if (!a->alive)
284 {
285 269 continue;
286 }
287
288 6 int x = a->sprite.x;
289 6 int w = a->sprite.width;
290 6 int y = a->sprite.y;
291
292
2/2
✓ Branch 5 → 6 taken 5 times.
✓ Branch 5 → 7 taken 1 time.
6 if (x < *left)
293 {
294 5 *left = x;
295 }
296
2/2
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 3 times.
6 if (x + w > *right)
297 {
298 3 *right = x + w;
299 }
300
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 11 taken 1 time.
6 if (y < *down)
301 {
302 5 *down = y;
303 }
304 }
305 5 }
306
307 void
308 5 aliensMove(void)
309 {
310 5 int left, right, down;
311 5 getFormationBounds(&left, &right, &down);
312
313
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 4 times.
5 if (down <= ALIENS_HEIGHT_GAME_OVER)
314 {
315 1 eventEmit(EVENT_ALIENS_REACHED_BOTTOM);
316 1 return;
317 }
318
319
4/4
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 12 taken 2 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
4 if (alienPool.current_dir == RIGHT && right + alienPool.pixels_to_move >= GAME_WIDTH)
320 {
321 1 alienPool.current_dir = LEFT;
322 1 alienPool.descend_pending = true;
323 }
324
4/4
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 15 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 1 time.
3 else if (alienPool.current_dir == LEFT && left - alienPool.pixels_to_move <= 0)
325 {
326 1 alienPool.current_dir = RIGHT;
327 1 alienPool.descend_pending = true;
328 }
329
330
2/2
✓ Branch 22 → 16 taken 220 times.
✓ Branch 22 → 23 taken 4 times.
224 for (int i = 0; i < ALIENS_INITIAL_NUMBER; i++)
331 {
332 220 alien_t a = &alienPool.aliens[i];
333
2/2
✓ Branch 16 → 17 taken 215 times.
✓ Branch 16 → 18 taken 5 times.
220 if (!a->alive)
334 {
335 215 continue;
336 }
337
338
2/2
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 3 times.
5 if (alienPool.descend_pending)
339 {
340 2 physicMoveSprite(&a->sprite, DOWN);
341 }
342 else
343 {
344 3 physicMoveSprite(&a->sprite, alienPool.current_dir);
345 }
346 }
347
348 4 alienPool.descend_pending = false;
349 }
350
351 #ifdef UNIT_TESTING
352 #include <string.h>
353
354 void
355 16 helperUT_alienInitPool(void)
356 {
357 16 memset(&alienPool, 0, sizeof(alienPool));
358 16 }
359
360 void
361 1 helperUT_alienSetAlive(alien_t alien, bool alive)
362 {
363 1 alien->alive = alive;
364 1 }
365
366 alien_t
367 70 helperUT_alienInjectInPool(int row, int col, sprite_t *sprite)
368 {
369 70 int index = INDEX_ALIENS(row, col);
370 70 alien_t alien = &alienPool.aliens[index];
371
372 70 memset(alien, 0, sizeof(*alien));
373
374
2/2
✓ Branch 2 → 3 taken 6 times.
✓ Branch 2 → 4 taken 64 times.
70 if (sprite)
375 {
376 6 alien->sprite = *sprite;
377 }
378
379 70 alien->alive = true;
380 70 alienPool.aliens_alive++;
381
382 70 return alien;
383 }
384
385 void
386 4 helperUT_alienSetCurrentDirection(direction_t dir)
387 {
388 4 alienPool.current_dir = dir;
389 4 }
390
391 #endif /* UNIT_TESTING */
392