Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rain style #7

Merged
merged 12 commits into from
Sep 9, 2024
2 changes: 1 addition & 1 deletion src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub const setup_cmd: CommandT = .{
},
.{
.name = "style",
.description = "Set output style (default, columns, crypto, grid, blocks).",
.description = "Set output style (default, columns, crypto, grid, blocks, rain).",
.short_name = 's',
.long_name = "style",
.val = ValueT.ofType(Style, .{ .name = "style_val", .default_val = Style.default, .alias_child_type = "string" }),
Expand Down
277 changes: 235 additions & 42 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const FRAME = 39730492;

pub const Speed = enum { slow, fast };
pub const Mode = enum { binary, decimal, hexadecimal, textual };
pub const Style = enum { default, columns, crypto, grid, blocks };
pub const Style = enum { default, columns, crypto, grid, blocks, rain };
pub const Color = enum(u32) { default = tb.TB_DEFAULT, red = tb.TB_RED, green = tb.TB_GREEN, blue = tb.TB_BLUE, yellow = tb.TB_YELLOW, magenta = tb.TB_MAGENTA };

var sbuf: [2]u8 = undefined;
Expand All @@ -42,6 +42,7 @@ const Core = struct {
bg: u32 = tb.TB_DEFAULT,
width: i32 = 0,
height: i32 = 0,
columns: ?std.ArrayListAligned(?Column, null) = null,
width_gaps: ?std.ArrayListAligned(u32, null) = null,
height_gaps: ?std.ArrayListAligned(u32, null) = null,

Expand Down Expand Up @@ -71,6 +72,15 @@ const Core = struct {
self.height_gaps = try getNthValues(self.height, adv, self.allocator);
}

fn updateColumns(self: *Core) !void {
for (self.columns.?.items) |column| {
column.?.chars.deinit();
}

self.columns.?.clearAndFree();
try self.columns.?.ensureTotalCapacity(@as(u32, @intCast(self.width)));
}

fn updateStyle(self: *Core, style: Style) !void {
if (style == Style.grid) {
self.mutex.lock();
Expand All @@ -95,6 +105,11 @@ const Core = struct {
defer self.mutex.unlock();

try self.updateWidthSec(4);
} else if (style == Style.rain) {
self.mutex.lock();
defer self.mutex.unlock();

try self.updateColumns();
}
}

Expand All @@ -105,6 +120,10 @@ const Core = struct {
fn start(self: *Core) void {
_ = tb.tb_init();

if (self.columns == null) {
self.columns = std.ArrayList(?Column).init(self.allocator);
}

if (self.width_gaps == null) {
self.width_gaps = std.ArrayList(u32).init(self.allocator);
}
Expand All @@ -123,6 +142,14 @@ const Core = struct {
_ = tb.tb_shutdown();
}

if (self.columns) |columns| {
for (columns.items) |column| {
column.?.chars.deinit();
}

columns.deinit();
}

if (self.width_gaps != null) {
self.width_gaps.?.deinit();
}
Expand Down Expand Up @@ -191,74 +218,219 @@ const Handler = struct {
}
};

const Char = struct { i: u8, p: u32, b: u32 };

const Column = struct {
chars: std.ArrayList(?Char),
printing: bool = false,

fn init(allocator: std.mem.Allocator) Column {
return .{
.chars = std.ArrayList(?Char).init(allocator),
};
}

fn strLen(self: *Column) usize {
var len: usize = 0;
var target: usize = 0;
var check = true;

while (check) {
if (self.chars.items[target] != null) {
len += 1;
target += 1;
} else {
check = false;
}
}

return len;
}

fn newChar(self: *Column, core: *Core, mode: Mode, rand: std.rand.Random) !void {
const rand_int = switch (mode) {
.binary => rand.int(u1),
.decimal => rand.uintLessThan(u8, 10),
.hexadecimal => rand.int(u4),
.textual => rand.uintLessThan(u8, 76),
};

var color = @intFromEnum(core.color);
var pulse = core.bg;

const bold = rand.boolean();

if (core.pulse) {
const blank = @mod(rand.int(u8), 255);

// small probability
if (blank >= 254) {
pulse = core.bg | tb.TB_REVERSE;
}
}

if (bold) {
color = color | tb.TB_BOLD;
}

try self.chars.insert(0, Char{ .i = rand_int, .p = pulse, .b = color });

if (core.pulse) {
core.bg = tb.TB_DEFAULT;
}
}

fn skip(self: *Column) !void {
try self.chars.append(null);
}
};

fn printCells(core: *Core, handler: *Handler, rand: std.rand.Random) !void {
handler.mutex.lock();
defer handler.mutex.unlock();

if (!handler.pause) {
core.setRendering(true);

for (1..@intCast(core.width)) |w| {
if (handler.style != Style.default) {
if (checkSec(&core.width_gaps.?, w)) {
continue;
}
}
_ = tb.tb_clear();
if (handler.style != .rain) {
core.setRendering(true);

for (1..@intCast(core.height)) |h| {
for (0..@intCast(core.width)) |w| {
if (handler.style != Style.default) {
if (checkSec(&core.height_gaps.?, h)) {
if (checkSec(&core.width_gaps.?, w)) {
continue;
}
}

const rand_int = switch (handler.mode) {
.binary => rand.int(u1),
.decimal => rand.uintLessThan(u8, 10),
.hexadecimal => rand.int(u4),
.textual => rand.uintLessThan(u8, 76),
};
for (0..@intCast(core.height)) |h| {
if (handler.style != Style.default) {
if (checkSec(&core.height_gaps.?, h)) {
continue;
}
}

const rand_int = switch (handler.mode) {
.binary => rand.int(u1),
.decimal => rand.uintLessThan(u8, 10),
.hexadecimal => rand.int(u4),
.textual => rand.uintLessThan(u8, 76),
};

var color = @intFromEnum(core.color);

const bold = rand.boolean();

if (core.pulse) {
const blank = @mod(rand.int(u8), 255);

// small probability
if (blank >= 254) {
core.bg = core.bg | tb.TB_REVERSE;
}
}

if (bold) {
color = color | tb.TB_BOLD;
}

var color = @intFromEnum(core.color);
const char: [:0]u8 = switch (handler.mode) {
.binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{rand_int}),
.hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[rand_int]}),
.textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[rand_int]}),
};

const bold = rand.boolean();
tbPrint(w, h, color, core.bg, char);

if (core.pulse) {
const blank = @mod(rand.int(u8), 255);
if (core.pulse) {
core.bg = tb.TB_DEFAULT;
}
}
}
} else {
// init columns
if (core.columns.?.items.len != core.width) {
for (0..@intCast(core.width)) |_| {
var column = Column.init(core.allocator);
try column.chars.append(null);
try core.columns.?.append(column);
}

// small probability
if (blank >= 254) {
core.bg = core.bg | tb.TB_REVERSE;
for (0..@intCast(core.width)) |w| {
for (0..@intCast(core.height)) |_| {
try core.columns.?.items[w].?.chars.append(null);
}
}
}

for (0..@intCast(core.width)) |w| {
if (rand.boolean()) continue;
if (core.columns.?.items[w].?.chars.items.len == core.height) {
const old_char = core.columns.?.items[w].?.chars.pop();
core.allocator.destroy(&old_char);
}

if (rand.uintLessThan(u3, 7) < 5) {
try core.columns.?.items[w].?.chars.insert(0, null);
continue;
}

const str_len = core.columns.?.items[w].?.strLen();

if ((str_len == 0) and rand.boolean()) {
try core.columns.?.items[w].?.chars.insert(0, null);
continue;
}

if (bold) {
color = color | tb.TB_BOLD;
if (str_len < @as(u32, @intCast(core.height)) / 2) {
if (str_len < 12) {
try core.columns.?.items[w].?.newChar(core, handler.mode, rand);
} else {
try core.columns.?.items[w].?.chars.insert(0, null);
}

try core.columns.?.items[w].?.newChar(core, handler.mode, rand);
} else {
try core.columns.?.items[w].?.chars.insert(0, null);
}
}

const char: [:0]u8 = switch (handler.mode) {
.binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{rand_int}),
.hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[rand_int]}),
.textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[rand_int]}),
};
for (0..core.columns.?.items.len) |w| {
h_loop: for (0..@intCast(core.height)) |h| {
const column_char = core.columns.?.items[w].?.chars.items[h];
if (column_char == null) {
continue :h_loop;
}

_ = tb.tb_print(@intCast(w), @intCast(h), @intCast(color), @intCast(core.bg), char);
const char: [:0]u8 = switch (handler.mode) {
.binary, .decimal => try std.fmt.bufPrintZ(&sbuf, "{d}", .{column_char.?.i}),
.hexadecimal => try std.fmt.bufPrintZ(&mbuf, "{c}", .{assets.hex_chars[column_char.?.i]}),
.textual => try std.fmt.bufPrintZ(&lbuf, "{u}", .{assets.tex_chars[column_char.?.i]}),
};

if (core.pulse) {
core.bg = tb.TB_DEFAULT;
tbPrint(w, h, column_char.?.b, column_char.?.p, char);
}
}
}

_ = tb.tb_present();
core.setRendering(false);
std.time.sleep(switch (handler.speed) {
.slow => FRAME * 2,
.fast => FRAME,
});
if (handler.style != .rain) {
std.time.sleep(switch (handler.speed) {
.slow => FRAME * 2,
.fast => FRAME,
});
} else {
std.time.sleep(switch (handler.speed) {
.slow => FRAME * 23,
.fast => FRAME * 5,
});
}
}
}

fn tbPrint(w: usize, h: usize, c: usize, b: usize, char: [*c]const u8) void {
_ = tb.tb_print(@intCast(w), @intCast(h), @intCast(c), @intCast(b), char);
}

fn animation(handler: *Handler, core: *Core) !void {
var prng = std.rand.DefaultPrng.init(@as(u64, @bitCast(std.time.milliTimestamp())));

Expand All @@ -277,6 +449,8 @@ fn getNthValues(number: i32, adv: u32, allocator: std.mem.Allocator) !std.ArrayL
var array = std.ArrayList(u32).init(allocator);
var val = adv;

try array.append(0);

while (val <= @as(u32, @intCast(number))) {
try array.append(val);
val += adv;
Expand Down Expand Up @@ -365,6 +539,24 @@ pub fn main() !void {
}
}

test "column" {
var prng = std.rand.DefaultPrng.init(1337);
const rand = prng.random();

var core = Core{ .allocator = std.testing.allocator };
core.start();

const column = Column.init(core.allocator);
try core.columns.?.append(column);
try core.columns.?.items[0].?.newChar(&core, Mode.decimal, rand);
try core.columns.?.items[0].?.newChar(&core, Mode.decimal, rand);

try std.testing.expect(core.columns.?.items[0].?.chars.items.len == 2);

core.active = false;
core.shutdown();
}

test "handler" {
var core = Core{ .allocator = std.testing.allocator, .active = true };
var handler = Handler{ .duration = 1 };
Expand Down Expand Up @@ -403,8 +595,9 @@ test "sections" {
const array = try getNthValues(12, 4, std.testing.allocator);
defer array.deinit();

try std.testing.expect(array.items[0] == 4);
try std.testing.expect(array.items[1] == 8);
try std.testing.expect(array.items[2] == 12);
try std.testing.expect(array.items.len == 3);
try std.testing.expect(array.items[0] == 0);
try std.testing.expect(array.items[1] == 4);
try std.testing.expect(array.items[2] == 8);
try std.testing.expect(array.items[3] == 12);
try std.testing.expect(array.items.len == 4);
}
7 changes: 7 additions & 0 deletions test/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,10 @@ test "style: blocks" {

try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 });
}

test "style: rain" {
const argv = [_][]const u8{ exe_path, "--time=1", "-s=rain" };
const term = try runner(argv);

try std.testing.expectEqual(term, std.process.Child.Term{ .Exited = 0 });
}