summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c449
1 files changed, 449 insertions, 0 deletions
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 <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <poll.h>
+
+/* 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;
+}