diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/actions.zig | 20 | ||||
-rw-r--r-- | src/ecs/component.zig | 4 | ||||
-rw-r--r-- | src/ecs/ecs.zig | 39 | ||||
-rw-r--r-- | 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; |