commit 410b751ad9501886166e35a73a400a597286fce2
parent b6efce7dc80d44096c058ba013768601f57b5953
Author: brookjeynes <me@brookjeynes.dev>
Date: Sat, 1 Aug 2026 21:04:37 +1000
feat: add upgrades menu
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
2 files changed, 104 insertions(+), 2 deletions(-)
diff --git a/src/main.zig b/src/main.zig
@@ -8,6 +8,9 @@ const Player = @import("./player.zig");
const GameState = @import("./state.zig").GameState;
const Timer = @import("./timer.zig");
const Button = @import("./ui.zig").Button;
+const UpgradeId = @import("./upgrade.zig").UpgradeId;
+const upgrade_meta = @import("./upgrade.zig").meta;
+const upgrade_fields = @typeInfo(UpgradeId).@"enum".fields;
const start_button = Button.new(
.{ .x = 100, .y = 70 },
@@ -53,7 +56,104 @@ pub fn main() !void {
game_state.reset();
}
},
- .upgrade => {},
+ .upgrade => {
+ raylib.BeginDrawing();
+ defer raylib.EndDrawing();
+
+ raylib.ClearBackground(raylib.RAYWHITE);
+
+ raylib.DrawText("upgrades", 20, 40, 30, raylib.BLACK);
+ raylib.DrawText(
+ raylib.TextFormat("points: %d", game_state.enemies_killed),
+ 20,
+ 90,
+ 20,
+ raylib.BLACK,
+ );
+ raylib.DrawText(
+ raylib.TextFormat("last run: %0.1fs", game_state.time),
+ 20,
+ 120,
+ 18,
+ raylib.GRAY,
+ );
+
+ const row_size: raylib.Vector2 = .{ .x = 700, .y = 60 };
+ const row_spacing: f32 = 80;
+ var row_position: raylib.Vector2 = .{ .x = 50, .y = 200 };
+
+ inline for (upgrade_fields) |field| {
+ const id: UpgradeId = @enumFromInt(field.value);
+ const meta = upgrade_meta[@intFromEnum(id)];
+
+ const level = game_state.upgrades.level(id);
+ const cost = game_state.upgrades.cost(id);
+ const buyable = !game_state.upgrades.maxed(id) and game_state.enemies_killed >= cost;
+
+ const row = Button.new(row_size, row_position);
+ raylib.DrawRectangleV(
+ row.position,
+ row.size,
+ if (buyable) raylib.BLACK else raylib.GRAY,
+ );
+ raylib.DrawText(
+ meta.name.ptr,
+ @intFromFloat(row.position.x + 20),
+ @intFromFloat(row.position.y + row.size.y / 2 - 10),
+ 20,
+ raylib.WHITE,
+ );
+
+ if (game_state.upgrades.maxed(id)) {
+ raylib.DrawText(
+ "MAX",
+ @intFromFloat(row.position.x + row.size.x - 120),
+ @intFromFloat(row.position.y + row.size.y / 2 - 10),
+ 18,
+ raylib.WHITE,
+ );
+ } else {
+ raylib.DrawText(
+ raylib.TextFormat("lvl %d/%d cost %d", level, meta.max_level, cost),
+ @intFromFloat(row.position.x + row.size.x - 300),
+ @intFromFloat(row.position.y + row.size.y / 2 - 10),
+ 18,
+ raylib.WHITE,
+ );
+ }
+
+ if (buyable and row.clicked(raylib.GetMousePosition())) {
+ game_state.enemies_killed -= cost;
+ _ = game_state.upgrades.upgrade(id);
+ }
+
+ row_position.y += row_spacing;
+ }
+
+ const continue_button = Button.new(
+ .{ .x = 300, .y = 60 },
+ .{
+ .x = (constants.screen_width / 2) - 150,
+ .y = row_position.y + 60,
+ },
+ );
+ raylib.DrawRectangleV(
+ continue_button.position,
+ continue_button.size,
+ raylib.BLACK,
+ );
+ raylib.DrawText(
+ "continue",
+ @intFromFloat(continue_button.position.x + continue_button.size.x / 2 - 30),
+ @intFromFloat(continue_button.position.y + continue_button.size.y / 2 - 10),
+ 20,
+ raylib.WHITE,
+ );
+
+ if (continue_button.clicked(raylib.GetMousePosition())) {
+ game_state.reset();
+ }
+ },
.gameplay => {
// -- tick
for (0..game_state.enemy_count) |idx| {
diff --git a/src/state.zig b/src/state.zig
@@ -23,6 +23,7 @@ pub const GameState = struct {
player: Player,
upgrades: Upgrades,
+ enemies_killed: u16 = 0,
enemies: [constants.max_enemies]Entity,
enemy_count: u8,
@@ -96,7 +97,7 @@ pub const GameState = struct {
self.player.tick();
if (self.player.health <= 0) {
- self.screen = .title;
+ self.screen = .upgrade;
}
for (0..self.enemy_count) |i| {
@@ -165,6 +166,7 @@ pub const GameState = struct {
if (is_collision) {
self.player.bullet_pool.despawn(player_bullet_idx);
self.despawn_enemy(enemy_idx);
+ self.enemies_killed += 1;
continue :lbl;
}
}