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 90a9d3c81f27588c62faa43f22d946740042a3ce
Julian Schweinsberg <pazz0@0xfa.de>
2024-10-28 15:17:33 +0100

Update ui_rogue

ui_rogue: Overengineered Edition

This adds a readme and notes (mostly ideas) file for ui_rogue, too.

These changes were made half a year ago and I don't do real
documentation of my changes...
(There is some git branch, but the commit messages aren't helpful:
"HuH?!", "Well, better than nothing...", "Typical
pazz0-overengineering.", ...)

Changes:
- Better menus
- Bigger maps (160x50)
  - Moving scrolls the displayed map if needed
- Rooms and corridors use ASCII characters to look like hack
- Removes look mode for now
- Replaces xorshift PRNG algorithm with ranqd1 (this means that the
  layout of gopherholes change with this update)
- Moved setupterm call out of signal handler (IIRC [you remember:
  months] there were some crashes I had in combination with heap
  allocations, my thought was that setupterm is using heap allocations)
- Parameters for room generation are randomly selected (based on the
  hostname and port) from a list of "dungeontypes"

 ui_rogue.c      | 1225 ++++++++++++++++++++++++++++++++++++++-----------------
 ui_rogue_notes  |   31 ++
 ui_rogue_readme |   28 ++
 3 files changed, 912 insertions(+), 372 deletions(-)
diff --git a/ui_rogue.c b/ui_rogue.c
index cd8859c..0ddd2e7 100644
--- a/ui_rogue.c
+++ b/ui_rogue.c
@@ -1,3 +1,5 @@
+#include <errno.h>
+#include <signal.h>
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -6,6 +8,7 @@
 #include <term.h>
 #include <termios.h>
 #include <unistd.h>
+#include <sys/select.h>
 #include <sys/types.h>
 
 #include "common.h"
@@ -22,6 +25,47 @@
 #define ERR (-1)
 #endif
 
+#define maplines (lines - 2)
+
+enum {
+	Blocks = 1,
+	Standout = 2,
+	Important = 4
+};
+
+struct cell;
+struct tile {
+	char c;
+	char flags;
+	char *name;
+	char *description;
+	char *afterinteract;
+	Item *(*interact)(struct cell *);
+};
+
+struct cell {
+	struct tile *tile;
+	size_t nitems;
+	Item **items;
+};
+
+struct room {
+	size_t x, y;
+	size_t w, h;
+};
+
+struct rect {
+	struct rect *next, *next2;
+	struct rect *p;
+	size_t x1, y1;
+	size_t x2, y2;
+	size_t d;
+	union {
+		void *p;
+		int i;
+	} data;
+};
+
 static struct termios tsave;
 static struct termios tsacc;
 static Item *curentry;
@@ -29,8 +73,88 @@ static int termset = ERR;
 static char bufout[256];
 static char bufout2[256];
 
+size_t ox, oy;
+size_t px, py;
+
+#define MAPHEIGHT (50)
+#define MAPWIDTH (160)
+struct cell map[MAPHEIGHT][MAPWIDTH];
+
+enum {
+	DungeonScreen,
+	MenuScreen
+} screen;
+
+Item *interactitem(struct cell *);
+Item *interactmenu(struct cell *);
+
+struct tile tile_void = { ' ', Blocks, "Void", "The void. The thing which is everywhere where nothing is.", NULL, NULL };
+struct tile tile_floor = { '.', 0, "Floor", "An ordinary stone floor.", NULL, NULL };
+struct tile tile_corridor = { '#', 0, "Different Floor", "This floor looks different than the other one.", NULL, NULL };
+struct tile tile_verticalwall = { '|', Blocks, "Wall", "Wall.", NULL, NULL };
+struct tile tile_horizontalwall = { '-', Blocks, "Wall", "Wall.", NULL, NULL };
+struct tile tile_door = { '/', 0, "Door", "A door.", NULL, NULL };
+struct tile tile_bookshelf = { 'E', Important, "Bookshelf", "A bookshelf.", "A loading bar?! In a book?!", interactmenu };
+struct tile tile_book = { '?', Important, "%s", "A book: '%s'.", "A loading bar?! In a book?!", interactitem };
+struct tile tile_portal = { '0', Important, "%s", "A portal: '%s'.", "You are getting transported through time and space.", interactitem };
+struct tile tile_portalmachine = { 'O', Important, "Portal Machine", "A portal machine.", "You are getting transported through time and space.", interactmenu };
+struct tile tile_heapofstuff = { '%', Important, "Heap", "A heap of stuff.", "The thing you touches glows strangely...", interactmenu };
+struct tile tile_elevator = { 'L', Important, "Elevator", "An elevator.", "You hear elevator music...", interactmenu };
+struct tile tile_stairsdown = { '>', Important, "'%s'", "A staircase leading down: '%s'.", "Too many stairs...", interactitem };
+
+struct tile tile_stairsup = { '<', Standout | Important, "'%s'", "A staircase leading up: '%s'.", "Too many stairs...", interactitem };
+struct tile tile_backportal = { '0', Standout | Important, "'%s'", "A portal leading back to wherever you came from: '%s'.", "You are getting transported through time and space.", interactitem };
+
 void drawscreen(void);
 
+int
+mygetchar_(void)
+{
+	int r;
+	fd_set fdset;
+
+	FD_ZERO(&fdset);
+	FD_SET(0, &fdset);
+
+	if ((r = select(1, &fdset, NULL, NULL, NULL)) == -1) {
+		if (errno == EINTR)
+			return -1;
+		return -2;
+	}
+
+	return getchar();
+}
+
+volatile sig_atomic_t sigwinch;
+
+int
+mygetchar(void)
+{
+	int r;
+
+	while ((r = mygetchar_()) == -1) {
+		if (sigwinch) {
+			sigwinch = 0;
+
+			if (termset == OK)
+				del_curterm(cur_term);
+			termset = setupterm(NULL, 1, NULL);
+
+			drawscreen();
+		}
+	}
+
+	if (r == -2)
+		die("mygetchar: %s", strerror(errno));
+
+	return r;
+}
+
+/*
+	FNV-1a ( http://www.isthe.com/chongo/tech/comp/fnv/ )
+	FNV was published into the public domain ( https://creativecommons.org/publicdomain/zero/1.0/ )
+	by Landon Curt Noll: http://www.isthe.com/chongo/tech/comp/fnv/#public_domain
+*/
 uint32_t
 fnv1a(int n,...)
 {
@@ -41,7 +165,7 @@ fnv1a(int n,...)
 
 	h = 0x811c9dc5;
 
-	va_start(l, n); 
+	va_start(l, n);
 	for (i = 0; i < n; i++) {
 		for (s = va_arg(l, char*); *s; s++) {
 			h ^= *s;
@@ -53,42 +177,17 @@ fnv1a(int n,...)
 	return h;
 }
 
-uint32_t
-xorshift(uint32_t *s)
+/*
+	An LCG using the constants from "Numerical Recipes".
+*/
+uint16_t
+ranqd1(uint32_t *s)
 {
-	*s ^= *s << 13;
-	*s ^= *s >> 17;
-	*s ^= *s << 5;
-	return *s;
+	return (*s = 1664525 * (*s) + 1013904223) >> 16;
 }
 
-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)
+randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng, int (*filter)(struct rect *, struct rect *))
 {
 	struct rect *r, *result;
 	size_t n;
@@ -96,35 +195,54 @@ randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng)
 	n = 0;
 	result = NULL;
 	for (r = rs; r; r = r->next) {
+		if (r == x)
+			continue;
 		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;
+		if (!filter(x, r))
+			continue;
 		n++;
-		if (xorshift(prng) / (1. + UINT32_MAX) < 1. / n)
+		if (ranqd1(prng) / (1. + UINT16_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)
+min(size_t a, size_t b)
+{
+	if (a < b)
+		return a;
+	return b;
+}
+
+size_t
+max(size_t a, size_t b)
+{
+	if (a > b)
+		return a;
+	return b;
+}
+
+/*
+	Creates an uneven grid by splitting the map recursively.
+	Returns an array containing the cells (rects) of the grid.
+*/
+struct rect *
+generaterects(size_t heightmin, size_t widthmin, uint32_t prng)
 {
 	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;
+	struct rect *rects;
+	size_t w, h;
+	int vertical, spaceforvertical, spaceforhorizontal;
 
 	r = malloc(sizeof(*r));
-	r->x1 = r->y1 = ROOM_MARGIN_MIN;
+	memset(r, 0, sizeof(*r));
+	r->x1 = r->y1 = 0;
 	r->x2 = MAPWIDTH;
 	r->y2 = MAPHEIGHT;
 	r->d = 0;
@@ -134,7 +252,6 @@ generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
 	queuehead = r;
 
 	rects = NULL;
-	rl = 0;
 
 	while (queuehead) {
 		r = queuehead;
@@ -142,35 +259,36 @@ generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
 			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) {
+		spaceforvertical = r->y2 - r->y1 >= heightmin * 2;
+		spaceforhorizontal = r->x2 - r->x1 >= widthmin * 2;
+
+		if (spaceforhorizontal && spaceforvertical) {
+			vertical = ranqd1(&prng) & 1;
+		} else if (spaceforhorizontal) {
 			vertical = 0;
-		} else if (r->y2 - r->y1 >= CELL_HEIGHT_MIN * 2) {
+		} else if (spaceforvertical) {
 			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);
+			h = heightmin + ranqd1(&prng) % (1 + r->y2 - r->y1 - heightmin * 2);
 		} else {
-			w = CELL_WIDTH_MIN + xorshift(&prng) % (1 + r->x2 - r->x1 - CELL_WIDTH_MIN * 2);
+			w = widthmin + ranqd1(&prng) % (1 + r->x2 - r->x1 - widthmin * 2);
 			h = r->y2 - r->y1;
 		}
 
 		t = malloc(sizeof(*t));
+		memset(t, 0, 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;
@@ -181,6 +299,7 @@ generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
 		}
 
 		t = malloc(sizeof(*t));
+		memset(t, 0, sizeof(*t));
 		if (vertical) {
 			t->x1 = r->x1;
 			t->y1 = r->y1 + h;
@@ -191,8 +310,6 @@ generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
 		t->x2 = r->x2;
 		t->y2 = r->y2;
 		t->d = r->d + 1;
-		t->next = NULL;
-		t->room = NULL;
 
 		queuetail->next = t;
 		queuetail = t;
@@ -200,75 +317,107 @@ generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
 		free(r);
 	}
 
-	if (l > rl)
-		l = rl;
+	return rects;
+}
 
-	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;
-	}
+void
+connectpoints_horizontal(size_t y,
+                       size_t ax, int ea, struct tile *at,
+                       size_t bx, int eb, struct tile *bt,
+                       struct tile *t)
+{
+	size_t i, s, e;
+	ssize_t ii;
 
-	for (r = rects; r;) {
-		t = r->next;
-		free(r);
-		r = t;
-	}
+	if (ax < bx)
+		ii = 1;
+	else if (ax > bx)
+		ii = -1;
+	else
+		ii = 0;
 
-	return l;
+	s = ax;
+	if (ea)
+		s += ii;
+	e = bx + ii;
+	if (eb)
+		e -= ii;
+
+	for (i = s; i != e; i += ii)
+		map[y][i].tile = t;
+
+	if (e - ii == s) {
+		if (at != t)
+			map[y][s].tile = at;
+		if (bt != t)
+			map[y][s].tile = bt;
+	} else {
+		map[y][s].tile = at;
+		map[y][e - ii].tile = bt;
+	}
 }
 
-size_t
-distance(size_t x1, size_t y1, size_t x2, size_t y2)
+void
+connectpoints_vertical(size_t x,
+                       size_t ay, int ea, struct tile *at,
+                       size_t by, int eb, struct tile *bt,
+                       struct tile *t)
 {
-	size_t d;
+	size_t i, s, e;
+	ssize_t ii;
 
-	if (y1 < y2)
-		d = y2 - y1;
-	else
-		d = y1 - y2;
-	if (x1 < x2)
-		d += x2 - x1;
+	if (ay < by)
+		ii = 1;
+	else if (ay > by)
+		ii = -1;
 	else
-		d += x1 - x2;
+		ii = 0;
 
-	return d;
+	s = ay;
+	if (ea)
+		s += ii;
+	e = by + ii;
+	if (eb)
+		e -= ii;
+
+	for (i = s; i != e; i += ii)
+		map[i][x].tile = t;
+
+	if (e - ii == s) {
+		if (at != t)
+			map[s][x].tile = at;
+		if (bt != t)
+			map[s][x].tile = bt;
+	} else {
+		map[s][x].tile = at;
+		map[e - ii][x].tile = bt;
+	}
+}
+
+void
+connectpoints(size_t ax, size_t ay, int ea, struct tile *at,
+              size_t bx, size_t by, int eb, struct tile *bt,
+              int vertical, struct tile *ct)
+{
+	if (!vertical) {
+		connectpoints_horizontal(ay,
+					 ax, ea, at,
+					 bx, 0, ct,
+					 ct);
+		connectpoints_vertical(bx,
+				       ay, 0, ct,
+				       by, eb, bt,
+				       ct);
+	} else {
+		connectpoints_vertical(ax,
+				       ay, ea, at,
+				       by, 0, ct,
+				       ct);
+		connectpoints_horizontal(by,
+					 ax, 0, ct,
+					 bx, eb, bt,
+					 ct);
+	}
 }
 
 void
@@ -300,50 +449,246 @@ nearestpoints(struct room *a, struct room *b, size_t *ax, size_t *ay, size_t *bx
 }
 
 void
-connectrooms(struct room *a, struct room *b)
+connectadjacentrooms(struct rect *a, struct room *ar, struct rect *b, struct room *br)
 {
-	size_t i, j;
-	ssize_t ii;
-	size_t x1, y1;
-	size_t x2, y2;
+	size_t irx1, iry1, irx2, iry2;
+	size_t rx1, ry1, rx2, ry2;
+	size_t cx, cy;
+	struct rect *r1, *r2;
+	struct room *room1, *room2;
+	int vertical;
 
-	nearestpoints(a, b, &x1, &y1, &x2, &y2);
+	if (a->x2 == b->x1) {
+		r1 = a;
+		room1 = ar;
+		r2 = b;
+		room2 = br;
+	} else if (b->x2 == a->x1) {
+		r1 = b;
+		room1 = br;
+		r2 = a;
+		room2 = ar;
+	} else if (a->y2 == b->y1) {
+		r1 = a;
+		room1 = ar;
+		r2 = b;
+		room2 = br;
+	} else if (b->y2 == a->y1) {
+		r1 = b;
+		room1 = br;
+		room2 = ar;
+		r2 = a;
+	} else {
+		return;
+	}
 
-	if (y1 > y2) {
-		ii = -1;
-	} else if (y2 > y1) {
-		ii = 1;
+	if (r1->y2 == r2->y1) {
+		irx1 = max(r1->x1, r2->x1);
+		irx2 = min(r1->x2, r2->x2);
+		iry1 = r1->y2;
+		iry2 = r1->y2 + 1;
 	} else {
-		ii = 0;
+		iry1 = max(r1->y1, r2->y1);
+		iry2 = min(r1->y2, r2->y2);
+		irx1 = r1->x2;
+		irx2 = r1->x2 + 1;
+	}
+
+	nearestpoints(room1, room2, &rx1, &ry1, &rx2, &ry2);
+
+	if (r1->y2 == r2->y1) {
+		/* both points are in the intersection */
+		if (rx1 >= irx1 && rx1 < irx2 &&
+		    rx2 >= irx1 && rx2 < irx2) {
+			vertical = 1;
+			cx = (rx2 + rx1) / 2;
+			cy = (ry2 + ry1) / 2;
+		} else
+		/* none is in the intersection */
+		if (!(rx1 >= irx1 && rx1 < irx2) &&
+		    !(rx2 >= irx1 && rx2 < irx2)) {
+			vertical = 0;
+			cx = irx1;
+			cy = r1->y2;
+		} else if (rx1 >= irx1 && rx1 < irx2) {
+			vertical = 1;
+			cx = (rx2 + rx1) / 2;
+			cy = r1->y2;
+		} else if (rx2 >= irx1 && rx2 < irx2) {
+			vertical = 1;
+			cx = rx2;
+			cy = r1->y2 - 1;
+		}
+	} else {
+		/* both points are in the intersection */
+		if (ry1 >= iry1 && ry1 < iry2 &&
+		    ry2 >= iry1 && ry2 < iry2) {
+			vertical = 0;
+			cx = (rx2 + rx1) / 2;
+			cy = (ry2 + ry1) / 2;
+		} else
+		/* none is in the intersection */
+		if (!(ry1 >= iry1 && ry1 < iry2) &&
+		    !(ry2 >= iry1 && ry2 < iry2)) {
+			vertical = 1;
+			cx = r1->x2;
+			cy = iry1;
+		} else if (ry1 >= iry1 && ry1 < iry2) {
+			vertical = 0;
+			cx = r1->x2;
+			cy = (ry2 + ry1) / 2;
+		} else if (ry2 >= iry1 && ry2 < iry2) {
+			vertical = 0;
+			cx = r1->x2 - 1;
+			cy = ry2;
+		}
 	}
 
+	if (rx1 == rx2) {
+		connectpoints_vertical(rx1,
+		                       ry1, 1, &tile_door,
+		                       ry2, 1, &tile_door,
+		                       &tile_corridor);
+	} else if (ry1 == ry2) {
+		connectpoints_horizontal(ry1,
+		                         rx1, 1, &tile_door,
+		                         rx2, 1, &tile_door,
+		                         &tile_corridor);
+	} else {
+		connectpoints(rx1, ry1, 1, &tile_door,
+			      cx, cy, 0, &tile_corridor,
+			      vertical, &tile_corridor);
+		connectpoints(cx, cy, 1, &tile_corridor,
+			      rx2, ry2, 1, &tile_door,
+			      !vertical, &tile_corridor);
+	}
+}
+
+int
+rectisfull(struct rect *x, struct rect *r)
+{
+	return !!r->data.i;
+}
+
+int
+rectisempty(struct rect *x, struct rect *r)
+{
+	return !r->data.i;
+}
+
+int
+rectisnotp(struct rect *x, struct rect *r)
+{
+	return r->data.p && x->p != r && r->p != x;
+}
+
+int
+rectisrandom(struct rect *x, struct rect *r)
+{
+	return 1;
+}
+
 /*
-printf("%lu\t%lu\t%d\n", y1, y2, ii);
+	Basically https://www.roguebasin.com/index.php/Diffusion-limited_aggregation
+	Returns the list of carved rooms.
 */
-	for (i = y1; i != y2; i += ii)
-		map[i][x1].c = '.';
+struct rect *
+dla(struct rect *rects, size_t l, uint32_t prng) {
+	size_t rl, i, n;
+	struct rect *r, *t, *walk, *p;
 
-	if (x1 > x2) {
-		ii = -1;
-	} else if (x2 > x1) {
-		ii = 1;
-	} else {
-		ii = 0;
+	for (r = rects, rl = 0; r; r = r->next)
+		rl++;
+
+	if (l > rl)
+		l = rl;
+
+	/* get the rect which contains the map center */
+	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;
 	}
 
-	for (i = x1; i != x2; i += ii)
-		map[y2][i].c = '.';
+	p = NULL;
+	walk = NULL;
+	i = 0;
+	for (;;) {
+		r->p = p;
+		r->data.i = 1;
+		r->next2 = walk;
+		walk = r;
+
+		if (i >= l - 1)
+			break;
+
+		t = NULL;
+		for (r = rects, n = 0; r; r = r->next) {
+			if (r->data.i)
+				continue;
+			n++;
+			if (ranqd1(&prng) / (1. + UINT16_MAX) < 1. / n)
+				t = r;
+		}
+
+		/* there is no free rect left */
+		if (!t)
+			break;
+
+		/* do a random walk starting from t until the walk collides with a carved room (r) */
+		while ((r = randomneighbor(t, rects, &prng, rectisrandom)) && !r->data.i)
+			t = r;
+
+		p = r;
+		r = t;
+
+		i++;
+	}
+
+	return walk;
+}
+
+void
+rendermapchar(size_t i, size_t j) {
+	if (map[i][j].tile->flags & Standout)
+		putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	putchar(map[i][j].tile->c);
+	if (map[i][j].tile->flags & Standout)
+		putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+void
+rendermapline(size_t i)
+{
+	size_t j;
+
+	for (j = ox; j < min(MAPWIDTH, ox + columns); j++)
+		rendermapchar(i, j);
 }
 
 void
 rendermap(void)
 {
-	size_t i, j;
+	size_t i;
 
-	for (i = 0; i < MAPHEIGHT; i++) {
-		for (j = 0; j < MAPWIDTH; j++)
-			putchar(map[i][j].c);
-		putchar('\n');
+	if (px < columns / 2 || MAPWIDTH <= columns)
+		ox = 0;
+	else if (px >= MAPWIDTH - columns / 2 - 1)
+		ox = MAPWIDTH - columns;
+	else
+		ox = px - columns / 2;
+
+	if (py < maplines / 2 || MAPHEIGHT <= maplines)
+		oy = 0;
+	else if (py >= MAPHEIGHT - maplines / 2 - 1)
+		oy = MAPHEIGHT - maplines;
+	else
+		oy = py - maplines / 2;
+
+	for (i = oy; i < min(MAPHEIGHT, oy + (lines - 2)); i++) {
+		if (i != oy)
+			putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+		rendermapline(i);
 	}
 }
 
@@ -366,92 +711,171 @@ placeitems_hash(Item *item, size_t *assocs, size_t k)
 	return k;
 }
 
-#define POSITIONS_LENGTH 4
+#define POSITIONS_LENGTH 5
 enum {
 	Portal,
 	StaircaseDown,
 	Bookshelf,
+	OtherStuff,
 	Back
 };
 
-size_t px, py;
+enum {
+	FillEntireCell = 1
+};
+
+#define length(a) (sizeof(a) / sizeof(a[0]))
+static struct dungeontype {
+	char *name;
+	char flags;
+	size_t heightmin;
+	size_t heightmax;
+	size_t widthmin;
+	size_t widthmax;
+	size_t margin;
+	size_t wiggle;
+} dungeontypes[] = {
+	{ "rogueish", 0, 2, 5, 3, 7, 2, 0 },
+	{ "rogueish-wide", 0, 2, 5, 3, 7, 3, 1 },
+	{ "compact", FillEntireCell, 2, 2, 3, 3, 1, 0 },
+};
 
 void
-generatemap(Item *item, int new)
+generatemap(Item *item, Item *pitem)
 {
 	Dir *dir;
 	Item *citem;
-	size_t i, j, k, l, ir;
-	size_t x, y;
-	ssize_t n, m;
+	size_t l, i, j, k, ir, n, m, x, y;
+	struct rect *rects, *walk, *tr, *cr;
+	struct room *rooms, *room;
 	size_t *cassocs;
-	struct room *rooms, *r;
+	int changedlevel, gonedown;
+	char buffer[10];
+	uint32_t prng;
 	struct {
-		unsigned char x, y;
+		size_t x, y;
 	} positions[POSITIONS_LENGTH];
-	uint32_t prng;
-	char buffer[3];
+	size_t cellwidth, cellheight;
+	struct dungeontype *type;
+
+	type = &dungeontypes[fnv1a(3, item->host, item->port, "dungeontype") % length(dungeontypes)];
+
+	cellheight = type->heightmin + 2 * type->margin + type->wiggle;
+	cellwidth = type->widthmin + 2 * type->margin + type->wiggle;
+
+	rects = generaterects(cellheight, cellwidth, fnv1a(4, item->host, item->port, item->selector, "gridseed"));
+
+	dir = item->dat;
+	for (j = l = 0; j < dir->nitems; j++) {
+		if (dir->items[j].type != 0 &&
+		    dir->items[j].type != 'i' &&
+		    dir->items[j].type != '3')
+			l++;
+	}
+
+	k = 1 + l / 10;
+	walk = dla(rects, k, fnv1a(4, item->host, item->port, item->selector, "randomwalkseed"));
+	for (cr = walk, k = 0; cr; cr = cr->next2, k++);
+
+	for (cr = rects; cr; cr = cr->next)
+		cr->data.p = NULL;
+
+	rooms = calloc(k, sizeof(*rooms));
+	for (cr = walk, i = 0; cr; cr = cr->next2, i++)
+		cr->data.p = &rooms[i];
+
+	prng = fnv1a(4, item->host, item->port, item->selector, "roomsseed");
+	for (cr = walk; cr; cr = cr->next2) {
+		room = cr->data.p;
+
+		if (type->flags & FillEntireCell) {
+			room->w = cr->x2 - cr->x1 - 2 * type->margin;
+			room->x = cr->x1 + type->margin;
+			room->h = cr->y2 - cr->y1 - 2 * type->margin;
+			room->y = cr->y1 + type->margin;
+		} else {
+			room->w = type->widthmin + ranqd1(&prng) % (1 + min(cr->x2 - cr->x1 - type->widthmin - 2 * type->margin, type->widthmax - type->widthmin));
+			room->x = cr->x1 + type->margin + ranqd1(&prng) % (1 + cr->x2 - cr->x1 - room->w - 2 * type->margin);
+			room->h = type->heightmin + ranqd1(&prng) % (1 + min(cr->y2 - cr->y1 - type->heightmin - 2 * type->margin, type->heightmax - type->heightmin));
+			room->y = cr->y1 + type->margin + ranqd1(&prng) % (1 + cr->y2 - cr->y1 - room->h - 2 * type->margin);
+		}
+	}
 
 	for (i = 0; i < MAPHEIGHT; i++) {
 		for (j = 0; j < MAPWIDTH; j++) {
-			map[i][j].c = '#';
+			map[i][j].tile = &tile_void;
 			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++;
+	for (cr = walk; cr; cr = cr->next2) {
+		room = cr->data.p;
+
+		for (x = room->x - 1; x < room->x + room->w + 1; x++)
+			map[room->y-1][x].tile = &tile_horizontalwall;
+		for (y = room->y; y < room->y + room->h; y++) {
+			map[y][room->x - 1].tile = &tile_verticalwall;
+			for (x = room->x; x < room->x + room->w; x++)
+				map[y][x].tile = &tile_floor;
+			map[y][room->x + room->w].tile = &tile_verticalwall;
+		}
+		for (x = room->x - 1; x < room->x + room->w + 1; x++)
+			map[room->y + room->h][x].tile = &tile_horizontalwall;
 	}
 
-	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);
+	for (cr = walk; cr; cr = cr->next2) {
+		if (cr->p)
+			connectadjacentrooms(cr, cr->data.p,
+			                     cr->p, cr->p->data.p);
+
+		/* Add some loop possibility */
+		if (tr = randomneighbor(cr, rects, &prng, rectisnotp))
+			connectadjacentrooms(cr, cr->data.p,
+			                     tr, tr->data.p);
+	}
 
 	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);
-	}
+	changedlevel = item != pitem;
+	gonedown = pitem == item->entry;
 
 	/*
 		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.
+                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;
+
+	/*
+                The initial room is everytime the first one. Reason: The count
+                of rooms is based on how many entries are in the gophermap and
+                how many rooms can fit on the map. There will be at minimum 1.
+                So when more entries get added there will be more rooms but the
+                first one stays at the same position. I think about the
+                retrying and clownflare things on bitreich.org, the selector
+                doesn't change...
+	*/
+	ir = 0;
 
 	for (i = 0; i < k; i++) {
-		snprintf(buffer, sizeof(buffer), "%d", i);
+		/* select random positions for different item types inside the current room */
+		snprintf(buffer, sizeof(buffer), "%lu", 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) {
+		for (j = 0, m = rooms[i].h * rooms[i].w; j < m; j++) {
+			n = j;
+			if (j >= POSITIONS_LENGTH)
+				n *= ranqd1(&prng) / (double)UINT16_MAX;
+
+			if (n < POSITIONS_LENGTH) {
 				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;
@@ -462,35 +886,44 @@ generatemap(Item *item, int new)
 				x = positions[Bookshelf].x;
 				y = positions[Bookshelf].y;
 				if (map[y][x].nitems)
-					map[y][x].c = 'E';
+					map[y][x].tile = &tile_bookshelf;
 				else
-					map[y][x].c = '?';
+					map[y][x].tile = &tile_book;
 				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';
+						map[y][x].tile = &tile_portalmachine;
 					else
-						map[y][x].c = '0';
+						map[y][x].tile = &tile_portal;
 				} else {
 					x = positions[StaircaseDown].x;
 					y = positions[StaircaseDown].y;
 					if (map[y][x].nitems)
-						map[y][x].c = 'L';
+						map[y][x].tile = &tile_elevator;
 					else
-						map[y][x].c = '>';
+						map[y][x].tile = &tile_stairsdown;
 				}
 				break;
-			default:
+			case 0:
+			case 'i':
+			case '3':
 				continue;
+				break;
+			default:
+				x = positions[OtherStuff].x;
+				y = positions[OtherStuff].y;
+				map[y][x].tile = &tile_heapofstuff;
+				break;
 			}
+
 			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) {
+			if (changedlevel && citem == pitem) {
 				px = x;
 				py = y;
 			}
@@ -500,23 +933,28 @@ generatemap(Item *item, int new)
 			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';
+				map[y][x].tile = &tile_backportal;
 			else
-				map[y][x].c = '<';
+				map[y][x].tile = &tile_stairsup;
 			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) {
+		if (changedlevel && i == ir && (gonedown || !pitem)) {
 			px = positions[Back].x;
 			py = positions[Back].y;
 		}
 	}
-	free(cassocs);
 
-cleanup:
+	free(cassocs);
 	free(rooms);
+
+	for (cr = rects; cr;) {
+		tr = cr;
+		cr = cr->next;
+		free(tr);
+	}
 }
 
 void
@@ -544,6 +982,7 @@ uicleanup(void)
 	if (termset != OK)
 		return;
 
+	putp(tparm(change_scroll_region, 0, lines-1, 0, 0, 0, 0, 0, 0, 0));
 	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
 	fflush(stdout);
 }
@@ -655,7 +1094,7 @@ uistatus(char *fmt, ...)
 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
 	fflush(stdout);
 
-	getchar();
+	mygetchar();
 }
 
 void
@@ -668,225 +1107,287 @@ displayinfoline(char *fmt, ...)
 	va_end(ap);
 }
 
-Item *
-showmenu(char *title, Item **item, size_t l)
+char *menutitle;
+Item **menuitems;
+size_t menunitems;
+volatile size_t menuoffset;
+size_t menuselected;
+
+void
+menudraw(void)
 {
-	size_t i;
+	size_t i, n;
 
+	putp(tparm(change_scroll_region, 1, lines-1, 0, 0, 0, 0, 0, 0, 0));
 	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);
+	puts(menutitle);
 	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;
+	if (menuselected - menuoffset >= lines - 1)
+		menuoffset = menuselected - (lines - 1) + 1;
+
+	for (i = menuoffset, n = 0; i < menunitems && n < lines - 1; i++, n++) {
+		if (i != menuoffset)
+			putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+		if (i == menuselected)
+			putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+		mbsprint(menuitems[i]->username, columns);
+		if (i == menuselected)
+			putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+		putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+	}
 	fflush(stdout);
-
-	return item[i];
 }
 
 Item *
