From 0677c0fb4237bc8ce865f50e3bfeb398feb240a0 Mon Sep 17 00:00:00 2001 From: const-void Date: Sat, 15 Jan 2022 17:56:09 -0500 Subject: [PATCH] r2022 --- .gitignore | 4 + README.md | 35 +++ build.zig | 34 +++ src/main.zig | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 730 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7636a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +zig-cache +zig-out +.DS_Store + diff --git a/README.md b/README.md new file mode 100644 index 0000000..00b26de --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# DOOMFIRE +Test your TTY might! + +![demo](https://user-images.githubusercontent.com/76228776/149635702-a331f892-7799-4f7f-a4a6-7048d3529dcf.mp4) + +The doom-fire algo can push upwards of 180k a frame - results my vary! + +# 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! + +``` +$ git clone https://github.com/const-void/DOOM-fire-zig/ +$ cd DOOM-fire-zig +$ zig build run +... +``` + +# Results +* iTerm.app - ok +* kitty.app - great +* Terminal.app - poor +* VS Code - great + +# Inspiration / Credits +* doom fire - https://github.com/filipedeschamps/doom-fire-algorithm, https://github.com/fabiensanglard/DoomFirePSX/blob/master/flames.html +* color layout - https://en.wikipedia.org/wiki/ANSI_escape_code +* ansi codes - http://xfree86.org/current/ctlseqs.html +* str, zig - https://www.huy.rocks/everyday/01-04-2022-zig-strings-in-5-minutes +* emit - https://zig.news/kristoff/where-is-print-in-zig-57e9 +* term sz, zig - https://github.com/jessrud/zbox/blob/master/src/prim.zig +* osx term sz - https://github.com/sindresorhus/macos-term-size/blob/main/term-size.c +* px char - https://github.com/cronvel/terminal-kit + + + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..5068f9c --- /dev/null +++ b/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("DOOM-fire", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_tests = b.addTest("src/main.zig"); + exe_tests.setTarget(target); + exe_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&exe_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..1ef9922 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,657 @@ +// TEST YOUR (TTY) MIGHT: DOOM FIRE! +// (c) 2022 const*void +const std = @import("std"); +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 +// slow - Terminal.app +/////////////////////////////////// + +/////////////////////////////////// +// credits / helpful articles / inspirations +/////////////////////////////////// + +// doom fire - https://github.com/filipedeschamps/doom-fire-algorithm +// - https://github.com/fabiensanglard/DoomFirePSX/blob/master/flames.html +// color layout - https://en.wikipedia.org/wiki/ANSI_escape_code +// ansi codes - http://xfree86.org/current/ctlseqs.html +// str, zig - https://www.huy.rocks/everyday/01-04-2022-zig-strings-in-5-minutes +// emit - https://zig.news/kristoff/where-is-print-in-zig-57e9 +// term sz, zig - https://github.com/jessrud/zbox/blob/master/src/prim.zig +// osx term sz - https://github.com/sindresorhus/macos-term-size/blob/main/term-size.c +// px char - https://github.com/cronvel/terminal-kit + +/////////////////////////////////// +// zig hints +/////////////////////////////////// +// do or do not - there is no try: catch unreadable instead of try on memory / file io +// - put all try to initXXX() +// for (i=0; i var i=0; while (i, resize and try again."); + emit(line_clear_to_eol); + emit(color_reset); + emit("\n\nContinue?\n\n"); + + //assume ok...pause will exit for us. + pause(); + + //clear all the warning text and keep on trucking! + emit(color_reset); + emit(cursor_home); + emit(screen_clear); + } +} + +/// Part II - Show terminal capabilities +/// +/// Since user terminals vary in capabilities, handy to have a screen that renders ACTUAL colors +/// and exercises various terminal commands prior to DOOM fire. +/// + +pub fn showTermSz() void { + //todo - show os, os ver, zig ver + emit_fmt("Screen size: {d}w x {d}h\n\n", .{term_sz.width, term_sz.height}); +} + +pub fn showLabel(label:[]const u8) void { + emit_fmt("{s}{s}:\n",.{color_def,label}); +} + +pub fn showStdColors() void { + showLabel("Standard colors"); + + //first 8 colors (standard) + emit(fg[15]); + var color_idx:u8=0; + while (color_idx<8) { + defer color_idx+=1; + emit(bg[color_idx]); + if (color_idx==7) { emit(fg[0]); } + emit_fmt("{u} {d:2} ", .{sep,color_idx}); + } + emit(nl); + + //next 8 colors ("hilight") + emit(fg[15]); + while (color_idx<16) { + defer color_idx+=1; + emit(bg[color_idx]); + if (color_idx==15) { emit(fg[0]); } + emit_fmt("{u} {d:2} ", .{sep,color_idx}); + + } + + emit(nl); + emit(nl); +} + +pub fn show216Colors() void { + showLabel("216 colors"); + + var color_addendum:u8=0; + var bg_idx:u8=0; + var fg_idx:u8=0; + + //show remaining of colors in 6 blocks of 6x6 + + // 6 rows of color + var color_shift:u8=0; + while (color_shift<6) { + defer color_shift+=1; + + color_addendum=color_shift*36+16; + + // colors are pre-organized into blocks + var color_idx:u8=0; + while(color_idx<36) { + defer color_idx+=1; + bg_idx=color_idx+color_addendum; + + // invert color id for readability + 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(nl); + } + emit(nl); +} + +pub fn showGrayscale() void { + showLabel("Grayscale"); + + var fg_idx:u8=15; + emit(fg[fg_idx]); + + var bg_idx:u16=232; + while (bg_idx<256) { + defer bg_idx+=1; + + if (bg_idx>243) { + fg_idx=0; + emit(fg[fg_idx]); + } + + emit(bg[bg_idx]); + emit_fmt("{u}{d} ",.{sep,bg_idx}); + } + emit(nl); + + //cleanup + emit(color_def); + emit(nl); +} + +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; + + //init marquee background + emit(cursor_save); + emit(bg[bg_idx]); + 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 + + //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; + + var fade_idx:u8=0; + var txt_idx:u8=0; + + while (txt_idx0) { + defer fade_idx-=1; + + //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(line_clear_to_eol); + emit(nl); + emit(txt[txt_idx*2+1]); + emit(line_clear_to_eol); + emit(nl); + std.time.sleep(10 * std.time.ns_per_ms); + } + } +} + +// prove out terminal implementation by rendering colors and some simple animations +pub fn showTermCap() void { + showTermSz(); + showStdColors(); + show216Colors(); + showGrayscale(); + scrollMarquee(); + + pause(); +} + +/// DOOM Fire +/// Slowest - raw emit() +/// Slower - raw emit() + \n +/// Below - moderately faster + +//pixel character +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; + +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; + + bs=allocator.alloc(u8,bs_sz*2) catch unreachable; + resetBuf(); +} + +//reset buffer indexes to start of buffer +pub fn resetBuf() void { + bs_idx=0; + bs_len=0; +} + +//copy input string to buffer string +pub fn drawBuf(s:[]const u8) void { + for (s) |b| { + bs[bs_idx] = b; + 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]); + 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; + } + 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; + } + emit(fg[0]); + emit_fmt("mem: {s:.2} min / {s:.2} avg / {s:.2} max",.{std.fmt.fmtIntSizeBin(bs_sz_min), std.fmt.fmtIntSizeBin(bs_sz_avg), std.fmt.fmtIntSizeBin(bs_sz_max)}); +} + +// initBuf(); defer freeBuf(); +pub fn freeBuf() void { allocator.free(bs); } + + +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; + + //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; + + //screen buf default color is black + 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 { + spread_rnd_idx = rand.intRangeAtMost(u8, 0, 3); + 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); + } + } + } + } + + //paint fire buf + resetBuf(); + drawBuf(init_frame); + + // for each row + frame_y=0; + while (frame_y 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]; + + // only *update* color if prior color is actually diff + if (px_lo!=px_prev_lo) { + drawBuf(bg[fire_palette[px_lo]]); + } + 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; + } + drawBuf(nl); + } + paintBuf(); + resetBuf(); + } +} + +/////////////////////////////////// +// main +/////////////////////////////////// + +pub fn main() anyerror!void { + try initTerm(); + defer complete(); + + checkTermSz(); + showTermCap(); + showDoomFire(); + +}