tis100

Reimplementation of Zachtronics TIS-100 as a TUI game
git clone https://git.sinitax.com/sinitax/tis100
Log | Files | Refs | sfeed.txt

tis100-curses.c (12319B)


      1#define NCURSES_WIDECHAR 1
      2
      3#include "tpu.h"
      4#include "util.h"
      5#include "asm.h"
      6
      7#include <curses.h>
      8
      9#include <sys/inotify.h>
     10#include <unistd.h>
     11#include <locale.h>
     12
     13#include <errno.h>
     14#include <ctype.h>
     15#include <stdarg.h>
     16#include <string.h>
     17#include <stdio.h>
     18#include <stdbool.h>
     19#include <stdlib.h>
     20
     21#define KEY_ESC 0x1b
     22#define KEY_CTRL(c) ((c) & ~0x60)
     23
     24#define TPU_INFO_W 7
     25#define TPU_INFO_H 4
     26#define TPU_W (1 + TPU_MAX_COLS + TPU_INFO_W)
     27#define TPU_H (1 + TPU_MAX_ROWS + 1)
     28
     29#define TIMEOUT 50
     30
     31enum input_mode {
     32	MAIN,
     33	TPU_NAV
     34};
     35
     36enum {
     37	NONE, MIN, MAX, MID
     38};
     39
     40enum {
     41	THEME_DEFAULT,
     42	THEME_DISABLED
     43};
     44
     45static const char *mode_repr[] = {
     46	"IDL", "RUN", "REA", "WRI"
     47};
     48
     49static int scrx = 0;
     50static int scry = 0;
     51static int scrw = 80;
     52static int scrh = 40;
     53
     54static struct tis tis;
     55
     56static int show_reloaded = 0;
     57
     58static enum input_mode input_mode = MAIN;
     59
     60static struct tpu *tpu_sel = NULL;
     61
     62int (*cleanup)(void) = endwin;
     63const char *progname = "tis100-curses";
     64
     65static enum tpu_port_dir
     66key_to_dir(int key)
     67{
     68	switch (key) {
     69	case KEY_LEFT:
     70		return DIR_LEFT;
     71	case KEY_RIGHT:
     72		return DIR_RIGHT;
     73	case KEY_UP:
     74		return DIR_UP;
     75	case KEY_DOWN:
     76		return DIR_DOWN;
     77	default:
     78		abort();
     79	}
     80}
     81
     82static const cchar_t *
     83dir_to_arrow(enum tpu_port_dir dir)
     84{
     85	switch (dir) {
     86	case DIR_UP:
     87		return WACS_UARROW;
     88	case DIR_DOWN:
     89		return WACS_DARROW;
     90	case DIR_LEFT:
     91		return WACS_LARROW;
     92	case DIR_RIGHT:
     93		return WACS_RARROW;
     94	}
     95}
     96
     97static int
     98tpu_pos_x(struct tpu *tpu)
     99{
    100	return 2 + (int) tpu->x * (TPU_W + 4);
    101}
    102
    103static int
    104tpu_pos_y(struct tpu *tpu)
    105{
    106	return 2 + (int) tpu->y * (TPU_H + 2);
    107}
    108
    109static void
    110tui_draw_box(int sx, int sy, int w, int h, attr_t attr,
    111	const cchar_t *ul, const cchar_t *ur,
    112	const cchar_t *ll, const cchar_t *lr)
    113{
    114	int x, y;
    115
    116	if (sx + w < scrx || sx >= scrx + scrw) return;
    117	if (sy + h < scry || sy >= scry + scrh) return;
    118
    119	sx -= scrx;
    120	sy -= scry;
    121
    122	attron(attr);
    123	mvadd_wch(sy, sx, ul);
    124	mvadd_wch(sy, sx + w - 1, ur);
    125	mvadd_wch(sy + h - 1, sx, ll);
    126	mvadd_wch(sy + h - 1, sx + w - 1, lr);
    127	for (x = sx + 1; x < sx + w - 1; x++)
    128		mvadd_wch(sy, x, WACS_D_HLINE);
    129	for (x = sx + 1; x < sx + w - 1; x++)
    130		mvadd_wch(sy + h - 1, x, WACS_D_HLINE);
    131	for (y = sy + 1; y < sy + h - 1; y++)
    132		mvadd_wch(y, sx, WACS_D_VLINE);
    133	for (y = sy + 1; y < sy + h - 1; y++)
    134		mvadd_wch(y, sx + w - 1, WACS_D_VLINE);
    135	attroff(attr);
    136}
    137
    138static void
    139__attribute__((format(printf, 4, 5)))
    140tui_draw_text(int x, int y, attr_t attr, const char *fmt, ...)
    141{
    142	char buf[256];
    143	va_list ap;
    144	int i;
    145
    146	va_start(ap, fmt);
    147	vsnprintf(buf, 256, fmt, ap);
    148	va_end(ap);
    149
    150	attron(attr);
    151	for (i = 0; i < strlen(buf) && x + i < scrx + scrw; i++)
    152		mvaddch(y - scry, x + i - scrx, (chtype) buf[i]);
    153	attroff(attr);
    154}
    155
    156static void
    157tui_draw_lit(int x, int y, attr_t attr, int lit)
    158{
    159	char buf[6];
    160
    161	snprintf(buf, 5, "% 04i", lit);
    162	tui_draw_text(x, y, attr, "%.*s", 4, buf);
    163}
    164
    165static void
    166tui_draw_wch(int x, int y, attr_t attr, const cchar_t *c)
    167{
    168	attron(attr);
    169	mvadd_wch(y - scry, x - scrx, c);
    170	attroff(attr);
    171}
    172
    173static void
    174tui_draw_tpu(struct tpu *tpu)
    175{
    176	char rowbuf[TPU_MAX_COLS + 1];
    177	struct tpu_port *port;
    178	int sx, sy, x, y, w, h;
    179	int offx, offy, start, inst;
    180	struct label *label;
    181	size_t len;
    182	attr_t attr;
    183	int idle;
    184
    185	attr = (tpu_sel == tpu && input_mode == TPU_NAV) ? A_BOLD : 0;
    186	attr |= tpu->disabled ? COLOR_PAIR(THEME_DISABLED) : COLOR_PAIR(THEME_DEFAULT);
    187
    188	sx = tpu_pos_x(tpu);
    189	sy = tpu_pos_y(tpu);
    190	tui_draw_box(sx, sy, TPU_W, TPU_H, attr,
    191		WACS_D_ULCORNER, WACS_D_URCORNER,
    192		WACS_D_LLCORNER, WACS_D_LRCORNER);
    193
    194	if (tpu->disabled) {
    195		tui_draw_text(sx + 4, sy + 1 + 1, attr | A_REVERSE, "             ");
    196		tui_draw_text(sx + 4, sy + 1 + 3, attr | A_BOLD,    "COMMUNICATION");
    197		tui_draw_text(sx + 4, sy + 1 + 4, attr | A_BOLD,    "   FAILURE   ");
    198		tui_draw_text(sx + 4, sy + 1 + 6, attr | A_REVERSE, "             ");
    199	} else if (tpu->inst_cnt > 0) {
    200		start = MAX(0, MIN(tpu->pc - 4, (int) tpu->inst_cnt - TPU_MAX_ROWS));
    201		inst = start;
    202		for (offy = 0; offy < TPU_MAX_ROWS && inst < tpu->inst_cnt; ) {
    203			asm_print_inst(rowbuf, sizeof(rowbuf), &tpu->insts[inst],
    204				TPU_MAX_COLS - (size_t) offx);
    205			label = &tpu->label_map.labels[inst];
    206			if (label->str) {
    207				len = strlen(label->str);
    208				tui_draw_text(sx + 1 + offx, sy + 1 + offy,
    209					A_DIM, "%s:", label->str);
    210				if (label->prefix) {
    211					offx += (int) strlen(label->str) + 1;
    212					if (offx + 1 + (int) strlen(rowbuf) <= TPU_MAX_COLS)
    213						offx += 1;
    214				} else {
    215					offy += 1;
    216				}
    217			}
    218			if ((size_t) offx + strlen(rowbuf) > TPU_MAX_COLS)
    219				asm_print_inst(rowbuf, sizeof(rowbuf), &tpu->insts[inst],
    220					TPU_MAX_COLS - (size_t) offx);
    221			tui_draw_text(sx + 1 + offx, sy + 1 + offy,
    222				inst == tpu->pc ?
    223				A_STANDOUT | (tpu->idle ? A_DIM : 0) : 0,
    224				"%-*.*s", TPU_MAX_COLS - offx,
    225				TPU_MAX_COLS, rowbuf);
    226			inst += 1;
    227			offy += 1;
    228			offx = 0;
    229		}
    230	}
    231
    232	x = sx + TPU_W - TPU_INFO_W;
    233	y = sy;
    234	w = TPU_INFO_W;
    235	h = TPU_INFO_H;
    236	tui_draw_box(x, y, w, h, attr,
    237		WACS_D_TTEE, WACS_D_URCORNER, WACS_D_LTEE, WACS_D_RTEE);
    238	tui_draw_text(x + 2, y + 1, attr | A_BOLD, "ACC");
    239	tui_draw_lit(x + 1, y + 2, attr, tpu->acc);
    240
    241	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    242		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    243	tui_draw_text(x + 2, y + 1, attr | A_BOLD, "BAK");
    244	tui_draw_lit(x + 1, y + 2, attr, tpu->bak);
    245
    246	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    247		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    248	tui_draw_text(x + 2, y + 1, attr | A_BOLD, "LST");
    249	if (tpu->last < 0) {
    250		tui_draw_text(x + 2, y + 2, attr, "N/A");
    251	} else {
    252		tui_draw_wch(x + 2, y + 2, attr,
    253			dir_to_arrow((enum tpu_port_dir) tpu->last));
    254		tui_draw_wch(x + 3, y + 2, attr,
    255			dir_to_arrow((enum tpu_port_dir) tpu->last));
    256		tui_draw_wch(x + 4, y + 2, attr,
    257			dir_to_arrow((enum tpu_port_dir) tpu->last));
    258	}
    259
    260	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    261		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    262	tui_draw_text(x + 2, y + 1, attr | A_BOLD, "MOD");
    263	tui_draw_text(x + 2, y + 2, attr, "%s", mode_repr[tpu->mode]);
    264
    265	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    266		WACS_D_LTEE, WACS_D_RTEE, WACS_D_BTEE, WACS_D_RTEE);
    267	tui_draw_text(x + 2, y + 1, attr | A_BOLD, "IDL");
    268	if (tpu->steps > 0)
    269		idle = (int) ((double) tpu->idle_steps * 100 / (double) tpu->steps);
    270	else
    271		idle = 0;
    272	tui_draw_text(x + 2, y + 2, attr, "%03i", idle);
    273
    274	tui_draw_box(x, (y += TPU_INFO_H - 1), w, 2, attr,
    275		WACS_D_LTEE, WACS_D_RTEE, WACS_D_BTEE, WACS_D_LRCORNER);
    276
    277	if (tpu->ports[DIR_LEFT].attached) {
    278		port = &tpu->ports[DIR_LEFT];
    279		if (port->avail)
    280			tui_draw_lit(sx - 4, sy + 6, A_BOLD, port->in);
    281		if (port->type & PORT_IN)
    282			tui_draw_wch(sx - 1, sy + 7,
    283				port->avail ? A_BOLD : 0, WACS_RARROW);
    284		if (port->type & PORT_OUT)
    285			tui_draw_wch(sx - 1, sy + 8,
    286				port->writing ? A_BOLD : 0, WACS_LARROW);
    287		if (port->writing)
    288			tui_draw_lit(sx - 4, sy + 10, A_BOLD, port->out);
    289	}
    290
    291	if (tpu->ports[DIR_RIGHT].attached) {
    292		port = &tpu->ports[DIR_RIGHT];
    293		if (port->writing)
    294			tui_draw_lit(sx + TPU_W, sy + 5, A_BOLD, port->out);
    295		if (port->type & PORT_OUT)
    296			tui_draw_wch(sx + TPU_W, sy + 7,
    297				port->writing ? A_BOLD : 0, WACS_RARROW);
    298		if (port->type & PORT_IN)
    299			tui_draw_wch(sx + TPU_W, sy + 8,
    300				port->avail ? A_BOLD : 0, WACS_LARROW);
    301		if (port->avail)
    302			tui_draw_lit(sx + TPU_W, sy + 9, A_BOLD, port->in);
    303	}
    304
    305	if (tpu->ports[DIR_UP].attached) {
    306		port = &tpu->ports[DIR_UP];
    307		if (port->dst_port->io)
    308			tui_draw_text(sx + 10, sy - 1, A_BOLD,
    309				"IN.%c", toupper(port->dst_port->io->c));
    310		if (port->writing)
    311			tui_draw_lit(sx + 8, sy - 1, A_BOLD, port->out);
    312		if (port->type & PORT_OUT)
    313			tui_draw_wch(sx + 13, sy - 1,
    314				port->writing ? A_BOLD : 0, WACS_UARROW);
    315		if (port->type & PORT_IN)
    316			tui_draw_wch(sx + 15, sy - 1,
    317				port->avail ? A_BOLD : 0, WACS_DARROW);
    318		if (port->avail)
    319			tui_draw_lit(sx + 16, sy - 1, A_BOLD, port->in);
    320	}
    321
    322	if (tpu->ports[DIR_DOWN].attached) {
    323		port = &tpu->ports[DIR_DOWN];
    324		if (port->dst_port->io)
    325			tui_draw_text(sx + 9, sy + TPU_H, A_BOLD,
    326				"OUT.%c", toupper(port->dst_port->io->c));
    327		if (port->avail)
    328			tui_draw_lit(sx + 8, sy + TPU_H, A_BOLD, port->in);
    329		if (port->type & PORT_IN)
    330			tui_draw_wch(sx + 13, sy + TPU_H,
    331				port->avail ? A_BOLD : 0, WACS_UARROW);
    332		if (port->type & PORT_OUT)
    333			tui_draw_wch(sx + 15, sy + TPU_H,
    334				port->writing ? A_BOLD : 0, WACS_DARROW);
    335		if (port->writing)
    336			tui_draw_lit(sx + 16, sy + TPU_H, A_BOLD, port->out);
    337	}
    338}
    339
    340static void
    341tui_draw(void)
    342{
    343	struct tpu_map_link *link;
    344	size_t i;
    345	int tx;
    346
    347	clear();
    348	for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    349		for (link = tis.tpu_map.buckets[i]; link; link = link->next)
    350			tui_draw_tpu(link->tpu);
    351	}
    352	if (show_reloaded > 0) {
    353		tx = MAX(0, scrx + scrw / 2 - 4);
    354		tui_draw_text(scrx, scry, A_STANDOUT, "%-*s", scrw, "");
    355		tui_draw_text(tx, scry, A_STANDOUT, "RELOADED");
    356		show_reloaded--;
    357	}
    358	refresh();
    359}
    360
    361static void
    362tui_resize(void)
    363{
    364	scrw = getmaxx(stdscr);
    365	scrh = getmaxy(stdscr);
    366}
    367
    368static void
    369tui_seek(struct tpu *tpu, int dx, int dy)
    370{
    371	struct tpu_map_link *link;
    372	int minx, miny, maxx, maxy;
    373	int x, y;
    374	size_t i;
    375
    376	if (tpu) {
    377		minx = maxx = tpu_pos_x(tpu);
    378		miny = maxy = tpu_pos_y(tpu);
    379 	} else {
    380		minx = miny = maxx = maxy = -1;
    381		for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    382			link = tis.tpu_map.buckets[i];
    383			for (; link; link = link->next) {
    384				if (tpu && tpu != link->tpu)
    385					continue;
    386				x = tpu_pos_x(link->tpu);
    387				if (minx == -1 || x < minx) minx = x;
    388				if (maxx == -1 || x > maxx) maxx = x;
    389				y = tpu_pos_y(link->tpu);
    390				if (miny == -1 || y < miny) miny = y;
    391				if (maxy == -1 || y > maxy) maxy = y;
    392			}
    393		}
    394		if (minx == -1 || miny == -1) return;
    395	}
    396
    397	if (dx == MIN) scrx = minx - 2;
    398	else if (dx == MAX) scrx = maxx + TPU_W + 2 - scrw;
    399	else if (dx == MID) scrx = (minx + maxx + TPU_W - scrw) / 2;
    400
    401	if (dy == MIN) scry = miny - 2;
    402	else if (dy == MAX) scry = maxy + TPU_H + 2 - scrh;
    403	else if (dy == MID) scry = (miny + maxy + TPU_H - scrh) / 2;
    404}
    405
    406static void
    407handlekey(int key)
    408{
    409	enum tpu_port_dir dir;
    410
    411	if (input_mode == MAIN) {
    412		switch (key) {
    413		case 'I':
    414			input_mode = TPU_NAV;
    415			break;
    416		case KEY_UP:
    417			scry -= 2;
    418			break;
    419		case KEY_DOWN:
    420			scry += 2;
    421			break;
    422		case KEY_LEFT:
    423			scrx -= 4;
    424			break;
    425		case KEY_RIGHT:
    426			scrx += 4;
    427			break;
    428		case 's':
    429			tis_step(&tis);
    430			break;
    431		}
    432	} else {
    433		switch (key) {
    434		case KEY_ESC:
    435			input_mode = MAIN;
    436			break;
    437		case KEY_UP:
    438		case KEY_DOWN:
    439		case KEY_LEFT:
    440		case KEY_RIGHT:
    441			dir = key_to_dir(key);
    442			if (tpu_sel && tpu_sel->ports[dir].dst_port->tpu)
    443				tpu_sel = tpu_sel->ports[dir].dst_port->tpu;
    444			tui_seek(tpu_sel, MID, MID);
    445			break;
    446		}
    447	}
    448}
    449
    450static struct tpu *
    451first_tpu(void)
    452{
    453	size_t i;
    454
    455	for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    456		if (tis.tpu_map.buckets[i])
    457			return tis.tpu_map.buckets[i]->tpu;
    458	}
    459
    460	return NULL;
    461}
    462
    463static void
    464reset(int ifd, int argc, const char **argv, bool watch)
    465{
    466	tis_deinit(&tis);
    467	tis_init(&tis);
    468	tis_load(&tis, argv);
    469
    470	if (watch) {
    471		if (inotify_add_watch(ifd, tis.asm_filepath, IN_MODIFY) < 0)
    472			die("inotify_add_watch '%s':", argv[1]);
    473	}
    474
    475	tpu_sel = first_tpu();
    476}
    477
    478int
    479main(int argc, const char **argv)
    480{
    481	struct inotify_event event;
    482	ssize_t len;
    483	bool quit;
    484	int key;
    485	int ifd;
    486
    487	if (argc < 2) {
    488		fprintf(stderr, "Usage: tis100-curses FILE [IO..]\n");
    489		exit(1);
    490	}
    491
    492	setlocale(LC_ALL, "");
    493
    494	initscr();
    495
    496	raw();
    497	noecho();
    498	keypad(stdscr, TRUE);
    499	start_color();
    500	init_pair(THEME_DEFAULT, COLOR_WHITE, COLOR_BLACK);
    501	init_pair(THEME_DISABLED, COLOR_RED, COLOR_BLACK);
    502	curs_set(0);
    503	tui_resize();
    504	timeout(TIMEOUT);
    505	ESCDELAY = 0;
    506
    507	tis_init(&tis);
    508
    509	ifd = inotify_init1(IN_NONBLOCK);
    510
    511	reset(ifd, argc, argv, true);
    512
    513	quit = false;
    514	while (!quit) {
    515		len = read(ifd, &event, sizeof(event));
    516		if (len < 0 && errno != EAGAIN)
    517			die("inotify_read:");
    518		if (len >= 0) {
    519			reset(ifd, argc, argv, true);
    520			show_reloaded = 1000 / TIMEOUT;
    521		}
    522
    523		tui_draw();
    524		key = getch();
    525		switch (key) {
    526		case KEY_RESIZE:
    527			tui_resize();
    528			break;
    529		case 'g':
    530			tui_seek(tpu_sel, MID, MID);
    531			break;
    532		case 'h':
    533			tui_seek(NULL, MID, MID);
    534			break;
    535		case 'r':
    536			reset(ifd, argc, argv, false);
    537			break;
    538		case 'q':
    539		case KEY_CTRL('c'):
    540			quit = true;
    541			break;
    542		default:
    543			handlekey(key);
    544			break;
    545		}
    546	}
    547
    548	tis_deinit(&tis);
    549
    550	close(ifd);
    551
    552	endwin();
    553}