code / conway

Lines247 C195 make33 Markdown19
(211 lines)
1 // A console conway's game of life program by Bruce Hill
2 // Released under the MIT license, see LICENSE for details.
3 #include <fcntl.h>
4 #include <signal.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/ioctl.h>
9 #include <sys/random.h>
10 #include <termios.h>
11 #include <unistd.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";
18 int ttyin_fd = -1;
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++) {
26 int neighbors =
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);
33 color = alive_color;
34 } else if (!CELL(future_cells, x, y) && color != dead_color) {
35 fputs(dead_color, out);
36 color = dead_color;
38 fputs(" ", out);
41 fputs("\033[0m", out);
42 fflush(out);
45 static void update_term_size(int sig)
47 (void)sig;
48 struct winsize sz = {0};
49 ioctl(ttyin_fd, TIOCGWINSZ, &sz);
50 W = sz.ws_col / 2;
51 H = sz.ws_row;
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)
59 return 0;
60 *r *= 16; *g *= 16; *b *= 16;
61 return 3;
62 } else {
63 return sscanf(str, "%02X%02X%02X", r, g, b);
67 int main(int argc, char **argv) {
68 int ret = 0;
69 for (int i = 1; i < argc; i++) {
70 if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
71 usage:
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");
74 return ret;
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;
89 int r,g,b;
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) {
93 int r,g,b;
94 if (gethexrgb(argv[++i], &r, &g, &b) != 3) goto usage;
95 sprintf(dead_color, "\033[48;2;%d;%d;%dm", r,g,b);
96 } else {
97 goto usage;
101 ttyin_fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
102 if (ttyin_fd < 0) {
103 fprintf(stderr, "Couldn't open terminal input.\n");
104 return 1;
106 FILE *tty_out = fopen("/dev/tty", "w");
107 if (!tty_out) {
108 fprintf(stderr, "Couldn't open terminal output.\n");
109 return 1;
112 struct termios orig_termios, termios;
113 tcgetattr(fileno(tty_out), &orig_termios);
114 memcpy(&termios, &orig_termios, sizeof(termios));
115 cfmakeraw(&termios);
116 termios.c_cc[VMIN] = 10;
117 termios.c_cc[VTIME] = 1;
118 if (tcsetattr(fileno(tty_out), TCSAFLUSH, &termios) == -1)
119 return 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
127 "h", tty_out);
129 update_term_size(0);
130 signal(SIGWINCH, update_term_size);
132 char *buffers[2];
133 int flipflop = 0, prev_W = W, prev_H = H;
134 resize:
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);
145 while (1) {
146 if (prev_W != W || prev_H != H) {
147 prev_W = W, prev_H = H;
148 free(buffers[0]);
149 free(buffers[1]);
150 buffers[0] = calloc(W*H, sizeof(char));
151 buffers[1] = calloc(W*H, sizeof(char));
152 goto resize;
154 if (!paused) {
155 update(tty_out, buffers[flipflop], buffers[!flipflop]);
156 flipflop = !flipflop;
158 present:
159 usleep(60000);
160 int drew = 0;
161 char key;
162 while (read(ttyin_fd, &key, 1) == 1) {
163 switch (key) {
164 case '\3': ret = 1; goto done; // CTRL-C
165 case 'q': goto done;
166 case 'p': paused ^= 1; break;
167 case 'c': memset(buffers[flipflop], 0, W*H); break;
168 case 'r':
169 getrandom(buffers[flipflop], W*H, 0);
170 for (int i = 0; i < W*H; i++)
171 buffers[flipflop][i] &= 0x1;
172 break;
173 case ' ': draw_mode ^= 1; break;
174 case '<': { // Mouse dragging/clicks
175 int buttons = 0, x = 0, y = 0;
176 char buf;
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",
189 y, 2*((x-1)/2)+1,
190 draw_mode ? alive_color : dead_color);
191 fflush(tty_out);
192 drew = 1;
194 break;
198 if (drew) goto present;
200 done:
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
207 "l", tty_out);
208 fclose(tty_out);
209 close(ttyin_fd);
210 return ret;