Last active
July 7, 2016 14:02
-
-
Save fourkbomb/c83f716540fa25da525e528f9fa45646 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// gcc -l X11 conway.c | |
#include <X11/XKBlib.h> | |
#include <X11/Xlib.h> | |
#include <X11/keysym.h> | |
#include <inttypes.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <unistd.h> | |
#define STATE_ALIVE (1) | |
#define STATE_DEAD (0) | |
#define GRID_SIZE (200) | |
#define DEBUG_MIN_BLOCK_SIZE 40 | |
#define MAX_GRID (GRID_SIZE-1) | |
#define INIT_WINDOW_SIZE 400 | |
int WINDOW_SIZE = INIT_WINDOW_SIZE; | |
int BLOCK_SIZE = INIT_WINDOW_SIZE/GRID_SIZE; | |
long UPDATE_INTERVAL = 5e7l; | |
bool debug = false; | |
int mouseX = 0; | |
int mouseY = 0; | |
char *help = "<SPACE> to pause/unpause, <Q> to quit, <C> to clear, <F> to fill. Angle brackets to speed up/slow down"; | |
#define min(a,b) (a<b ? a : b) | |
int grid[GRID_SIZE][GRID_SIZE]; | |
int getNewCellState(int y, int x) { | |
int living_neighbours = 0; | |
for (int dy = -1; dy < 2; dy++) { | |
int cy = y + dy; | |
if (y == 0 && dy == -1) cy = MAX_GRID; | |
else if (y == MAX_GRID && dy == 1) cy = 0; | |
for (int dx = -1; dx < 2; dx++) { | |
int cx = x + dx; | |
if (x == 0 && dx == -1) cx = MAX_GRID; | |
else if (x == MAX_GRID && dx == 1) cx = 0; | |
if (dx == 0 && dy == 0) continue; | |
if (grid[cy][cx] == STATE_ALIVE) { | |
if (debug && grid[y][x] == STATE_ALIVE) printf("(%d,%d), ", cx, cy); | |
living_neighbours++; | |
} | |
} | |
} | |
if (grid[y][x] == STATE_ALIVE) { | |
if (debug) | |
printf("%d,%d has %d alive\n",x,y, living_neighbours); | |
// Any live cell with fewer than two live neighbours dies, as if caused by under-population. | |
if (living_neighbours < 2) return STATE_DEAD; | |
// Any live cell with two or three live neighbours lives on to the next generation. | |
if (living_neighbours == 3 || living_neighbours == 2) return STATE_ALIVE; | |
// Any live cell with more than three live neighbours dies, as if by over-population. | |
if (living_neighbours > 3) return STATE_DEAD; | |
} else { | |
// Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. | |
if (living_neighbours == 3) return STATE_ALIVE; | |
} | |
return STATE_DEAD; | |
} | |
void update(void) { | |
int ngrid[GRID_SIZE][GRID_SIZE]; | |
for (int y = 0; y < GRID_SIZE; y++) { | |
for (int x = 0; x < GRID_SIZE; x++) { | |
ngrid[y][x] = getNewCellState(y, x); | |
} | |
} | |
memcpy(&grid, &ngrid, sizeof(int)*GRID_SIZE*GRID_SIZE); | |
} | |
void redraw(Display* display, uintptr_t window, int s, bool hideHelp) { | |
GC black = DefaultGC(display, s); | |
XSetForeground(display, black, BlackPixel(display, s)); | |
GC white= XCreateGC(display, window, 0, NULL); | |
XSetForeground(display, white, WhitePixel(display, s)); | |
char *coords = malloc(sizeof(char) * 50); | |
for (int y = 0; y < GRID_SIZE; y++) { | |
for (int x = 0; x < GRID_SIZE; x++) { | |
bool alive = grid[y][x] == STATE_ALIVE; | |
XFillRectangle(display, window, (alive ? black : white), | |
x*BLOCK_SIZE, y*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); | |
if (debug) { | |
snprintf(coords, 49, "(%d,%d)", x, y); | |
XDrawString(display, window, (alive ? white : black), | |
x*BLOCK_SIZE, y*BLOCK_SIZE+BLOCK_SIZE/2, coords, strlen(coords)); | |
} | |
} | |
} | |
if (!hideHelp) | |
XDrawString(display, window, black, 10, 20, help, strlen(help)); | |
if (debug) { | |
snprintf(coords, 49, "(%d,%d)", mouseX, mouseY); | |
XDrawString(display, window, black, 10, 20, coords, strlen(coords)); | |
} | |
free(coords); | |
} | |
void reset_grid(int val) { | |
for (int y = 0; y < GRID_SIZE; y++) { | |
for (int x = 0; x < GRID_SIZE; x++) { | |
grid[y][x] = val; | |
} | |
} | |
}; | |
bool is_in_past(struct timespec *ts) { | |
struct timespec now; | |
clock_gettime(CLOCK_MONOTONIC, &now); | |
if (ts->tv_sec < now.tv_sec) { | |
return true; | |
} else if (ts->tv_sec == now.tv_sec && ts->tv_nsec <= now.tv_nsec) { | |
return true; | |
} | |
return false; | |
} | |
void update_next_ts(struct timespec *ts) { | |
clock_gettime(CLOCK_MONOTONIC, ts); | |
ts->tv_nsec += UPDATE_INTERVAL; | |
} | |
void get_mouse_coords(int *px, int *py) { | |
int x = *px; | |
int y = *py; | |
if (x < WINDOW_SIZE && y < WINDOW_SIZE) { | |
// round down to nearest 'block' | |
x -= x % BLOCK_SIZE; | |
y -= y % BLOCK_SIZE; | |
// x_to_pixel = x * BLOCK_SIZE | |
// pixel_to_x = x / BLOCK_SIZE | |
x /= BLOCK_SIZE; | |
y /= BLOCK_SIZE; | |
} else { | |
x = 0; | |
y = 0; | |
} | |
*px = x; | |
*py = y; | |
} | |
int set_block_at_pixel(int x, int y, int val) { | |
if (x < WINDOW_SIZE && y < WINDOW_SIZE) { | |
// round down to nearest 'block' | |
x -= x % BLOCK_SIZE; | |
y -= y % BLOCK_SIZE; | |
// x_to_pixel = x * BLOCK_SIZE | |
// pixel_to_x = x / BLOCK_SIZE | |
x /= BLOCK_SIZE; | |
y /= BLOCK_SIZE; | |
if (val == -1) { | |
if (grid[y][x] == STATE_ALIVE) { | |
grid[y][x] = STATE_DEAD; | |
} else { | |
grid[y][x] = STATE_ALIVE; | |
} | |
return grid[y][x]; | |
} else { | |
grid[y][x] = val; | |
return val; | |
} | |
} | |
return -1; | |
} | |
int main(int argc, char*argv[]) { | |
Display *display; | |
uintptr_t window; | |
XEvent event; | |
bool running = true; | |
bool paused = false; | |
bool painting = false; | |
bool keyPressed = false; | |
int paintMode = STATE_DEAD; | |
int s; | |
struct timespec nextTick; | |
clock_gettime(CLOCK_MONOTONIC, &nextTick); | |
for (int y = 0; y < GRID_SIZE; y++) { | |
for (int x = 0; x < GRID_SIZE; x++) { | |
grid[y][x] = STATE_DEAD; | |
} | |
} | |
printf("ready\n"); | |
XInitThreads(); | |
display = XOpenDisplay(NULL); | |
if (display == NULL) { | |
printf("open display failed\n"); | |
return 1; | |
} | |
s = DefaultScreen(display); | |
window = XCreateSimpleWindow(display, RootWindow(display, s), 0, 0, | |
WINDOW_SIZE, WINDOW_SIZE, 1, BlackPixel(display, s), WhitePixel(display, s)); | |
XSelectInput(display, window, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | | |
SubstructureNotifyMask | ExposureMask | KeyPressMask | StructureNotifyMask); | |
#ifdef DEBUG | |
XSynchronize(display, true); | |
#endif | |
XMapWindow(display, window); | |
XStoreName(display, window, "Conway's Game of Life"); | |
while (running) { | |
if (XPending(display)) { | |
XNextEvent(display, &event); | |
switch (event.type) { | |
case ButtonPress: ; | |
int x, y; | |
x = event.xbutton.x; | |
y = event.xbutton.y; | |
paintMode = set_block_at_pixel(x, y, -1); | |
redraw(display, window, s, keyPressed); | |
painting = true; | |
break; | |
case ButtonRelease: | |
painting = false; | |
redraw(display, window, s, keyPressed); | |
break; | |
case KeyPress: ; | |
KeySym k = XkbKeycodeToKeysym(display, event.xkey.keycode, | |
0, event.xkey.state & ShiftMask ? 1 : 0); | |
keyPressed = true; | |
switch (k) { | |
case XK_q: | |
running = false; | |
break; | |
case XK_d: | |
if (BLOCK_SIZE < DEBUG_MIN_BLOCK_SIZE) break; | |
debug = true; | |
update(); | |
redraw(display, window, s, keyPressed); | |
break; | |
case XK_D: | |
debug = false; | |
break; | |
case XK_space: | |
paused = !paused; | |
break; | |
case XK_c: | |
reset_grid(STATE_DEAD); | |
redraw(display, window, s, keyPressed); | |
break; | |
case XK_f: | |
reset_grid(STATE_ALIVE); | |
redraw(display, window, s, keyPressed); | |
break; | |
case XK_less: // left angle bracket | |
if (UPDATE_INTERVAL > 5e7l) break; | |
UPDATE_INTERVAL *= 10; | |
printf("now updating every %ld ns\n", UPDATE_INTERVAL); | |
break; | |
case XK_greater: // right angle bracket | |
if (UPDATE_INTERVAL < 5e2l) break; | |
UPDATE_INTERVAL /= 10; | |
printf("now updating every %ld ns\n", UPDATE_INTERVAL); | |
break; | |
default: | |
printf("Key: %d\n", k); | |
break; | |
} | |
break; | |
case MotionNotify: ; | |
XMotionEvent xme = event.xmotion; | |
if (debug) { | |
mouseX = xme.x; | |
mouseY = xme.y; | |
get_mouse_coords(&mouseX, &mouseY); | |
redraw(display, window, s, true); | |
} | |
if (!painting) break; | |
set_block_at_pixel(xme.x, xme.y, paintMode); | |
redraw(display, window, s, keyPressed); | |
break; | |
case ConfigureNotify: ; | |
XConfigureEvent xce = event.xconfigure; | |
// scale up to new size | |
printf("old size: %d\n", WINDOW_SIZE); | |
printf("%d, %d => %d", xce.height, xce.width, min(xce.height, xce.width)); | |
WINDOW_SIZE = min(xce.height, xce.width); | |
printf("new size: %d\n", WINDOW_SIZE); | |
BLOCK_SIZE = WINDOW_SIZE / GRID_SIZE; | |
redraw(display, window, s, keyPressed); | |
break; | |
case Expose: | |
redraw(display, window, s, keyPressed); | |
break; | |
} | |
} | |
if (is_in_past(&nextTick)) { | |
update_next_ts(&nextTick); | |
if (!paused) { | |
update(); | |
redraw(display, window, s, keyPressed); | |
XFlush(display); | |
} | |
} | |
usleep(500); | |
} | |
printf("gone\n"); | |
XCloseDisplay(display); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment