diff --git a/args.h b/args.h index 9752a13..1270d2a 100644 --- a/args.h +++ b/args.h @@ -26,11 +26,17 @@ extern char *argv0; typedef struct Options { size_t refresh_rate; size_t animation_speed; - size_t max_heat_loss; + size_t heat_loss; size_t wind; size_t random_factor; bool random_wind; bool truecolor; + + uint32_t width; + uint32_t height; + + bool farbfeld_mode; + size_t ff_frame; } Options; /* use main(int argc, char *argv[]) */ diff --git a/colors.c b/colors.c new file mode 100644 index 0000000..da46907 --- /dev/null +++ b/colors.c @@ -0,0 +1,70 @@ +#ifndef COLORS_INCLUDED +#define COLORS_INCLUDED + +#include "colors.h" +#include "termbox.h" + +#define BLACK 0x000000 + +const struct tb_cell normcolors[13] = { + // default + { ' ', 9, 0 }, + + // red/black + { 0x2591, 2, 0 }, + { 0x2592, 2, 0 }, + { 0x2593, 2, 0 }, + { 0x2588, 2, 0 }, + + // yellow/red + { 0x2591, 4, 2 }, + { 0x2592, 4, 2 }, + { 0x2593, 4, 2 }, + { 0x2588, 4, 2 }, + + // white/red + { 0x2591, 8, 2 }, + { 0x2592, 8, 2 }, + { 0x2593, 8, 2 }, + { 0x2588, 8, 2 }, +}; + +const struct tb_cell truecolors[36] = { + { ' ', BLACK, 0x070707 }, + { ' ', BLACK, 0x1F0707 }, + { ' ', BLACK, 0x2F0F07 }, + { ' ', BLACK, 0x470F07 }, + { ' ', BLACK, 0x571707 }, + { ' ', BLACK, 0x671F07 }, + { ' ', BLACK, 0x771F07 }, + { ' ', BLACK, 0x8F2707 }, + { ' ', BLACK, 0x9F2F07 }, + { ' ', BLACK, 0xAF3F07 }, + { ' ', BLACK, 0xBF4707 }, + { ' ', BLACK, 0xC74707 }, + { ' ', BLACK, 0xDF4F07 }, + { ' ', BLACK, 0xDF5707 }, + { ' ', BLACK, 0xDF5707 }, + { ' ', BLACK, 0xD75F07 }, + { ' ', BLACK, 0xD7670F }, + { ' ', BLACK, 0xCF6F0F }, + { ' ', BLACK, 0xCF770F }, + { ' ', BLACK, 0xCF7F0F }, + { ' ', BLACK, 0xCF8717 }, + { ' ', BLACK, 0xC78717 }, + { ' ', BLACK, 0xC78F17 }, + { ' ', BLACK, 0xC7971F }, + { ' ', BLACK, 0xBF9F1F }, + { ' ', BLACK, 0xBF9F1F }, + { ' ', BLACK, 0xBFA727 }, + { ' ', BLACK, 0xBFA727 }, + { ' ', BLACK, 0xBFAF2F }, + { ' ', BLACK, 0xB7AF2F }, + { ' ', BLACK, 0xB7B737 }, + { ' ', BLACK, 0xCFCF6F }, + { ' ', BLACK, 0xDFDF9F }, + { ' ', BLACK, 0xEFEFC7 }, + { ' ', BLACK, 0xFFFFFF } +}; + +#endif diff --git a/colors.h b/colors.h index a53cb4e..175e125 100644 --- a/colors.h +++ b/colors.h @@ -3,74 +3,7 @@ #include "termbox.h" -#ifdef __OpenBSD__ -#include "sys/types.h" -#else -#include "stdint.h" -#endif - -#define BLACK 0x000000 - -struct tb_cell normcolors[13] = -{ - // default - { ' ', 9, 0 }, - - // red/black - { 0x2591, 2, 0 }, - { 0x2592, 2, 0 }, - { 0x2593, 2, 0 }, - { 0x2588, 2, 0 }, - - // yellow/red - { 0x2591, 4, 2 }, - { 0x2592, 4, 2 }, - { 0x2593, 4, 2 }, - { 0x2588, 4, 2 }, - - // white/red - { 0x2591, 8, 2 }, - { 0x2592, 8, 2 }, - { 0x2593, 8, 2 }, - { 0x2588, 8, 2 }, -}; - -struct tb_cell truecolors[36] = { - { ' ', BLACK, 0x070707 }, - { ' ', BLACK, 0x1F0707 }, - { ' ', BLACK, 0x2F0F07 }, - { ' ', BLACK, 0x470F07 }, - { ' ', BLACK, 0x571707 }, - { ' ', BLACK, 0x671F07 }, - { ' ', BLACK, 0x771F07 }, - { ' ', BLACK, 0x8F2707 }, - { ' ', BLACK, 0x9F2F07 }, - { ' ', BLACK, 0xAF3F07 }, - { ' ', BLACK, 0xBF4707 }, - { ' ', BLACK, 0xC74707 }, - { ' ', BLACK, 0xDF4F07 }, - { ' ', BLACK, 0xDF5707 }, - { ' ', BLACK, 0xDF5707 }, - { ' ', BLACK, 0xD75F07 }, - { ' ', BLACK, 0xD7670F }, - { ' ', BLACK, 0xCF6F0F }, - { ' ', BLACK, 0xCF770F }, - { ' ', BLACK, 0xCF7F0F }, - { ' ', BLACK, 0xCF8717 }, - { ' ', BLACK, 0xC78717 }, - { ' ', BLACK, 0xC78F17 }, - { ' ', BLACK, 0xC7971F }, - { ' ', BLACK, 0xBF9F1F }, - { ' ', BLACK, 0xBF9F1F }, - { ' ', BLACK, 0xBFA727 }, - { ' ', BLACK, 0xBFA727 }, - { ' ', BLACK, 0xBFAF2F }, - { ' ', BLACK, 0xB7AF2F }, - { ' ', BLACK, 0xB7B737 }, - { ' ', BLACK, 0xCFCF6F }, - { ' ', BLACK, 0xDFDF9F }, - { ' ', BLACK, 0xEFEFC7 }, - { ' ', BLACK, 0xFFFFFF } -}; +extern struct tb_cell normcolors[13]; +extern struct tb_cell truecolors[36]; #endif diff --git a/draw.c b/draw.c index 7368bdb..668969f 100644 --- a/draw.c +++ b/draw.c @@ -22,11 +22,11 @@ extern struct Options *opts; // initialize the framebuffer void -init(struct buffer *buf) +init(struct buffer *buf, uint16_t width, uint16_t height) { // initialize width/height of terminal - buf->width = tb_width(); - buf->height = tb_height(); + buf->width = width; + buf->height = height; size_t len = buf->width * buf->height; buf->buf = (uint8_t*) calloc(len, sizeof(uint8_t)); @@ -49,21 +49,26 @@ void dofire(struct buffer *buf) { size_t src; - size_t random = (rand() % 7) & 3; + size_t rnd_wind = (lrand48() % 7) & 3; + size_t rnd_lose = lrand48() % 100; + size_t rnd_loss = (lrand48() % 7) & 3; size_t dest; struct tb_cell *realbuf = tb_cell_buffer(); for (size_t x = 0; x < buf->width; ++x) { for (size_t y = 1; y < buf->height; ++y) { - if ((rand() % opts->random_factor) == 0) { - random = (rand() % 7) & 3; + // TODO; test rngs + if ((lrand48() % opts->random_factor) == 0) { + rnd_wind = (lrand48() % 7) & 3; + rnd_lose = lrand48() % 100; + rnd_loss = (lrand48() % 7) & 3; } src = y * buf->width + x; if (opts->random_wind) { - dest = src - random + opts->wind; + dest = src - rnd_wind + opts->wind; } else { dest = src + opts->wind; } @@ -84,13 +89,21 @@ dofire(struct buffer *buf) dest -= buf->width; } - size_t loss = MIN(opts->max_heat_loss, 3); - buf->buf[dest] = MAX(buf->buf[src] - (random & loss), 0); + size_t loss = rnd_lose < opts->heat_loss ? 2 : 0; + buf->buf[dest] = MAX(buf->buf[src] - (rnd_loss & loss), 0); if (buf->buf[dest] > max_value) { buf->buf[dest] = 0; } + // TODO: comment everything + + // copy from our buffer to termbox's buffer + // unless, of course, our buffer is bigger + if (src >= tb_width() * tb_height()) { + continue; + } + realbuf[dest] = colors[buf->buf[dest]]; realbuf[src] = colors[buf->buf[src]]; } @@ -103,5 +116,4 @@ void cleanup(struct buffer *buf) { free(buf->buf); - tb_shutdown(); } diff --git a/draw.h b/draw.h index b556cb7..bd3d138 100644 --- a/draw.h +++ b/draw.h @@ -14,7 +14,7 @@ struct buffer { uint8_t* buf; }; -void init(struct buffer *buf); +void init(struct buffer *buf, uint16_t width, uint16_t height); void dofire(struct buffer *buf); void cleanup(struct buffer *buf); diff --git a/ff.c b/ff.c new file mode 100644 index 0000000..34278e9 --- /dev/null +++ b/ff.c @@ -0,0 +1,54 @@ +/* + * winsock.h on windows, + * arpa/inet.h on unix + * for htons() + */ +#if defined(_WIN32) || defined(__WIN32__) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include "termbox.h" +#include "bool.h" +#include "ff.h" + +static uint16_t +clr(uint32_t clr) +{ + uint16_t nclr = (((uint16_t) clr) & 0xff) & 0xffff; + return nclr | (nclr << 8); +} + +void +ff_from_tbscr(uint32_t width, uint32_t height, uint8_t *img, struct tb_cell *colors, FILE *fp) +{ + /* write farbfeld headers */ + fputs("farbfeld", fp); + uint32_t tmp; + tmp = htonl(width); + fwrite(&tmp, sizeof(tmp), 1, fp); + tmp = htonl(height); + fwrite(&tmp, sizeof(tmp), 1, fp); + + /* write image row by row */ + uint16_t *rowbuf = malloc(width * (4 * sizeof(uint16_t))); + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + uint32_t color = colors[img[y * width + x]].bg; + + rowbuf[4 * x + 0] = htons(clr(color >> 16)); + rowbuf[4 * x + 1] = htons(clr(color >> 8)); + rowbuf[4 * x + 2] = htons(clr(color)); + rowbuf[4 * x + 3] = htons((uint16_t) 0xffff); + } + + fwrite(rowbuf, sizeof(uint16_t), width * 4, fp); + memset(rowbuf, 0, width * (4 * sizeof(uint16_t))); + } +} diff --git a/ff.h b/ff.h new file mode 100644 index 0000000..d242698 --- /dev/null +++ b/ff.h @@ -0,0 +1,15 @@ +#ifndef FF_H +#define FF_H + +#include +#include +#include "termbox.h" +#include "bool.h" + +void ff_from_tbscr( + uint32_t width, uint32_t height, + uint8_t *img, struct tb_cell *colors, + FILE *fp +); + +#endif diff --git a/main.c b/main.c index 2109a84..1f54a51 100644 --- a/main.c +++ b/main.c @@ -1,10 +1,16 @@ +#include #include +#include +#include #include "bool.h" #include "output.h" #include "draw.h" +#include "colors.h" +#include "terminfo.h" #include "termbox.h" #include "args.h" +#include "ff.h" #ifdef __OpenBSD__ #include "sys/types.h" @@ -31,15 +37,29 @@ main(int argc, char *argv[]) opts->refresh_rate = 1; size_t output_mode = TB_OUTPUT_NORMAL; opts->truecolor = FALSE; - opts->max_heat_loss = 1; + opts->heat_loss = 45; opts->wind = 1; opts->random_wind = TRUE; opts->random_factor = 4; + opts->width = ttywidth(); + opts->height = ttyheight(); // argument parsing argv0 = argv[0]; ARGBEGIN { + case 'F': + opts->farbfeld_mode = TRUE; + opts->ff_frame = atoi(ARGF()); + output_mode = TB_OUTPUT_TRUECOLOR; + opts->truecolor = TRUE; + break; + case 'H': + opts->height = atoi(ARGF()); + break; + case 'W': + opts->width = atoi(ARGF()); + break; case 't': output_mode = TB_OUTPUT_TRUECOLOR; opts->truecolor = TRUE; @@ -51,7 +71,7 @@ main(int argc, char *argv[]) opts->refresh_rate = atoi(ARGF()); break; case 'l': - opts->max_heat_loss = atoi(ARGF()); + opts->heat_loss = atoi(ARGF()); break; case 'w': opts->wind = atoi(ARGF()); @@ -62,6 +82,7 @@ main(int argc, char *argv[]) case 'f': opts->random_factor = atoi(ARGF()); break; + case 'v': case 'V': printf("%s %s\n", argv0, VERSION); return 0; @@ -74,7 +95,7 @@ main(int argc, char *argv[]) printf(" Higher values == slower refresh rate.\n"); printf(" -s [speed] Change animation speed. (default: 5)\n"); printf(" Higher values == slower animation.\n"); - printf(" -l [loss] Maximum heat loss for each row upward. (default: 1)\n"); + printf(" -l [loss] Heat loss for each row upward. (default: 45)\n"); printf(" Higher values will lead to a smaller fire.\n"); printf(" -w [wind] Wind. Negative values, or values less than one will\n"); printf(" cause the fire to be blown west. (default: 1)\n"); @@ -99,14 +120,15 @@ main(int argc, char *argv[]) } ARGEND // initialize termbox - tb_init(); + tb_init(); /* check return value */ tb_select_output_mode(output_mode); tb_clear(); + struct buffer buf; struct tb_event e; // initialize drawing - init(&buf); + init(&buf, opts->width, opts->height); uint64_t ctr = 0; @@ -117,7 +139,24 @@ main(int argc, char *argv[]) // update framebuffer dofire(&buf); - if ((ctr % opts->refresh_rate) != 0) { + /* check if we should print the image now */ + if (opts->farbfeld_mode && ctr >= opts->ff_frame) { + tb_shutdown(); /* shutdown so we can output the image to stdout */ + + if (isatty(STDOUT_FILENO)) { + EPRINT("fire: cowardly refusing to write farbfeld image to the terminal.\n"); + return 1; + } + + ff_from_tbscr(opts->width, opts->height, buf.buf, + (struct tb_cell*) &truecolors, stdout); + + cleanup(&buf); + return 0; + } + + /* don't display or check for events if not on refresh frame */ + if ((ctr % opts->refresh_rate) != 0 || opts->farbfeld_mode) { continue; } @@ -133,6 +172,7 @@ main(int argc, char *argv[]) // handle keypresses // q, ctrl+c, ctrl+d => quit + /* TODO: handle resizes */ if (e.type == TB_EVENT_KEY) { switch (e.ch) { case 'q': @@ -153,5 +193,6 @@ main(int argc, char *argv[]) cleanup: cleanup(&buf); + tb_shutdown(); return 0; } diff --git a/makefile b/makefile index aeab7e5..9ee869f 100644 --- a/makefile +++ b/makefile @@ -11,11 +11,11 @@ WARNING = -Wall -Wextra -pedantic -Wmissing-prototypes \ INC = -Isub/termbox_next/src CC = gcc -CFLAGS = -std=c99 $(WARNING) $(INC) -fsanitize=address -Og -ggdb -LDFLAGS = +CFLAGS = -std=c99 -O0 -ggdb $(WARNING) $(INC) #-fsanitize=address +LDFLAGS = -static TRMBOX = sub/termbox_next/bin/termbox.a -SRC = main.c draw.c +SRC = main.c draw.c ff.c colors.c terminfo.c OBJ = $(SRC:.c=.o) DESTDIR = / diff --git a/terminfo.c b/terminfo.c new file mode 100644 index 0000000..d98b8de --- /dev/null +++ b/terminfo.c @@ -0,0 +1,95 @@ +/* + * terminfo.c: get TTY/console's buffer + * height and width. + * + * TODO: test this on Windows 10. + * + * why is all Windows programming so + * damn complicated. + * + * Thanks to this SO question for Windows stuff: + * stackoverflow.com/questions/6812224/getting-terminal-size-in-c-for-windows + * + * (c) Kiëd Llaentenn and contributors + * See the LICENSE.md file for copyright + * information. + */ + +#include +#include + +#include "terminfo.h" + +#if defined(_WIN32) || defined(__WIN32__) +#define WOE_IS_ME +#endif + +#ifdef WOE_IS_ME +#include +#else +#include +#include +#endif + +/* + * sizes to fall back to if: + * 1) stdout isn't a tty + * 2) if on Windows (temporary, only until win32 + * support it added + */ +const uint16_t fallback_width = 80; +const uint16_t fallback_height = 24; + +/* file descriptor to get dimensions of */ +#ifdef WOE_IS_ME +CONST DWORD fd = STD_OUTPUT_HANDLE; +#else +const size_t fd = STDOUT_FILENO; +#endif + +uint16_t +ttywidth(void) +{ +#ifdef WOE_IS_ME + /* why on earth does _isatty return NONZERO + * if FD is a tty?! */ + if (_isatty(fd)) + return fallback_width; + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo( + GetStdHandle(fd), &csbi + ); + + return csbi.srWindow.Right - csbi.srWindow.Left + 1; +#else + if (!isatty(fd)) + return fallback_width; + + struct winsize w; + ioctl(fd, TIOCGWINSZ, &w); + return w.ws_col; +#endif +} + +uint16_t +ttyheight(void) +{ +#ifdef WOE_IS_ME + if (_isatty(fd)) + return fallback_height; + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo( + GetStdHandle(fd), &csbi + ); + + return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +#else + if (!isatty(fd)) + return fallback_height; + + struct winsize w; + ioctl(fd, TIOCGWINSZ, &w); + + return w.ws_row; +#endif +} diff --git a/terminfo.h b/terminfo.h new file mode 100644 index 0000000..1633781 --- /dev/null +++ b/terminfo.h @@ -0,0 +1,18 @@ +/* + * terminfo.h: get TTY/console's buffer + * height and width. + * + * (c) Kiëd Llaentenn and contributors + * See the LICENSE.md file for copyright + * information. + */ + +#ifndef TERMINFO_H +#define TERMINFO_H + +#include + +uint16_t ttywidth(void); +uint16_t ttyheight(void); + +#endif