Recently Written · git

sacc

Mirror of git://bitreich.org/sacc, the sacc gopher client from bitreich.org.

git clone https://github.com/equwal/sacc

Log | Files | Refs


commit d16bb53db353da7b817b18809adf57b57c4f973e
Quentin Rameau <quinq@fifth.space>
2024-03-28 12:13:51 +0100

UI: Add pazz0's dungeon-crawler-like interface

This definitely looks fun and interesting

 ui_rogue.c | 938 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 938 insertions(+)
diff --git a/ui_rogue.c b/ui_rogue.c
new file mode 100644
index 0000000..cd8859c
--- /dev/null
+++ b/ui_rogue.c
@@ -0,0 +1,938 @@
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "common.h"
+#include "config.h"
+
+#define C(c) #c
+#define S(c) C(c)
+
+/* ncurses doesn't define those in term.h, where they're used */
+#ifndef OK
+#define OK (0)
+#endif
+#ifndef ERR
+#define ERR (-1)
+#endif
+
+static struct termios tsave;
+static struct termios tsacc;
+static Item *curentry;
+static int termset = ERR;
+static char bufout[256];
+static char bufout2[256];
+
+void drawscreen(void);
+
+uint32_t
+fnv1a(int n,...)
+{
+	int i;
+	char *s;
+	va_list l;
+	uint32_t h;
+
+	h = 0x811c9dc5;
+
+	va_start(l, n); 
+	for (i = 0; i < n; i++) {
+		for (s = va_arg(l, char*); *s; s++) {
+			h ^= *s;
+			h *= 0x01000193;
+		}
+	}
+	va_end(l);
+
+	return h;
+}
+
+uint32_t
+xorshift(uint32_t *s)
+{
+	*s ^= *s << 13;
+	*s ^= *s >> 17;
+	*s ^= *s << 5;
+	return *s;
+}
+
+struct cell {
+	char c;
+	size_t nitems;
+	Item **items;
+};
+
+#define MAPHEIGHT (25)
+#define MAPWIDTH (80)
+struct cell map[MAPHEIGHT][MAPWIDTH];
+
+struct room {
+	struct room *p;
+	void *d;
+	size_t x, y;
+	size_t w, h;
+};
+
+struct rect {
+	struct rect *next, *next2;
+	struct room *room;
+	size_t x1, y1;
+	size_t x2, y2;
+	size_t d;
+};
+
+struct rect *
+randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng)
+{
+	struct rect *r, *result;
+	size_t n;
+
+	n = 0;
+	result = NULL;
+	for (r = rs; r; r = r->next) {
+		if (r->y2 < x->y1 || r->y1 > x->y2 || r->x2 < x->x1 || r->x1 > x->x2)
+			continue;
+		if ((r->y2 == x->y1 || r->y1 == x->y2) && (r->x2 == x->x1 || r->x1 == x->x2))
+			continue;
+		n++;
+		if (xorshift(prng) / (1. + UINT32_MAX) < 1. / n)
+			result = r;
+	}
+
+	return result;
+}
+
+#define ROOM_HEIGHT_MIN 3
+#define ROOM_WIDTH_MIN 5
+#define ROOM_MARGIN_MIN 1
+#define CELL_HEIGHT_MIN (ROOM_HEIGHT_MIN + ROOM_MARGIN_MIN + 3)
+#define CELL_WIDTH_MIN (ROOM_WIDTH_MIN + ROOM_MARGIN_MIN + 3)
+size_t
+generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
+{
+	struct rect *queuehead, *queuetail;
+	struct rect *r, *t;
+	struct rect *rects, *walk;
+	size_t w, h, i, j, rl, n;
+	int vertical;
+	struct room *room;
+
+	r = malloc(sizeof(*r));
+	r->x1 = r->y1 = ROOM_MARGIN_MIN;
+	r->x2 = MAPWIDTH;
+	r->y2 = MAPHEIGHT;
+	r->d = 0;
+
+	queuetail = r;
+	queuetail->next = NULL;
+	queuehead = r;
+
+	rects = NULL;
+	rl = 0;
+
+	while (queuehead) {
+		r = queuehead;
+		if (queuetail == queuehead)
+			queuetail = NULL;
+		queuehead = queuehead->next;
+
+		if (r->x2 - r->x1 >= CELL_WIDTH_MIN * 2 && r->y2 - r->y1 >= CELL_HEIGHT_MIN * 2) {
+			vertical = xorshift(&prng) & 1;
+		} else if (r->x2 - r->x1 >= CELL_WIDTH_MIN * 2) {
+			vertical = 0;
+		} else if (r->y2 - r->y1 >= CELL_HEIGHT_MIN * 2) {
+			vertical = 1;
+		} else {
+			r->next = rects;
+			rects = r;
+			rl++;
+			continue;
+		}
+
+		if (vertical) {
+			w = r->x2 - r->x1;
+			h = CELL_HEIGHT_MIN + xorshift(&prng) % (1 + r->y2 - r->y1 - CELL_HEIGHT_MIN * 2);
+		} else {
+			w = CELL_WIDTH_MIN + xorshift(&prng) % (1 + r->x2 - r->x1 - CELL_WIDTH_MIN * 2);
+			h = r->y2 - r->y1;
+		}
+
+		t = malloc(sizeof(*t));
+		t->x1 = r->x1;
+		t->y1 = r->y1;
+		t->x2 = r->x1 + w;
+		t->y2 = r->y1 + h;
+		t->d = r->d + 1;
+		t->next = NULL;
+		t->room = NULL;
+
+		if (!queuetail) {
+			queuehead = t;
+			queuetail = t;
+		} else {
+			queuetail->next = t;
+			queuetail = t;
+		}
+
+		t = malloc(sizeof(*t));
+		if (vertical) {
+			t->x1 = r->x1;
+			t->y1 = r->y1 + h;
+		} else {
+			t->x1 = r->x1 + w;
+			t->y1 = r->y1;
+		}
+		t->x2 = r->x2;
+		t->y2 = r->y2;
+		t->d = r->d + 1;
+		t->next = NULL;
+		t->room = NULL;
+
+		queuetail->next = t;
+		queuetail = t;
+
+		free(r);
+	}
+
+	if (l > rl)
+		l = rl;
+
+	for (r = rects; r; r = r->next) {
+		if (MAPHEIGHT / 2 >= r->y1 && MAPHEIGHT / 2 < r->y2 &&
+		    MAPWIDTH / 2 >= r->x1 && MAPWIDTH / 2 < r->x2)
+			break;
+	}
+	
+	i = 0;
+	rs[i].w = ROOM_WIDTH_MIN + xorshift(&prng) % (1 + r->x2 - r->x1 - ROOM_MARGIN_MIN - ROOM_WIDTH_MIN);
+	rs[i].h = ROOM_HEIGHT_MIN + xorshift(&prng) % (1 + r->y2 - r->y1 - ROOM_MARGIN_MIN - ROOM_HEIGHT_MIN);
+	rs[i].x = r->x1 + xorshift(&prng) % (1 + r->x2 - r->x1 - ROOM_MARGIN_MIN - rs[i].w);
+	rs[i].y = r->y1 + xorshift(&prng) % (1 + r->y2 - r->y1 - ROOM_MARGIN_MIN - rs[i].h);
+	rs[i].p = NULL;
+	r->room = &rs[i];
+
+	walk = r;
+	walk->next2 = NULL;
+
+	i++;
+	for (; i < l;) {
+		t = randomneighbor(r, rects, &prng);
+		if (!t || t->room) {
+			n = 0;
+			for (t = walk; t; t = t->next2) {
+				n++;
+				if (xorshift(&prng) / (1. + UINT32_MAX) < 1. / n)
+					r = t;
+				
+			}
+			continue;
+		}
+		rs[i].w = ROOM_WIDTH_MIN + xorshift(&prng) % (1 + t->x2 - t->x1 - ROOM_MARGIN_MIN - ROOM_WIDTH_MIN);
+		rs[i].h = ROOM_HEIGHT_MIN + xorshift(&prng) % (1 + t->y2 - t->y1 - ROOM_MARGIN_MIN - ROOM_HEIGHT_MIN);
+		rs[i].x = t->x1 + xorshift(&prng) % (1 + t->x2 - t->x1 - ROOM_MARGIN_MIN - rs[i].w);
+		rs[i].y = t->y1 + xorshift(&prng) % (1 + t->y2 - t->y1 - ROOM_MARGIN_MIN - rs[i].h);
+		rs[i].p = r->room;
+		t->room = &rs[i];
+		i++;
+		r = t;
+		r->next2 = walk;
+		walk = r;
+	}
+
+	for (r = rects; r;) {
+		t = r->next;
+		free(r);
+		r = t;
+	}
+
+	return l;
+}
+
+size_t
+distance(size_t x1, size_t y1, size_t x2, size_t y2)
+{
+	size_t d;
+
+	if (y1 < y2)
+		d = y2 - y1;
+	else
+		d = y1 - y2;
+	if (x1 < x2)
+		d += x2 - x1;
+	else
+		d += x1 - x2;
+
+	return d;
+}
+
+void
+nearestpoints(struct room *a, struct room *b, size_t *ax, size_t *ay, size_t *bx, size_t *by)
+{
+	if (a->y >= b->y && a->y < b->y + b->h) {
+		*ay = *by = a->y;
+	} else if (b->y >= a->y && b->y < a->y + a->h) {
+		*ay = *by = b->y;
+	} else if (a->y >= b->y) {
+		*ay = a->y;
+		*by = b->y + b->h - 1;
+	} else if (b->y >= a->y) {
+		*ay = a->y + a->h - 1;
+		*by = b->y;
+	}
+
+	if (a->x >= b->x && a->x < b->x + b->w) {
+		*ax = *bx = a->x;
+	} else if (b->x >= a->x && b->x < a->x + a->w) {
+		*ax = *bx = b->x;
+	} else if (a->x >= b->x) {
+		*ax = a->x;
+		*bx = b->x + b->w - 1;
+	} else if (b->x >= a->x) {
+		*ax = a->x + a->w - 1;
+		*bx = b->x;
+	}
+}
+
+void
+connectrooms(struct room *a, struct room *b)
+{
+	size_t i, j;
+	ssize_t ii;
+	size_t x1, y1;
+	size_t x2, y2;
+
+	nearestpoints(a, b, &x1, &y1, &x2, &y2);
+
+	if (y1 > y2) {
+		ii = -1;
+	} else if (y2 > y1) {
+		ii = 1;
+	} else {
+		ii = 0;
+	}
+
+/*
+printf("%lu\t%lu\t%d\n", y1, y2, ii);
+*/
+	for (i = y1; i != y2; i += ii)
+		map[i][x1].c = '.';
+
+	if (x1 > x2) {
+		ii = -1;
+	} else if (x2 > x1) {
+		ii = 1;
+	} else {
+		ii = 0;
+	}
+
+	for (i = x1; i != x2; i += ii)
+		map[y2][i].c = '.';
+}
+
+void
+rendermap(void)
+{
+	size_t i, j;
+
+	for (i = 0; i < MAPHEIGHT; i++) {
+		for (j = 0; j < MAPWIDTH; j++)
+			putchar(map[i][j].c);
+		putchar('\n');
+	}
+}
+
+size_t
+placeitems_hash(Item *item, size_t *assocs, size_t k)
+{
+	Dir *dir;
+	Item *citem;
+	size_t i;
+
+	dir = item->dat;
+	for (i = 0; i < dir->nitems; i++) {
+		citem = &dir->items[i];
+		/* TODO Somewhere else */
+		if (!citem->host || !citem->port || !citem->selector)
+			continue;
+		assocs[i] = fnv1a(6, item->host, item->port, item->selector, citem->host, citem->port, citem->selector) % k;
+	}
+
+	return k;
+}
+
+#define POSITIONS_LENGTH 4
+enum {
+	Portal,
+	StaircaseDown,
+	Bookshelf,
+	Back
+};
+
+size_t px, py;
+
+void
+generatemap(Item *item, int new)
+{
+	Dir *dir;
+	Item *citem;
+	size_t i, j, k, l, ir;
+	size_t x, y;
+	ssize_t n, m;
+	size_t *cassocs;
+	struct room *rooms, *r;
+	struct {
+		unsigned char x, y;
+	} positions[POSITIONS_LENGTH];
+	uint32_t prng;
+	char buffer[3];
+
+	for (i = 0; i < MAPHEIGHT; i++) {
+		for (j = 0; j < MAPWIDTH; j++) {
+			map[i][j].c = '#';
+			free(map[i][j].items);
+			map[i][j].items = NULL;
+			map[i][j].nitems = 0;
+		}
+	}
+
+	dir = item->dat;
+	for (j = l = 0; j < dir->nitems; j++) {
+		if (dir->items[j].type == '0' ||
+		    dir->items[j].type == '1')
+			l++;
+	}
+
+	k = 1 + l / 10;
+	rooms = calloc(k, sizeof(*rooms));
+	if (!rooms)
+		return;
+	k = generaterooms_gnarf(fnv1a(3, item->host, item->port, item->selector), rooms, k);
+
+	cassocs = calloc(dir->nitems, sizeof(*cassocs));
+	if (!cassocs)
+		goto cleanup;
+
+	k = placeitems_hash(item, cassocs, k);
+
+	/* Insert rooms */
+	for (i = 0; i < k; i++) {
+		for (y = rooms[i].y; y < rooms[i].y + rooms[i].h; y++) {
+			for (x = rooms[i].x; x < rooms[i].x + rooms[i].w; x++)
+				map[y][x].c = '.';
+		} 
+	}
+
+	/* Insert connections */
+	for (i = 0; i < k; i++) {
+		if (rooms[i].p)
+			connectrooms(&rooms[i], rooms[i].p);
+	}
+
+	/*
+		Insert items
+		The placement of items affects the initial placement of the player, because they could have gone back to this map, so they should appear at the elevator/portal/stair they used.
+	*/
+	ir = fnv1a(4, item->host, item->port, item->selector, "initial_room") % k;
+
+	for (i = 0; i < k; i++) {
+		snprintf(buffer, sizeof(buffer), "%d", i);
+		prng = fnv1a(4, item->host, item->port, item->selector, buffer);
+		for (j = 0, n = 0, m = rooms[i].h * rooms[i].w; j < m; j++) {
+			if ((m - j) * (xorshift(&prng) / (double)UINT32_MAX) < POSITIONS_LENGTH - n) {
+				positions[n].x = rooms[i].x + j % rooms[i].w;
+				positions[n].y = rooms[i].y + j / rooms[i].w;
+				n++;
+			}
+			if (n == POSITIONS_LENGTH)
+				break;
+		}
+		for (j = 0; j < dir->nitems; j++) {
+			if (cassocs[j] != i)
+				continue;
+
+			citem = &dir->items[j];
+			switch (citem->type) {
+			case '0':
+				x = positions[Bookshelf].x;
+				y = positions[Bookshelf].y;
+				if (map[y][x].nitems)
+					map[y][x].c = 'E';
+				else
+					map[y][x].c = '?';
+				break;
+			case '1':
+				if (strcmp(citem->host, item->host) || strcmp(citem->port, item->port)) {
+					x = positions[Portal].x;
+					y = positions[Portal].y;
+					if (map[y][x].nitems)
+						map[y][x].c = 'O';
+					else
+						map[y][x].c = '0';
+				} else {
+					x = positions[StaircaseDown].x;
+					y = positions[StaircaseDown].y;
+					if (map[y][x].nitems)
+						map[y][x].c = 'L';
+					else
+						map[y][x].c = '>';
+				}
+				break;
+			default:
+				continue;
+			}
+			map[y][x].nitems++;
+			map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
+			map[y][x].items[map[y][x].nitems-1] = citem;
+
+			if (new && j == dir->curline && citem->raw) {
+				px = x;
+				py = y;
+			}
+		}
+
+		if (i == ir && item->entry != item) {
+			y = positions[Back].y;
+			x = positions[Back].x;
+			if (strcmp(item->entry->host, item->host) || strcmp(item->entry->port, item->port))
+				map[y][x].c = '0';
+			else
+				map[y][x].c = '<';
+			map[y][x].nitems++;
+			map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
+			map[y][x].items[map[y][x].nitems-1] = item->entry;
+		}
+
+		if (i == ir && new && !dir->items[dir->curline].raw) {
+			px = positions[Back].x;
+			py = positions[Back].y;
+		}
+	}
+	free(cassocs);
+
+cleanup:
+	free(rooms);
+}
+
+void
+uisetup(void)
+{
+	tcgetattr(0, &tsave);
+	tsacc = tsave;
+	tsacc.c_lflag &= ~(ECHO|ICANON);
+	tsacc.c_cc[VMIN] = 1;
+	tsacc.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &tsacc);
+
+	if (termset != OK)
+		/* setupterm call exits on error */
+		termset = setupterm(NULL, 1, NULL);
+	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+}
+
+void
+uicleanup(void)
+{
+	tcsetattr(0, TCSANOW, &tsave);
+
+	if (termset != OK)
+		return;
+
+	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+}
+
+char *
+uiprompt(char *fmt, ...)
+{
+	va_list ap;
+	char *input = NULL;
+	size_t n;
+	ssize_t r;
+
+	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	va_start(ap, fmt);
+	vsnprintf(bufout, sizeof(bufout), fmt, ap);
+	va_end(ap);
+
+	n = mbsprint(bufout, columns);
+
+	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0));
+
+	tsacc.c_lflag |= (ECHO|ICANON);
+	tcsetattr(0, TCSANOW, &tsacc);
+	fflush(stdout);
+
+	n = 0;
+	r = getline(&input, &n, stdin);
+
+	tsacc.c_lflag &= ~(ECHO|ICANON);
+	tcsetattr(0, TCSANOW, &tsacc);
+	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+
+	if (r == -1) {
+		clearerr(stdin);
+		clear(&input);
+	} else if (input[r - 1] == '\n') {
+		input[--r] = '\0';
+	}
+
+	return input;
+}
+
+void
+displaybar(char *s) {
+	size_t n;
+
+	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(cursor_address, lines-2, 0, 0, 0, 0, 0, 0, 0, 0));
+	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	n = mbsprint(s, columns);
+	for (n = columns - n; n; n--)
+		putchar(' ');
+
+	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+}
+
+void
+vdisplayinfoline(char *fmt, va_list ap)
+{
+	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	vsnprintf(bufout, sizeof(bufout), fmt, ap);
+
+	mbsprint(bufout, columns);
+
+	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+}
+
+void
+uistatus(char *fmt, ...)
+{
+	va_list ap;
+	size_t n;
+
+	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	va_start(ap, fmt);
+	n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
+	va_end(ap);
+
+	if (n < sizeof(bufout)-1) {
+		snprintf(bufout+n, sizeof(bufout)-n,
+		         " [Press a key to continue \xe2\x98\x83]");
+	}
+
+	mbsprint(bufout, columns);
+
+	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	fflush(stdout);
+
+	getchar();
+}
+
+void
+displayinfoline(char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vdisplayinfoline(fmt, ap);
+	va_end(ap);
+}
+
+Item *
+showmenu(char *title, Item **item, size_t l)
+{
+	size_t i;
+
+	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	printf("%s\n", title);
+	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	for (i = 0; i < l; i++)
+		printf("%lu\t%s\n", i, item[i]->username);
+
+	if (!scanf("%lu", &i) || i >= l)
+		return NULL;
+	fflush(stdout);
+
+	return item[i];
+}
+
+Item *
+prompt(char *text, Item *item)
+{
+	displayinfoline(text, item->username);
+	getchar();
+	return item;
+}
+
+Item *
+interact(Item *item)
+{
+	Item *selection;
+
+	selection = NULL;
+
+	switch (map[py][px].c) {
+	case '?':
+	case '0':
+	case '>':
+	case '<':
+		selection = map[py][px].items[0];
+		break;
+	case 'E':
+		selection = showmenu("Bookshelf", map[py][px].items, map[py][px].nitems);
+		break;
+	case 'O':
+		selection = showmenu("Portal machine", map[py][px].items, map[py][px].nitems);
+		break;
+	case 'L':
+		selection = showmenu("Elevator", map[py][px].items, map[py][px].nitems);
+		break;
+	}
+
+	drawscreen();
+
+	if (selection) {
+		switch (map[py][px].c) {
+		case '?':
+		case 'E':
+			displayinfoline("A loading bar?! In a book?!");
+			break;
+		case 'O':
+		case '0':
+			displayinfoline("You are getting transported through time and space.");
+			break;
+		case 'L':
+			displayinfoline("You hear elevator music...");
+			break;
+		case '<':
+		case '>':
+			displayinfoline("Too many stairs...");
+			break;
+		}
+	}
+
+	return selection;
+}
+
+void
+describe(size_t x, size_t y, int verbose)
+{
+	switch (map[y][x].c) {
+	case 'E':
+		displayinfoline("A bookshelf.");
+		break;
+	case 'O':
+		displayinfoline("A portal machine.");
+		break;
+	case 'L':
+		displayinfoline("An elevator.");
+		break;
+	case '?':
+	case '>':
+	case '<':
+	case '0':
+		if (*map[y][x].items[0]->username) {
+			displayinfoline("'%s'.", map[y][x].items[0]->username);
+		} else {
+			itemuri(map[y][x].items[0], bufout2, sizeof(bufout2));
+			displayinfoline("'%s'.", bufout2);
+		}
+		break;
+	default:
+		if (verbose) {
+			switch (map[y][x].c) {
+			case '.':
+				displayinfoline("Floor.");
+				break;
+			case '#':
+				displayinfoline("Wall.");
+				break;
+			}
+		} else {
+			displayinfoline("");
+		}
+		break;
+	}
+}
+
+void
+move(ssize_t dx, ssize_t dy)
+{
+	size_t x, y;
+
+	/* allow wraparound of the world for the lulz, even if it's not happening */
+	y = (MAPHEIGHT + py + dy) % MAPHEIGHT;
+	x = (MAPWIDTH + px + dx) % MAPWIDTH;
+
+	if (map[y][x].c == '#')
+		return;
+
+	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+	putchar(map[py][px].c);
+
+	py = y;
+	px = x;
+	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+	putchar('@');
+	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+
+	describe(x, y, 0);
+	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+}
+
+void
+drawscreen(void)
+{
+	Dir *dir;
+
+	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	rendermap();
+
+	if (curentry->entry != curentry && (dir = curentry->entry->dat)) {
+		displaybar(dir->items[dir->curline].username);
+	} else {
+		itemuri(curentry, bufout, sizeof(bufout));
+		displaybar(bufout);
+	}
+
+	move(0, 0);
+
+}
+
+void
+uidisplay(Item *entry)
+{
+	if (!entry || entry->type != '1')
+		return;
+
+	generatemap(entry, curentry != entry);
+
+	curentry = entry;
+	drawscreen();
+}
+
+void
+lookmode(void)
+{
+	size_t x, y;
+
+	x = px;
+	y = py;
+
+	for (;;) {
+		switch (getchar()) {
+		case 0x1B:
+		case 'q':
+			putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0));
+			return;
+		case 'h':
+			x = (MAPWIDTH + x - 1) % MAPWIDTH;
+			break;
+		case 'j':
+			y = (y + 1) % MAPHEIGHT;
+			break;
+		case 'k':
+			y = (MAPHEIGHT + y - 1) % MAPHEIGHT;
+			break;
+		case 'l':
+			x = (x + 1) % MAPWIDTH;
+			break;
+		}
+		putp(tparm(cursor_address, y, x, 0, 0, 0, 0, 0, 0, 0));
+		describe(x, y, 1);
+	}
+}
+
+Item *
+uiselectitem(Item *entry)
+{
+	Dir *dir;
+	Item *e;
+	size_t i;
+
+	if (!entry || !(dir = entry->dat))
+		return NULL;
+
+	for (;;) {
+		switch (getchar()) {
+		case 'h':
+			move(-1, 0);
+			break;
+		case 'j':
+			move(0, 1);
+			break;
+		case 'k':
+			move(0, -1);
+			break;
+		case 'l':
+			move(1, 0);
+			break;
+		case 'L':
+			lookmode();
+			break;
+		case ' ':
+			/* Portals, stairs, bookshelfs */
+			if (e = interact(entry)) {
+				if (e->type == '1') {
+					for (i = 0; i < dir->nitems; i++) {
+						if (e == &dir->items[i]) {
+							dir->curline = i;
+							break;
+						}
+					}
+				}
+				return e;
+			}
+			break;
+		case 'q':
+			return NULL;
+		}
+	}
+}
+
+void
+uisigwinch(int signal)
+{
+	Dir *dir;
+
+	if (termset == OK)
+		del_curterm(cur_term);
+	termset = setupterm(NULL, 1, NULL);
+
+	if (!curentry || !(dir = curentry->dat))
+		return;
+
+	uidisplay(curentry);
+}