URI: 
       Update ui_rogue - sacc - sacc(omys), simple console gopher client
  HTML git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR LICENSE
       ---
   DIR commit 90a9d3c81f27588c62faa43f22d946740042a3ce
   DIR parent cc6a6881e06ab4b851c7778842374b953e51539c
  HTML Author: Julian Schweinsberg <pazz0@0xfa.de>
       Date:   Mon, 28 Oct 2024 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"
       
       Diffstat:
         M ui_rogue.c                          |    1225 +++++++++++++++++++++----------
         A ui_rogue_notes                      |      31 +++++++++++++++++++++++++++++++
         A ui_rogue_readme                     |      28 ++++++++++++++++++++++++++++
       
       3 files changed, 912 insertions(+), 372 deletions(-)
       ---
   DIR diff --git 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;
        }
   DIR diff --git a/ui_rogue_notes 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
   DIR diff --git a/ui_rogue_readme 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