summary refs log tree commit diff
path: root/src/misc.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/misc.zig')
-rw-r--r--src/misc.zig66
1 files changed, 58 insertions, 8 deletions
diff --git a/src/misc.zig b/src/misc.zig
index 2e54416..e30c154 100644
--- a/src/misc.zig
+++ b/src/misc.zig
@@ -35,7 +35,9 @@ const data_dir = @import("build_options").data_dir ++ [_]u8{ sep };
 const endsWith = std.mem.endsWith;
 const eql = std.mem.eql;
 const free = std.c.free;
+const ini = @import("ini");
 const join = std.fs.path.joinZ;
+const hash = std.crypto.hash.Blake3.hash;
 const maxInt = std.math.maxInt;
 const parseFloat = std.fmt.parseFloat;
 const parseInt = std.fmt.parseInt;
@@ -45,14 +47,12 @@ const startsWith = std.mem.startsWith;
 const std = @import("std");
 const tokenize = std.mem.tokenize;
 
-// Don't judge me, take care of me!
-const max_size = maxInt(usize);
-
 /// Read given file to heap, allocated by C allocator.
 fn readFile(dir: Dir, comptime fmt: []const u8, args: anytype) ![]const u8 {
     const filename = try allocPrint(allocator, fmt, args);
     defer allocator.free(filename);
-    return dir.readFileAlloc(allocator, filename, max_size);
+    // Don't judge me, take care of me!
+    return dir.readFileAlloc(allocator, filename, maxInt(usize));
 }
 
 const Frame = extern struct {
@@ -268,6 +268,36 @@ export fn loadMuscles(muscles: [*]Muscle) void {
         muscles[i].load(row) catch unreachable;
 }
 
+pub const Scores = extern struct {
+    // Don't underestimate players!
+    high_score: usize = 0,
+    completed: bool = false,
+};
+
+/// Read scores from user data directory and return it.
+pub fn loadScores(base_dir: []const u8) !Scores {
+    var dir = try cwd().openDir(base_dir, .{});
+    defer dir.close();
+    var scores = Scores{};
+    const path = "blackshades" ++ [_]u8{ sep } ++ "scores.ini";
+    const input = dir.openFile(path, .{}) catch return scores;
+    defer input.close();
+    var parser = ini.parse(allocator, input.reader());
+    defer parser.deinit();
+
+    while (try parser.next()) |record|
+        switch (record) {
+            .section => {},
+            .property => |kv| if (eql(u8, kv.key, "high score")) {
+                scores.high_score = try parseInt(usize, kv.value, 10);
+            } else if (eql(u8, kv.key, "completed")) {
+                scores.completed = try parseBool(kv.value);
+            } else return error.InvalidData,
+            else => return error.InvalidData,
+        };
+    return scores;
+}
+
 /// Load audio file into an OpenAL buffer and return it.
 export fn loadSound(filename: [*:0]const u8) u32 {
     const path = join(allocator, &.{ data_dir ++ "sounds", span(filename) })
@@ -287,10 +317,9 @@ fn check(errorString: fn (c_uint) callconv(.C) [*c]const u8,
 
 /// Load PNG file into an OpenGL buffer and return it.
 export fn loadTexture(filename: [*:0]const u8) GLuint {
-    var dir = cwd().openDir(data_dir ++ "textures", .{}) catch unreachable;
-    defer dir.close();
-    const file = dir.readFileAlloc(allocator, span(filename), max_size)
-        catch unreachable;
+    const file = readFile(cwd(), data_dir ++ "textures{c}{s}", .{
+        sep, filename,
+    }) catch unreachable;
     defer allocator.free(file);
 
     var data: [*c]u8 = undefined;
@@ -325,3 +354,24 @@ export fn playSound(source: u32, x: f32, y: f32, z: f32) void {
     src.setPosition(.{ x / 32, y / 32, z / 32 }) catch unreachable;
     src.play() catch unreachable;
 }
+
+// TODO: replace with @maximum from Zig 0.9+
+fn maximum(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
+    return if (a > b) a else b;
+}
+
+/// Write scores to user data directory.
+pub fn saveScores(base_dir: []const u8, current: Scores) !void {
+    const previous = try loadScores(base_dir);
+    const dir_path = try join(allocator, &.{ base_dir, "blackshades" });
+    defer allocator.free(dir_path);
+    var dir = try cwd().openDir(dir_path, .{});
+    defer dir.close();
+    const format = "[scores]\nhigh score = {}\ncompleted = {}\n";
+    const data = try allocPrint(allocator, format, .{
+        maximum(previous.high_score, current.high_score),
+        previous.completed or current.completed,
+    });
+    defer allocator.free(data);
+    try dir.writeFile("scores.ini", data);
+}