1.44mb-gamejam

submission for the 1.44mb game jam - https://2pgarcade.com/contest-144mb.html
git clone git://brookjeynes.dev/bjeynes/1.44mb-gamejam.git
Log | Files | Refs

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 }