Skip to content

Instantly share code, notes, and snippets.

@fourkbomb
Last active July 7, 2016 14:02
Show Gist options
  • Save fourkbomb/c83f716540fa25da525e528f9fa45646 to your computer and use it in GitHub Desktop.
Save fourkbomb/c83f716540fa25da525e528f9fa45646 to your computer and use it in GitHub Desktop.
// 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