diff --git a/build.zig b/build.zig index 234e844..c76d62f 100644 --- a/build.zig +++ b/build.zig @@ -21,7 +21,7 @@ pub fn build(b: *std.Build) void { .name = "games", .linkage = .static, .root_module = b.createModule(.{ - .root_source_file = b.path("src/subcmds/games.zig"), + .root_source_file = b.path("src/subcmds/arcade/arcade.zig"), .target = target, .optimize = optimize, .link_libc = true, @@ -48,7 +48,7 @@ pub fn build(b: *std.Build) void { "-DVERSION=\"hi\"", "-std=c23", "-D_GNU_SOURCE", - "-MJ compile_commands.json", + "-MJcompile_commands.json", } }); diff --git a/include/subcmds.h b/include/subcmds.h index a2ec9c0..86f12da 100644 --- a/include/subcmds.h +++ b/include/subcmds.h @@ -9,5 +9,5 @@ void timer_subcmd(void *, int argc, char *argv[]); void stopwatch_subcmd(void *, int argc, char *argv[]); void subcmds_dev(void *, int argc, char *argv[]); void motd_subcmd(void *, int argc, char *argv[]); -void games_subcmd(void *, int argc, char *argv[]); +void arcade_subcmd(void *, int argc, char *argv[]); void subcmds_subcmd(void *, int argc, char *argv[]); diff --git a/src/main.c b/src/main.c index 999ce5a..eaad3df 100644 --- a/src/main.c +++ b/src/main.c @@ -100,7 +100,7 @@ main(int argc, char *argv[]) register_subcmd("stopwatch", stopwatch_subcmd, NULL); register_subcmd("subcmds", subcmds_subcmd, NULL); register_subcmd("dev", subcmds_dev, NULL); - register_subcmd("games", games_subcmd, NULL); + register_subcmd("arcade", arcade_subcmd, NULL); /* if the user didn't specify a config path */ if (!config_path) { diff --git a/src/subcmds/arcade/arcade.txt b/src/subcmds/arcade/arcade.txt new file mode 100644 index 0000000..2e0afd7 --- /dev/null +++ b/src/subcmds/arcade/arcade.txt @@ -0,0 +1,5 @@ + @@@@@@ @@@@@@@ @@@@@@@ @@@@@@ @@@@@@@ @@@@@@@@ +@@! @@@ @@! @@@ !@@ @@! @@@ @@! @@@ @@! +@!@!@!@! @!@!!@! !@! @!@!@!@! @!@ !@! @!!!:! +!!: !!! !!: :!! :!! !!: !!! !!: !!! !!: + : : : : : : :: :: : : : : :: : : : :: ::: diff --git a/src/subcmds/arcade/arcade.zig b/src/subcmds/arcade/arcade.zig new file mode 100644 index 0000000..03a425e --- /dev/null +++ b/src/subcmds/arcade/arcade.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +const Conway = @import("conway.zig"); + +pub export fn arcade_subcmd(_: *anyopaque, argc: c_int, argv: [*c]u8) callconv(.c) void { + _ = argc; + _ = argv; + + // const title = @embedFile("arcade.txt"); + var buf: [4096]u8 = undefined; + const writer = std.fs.File.stdout().writer(&buf); + Conway.play(40, 40, @constCast(&writer.interface)) catch unreachable; +} diff --git a/src/subcmds/arcade/conway.zig b/src/subcmds/arcade/conway.zig new file mode 100644 index 0000000..613781c --- /dev/null +++ b/src/subcmds/arcade/conway.zig @@ -0,0 +1,135 @@ +// TODO: make interactive +const Conway = @This(); + +const std = @import("std"); +const gpa = std.heap.page_allocator; + +generation: u64 = 0, +width: u64, +height: u64, +world: [][]*Cell, +writer: *std.io.Writer, + +const Cell = struct { + /// Technically we don't need to store any neighbors and we could just + /// check using the world, but that makes the logic far more terse. + /// + /// storage of neighbors + /// ? 0 ? + /// 1 x 2 + /// ? 3 ? + /// instead of storing all 8 neighbors I've decided to contact them + /// through our other neighbors. + neighbors: [4]?*Cell = .{ null, null, null, null }, + /// the state on the current generation + alive: bool = false, + /// the state on the next generation + will_live: bool = false, +}; + +pub fn init(width: u64, height: u64, writer: *std.Io.Writer) !Conway { + var self: Conway = .{ + .width = width, + .height = height, + .world = undefined, + .writer = writer, + }; + + // create the world and the cells + self.world = try gpa.alloc([]*Cell, width); + for (self.world, 0..) |col, i| { + self.world[i] = try gpa.alloc(*Cell, height); + for (col, 0..) |_, j| { + if (j >= height) break; + self.world[i][j] = try gpa.create(Cell); + } + } + + // initialize all the cell properties + for (self.world, 0..) |col, i| for (col, 0..) |cell, j| { + // the top row has nothing above it + cell.neighbors[0] = if (j == 0) null else self.world[i][j - 1]; + // the left column has nothing on the left + cell.neighbors[1] = if (i == 0) null else self.world[i - 1][j]; + // the right row has nothing on the right + cell.neighbors[2] = if (i == width - 1) null else self.world[i + 1][j]; + // the bottom row has nothing below it + cell.neighbors[3] = if (j == height - 1) null else self.world[i][j + 1]; + cell.alive = false; + }; + + return self; +} + +pub fn deinit(self: *Conway) void { + for (self.world) |col| { + for (col) |cell| gpa.destroy(cell); + gpa.free(col); + } + gpa.free(self.world); +} + +pub fn step(self: *Conway) void { + for (self.world) |col| for (col) |cell| { + var alive_neighbors: u4 = 0; + + // count the neighbors + for (cell.neighbors, 0..) |n, i| { + if (n == null) continue; + if (i == 0 or i == 3) { + if (n.?.neighbors[1]) |nn| if (nn.alive) { + alive_neighbors += 1; + }; + if (n.?.neighbors[2]) |nn| if (nn.alive) { + alive_neighbors += 1; + }; + } + if (n.?.alive) alive_neighbors += 1; + } + + // rules + if (cell.alive) { + cell.will_live = switch (alive_neighbors) { + 2, 3 => true, + else => false + }; + } else if (alive_neighbors == 3) cell.will_live = true; + }; + + // the living state must be applied after all the cells have been checked + for (self.world) |col| for (col) |cell| { + cell.alive = cell.will_live; + }; +} + +pub fn print(self: *Conway) !void { + for (self.world) |col| { + for (col) |cell| { + try self.writer.print("{s}", .{if (cell.alive) "x" else " "}); + } + _ = try self.writer.write("\n"); + } + try self.writer.flush(); +} + +pub fn play(width: u64, height: u64, writer: *std.Io.Writer) !void { + var con = try Conway.init(width, height, writer); + + con.world[4][2].alive = true; + con.world[4][3].alive = true; + con.world[4][4].alive = true; + con.world[4][5].alive = true; + con.world[4][6].alive = true; + con.world[4][7].alive = true; + con.world[5][2].alive = true; + con.world[5][3].alive = true; + con.world[5][4].alive = true; + con.world[5][5].alive = true; + con.world[5][6].alive = true; + + while (true) : (con.step()) { + try con.print(); + std.posix.nanosleep(0, 250000000); + try con.writer.print("\x1B[{}A", .{con.height}); + } +} diff --git a/src/subcmds/games.zig b/src/subcmds/games.zig deleted file mode 100644 index 7f30f85..0000000 --- a/src/subcmds/games.zig +++ /dev/null @@ -1,121 +0,0 @@ -const std = @import("std"); -const gpa = std.heap.page_allocator; - -const Conway = struct { - generation: u64 = 0, - width: u64, - height: u64, - world: [][]*Cell, - - const Cell = struct { - /// storage of neighbors - /// ? 0 ? - /// 1 x 2 - /// ? 3 ? - /// instead of storing all 8 neighbors I've decided to contact them - /// through our other neighbors. - neighbors: [4]?*Cell = .{ null, null, null, null }, - /// the state on the current generation - alive: bool = false, - /// the state on the next generation - will_live: bool = false, - }; - - pub fn init(width: u64, height: u64) !Conway { - var self: Conway = .{ - .width = width, - .height = height, - .world = undefined, - }; - - // create the world and the cells - self.world = try gpa.alloc([]*Cell, width); - for (self.world, 0..) |col, i| { - self.world[i] = try gpa.alloc(*Cell, height); - for (col, 0..) |_, j| { - if (j >= height) break; - self.world[i][j] = try gpa.create(Cell); - } - } - - // initialize all the cell properties - for (self.world, 0..) |col, i| for (col, 0..) |cell, j| { - // the top row has nothing above it - cell.neighbors[0] = if (j == 0) null else self.world[i][j - 1]; - // the left column has nothing on the left - cell.neighbors[1] = if (i == 0) null else self.world[i - 1][j]; - // the right row has nothing on the right - cell.neighbors[2] = if (i == width - 1) null else self.world[i + 1][j]; - // the bottom row has nothing below it - cell.neighbors[3] = if (j == height - 1) null else self.world[i][j + 1]; - cell.alive = false; - }; - - return self; - } - - pub fn step(self: *Conway) void { - for (self.world) |col| for (col) |cell| { - var alive_neighbors: u4 = 0; - - // count the neighbors - for (cell.neighbors, 0..) |n, i| { - if (n == null) continue; - if (i == 0 or i == 3) { - if (n.?.neighbors[1]) |nn| if (nn.alive) { - alive_neighbors += 1; - }; - if (n.?.neighbors[2]) |nn| if (nn.alive) { - alive_neighbors += 1; - }; - } - if (n.?.alive) alive_neighbors += 1; - } - - // rules - if (cell.alive) { - cell.will_live = switch (alive_neighbors) { - 2, 3 => true, - else => false - }; - } else if (alive_neighbors == 3) cell.will_live = true; - }; - - for (self.world) |col| for (col) |cell| { - cell.alive = cell.will_live; - }; - } - - pub fn print(self: *Conway) void { - for (self.world) |col| { - for (col) |cell| { - std.debug.print("{s}", .{if (cell.alive) "x" else " "}); - } - std.debug.print("\n", .{}); - } - } -}; - -pub export fn games_subcmd(_: *anyopaque, argc: c_int, argv: [*c]u8) callconv(.c) void { - _ = argc; - _ = argv; - - var con = Conway.init(40, 40) catch unreachable; - con.world[4][2].alive = true; - con.world[4][3].alive = true; - con.world[4][4].alive = true; - con.world[4][5].alive = true; - con.world[4][6].alive = true; - con.world[4][7].alive = true; - con.world[5][2].alive = true; - con.world[5][3].alive = true; - con.world[5][4].alive = true; - con.world[5][5].alive = true; - con.world[5][6].alive = true; - - while (true) : (con.step()) { - con.print(); - std.posix.nanosleep(0, 250000000); - std.debug.print("\x1B[{}A", .{con.height}); - } -}