diff --git a/README.md b/README.md index 6b45a27..421cddd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # DOOMFIRE -Test your TTY might! +## Test your TTY's might! ![demo](https://user-images.githubusercontent.com/76228776/149635702-a331f892-7799-4f7f-a4a6-7048d3529dcf.mp4) @@ -8,7 +8,10 @@ The doom-fire algo can push upwards of 180k a frame - results may vary! It is, As a comparable, this is the younger sibling of a node variant ( https://github.com/const-void/DOOM-fire-node ). # INSTALL -Tested on OX Monterey / M1 w/zig 0.9...unsure if it will work on Win or Linux, sadly, due to TIOCGWINSZ flag. No third party dependencies! +Tested on OX Monterey / M1 w/zig 0.9... + +EDIT: Now tested on Artix Linux - links against libc to get the size of the TTY. + ``` $ git clone https://github.com/const-void/DOOM-fire-zig/ @@ -22,6 +25,7 @@ $ zig build run * kitty.app - great * Terminal.app - poor -- seems to drop framerates * VS Code - great +* Alacritty (artix linux) - great # Inspiration / Credits * doom fire - https://github.com/filipedeschamps/doom-fire-algorithm, https://github.com/fabiensanglard/DoomFirePSX/blob/master/flames.html diff --git a/build.zig b/build.zig index 5068f9c..13c66c4 100644 --- a/build.zig +++ b/build.zig @@ -12,6 +12,9 @@ pub fn build(b: *std.build.Builder) void { const mode = b.standardReleaseOptions(); const exe = b.addExecutable("DOOM-fire", "src/main.zig"); + + exe.addIncludeDir("src"); + exe.linkLibC(); exe.setTarget(target); exe.setBuildMode(mode); exe.install(); diff --git a/src/main.zig b/src/main.zig index f3c992f..8541eb2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,18 +1,23 @@ -// TEST YOUR (TTY) MIGHT: DOOM FIRE! +// TEST YOUR (TTY) MIGHT: DOOM FIRE! // (c) 2022 const void* // // Copy/paste as it helps! // const std = @import("std"); -const allocator = std.heap.page_allocator; +// Gets the correct TIOCGWINSZ value from libc. +const c = @cImport({ + @cInclude("sys/ioctl.h"); +}); + +const allocator = std.heap.page_allocator; const stdout = std.io.getStdOut().writer(); const stdin = std.io.getStdIn().reader(); /////////////////////////////////// -// Tested on M1 osx12.1 -// fast - vs code terminal +// Tested on M1 osx12.1 + Linux +// fast - vs code terminal // slow - Terminal.app /////////////////////////////////// @@ -42,11 +47,11 @@ const stdin = std.io.getStdIn().reader(); /////////////////////////////////// //// consts, vars, settings -var rand:std.rand.Random = undefined; +var rand: std.rand.Random = undefined; //// functions -// seed & prep for rng +// seed & prep for rng pub fn initRNG() !void { //rnd setup -- https://ziglearn.org/chapter-2/#random-numbers var prng = std.rand.DefaultPrng.init(blk: { @@ -59,14 +64,16 @@ pub fn initRNG() !void { // print pub fn emit(s: []const u8) void { - const sz=stdout.write(s) catch unreachable; - if (sz==0) {return;} // cauze I c + const sz = stdout.write(s) catch unreachable; + if (sz == 0) { + return; + } // cauze I c return; } // format a string then print pub fn emit_fmt(comptime s: []const u8, args: anytype) void { - const t=std.fmt.allocPrint(allocator,s,args) catch unreachable; + const t = std.fmt.allocPrint(allocator, s, args) catch unreachable; defer allocator.free(t); emit(t); } @@ -77,63 +84,63 @@ pub fn emit_fmt(comptime s: []const u8, args: anytype) void { //// Settings -//OSX specific ... maybe -const TIOCGWINSZ=0x40087468; //ioctl flag +// Get this from libc instead of some random value. +const TIOCGWINSZ = c.TIOCGWINSZ; //term size const TermSz = struct { height: usize, width: usize }; -var term_sz:TermSz = .{ .height=0, .width=0 }; // set via initTermSz +var term_sz: TermSz = .{ .height = 0, .width = 0 }; // set via initTermSz //ansi escape codes const esc = "\x1B"; -const csi = esc++"["; +const csi = esc ++ "["; -const cursor_save=esc++"7"; -const cursor_load=esc++"8"; +const cursor_save = esc ++ "7"; +const cursor_load = esc ++ "8"; -const cursor_show=csi++"?25h"; //h=high -const cursor_hide=csi++"?25l"; //l=low -const cursor_home=csi++"1;1H"; //1,1 +const cursor_show = csi ++ "?25h"; //h=high +const cursor_hide = csi ++ "?25l"; //l=low +const cursor_home = csi ++ "1;1H"; //1,1 -const screen_clear=csi++"2J"; -const screen_buf_on=csi++"?1049h"; //h=high -const screen_buf_off=csi++"?1049l"; //l=low +const screen_clear = csi ++ "2J"; +const screen_buf_on = csi ++ "?1049h"; //h=high +const screen_buf_off = csi ++ "?1049l"; //l=low -const line_clear_to_eol=csi++"0K"; +const line_clear_to_eol = csi ++ "0K"; -const color_reset=csi++"0m"; -const color_fg="38;5;"; -const color_bg="48;5;"; +const color_reset = csi ++ "0m"; +const color_fg = "38;5;"; +const color_bg = "48;5;"; -const color_fg_def=csi++color_fg++"15m"; // white -const color_bg_def=csi++color_bg++"0m"; // black -const color_def=color_bg_def++color_fg_def; -const color_italic=csi++"3m"; -const color_not_italic=csi++"23m"; +const color_fg_def = csi ++ color_fg ++ "15m"; // white +const color_bg_def = csi ++ color_bg ++ "0m"; // black +const color_def = color_bg_def ++ color_fg_def; +const color_italic = csi ++ "3m"; +const color_not_italic = csi ++ "23m"; -const term_on=screen_buf_on++cursor_hide++cursor_home++screen_clear++color_def; -const term_off=screen_buf_off++cursor_show++nl; +const term_on = screen_buf_on ++ cursor_hide ++ cursor_home ++ screen_clear ++ color_def; +const term_off = screen_buf_off ++ cursor_show ++ nl; //handy characters -const nl="\n"; -const sep='▏'; +const nl = "\n"; +const sep = '▏'; //colors -const MAX_COLOR=256; -const LAST_COLOR=MAX_COLOR-1; +const MAX_COLOR = 256; +const LAST_COLOR = MAX_COLOR - 1; -var fg:[MAX_COLOR][]u8 = undefined; -var bg:[MAX_COLOR][]u8 = undefined; +var fg: [MAX_COLOR][]u8 = undefined; +var bg: [MAX_COLOR][]u8 = undefined; //// functions // cache fg/bg ansi codes pub fn initColor() void { - var color_idx:u16=0; - while (color_idx17) { - fg_idx=0; - } - else { - fg_idx=15; + if (color_idx > 17) { + fg_idx = 0; + } else { + fg_idx = 15; } // display color emit(bg[bg_idx]); emit(fg[fg_idx]); - emit_fmt("{d:3}",.{bg_idx}); + emit_fmt("{d:3}", .{bg_idx}); } - emit(nl); + emit(nl); } - emit(nl); + emit(nl); } pub fn showGrayscale() void { showLabel("Grayscale"); - var fg_idx:u8=15; + var fg_idx: u8 = 15; emit(fg[fg_idx]); - var bg_idx:u16=232; - while (bg_idx<256) { - defer bg_idx+=1; + var bg_idx: u16 = 232; + while (bg_idx < 256) { + defer bg_idx += 1; - if (bg_idx>243) { - fg_idx=0; + if (bg_idx > 243) { + fg_idx = 0; emit(fg[fg_idx]); } emit(bg[bg_idx]); - emit_fmt("{u}{d} ",.{sep,bg_idx}); - } + emit_fmt("{u}{d} ", .{ sep, bg_idx }); + } emit(nl); //cleanup @@ -349,9 +368,9 @@ pub fn showGrayscale() void { pub fn scrollMarquee() void { //marquee - 4 lines of yellowish background - const bg_idx:u8=222; - const marquee_row=line_clear_to_eol++nl; - const marquee_bg=marquee_row++marquee_row++marquee_row++marquee_row; + const bg_idx: u8 = 222; + const marquee_row = line_clear_to_eol ++ nl; + const marquee_bg = marquee_row ++ marquee_row ++ marquee_row ++ marquee_row; //init marquee background emit(cursor_save); @@ -359,31 +378,23 @@ pub fn scrollMarquee() void { emit(marquee_bg); //quotes - will confirm animations are working on current terminal - const txt =[_][]const u8{ - " Things move along so rapidly nowadays that people saying "++color_italic++"It can't be done"++color_not_italic++" are always being interrupted", - " by somebody doing it. "++color_italic++"-- Puck, 1902"++color_not_italic, - - " Test your might!", - " "++color_italic++"-- Mortal Kombat"++color_not_italic, - - " How much is the fish?", - " "++color_italic++"-- Scooter"++color_not_italic}; - const txt_len:u8 = txt.len/2; // print two rows at a time - + const txt = [_][]const u8{ " Things move along so rapidly nowadays that people saying " ++ color_italic ++ "It can't be done" ++ color_not_italic ++ " are always being interrupted", " by somebody doing it. " ++ color_italic ++ "-- Puck, 1902" ++ color_not_italic, " Test your might!", " " ++ color_italic ++ "-- Mortal Kombat" ++ color_not_italic, " How much is the fish?", " " ++ color_italic ++ "-- Scooter" ++ color_not_italic }; + const txt_len: u8 = txt.len / 2; // print two rows at a time + //fade txt in and out - const fade_seq=[_]u8{222,221,220,215,214,184,178,130,235,58,16}; - const fade_len:u8=fade_seq.len; + const fade_seq = [_]u8{ 222, 221, 220, 215, 214, 184, 178, 130, 235, 58, 16 }; + const fade_len: u8 = fade_seq.len; - var fade_idx:u8=0; - var txt_idx:u8=0; + var fade_idx: u8 = 0; + var txt_idx: u8 = 0; + + while (txt_idx < txt_len) { + defer txt_idx += 1; - while (txt_idx0) { - defer fade_idx-=1; + fade_idx = fade_len - 1; + while (fade_idx > 0) { + defer fade_idx -= 1; - //reset to 1,1 of marquee + //reset to 1,1 of marquee emit(cursor_load); emit(bg[bg_idx]); emit(nl); //print marquee txt emit(fg[fade_seq[fade_idx]]); - emit(txt[txt_idx*2]); + emit(txt[txt_idx * 2]); emit(line_clear_to_eol); emit(nl); - emit(txt[txt_idx*2+1]); + emit(txt[txt_idx * 2 + 1]); emit(line_clear_to_eol); emit(nl); std.time.sleep(10 * std.time.ns_per_ms); @@ -436,7 +446,7 @@ pub fn showTermCap() void { show216Colors(); showGrayscale(); scrollMarquee(); - + pause(); } @@ -446,109 +456,112 @@ pub fn showTermCap() void { /// Below - moderately faster //pixel character -const px="▀"; +const px = "▀"; //bs = buffer string -var bs:[]u8=undefined; -var bs_idx:u32=0; -var bs_len:u32=0; -var bs_sz_min:u32=0; -var bs_sz_max:u32=0; -var bs_sz_avg:u32=0; -var bs_frame_tic:u32=0; -var t_start:i64=0; -var t_now:i64=0; -var t_dur:f64=0.0; -var fps:f64=0.0; +var bs: []u8 = undefined; +var bs_idx: u32 = 0; +var bs_len: u32 = 0; +var bs_sz_min: u32 = 0; +var bs_sz_max: u32 = 0; +var bs_sz_avg: u32 = 0; +var bs_frame_tic: u32 = 0; +var t_start: i64 = 0; +var t_now: i64 = 0; +var t_dur: f64 = 0.0; +var fps: f64 = 0.0; - -pub fn initBuf() void { +pub fn initBuf() void { //some lazy guesswork to make sure we have enough of a buffer to render DOOM fire. - const px_char_sz=px.len; - const px_color_sz=bg[LAST_COLOR].len+fg[LAST_COLOR].len; - const px_sz=px_color_sz+px_char_sz; - const screen_sz:u64=@as(u64,px_sz*term_sz.width*term_sz.width); - const overflow_sz:u64=px_char_sz*100; - const bs_sz:u64=screen_sz+overflow_sz; + const px_char_sz = px.len; + const px_color_sz = bg[LAST_COLOR].len + fg[LAST_COLOR].len; + const px_sz = px_color_sz + px_char_sz; + const screen_sz: u64 = @as(u64, px_sz * term_sz.width * term_sz.width); + const overflow_sz: u64 = px_char_sz * 100; + const bs_sz: u64 = screen_sz + overflow_sz; - bs=allocator.alloc(u8,bs_sz*2) catch unreachable; - t_start=std.time.milliTimestamp(); + bs = allocator.alloc(u8, bs_sz * 2) catch unreachable; + t_start = std.time.milliTimestamp(); resetBuf(); -} +} //reset buffer indexes to start of buffer -pub fn resetBuf() void { - bs_idx=0; - bs_len=0; +pub fn resetBuf() void { + bs_idx = 0; + bs_len = 0; } //copy input string to buffer string -pub fn drawBuf(s:[]const u8) void { +pub fn drawBuf(s: []const u8) void { for (s) |b| { bs[bs_idx] = b; - bs_idx+=1; - bs_len+=1; + bs_idx += 1; + bs_len += 1; } - } +} //print buffer to string...can be a decent amount of text! -pub fn paintBuf() void { - emit(bs[0..bs_len-1]); - t_now=std.time.milliTimestamp(); - bs_frame_tic+=1; - if (bs_sz_min==0) { +pub fn paintBuf() void { + emit(bs[0 .. bs_len - 1]); + t_now = std.time.milliTimestamp(); + bs_frame_tic += 1; + if (bs_sz_min == 0) { //first frame - bs_sz_min=bs_len; - bs_sz_max=bs_len; - bs_sz_avg=bs_len; + bs_sz_min = bs_len; + bs_sz_max = bs_len; + bs_sz_avg = bs_len; + } else { + if (bs_len < bs_sz_min) { + bs_sz_min = bs_len; + } + if (bs_len > bs_sz_max) { + bs_sz_max = bs_len; + } + bs_sz_avg = bs_sz_avg * (bs_frame_tic - 1) / bs_frame_tic + bs_len / bs_frame_tic; } - else { - if( bs_len < bs_sz_min) { bs_sz_min=bs_len; } - if( bs_len > bs_sz_max) { bs_sz_max=bs_len; } - bs_sz_avg=bs_sz_avg*(bs_frame_tic-1)/bs_frame_tic+bs_len/bs_frame_tic; - } - - t_dur=@intToFloat(f64,t_now-t_start)/1000.0; - fps=@intToFloat(f64,bs_frame_tic)/t_dur; + + t_dur = @intToFloat(f64, t_now - t_start) / 1000.0; + fps = @intToFloat(f64, bs_frame_tic) / t_dur; emit(fg[0]); - emit_fmt("mem: {s:.2} min / {s:.2} avg / {s:.2} max [ {d:.2} fps ]",.{std.fmt.fmtIntSizeBin(bs_sz_min), std.fmt.fmtIntSizeBin(bs_sz_avg), std.fmt.fmtIntSizeBin(bs_sz_max), fps}); + emit_fmt("mem: {s:.2} min / {s:.2} avg / {s:.2} max [ {d:.2} fps ]", .{ std.fmt.fmtIntSizeBin(bs_sz_min), std.fmt.fmtIntSizeBin(bs_sz_avg), std.fmt.fmtIntSizeBin(bs_sz_max), fps }); } // initBuf(); defer freeBuf(); -pub fn freeBuf() void { allocator.free(bs); } +pub fn freeBuf() void { + allocator.free(bs); +} - -pub fn showDoomFire() void { +pub fn showDoomFire() void { //term size => fire size - const FIRE_H:u16=@intCast(u16,term_sz.height)*2; - const FIRE_W:u16=@intCast(u16,term_sz.width); - const FIRE_SZ:u16=FIRE_H*FIRE_W; - const FIRE_LAST_ROW:u16=(FIRE_H-1)*FIRE_W; + const FIRE_H: u16 = @intCast(u16, term_sz.height) * 2; + const FIRE_W: u16 = @intCast(u16, term_sz.width); + const FIRE_SZ: u16 = FIRE_H * FIRE_W; + const FIRE_LAST_ROW: u16 = (FIRE_H - 1) * FIRE_W; //colors - tinker w/palette as needed! - const fire_palette=[_]u8{0,233,234,52,53,88,89,94,95,96,130,131,132,133,172,214,215,220,220,221,3,226,227,230,195,230}; - const fire_black:u8=0; - const fire_white:u8=fire_palette.len-1; + const fire_palette = [_]u8{ 0, 233, 234, 52, 53, 88, 89, 94, 95, 96, 130, 131, 132, 133, 172, 214, 215, 220, 220, 221, 3, 226, 227, 230, 195, 230 }; + const fire_black: u8 = 0; + const fire_white: u8 = fire_palette.len - 1; //screen buf default color is black - var screen_buf:[]u8=undefined; //{fire_black}**FIRE_SZ; - screen_buf=allocator.alloc(u8,FIRE_SZ) catch unreachable; + var screen_buf: []u8 = undefined; //{fire_black}**FIRE_SZ; + screen_buf = allocator.alloc(u8, FIRE_SZ) catch unreachable; defer allocator.free(screen_buf); //init buffer - var buf_idx:u16=0; - while(buf_idx=FIRE_W) ) { - screen_buf[doFire_idx-FIRE_W]=0; - } - else { + //bounds checking + if ((spread_px == 0) and (doFire_idx >= FIRE_W)) { + screen_buf[doFire_idx - FIRE_W] = 0; + } else { spread_rnd_idx = rand.intRangeAtMost(u8, 0, 3); - if (doFire_idx>=(spread_rnd_idx+1)) { - spread_dst=doFire_idx-spread_rnd_idx+1; + if (doFire_idx >= (spread_rnd_idx + 1)) { + spread_dst = doFire_idx - spread_rnd_idx + 1; } - if (( spread_dst>=FIRE_W ) and (spread_px > (spread_rnd_idx & 1))) { - screen_buf[spread_dst-FIRE_W]=spread_px - (spread_rnd_idx & 1); + if ((spread_dst >= FIRE_W) and (spread_px > (spread_rnd_idx & 1))) { + screen_buf[spread_dst - FIRE_W] = spread_px - (spread_rnd_idx & 1); } } } } - + //paint fire buf resetBuf(); drawBuf(init_frame); // for each row - frame_y=0; - while (frame_y fg char) + // - "hi" (current px row => fg char) // - "low" (next row => bg color) - px_hi=screen_buf[frame_y*FIRE_W+frame_x]; - px_lo=screen_buf[(frame_y+1)*FIRE_W+frame_x]; - + px_hi = screen_buf[frame_y * FIRE_W + frame_x]; + px_lo = screen_buf[(frame_y + 1) * FIRE_W + frame_x]; + // only *update* color if prior color is actually diff - if (px_lo!=px_prev_lo) { + if (px_lo != px_prev_lo) { drawBuf(bg[fire_palette[px_lo]]); } - if (px_hi!=px_prev_hi) { + if (px_hi != px_prev_hi) { drawBuf(fg[fire_palette[px_hi]]); } drawBuf(px); //cache current colors - px_prev_hi=px_hi; - px_prev_lo=px_lo; + px_prev_hi = px_hi; + px_prev_lo = px_lo; } drawBuf(nl); //is this needed? - } + } paintBuf(); resetBuf(); } } /////////////////////////////////// -// main +// main /////////////////////////////////// pub fn main() anyerror!void { try initTerm(); defer complete(); - + checkTermSz(); showTermCap(); showDoomFire(); - }