-prompt(char *text, Item *item)
-{
-	displayinfoline(text, item->username);
-	getchar();
-	return item;
-}
-
-Item *
-interact(Item *item)
+showmenu(char *title, Item **item, size_t l)
 {
 	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;
-	}
-
+	menutitle = title;
+	menuitems = item;
+	menunitems = l;
+	menuselected = 0;
+	menuoffset = 0;
+	screen = MenuScreen;
 	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.");
+	selection = NULL;
+	for (;;) {
+		switch (mygetchar()) {
+		case 'j':
+			if (menuselected + 1 < menunitems) {
+				putp(tparm(cursor_address, 1 + menuselected - menuoffset, 0, 0, 0, 0, 0, 0, 0, 0));
+				mbsprint(menuitems[menuselected]->username, columns);
+				menuselected++;
+				putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				if (menuselected - menuoffset >= lines - 1) {
+					menuoffset++;
+					putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				} else {
+					putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				}
+				putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				mbsprint(menuitems[menuselected]->username, columns);
+				putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			}
 			break;
-		case 'L':
-			displayinfoline("You hear elevator music...");
+		case 'k':
+			if (menuselected > 0) {
+				putp(tparm(cursor_address, 1 + menuselected - menuoffset, 0, 0, 0, 0, 0, 0, 0, 0));
+				mbsprint(menuitems[menuselected]->username, columns);
+				menuselected--;
+				putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				if (menuselected < menuoffset) {
+					menuoffset = menuselected;
+					putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				} else {
+					putp(tparm(cursor_up, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				}
+				putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				mbsprint(menuitems[menuselected]->username, columns);
+				putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			}
 			break;
-		case '<':
-		case '>':
-			displayinfoline("Too many stairs...");
+		case ' ':
+			selection = menuitems[menuselected];
+		case 0x1b:
+			goto endloop;
 			break;
 		}
+		fflush(stdout);
 	}
 
+endloop:
+	screen = DungeonScreen;
+	drawscreen();
+
 	return selection;
 }
 
+Item *
+interactitem(struct cell *c)
+{
+	displayinfoline(map[py][px].tile->afterinteract);
+
+	return map[py][px].items[0];
+}
+
+Item *
+interactmenu(struct cell *c)
+{
+	Item *selection;
+
+	if (selection = showmenu(map[py][px].tile->name, map[py][px].items, map[py][px].nitems))
+		displayinfoline(map[py][px].tile->afterinteract);
+
+	return selection;
+}
+
+Item *
+interact(Item *item)
+{
+	if (map[py][px].tile->interact)
+		return map[py][px].tile->interact(&map[py][px]);
+
+	return NULL;
+}
+
 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':
+	char *name;
+
+	if (map[y][x].nitems) {
 		if (*map[y][x].items[0]->username) {
-			displayinfoline("'%s'.", map[y][x].items[0]->username);
+			name = map[y][x].items[0]->username;
 		} else {
 			itemuri(map[y][x].items[0], bufout2, sizeof(bufout2));
-			displayinfoline("'%s'.", bufout2);
+			name = bufout2;
 		}
-		break;
-	default:
-		if (verbose) {
-			switch (map[y][x].c) {
-			case '.':
-				displayinfoline("Floor.");
-				break;
-			case '#':
-				displayinfoline("Wall.");
-				break;
-			}
-		} else {
-			displayinfoline("");
-		}
-		break;
+	} else {
+		name = NULL;
 	}
+	if (map[y][x].tile->flags & Important || verbose)
+		displayinfoline(map[y][x].tile->description, name);
+	else
+		displayinfoline("");
+}
+
+void
+dungeondraw(void)
+{
+	putp(tparm(change_scroll_region, 0, lines-3, 0, 0, 0, 0, 0, 0, 0));
+	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+	rendermap();
+
+	putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
+	putchar('@');
+	putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
+
+	if (curentry->entry != curentry) {
+		displaybar(curentry->username);
+	} else {
+		itemuri(curentry, bufout, sizeof(bufout));
+		displaybar(bufout);
+	}
+
+	describe(px, py, 0);
 }
 
 void
 move(ssize_t dx, ssize_t dy)
 {
+	ssize_t i;
 	size_t x, y;
+	size_t noy, nox;
+
+	if ((ssize_t)py + dy >= MAPHEIGHT || (ssize_t)py + dy < 0)
+		return;
+	if ((ssize_t)px + dx >= MAPWIDTH || (ssize_t)px + dx < 0)
+		return;
 
-	/* allow wraparound of the world for the lulz, even if it's not happening */
-	y = (MAPHEIGHT + py + dy) % MAPHEIGHT;
-	x = (MAPWIDTH + px + dx) % MAPWIDTH;
+	x = px + dx;
+	y = py + dy;
 
-	if (map[y][x].c == '#')
+	if (map[y][x].tile->flags & Blocks)
 		return;
 
-	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
-	putchar(map[py][px].c);
+	if (dx) {
+		if (x < columns / 2 || MAPWIDTH <= columns)
+			nox = 0;
+		else if (x >= MAPWIDTH - columns / 2 - 1)
+			nox = MAPWIDTH - columns;
+		else
+			nox = x - columns / 2;
+
+		if (ox != nox) {
+			putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			rendermap();
+		} else {
+			putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
+			rendermapchar(py, px);
+		}
+	} else if (dy) {
+		putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
+		rendermapchar(py, px);
+
+		if (y < maplines / 2 || MAPHEIGHT <= maplines) {
+			noy = 0;
+		} else if (y >= MAPHEIGHT - maplines / 2 - 1) {
+			noy = MAPHEIGHT - maplines;
+		} else {
+			noy = y - maplines / 2;
+		}
+
+		if (noy < oy) {
+			putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			for (i = (ssize_t)oy - 1; i >= (ssize_t)noy; i--) {
+				putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				rendermapline(i);
+				putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			}
+		} else if (noy > oy) {
+			putp(tparm(cursor_address, lines-3, 0, 0, 0, 0, 0, 0, 0, 0));
+			for (i = oy + 1; i <= noy; i++) {
+				putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+				rendermapline(i + maplines - 1);
+				putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+			}
+		}
+		oy = noy;
+	}
 
 	py = y;
 	px = x;
-	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+	putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
 	putchar('@');
-	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+	putp(tparm(cursor_address, py - oy, px - ox, 0, 0, 0, 0, 0, 0, 0));
 
-	describe(x, y, 0);
-	putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+	describe(px, py, 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);
+	switch (screen) {
+	case DungeonScreen:
+		dungeondraw();
+		break;
+	case MenuScreen:
+		menudraw();
+		break;
 	}
-
-	move(0, 0);
-
+	fflush(stdout);
 }
 
 void
 uidisplay(Item *entry)
 {
-	if (!entry || entry->type != '1')
+	if (!entry || !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
 		return;
 
-	generatemap(entry, curentry != entry);
+	if (entry != curentry) {
+		generatemap(entry, curentry);
+		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))
+	if (!entry || !entry->dat)
 		return NULL;
 
 	for (;;) {
-		switch (getchar()) {
+		switch (mygetchar()) {
 		case 'h':
 			move(-1, 0);
 			break;
@@ -899,40 +1400,20 @@ uiselectitem(Item *entry)
 		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;
-						}
-					}
-				}
+			if (e = interact(entry))
 				return e;
-			}
 			break;
 		case 'q':
