interactive conways game of life
This commit is contained in:
parent
70846f6283
commit
9bb72f3cdb
2 changed files with 115 additions and 24 deletions
|
|
@ -2,12 +2,24 @@ const std = @import("std");
|
||||||
|
|
||||||
const Conway = @import("conway.zig");
|
const Conway = @import("conway.zig");
|
||||||
|
|
||||||
|
const games = enum{ conway };
|
||||||
|
|
||||||
pub export fn arcade_subcmd(_: *anyopaque, argc: c_int, argv: [*c]u8) callconv(.c) void {
|
pub export fn arcade_subcmd(_: *anyopaque, argc: c_int, argv: [*c]u8) callconv(.c) void {
|
||||||
_ = argc;
|
_ = argc;
|
||||||
_ = argv;
|
_ = argv;
|
||||||
|
|
||||||
|
var terminfo: std.posix.winsize = undefined;
|
||||||
|
switch (std.posix.errno(std.posix.system.ioctl(
|
||||||
|
std.posix.STDOUT_FILENO,
|
||||||
|
std.posix.T.IOCGWINSZ,
|
||||||
|
@intFromPtr(&terminfo),
|
||||||
|
))) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
// const title = @embedFile("arcade.txt");
|
// const title = @embedFile("arcade.txt");
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
const writer = std.fs.File.stdout().writer(&buf);
|
const writer = std.fs.File.stdout().writer(&buf);
|
||||||
Conway.play(40, 40, @constCast(&writer.interface)) catch unreachable;
|
Conway.play(terminfo.col, terminfo.row, @constCast(&writer.interface)) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// TODO: make interactive
|
|
||||||
const Conway = @This();
|
const Conway = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -28,6 +27,9 @@ const Cell = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(width: u64, height: u64, writer: *std.Io.Writer) !Conway {
|
pub fn init(width: u64, height: u64, writer: *std.Io.Writer) !Conway {
|
||||||
|
std.debug.assert(width > 0);
|
||||||
|
std.debug.assert(height > 0);
|
||||||
|
|
||||||
var self: Conway = .{
|
var self: Conway = .{
|
||||||
.width = width,
|
.width = width,
|
||||||
.height = height,
|
.height = height,
|
||||||
|
|
@ -36,11 +38,11 @@ pub fn init(width: u64, height: u64, writer: *std.Io.Writer) !Conway {
|
||||||
};
|
};
|
||||||
|
|
||||||
// create the world and the cells
|
// create the world and the cells
|
||||||
self.world = try gpa.alloc([]*Cell, width);
|
self.world = try gpa.alloc([]*Cell, height);
|
||||||
for (self.world, 0..) |col, i| {
|
for (self.world, 0..) |col, i| {
|
||||||
self.world[i] = try gpa.alloc(*Cell, height);
|
self.world[i] = try gpa.alloc(*Cell, width);
|
||||||
for (col, 0..) |_, j| {
|
for (col, 0..) |_, j| {
|
||||||
if (j >= height) break;
|
if (j >= width) break;
|
||||||
self.world[i][j] = try gpa.create(Cell);
|
self.world[i][j] = try gpa.create(Cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,9 +54,9 @@ pub fn init(width: u64, height: u64, writer: *std.Io.Writer) !Conway {
|
||||||
// the left column has nothing on the left
|
// the left column has nothing on the left
|
||||||
cell.neighbors[1] = if (i == 0) null else self.world[i - 1][j];
|
cell.neighbors[1] = if (i == 0) null else self.world[i - 1][j];
|
||||||
// the right row has nothing on the right
|
// the right row has nothing on the right
|
||||||
cell.neighbors[2] = if (i == width - 1) null else self.world[i + 1][j];
|
cell.neighbors[2] = if (i == height - 1) null else self.world[i + 1][j];
|
||||||
// the bottom row has nothing below it
|
// the bottom row has nothing below it
|
||||||
cell.neighbors[3] = if (j == height - 1) null else self.world[i][j + 1];
|
cell.neighbors[3] = if (j == width - 1) null else self.world[i][j + 1];
|
||||||
cell.alive = false;
|
cell.alive = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,6 +102,7 @@ pub fn step(self: *Conway) void {
|
||||||
for (self.world) |col| for (col) |cell| {
|
for (self.world) |col| for (col) |cell| {
|
||||||
cell.alive = cell.will_live;
|
cell.alive = cell.will_live;
|
||||||
};
|
};
|
||||||
|
self.generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(self: *Conway) !void {
|
pub fn print(self: *Conway) !void {
|
||||||
|
|
@ -109,27 +112,103 @@ pub fn print(self: *Conway) !void {
|
||||||
}
|
}
|
||||||
_ = try self.writer.write("\n");
|
_ = try self.writer.write("\n");
|
||||||
}
|
}
|
||||||
try self.writer.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(width: u64, height: u64, writer: *std.Io.Writer) !void {
|
pub fn play(width: u64, height: u64, writer: *std.Io.Writer) !void {
|
||||||
var con = try Conway.init(width, height, writer);
|
var con = try Conway.init(width, height - 1, writer);
|
||||||
|
|
||||||
con.world[4][2].alive = true;
|
// here we're disabling character echoing and newline requirements on input
|
||||||
con.world[4][3].alive = true;
|
const oldios = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
|
||||||
con.world[4][4].alive = true;
|
defer std.posix.tcsetattr(std.posix.STDIN_FILENO, std.posix.TCSA.NOW, oldios) catch {
|
||||||
con.world[4][5].alive = true;
|
// oh well we've left the terminal in a shitty state, not much we can
|
||||||
con.world[4][6].alive = true;
|
// do to fix this.
|
||||||
con.world[4][7].alive = true;
|
};
|
||||||
con.world[5][2].alive = true;
|
var newios = oldios;
|
||||||
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()) {
|
newios.lflag.ECHO = false;
|
||||||
|
newios.lflag.ICANON = false;
|
||||||
|
try std.posix.tcsetattr(std.posix.STDIN_FILENO, std.posix.TCSA.NOW, newios);
|
||||||
|
|
||||||
|
var fd = [_]std.posix.pollfd{.{
|
||||||
|
.fd = std.posix.STDIN_FILENO,
|
||||||
|
.events = std.posix.POLL.IN,
|
||||||
|
.revents = 0
|
||||||
|
}};
|
||||||
|
|
||||||
|
var timeout: i32 = 250;
|
||||||
|
var cursor_x: i64 = 1;
|
||||||
|
var cursor_y: i64 = 1;
|
||||||
|
var interactive = true;
|
||||||
|
var running = true;
|
||||||
|
while (running) : (con.step()) run: while (true) {
|
||||||
|
// not sure why but adding one here makes the status bar refresh fully
|
||||||
|
try con.writer.print("\x1B[{}A", .{height + 1});
|
||||||
try con.print();
|
try con.print();
|
||||||
std.posix.nanosleep(0, 250000000);
|
|
||||||
try con.writer.print("\x1B[{}A", .{con.height});
|
// keep the cursor in the game window to prevent the terminal from doing
|
||||||
|
// anything we can't/don't account for
|
||||||
|
cursor_x = std.math.clamp(cursor_x, 1, con.width);
|
||||||
|
cursor_y = std.math.clamp(cursor_y, 1, con.height);
|
||||||
|
|
||||||
|
// statusbar
|
||||||
|
try con.writer.print("ge[n]: {} | {s} [i]nteractive \x1B[0m {},{} {}x{} | [+]{}[-]", .{
|
||||||
|
con.generation,
|
||||||
|
if (interactive) "\x1B[47;30;1m" else "\x1B[40;37m",
|
||||||
|
cursor_x, cursor_y,
|
||||||
|
con.width, con.height,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
// position cursor in interactive mode
|
||||||
|
if (interactive) {
|
||||||
|
try con.writer.print("\x1B[{};{}H", .{cursor_y, cursor_x}); // swapped on purpose
|
||||||
|
try con.writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try con.writer.flush();
|
||||||
|
|
||||||
|
const size = try std.posix.poll(@constCast(&fd), if (!interactive) @intCast(timeout) else -1);
|
||||||
|
if (size > 0) {
|
||||||
|
var buf: [50]u8 = undefined;
|
||||||
|
_ = try std.fs.File.stdin().read(@constCast(&buf));
|
||||||
|
for (buf) |c| switch (c) {
|
||||||
|
// we don't wanna worry about case when using +
|
||||||
|
'+', '=' => timeout += 50,
|
||||||
|
'-' => timeout = std.math.clamp(timeout - 50, 1, 1000),
|
||||||
|
'n' => break :run, // next generation
|
||||||
|
'q' => {
|
||||||
|
running = false;
|
||||||
|
break :run;
|
||||||
|
},
|
||||||
|
'i' => interactive = !interactive,
|
||||||
|
|
||||||
|
// interactive exclusive keys
|
||||||
|
else => if (interactive) switch (c) {
|
||||||
|
// movement keys, all movement is clamped before it's displayed
|
||||||
|
'h' => cursor_x -= 1,
|
||||||
|
'j' => cursor_y += 1,
|
||||||
|
'k' => cursor_y -= 1,
|
||||||
|
'l' => cursor_x += 1,
|
||||||
|
|
||||||
|
'_' => cursor_x = 1,
|
||||||
|
'$' => cursor_x = @intCast(con.width),
|
||||||
|
'g' => cursor_y = 1,
|
||||||
|
'G' => cursor_y = @intCast(con.height),
|
||||||
|
|
||||||
|
|
||||||
|
' ' => { // toggle the current value's aliveness
|
||||||
|
const cell = con.world[@intCast(cursor_y - 1)][@intCast(cursor_x - 1)];
|
||||||
|
cell.alive = !cell.alive;
|
||||||
|
// prevent the cell from coming back
|
||||||
|
cell.will_live = false;
|
||||||
|
},
|
||||||
|
else => break,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else break;
|
||||||
|
};
|
||||||
|
|
||||||
|
try con.writer.print("\n\x1B[{}A\n", .{height});
|
||||||
|
try con.writer.flush();
|
||||||
|
con.deinit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue