mirror of https://github.com/fairyglade/ly.git
185 lines
5.9 KiB
Zig
185 lines
5.9 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const Random = std.Random;
|
|
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
|
|
const utils = @import("../tui/utils.zig");
|
|
|
|
const interop = @import("../interop.zig");
|
|
const termbox = interop.termbox;
|
|
|
|
pub const FRAME_DELAY: usize = 8;
|
|
|
|
// Allowed codepoints
|
|
pub const MIN_CODEPOINT: u16 = 33;
|
|
pub const MAX_CODEPOINT: u16 = 123 - MIN_CODEPOINT;
|
|
|
|
// Characters change mid-scroll
|
|
pub const MID_SCROLL_CHANGE = true;
|
|
|
|
const Matrix = @This();
|
|
|
|
pub const Dot = struct {
|
|
value: ?usize,
|
|
is_head: bool,
|
|
};
|
|
|
|
pub const Line = struct {
|
|
space: usize,
|
|
length: usize,
|
|
update: usize,
|
|
};
|
|
|
|
allocator: Allocator,
|
|
terminal_buffer: *TerminalBuffer,
|
|
dots: []Dot,
|
|
lines: []Line,
|
|
frame: usize,
|
|
count: usize,
|
|
fg_ini: u32,
|
|
|
|
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u32) !Matrix {
|
|
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
|
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
|
|
|
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
|
|
|
|
return .{
|
|
.allocator = allocator,
|
|
.terminal_buffer = terminal_buffer,
|
|
.dots = dots,
|
|
.lines = lines,
|
|
.frame = 3,
|
|
.count = 0,
|
|
.fg_ini = fg_ini,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: Matrix) void {
|
|
self.allocator.free(self.dots);
|
|
self.allocator.free(self.lines);
|
|
}
|
|
|
|
pub fn realloc(self: *Matrix) !void {
|
|
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
|
|
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
|
|
|
|
initBuffers(dots, lines, self.terminal_buffer.width, self.terminal_buffer.height, self.terminal_buffer.random);
|
|
|
|
self.dots = dots;
|
|
self.lines = lines;
|
|
}
|
|
|
|
pub fn draw(self: *Matrix) void {
|
|
const buf_height = self.terminal_buffer.height;
|
|
const buf_width = self.terminal_buffer.width;
|
|
self.count += 1;
|
|
if (self.count > FRAME_DELAY) {
|
|
self.frame += 1;
|
|
if (self.frame > 4) self.frame = 1;
|
|
self.count = 0;
|
|
|
|
var x: usize = 0;
|
|
while (x < self.terminal_buffer.width) : (x += 2) {
|
|
var tail: usize = 0;
|
|
var line = &self.lines[x];
|
|
if (self.frame <= line.update) continue;
|
|
|
|
if (self.dots[x].value == null and self.dots[self.terminal_buffer.width + x].value == ' ') {
|
|
if (line.space > 0) {
|
|
line.space -= 1;
|
|
} else {
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
const h = self.terminal_buffer.height;
|
|
line.length = @mod(randint, h - 3) + 3;
|
|
self.dots[x].value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
|
|
line.space = @mod(randint, h + 1);
|
|
}
|
|
}
|
|
|
|
var y: usize = 0;
|
|
var first_col = true;
|
|
var seg_len: u64 = 0;
|
|
height_it: while (y <= buf_height) : (y += 1) {
|
|
var dot = &self.dots[buf_width * y + x];
|
|
// Skip over spaces
|
|
while (y <= buf_height and (dot.value == ' ' or dot.value == null)) {
|
|
y += 1;
|
|
if (y > buf_height) break :height_it;
|
|
dot = &self.dots[buf_width * y + x];
|
|
}
|
|
|
|
// Find the head of this column
|
|
tail = y;
|
|
seg_len = 0;
|
|
while (y <= buf_height and dot.value != ' ' and dot.value != null) {
|
|
dot.is_head = false;
|
|
if (MID_SCROLL_CHANGE) {
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
if (@mod(randint, 8) == 0) {
|
|
dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
|
|
}
|
|
}
|
|
|
|
y += 1;
|
|
seg_len += 1;
|
|
// Head's down offscreen
|
|
if (y > buf_height) {
|
|
self.dots[buf_width * tail + x].value = ' ';
|
|
break :height_it;
|
|
}
|
|
dot = &self.dots[buf_width * y + x];
|
|
}
|
|
|
|
const randint = self.terminal_buffer.random.int(u16);
|
|
dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
|
|
dot.is_head = true;
|
|
|
|
if (seg_len > line.length or !first_col) {
|
|
self.dots[buf_width * tail + x].value = ' ';
|
|
self.dots[x].value = null;
|
|
}
|
|
first_col = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
var x: usize = 0;
|
|
while (x < buf_width) : (x += 2) {
|
|
var y: usize = 1;
|
|
while (y <= self.terminal_buffer.height) : (y += 1) {
|
|
const dot = self.dots[buf_width * y + x];
|
|
|
|
var fg = self.fg_ini;
|
|
|
|
if (dot.value == null or dot.value == ' ') {
|
|
utils.putCell(x, y - 1, .{ .ch = ' ', .fg = fg, .bg = termbox.TB_DEFAULT });
|
|
continue;
|
|
}
|
|
|
|
if (dot.is_head) fg = @intCast(0x00FFFFFF | termbox.TB_BOLD); // White and bold
|
|
utils.putCell(x, y - 1, .{ .ch = @intCast(dot.value.?), .fg = fg, .bg = termbox.TB_DEFAULT });
|
|
}
|
|
}
|
|
}
|
|
|
|
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
|
|
var y: usize = 0;
|
|
while (y <= height) : (y += 1) {
|
|
var x: usize = 0;
|
|
while (x < width) : (x += 2) {
|
|
dots[y * width + x].value = null;
|
|
}
|
|
}
|
|
|
|
var x: usize = 0;
|
|
while (x < width) : (x += 2) {
|
|
var line = lines[x];
|
|
line.space = @mod(random.int(u16), height) + 1;
|
|
line.length = @mod(random.int(u16), height - 3) + 3;
|
|
line.update = @mod(random.int(u16), 3) + 1;
|
|
lines[x] = line;
|
|
|
|
dots[width + x].value = ' ';
|
|
}
|
|
}
|