From 175b80bd9881f4ad6a23c239fb878b33614453ea Mon Sep 17 00:00:00 2001 From: WormHeamer Date: Mon, 3 Nov 2025 23:06:41 -0500 Subject: initial commit --- main.c | 449 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 main.c (limited to 'main.c') diff --git a/main.c b/main.c new file mode 100644 index 0000000..be9fea8 --- /dev/null +++ b/main.c @@ -0,0 +1,449 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* TODO + * + * - utf-8 input + * - arrow keys + * - KEY_RESIZE or draw callback + * - maybe some custom colors + */ + +typedef enum { + FG_BLACK = 0, + FG_RED = 1, + FG_GREEN = 2, + FG_YELLOW = 3, + FG_BLUE = 4, + FG_MAGENTA = 5, + FG_CYAN = 6, + FG_WHITE = 7, + + FG_BBLACK = FG_BLACK + 8, + FG_BRED = FG_RED + 8, + FG_BGREEN = FG_GREEN + 8, + FG_BYELLOW = FG_YELLOW + 8, + FG_BBLUE = FG_BLUE + 8, + FG_BMAGENTA = FG_MAGENTA + 8, + FG_BCYAN = FG_CYAN + 8, + FG_BWHITE = FG_WHITE + 8, + + BG_BLACK = FG_BLACK << 4, + BG_RED = FG_RED << 4, + BG_GREEN = FG_GREEN << 4, + BG_YELLOW = FG_YELLOW << 4, + BG_BLUE = FG_BLUE << 4, + BG_MAGENTA = FG_MAGENTA << 4, + BG_CYAN = FG_CYAN << 4, + BG_WHITE = FG_WHITE << 4, + + BG_BBLACK = FG_BBLACK << 4, + BG_BRED = FG_BRED << 4, + BG_BGREEN = FG_BGREEN << 4, + BG_BYELLOW = FG_BYELLOW << 4, + BG_BBLUE = FG_BBLUE << 4, + BG_BMAGENTA = FG_BMAGENTA << 4, + BG_BCYAN = FG_BCYAN << 4, + BG_BWHITE = FG_BWHITE << 4, + + A_BOLD = 1 << 8, + A_DIM = 1 << 9, + A_ITALIC = 1 << 10, + A_UNDERSCORE = 1 << 11, + A_BLINK = 1 << 12, + A_REVERSE = 1 << 13, +} VuiAttr; + +#define ATTR_FG(a) (a & 0xf) +#define ATTR_BG(a) ((a>>4) & 0xf) +#define ATTR_A(a) (a & ~0xff) +#define ATTR_DEFAULT (FG_WHITE | BG_BLACK) + +typedef struct { + unsigned width, height; + char *chr; + uint16_t *attr; +} VuiBuffer; + +typedef struct { + unsigned width, height; + VuiBuffer buf1, buf2; + VuiBuffer *front, *back; + int redraw_all; +} VuiWindow; + +static struct termios vui_init_stdin_tos, vui_raw_stdin_tos; +static VuiWindow win = { + .front = &win.buf1, + .back = &win.buf2, +}; + +#define BCHR(b,x,y) ((b)->chr[(x) + (y) * (b)->width]) +#define BATTR(b,x,y) ((b)->attr[(x) + (y) * (b)->width]) +#define CHR(x,y) BCHR(win.front, x, y) +#define ATTR(x,y) BATTR(win.front, x, y) + +void vui_getwinsz(unsigned *w, unsigned *h) { + struct winsize wsz; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz); + *w = wsz.ws_col; + *h = wsz.ws_row; +} + +static void vui_clrtoeol(VuiBuffer *buf, unsigned x0, unsigned y) { + for (unsigned x = x0; x < buf->width; x++) { + BCHR(buf, x, y) = ' '; + } + for (unsigned x = x0; x < buf->width; x++) { + BATTR(buf, x, y) = ATTR_DEFAULT; + } +} + +static void clear_buf(VuiBuffer *buf) { + for (unsigned y = 0; y < buf->height; y++) { + vui_clrtoeol(buf, 0, y); + } +} + +static void vui_clear(void) { + clear_buf(win.front); +} + +static void resize_buf(VuiBuffer *buf, unsigned nw, unsigned nh) { + char *nchr = calloc(nw * nh, sizeof(*buf->chr)); + uint16_t *nattr = calloc(nw * nh, sizeof(*buf->attr)); + assert(nchr); + assert(nattr); + + unsigned oldw = buf->width; + unsigned oldh = buf->height; + unsigned minw = nw < oldw ? nw : oldw; + unsigned minh = nh < oldh ? nh : oldh; + for (unsigned y = 0; y < minh; y++) { + memcpy(nchr + y*nw, buf->chr + y*oldw, minw * sizeof(*buf->chr)); + memcpy(nattr + y*nw, buf->attr + y*oldw, minw * sizeof(*buf->attr)); + } + + free(buf->chr); + free(buf->attr); + buf->chr = nchr; + buf->attr = nattr; + buf->width = nw; + buf->height = nh; + for (unsigned y = 0; y < minh; y++) vui_clrtoeol(buf, minw, y); + for (unsigned y = minh; y < buf->height; y++) vui_clrtoeol(buf, 0, y); +} + +static void vui_resize(unsigned nw, unsigned nh) { + resize_buf(&win.buf1, nw, nh); + resize_buf(&win.buf2, nw, nh); + win.width = nw; + win.height = nh; +} + +static void vui_adjust(void) { + unsigned w, h; + vui_getwinsz(&w, &h); + if (w != win.width || h != win.height) { + vui_resize(w, h); + win.redraw_all = 1; + } +} + +void vui_curs_vis(int vis) { + if (vis) { + fputs("\x1b[?25h", stdout); + } else { + fputs("\x1b[?25l", stdout); + } +} + +void vui_init(void) { + tcgetattr(STDIN_FILENO, &vui_init_stdin_tos); + vui_raw_stdin_tos = vui_init_stdin_tos; + vui_raw_stdin_tos.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); + tcsetattr(STDIN_FILENO, TCSANOW, &vui_raw_stdin_tos); + vui_adjust(); +} + + +static void free_buf(VuiBuffer *buf) { + free(buf->chr); + free(buf->attr); + buf->chr = NULL; + buf->attr = NULL; + buf->width = 0; + buf->height = 0; +} + +void vui_fini(void) { + tcsetattr(STDIN_FILENO, TCSANOW, &vui_init_stdin_tos); + vui_curs_vis(1); + free_buf(&win.buf1); + free_buf(&win.buf2); + printf("\x1b[H\x1b[2J\x1b[0m"); +} + +static inline int bchr_equiv(VuiBuffer *back, VuiBuffer *front, int x, int y) { + return BCHR(back,x,y) == BCHR(front,x,y) + && (BATTR(back,x,y) == BATTR(front,x,y) + /* || BCHR(back,x,y) == ' ' */); +} + +static inline void curs_move(int src_x, int src_y, int dst_x, int dst_y) { + if (src_x != dst_x && src_y != dst_y) { + if (dst_x > 0) { + printf("\x1b[%d;%dH", dst_y + 1, dst_x + 1); + } else { + printf("\x1b[%dH", dst_y + 1); + } + } else if (src_x != dst_x) { + printf("\x1b[%dG", dst_x + 1); + } else if (src_y != dst_y) { + printf("\x1b[%dd", dst_y + 1); + } +} + +static void attr_chg(VuiAttr from, VuiAttr to) { + if (from == to) return; + + printf("\x1b["); + + if (ATTR_A(from) != ATTR_A(to)) { + printf("0"); + from = ATTR_DEFAULT; + if (to & A_BOLD) printf(";1"); + if (to & A_DIM) printf(";2"); + if (to & A_ITALIC) printf(";3"); + if (to & A_UNDERSCORE) printf(";4"); + if (to & A_BLINK) printf(";5"); + if (to & A_REVERSE) printf(";7"); + } + + int f_fg = ATTR_FG(from); + int f_bg = ATTR_BG(from); + int t_fg = ATTR_FG(to); + int t_bg = ATTR_BG(to); + int chg_fg = (f_fg != t_fg); + int chg_bg = (f_bg != t_bg); + + if (chg_fg) printf(";%d", t_fg > 7 ? 90 + (t_fg&7) : 30 + t_fg); + if (chg_bg) printf(";%d", t_bg > 7 ? 100 + (t_bg&7) : 40 + t_bg); + + putchar('m'); +} + +unsigned vui_changes = 0; +void vui_blit(void) { + VuiBuffer *front = win.front; + VuiBuffer *back = win.back; + + printf("\x1b[H\x1b[0m"); + VuiAttr attr_last = ATTR_DEFAULT; + + if (win.redraw_all) { + vui_changes = win.width * win.height; + for (unsigned y = 0; y < win.height; y++) { + for (unsigned x = 0; x < win.width; x++) { + VuiAttr a = BATTR(front, x, y); + attr_chg(attr_last, a); + putchar(BCHR(front, x, y)); + attr_last = a; + } + } + win.redraw_all = 0; + goto copy_buf; + } + + vui_changes = 0; + unsigned cur_x = 0, cur_y = 0; + for (unsigned y = 0; y < win.height; y++) { + unsigned x = 0; + while (x < win.width) { + while (x < win.width && bchr_equiv(back, front, x, y)) x++; + if (x >= win.width) break; + unsigned x0 = x; + VuiAttr a = BATTR(front, x0, y); + while (x < win.width && !bchr_equiv(back, front, x, y) && BATTR(front, x, y) == a) x++; + if (x0 != x) { + vui_changes++; + curs_move(cur_x, cur_y, x0, y); + attr_chg(attr_last, a); + printf("%.*s", x - x0, &BCHR(front, x0, y)); + cur_x = x; + cur_y = y; + attr_last = a; + } + } + } + +copy_buf: + fflush(stdout); + memcpy(back->chr, front->chr, sizeof(*back->chr) * (back->width * back->height)); + memcpy(back->attr, front->attr, sizeof(*back->attr) * (back->width * back->height)); + win.front = back; + win.back = front; +} + +void vui_chra(int x, int y, char c, VuiAttr a) { + if (x >= 0 && x < (int)win.width && y >= 0 && y < (int)win.height) { + CHR(x, y) = c; + ATTR(x, y) = a; + } +} + +void vui_chr(int x, int y, char c) { + vui_chra(x, y, c, 0); +} + +int vui_avprintf(int x, int y, VuiAttr a, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int n = vsnprintf(NULL, 0, fmt, ap); + if (x < 0) x = win.width + x - (n - 1); + if (y < 0) y = win.height + y; + if (n > 0) { + char buf[n + 1]; + vsnprintf(buf, n + 1, fmt, ap2); + memcpy(&CHR(x,y), buf, n < win.width - x ? n : win.width - x); + for (unsigned x1 = x; x1 < win.width && x1 < x + n; x1++) { + ATTR(x1, y) = a; + } + } + return n; +} + +int vui_aprintf(int x, int y, VuiAttr a, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vui_avprintf(x, y, a, fmt, ap); + va_end(ap); + return r; +} + +int vui_printf(int x, int y, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vui_avprintf(x, y, ATTR_DEFAULT, fmt, ap); + va_end(ap); + return r; +} + +void on_sigwinch(int _) { + vui_adjust(); + vui_blit(); +} + +int wait_for_input(int fd, int ms) { + return poll(&(struct pollfd) { .fd = fd, .events = POLLIN }, 1, ms); +} + +unsigned isqrt(unsigned x) { + if (x <= 1) return x; + unsigned a = x/2, b = (a + x/a) / 2; + while (b < a) a = b, b = (a + x/a) / 2; + return a; +} + +int main(int argc, const char **argv) { + vui_init(); + vui_curs_vis(0); + signal(SIGWINCH, on_sigwinch); + int x = 0, y = 0; + int dx = 0, dy = 0; + int C = '*'; + unsigned frame = 0; + for (;;) { + //vui_clear(); + frame++; + x += dx; + if (frame & 1) y += dy; + if (x < 0) x += win.width; + if (x > win.width) x -= win.width; + if (y < 0) y += win.height; + if (y > win.height) y -= win.height; + + static int spawning = 1; + if (spawning) { + + for (int y = 0; y < win.height; y++) { + for (int x = 0; x < win.width; x++) { + int tx = x - dx, ty = y - dy; + int txa = tx, txc = tx, tya = ty, tyc = ty; + if (frame & 1) { + /* + txa += (random() & 1) * 2 - 1; + tya += (random() & 1) * 2 - 1; + txc += (random() & 1) * 2 - 1; + tyc += (random() & 1) * 2 - 1; + */ + txa += (random() % 3) - 1; + tya += (random() % 3) - 1; + txc += (random() % 3) - 1; + tyc += (random() % 3) - 1; + } + if (txa >= 0 && txa < win.width && tya >= 0 && tya < win.height) { + ATTR(txa, tya) = BATTR(win.back, x, y); + if (!(random() & 127)) ATTR(x,y) = ATTR_DEFAULT; + } + if (txc >= 0 && txc < win.width && tyc >= 0 && tyc < win.height) { + CHR(txc, tyc) = BCHR(win.back, x, y); + if (!(random() & 127)) CHR(x,y) = ' '; + } + } + } + + for (unsigned i = 0; i < isqrt(win.width * win.height) / 10; i++) { + int tx = random() % win.width; + int ty = random() % win.height; + VuiAttr a = ((random()&15) << 4) | (random()&15); + vui_chra(tx, ty, ' ' + (random() % 0x5f), a); + } + } + + vui_chra(x, y, C, ((x + y) & 0xf) | BG_BLUE | A_UNDERSCORE); + vui_chra(x-1, y, C, ((x + y) & 0xf) | BG_BLUE | A_UNDERSCORE); + vui_chra(x+1, y, C, ((x + y) & 0xf) | BG_BLUE | A_UNDERSCORE); + vui_chra(x, y-1, C, ((x + y) & 0xf) | BG_BLUE | A_UNDERSCORE); + vui_chra(x, y+1, C, ((x + y) & 0xf) | BG_BLUE | A_UNDERSCORE); + + vui_printf(-1, -1, "(%u, %u)", win.width, win.height); + vui_printf(0, -1, "front buffer = %p", (void*)win.front); + vui_printf(-1, 0, "changes = %04u, chars = %04u", vui_changes, win.width * win.height); + + // win.redraw_all = 1; + vui_blit(); + + if (!wait_for_input(STDIN_FILENO, 33)) continue; + int c = getchar(); + + if (c == 'q') break; + if (c > 0x1f && c < 0x7f) C = c; + switch (c) { + case 'k': dx=0; dy=-1; break; + case 'j': dx=0; dy=1; break; + case 'h': dx=-1; dy=0; break; + case 'l': dx=1; dy=0; break; + case 'y': dx=-1; dy=-1; break; + case 'u': dx=1; dy=-1; break; + case 'b': dx=-1; dy=1; break; + case 'n': dx=1; dy=1; break; + case ' ': spawning = !spawning; break; + default: dx=1; dy=0; break; + } + } + vui_fini(); + printf("\x1b[2J\x1b[H %u, %u\n", win.width, win.height); + return 0; +} -- cgit v1.2.3