1 // A console conway's game of life program by Bruce Hill
2 // Released under the MIT license, see LICENSE for details.
9 #include <sys/random.h>
13 #define CELL(cells, x, y) cells[(((y)+H) % H)*W + (((x)+W) % W)]
15 static char rules[2][9] = {{0,0,0,1,0,0,0,0,0}, {0,0,1,1,0,0,0,0,0}};
16 static unsigned int W = 0, H = 0;
17 static char alive_color[64] = "\033[47m", dead_color[64] = "\033[0m";
20 static void update(FILE *out, const char *cells, char *future_cells)
22 const char *color = NULL;
23 for (int y = 0; y < H; y++) {
24 fprintf(out, "\033[%d;1H", y+1);
25 for (int x = 0; x < W; x++) {
27 CELL(cells,x-1,y-1) + CELL(cells,x,y-1) + CELL(cells,x+1,y-1)
28 + CELL(cells,x-1,y) + CELL(cells,x+1,y)
29 + CELL(cells,x-1,y+1) + CELL(cells,x,y+1) + CELL(cells,x+1,y+1);
30 CELL(future_cells, x, y) = rules[CELL(cells, x, y)][neighbors];
31 if (CELL(future_cells, x, y) && color != alive_color) {
32 fputs(alive_color, out);
34 } else if (!CELL(future_cells, x, y) && color != dead_color) {
35 fputs(dead_color, out);
41 fputs("\033[0m", out);
45 static void update_term_size(int sig)
48 struct winsize sz = {0};
49 ioctl(ttyin_fd, TIOCGWINSZ, &sz);
54 static int gethexrgb(const char *str, int *r, int *g, int *b)
56 if (str[0] == '#') ++str;
57 if (strlen(str) == 3) {
58 if (sscanf(str, "%01X%01X%01X", r, g, b) != 3)
60 *r *= 16; *g *= 16; *b *= 16;
63 return sscanf(str, "%02X%02X%02X", r, g, b);
67 int main(int argc, char **argv) {
69 for (int i = 1; i < argc; i++) {
70 if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
72 fprintf(stderr, "Conway's Game of Life Viewer\n"
73 "Usage: conway [-S <survival rules>] [-B <birth rules>] [-A <alive RGB hex>] [-D <dead RGB hex>]\n");
75 } else if (strcmp(argv[i], "-B") == 0) {
76 if (i + 1 >= argc) goto usage;
77 memset(rules[0], 0, sizeof(rules[0]));
78 for (char *p = argv[++i]; *p; p++)
79 if ('0' <= *p && *p <= '8')
80 rules[0][*p - '0'] = 1;
81 } else if (strcmp(argv[i], "-S") == 0) {
82 if (i + 1 >= argc) goto usage;
83 memset(rules[1], 0, sizeof(rules[1]));
84 for (char *p = argv[++i]; *p; p++)
85 if ('0' <= *p && *p <= '8')
86 rules[1][*p - '0'] = 1;
87 } else if (strcmp(argv[i], "-A") == 0) {
88 if (i + 1 >= argc) goto usage;
90 if (gethexrgb(argv[++i], &r, &g, &b) != 3) goto usage;
91 sprintf(alive_color, "\033[48;2;%d;%d;%dm", r,g,b);
92 } else if (strcmp(argv[i], "-D") == 0) {
94 if (gethexrgb(argv[++i], &r, &g, &b) != 3) goto usage;
95 sprintf(dead_color, "\033[48;2;%d;%d;%dm", r,g,b);
101 ttyin_fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
103 fprintf(stderr, "Couldn't open terminal input.\n");
106 FILE *tty_out = fopen("/dev/tty", "w");
108 fprintf(stderr, "Couldn't open terminal output.\n");
112 struct termios orig_termios, termios;
113 tcgetattr(fileno(tty_out), &orig_termios);
114 memcpy(&termios, &orig_termios, sizeof(termios));
116 termios.c_cc[VMIN] = 10;
117 termios.c_cc[VTIME] = 1;
118 if (tcsetattr(fileno(tty_out), TCSAFLUSH, &termios) == -1)
121 struct winsize wsize = {0};
122 fputs("\033[?25l" // Hide cursor
123 "\033[?1049;" // Use alternate screen
124 "1000;" // Enable mouse x/y on press
125 "1002;" // Enable mouse dragging
126 "1006" // Use mouse SGR mode
130 signal(SIGWINCH, update_term_size);
133 int flipflop = 0, prev_W = W, prev_H = H;
135 // TODO: handle resize in SIGWINCH handler
136 // Cells are 2 characters wide so they look square
137 buffers[0] = calloc(W*H, sizeof(char));
138 buffers[1] = calloc(W*H, sizeof(char));
139 for (int x = 0; x < W; x++)
140 for (int y = 0; y < H; y++)
141 CELL(buffers[flipflop], x, y) = random() % 2;
143 int draw_mode = 1, paused = 0;
144 fputs("\033[2J", tty_out);
146 if (prev_W != W || prev_H != H) {
147 prev_W = W, prev_H = H;
150 buffers[0] = calloc(W*H, sizeof(char));
151 buffers[1] = calloc(W*H, sizeof(char));
155 update(tty_out, buffers[flipflop], buffers[!flipflop]);
156 flipflop = !flipflop;
162 while (read(ttyin_fd, &key, 1) == 1) {
164 case '\3': ret = 1; goto done; // CTRL-C
166 case 'p': paused ^= 1; break;
167 case 'c': memset(buffers[flipflop], 0, W*H); break;
169 getrandom(buffers[flipflop], W*H, 0);
170 for (int i = 0; i < W*H; i++)
171 buffers[flipflop][i] &= 0x1;
173 case ' ': draw_mode ^= 1; break;
174 case '<': { // Mouse dragging/clicks
175 int buttons = 0, x = 0, y = 0;
177 while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
178 buttons = buttons * 10 + (buf - '0');
179 if (buf != ';') return -1;
180 while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
181 x = x * 10 + (buf - '0');
182 if (buf != ';') return -1;
183 while (read(ttyin_fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
184 y = y * 10 + (buf - '0');
185 if (buf != 'm' && buf != 'M') return -1;
186 if ((buttons & (~32)) == 0 && ((x-1)/2) < W) {
187 CELL(buffers[flipflop], (x-1)/2, y-1) = draw_mode;
188 fprintf(tty_out, "\033[%d;%dH%s \033[0m",
190 draw_mode ? alive_color : dead_color);
198 if (drew) goto present;
201 tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios);
202 fputs("\033[?25h" // Show cursor
203 "\033[?1049;" // Back to normal screen
204 "1000;" // Disable mouse x/y reporting
205 "1002;" // Disable mouse dragging
206 "1006" // Disable mouse SGR mode