1397 lines
38 KiB
C
1397 lines
38 KiB
C
/*-*- 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 "dsp/core/gamma.h"
|
||
#include "dsp/scale/scale.h"
|
||
#include "libc/bits/bits.h"
|
||
#include "libc/bits/popcnt.h"
|
||
#include "libc/bits/safemacros.internal.h"
|
||
#include "libc/bits/xchg.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/conv/itoa.h"
|
||
#include "libc/dce.h"
|
||
#include "libc/errno.h"
|
||
#include "libc/fmt/fmt.h"
|
||
#include "libc/log/check.h"
|
||
#include "libc/log/log.h"
|
||
#include "libc/macros.h"
|
||
#include "libc/mem/mem.h"
|
||
#include "libc/nexgen32e/nt2sysv.h"
|
||
#include "libc/nt/comdlg.h"
|
||
#include "libc/nt/dll.h"
|
||
#include "libc/nt/enum/bitblt.h"
|
||
#include "libc/nt/enum/color.h"
|
||
#include "libc/nt/enum/cs.h"
|
||
#include "libc/nt/enum/cw.h"
|
||
#include "libc/nt/enum/ht.h"
|
||
#include "libc/nt/enum/idc.h"
|
||
#include "libc/nt/enum/mb.h"
|
||
#include "libc/nt/enum/mf.h"
|
||
#include "libc/nt/enum/mk.h"
|
||
#include "libc/nt/enum/ofn.h"
|
||
#include "libc/nt/enum/rdw.h"
|
||
#include "libc/nt/enum/sc.h"
|
||
#include "libc/nt/enum/size.h"
|
||
#include "libc/nt/enum/sw.h"
|
||
#include "libc/nt/enum/tpm.h"
|
||
#include "libc/nt/enum/vk.h"
|
||
#include "libc/nt/enum/wm.h"
|
||
#include "libc/nt/enum/ws.h"
|
||
#include "libc/nt/events.h"
|
||
#include "libc/nt/messagebox.h"
|
||
#include "libc/nt/paint.h"
|
||
#include "libc/nt/struct/msg.h"
|
||
#include "libc/nt/struct/openfilename.h"
|
||
#include "libc/nt/struct/paintstruct.h"
|
||
#include "libc/nt/struct/windowplacement.h"
|
||
#include "libc/nt/struct/wndclass.h"
|
||
#include "libc/nt/windows.h"
|
||
#include "libc/rand/rand.h"
|
||
#include "libc/runtime/runtime.h"
|
||
#include "libc/sock/sock.h"
|
||
#include "libc/stdio/stdio.h"
|
||
#include "libc/str/str.h"
|
||
#include "libc/str/tpenc.h"
|
||
#include "libc/sysv/consts/ex.h"
|
||
#include "libc/sysv/consts/exit.h"
|
||
#include "libc/sysv/consts/map.h"
|
||
#include "libc/sysv/consts/poll.h"
|
||
#include "libc/sysv/consts/prot.h"
|
||
#include "libc/sysv/consts/termios.h"
|
||
#include "libc/time/time.h"
|
||
#include "libc/unicode/unicode.h"
|
||
#include "libc/x/x.h"
|
||
#include "third_party/getopt/getopt.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.
|
||
*
|
||
* There's about 20 million Software Engineers in the world, which means
|
||
* Game of Life has likely been implemented 20 million times before. Why
|
||
* do we need this one?
|
||
*
|
||
* - It's a tutorial on how to build an Actually Portable Executable
|
||
* that'll run as a GUI on Windows, and as a TUI on Linux/Mac/BSDs
|
||
* using roughly one thousand lines of code. For a much better GUI
|
||
* that's not as hackable, try Golly: http://golly.sourceforge.net
|
||
*
|
||
* - It's a tutorial on how to implement XTERM mouse cursor dragging
|
||
* where zooming in/out can be performed too using ctrl+mousewheel
|
||
* which is an underused (but easily implemented) terminal feature
|
||
* that even the Windows10 Command Prompt supports these days.
|
||
*
|
||
* - It uses bitboards. That's almost as simple as the naive approach
|
||
* but goes significantly faster, needing 150 picoseconds per board
|
||
* position. See "Bitboard Methods for Games" by Cameron Browne for
|
||
* further details on how it works. More advanced algorithms exist,
|
||
* such as Hashlife: quadtree memoization to make humongous numbers
|
||
* of generations tractable.
|
||
*
|
||
* Here's how you can compile this program on Linux:
|
||
*
|
||
* git clone https://github.com/jart/cosmopolitan && cd cosmopolitan
|
||
* make -j12 o//tool/viz/life.com
|
||
*
|
||
* The output binary works on Linux, Windows, Mac, and FreeBSD:
|
||
*
|
||
* o//tool/viz/life.com
|
||
*
|
||
* @see https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
|
||
* @see https://www.conwaylife.com/wiki/Run_Length_Encoded
|
||
* @see http://golly.sourceforge.net/
|
||
*/
|
||
|
||
#define USAGE \
|
||
" [-zNW] [-w WIDTH] [-h HEIGHT] [PATH]\n\
|
||
\n\
|
||
DESCRIPTION\n\
|
||
\n\
|
||
Conway's Game of Life\n\
|
||
\n\
|
||
FLAGS\n\
|
||
\n\
|
||
-? help\n\
|
||
-z zoom\n\
|
||
-w INT board width\n\
|
||
-h INT board height\n\
|
||
-N natural scrolling\n\
|
||
-W white terminal background\n\
|
||
\n\
|
||
SHORTCUTS\n\
|
||
\n\
|
||
space step\n\
|
||
left+drag draw\n\
|
||
right+drag move\n\
|
||
ctrl+wheel zoom\n\
|
||
ctrl+t turbo\n\
|
||
alt+t slowmo\n\
|
||
R reset\n\
|
||
q quit\n\
|
||
\n"
|
||
|
||
#define MAXZOOM 14
|
||
#define VOIDSPACE "."
|
||
#define ALT (1 << 29)
|
||
|
||
#define INTERRUPTED 1
|
||
#define RESIZED 2
|
||
|
||
#define IDM_ABOUT 0x10
|
||
#define IDM_OPEN 0x20
|
||
|
||
#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
|
||
|
||
struct Buffer {
|
||
unsigned i, n;
|
||
char *p;
|
||
};
|
||
|
||
static bool erase;
|
||
static bool white;
|
||
static bool natural;
|
||
static bool mousemode;
|
||
static bool isdragging;
|
||
static bool dimensioned;
|
||
|
||
static int out;
|
||
static int line;
|
||
static int column;
|
||
static int action;
|
||
static int color[2];
|
||
|
||
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 zoom;
|
||
static long speed;
|
||
static long save_y;
|
||
static long save_x;
|
||
static long save_top;
|
||
static long save_left;
|
||
static long generation;
|
||
|
||
static uint64_t *board;
|
||
static uint64_t *board2;
|
||
static size_t boardsize;
|
||
static int64_t oldcursor;
|
||
|
||
static struct Buffer buffer;
|
||
static struct termios oldterm;
|
||
|
||
static char name[64];
|
||
static char statusline[256];
|
||
static char16_t statusline16[256];
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » algorithm ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
#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 = r0 & (X); \
|
||
c2 = r1 & c1; \
|
||
r0 ^= (X); \
|
||
r1 ^= c1; \
|
||
r2 |= c2; \
|
||
} while (0)
|
||
|
||
#define STEP(RES, B00, B01, B02, B10, B11, B12, B20, B21, B22) \
|
||
do { \
|
||
uint64_t r0 = 0, r1 = 0, r2 = 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 | r0) & r1 & ~r2; \
|
||
} while (0)
|
||
|
||
static void Step(void) {
|
||
long y, x, yn, xn;
|
||
yn = byn >> 3;
|
||
xn = bxn >> 3;
|
||
for (y = 0; y < yn; ++y) {
|
||
for (x = 0; x < xn; ++x) {
|
||
STEP(board2[y * xn + x],
|
||
board[(y ? y - 1 : yn - 1) * xn + (x ? x - 1 : xn - 1)],
|
||
board[(y ? y - 1 : yn - 1) * xn + x],
|
||
board[(y ? y - 1 : yn - 1) * xn + (x + 1 < xn ? x + 1 : 0)],
|
||
board[y * xn + (x ? x - 1 : xn - 1)], board[y * xn + x],
|
||
board[y * xn + (x + 1 < xn ? x + 1 : 0)],
|
||
board[(y + 1 < yn ? y + 1 : 0) * xn + (x ? x - 1 : xn - 1)],
|
||
board[(y + 1 < yn ? y + 1 : 0) * xn + x],
|
||
board[(y + 1 < yn ? y + 1 : 0) * xn + (x + 1 < xn ? x + 1 : 0)]);
|
||
}
|
||
}
|
||
xchg(&board, &board2);
|
||
++generation;
|
||
}
|
||
|
||
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 long Population(void) {
|
||
long i, n, p;
|
||
n = (byn * bxn) >> 6;
|
||
for (p = i = 0; i < n; ++i) {
|
||
p += popcnt(board[i]);
|
||
}
|
||
return p;
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » buffer ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
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);
|
||
}
|
||
|
||
#define AppendStr(s) AppendData(s, strlen(s))
|
||
|
||
static void AppendInt(long x) {
|
||
char ibuf[21];
|
||
AppendData(ibuf, int64toarray_radix10(x, ibuf));
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » board control ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
static void Generation(void) {
|
||
long i;
|
||
for (i = 0; i < speed; ++i) {
|
||
Step();
|
||
}
|
||
}
|
||
|
||
static void Dimension(void) {
|
||
if (!dimensioned) {
|
||
top = byn / 2 - tyn / 2;
|
||
left = bxn / 2 - txn / 2;
|
||
dimensioned = true;
|
||
}
|
||
right = left + txn;
|
||
bottom = top + tyn;
|
||
}
|
||
|
||
static void Move(long dy, long dx) {
|
||
long yn, xn;
|
||
yn = zoom ? tyn * 2 : tyn;
|
||
xn = txn;
|
||
top = top + (dy << zoom);
|
||
left = left + (dx << zoom);
|
||
bottom = top + (yn << zoom);
|
||
right = left + (xn << zoom);
|
||
}
|
||
|
||
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 OnPageUp(void) {
|
||
Move(-(tyn - 2), 0);
|
||
}
|
||
|
||
static void OnPageDown(void) {
|
||
Move(+(tyn - 2), 0);
|
||
}
|
||
|
||
static void OnTurbo(void) {
|
||
++speed;
|
||
}
|
||
|
||
static void OnSlowmo(void) {
|
||
--speed;
|
||
if (speed < 1) speed = 1;
|
||
}
|
||
|
||
static void SetZoom(long y, long x, int d) {
|
||
long a, b;
|
||
if ((0 <= y && y < tyn) && (0 <= x && x < txn)) {
|
||
a = zoom;
|
||
b = MIN(MAXZOOM, MAX(0, a + d));
|
||
zoom = b;
|
||
Move(((y << (a + !!a)) - (y << (b + !!b))) >> b,
|
||
((x << a) - (x << b)) >> b);
|
||
}
|
||
}
|
||
|
||
static void OnZoom(long y, long x) {
|
||
SetZoom(y, x, +1);
|
||
}
|
||
|
||
static void OnUnzoom(long y, long x) {
|
||
SetZoom(y, x, -1);
|
||
}
|
||
|
||
static void OnMouseLeftDrag(long y, long x) {
|
||
if (y == save_y && x == save_x) return;
|
||
save_y = y;
|
||
save_x = x;
|
||
y = top + (y << (zoom + !!zoom));
|
||
x = left + (x << zoom);
|
||
y += rand64() & ((1ul << (zoom + !!zoom)) - 1);
|
||
x += rand64() & ((1ul << zoom) - 1);
|
||
if (y < 0 || y >= byn) return;
|
||
if (x < 0 || x >= bxn) return;
|
||
if (erase) {
|
||
Unset(y, x);
|
||
} else {
|
||
Set(y, x);
|
||
}
|
||
}
|
||
|
||
static void OnMouseLeftUp(long y, long x) {
|
||
isdragging = false;
|
||
}
|
||
|
||
static void OnMouseLeftDown(long y, long x) {
|
||
isdragging = true;
|
||
save_y = y;
|
||
save_x = x;
|
||
y = top + (y << (zoom + !!zoom));
|
||
x = left + (x << zoom);
|
||
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 OnMouseRightUp(long y, long x) {
|
||
isdragging = false;
|
||
}
|
||
|
||
static void OnMouseRightDown(long y, long x) {
|
||
isdragging = true;
|
||
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) << zoom;
|
||
dx = (save_x - x) << zoom;
|
||
if (zoom) dy <<= 1;
|
||
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 *NewBoard(size_t *out_size) {
|
||
char *p;
|
||
size_t s, n, k;
|
||
s = (byn * bxn) >> 3;
|
||
k = PAGESIZE + ROUNDUP(s, PAGESIZE);
|
||
n = ROUNDUP(k + PAGESIZE, FRAMESIZE);
|
||
p = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||
mprotect(p, PAGESIZE, PROT_NONE);
|
||
mprotect(p + k, n - k, PROT_NONE);
|
||
if (out_size) *out_size = n;
|
||
return p + PAGESIZE;
|
||
}
|
||
|
||
static void FreeBoard(void *p, size_t n) {
|
||
munmap((char *)p - PAGESIZE, n);
|
||
}
|
||
|
||
static void AllocateBoardsWithHardwareAcceleratedMemorySafety(void) {
|
||
if (board) {
|
||
FreeBoard(board2, boardsize);
|
||
FreeBoard(board, boardsize);
|
||
}
|
||
board = NewBoard(&boardsize);
|
||
board2 = NewBoard(NULL);
|
||
}
|
||
|
||
static void GenerateStatusLine(void) {
|
||
snprintf(statusline, sizeof(statusline),
|
||
"%s :: %,ldg %,ldp %lds %ldx %ld×%ld (%ld,%ld,%ld,%ld)", name,
|
||
generation, Population(), speed, zoom, byn, bxn, left, top, right,
|
||
bottom);
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » files ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
static void OnHeader(void) {
|
||
size_t n;
|
||
if (!buffer.i) return;
|
||
switch (buffer.p[0]) {
|
||
case 'N':
|
||
if (buffer.i > 2) {
|
||
n = MIN(buffer.i - 2, sizeof(name) - 1);
|
||
memcpy(name, buffer.p + 2, n);
|
||
name[n] = 0;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static int ReadChar(FILE *f) {
|
||
int c;
|
||
++column;
|
||
if ((c = fgetc(f)) == -1) return -1;
|
||
if (c == '\n') {
|
||
++line;
|
||
column = 0;
|
||
}
|
||
return c;
|
||
}
|
||
|
||
static int GetChar(FILE *f) {
|
||
int c;
|
||
for (;;) {
|
||
if ((c = ReadChar(f)) == -1) return -1;
|
||
if (c == '#' && column == 1) {
|
||
for (;;) {
|
||
if ((c = ReadChar(f)) == -1) return -1;
|
||
if (c == '\r') {
|
||
continue;
|
||
} else if (c == '\n') {
|
||
OnHeader();
|
||
break;
|
||
}
|
||
AppendChar(c);
|
||
}
|
||
continue;
|
||
}
|
||
return c;
|
||
}
|
||
}
|
||
|
||
static int LoadFile(const char *path) {
|
||
FILE *f;
|
||
long c, y, x, i, j, n, yn, xn, yo, xo;
|
||
line = 0;
|
||
f = fopen(path, "r");
|
||
if (GetChar(f) != 'x') goto ReadError;
|
||
if (GetChar(f) != ' ') goto ReadError;
|
||
if (GetChar(f) != '=') goto ReadError;
|
||
if (GetChar(f) != ' ') goto ReadError;
|
||
xn = 0;
|
||
for (;;) {
|
||
if ((c = GetChar(f)) == -1) goto ReadError;
|
||
if (!isdigit(c)) break;
|
||
xn *= 10;
|
||
xn += c - '0';
|
||
}
|
||
do {
|
||
if ((c = GetChar(f)) == -1) goto ReadError;
|
||
} while (!isdigit(c));
|
||
yn = 0;
|
||
do {
|
||
yn *= 10;
|
||
yn += c - '0';
|
||
if ((c = GetChar(f)) == -1) goto ReadError;
|
||
} while (isdigit(c));
|
||
while (c != '\n') {
|
||
if ((c = ReadChar(f)) == -1) goto ReadError;
|
||
}
|
||
if (yn > byn || xn > bxn) goto ReadError;
|
||
xchg(&board, &board2);
|
||
memset(board, 0, (byn * bxn) >> 3);
|
||
yo = byn / 2 - yn / 2;
|
||
xo = bxn / 2 - xn / 2;
|
||
y = 0;
|
||
x = 0;
|
||
for (;;) {
|
||
if ((c = GetChar(f)) == -1) goto ReadError;
|
||
if (c == '!') {
|
||
break;
|
||
} else if (isspace(c)) {
|
||
continue;
|
||
}
|
||
if (isdigit(c)) {
|
||
n = c - '0';
|
||
for (;;) {
|
||
if ((c = GetChar(f)) == -1) goto ReadError;
|
||
if (!isdigit(c)) break;
|
||
n *= 10;
|
||
n += c - '0';
|
||
}
|
||
} else {
|
||
n = 1;
|
||
}
|
||
if (c == '$') {
|
||
if (++y == yn) y = 0;
|
||
x = 0;
|
||
} else if (c == 'b' || c == 'o') {
|
||
for (i = 0; i < n; ++i) {
|
||
if (x >= xn) {
|
||
if (++y == yn) y = 0;
|
||
x = 0;
|
||
}
|
||
if (c == 'o') {
|
||
Set(yo + y, xo + x);
|
||
}
|
||
++x;
|
||
}
|
||
} else {
|
||
goto ReadError;
|
||
}
|
||
}
|
||
fclose(f);
|
||
dimensioned = false;
|
||
return 0;
|
||
ReadError:
|
||
fclose(f);
|
||
xchg(&board, &board2);
|
||
return -1;
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » terminal user interface ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
static int Write(const char *s) {
|
||
return write(out, s, strlen(s));
|
||
}
|
||
|
||
static wontreturn void PrintUsage(int rc) {
|
||
Write("SYNOPSIS\n\n ");
|
||
Write(program_invocation_name);
|
||
Write(USAGE);
|
||
exit(rc);
|
||
}
|
||
|
||
static void GetOpts(int argc, char *argv[]) {
|
||
int opt;
|
||
while ((opt = getopt(argc, argv, "?hNWzw:h:")) != -1) {
|
||
switch (opt) {
|
||
case 'w':
|
||
bxn = strtol(optarg, NULL, 0);
|
||
bxn = ROUNDUP(MAX(8, bxn), 8);
|
||
break;
|
||
case 'h':
|
||
byn = strtol(optarg, NULL, 0);
|
||
byn = ROUNDUP(MAX(8, byn), 8);
|
||
break;
|
||
case 'z':
|
||
++zoom;
|
||
break;
|
||
case 'N':
|
||
natural = true;
|
||
break;
|
||
case 'W':
|
||
white = true;
|
||
break;
|
||
case '?':
|
||
PrintUsage(EXIT_SUCCESS);
|
||
default:
|
||
PrintUsage(EX_USAGE);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void HideTtyCursor(void) {
|
||
Write("\e[?25l");
|
||
}
|
||
|
||
static void ShowTtyCursor(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[0m\e[J");
|
||
}
|
||
|
||
static void GetTtySize(void) {
|
||
struct winsize wsize;
|
||
wsize.ws_row = tyn + 1;
|
||
wsize.ws_col = txn;
|
||
getttysize(out, &wsize);
|
||
tyn = wsize.ws_row - 1;
|
||
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();
|
||
ShowTtyCursor();
|
||
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 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_CTRL_WHEEL_UP:
|
||
if (natural) {
|
||
OnZoom(y, x);
|
||
} else {
|
||
OnUnzoom(y, x);
|
||
}
|
||
break;
|
||
case MOUSE_CTRL_WHEEL_DOWN:
|
||
if (natural) {
|
||
OnUnzoom(y, x);
|
||
} else {
|
||
OnZoom(y, x);
|
||
}
|
||
break;
|
||
case MOUSE_RIGHT_DOWN:
|
||
OnMouseRightDown(y, x);
|
||
break;
|
||
case MOUSE_RIGHT_DRAG:
|
||
OnMouseRightDrag(y, x);
|
||
break;
|
||
case MOUSE_RIGHT_UP:
|
||
OnMouseRightUp(y, x);
|
||
break;
|
||
case MOUSE_LEFT_DOWN:
|
||
OnMouseLeftDown(y, x);
|
||
break;
|
||
case MOUSE_LEFT_DRAG:
|
||
OnMouseLeftDrag(y, x);
|
||
break;
|
||
case MOUSE_LEFT_UP:
|
||
OnMouseLeftUp(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':
|
||
case '\t':
|
||
Generation();
|
||
break;
|
||
case 'k':
|
||
case CTRL('P'):
|
||
OnUp();
|
||
break;
|
||
case 'j':
|
||
case CTRL('N'):
|
||
OnDown();
|
||
break;
|
||
case CTRL('V'):
|
||
OnPageDown();
|
||
break;
|
||
case 'M':
|
||
if (mousemode) {
|
||
DisableMouse();
|
||
} else {
|
||
EnableMouse();
|
||
}
|
||
break;
|
||
case 'R':
|
||
memset(board, 0, (byn * bxn) >> 3);
|
||
break;
|
||
case CTRL('T'):
|
||
OnTurbo();
|
||
break;
|
||
case '\e':
|
||
switch (*p++) {
|
||
case 'v':
|
||
OnPageUp();
|
||
break;
|
||
case 't':
|
||
OnSlowmo();
|
||
break;
|
||
case '[':
|
||
switch (*p++) {
|
||
case '<':
|
||
OnMouse(p);
|
||
break;
|
||
case 'A':
|
||
OnUp();
|
||
break;
|
||
case 'B':
|
||
OnDown();
|
||
break;
|
||
case 'D':
|
||
OnLeft();
|
||
break;
|
||
case 'C':
|
||
OnRight();
|
||
break;
|
||
case '5':
|
||
switch (*p++) {
|
||
case '~':
|
||
OnPageUp();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
case '6':
|
||
switch (*p++) {
|
||
case '~':
|
||
OnPageDown();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static int InvertXtermGreyscale(int x) {
|
||
return -(x - 232) + 255;
|
||
}
|
||
|
||
static int ByteToColor(int x) {
|
||
uint8_t c;
|
||
c = x / 256. * 24 + 232;
|
||
if (white) c = InvertXtermGreyscale(c);
|
||
return c;
|
||
}
|
||
|
||
static void SetColor(int x, bool isbg) {
|
||
if (x != color[isbg]) {
|
||
AppendStr("\e[");
|
||
AppendInt(38 + 10 * isbg);
|
||
AppendStr(";5;");
|
||
AppendInt(x);
|
||
AppendStr("m");
|
||
color[isbg] = x;
|
||
}
|
||
}
|
||
|
||
static void SetFg(int x) {
|
||
SetColor(x, false);
|
||
}
|
||
|
||
static void SetBg(int x) {
|
||
SetColor(x, true);
|
||
}
|
||
|
||
static void BitsToBytes(uint8_t a[8], uint64_t x) {
|
||
a[0] = -((x >> 0) & 1);
|
||
a[1] = -((x >> 1) & 1);
|
||
a[2] = -((x >> 2) & 1);
|
||
a[3] = -((x >> 3) & 1);
|
||
a[4] = -((x >> 4) & 1);
|
||
a[5] = -((x >> 5) & 1);
|
||
a[6] = -((x >> 6) & 1);
|
||
a[7] = -((x >> 7) & 1);
|
||
}
|
||
|
||
static void Raster(void) {
|
||
long y, x;
|
||
SetBg(ByteToColor(0));
|
||
SetFg(ByteToColor(255));
|
||
for (y = top; y < bottom; ++y) {
|
||
for (x = left; x < right; ++x) {
|
||
if ((0 <= y && y < byn) && (0 <= x && x < bxn)) {
|
||
if (Test(y, x)) {
|
||
AppendStr("█");
|
||
} else {
|
||
AppendStr(" ");
|
||
}
|
||
} else {
|
||
AppendStr(VOIDSPACE);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void RasterZoomed(long t, long l, long b, long r,
|
||
uint8_t p[b - t][r - l]) {
|
||
uint64_t c;
|
||
uint8_t b1, b2;
|
||
long i, y, x, yn, xn;
|
||
for (y = MAX(0, t); y < MIN(b, byn); y += 8) {
|
||
for (x = MAX(0, l); x < MIN(r, bxn); x += 8) {
|
||
c = board[(bxn >> 3) * (y >> 3) + (x >> 3)];
|
||
for (i = 0; i < 8; ++i) {
|
||
BitsToBytes(&p[y - t + i][x - l], c);
|
||
c >>= 8;
|
||
}
|
||
}
|
||
}
|
||
yn = b - t;
|
||
xn = r - l;
|
||
for (i = 0; i < zoom; ++i) {
|
||
Magikarp2xX(b - t, r - l, p, yn, xn);
|
||
Magikarp2xY(b - t, r - l, p, yn, xn);
|
||
yn >>= 1;
|
||
xn >>= 1;
|
||
}
|
||
for (y = top; y < bottom; y += 2ul << zoom) {
|
||
for (x = left; x < right; x += 1ul << zoom) {
|
||
if ((0 <= y && y < byn) && (0 <= x && x < bxn)) {
|
||
b1 = p[((top - t) + ((y + 0) - top)) >> zoom]
|
||
[((left - l) + (x - left)) >> zoom];
|
||
b2 = y + (1ul << zoom) < bottom
|
||
? p[((top - t) + ((y + 1) - top)) >> zoom]
|
||
[((left - l) + (x - left)) >> zoom]
|
||
: 0;
|
||
if (b1 || b2) {
|
||
SetBg(ByteToColor(b1));
|
||
SetFg(ByteToColor(b2));
|
||
AppendStr("▄");
|
||
} else {
|
||
SetBg(ByteToColor(0));
|
||
SetFg(ByteToColor(255));
|
||
AppendStr(" ");
|
||
}
|
||
} else {
|
||
SetBg(ByteToColor(0));
|
||
SetFg(ByteToColor(255));
|
||
AppendStr(VOIDSPACE);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void Draw(void) {
|
||
void *m;
|
||
long t, l, b, r, i, n;
|
||
buffer.i = 0;
|
||
color[0] = -1;
|
||
color[1] = -1;
|
||
AppendStr("\e[H");
|
||
if (!zoom) {
|
||
Raster();
|
||
} else {
|
||
t = ROUNDDOWN(top, 16);
|
||
l = ROUNDDOWN(left, 16);
|
||
b = ROUNDUP(bottom, 16);
|
||
r = ROUNDUP(right, 16);
|
||
if ((m = calloc((b - t) * (r - l), 1))) {
|
||
RasterZoomed(t, l, b, r, m);
|
||
free(m);
|
||
}
|
||
}
|
||
AppendStr("\e[0;7m");
|
||
GenerateStatusLine();
|
||
AppendStr(statusline);
|
||
n = txn - strwidth(statusline);
|
||
for (i = 0; i < n; ++i) {
|
||
AppendStr(" ");
|
||
}
|
||
AppendStr("\e[0m");
|
||
write(out, buffer.p, buffer.i);
|
||
}
|
||
|
||
static bool HasPendingInput(void) {
|
||
struct pollfd fds[1];
|
||
fds[0].fd = 0;
|
||
fds[0].events = POLLIN;
|
||
fds[0].revents = 0;
|
||
poll(fds, ARRAYLEN(fds), 0);
|
||
return fds[0].revents & (POLLIN | POLLERR);
|
||
}
|
||
|
||
static bool ShouldDraw(void) {
|
||
long double now, rate;
|
||
static long double next;
|
||
if (!isdragging) return true;
|
||
now = nowl();
|
||
rate = 1. / 24;
|
||
if (now > next && !HasPendingInput()) {
|
||
next = now + rate;
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static void Tui(void) {
|
||
GetTtySize();
|
||
Dimension();
|
||
ioctl(out, TCGETS, &oldterm);
|
||
HideTtyCursor();
|
||
EnableRaw();
|
||
EnableMouse();
|
||
atexit(OnExit);
|
||
sigaction(SIGINT, &(struct sigaction){.sa_sigaction = OnSigInt}, NULL);
|
||
sigaction(SIGWINCH, &(struct sigaction){.sa_sigaction = OnSigWinch}, NULL);
|
||
do {
|
||
if (action & RESIZED) {
|
||
GetTtySize();
|
||
action &= ~RESIZED;
|
||
Draw();
|
||
} else if (ShouldDraw()) {
|
||
Draw();
|
||
}
|
||
ReadKeyboard();
|
||
} while (!(action & INTERRUPTED));
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » graphical user interface ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
static const char16_t kClassName[] = u"apelife";
|
||
|
||
static void OnMenuAbout(int64_t hwnd) {
|
||
MessageBox(hwnd, u"\
|
||
Apelife\r\n\
|
||
Cosmopolitan C Library\r\n\
|
||
αcτµαlly pδrταblε εxεcµταblε\r\n\
|
||
By Justine Tunney <jtunney@gmail.com>\r\n\
|
||
In memory of John Horton Conway, 1937-2020\r\n\
|
||
https://github.com/jart/cosmopolitan\r\n\
|
||
\r\n\
|
||
- Hold space to animate\r\n\
|
||
- Hold left mouse to draw cells\r\n\
|
||
- Hold right mouse to move view\r\n\
|
||
- Press t or alt+t to adjust speed",
|
||
u"Conway's Game of Life", kNtMbOk | kNtMbIconinformation);
|
||
}
|
||
|
||
static void OnMenuOpen(int64_t hwnd) {
|
||
char buf8[PATH_MAX];
|
||
char16_t buf16[PATH_MAX];
|
||
struct NtOpenFilename ofn;
|
||
memset(&ofn, 0, sizeof(ofn));
|
||
ofn.lStructSize = sizeof(ofn);
|
||
ofn.hwndOwner = hwnd;
|
||
ofn.lpstrFile = buf16;
|
||
ofn.nMaxFile = ARRAYLEN(buf16);
|
||
ofn.lpstrFilter = u"RLE Format (*.RLE;*.LIF;*.LIFE)\0"
|
||
u"*.RLE;*.LIF;*.LIFE\0"
|
||
u"All (*.*)\0"
|
||
u"*.*\0";
|
||
ofn.Flags = kNtOfnPathmustexist | kNtOfnFilemustexist | kNtOfnExplorer;
|
||
if (GetOpenFileName(&ofn)) {
|
||
tprecode16to8(buf8, sizeof(buf8), ofn.lpstrFile);
|
||
if (LoadFile(buf8) == -1) {
|
||
MessageBox(hwnd, u"Failed to open run-length encoded game file",
|
||
u"Open Failed", kNtMbOk | kNtMbIconerror);
|
||
}
|
||
RedrawWindow(hwnd, 0, 0, kNtRdwInvalidate);
|
||
}
|
||
}
|
||
|
||
static bool IsMaximized(int64_t hwnd) {
|
||
struct NtWindowPlacement wp;
|
||
wp.length = sizeof(wp);
|
||
return GetWindowPlacement(hwnd, &wp) && wp.showCmd == kNtSwMaximize;
|
||
}
|
||
|
||
static void OnWindowPaint(int64_t hwnd) {
|
||
int y, x;
|
||
int64_t mdc, mbm;
|
||
struct NtRect r, w;
|
||
struct NtPaintStruct ps;
|
||
BeginPaint(hwnd, &ps);
|
||
r.top = 0;
|
||
r.left = 0;
|
||
r.right = ps.rcPaint.right - ps.rcPaint.left;
|
||
r.bottom = ps.rcPaint.bottom - ps.rcPaint.top;
|
||
w.top = MAX(r.top, -(top + ps.rcPaint.top));
|
||
w.left = MAX(r.left, -(left + ps.rcPaint.left));
|
||
w.right = MIN(r.right, bxn - (left + ps.rcPaint.left));
|
||
w.bottom = MIN(r.bottom, byn - (top + ps.rcPaint.top));
|
||
mdc = CreateCompatibleDC(ps.hdc);
|
||
mbm = CreateCompatibleBitmap(ps.hdc, r.right, r.bottom);
|
||
SelectObject(mdc, mbm);
|
||
FillRect(mdc, &r, kNtColorInactiveborder);
|
||
FillRect(mdc, &w, kNtColorAppworkspace);
|
||
for (y = w.top; y < w.bottom; ++y) {
|
||
for (x = w.left; x < w.right; ++x) {
|
||
if (Test(top + ps.rcPaint.top + y, left + ps.rcPaint.left + x)) {
|
||
SetPixel(mdc, x, y, 0);
|
||
}
|
||
}
|
||
}
|
||
BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, r.right, r.bottom, mdc, 0, 0,
|
||
kNtSrccopy);
|
||
DeleteObject(mbm);
|
||
DeleteDC(mdc);
|
||
GenerateStatusLine();
|
||
tprecode8to16(statusline16, ARRAYLEN(statusline16), statusline);
|
||
SetWindowText(hwnd, statusline16);
|
||
EndPaint(hwnd, &ps);
|
||
}
|
||
|
||
static void OnWindowCharStep(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int i, repeats;
|
||
repeats = lParam & 0xFFFF;
|
||
for (i = 0; i < repeats; ++i) {
|
||
Generation();
|
||
}
|
||
RedrawWindow(hwnd, 0, 0, kNtRdwInvalidate);
|
||
}
|
||
|
||
static void OnWindowChar(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
switch (wParam) {
|
||
case ' ':
|
||
case 's':
|
||
case '\t':
|
||
OnWindowCharStep(hwnd, wParam, lParam);
|
||
break;
|
||
case 't':
|
||
if (lParam & ALT) {
|
||
OnSlowmo();
|
||
} else {
|
||
OnTurbo();
|
||
}
|
||
break;
|
||
case '\r':
|
||
if (IsMaximized(hwnd)) {
|
||
SendMessage(hwnd, kNtWmSyscommand, kNtScRestore, 0);
|
||
} else {
|
||
SendMessage(hwnd, kNtWmSyscommand, kNtScMaximize, 0);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void OnWindowSize(int64_t lParam) {
|
||
txn = (lParam & 0x0000FFFF) >> 000;
|
||
tyn = (lParam & 0xFFFF0000) >> 020;
|
||
Dimension();
|
||
}
|
||
|
||
static void OnWindowLbuttondown(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int y, x;
|
||
y = (lParam & 0xFFFF0000) >> 020;
|
||
x = (lParam & 0x0000FFFF) >> 000;
|
||
SetCapture(hwnd);
|
||
OnMouseLeftDown(y, x);
|
||
RedrawWindow(hwnd, &(struct NtRect){x, y, x + 1, y + 1}, 0, kNtRdwInvalidate);
|
||
}
|
||
|
||
static void OnWindowLbuttonup(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int y, x;
|
||
y = (lParam & 0xFFFF0000) >> 020;
|
||
x = (lParam & 0x0000FFFF) >> 000;
|
||
OnMouseLeftUp(y, x);
|
||
ReleaseCapture();
|
||
}
|
||
|
||
static void OnWindowRbuttondown(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int y, x;
|
||
y = (lParam & 0xFFFF0000) >> 020;
|
||
x = (lParam & 0x0000FFFF) >> 000;
|
||
oldcursor = GetCursor();
|
||
SetCapture(hwnd);
|
||
SetCursor(LoadCursor(0, kNtIdcSizeall));
|
||
OnMouseRightDown(y, x);
|
||
RedrawWindow(hwnd, NULL, 0, kNtRdwInvalidate);
|
||
}
|
||
|
||
static void OnWindowRbuttonup(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int y, x;
|
||
y = (lParam & 0xFFFF0000) >> 020;
|
||
x = (lParam & 0x0000FFFF) >> 000;
|
||
OnMouseRightUp(y, x);
|
||
SetCursor(oldcursor);
|
||
ReleaseCapture();
|
||
}
|
||
|
||
static void OnWindowMousemove(int64_t hwnd, int64_t wParam, int64_t lParam) {
|
||
int y, x, by, bx;
|
||
y = (lParam & 0xFFFF0000) >> 020;
|
||
x = (lParam & 0x0000FFFF) >> 000;
|
||
if (wParam & kNtMkLbutton) {
|
||
OnMouseLeftDrag(y, x);
|
||
RedrawWindow(hwnd, &(struct NtRect){x, y, x + 1, y + 1}, 0,
|
||
kNtRdwInvalidate);
|
||
} else if (wParam & kNtMkRbutton) {
|
||
OnMouseRightDrag(y, x);
|
||
RedrawWindow(hwnd, NULL, 0, kNtRdwInvalidate);
|
||
}
|
||
}
|
||
|
||
static int64_t WindowProc(int64_t hwnd, uint32_t uMsg, uint64_t wParam,
|
||
int64_t lParam) {
|
||
switch (uMsg) {
|
||
case kNtWmDestroy:
|
||
PostQuitMessage(0);
|
||
return 0;
|
||
case kNtWmSize:
|
||
OnWindowSize(lParam);
|
||
return 0;
|
||
case kNtWmPaint:
|
||
OnWindowPaint(hwnd);
|
||
return 0;
|
||
case kNtWmChar:
|
||
OnWindowChar(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmLbuttondown:
|
||
OnWindowLbuttondown(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmLbuttonup:
|
||
OnWindowLbuttonup(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmRbuttondown:
|
||
OnWindowRbuttondown(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmRbuttonup:
|
||
OnWindowRbuttonup(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmMousemove:
|
||
OnWindowMousemove(hwnd, wParam, lParam);
|
||
return 0;
|
||
case kNtWmCommand:
|
||
case kNtWmSyscommand:
|
||
switch (wParam & ~0xF) {
|
||
case IDM_ABOUT:
|
||
OnMenuAbout(hwnd);
|
||
return 0;
|
||
case IDM_OPEN:
|
||
OnMenuOpen(hwnd);
|
||
return 0;
|
||
}
|
||
/* fallthrough */
|
||
default:
|
||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||
}
|
||
}
|
||
|
||
static void Gui(void) {
|
||
int64_t hwnd, mh;
|
||
struct NtMsg msg;
|
||
struct NtWndClass wc;
|
||
memset(&wc, 0, sizeof(wc));
|
||
wc.lpfnWndProc = NT2SYSV(WindowProc);
|
||
wc.hInstance = GetModuleHandle(NULL);
|
||
wc.hCursor = LoadCursor(0, kNtIdcCross);
|
||
wc.lpszClassName = kClassName;
|
||
wc.hbrBackground = kNtColorInactiveborder;
|
||
CHECK(RegisterClass(&wc));
|
||
CHECK((hwnd = CreateWindowEx(0, kClassName, u"Conway's Game of Life",
|
||
kNtWsOverlappedwindow, kNtCwUsedefault,
|
||
kNtCwUsedefault, kNtCwUsedefault,
|
||
kNtCwUsedefault, 0, 0, wc.hInstance, 0)));
|
||
mh = GetSystemMenu(hwnd, false);
|
||
AppendMenu(mh, kNtMfSeparator, 0, 0);
|
||
AppendMenu(mh, kNtMfEnabled, IDM_OPEN, u"&Open File...");
|
||
AppendMenu(mh, kNtMfEnabled, IDM_ABOUT, u"&About...");
|
||
ShowWindow(hwnd, kNtSwNormal);
|
||
while (GetMessage(&msg, 0, 0, 0)) {
|
||
TranslateMessage(&msg);
|
||
DispatchMessage(&msg);
|
||
}
|
||
}
|
||
|
||
/*───────────────────────────────────────────────────────────────────────────│─╗
|
||
│ cosmopolitan § game of life » program ─╬─│┼
|
||
╚────────────────────────────────────────────────────────────────────────────│*/
|
||
|
||
int main(int argc, char *argv[]) {
|
||
if (!NoDebug()) showcrashreports();
|
||
out = 1;
|
||
speed = 1;
|
||
tyn = right = 80;
|
||
txn = bottom = 24;
|
||
byn = 64 * 64;
|
||
bxn = 64 * 64;
|
||
strcpy(name, "apelife");
|
||
GetOpts(argc, argv);
|
||
AllocateBoardsWithHardwareAcceleratedMemorySafety();
|
||
if (optind < argc) {
|
||
if (LoadFile(argv[optind]) == -1) {
|
||
fprintf(
|
||
stderr, "%s:%d:%d: %s\n", argv[optind], line + 1, column,
|
||
"error: failed to load game of life run length encoded (rle) file");
|
||
return 1;
|
||
}
|
||
}
|
||
if (0 && IsWindows()) {
|
||
Gui();
|
||
} else {
|
||
Tui();
|
||
}
|
||
return 0;
|
||
}
|