cleanup
This commit is contained in:
43
src/data.zig
43
src/data.zig
@@ -1,19 +1,19 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Type = enum {
|
||||
pub const Type = enum {
|
||||
ailment,
|
||||
recipe,
|
||||
ingredient,
|
||||
};
|
||||
|
||||
const NodeID = enum {
|
||||
pub const NodeID = enum {
|
||||
sellery_juice,
|
||||
salt_craving,
|
||||
};
|
||||
|
||||
const Node = struct {
|
||||
name: []const u8,
|
||||
description: []const u8,
|
||||
pub const Node = struct {
|
||||
name: [:0]const u8,
|
||||
description: [:0]const u8,
|
||||
type: Type,
|
||||
};
|
||||
|
||||
@@ -23,28 +23,7 @@ const Edge = struct {
|
||||
dest: NodeID,
|
||||
};
|
||||
|
||||
fn validated(arr: std.EnumArray(NodeID, Node)) std.EnumArray(NodeID, Node) {
|
||||
for (std.enums.values(NodeID)) |id| {
|
||||
const desc = arr.get(id).description;
|
||||
var i: usize = 0;
|
||||
while (i + 1 < desc.len) : (i += 1) {
|
||||
if (desc[i] == '[' and desc[i + 1] == '[') {
|
||||
const start = i + 2;
|
||||
var j = start;
|
||||
while (j + 1 < desc.len and !(desc[j] == ']' and desc[j + 1] == ']')) : (j += 1) {}
|
||||
const ref = desc[start..j];
|
||||
if (std.meta.stringToEnum(NodeID, ref) == null) {
|
||||
@compileLog("Unknown node reference: ", ref);
|
||||
@compileError("Invalid wiki link in node description (see log above)");
|
||||
}
|
||||
i = j + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
pub const nodes = validated(std.EnumArray(NodeID, Node).init(.{
|
||||
pub const nodes = std.EnumArray(NodeID, Node).init(.{
|
||||
.sellery_juice = .{
|
||||
.name = "Sellery Juice",
|
||||
.description = "A healing drink made from celery.",
|
||||
@@ -52,8 +31,12 @@ pub const nodes = validated(std.EnumArray(NodeID, Node).init(.{
|
||||
},
|
||||
.salt_craving = .{
|
||||
.name = "Salt craving",
|
||||
.description = "Can be caused by sodium deficincy. Can be fixed with [[sellery_juice]].",
|
||||
.type = .recipe,
|
||||
.description = "Can be caused by sodium deficincy. Can be fixed with " ++ linkTo(.sellery_juice) ++ ".",
|
||||
.type = .ailment,
|
||||
},
|
||||
}));
|
||||
});
|
||||
var edges: []const Edge = &.{};
|
||||
|
||||
fn linkTo(node: NodeID) []const u8 {
|
||||
return "[[" ++ @tagName(node) ++ "]]";
|
||||
}
|
||||
|
||||
157
src/main.zig
157
src/main.zig
@@ -3,9 +3,6 @@ const rl = @import("raylib");
|
||||
const builtin = @import("builtin");
|
||||
const math = std.math;
|
||||
const data = @import("data.zig");
|
||||
comptime {
|
||||
_ = data.nodes;
|
||||
}
|
||||
|
||||
const NODE_R: f32 = 42.0;
|
||||
|
||||
@@ -25,125 +22,45 @@ const ORBIT_R: f32 = 220.0;
|
||||
|
||||
// ── Data model ───────────────────────────────────────────────────────────────
|
||||
|
||||
const NodeKind = enum { body_part, ailment, recipe, ingredient, property };
|
||||
|
||||
const NodeDef = struct {
|
||||
id: u8,
|
||||
name: [:0]const u8,
|
||||
kind: NodeKind,
|
||||
links: []const u8,
|
||||
};
|
||||
|
||||
/// All content is hardcoded and will be embedded in the binary at compile time.
|
||||
const all_nodes = [_]NodeDef{
|
||||
// Body systems (0–4)
|
||||
.{ .id = 0, .name = "Digestive System", .kind = .body_part, .links = &.{ 10, 11, 12 } },
|
||||
.{ .id = 1, .name = "Immune System", .kind = .body_part, .links = &.{ 13, 14 } },
|
||||
.{ .id = 2, .name = "Nervous System", .kind = .body_part, .links = &.{15} },
|
||||
.{ .id = 3, .name = "Circulatory", .kind = .body_part, .links = &.{ 13, 14 } },
|
||||
.{ .id = 4, .name = "Endocrine", .kind = .body_part, .links = &.{ 14, 15 } },
|
||||
// Ailments (10–15)
|
||||
.{ .id = 10, .name = "Poor Digestion", .kind = .ailment, .links = &.{ 20, 21 } },
|
||||
.{ .id = 11, .name = "Constipation", .kind = .ailment, .links = &.{ 20, 22 } },
|
||||
.{ .id = 12, .name = "Bloating", .kind = .ailment, .links = &.{21} },
|
||||
.{ .id = 13, .name = "Inflammation", .kind = .ailment, .links = &.{ 22, 23 } },
|
||||
.{ .id = 14, .name = "Fatigue", .kind = .ailment, .links = &.{ 21, 23 } },
|
||||
.{ .id = 15, .name = "Brain Fog", .kind = .ailment, .links = &.{23} },
|
||||
// Recipes (20–23)
|
||||
.{ .id = 20, .name = "Meat Smoothie", .kind = .recipe, .links = &.{ 30, 31, 32 } },
|
||||
.{ .id = 21, .name = "Raw Milk Kefir", .kind = .recipe, .links = &.{ 33, 34 } },
|
||||
.{ .id = 22, .name = "Raw Egg Blend", .kind = .recipe, .links = &.{ 31, 32, 34 } },
|
||||
.{ .id = 23, .name = "Honey-Butter Mix", .kind = .recipe, .links = &.{ 35, 36 } },
|
||||
// Ingredients (30–36)
|
||||
.{ .id = 30, .name = "Raw Meat", .kind = .ingredient, .links = &.{ 40, 41 } },
|
||||
.{ .id = 31, .name = "Raw Eggs", .kind = .ingredient, .links = &.{ 40, 42 } },
|
||||
.{ .id = 32, .name = "Lemon Juice", .kind = .ingredient, .links = &.{43} },
|
||||
.{ .id = 33, .name = "Raw Milk", .kind = .ingredient, .links = &.{ 42, 44 } },
|
||||
.{ .id = 34, .name = "Kefir Grains", .kind = .ingredient, .links = &.{44} },
|
||||
.{ .id = 35, .name = "Raw Honey", .kind = .ingredient, .links = &.{ 41, 43 } },
|
||||
.{ .id = 36, .name = "Raw Butter", .kind = .ingredient, .links = &.{ 41, 44 } },
|
||||
// Properties (40–44)
|
||||
.{ .id = 40, .name = "High Protein", .kind = .property, .links = &.{} },
|
||||
.{ .id = 41, .name = "Energy Dense", .kind = .property, .links = &.{} },
|
||||
.{ .id = 42, .name = "Probiotic", .kind = .property, .links = &.{} },
|
||||
.{ .id = 43, .name = "Alkalizing", .kind = .property, .links = &.{} },
|
||||
.{ .id = 44, .name = "Enzyme Rich", .kind = .property, .links = &.{} },
|
||||
};
|
||||
|
||||
const root_ids = [_]u8{ 0, 1, 2, 3, 4 };
|
||||
|
||||
fn findNode(id: u8) ?*const NodeDef {
|
||||
for (&all_nodes) |*n| {
|
||||
if (n.id == id) return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn kindColor(kind: NodeKind) rl.Color {
|
||||
fn kindColor(kind: data.Type) rl.Color {
|
||||
return switch (kind) {
|
||||
.body_part => rl.Color.init(70, 150, 215, 255),
|
||||
.ailment => rl.Color.init(210, 75, 65, 255),
|
||||
.recipe => rl.Color.init(75, 190, 110, 255),
|
||||
.ingredient => rl.Color.init(215, 170, 50, 255),
|
||||
.property => rl.Color.init(170, 90, 215, 255),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Navigation state ──────────────────────────────────────────────────────────
|
||||
|
||||
const VisNode = struct { id: u8, x: f32, y: f32, is_center: bool };
|
||||
const VisNode = struct { id: data.NodeID, x: f32, y: f32, is_center: bool };
|
||||
|
||||
const MAX_STACK = 16;
|
||||
const MAX_VIS = 24;
|
||||
|
||||
var nav_stack: [MAX_STACK]u8 = undefined;
|
||||
var nav_depth: usize = 0;
|
||||
var nav_stack: [MAX_STACK]data.NodeID = undefined;
|
||||
var vis: [MAX_VIS]VisNode = undefined;
|
||||
var vis_count: usize = 0;
|
||||
var hovered_id: ?u8 = null;
|
||||
var hovered_id: ?data.NodeID = null;
|
||||
var ready: bool = false;
|
||||
|
||||
fn buildVis() void {
|
||||
vis_count = 0;
|
||||
if (nav_depth == 0) {
|
||||
// Root view: body systems arranged in a circle
|
||||
const n = root_ids.len;
|
||||
const r: f32 = 200.0;
|
||||
for (root_ids, 0..) |id, i| {
|
||||
const angle = (2.0 * math.pi * @as(f32, @floatFromInt(i))) /
|
||||
@as(f32, @floatFromInt(n)) - math.pi / 2.0;
|
||||
vis[vis_count] = .{
|
||||
.id = id,
|
||||
.x = centerX() + r * @cos(angle),
|
||||
.y = centerY() + r * @sin(angle),
|
||||
.is_center = false,
|
||||
};
|
||||
vis_count += 1;
|
||||
}
|
||||
} else {
|
||||
// Drill-down view: selected node at centre, its links in orbit
|
||||
const cur_id = nav_stack[nav_depth - 1];
|
||||
const cur = findNode(cur_id) orelse return;
|
||||
vis[vis_count] = .{ .id = cur_id, .x = centerX(), .y = centerY(), .is_center = true };
|
||||
// Root view: body systems arranged in a circle
|
||||
const r: f32 = 200.0;
|
||||
for (std.enums.values(data.NodeID), 0..) |id, i| {
|
||||
const angle = (2.0 * math.pi * @as(f32, @floatFromInt(i))) /
|
||||
@as(f32, @floatFromInt(std.enums.values(data.NodeID).len)) - math.pi / 2.0;
|
||||
vis[vis_count] = .{
|
||||
.id = id,
|
||||
.x = centerX() + r * @cos(angle),
|
||||
.y = centerY() + r * @sin(angle),
|
||||
.is_center = false,
|
||||
};
|
||||
vis_count += 1;
|
||||
const n = cur.links.len;
|
||||
if (n > 0) {
|
||||
for (cur.links, 0..) |link_id, i| {
|
||||
const angle = (2.0 * math.pi * @as(f32, @floatFromInt(i))) /
|
||||
@as(f32, @floatFromInt(n)) - math.pi / 2.0;
|
||||
vis[vis_count] = .{
|
||||
.id = link_id,
|
||||
.x = centerX() + ORBIT_R * @cos(angle),
|
||||
.y = centerY() + ORBIT_R * @sin(angle),
|
||||
.is_center = false,
|
||||
};
|
||||
vis_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visNodeAt(mx: f32, my: f32) ?u8 {
|
||||
fn visNodeAt(mx: f32, my: f32) ?data.NodeID {
|
||||
for (vis[0..vis_count]) |vn| {
|
||||
const dx = mx - vn.x;
|
||||
const dy = my - vn.y;
|
||||
@@ -160,29 +77,11 @@ fn update() void {
|
||||
const mx: f32 = @floatFromInt(rl.getMouseX());
|
||||
const my: f32 = @floatFromInt(rl.getMouseY());
|
||||
hovered_id = visNodeAt(mx, my);
|
||||
|
||||
if (rl.isMouseButtonPressed(.left)) {
|
||||
if (hovered_id) |id| {
|
||||
const is_cur = nav_depth > 0 and nav_stack[nav_depth - 1] == id;
|
||||
if (!is_cur and nav_depth < MAX_STACK) {
|
||||
nav_stack[nav_depth] = id;
|
||||
nav_depth += 1;
|
||||
buildVis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rl.isMouseButtonPressed(.right) or rl.isKeyPressed(.b)) {
|
||||
if (nav_depth > 0) {
|
||||
nav_depth -= 1;
|
||||
buildVis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drawScene() void {
|
||||
// Edges from centre to orbit
|
||||
if (nav_depth > 0 and vis_count > 1) {
|
||||
if (vis_count > 1) {
|
||||
const c = vis[0];
|
||||
for (vis[1..vis_count]) |vn| {
|
||||
rl.drawLineEx(
|
||||
@@ -196,8 +95,8 @@ fn drawScene() void {
|
||||
|
||||
// Nodes
|
||||
for (vis[0..vis_count]) |vn| {
|
||||
const node = findNode(vn.id) orelse continue;
|
||||
const col = kindColor(node.kind);
|
||||
const node = data.nodes.get(vn.id);
|
||||
const col = kindColor(node.@"type");
|
||||
const hov = hovered_id != null and hovered_id.? == vn.id;
|
||||
const r = if (hov) NODE_R + 5.0 else NODE_R;
|
||||
const ix: i32 = @intFromFloat(vn.x);
|
||||
@@ -229,28 +128,12 @@ fn drawHUD() void {
|
||||
const by: i32 = 10;
|
||||
rl.drawText("Body Systems", bx, by, 20, rl.Color.gray);
|
||||
bx += rl.measureText("Body Systems", 20);
|
||||
for (nav_stack[0..nav_depth]) |id| {
|
||||
rl.drawText(" > ", bx, by, 20, rl.Color.dark_gray);
|
||||
bx += rl.measureText(" > ", 20);
|
||||
const node = findNode(id) orelse continue;
|
||||
rl.drawText(node.name, bx, by, 20, rl.Color.ray_white);
|
||||
bx += rl.measureText(node.name, 20);
|
||||
}
|
||||
|
||||
// Navigation hint at bottom
|
||||
const hint: [:0]const u8 = if (nav_depth > 0)
|
||||
"[B] / right-click to go back"
|
||||
else
|
||||
"Click a body system to explore";
|
||||
rl.drawText(hint, 10, screenH() - 28, 18, rl.Color.dark_gray);
|
||||
|
||||
// Colour legend (top-right)
|
||||
const legend = [_]struct { kind: NodeKind, label: [:0]const u8 }{
|
||||
.{ .kind = .body_part, .label = "Body System" },
|
||||
const legend = [_]struct { kind: data.Type, label: [:0]const u8 }{
|
||||
.{ .kind = .ailment, .label = "Ailment" },
|
||||
.{ .kind = .recipe, .label = "Recipe" },
|
||||
.{ .kind = .ingredient, .label = "Ingredient" },
|
||||
.{ .kind = .property, .label = "Property" },
|
||||
};
|
||||
const lx: i32 = screenW() - 140;
|
||||
var ly: i32 = 10;
|
||||
|
||||
Reference in New Issue
Block a user