state.zig (12910B)
1 const raylib = @import("raylib"); 2 const raymath = @import("raymath"); 3 4 const Bullet = @import("./bullet.zig"); 5 const constants = @import("./constants.zig"); 6 const Blorb = @import("./enemy_types/blorb.zig"); 7 const Grunt = @import("./enemy_types/grunt.zig"); 8 const Entity = @import("./entity.zig").Entity; 9 const EntityType = @import("./entity.zig").EntityType; 10 const Player = @import("./player.zig"); 11 const Timer = @import("./timer.zig"); 12 const UpgradeId = @import("./upgrade.zig").UpgradeId; 13 const Upgrades = @import("./upgrade.zig").Upgrades; 14 15 pub const Screen = enum(u3) { 16 title, 17 gameplay, 18 upgrade, 19 }; 20 21 pub const GameState = struct { 22 screen: Screen, 23 time: f32, 24 25 player: Player, 26 upgrades: Upgrades, 27 enemies_killed: u16 = 0, 28 29 enemies: [constants.max_enemies]Entity, 30 enemy_count: u8, 31 grunt_spawn_time: Timer = .{ .duration = constants.grunt_spawn_interval }, 32 blorb_spawn_time: Timer = .{ .duration = constants.blorb_spawn_interval }, 33 34 orphaned_bullet_pool: Bullet.Pool, 35 36 pub fn init() GameState { 37 return .{ 38 .screen = .title, 39 .time = 0, 40 41 .orphaned_bullet_pool = .{ 42 .bullets = undefined, 43 .fire_timer = .{ 44 .duration = 0, 45 }, 46 }, 47 48 .player = .{ 49 .bullet_pool = .{ 50 .bullets = undefined, 51 .fire_timer = .{ 52 .duration = Player.bullet_spawn_cooldown, 53 }, 54 }, 55 .position = .{ 56 .x = constants.screen_width / 2, 57 .y = constants.screen_height / 2, 58 }, 59 }, 60 .upgrades = .{ .levels = [_]u8{0} ** UpgradeId.count }, 61 62 .enemies = undefined, 63 .enemies_killed = 0, 64 .enemy_count = 0, 65 }; 66 } 67 68 pub fn spawn_enemy(self: *GameState, enemy: Entity) void { 69 if (self.enemy_count == self.enemies.len) return; 70 71 self.enemies[self.enemy_count] = enemy; 72 self.enemy_count += 1; 73 } 74 75 pub fn despawn_enemy(self: *GameState, enemy_idx: u8) void { 76 for (enemy_idx..self.enemy_count - 1) |i| { 77 self.enemies[i] = self.enemies[i + 1]; 78 } 79 80 if (self.enemy_count > 0) self.enemy_count -= 1; 81 } 82 83 pub fn reset(self: *GameState) void { 84 self.screen = .gameplay; 85 self.time = 0; 86 87 self.enemy_count = 0; 88 self.enemies_killed = 0; 89 90 self.orphaned_bullet_pool.count = 0; 91 92 self.grunt_spawn_time = .{ .duration = constants.grunt_spawn_interval }; 93 self.blorb_spawn_time = .{ .duration = constants.blorb_spawn_interval }; 94 95 self.upgrades.apply(&self.player); 96 self.player.health = self.player.max_health; 97 self.player.position = .{ 98 .x = constants.screen_width / 2, 99 .y = constants.screen_height / 2, 100 }; 101 } 102 103 pub fn game_difficulty(self: GameState) f32 { 104 return @min(5.0, 1.0 + self.time * 0.02); 105 } 106 107 pub fn tick(self: *GameState) void { 108 const difficulty = self.game_difficulty(); 109 const delta = raylib.GetFrameTime(); 110 self.time += delta; 111 112 self.grunt_spawn_time.duration = @max(0.15, constants.grunt_spawn_interval / difficulty); 113 self.blorb_spawn_time.duration = @max(0.15, constants.blorb_spawn_interval / difficulty); 114 115 self.player.tick(); 116 if (self.player.health <= 0) { 117 self.screen = .upgrade; 118 } 119 120 for (0..self.enemy_count) |i| { 121 self.enemies[i].tick(); 122 } 123 124 if (self.grunt_spawn_time.tick(delta)) { 125 const grunt = createEnemy( 126 .{ .x = constants.screen_width, .y = constants.screen_height }, 127 self.player, 128 difficulty, 129 .grunt, 130 Grunt.size, 131 ); 132 133 self.spawn_enemy(grunt); 134 } 135 136 if (self.time > constants.blorb_spawn_after) { 137 if (self.blorb_spawn_time.tick(delta)) { 138 const blorb = createEnemy( 139 .{ .x = constants.screen_width, .y = constants.screen_height }, 140 self.player, 141 difficulty, 142 .blorb, 143 Blorb.size, 144 ); 145 146 self.spawn_enemy(blorb); 147 } 148 } 149 150 var enemy_idx: u8 = 0; 151 lbl: while (enemy_idx < self.enemy_count) { 152 const entity = &self.enemies[enemy_idx]; 153 154 switch (entity.*) { 155 .grunt => |*grunt| { 156 var enemy_bullet_idx = grunt.bullet_pool.count; 157 while (enemy_bullet_idx > 0) { 158 enemy_bullet_idx -= 1; 159 160 const bullet = grunt.bullet_pool.bullets[enemy_bullet_idx]; 161 const is_collision = raylib.CheckCollisionCircleRec( 162 .{ .x = bullet.position.x, .y = bullet.position.y }, 163 Bullet.radius, 164 .{ 165 .x = self.player.position.x, 166 .y = self.player.position.y, 167 .width = self.player.size.x, 168 .height = self.player.size.y, 169 }, 170 ); 171 172 if (is_collision) { 173 grunt.bullet_pool.despawn(enemy_bullet_idx); 174 self.player.handleDamage(bullet.damage); 175 } 176 } 177 178 var player_bullet_idx = self.player.bullet_pool.count; 179 while (player_bullet_idx > 0) { 180 player_bullet_idx -= 1; 181 182 const bullet = self.player.bullet_pool.bullets[player_bullet_idx]; 183 const is_collision = raylib.CheckCollisionCircleRec( 184 .{ 185 .x = bullet.position.x, 186 .y = bullet.position.y, 187 }, 188 Bullet.radius, 189 .{ 190 .x = grunt.position.x, 191 .y = grunt.position.y, 192 .width = Grunt.size.x, 193 .height = Grunt.size.y, 194 }, 195 ); 196 197 if (is_collision) { 198 self.player.bullet_pool.despawn(player_bullet_idx); 199 200 if (grunt.handleDamage(constants.player_bullet_damage)) { 201 self.orphaned_bullet_pool.transferFrom(&grunt.bullet_pool); 202 self.despawn_enemy(enemy_idx); 203 self.enemies_killed += 1; 204 continue :lbl; 205 } 206 } 207 } 208 }, 209 .blorb => |*blorb| { 210 var enemy_bullet_idx = blorb.bullet_pool.count; 211 while (enemy_bullet_idx > 0) { 212 enemy_bullet_idx -= 1; 213 214 const bullet = blorb.bullet_pool.bullets[enemy_bullet_idx]; 215 const is_collision = raylib.CheckCollisionCircleRec( 216 .{ .x = bullet.position.x, .y = bullet.position.y }, 217 Bullet.radius, 218 .{ 219 .x = self.player.position.x, 220 .y = self.player.position.y, 221 .width = self.player.size.x, 222 .height = self.player.size.y, 223 }, 224 ); 225 226 if (is_collision) { 227 blorb.bullet_pool.despawn(enemy_bullet_idx); 228 self.player.handleDamage(bullet.damage); 229 } 230 } 231 232 var player_bullet_idx = self.player.bullet_pool.count; 233 while (player_bullet_idx > 0) { 234 player_bullet_idx -= 1; 235 236 const bullet = self.player.bullet_pool.bullets[player_bullet_idx]; 237 const is_collision = raylib.CheckCollisionCircleRec( 238 .{ 239 .x = bullet.position.x, 240 .y = bullet.position.y, 241 }, 242 Bullet.radius, 243 .{ 244 .x = blorb.position.x, 245 .y = blorb.position.y, 246 .width = Blorb.size.x, 247 .height = Blorb.size.y, 248 }, 249 ); 250 251 if (is_collision) { 252 self.player.bullet_pool.despawn(player_bullet_idx); 253 254 if (blorb.handleDamage(constants.player_bullet_damage)) { 255 self.orphaned_bullet_pool.transferFrom(&blorb.bullet_pool); 256 self.despawn_enemy(enemy_idx); 257 self.enemies_killed += 1; 258 continue :lbl; 259 } 260 } 261 } 262 }, 263 } 264 265 enemy_idx += 1; 266 } 267 268 var enemy_bullet_idx = self.orphaned_bullet_pool.count; 269 while (enemy_bullet_idx > 0) { 270 enemy_bullet_idx -= 1; 271 272 const bullet = self.orphaned_bullet_pool.bullets[enemy_bullet_idx]; 273 const is_collision = raylib.CheckCollisionCircleRec( 274 .{ .x = bullet.position.x, .y = bullet.position.y }, 275 Bullet.radius, 276 .{ 277 .x = self.player.position.x, 278 .y = self.player.position.y, 279 .width = self.player.size.x, 280 .height = self.player.size.y, 281 }, 282 ); 283 284 if (is_collision) { 285 self.orphaned_bullet_pool.despawn(enemy_bullet_idx); 286 self.player.handleDamage(bullet.damage); 287 } 288 } 289 } 290 }; 291 292 fn createEnemy( 293 screen: raylib.Vector2, 294 player: Player, 295 difficulty: f32, 296 enemy_type: EntityType, 297 enemy_size: raylib.Vector2, 298 ) Entity { 299 const min_dist: f32 = 200.0; 300 const player_cx = player.position.x + player.size.x / 2; 301 const player_cy = player.position.y + player.size.y / 2; 302 303 const spawn_position: raylib.Vector2 = lbl: { 304 while (true) { 305 const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); 306 const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); 307 const spawn_x = (rnd_x / 100.0) * (@as(f32, @floatCast(screen.x)) - enemy_size.x); 308 const spawn_y = (rnd_y / 100.0) * (@as(f32, @floatCast(screen.y)) - enemy_size.y); 309 310 const center_x = spawn_x + enemy_size.x / 2; 311 const center_y = spawn_y + enemy_size.y / 2; 312 313 const distance_between_player = raymath.Vector2Distance( 314 .{ .x = center_x, .y = center_y }, 315 .{ .x = player_cx, .y = player_cy }, 316 ); 317 318 if (distance_between_player >= min_dist) { 319 break :lbl .{ .x = spawn_x, .y = spawn_y }; 320 } 321 } 322 }; 323 324 return switch (enemy_type) { 325 .grunt => lbl: { 326 var grunt = Grunt{ 327 .bullet_pool = .{ 328 .bullets = undefined, 329 .fire_timer = .{ 330 .duration = 2.0, 331 }, 332 }, 333 .position = spawn_position, 334 }; 335 336 grunt.movement_speed = @min(160.0, 40.0 * difficulty); 337 grunt.bullet_speed = @min(300.0, 100.0 * difficulty); 338 grunt.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty); 339 340 break :lbl .{ .grunt = grunt }; 341 }, 342 .blorb => lbl: { 343 var blorb = Blorb{ 344 .bullet_pool = .{ 345 .bullets = undefined, 346 .fire_timer = .{ 347 .duration = 4.0, 348 }, 349 }, 350 .position = spawn_position, 351 }; 352 353 blorb.movement_speed = @min(160.0, 40.0 * difficulty); 354 blorb.bullet_speed = @min(300.0, 100.0 * difficulty); 355 blorb.bullet_pool.fire_timer.duration = @max(0.3, 4.0 / difficulty); 356 blorb.setRandomDirection(); 357 358 break :lbl .{ .blorb = blorb }; 359 }, 360 }; 361 }