+		case 0x1b:
 			return NULL;
 		}
+		fflush(stdout);
 	}
 }
 
 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);
+	sigwinch = 1;
 }
diff --git a/ui_rogue_notes b/ui_rogue_notes
new file mode 100644
index 0000000..77ff90f
--- /dev/null
+++ b/ui_rogue_notes
@@ -0,0 +1,31 @@
+# Notes
+- in need for a big gopher directory? use gopher://bitreich.org/1/memecache/
+
+# Bugs
+- not all corridors have 2 doors
+
+# Future Features?
+- k-medoids clustering using selector strings in reasonable time...
+  - items with similar selectors should get inserted into the same rooms
+  - one could use some mapping (sammon mapping?) to place the rooms based on the cluster medoids
+    - similar clusters are near together
+- examine mode
+  - allow the cursor to move freely and describe the tiles with describe() in verbose mode
+- running using H, J, K, L
+  - go into the direction until the floor tile changes?
+  - with wall sliding to go through corridors
+    - needs to stop at junctions
+- spells
+  - teleport to a random room on the current level
+  - teleport to a random already visited level
+  - inscribe a scroll to teleport later back to the current level
+- visibility thingies to declutter things
+  - maybe like hack: keep things visible after discovery
+    - and what's with persisting this information?
+  - only show the content of the current room
+- better dungeon generation
+  - analyse things to create clever corridors
+- different dungeon themes
+  - so basically an extended dungeontype thing, with different dungeon generation algorithms
+  - maybe some special layouts for root directories (empty selector)
+    - i look at you, ultima castle generator! https://slash.itch.io/ultima-castle-generator
diff --git a/ui_rogue_readme b/ui_rogue_readme
new file mode 100644
index 0000000..b091b2a
--- /dev/null
+++ b/ui_rogue_readme
@@ -0,0 +1,28 @@
+# Description
+This UI module (?) for sacc displays directories like rogue/hack dungeon levels.
+
+The code is ugly. It's obvious that I (pazz0) don't know what I do.
+
+# Compiling
+Copy ui_rogue.c in the sacc source directory.
+Go in the sacc source directory and execute blindly the following commands:
+```
+make clean && make UI=rogue
+```
+sacc will now have the much more confusing ui_rogue interface.
+
+# Key mapping
+h, j, k, l: left, down, up, right
+space:      interact (in dungeon), select (in menu)
+ESC or q:   exit (in dungeon), close menu
+
+# Map explanation
+E or ?:     bookshelf/book (gopher type '0')
+L or >:     elevator/staircase down/staircase up (gopher type '1' on the current server)
+O or 0:     portal machine/portal (gopher type '1' on another server)
+%:          heap of stuff (other non-handled gopher types, apart from 'i' and '3')
+standout (inverse) 0 or <: a way to go back to the previous gopher directory
+
+. or #:     floor
+/:          doors (behaves like floor)
+| or -:     wall