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

commit 5ec394929f5acb6a022d18a61d772b6f8723cb99
parent b99458bcc2e81750dc5242157ed953aeda2d8280
Author: brookjeynes <me@brookjeynes.dev>
Date:   Fri, 31 Jul 2026 14:42:47 +1000

refactor: move things around

Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
Asrc/constants.zig | 5+++++
Msrc/enemy_types/grunt.zig | 28+++++++++++++++-------------
Msrc/entity.zig | 19+++++++++++++++++++
Msrc/main.zig | 301+++++++++++++------------------------------------------------------------------
Msrc/player.zig | 4++++
Asrc/state.zig | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui.zig | 28++++++++++++++++++++++++++++
7 files changed, 343 insertions(+), 267 deletions(-)

diff --git a/src/constants.zig b/src/constants.zig @@ -0,0 +1,5 @@ +pub const max_enemies: u8 = 255; +pub const screen_width: u16 = 800; +pub const screen_height: u16 = 1000; +pub const screen_title: [*]const u8 = "1.44mb game jam"; +pub const fps: u8 = 60; diff --git a/src/enemy_types/grunt.zig b/src/enemy_types/grunt.zig @@ -15,20 +15,22 @@ position: raylib.Vector2 = .{}, bullet_pool: bullet.Pool, +pub fn tick(self: *Grunt) void { + if (self.bullet_pool.tick()) self.shoot(); +} + pub fn shoot(self: *Grunt) void { - if (self.bullet_pool.tick()) { - self.bullet_pool.spawn( - .{ - .x = self.position.x + (size.x / 2), - .y = self.position.y + (size.y / 2), - }, - .{ - .x = self.direction.x * self.bullet_speed, - .y = self.direction.y * self.bullet_speed, - }, - raylib.RED, - ); - } + self.bullet_pool.spawn( + .{ + .x = self.position.x + (size.x / 2), + .y = self.position.y + (size.y / 2), + }, + .{ + .x = self.direction.x * self.bullet_speed, + .y = self.direction.y * self.bullet_speed, + }, + raylib.RED, + ); } pub fn handleMovement(self: *Grunt, target: raylib.Vector2) void { diff --git a/src/entity.zig b/src/entity.zig @@ -1,7 +1,26 @@ const Grunt = @import("./enemy_types/grunt.zig"); +const raylib = @import("raylib"); pub const EntityType = enum { grunt }; pub const Entity = union(EntityType) { grunt: Grunt, + + pub fn render(entity: *Entity) void { + switch (entity.*) { + .grunt => |*grunt| grunt.render(), + } + } + + pub fn move(entity: *Entity, target_position: raylib.Vector2) void { + switch (entity.*) { + .grunt => |*grunt| grunt.handleMovement(target_position), + } + } + + pub fn tick(entity: *Entity) void { + switch (entity.*) { + .grunt => |*grunt| grunt.tick(), + } + } }; diff --git a/src/main.zig b/src/main.zig @@ -3,308 +3,101 @@ const raymath = @import("raymath"); const Bullet = @import("./bullet.zig"); const Grunt = @import("./enemy_types/grunt.zig"); -const Entity = @import("./entity.zig").Entity; -const EntityType = @import("./entity.zig").EntityType; const Player = @import("./player.zig"); +const GameState = @import("./state.zig").GameState; const Timer = @import("./timer.zig"); - -const max_enemies: u8 = 255; - -const Screen = enum(u3) { - title, - gameplay, - upgrade, -}; - -const GameState = struct { - screen: struct { - width: u16 = 800, - height: u16 = 1000, - current: Screen = .title, - title: [*]const u8 = "1.44mb game jam", +const Button = @import("./ui.zig").Button; +const constants = @import("./constants.zig"); + +const start_button = Button.new( + .{ .x = 100, .y = 70 }, + .{ + .x = (constants.screen_width / 2) - 50, + .y = (constants.screen_height / 2) - 35, }, - fps: u8 = 60, - time: f32 = 0, - - enemies: [max_enemies]Entity, - enemy_count: u8 = 0, - - pub fn spawn_enemy(self: *GameState, enemy: Entity) void { - if (self.enemy_count == self.enemies.len) return; - - self.enemies[self.enemy_count] = enemy; - self.enemy_count += 1; - } - - pub fn despawn_enemy(self: *GameState, enemy_idx: u8) void { - for (enemy_idx..self.enemy_count) |i| { - self.enemies[i] = self.enemies[i + 1]; - } - - if (self.enemy_count > 0) self.enemy_count -= 1; - } - - pub fn reset(self: *GameState) void { - self.screen.current = .gameplay; - self.time = 0; - self.enemy_count = 0; - } - - pub fn difficulty(self: GameState) f32 { - return @min(5.0, 1.0 + self.time * 0.02); - } -}; +); pub fn main() !void { - var game_state: GameState = .{ - .screen = .{}, - .enemies = undefined, - }; + var game_state: GameState = .init(); raylib.InitWindow( - game_state.screen.width, - game_state.screen.height, - game_state.screen.title, + constants.screen_width, + constants.screen_height, + constants.screen_title, ); defer raylib.CloseWindow(); - raylib.SetTargetFPS(game_state.fps); - - var player: Player = .{ - .bullet_pool = .{ - .bullets = undefined, - .fire_timer = .{ - .duration = Player.bullet_spawn_cooldown, - }, - }, - .position = .{ - .x = game_state.screen.width / 2, - .y = game_state.screen.height / 2, - }, - }; - - var enemy_spawn_time: Timer = .{ .duration = 0.7 }; - - const start_button_size: raylib.Vector2 = .{ .x = 100, .y = 70 }; - const start_button_position: raylib.Vector2 = .{ - .x = (game_state.screen.width / 2) - (start_button_size.x / 2), - .y = (game_state.screen.height / 2) - (start_button_size.y / 2), - }; - const start_button_bounds: raylib.Rectangle = .{ - .x = start_button_position.x, - .y = start_button_position.y, - .width = start_button_size.x, - .height = start_button_size.y, - }; - + raylib.SetTargetFPS(constants.fps); while (!raylib.WindowShouldClose()) { - switch (game_state.screen.current) { + switch (game_state.screen) { .title => { - const mouse_point = raylib.GetMousePosition(); - raylib.BeginDrawing(); defer raylib.EndDrawing(); raylib.ClearBackground(raylib.RAYWHITE); raylib.DrawRectangleV( - start_button_position, - start_button_size, + start_button.position, + start_button.size, raylib.BLACK, ); raylib.DrawText( "start", - @intFromFloat(start_button_position.x + (start_button_size.x / 2)), - @intFromFloat(start_button_position.y + (start_button_size.y / 2)), + @intFromFloat(start_button.position.x + (start_button.size.x / 2)), + @intFromFloat(start_button.position.y + (start_button.size.y / 2)), 18, raylib.WHITE, ); - if (raylib.CheckCollisionPointRec(mouse_point, start_button_bounds)) { - if (raylib.IsMouseButtonReleased(raylib.MOUSE_BUTTON_LEFT)) { - game_state.reset(); - enemy_spawn_time = .{ .duration = 0.7 }; - player.health = Player.max_health; - } + if (start_button.clicked(raylib.GetMousePosition())) { + game_state.reset(); } }, .upgrade => {}, .gameplay => { - const delta = raylib.GetFrameTime(); - const difficulty = game_state.difficulty(); + // -- tick - game_state.time += delta; - - enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty); - - player.tick(); - player.handleMovement(); - - for (0..game_state.enemy_count) |i| { - const entity = &game_state.enemies[i]; - - switch (entity.*) { - .grunt => |*e| { - e.handleMovement(player.position); - e.shoot(); - }, - } - } - - if (enemy_spawn_time.tick(delta)) { - game_state.spawn_enemy(spawnEnemy( - .{ - .x = game_state.screen.width, - .y = game_state.screen.height, - }, - player, - difficulty, - .grunt, - Grunt.size, - )); + for (0..game_state.enemy_count) |idx| { + game_state.enemies[idx].move(game_state.player.position); } + game_state.player.handleMovement(); + game_state.tick(); + // -- render raylib.BeginDrawing(); defer raylib.EndDrawing(); raylib.ClearBackground(raylib.RAYWHITE); - player.render(); - - var enemy_idx: u8 = 0; - while (enemy_idx < game_state.enemy_count) { - const entity = &game_state.enemies[enemy_idx]; - - switch (entity.*) { - .grunt => |*grunt| { - var enemy_bullet_idx = grunt.bullet_pool.count; - while (enemy_bullet_idx > 0) { - enemy_bullet_idx -= 1; - - const bullet = grunt.bullet_pool.bullets[enemy_bullet_idx]; - const is_collision = raylib.CheckCollisionCircleRec( - .{ .x = bullet.position.x, .y = bullet.position.y }, - Bullet.radius, - .{ - .x = player.position.x, - .y = player.position.y, - .width = player.size.x, - .height = player.size.y, - }, - ); - - if (is_collision) { - grunt.bullet_pool.despawn(enemy_bullet_idx); - player.health -= Grunt.bullet_damage; - if (player.health <= 0) { - game_state.screen.current = .title; - } - } - } - - var player_bullet_idx = player.bullet_pool.count; - while (player_bullet_idx > 0) { - player_bullet_idx -= 1; - - const bullet = player.bullet_pool.bullets[player_bullet_idx]; - const is_collision = raylib.CheckCollisionCircleRec( - .{ - .x = bullet.position.x, - .y = bullet.position.y, - }, - Bullet.radius, - .{ - .x = grunt.position.x, - .y = grunt.position.y, - .width = Grunt.size.x, - .height = Grunt.size.y, - }, - ); - - if (is_collision) { - player.bullet_pool.despawn(player_bullet_idx); - - game_state.despawn_enemy(enemy_idx); - break; - } - } - }, - } - - enemy_idx += 1; - } - + // Render entities + game_state.player.render(); for (0..game_state.enemy_count) |idx| { - const entity = &game_state.enemies[idx]; - switch (entity.*) { - .grunt => |*grunt| grunt.render(), - } + game_state.enemies[idx].render(); } - const health_percent: f32 = @max(0, @min(1, player.health / Player.max_health)); + // Health overlay + const health_percent: f32 = @max( + 0, + @min(1, game_state.player.health / Player.max_health), + ); const damage_percent = 1 - health_percent; raylib.DrawRectangle( 0, 0, - game_state.screen.width, - game_state.screen.height, + constants.screen_width, + constants.screen_height, raylib.ColorAlpha(raylib.RED, damage_percent), ); - raylib.DrawText(raylib.TextFormat("score: %0.0f", game_state.time), 10, 10, 18, raylib.BLACK); + + // Score + raylib.DrawText( + raylib.TextFormat("score: %0.0f", game_state.time), + 10, + 10, + 18, + raylib.BLACK, + ); }, } } } - -fn spawnEnemy( - screen: raylib.Vector2, - player: Player, - difficulty: f32, - enemy_type: EntityType, - enemy_size: raylib.Vector2, -) Entity { - const min_dist: f32 = 200.0; - const player_cx = player.position.x + player.size.x / 2; - const player_cy = player.position.y + player.size.y / 2; - - const spawn_position: raylib.Vector2 = lbl: { - while (true) { - const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); - const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); - const spawn_x = (rnd_x / 100.0) * (@as(f32, @floatCast(screen.x)) - enemy_size.x); - const spawn_y = (rnd_y / 100.0) * (@as(f32, @floatCast(screen.y)) - enemy_size.y); - - const center_x = spawn_x + enemy_size.x / 2; - const center_y = spawn_y + enemy_size.y / 2; - - const distance_between_player = raymath.Vector2Distance( - .{ .x = center_x, .y = center_y }, - .{ .x = player_cx, .y = player_cy }, - ); - - if (distance_between_player >= min_dist) { - break :lbl .{ .x = spawn_x, .y = spawn_y }; - } - } - }; - - return switch (enemy_type) { - .grunt => lbl: { - var grunt = Grunt{ - .bullet_pool = .{ - .bullets = undefined, - .fire_timer = .{ - .duration = 2.0, - }, - }, - .position = spawn_position, - }; - - grunt.movement_speed = @min(160.0, 40.0 * difficulty); - grunt.bullet_speed = @min(300.0, 100.0 * difficulty); - grunt.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty); - - break :lbl .{ .grunt = grunt }; - }, - }; -} diff --git a/src/player.zig b/src/player.zig @@ -45,6 +45,10 @@ pub fn tick(self: *Player) void { } } +pub fn handleDamage(self: *Player, damage: f32) void { + self.health -= damage; +} + pub fn handleMovement(self: *Player) void { const delta: f32 = @min(raylib.GetFrameTime(), max_delta); diff --git a/src/state.zig b/src/state.zig @@ -0,0 +1,225 @@ +const raylib = @import("raylib"); +const raymath = @import("raymath"); + +const Bullet = @import("./bullet.zig"); +const constants = @import("./constants.zig"); +const Grunt = @import("./enemy_types/grunt.zig"); +const Entity = @import("./entity.zig").Entity; +const EntityType = @import("./entity.zig").EntityType; +const Player = @import("./player.zig"); +const Timer = @import("./timer.zig"); + +pub const Screen = enum(u3) { + title, + gameplay, + upgrade, +}; + +pub const GameState = struct { + screen: Screen, + time: f32, + + player: Player, + + enemies: [constants.max_enemies]Entity, + enemy_count: u8, + enemy_spawn_time: Timer = .{ .duration = 0.7 }, + + pub fn init() GameState { + return .{ + .screen = .title, + .time = 0, + + .player = .{ + .bullet_pool = .{ + .bullets = undefined, + .fire_timer = .{ + .duration = Player.bullet_spawn_cooldown, + }, + }, + .position = .{ + .x = constants.screen_width / 2, + .y = constants.screen_height / 2, + }, + }, + + .enemies = undefined, + .enemy_count = 0, + }; + } + + pub fn spawn_enemy(self: *GameState, enemy: Entity) void { + if (self.enemy_count == self.enemies.len) return; + + self.enemies[self.enemy_count] = enemy; + self.enemy_count += 1; + } + + pub fn despawn_enemy(self: *GameState, enemy_idx: u8) void { + for (enemy_idx..self.enemy_count) |i| { + self.enemies[i] = self.enemies[i + 1]; + } + + if (self.enemy_count > 0) self.enemy_count -= 1; + } + + pub fn reset(self: *GameState) void { + self.screen = .gameplay; + self.time = 0; + + self.enemy_count = 0; + self.enemy_spawn_time = .{ .duration = 0.7 }; + + self.player.health = Player.max_health; + self.player.position = .{ + .x = constants.screen_width / 2, + .y = constants.screen_height / 2, + }; + } + + pub fn game_difficulty(self: GameState) f32 { + return @min(5.0, 1.0 + self.time * 0.02); + } + + pub fn tick(self: *GameState) void { + const difficulty = self.game_difficulty(); + const delta = raylib.GetFrameTime(); + self.time += delta; + + self.enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty); + + self.player.tick(); + if (self.player.health <= 0) { + self.screen = .title; + } + + for (0..self.enemy_count) |i| { + self.enemies[i].tick(); + } + + if (self.enemy_spawn_time.tick(delta)) { + const grunt = createEnemy( + .{ .x = constants.screen_width, .y = constants.screen_height }, + self.player, + difficulty, + .grunt, + Grunt.size, + ); + + self.spawn_enemy(grunt); + } + + var enemy_idx: u8 = 0; + while (enemy_idx < self.enemy_count) { + const entity = &self.enemies[enemy_idx]; + + switch (entity.*) { + .grunt => |*grunt| { + var enemy_bullet_idx = grunt.bullet_pool.count; + while (enemy_bullet_idx > 0) { + enemy_bullet_idx -= 1; + + const bullet = grunt.bullet_pool.bullets[enemy_bullet_idx]; + const is_collision = raylib.CheckCollisionCircleRec( + .{ .x = bullet.position.x, .y = bullet.position.y }, + Bullet.radius, + .{ + .x = self.player.position.x, + .y = self.player.position.y, + .width = self.player.size.x, + .height = self.player.size.y, + }, + ); + + if (is_collision) { + grunt.bullet_pool.despawn(enemy_bullet_idx); + self.player.handleDamage(Grunt.bullet_damage); + } + } + + var player_bullet_idx = self.player.bullet_pool.count; + while (player_bullet_idx > 0) { + player_bullet_idx -= 1; + + const bullet = self.player.bullet_pool.bullets[player_bullet_idx]; + const is_collision = raylib.CheckCollisionCircleRec( + .{ + .x = bullet.position.x, + .y = bullet.position.y, + }, + Bullet.radius, + .{ + .x = grunt.position.x, + .y = grunt.position.y, + .width = Grunt.size.x, + .height = Grunt.size.y, + }, + ); + + if (is_collision) { + self.player.bullet_pool.despawn(player_bullet_idx); + + self.despawn_enemy(enemy_idx); + enemy_idx -= 1; + } + } + }, + } + + enemy_idx += 1; + } + } +}; + +fn createEnemy( + screen: raylib.Vector2, + player: Player, + difficulty: f32, + enemy_type: EntityType, + enemy_size: raylib.Vector2, +) Entity { + const min_dist: f32 = 200.0; + const player_cx = player.position.x + player.size.x / 2; + const player_cy = player.position.y + player.size.y / 2; + + const spawn_position: raylib.Vector2 = lbl: { + while (true) { + const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); + const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100))); + const spawn_x = (rnd_x / 100.0) * (@as(f32, @floatCast(screen.x)) - enemy_size.x); + const spawn_y = (rnd_y / 100.0) * (@as(f32, @floatCast(screen.y)) - enemy_size.y); + + const center_x = spawn_x + enemy_size.x / 2; + const center_y = spawn_y + enemy_size.y / 2; + + const distance_between_player = raymath.Vector2Distance( + .{ .x = center_x, .y = center_y }, + .{ .x = player_cx, .y = player_cy }, + ); + + if (distance_between_player >= min_dist) { + break :lbl .{ .x = spawn_x, .y = spawn_y }; + } + } + }; + + return switch (enemy_type) { + .grunt => lbl: { + var grunt = Grunt{ + .bullet_pool = .{ + .bullets = undefined, + .fire_timer = .{ + .duration = 2.0, + }, + }, + .position = spawn_position, + }; + + grunt.movement_speed = @min(160.0, 40.0 * difficulty); + grunt.bullet_speed = @min(300.0, 100.0 * difficulty); + grunt.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty); + + break :lbl .{ .grunt = grunt }; + }, + }; +} diff --git a/src/ui.zig b/src/ui.zig @@ -0,0 +1,28 @@ +const raylib = @import("raylib"); + +pub const Button = struct { + size: raylib.Vector2, + position: raylib.Vector2, + bounds: raylib.Rectangle, + + pub fn new(size: raylib.Vector2, position: raylib.Vector2) Button { + return .{ + .size = size, + .position = position, + .bounds = .{ + .x = position.x, + .y = position.y, + .width = size.x, + .height = size.y, + }, + }; + } + + pub fn clicked(self: Button, target: raylib.Vector2) bool { + if (raylib.CheckCollisionPointRec(target, self.bounds)) { + return raylib.IsMouseButtonReleased(raylib.MOUSE_BUTTON_LEFT); + } + + return false; + } +};