Add Conway's Game of Life

This commit is contained in:
Justine Tunney 2020-11-18 08:26:03 -08:00
parent db33973e0a
commit dba7552c1e
22 changed files with 664 additions and 186 deletions

527
tool/viz/life.c Normal file
View file

@ -0,0 +1,527 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/calls/ioctl.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/struct/winsize.h"
#include "libc/calls/termios-internal.h"
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/str/tpenc.h"
#include "libc/sysv/consts/termios.h"
#include "libc/x/x.h"
/**
* @fileoverview Conway's Game of Life
*
* The Game of Life, also known simply as Life, is a cellular automaton
* devised by the British mathematician John Horton Conway in 1970. It
* is Turing complete and can simulate a universal constructor or any
* other Turing machine.
*
* This program may be used by dragging the mouse in the terminal. Left
* mouse draws or erases cells. Right mouse scrolls or moves the plane.
* The space bar may be held down to move time forward.
*
* This implementation uses bitboards on an n×m wraparound plane. For an
* explanation of how this technique works on an 8×8 plane see "Bitboard
* Methods for Games" by Cameron Browne.
*/
#define INTERRUPTED 1
#define RESIZED 2
#define MOUSE_LEFT_DOWN 0
#define MOUSE_MIDDLE_DOWN 1
#define MOUSE_RIGHT_DOWN 2
#define MOUSE_LEFT_UP 4
#define MOUSE_MIDDLE_UP 5
#define MOUSE_RIGHT_UP 6
#define MOUSE_LEFT_DRAG 32
#define MOUSE_MIDDLE_DRAG 33
#define MOUSE_RIGHT_DRAG 34
#define MOUSE_WHEEL_UP 64
#define MOUSE_WHEEL_DOWN 65
#define MOUSE_CTRL_WHEEL_UP 80
#define MOUSE_CTRL_WHEEL_DOWN 81
#define LEFT 0x0101010101010101ul
#define RIGHT 0x8080808080808080ul
#define TOP 0x00000000000000FFul
#define BOTTOM 0xFF00000000000000ul
#define CTRL(C) ((C) ^ 0100)
#define GOUP(x) ((x) >> 8)
#define GODOWN(x) ((x) << 8)
#define GORIGHT(x) (((x) & ~RIGHT) << 1)
#define GOLEFT(x) (((x) & ~LEFT) >> 1)
#define LEFTMOST(x) ((x)&LEFT)
#define RIGHTMOST(x) ((x)&RIGHT)
#define TOPMOST(x) ((x)&TOP)
#define BOTMOST(x) ((x)&BOTTOM)
#define ADD(X) \
do { \
uint64_t c1, c2; \
c1 = r[0] & (X); \
c2 = r[1] & c1; \
r[0] ^= (X); \
r[1] ^= c1; \
r[2] |= c2; \
} while (0)
#define STEP(RES, B00, B01, B02, B10, B11, B12, B20, B21, B22) \
do { \
uint64_t r[3] = {0}; \
ADD(GORIGHT(GODOWN(B11)) | GORIGHT(BOTMOST(B01) >> 56) | \
GODOWN(RIGHTMOST(B10) >> 7) | BOTMOST(RIGHTMOST(B00)) >> 7 >> 56); \
ADD(GORIGHT(B11) | RIGHTMOST(B10) >> 7); \
ADD(GORIGHT(GOUP(B11)) | GORIGHT(TOPMOST(B21) << 56) | \
GOUP(RIGHTMOST(B10) >> 7) | TOPMOST(RIGHTMOST(B20)) >> 7 << 56); \
ADD(GODOWN(B11) | BOTMOST(B01) >> 56); \
ADD(GOUP(B11) | TOPMOST(B21) << 56); \
ADD(GOLEFT(GODOWN(B11)) | GOLEFT(BOTMOST(B01) >> 56) | \
GODOWN(LEFTMOST(B12) << 7) | BOTMOST(LEFTMOST(B02)) << 7 >> 56); \
ADD(GOLEFT(B11) | LEFTMOST(B12) << 7); \
ADD(GOLEFT(GOUP(B11)) | GOLEFT(TOPMOST(B21) << 56) | \
GOUP(LEFTMOST(B12) << 7) | TOPMOST(LEFTMOST(B22)) << 7 << 56); \
RES = (B11 | r[0]) & r[1] & ~r[2]; \
} while (0)
struct Buffer {
unsigned i, n;
char *p;
};
static bool erase;
static bool natural;
static bool mousemode;
static int out;
static int action;
static long top;
static long bottom;
static long left;
static long right;
static long tyn;
static long txn;
static long byn;
static long bxn;
static long save_y;
static long save_x;
static long save_top;
static long save_left;
static uint64_t *board;
static struct Buffer buffer;
static struct termios oldterm;
static int Write(const char *s) {
return write(out, s, strlen(s));
}
static void HideCursor(void) {
Write("\e[?25l");
}
static void ShowCursor(void) {
Write("\e[?25h");
}
static void EnableMouse(void) {
mousemode = true;
Write("\e[?1000;1002;1015;1006h");
}
static void DisableMouse(void) {
mousemode = false;
Write("\e[?1000;1002;1015;1006l");
}
static void LeaveScreen(void) {
Write("\e[H\e[J");
}
static void GetTtySize(void) {
struct winsize wsize;
wsize.ws_row = tyn;
wsize.ws_col = txn;
getttysize(out, &wsize);
tyn = wsize.ws_row;
txn = wsize.ws_col;
right = left + txn;
bottom = top + tyn;
}
static void EnableRaw(void) {
struct termios term;
memcpy(&term, &oldterm, sizeof(term));
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 1;
term.c_iflag &= ~(INPCK | ISTRIP | PARMRK | INLCR | IGNCR | ICRNL | IXON);
term.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHONL);
term.c_cflag &= ~(CSIZE | PARENB);
term.c_cflag |= CS8;
term.c_iflag |= IUTF8;
ioctl(out, TCSETS, &term);
}
static void OnExit(void) {
LeaveScreen();
ShowCursor();
DisableMouse();
ioctl(out, TCSETS, &oldterm);
}
static void OnSigInt(int sig, struct siginfo *sa, struct ucontext *uc) {
action |= INTERRUPTED;
}
static void OnSigWinch(int sig, struct siginfo *sa, struct ucontext *uc) {
action |= RESIZED;
}
static bool Test(long y, long x) {
return (board[(bxn >> 3) * (y >> 3) + (x >> 3)] >>
(((y & 7) << 3) + (x & 7))) &
1;
}
static void Set(long y, long x) {
board[(bxn >> 3) * (y >> 3) + (x >> 3)] |= 1ul << (((y & 7) << 3) + (x & 7));
}
static void Unset(long y, long x) {
board[(bxn >> 3) * (y >> 3) + (x >> 3)] &=
~(1ul << (((y & 7) << 3) + (x & 7)));
}
static void Setup(void) {
out = 1;
tyn = 80;
txn = 24;
byn = 64 * 4;
bxn = 64 * 8;
board = xcalloc((byn * bxn) >> 6, 8);
GetTtySize();
top = byn / 2 - tyn / 2;
left = bxn / 2 - txn / 2;
right = left + txn;
bottom = top + tyn;
ioctl(out, TCGETS, &oldterm);
HideCursor();
EnableRaw();
EnableMouse();
atexit(OnExit);
sigaction(SIGINT, &(struct sigaction){.sa_sigaction = OnSigInt}, NULL);
sigaction(SIGWINCH, &(struct sigaction){.sa_sigaction = OnSigWinch}, NULL);
}
static void AppendData(char *data, unsigned len) {
char *p;
unsigned n;
if (buffer.i + len + 1 > buffer.n) {
n = MAX(buffer.i + len + 1, MAX(16, buffer.n + (buffer.n >> 1)));
if (!(p = realloc(buffer.p, n))) return;
buffer.p = p;
buffer.n = n;
}
memcpy(buffer.p + buffer.i, data, len);
buffer.p[buffer.i += len] = 0;
}
static void AppendChar(char c) {
AppendData(&c, 1);
}
static void AppendStr(const char *s) {
AppendData(s, strlen(s));
}
static void AppendWide(wint_t wc) {
unsigned i;
uint64_t wb;
char buf[8];
i = 0;
wb = tpenc(wc);
do {
buf[i++] = wb & 0xFF;
wb >>= 8;
} while (wb);
AppendData(buf, i);
}
static void Move(long dy, long dx) {
top = top + dy;
bottom = bottom + dy;
left = left + dx;
right = right + dx;
}
static void OnUp(void) {
Move(-1, 0);
}
static void OnDown(void) {
Move(+1, 0);
}
static void OnLeft(void) {
Move(0, -1);
}
static void OnRight(void) {
Move(0, +1);
}
static void Generation(void) {
uint64_t *board2;
long y, x, yn, xn, yp, ym, xp, xm;
yn = byn >> 3;
xn = bxn >> 3;
board2 = xmalloc(yn * xn * 8);
for (y = 0; y < yn; ++y) {
for (x = 0; x < xn; ++x) {
ym = y ? y - 1 : yn - 1;
yp = y + 1 < yn ? y + 1 : 0;
xm = x ? x - 1 : xn - 1;
xp = x + 1 < xn ? x + 1 : 0;
STEP(board2[y * xn + x], board[ym * xn + xm], board[ym * xn + x],
board[ym * xn + xp], board[y * xn + xm], board[y * xn + x],
board[y * xn + xp], board[yp * xn + xm], board[yp * xn + x],
board[yp * xn + xp]);
}
}
free(board);
board = board2;
}
static void OnMouseLeftDrag(long y, long x) {
if (y == save_y && x == save_x) return;
save_y = y;
save_x = x;
y += top;
x += left;
if (y < 0 || y >= byn) return;
if (x < 0 || x >= bxn) return;
if (erase) {
Unset(y, x);
} else {
Set(y, x);
}
}
static void OnMouseLeftDown(long y, long x) {
save_y = y;
save_x = x;
y += top;
x += left;
erase = false;
if (y < 0 || y >= byn) return;
if (x < 0 || x >= bxn) return;
if ((erase = Test(y, x))) {
Unset(y, x);
} else {
Set(y, x);
}
}
static void OnMouseRightDown(long y, long x) {
save_y = y;
save_x = x;
save_top = top;
save_left = left;
}
static void OnMouseRightDrag(long y, long x) {
long dy, dx, h, w;
dy = save_y - y;
dx = save_x - x;
if (natural) {
dy = -dy;
dx = -dx;
}
h = bottom - top;
w = right - left;
top = save_top + dy;
left = save_left + dx;
bottom = top + h;
right = left + w;
}
static void OnMouse(char *p) {
int e, x, y;
e = strtol(p, &p, 10);
if (*p == ';') ++p;
x = min(txn, max(1, strtol(p, &p, 10))) - 1;
if (*p == ';') ++p;
y = min(tyn, max(1, strtol(p, &p, 10))) - 1;
e |= (*p == 'm') << 2;
switch (e) {
case MOUSE_WHEEL_UP:
if (natural) {
OnDown();
OnDown();
OnDown();
} else {
OnUp();
OnUp();
OnUp();
}
break;
case MOUSE_WHEEL_DOWN:
if (natural) {
OnUp();
OnUp();
OnUp();
} else {
OnDown();
OnDown();
OnDown();
}
break;
case MOUSE_RIGHT_DOWN:
OnMouseRightDown(y, x);
break;
case MOUSE_RIGHT_DRAG:
OnMouseRightDrag(y, x);
break;
case MOUSE_LEFT_DOWN:
OnMouseLeftDown(y, x);
break;
case MOUSE_LEFT_DRAG:
OnMouseLeftDrag(y, x);
break;
default:
break;
}
}
static void ReadKeyboard(void) {
char buf[32], *p = buf;
memset(buf, 0, sizeof(buf));
if (readansi(0, buf, sizeof(buf)) == -1) {
if (errno == EINTR) return;
exit(errno);
}
switch (*p++) {
case 'q':
exit(0);
case ' ':
case 's':
Generation();
break;
case 'k':
case CTRL('P'):
OnUp();
break;
case 'j':
case CTRL('N'):
OnDown();
break;
case 'M':
if (mousemode) {
DisableMouse();
} else {
EnableMouse();
}
break;
case '\e':
switch (*p++) {
case '[':
switch (*p++) {
case '<':
OnMouse(p);
break;
case 'A':
OnUp();
break;
case 'B':
OnDown();
break;
case 'D':
OnLeft();
break;
case 'C':
OnRight();
break;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
}
static void Draw(void) {
long y, x;
buffer.i = 0;
AppendStr("\e[H");
for (y = top; y < bottom; ++y) {
if (y > top) AppendStr("\e[K\r\n");
for (x = left; x < right; ++x) {
if ((0 <= y && y < byn) && (0 <= x && x < bxn)) {
if (Test(y, x)) {
AppendWide(u'');
} else {
AppendChar(' ');
}
} else {
AppendWide(u'');
}
}
}
write(out, buffer.p, buffer.i);
}
static void Life(void) {
do {
if (action & RESIZED) {
GetTtySize();
action &= ~RESIZED;
}
Draw();
ReadKeyboard();
} while (!(action & INTERRUPTED));
}
int main(int argc, char *argv[]) {
if (!NoDebug()) showcrashreports();
Setup();
Life();
return 0;
}