GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 95 / 2 / 97
Functions: 100.0% 13 / 0 / 13
Branches: 100.0% 46 / 1 / 47

bullet/src/bullet.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 "bullet.h"
11 #include "graph.h"
12 #include "graphGlutCallbacks.h"
13 #include "utils.h"
14 #include <assert.h>
15 #include <stdbool.h>
16
17 #define MAX_BULLETS 4
18 #define BULLET_SPACESHIP_WIDTH 1
19 #define BULLET_SPACESHIP_HEIGHT 4
20 #define BULLET_ALIEN_WIDTH 3
21 #define BULLET_ALIEN_HEIGHT 7
22 #define BULLET_ALIEN_NUM_FRAMES 4
23 #define BULLET_SPACESHIP_SLOT 0
24 #define BULLET_ALIEN_SLOT 1
25
26 typedef enum
27 {
28 CRACKLE,
29 PLASMA,
30 COIL,
31 BULLET_ALIEN_NUM_TYPES
32 } bullet_alien_type_t;
33
34 struct bullet_instance
35 {
36 sprite_t sprite;
37 bullet_type_t type;
38 bool used;
39 };
40
41 typedef struct
42 {
43 const char *image;
44 long long time_to_move;
45 int pixels_to_move;
46 } bullet_alien_t;
47
48 static struct
49 {
50 struct bullet_instance bullets[MAX_BULLETS];
51 bool inhibitBulletAlien;
52 } bulletPool;
53
54 static const char bulletSpaceshipImage[BULLET_SPACESHIP_HEIGHT][BULLET_SPACESHIP_WIDTH][NUM_RGBA_CHANNELS] = {
55 {W}, {W}, {W}, {W}};
56 static const char bulletAlienImage[BULLET_ALIEN_NUM_TYPES][BULLET_ALIEN_NUM_FRAMES][BULLET_ALIEN_HEIGHT][BULLET_ALIEN_WIDTH]
57 [NUM_RGBA_CHANNELS] = {
58 /* crackle */
59 /* frame 1 */
60 {{{B, W, B}, {B, W, B}, {W, B, B}, {B, W, B}, {B, B, W}, {B, W, B}, {B, W, B}},
61 /* frame 2 */
62 {{B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}},
63 /* frame 3 */
64 {{B, W, B}, {B, W, B}, {B, B, W}, {B, W, B}, {W, B, B}, {B, W, B}, {B, W, B}},
65 /* frame 4 */
66 {{B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}}},
67 /* plasma */
68 /* frame 1 */
69 {{{B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {W, W, W}, {W, W, W}},
70 /* frame 2 */
71 {{B, W, B}, {B, W, B}, {B, W, B}, {W, W, W}, {B, W, B}, {B, W, B}, {W, W, W}},
72 /* frame 3 */
73 {{B, W, B}, {W, W, W}, {B, W, B}, {B, W, B}, {B, W, B}, {B, W, B}, {W, W, W}},
74 /* frame 4 */
75 {{B, W, B}, {B, W, B}, {B, W, B}, {W, W, W}, {B, W, B}, {B, W, B}, {W, W, W}}},
76 /* coil */
77 /* frame 1 */
78 {{{B, W, W}, {B, W, B}, {W, W, B}, {B, W, B}, {B, W, W}, {B, W, B}, {W, W, B}},
79 /* frame 2 */
80 {{B, W, B}, {W, W, B}, {B, W, B}, {B, W, W}, {B, W, B}, {W, W, B}, {B, W, B}},
81 /* frame 3 */
82 {{W, W, B}, {B, W, B}, {B, W, W}, {B, W, B}, {W, W, B}, {B, W, B}, {B, W, W}},
83 /* frame 4 */
84 {{B, W, B}, {B, W, W}, {B, W, B}, {W, W, B}, {B, W, B}, {B, W, W}, {B, W, B}}}};
85
86 static bullet_alien_t bulletAlien[BULLET_ALIEN_NUM_TYPES] = {
87 {.image = (const char *)&bulletAlienImage[CRACKLE], .time_to_move = 1000 / 60 * NS_PER_MS, .pixels_to_move = 1},
88 {.image = (const char *)&bulletAlienImage[PLASMA], .time_to_move = 2000 / 60 * NS_PER_MS, .pixels_to_move = 1},
89 {.image = (const char *)&bulletAlienImage[COIL], .time_to_move = 1000 / 60 * NS_PER_MS, .pixels_to_move = 1}};
90
91 static bullet_alien_type_t
92 4 getBulletAlienType(void)
93 {
94 bullet_alien_type_t result;
95 4 int r = rand() % 100;
96
97
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 2 times.
4 if (r < 50)
98 {
99 2 result = CRACKLE;
100 }
101
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1 time.
2 else if (r < 80)
102 {
103 1 result = COIL;
104 }
105 else
106 {
107 1 result = PLASMA;
108 }
109
110 4 return result;
111 }
112
113 static void
114 6 setBulletType(bullet_t bullet, bullet_type_t type)
115 {
116
2/3
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 4 times.
6 switch (type)
117 {
118 2 case BULLET_SPACESHIP:
119 2 bullet->sprite.width = BULLET_SPACESHIP_WIDTH;
120 2 bullet->sprite.height = BULLET_SPACESHIP_HEIGHT;
121 2 bullet->sprite.image = (const char *)bulletSpaceshipImage;
122 2 bullet->sprite.pixels_to_move = 1;
123 2 bullet->sprite.max_movement.up = GAME_HEIGHT;
124 2 bullet->sprite.max_movement.down = 0;
125 2 bullet->sprite.time_to_move = 2 * NS_PER_MS;
126 2 break;
127 4 case BULLET_ALIEN:
128 {
129 4 bullet_alien_type_t alien_bullet_type = getBulletAlienType();
130 4 bullet->sprite.width = BULLET_ALIEN_WIDTH;
131 4 bullet->sprite.height = BULLET_ALIEN_HEIGHT;
132 4 bullet->sprite.image_base = bulletAlien[alien_bullet_type].image;
133 4 bullet->sprite.image = bullet->sprite.image_base;
134 4 bullet->sprite.num_frames = BULLET_ALIEN_NUM_FRAMES;
135 4 bullet->sprite.pixels_to_move = bulletAlien[alien_bullet_type].pixels_to_move;
136 4 bullet->sprite.max_movement.up = GAME_HEIGHT;
137 4 bullet->sprite.max_movement.down = 0;
138 4 bullet->sprite.time_to_move = bulletAlien[alien_bullet_type].time_to_move;
139 4 break;
140 }
141 /* GCOVR_EXCL_START */
142 default:
143 assert(!"invalid bullet type");
144 UNREACHABLE();
145 break; /* GCOVR_EXCL_BR_SOURCE */
146 /* GCOVR_EXCL_STOP */
147 }
148
149 6 bullet->type = type;
150 6 }
151
152 void
153 8 bulletCreate(int x, int y, bullet_type_t type)
154 {
155
2/2
✓ Branch 15 → 3 taken 21 times.
✓ Branch 15 → 16 taken 2 times.
23 for (int i = 0; i < MAX_BULLETS; i++)
156 {
157
4/4
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 6 taken 15 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 3 times.
21 if (type == BULLET_SPACESHIP && i != BULLET_SPACESHIP_SLOT)
158 {
159 3 continue;
160 }
161
162
6/6
✓ Branch 6 → 7 taken 15 times.
✓ Branch 6 → 10 taken 3 times.
✓ Branch 7 → 8 taken 10 times.
✓ Branch 7 → 9 taken 5 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 7 times.
18 if (type == BULLET_ALIEN && (i == BULLET_SPACESHIP_SLOT || bulletPool.inhibitBulletAlien))
163 {
164 8 continue;
165 }
166
167
2/2
✓ Branch 10 → 11 taken 6 times.
✓ Branch 10 → 14 taken 4 times.
10 if (!bulletPool.bullets[i].used)
168 {
169 6 struct bullet_instance *inst = &bulletPool.bullets[i];
170
171 6 setBulletType(inst, type);
172 6 inst->sprite.x = x;
173 6 inst->sprite.y = y;
174 6 inst->sprite.layer = LAYER_GAME_OBJECTS;
175 6 inst->sprite.visible = true;
176 6 graphRegisterPrint(&inst->sprite);
177
178 6 inst->used = true;
179
180 6 return;
181 }
182 }
183 }
184
185 void
186 3 bulletDestroy(bullet_t bullet)
187 {
188
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 2 times.
3 if (!bullet)
189 {
190 1 return;
191 }
192
193 2 sprite_t *bullet_sprite = graphGetSprite((base_t *)bullet);
194 2 graphUnregisterPrint(bullet_sprite);
195 2 bullet->used = false;
196 }
197
198 bool
199 3 bulletUsed(bullet_t bullet)
200 {
201
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 bullet && bullet->used;
202 }
203
204 int
205 4 bulletGetType(const bullet_t bullet, bullet_type_t *type)
206 {
207
4/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
4 if (!bullet || !type)
208 {
209 3 return -1;
210 }
211
212 1 *type = bullet->type;
213
214 1 return 0;
215 }
216
217 void
218 3 bulletCallFunctionForEach(void (*fn)(bullet_t))
219 {
220
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 2 times.
3 if (!fn)
221 {
222 assert(!"pointer to fn must not be NULL");
223 1 return;
224 }
225
226
2/2
✓ Branch 8 → 5 taken 8 times.
✓ Branch 8 → 9 taken 2 times.
10 for (int i = 0; i < MAX_BULLETS; i++)
227 {
228
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 7 times.
8 if (bulletPool.bullets[i].used)
229 {
230 1 fn(&bulletPool.bullets[i]);
231 }
232 }
233 }
234
235 void
236 1 bulletAlienInhibit(bool inhibit)
237 {
238 1 bulletPool.inhibitBulletAlien = inhibit;
239 1 }
240
241 bool
242 3 bulletNoneUsed(void)
243 {
244
2/2
✓ Branch 6 → 3 taken 9 times.
✓ Branch 6 → 7 taken 2 times.
11 for (int i = 0; i < MAX_BULLETS; i++)
245 {
246
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 8 times.
9 if (bulletPool.bullets[i].used)
247 {
248 1 return false;
249 }
250 }
251
252 2 return true;
253 }
254
255 bool
256 3 bulletSpaceshipHitBulletAliens(bool (*fn)(const sprite_t *const sprite1, const sprite_t *const sprite2))
257 {
258
2/2
✓ Branch 9 → 3 taken 7 times.
✓ Branch 9 → 10 taken 2 times.
9 for (int i = BULLET_ALIEN_SLOT; i < MAX_BULLETS; i++)
259 {
260
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 8 taken 5 times.
7 if (bulletPool.bullets[i].used)
261 {
262
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 8 taken 1 time.
2 if (fn(&bulletPool.bullets[BULLET_SPACESHIP_SLOT].sprite, &bulletPool.bullets[i].sprite))
263 {
264 1 bulletDestroy(&bulletPool.bullets[i]);
265 1 return true;
266 }
267 }
268 }
269
270 2 return false;
271 }
272
273 #ifdef UNIT_TESTING
274 void
275 19 helperUT_bulletInitPool(void)
276 {
277 19 memset(bulletPool.bullets, 0, sizeof(bulletPool.bullets));
278 19 }
279
280 void
281 1 helperUT_bulletSetUsed(bullet_t bullet, bool used)
282 {
283 1 bullet->used = used;
284 1 }
285
286 bullet_t
287 10 helperUT_bulletInjectInPool(int index, bullet_type_t type)
288 {
289 10 bullet_t bullet = &bulletPool.bullets[index];
290 10 memset(bullet, 0, sizeof(*bullet));
291
292 10 bulletPool.bullets[index].used = true;
293 10 bulletPool.bullets[index].type = type;
294
295 10 return bullet;
296 }
297 #endif /* UNIT_TESTING */
298