From 900c8756edc3bc547dfd2cd47921ecd453bf703b Mon Sep 17 00:00:00 2001
From: jjanzen <jjanzen@jjanzen.ca>
Date: Tue, 21 Jan 2025 23:26:23 -0600
Subject: doc comments and io refactor

---
 src/actions.zig          | 20 +++++++++++
 src/ecs/component.zig    |  4 +++
 src/ecs/ecs.zig          | 39 ++++++++++++++-------
 src/frontend/ncurses.zig | 91 +++++++++++++++++++++++++++---------------------
 4 files changed, 102 insertions(+), 52 deletions(-)

diff --git a/src/actions.zig b/src/actions.zig
index 431a6d0..ac46574 100644
--- a/src/actions.zig
+++ b/src/actions.zig
@@ -1,13 +1,33 @@
+//! This module provides an enum of available actions in game including internal signals.
 const std = @import("std");
 
+/// An enum of available actions in the game
 pub const Action = enum {
+    /// Illegal action. Treated as a noop.
     illegal,
+
+    /// One game tick has finished.
     tick,
+
+    /// Exit the game.
     exit,
+
+    /// Move the player character up one tile.
     move_up,
+
+    /// Move the player character down one tile.
     move_down,
+
+    /// Move the player character left one tile.
     move_left,
+
+    /// Move the player character right one tile.
     move_right,
+
+    /// The player character goes down a flight of stairs.
     down_stair,
+
+    /// The player character goes up a flight of stairs.
+    /// (TODO: possibly merge with down_stair)
     up_stair,
 };
diff --git a/src/ecs/component.zig b/src/ecs/component.zig
index 37956d0..3a50c88 100644
--- a/src/ecs/component.zig
+++ b/src/ecs/component.zig
@@ -1,5 +1,7 @@
+//! This module provides a basic component interface for an Entity Component System
 const std = @import("std");
 
