aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjjanzen <jjanzen@jjanzen.ca>2025-01-21 23:26:23 -0600
committerjjanzen <jjanzen@jjanzen.ca>2025-01-21 23:26:23 -0600
commit900c8756edc3bc547dfd2cd47921ecd453bf703b (patch)
tree057be3b086c66bab292da8b2ae798e92f8db54b2
parent385ca6511a8de0a4cc1dca774a3c743422c3d6be (diff)
doc comments and io refactor
-rw-r--r--src/actions.zig20
-rw-r--r--src/ecs/component.zig4
-rw-r--r--src/ecs/ecs.zig39
-rw-r--r--src/frontend/ncurses.zig91
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;