diff --git a/README.md b/README.md index f143ae8..679aea4 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ on the command line. ## Usage Command line flags: -* `-S[0-8]*` the rules for when an alive cell survives -* `-B[0-8]*` the rules for when a new cell is born -* `-A[0-256]` provide a color for alive cells -* `-D[0-256]` provide a color for dead cells +* `-S [0-8]*` the rules for when an alive cell survives +* `-B [0-8]*` the rules for when a new cell is born +* `-A ` provide a color for alive cells +* `-D ` provide a color for dead cells Some interesting rulesets can be found in [this list](http://psoup.math.wisc.edu/mcell/rullex_life.html). diff --git a/conway.c b/conway.c index d191130..6ee5475 100644 --- a/conway.c +++ b/conway.c @@ -1,77 +1,108 @@ // A console conway's game of life program by Bruce Hill // Released under the MIT license, see LICENSE for details. +#include #include -#include #include -#include +#include +#include +#include #include #define CELL(cells, x, y) cells[(((y)+H) % H)*W + (((x)+W) % W)] static char rules[2][9] = {{0,0,0,1,0,0,0,0,0}, {0,0,1,1,0,0,0,0,0}}; -static unsigned int W, H, DEAD = 0, ALIVE = 7; +static unsigned int W, H; +static char alive_color[64] = "\033[47m", dead_color[64] = "\033[0m"; -static void update(const char *cells, char *future_cells) +static void update(FILE *out, const char *cells, char *future_cells) { + const char *color = NULL; for (int y = 0; y < H; y++) { + fprintf(out, "\033[%d;1H", y+1); for (int x = 0; x < W; x++) { int neighbors = CELL(cells,x-1,y-1) + CELL(cells,x,y-1) + CELL(cells,x+1,y-1) + CELL(cells,x-1,y) + CELL(cells,x+1,y) + CELL(cells,x-1,y+1) + CELL(cells,x,y+1) + CELL(cells,x+1,y+1); CELL(future_cells, x, y) = rules[CELL(cells, x, y)][neighbors]; - tb_change_cell(2*x, y, ' ', 0, CELL(future_cells, x, y) ? ALIVE : DEAD); - tb_change_cell(2*x+1, y, ' ', 0, CELL(future_cells, x, y) ? ALIVE : DEAD); + if (CELL(future_cells, x, y) && color != alive_color) { + fputs(alive_color, out); + color = alive_color; + } else if (!CELL(future_cells, x, y) && color != dead_color) { + fputs(dead_color, out); + color = dead_color; + } + fputs(" ", out); } } + fputs("\033[0m", out); + fflush(out); } int main(int argc, char **argv) { + int ret = 0; for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { usage: printf("Conway's Game of Life Viewer\nUsage: conway [-S] [-B] [-A] [-D]\n"); return 0; - } else if (strncmp(argv[i], "-B", 2) == 0) { + } else if (strcmp(argv[i], "-B") == 0) { + if (i + 1 >= argc) goto usage; memset(rules[0], 0, sizeof(rules[0])); - for (char *p = &argv[i][2]; *p; p++) + for (char *p = argv[++i]; *p; p++) if ('0' <= *p && *p <= '8') rules[0][*p - '0'] = 1; - } else if (strncmp(argv[i], "-S", 2) == 0) { + } else if (strcmp(argv[i], "-S") == 0) { + if (i + 1 >= argc) goto usage; memset(rules[1], 0, sizeof(rules[1])); - for (char *p = &argv[i][2]; *p; p++) + for (char *p = argv[++i]; *p; p++) if ('0' <= *p && *p <= '8') rules[1][*p - '0'] = 1; - } else if (strncmp(argv[i], "-A", 2) == 0) { - ALIVE = atoi(&argv[i][2]); - if (ALIVE & ~0xFF) { - fprintf(stderr, "Colors must be between 0-255\n"); - return 1; - } - } else if (strncmp(argv[i], "-D", 2) == 0) { - DEAD = atoi(&argv[i][2]); - if (DEAD & ~0xFF) { - fprintf(stderr, "Colors must be between 0-255\n"); - return 1; - } + } else if (strcmp(argv[i], "-A") == 0) { + if (i + 1 >= argc) goto usage; + sprintf(alive_color, "\033[%sm", argv[++i]); + } else if (strcmp(argv[i], "-D") == 0) { + sprintf(dead_color, "\033[%sm", argv[++i]); } else { goto usage; } } - int ret = tb_init(); - if (ret) { - fprintf(stderr, "tb_init() failed with error code %d\n", ret); + int ttyin_fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); + if (ttyin_fd < 0) { + fprintf(stderr, "Couldn't open terminal input.\n"); return 1; } - tb_select_input_mode(TB_INPUT_MOUSE); - tb_select_output_mode(TB_OUTPUT_256); + FILE *tty_out = fopen("/dev/tty", "w"); + if (!tty_out) { + fprintf(stderr, "Couldn't open terminal output.\n"); + return 1; + } + + struct termios orig_termios, termios; + tcgetattr(fileno(tty_out), &orig_termios); + memcpy(&termios, &orig_termios, sizeof(termios)); + cfmakeraw(&termios); + termios.c_cc[VMIN] = 10; + termios.c_cc[VTIME] = 1; + if (tcsetattr(fileno(tty_out), TCSAFLUSH, &termios) == -1) + return 1; + + struct winsize wsize = {0}; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsize); + fputs("\033[?25l" // Hide cursor + "\033[?1049;" // Use alternate screen + "1000;" // Enable mouse x/y on press + "1002;" // Enable mouse dragging + "1006" // Use mouse SGR mode + "h", tty_out); char *buffers[2]; int flipflop = 0; resize: + // TODO: handle resize in SIGWINCH handler // Cells are 2 characters wide so they look square - W = tb_width()/2, H = tb_height(); + W = wsize.ws_col/2, H = wsize.ws_row; buffers[0] = calloc(W*H, sizeof(char)); buffers[1] = calloc(W*H, sizeof(char)); for (int x = 0; x < W; x++) @@ -79,49 +110,58 @@ int main(int argc, char **argv) { CELL(buffers[flipflop], x, y) = random() % 2; int draw_mode = 1, paused = 0; - tb_set_clear_attributes(0, DEAD); - tb_clear(); + fputs("\033[2J", tty_out); while (1) { if (!paused) { - update(buffers[flipflop], buffers[!flipflop]); + update(tty_out, buffers[flipflop], buffers[!flipflop]); flipflop = !flipflop; } present: - tb_present(); usleep(60000); - struct tb_event ev; int drew = 0; - while (tb_peek_event(&ev, 0)) { - switch (ev.type) { - case TB_EVENT_RESIZE: - free(buffers[0]); - free(buffers[1]); - goto resize; - - case TB_EVENT_KEY: - if (ev.key == TB_KEY_ESC && tb_peek_event(&ev, 50) == 0) - goto done; - else if (ev.ch == 'q') - goto done; - else if (ev.ch == 'p') - paused = !paused; - else if (ev.ch == 'c') - memset(buffers[flipflop], 0, W*H); - else if (ev.key == TB_KEY_SPACE) - draw_mode = !draw_mode; - break; - - case TB_EVENT_MOUSE: - CELL(buffers[flipflop], ev.x/2, ev.y) = draw_mode; - tb_change_cell(2*(ev.x/2), ev.y, ' ', 0, draw_mode ? ALIVE : DEAD); - tb_change_cell(2*(ev.x/2)+1, ev.y, ' ', 0, draw_mode ? ALIVE : DEAD); + char key; + while (read(ttyin_fd, &key, 1) == 1) { + switch (key) { + case '\3': ret = 1; goto done; // CTRL-C + case 'q': goto done; + case 'p': paused ^= 1; break; + case 'c': memset(buffers[flipflop], 0, W*H); break; + case ' ': draw_mode ^= 1; break; + case '<': { // Mouse dragging/clicks + int buttons = 0, x = 0, y = 0; + char buf; + while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') + buttons = buttons * 10 + (buf - '0'); + if (buf != ';') return -1; + while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') + x = x * 10 + (buf - '0'); + if (buf != ';') return -1; + while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') + y = y * 10 + (buf - '0'); + if (buf != 'm' && buf != 'M') return -1; + if ((buttons & (~32)) == 0 && ((x-1)/2) < W) { + CELL(buffers[flipflop], (x-1)/2, y-1) = draw_mode; + fprintf(tty_out, "\033[%d;%dH%s \033[0m", + y, 2*((x-1)/2)+1, + draw_mode ? alive_color : dead_color); + fflush(tty_out); drew = 1; - break; + } + break; + } } } if (drew) goto present; } done: - tb_shutdown(); - return 0; + tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios); + fputs("\033[?25h" // Show cursor + "\033[?1049;" // Back to normal screen + "1000;" // Disable mouse x/y reporting + "1002;" // Disable mouse dragging + "1006" // Disable mouse SGR mode + "l", tty_out); + fclose(tty_out); + close(ttyin_fd); + return ret; }