+/// An empty component for testing purposes
 pub const ComponentStub = struct {
     pub fn init(args: ComponentStub) ComponentStub {
         _ = args;
@@ -11,10 +13,12 @@ pub const ComponentStub = struct {
     }
 };
 
+/// All valid component types are stored in this enum
 pub const ComponentType = enum {
     component_stub,
 };
 
+/// The components are stored as a MultiArrayList over this struct
 pub const Components = struct {
     component_stub: ?ComponentStub,
 };
diff --git a/src/ecs/ecs.zig b/src/ecs/ecs.zig
index d1341bf..47c576b 100644
--- a/src/ecs/ecs.zig
+++ b/src/ecs/ecs.zig
@@ -1,12 +1,20 @@
+//! This module provides an implementation of an Entity Component System
 const std = @import("std");
 const component = @import("component.zig");
 const ComponentType = component.ComponentType;
 
+/// EntityComponentSystem keeps track of components in a MultiArrayList of Components.
+/// Each Entity is simply an index into that MultiArrayList of Components.
+/// To save on memory and avoid resizing, ids are recycled upon Entities being deleted.
 pub const EntityComponentSystem = struct {
+    /// An allocator for use by the EntityComponentSystem to manage memory
     allocator: std.mem.Allocator,
+
+    /// The mapping of Components to Entities is in this MultiArrayList
     components: std.MultiArrayList(component.Components),
+
+    /// Keeps track of ids available for reuse
     available_ids: std.ArrayList(usize),
-    next_id: usize,
 
     fn nullEntity(self: *EntityComponentSystem, entity: usize) void {
         inline for (std.enums.valuesFromFields(ComponentType, std.meta.fields(ComponentType))) |comp| {
@@ -19,7 +27,6 @@ pub const EntityComponentSystem = struct {
             .allocator = allocator,
             .components = .{},
             .available_ids = std.ArrayList(usize).init(allocator),
-            .next_id = 0,
         };
     }
 
@@ -28,28 +35,34 @@ pub const EntityComponentSystem = struct {
         self.components.deinit(self.allocator);
     }
 
+    /// Create a new entity (or recycle an old one) and return the id.
+    /// May error if the Allocator runs out of memory.
     pub fn createEntity(self: *EntityComponentSystem) !usize {
         const id = self.available_ids.popOrNull() orelse noroom: {
-            const id = self.next_id;
+            const id = self.components.len;
             try self.components.append(self.allocator, undefined);
-            self.next_id += 1;
             break :noroom id;
         };
         self.nullEntity(id);
         return id;
     }
 
+    /// Add an entity to a component.
+    /// Takes a ComponentType and an entity id along with any arguments needed to initialize the component.
     pub fn componentAddEntity(
         self: *EntityComponentSystem,
         comp: component.ComponentType,
         entity: usize,
         args: anytype,
-    ) !void {
+    ) void {
         switch (comp) {
             ComponentType.component_stub => self.components.items(.component_stub)[entity] = component.ComponentStub.init(args),
         }
     }
 
+    /// Delete an entity and make it ready for recycling by nulling all components.
+    /// Takes an entity id.
+    /// May error if the Allocator runs out of memory.
     pub fn deleteEntity(self: *EntityComponentSystem, entity: usize) !void {
         self.nullEntity(entity);
         try self.available_ids.append(entity);
@@ -61,6 +74,8 @@ pub const EntityComponentSystem = struct {
         comp[entity] = null;
     }
 
+    /// Removes an entity from a component.
+    /// Takes a ComponentType and an entity id.
     pub fn componentRemoveEntity(
         self: *EntityComponentSystem,
         comp: component.ComponentType,
@@ -80,7 +95,7 @@ test "add entities to components" {
     for (0..100) |i| {
         const entity = try ecs.createEntity();
         try std.testing.expectEqual(i, entity);
-        try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+        ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
         try std.testing.expect(ecs.components.items(.component_stub)[entity] != null);
     }
 }
@@ -90,9 +105,9 @@ test "remove entities from components" {
     defer ecs.deinit();
 
     const entity = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
     const entity2 = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
 
     try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
     ecs.componentRemoveEntity(ComponentType.component_stub, entity2);
@@ -104,9 +119,9 @@ test "delete entities" {
     defer ecs.deinit();
 
     const entity = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
     const entity2 = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
 
     try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
     try ecs.deleteEntity(entity2);
@@ -119,9 +134,9 @@ test "recycle entities" {
     defer ecs.deinit();
 
     const entity = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
     const entity2 = try ecs.createEntity();
-    try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+    ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
 
     try ecs.deleteEntity(entity2);
     try std.testing.expectEqual(1, ecs.available_ids.items.len);
diff --git a/src/frontend/ncurses.zig b/src/frontend/ncurses.zig
index 2a560a8..3d4fbb7 100644
--- a/src/frontend/ncurses.zig
+++ b/src/frontend/ncurses.zig
@@ -1,3 +1,4 @@
+//! This module provides an implementation of IO using the ncurses library.
 const std = @import("std");
 const Action = @import("../actions.zig").Action;
 const ncurses = @cImport({
@@ -17,7 +18,6 @@ const MESSAGE_PANEL_WIDTH = 100;
 const MESSAGE_PANEL_HEIGHT = 3;
 const STATUS_PANEL_WIDTH = 32;
 const STATUS_PANEL_HEIGHT = 5;
-
 const MIN_WIDTH = MAIN_PANEL_WIDTH + INSTRUCTION_PANEL_WIDTH;
 const MIN_HEIGHT = MAIN_PANEL_HEIGHT + MESSAGE_PANEL_HEIGHT;
 
@@ -29,6 +29,7 @@ const KEY_STAIR_UP: i32 = '<';
 const KEY_STAIR_DOWN: i32 = '>';
 const KEY_QUIT: i32 = ncurses.KEY_F(1);
 
+/// Provide an IO struct that manages the state of the display and user input
 pub const IO = struct {
     main: ?*ncurses.WINDOW,
     inst: ?*ncurses.WINDOW,
@@ -36,16 +37,19 @@ pub const IO = struct {
     stat: ?*ncurses.WINDOW,
     prev_tick_time: i64,
 
-    fn validTermSize() bool {
+    fn validTermSize(self: *IO) bool {
+        _ = self; // should be uncallable without initialization
         return ncurses.LINES >= MIN_HEIGHT and
             ncurses.COLS >= MIN_WIDTH;
     }
 
-    fn colorSupport() bool {
+    fn colorSupport(self: *IO) bool {
+        _ = self; // should be uncallable without initialization
         return ncurses.has_colors();
     }
 
-    fn createNewWin(height: i32, width: i32, y: i32, x: i32) ?*ncurses.WINDOW {
+    fn createNewWin(self: *IO, height: i32, width: i32, y: i32, x: i32) ?*ncurses.WINDOW {
+        _ = self; // should be uncallable without initialization
         const local_win = ncurses.newwin(height, width, y, x);
         _ = ncurses.box(local_win, 0, 0);
         _ = ncurses.wrefresh(local_win);
@@ -64,6 +68,13 @@ pub const IO = struct {
         }
     }
 
+    fn createPanels(self: *IO) void {
+        self.createInstructionPanel();
+        self.createMessagePanel();
+        self.createStatisticsPanel();
+        self.createMainPanel();
+    }
+
     fn handleResize(self: *IO) Action {
         const lines = @as(usize, @intCast(ncurses.LINES));
         const cols = @as(usize, @intCast(ncurses.COLS));
@@ -78,14 +89,10 @@ pub const IO = struct {
 
         self.deleteWindows();
 
-        if (!validTermSize()) {
+        if (!self.validTermSize()) {
             _ = ncurses.mvprintw(0, 0, "Terminal must be at least %dx%d", @as(i32, @intCast(MIN_WIDTH)), @as(i32, @intCast(MIN_HEIGHT)));
         } else {
-            self.inst = createInstructionPanel();
-            self.msgs = createMessagePanel();
-            self.stat = createStatisticsPanel();
-            self.main = createMainPanel();
-            self.displayInstructions();
+            self.createPanels();
         }
 
         _ = ncurses.refresh();
@@ -93,7 +100,7 @@ pub const IO = struct {
         return Action.illegal;
     }
 
-    pub fn displayInstructions(self: *IO) void {
+    fn displayInstructions(self: *IO) void {
         self.formatInstruction(1, KEY_MOVE_LEFT, "move left");
         self.formatInstruction(2, KEY_MOVE_DOWN, "move down");
         self.formatInstruction(3, KEY_MOVE_UP, "move up");
@@ -104,6 +111,9 @@ pub const IO = struct {
         _ = ncurses.wrefresh(self.inst);
     }
 
+    /// Display a message in the message box.
+    /// Takes a format string and arguments.
+    /// If the message is too wide for the box, display "Message too long" instead.
     pub fn displayMessage(self: *IO, comptime fmt: []const u8, args: anytype) void {
         if (self.msgs == null) return;
 
@@ -126,7 +136,7 @@ pub const IO = struct {
         _ = ncurses.wrefresh(self.msgs);
     }
 
-    pub fn displayStatus(self: *IO) void {
+    fn displayStatus(self: *IO) void {
         for (1..STATUS_PANEL_HEIGHT - 1) |i| {
             for (1..STATUS_PANEL_WIDTH - 1) |j| {
                 const i_32 = @as(i32, @intCast(i));
@@ -135,9 +145,14 @@ pub const IO = struct {
             }
         }
 
+        // TODO: Implement
+
         _ = ncurses.wrefresh(self.stat);
     }
 
+    /// An interface for user input and time processing.
+    /// Waits for the end of a tick and returns a tick action.
+    /// If input is given before the end of the tick, return that instead.
     pub fn waitForTick(self: *IO) Action {
         var new = std.time.milliTimestamp();
 
@@ -155,7 +170,7 @@ pub const IO = struct {
     fn processInput(self: *IO) Action {
         const ch = ncurses.getch();
 
-        if (!validTermSize()) {
+        if (!self.validTermSize()) {
             if (ch != KEY_QUIT and ch != ncurses.KEY_RESIZE) return Action.illegal;
         }
 
@@ -172,17 +187,20 @@ pub const IO = struct {
         };
     }
 
-    fn createInstructionPanel() ?*ncurses.WINDOW {
-        return createNewWin(
+    fn createInstructionPanel(self: *IO) void {
+        self.inst = self.createNewWin(
             INSTRUCTION_PANEL_HEIGHT,
             INSTRUCTION_PANEL_WIDTH,
             @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + 1,
             @divTrunc(ncurses.COLS - MAIN_PANEL_WIDTH - INSTRUCTION_PANEL_WIDTH, 2) + MAIN_PANEL_WIDTH - 1,
         );
+        if (self.inst != null) {
+            self.displayInstructions();
+        }
     }
 
-    fn createMessagePanel() ?*ncurses.WINDOW {
-        return createNewWin(
+    fn createMessagePanel(self: *IO) void {
+        self.msgs = self.createNewWin(
             MESSAGE_PANEL_HEIGHT,
             MESSAGE_PANEL_WIDTH,
             @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + MAIN_PANEL_HEIGHT,
@@ -190,8 +208,8 @@ pub const IO = struct {
         );
     }
 
-    fn createStatisticsPanel() ?*ncurses.WINDOW {
-        return createNewWin(
+    fn createStatisticsPanel(self: *IO) void {
+        self.stat = self.createNewWin(
             STATUS_PANEL_HEIGHT,
             STATUS_PANEL_WIDTH,
             @divTrunc(ncurses.LINES - INSTRUCTION_PANEL_HEIGHT - STATUS_PANEL_HEIGHT, 2) + INSTRUCTION_PANEL_HEIGHT,
@@ -199,8 +217,8 @@ pub const IO = struct {
         );
     }
 
-    fn createMainPanel() ?*ncurses.WINDOW {
-        return createNewWin(
+    fn createMainPanel(self: *IO) void {
+        self.main = self.createNewWin(
             MAIN_PANEL_HEIGHT,
             MAIN_PANEL_WIDTH,
             @divTrunc(ncurses.LINES - MAIN_PANEL_HEIGHT - MESSAGE_PANEL_HEIGHT, 2) + 1,
@@ -210,11 +228,20 @@ pub const IO = struct {
 
     pub fn init() !IO {
         _ = locale.setlocale(locale.LC_ALL, "");
+
+        var io = IO{
+            .inst = null,
+            .msgs = null,
+            .stat = null,
+            .main = null,
+            .prev_tick_time = std.time.milliTimestamp(),
+        };
+
         if (ncurses.initscr() == null) {
             return error.CursesInitFail;
         }
 
-        if (!colorSupport()) {
+        if (!io.colorSupport()) {
             _ = ncurses.endwin();
             return error.NoColorSupport;
         }
@@ -231,26 +258,10 @@ pub const IO = struct {
         _ = ncurses.wattron(ncurses.stdscr, ncurses.COLOR_PAIR(1));
         _ = ncurses.refresh();
 
-        var io: IO = undefined;
-
-        if (!validTermSize()) {
+        if (!io.validTermSize()) {
             _ = ncurses.mvprintw(0, 0, "Terminal must be at least %dx%d", @as(i32, @intCast(MIN_WIDTH)), @as(i32, @intCast(MIN_HEIGHT)));
-            io = IO{
-                .inst = null,
-                .msgs = null,
-                .stat = null,
-                .main = null,
-                .prev_tick_time = std.time.milliTimestamp(),
-            };
         } else {
-            io = IO{
-                .inst = createInstructionPanel(),
-                .msgs = createMessagePanel(),
-                .stat = createStatisticsPanel(),
-                .main = createMainPanel(),
-                .prev_tick_time = std.time.milliTimestamp(),
-            };
-            io.displayInstructions();
+            io.createPanels();
         }
 
         return io;
-- 
cgit v1.2.3