/* NetHack 5.0	mkmaze.c	$NHDT-Date: 1745114235 2025/04/19 17:57:15 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.179 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Pasi Kallinen, 2018. */
/* NetHack may be freely redistributed.  See license for details. */

#include "hack.h"
#include "sp_lev.h"

#ifndef SFCTOOL
staticfn int iswall(coordxy, coordxy);
staticfn int iswall_or_stone(coordxy, coordxy);
staticfn boolean is_solid(coordxy, coordxy);
staticfn int extend_spine(int[3][3], int, int, int);
staticfn void wall_cleanup(coordxy, coordxy, coordxy, coordxy);
staticfn boolean okay(coordxy, coordxy, coordxy);
staticfn void maze0xy(coord *);
staticfn boolean put_lregion_here(coordxy, coordxy, coordxy, coordxy, coordxy,
                                coordxy, xint16, boolean, d_level *);
staticfn void baalz_fixup(void);
staticfn void setup_waterlevel(void);
staticfn void unsetup_waterlevel(void);
staticfn void check_ransacked(const char *);
staticfn void migr_booty_item(int, const char *);
staticfn void migrate_orc(struct monst *, unsigned long);
staticfn void shiny_orc_stuff(struct monst *);
staticfn void stolen_booty(void);
staticfn boolean maze_inbounds(coordxy, coordxy);
staticfn void maze_remove_deadends(xint16);
staticfn void populate_maze(void);

/* adjust a coordinate one step in the specified direction */
#define mz_move(X, Y, dir) \
    do {                                                         \
        switch (dir) {                                           \
        case 0:  --(Y);  break;                                  \
        case 1:  (X)++;  break;                                  \
        case 2:  (Y)++;  break;                                  \
        case 3:  --(X);  break;                                  \
        default: panic("mz_move: bad direction %d", dir);        \
        }                                                        \
    } while (0)

/* used to determine if wall spines can join this location */
staticfn int
iswall(coordxy x, coordxy y)
{
    int type;

    if (!isok(x, y))
        return 0;
    type = levl[x][y].typ;
    return (IS_WALL(type) || IS_DOOR(type)
            || type == LAVAWALL || type == WATER
            || type == SDOOR || type == IRONBARS);
}

/* used to determine if wall spines can join this location */
staticfn int
iswall_or_stone(coordxy x, coordxy y)
{
    /* out of bounds = stone */
    if (!isok(x, y))
        return 1;

    return (levl[x][y].typ == STONE || iswall(x, y));
}

/* return TRUE if out of bounds, wall or rock */
staticfn boolean
is_solid(coordxy x, coordxy y)
{
    return (boolean) (!isok(x, y) || IS_STWALL(levl[x][y].typ));
}

/* set map terrain type, handling lava lit, ice melt timers, etc */
boolean
set_levltyp(coordxy x, coordxy y, schar newtyp)
{
    if (isok(x, y) && newtyp >= STONE && newtyp < MAX_TYPE) {
        schar oldtyp = levl[x][y].typ;

        /* hack for secret doors in garden theme rooms */
        if (oldtyp == SDOOR && newtyp == AIR) {
            /* levl[][].typ stays SDOOR rather than change to AIR */
            levl[x][y].arboreal_sdoor = 1;
            return TRUE;
        }

        if (CAN_OVERWRITE_TERRAIN(oldtyp)) {
            /* typ==ICE || (typ==DRAWBRIDGE_UP && drawbridgemask==DB_ICE) */
            boolean was_ice = is_ice(x, y);

            levl[x][y].typ = newtyp;
            /* TODO?
             *  if oldtyp used flags or horizontal differently from
             *  the way newtyp will use them, clear them.
             */

            if (IS_LAVA(newtyp)) /* [what about IS_LAVA(oldtyp)=>.lit = 0?] */
                levl[x][y].lit = 1;
            if (was_ice && newtyp != ICE) {
                /* frozen corpses resume rotting, no more ice to melt away */
                obj_ice_effects(x, y, TRUE);
                spot_stop_timers(x, y, MELT_ICE_AWAY);
            }
            if ((IS_FOUNTAIN(oldtyp) != IS_FOUNTAIN(newtyp))
                || (IS_SINK(oldtyp) != IS_SINK(newtyp)))
                count_level_features(); /* level.flags.nfountains,nsinks */

            return TRUE;
        }
#ifdef EXTRA_SANITY_CHECKS
    } else {
        impossible("set_levltyp(%d,%d,%d)%s%s",
                   (int) x, (int) y, (int) newtyp,
                   !isok(x, y) ? " not isok()" : "",
                   (newtyp < STONE || newtyp >= MAX_TYPE) ? " bad type" : "");
#endif /*EXTRA_SANITY_CHECKS*/
    }
    return FALSE;
}

/* set map terrain type and light state */
boolean
set_levltyp_lit(coordxy x, coordxy y, schar typ, schar lit)
{
    boolean ret = set_levltyp(x, y, typ);

    if (ret && isok(x, y)) {
        if (lit != SET_LIT_NOCHANGE) {
#ifdef EXTRA_SANITY_CHECKS
            if (lit < SET_LIT_NOCHANGE || lit > 1)
                impossible("set_levltyp_lit(%d,%d,%d,%d)",
                           (int) x, (int) y, (int) typ, (int) lit);
#endif /*EXTRA_SANITY_CHECKS*/
            if (IS_LAVA(typ))
                lit = 1;
            else if (lit == SET_LIT_RANDOM)
                lit = rn2(2);

            levl[x][y].lit = lit;
        }
    }
    return ret;
}

/*
 * Return 1 (not TRUE - we're doing bit vectors here) if we want to extend
 * a wall spine in the (dx,dy) direction.  Return 0 otherwise.
 *
 * To extend a wall spine in that direction, first there must be a wall there.
 * Then, extend a spine unless the current position is surrounded by walls
 * in the direction given by (dx,dy).  E.g. if 'x' is our location, 'W'
 * a wall, '.' a room, 'a' anything (we don't care), and our direction is
 * (0,1) - South or down - then:
 *
 *              a a a
 *              W x W           This would not extend a spine from x down
 *              W W W           (a corridor of walls is formed).
 *
 *              a a a
 *              W x W           This would extend a spine from x down.
 *              . W W
 */
staticfn int
extend_spine(int locale[3][3], int wall_there, int dx, int dy)
{
    int spine, nx, ny;

    nx = 1 + dx;
    ny = 1 + dy;

    if (wall_there) { /* wall in that direction */
        if (dx) {
            if (locale[1][0] && locale[1][2]         /* EW are wall/stone */
                && locale[nx][0] && locale[nx][2]) { /* diag are wall/stone */
                spine = 0;
            } else {
                spine = 1;
            }
        } else { /* dy */
            if (locale[0][1] && locale[2][1]         /* NS are wall/stone */
                && locale[0][ny] && locale[2][ny]) { /* diag are wall/stone */
                spine = 0;
            } else {
                spine = 1;
            }
        }
    } else {
        spine = 0;
    }

    return spine;
}

/* Remove walls totally surrounded by stone */
staticfn void
wall_cleanup(coordxy x1, coordxy y1, coordxy x2, coordxy y2)
{
    uchar type;
    coordxy x, y;
    struct rm *lev;

    /* sanity check on incoming variables */
    if (x1 < 0 || x2 >= COLNO || x1 > x2 || y1 < 0 || y2 >= ROWNO || y1 > y2)
        panic("wall_cleanup: bad bounds (%d,%d) to (%d,%d)", x1, y1, x2, y2);

    /* change walls surrounded by rock to rock. */
    for (x = x1; x <= x2; x++)
        for (y = y1; y <= y2; y++) {
            if (within_bounded_area(x, y,
                                  gb.bughack.inarea.x1, gb.bughack.inarea.y1,
                                  gb.bughack.inarea.x2, gb.bughack.inarea.y2))
                continue;
            lev = &levl[x][y];
            type = lev->typ;
            if (IS_WALL(type) && type != DBWALL) {
                if (is_solid(x - 1, y - 1) && is_solid(x - 1, y)
                    && is_solid(x - 1, y + 1) && is_solid(x, y - 1)
                    && is_solid(x, y + 1) && is_solid(x + 1, y - 1)
                    && is_solid(x + 1, y) && is_solid(x + 1, y + 1))
                    lev->typ = STONE;
            }
        }
}

/* Correct wall types so they extend and connect to each other */
void
fix_wall_spines(coordxy x1, coordxy y1, coordxy x2, coordxy y2)
{
    uchar type;
    coordxy x, y;
    struct rm *lev;
    int (*loc_f)(coordxy, coordxy);
    int bits;
    int locale[3][3]; /* rock or wall status surrounding positions */

    /*
     * Value 0 represents a free-standing wall.  It could be anything,
     * so even though this table says VWALL, we actually leave whatever
     * typ was there alone.
     */
    static xint16 spine_array[16] = { VWALL, HWALL,    HWALL,    HWALL,
                                     VWALL, TRCORNER, TLCORNER, TDWALL,
                                     VWALL, BRCORNER, BLCORNER, TUWALL,
                                     VWALL, TLWALL,   TRWALL,   CROSSWALL };

    /* sanity check on incoming variables */
    if (x1 < 0 || x2 >= COLNO || x1 > x2 || y1 < 0 || y2 >= ROWNO || y1 > y2)
        panic("wall_extends: bad bounds (%d,%d) to (%d,%d)", x1, y1, x2, y2);

    /* set the correct wall type. */
    for (x = x1; x <= x2; x++)
        for (y = y1; y <= y2; y++) {
            lev = &levl[x][y];
            type = lev->typ;
            if (!(IS_WALL(type) && type != DBWALL))
                continue;

            /* set the locations TRUE if rock or wall or out of bounds */
            loc_f = within_bounded_area(x, y, /* for baalz insect */
                                   gb.bughack.inarea.x1, gb.bughack.inarea.y1,
                                   gb.bughack.inarea.x2, gb.bughack.inarea.y2)
                       ? iswall
                       : iswall_or_stone;
            locale[0][0] = (*loc_f)(x - 1, y - 1);
            locale[1][0] = (*loc_f)(x, y - 1);
            locale[2][0] = (*loc_f)(x + 1, y - 1);

            locale[0][1] = (*loc_f)(x - 1, y);
            locale[2][1] = (*loc_f)(x + 1, y);

            locale[0][2] = (*loc_f)(x - 1, y + 1);
            locale[1][2] = (*loc_f)(x, y + 1);
            locale[2][2] = (*loc_f)(x + 1, y + 1);

            /* determine if wall should extend to each direction NSEW */
            bits = (extend_spine(locale, iswall(x, y - 1), 0, -1) << 3)
                   | (extend_spine(locale, iswall(x, y + 1), 0, 1) << 2)
                   | (extend_spine(locale, iswall(x + 1, y), 1, 0) << 1)
                   | extend_spine(locale, iswall(x - 1, y), -1, 0);

            /* don't change typ if wall is free-standing */
            if (bits)
                lev->typ = spine_array[bits];
        }
}

void
wallification(coordxy x1, coordxy y1, coordxy x2, coordxy y2)
{
    wall_cleanup(x1, y1, x2, y2);
    fix_wall_spines(x1, y1, x2, y2);
}

staticfn boolean
okay(coordxy x, coordxy y, coordxy dir)
{
    mz_move(x, y, dir);
    mz_move(x, y, dir);
    if (x < 3 || y < 3 || x > gx.x_maze_max || y > gy.y_maze_max
        || levl[x][y].typ != STONE)
        return FALSE;
    return TRUE;
}

/* find random starting point for maze generation */
staticfn void
maze0xy(coord *cc)
{
    cc->x = 3 + 2 * rn2((gx.x_maze_max >> 1) - 1);
    cc->y = 3 + 2 * rn2((gy.y_maze_max >> 1) - 1);
    return;
}

boolean
is_exclusion_zone(xint16 type, coordxy x, coordxy y)
{
    struct exclusion_zone *ez = sve.exclusion_zones;

    while (ez) {
        if (((type == LR_DOWNTELE
              && (ez->zonetype == LR_DOWNTELE || ez->zonetype == LR_TELE))
             || (type == LR_UPTELE
                 && (ez->zonetype == LR_UPTELE || ez->zonetype == LR_TELE))
             || type == ez->zonetype)
            && within_bounded_area(x, y, ez->lx, ez->ly, ez->hx, ez->hy))
            return TRUE;
        ez = ez->next;
    }
    return FALSE;
}

/*
 * Bad if:
 *      pos is occupied OR
 *      pos is inside restricted region (nlx,nly,nhx,nhy) OR
 *      NOT (pos is corridor and a maze level OR pos is a room OR pos is air)
 */
boolean
bad_location(
    coordxy x, coordxy y,
    coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy)
{
    return (boolean) (occupied(x, y)
                      || within_bounded_area(x, y, nlx, nly, nhx, nhy)
                      || !((levl[x][y].typ == CORR
                            && svl.level.flags.is_maze_lev)
                           || levl[x][y].typ == ROOM
                           || levl[x][y].typ == AIR));
}

/* pick a location in area (lx, ly, hx, hy) but not in (nlx, nly, nhx, nhy)
   and place something (based on rtype) in that region */
void
place_lregion(
    coordxy lx, coordxy ly, coordxy hx, coordxy hy,
    coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy,
    xint16 rtype,
    d_level *lev)
{
    int trycnt;
    boolean oneshot;
    coordxy x, y;

    if (!lx) { /* default to whole level */
        /*
         * if there are rooms and this a branch, let place_branch choose
         * the branch location (to avoid putting branches in corridors).
         */
        if (rtype == LR_BRANCH && svn.nroom) {
            place_branch(Is_branchlev(&u.uz), 0, 0);
            return;
        }

        lx = 1; /* column 0 is not used */
        hx = COLNO - 1;
        ly = 0; /* 3.6.0 and earlier erroneously had 1 here */
        hy = ROWNO - 1;
    }

    /* clamp the area to the map */
    if (lx < 1)
        lx = 1;
    if (hx > COLNO - 1)
        hx = COLNO - 1;
    if (ly < 0)
        ly = 0;
    if (hy > ROWNO - 1)
        hy = ROWNO - 1;

    /* first a probabilistic approach */

    oneshot = (lx == hx && ly == hy);
    for (trycnt = 0; trycnt < 200; trycnt++) {
        x = rn1((hx - lx) + 1, lx);
        y = rn1((hy - ly) + 1, ly);
        if (put_lregion_here(x, y, nlx, nly, nhx, nhy, rtype, oneshot, lev))
            return;
    }

    /* then a deterministic one */

    for (x = lx; x <= hx; x++)
        for (y = ly; y <= hy; y++)
            if (put_lregion_here(x, y, nlx, nly, nhx, nhy, rtype, TRUE, lev))
                return;

    impossible("Couldn't place lregion type %d!", rtype);
}

staticfn boolean
put_lregion_here(
    coordxy x, coordxy y,
    coordxy nlx, coordxy nly, coordxy nhx, coordxy nhy,
    xint16 rtype,
    boolean oneshot,
    d_level *lev)
{
    struct monst *mtmp;

    if (bad_location(x, y, nlx, nly, nhx, nhy)
        || is_exclusion_zone(rtype, x, y)) {
        if (!oneshot) {
            return FALSE; /* caller should try again */
        } else {
            /* Must make do with the only location possible;
               avoid failure due to a misplaced trap.
               It might still fail if there's a dungeon feature here. */
            struct trap *t = t_at(x, y);

            if (t && !undestroyable_trap(t->ttyp)) {
                if (((mtmp = m_at(x, y)) != 0) && mtmp->mtrapped)
                    mtmp->mtrapped = 0;
                deltrap(t);
            }
            if (bad_location(x, y, nlx, nly, nhx, nhy)
                || is_exclusion_zone(rtype, x, y))
                return FALSE;
        }
    }
    switch (rtype) {
    case LR_TELE:
    case LR_UPTELE:
    case LR_DOWNTELE:
        /* "something" means the player in this case */
        if ((mtmp = m_at(x, y)) != 0) {
            /* move the monster if no choice, or just try again */
            if (oneshot) {
                if (!rloc(mtmp, RLOC_NOMSG))
                    m_into_limbo(mtmp);
            } else
                return FALSE;
        }
        u_on_newpos(x, y);
        break;
    case LR_PORTAL:
        mkportal(x, y, lev->dnum, lev->dlevel);
        break;
    case LR_DOWNSTAIR:
    case LR_UPSTAIR:
        mkstairs(x, y, (char) rtype, (struct mkroom *) 0, FALSE);
        break;
    case LR_BRANCH:
        place_branch(Is_branchlev(&u.uz), x, y);
        break;
    }
    return TRUE;
}

/* fix up Baalzebub's lair, which depicts a level-sized beetle;
   its legs are walls within solid rock--regular wallification
   classifies them as superfluous and gets rid of them */
staticfn void
baalz_fixup(void)
{
    struct monst *mtmp;
    int x, y, lastx, lasty;

    /*
     * baalz level's nondiggable region surrounds the "insect" and rooms.
     * The outermost perimeter of that region is subject to wall cleanup
     * (hence 'x + 1' and 'y + 1' for starting don't-clean column and row,
     * 'lastx - 1' and 'lasty - 1' for ending don't-clean column and row)
     * and the interior is protected against that (in wall_cleanup()).
     *
     * Assumes level.flags.corrmaze is True, otherwise the bug legs will
     * have already been "cleaned" away by general wallification.
     */

    /* find low and high x for to-be-wallified portion of level */
    y = ROWNO / 2;
    for (lastx = x = 0; x < COLNO; ++x)
        if ((levl[x][y].wall_info & W_NONDIGGABLE) != 0) {
            if (!lastx)
                gb.bughack.inarea.x1 = x + 1;
            lastx = x;
        }
    gb.bughack.inarea.x2 = ((lastx > gb.bughack.inarea.x1) ? lastx : x) - 1;
    /* find low and high y for to-be-wallified portion of level */
    x = gb.bughack.inarea.x1;
    for (lasty = y = 0; y < ROWNO; ++y)
        if ((levl[x][y].wall_info & W_NONDIGGABLE) != 0) {
            if (!lasty)
                gb.bughack.inarea.y1 = y + 1;
            lasty = y;
        }
    gb.bughack.inarea.y2 = ((lasty > gb.bughack.inarea.y1) ? lasty : y) - 1;
    /* two pools mark where special post-wallify fix-ups are needed */
    for (x = gb.bughack.inarea.x1; x <= gb.bughack.inarea.x2; ++x)
        for (y = gb.bughack.inarea.y1; y <= gb.bughack.inarea.y2; ++y)
            if (levl[x][y].typ == POOL) {
                levl[x][y].typ = HWALL;
                if (gb.bughack.delarea.x1 == COLNO)
                    gb.bughack.delarea.x1 = x, gb.bughack.delarea.y1 = y;
                else
                    gb.bughack.delarea.x2 = x, gb.bughack.delarea.y2 = y;
            } else if (levl[x][y].typ == IRONBARS) {
                /* novelty effect; allowing digging in front of 'eyes' */
                if (isok(x - 1, y)
                    && (levl[x - 1][y].wall_info & W_NONDIGGABLE) != 0) {
                    levl[x - 1][y].wall_info &= ~W_NONDIGGABLE;
                    if (isok(x - 2, y))
                        levl[x - 2][y].wall_info &= ~W_NONDIGGABLE;
                } else if (isok(x + 1, y)
                    && (levl[x + 1][y].wall_info & W_NONDIGGABLE) != 0) {
                    levl[x + 1][y].wall_info &= ~W_NONDIGGABLE;
                    if (isok(x + 2, y))
                        levl[x + 2][y].wall_info &= ~W_NONDIGGABLE;
                }
            }

    wallification(max(gb.bughack.inarea.x1 - 2, 1),
                  max(gb.bughack.inarea.y1 - 2, 0),
                  min(gb.bughack.inarea.x2 + 2, COLNO - 1),
                  min(gb.bughack.inarea.y2 + 2, ROWNO - 1));

    /* bughack hack for rear-most legs on baalz level; first joint on
       both top and bottom gets a bogus extra connection to room area,
       producing unwanted rectangles; change back to separated legs */
    x = gb.bughack.delarea.x1, y = gb.bughack.delarea.y1;
    if (isok(x, y) && (levl[x][y].typ == TLWALL || levl[x][y].typ == TRWALL)
        && isok(x, y + 1) && levl[x][y + 1].typ == TUWALL) {
        levl[x][y].typ = (levl[x][y].typ == TLWALL) ? BRCORNER : BLCORNER;
        levl[x][y + 1].typ = HWALL;
        if ((mtmp = m_at(x, y)) != 0) /* something at temporary pool... */
            (void) rloc(mtmp, RLOC_ERR|RLOC_NOMSG);
    }

    x = gb.bughack.delarea.x2, y = gb.bughack.delarea.y2;
    if (isok(x, y) && (levl[x][y].typ == TLWALL || levl[x][y].typ == TRWALL)
        && isok(x, y - 1) && levl[x][y - 1].typ == TDWALL) {
        levl[x][y].typ = (levl[x][y].typ == TLWALL) ? TRCORNER : TLCORNER;
        levl[x][y - 1].typ = HWALL;
        if ((mtmp = m_at(x, y)) != 0) /* something at temporary pool... */
            (void) rloc(mtmp, RLOC_ERR|RLOC_NOMSG);
    }

    /* reset bughack region; set low end to <COLNO,ROWNO> so that
       within_bounded_region() in fix_wall_spines() will fail
       most quickly--on its first test--when loading other levels */
    gb.bughack.inarea.x1 = gb.bughack.delarea.x1 = COLNO;
    gb.bughack.inarea.y1 = gb.bughack.delarea.y1 = ROWNO;
    gb.bughack.inarea.x2 = gb.bughack.delarea.x2 = 0;
    gb.bughack.inarea.y2 = gb.bughack.delarea.y2 = 0;
}

/* this is special stuff that the level compiler cannot (yet) handle */
void
fixup_special(void)
{
    lev_region *r = gl.lregions;
    s_level *sp;
    struct d_level lev;
    int x, y;
    struct mkroom *croom;
    boolean added_branch = FALSE;

    if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) {
        svl.level.flags.hero_memory = 0;
        /* water level is an odd beast - it has to be set up
           before calling place_lregions etc. */
        setup_waterlevel();
    }
    for (x = 0; x < gn.num_lregions; x++, r++) {
        switch (r->rtype) {
        case LR_BRANCH:
            added_branch = TRUE;
            goto place_it;

        case LR_PORTAL:
            if (*r->rname.str >= '0' && *r->rname.str <= '9') {
                /* "chutes and ladders" */
                lev = u.uz;
                lev.dlevel = atoi(r->rname.str);
            } else {
                sp = find_level(r->rname.str);
                lev = sp->dlevel;
            }
            FALLTHROUGH;
            /*FALLTHRU*/

        case LR_UPSTAIR:
        case LR_DOWNSTAIR:
 place_it:
            place_lregion(r->inarea.x1, r->inarea.y1, r->inarea.x2,
                          r->inarea.y2, r->delarea.x1, r->delarea.y1,
                          r->delarea.x2, r->delarea.y2, r->rtype, &lev);
            break;

        case LR_TELE:
        case LR_UPTELE:
        case LR_DOWNTELE:
            /* save the region outlines for goto_level() */
            if (r->rtype == LR_TELE || r->rtype == LR_UPTELE) {
                svu.updest.lx = r->inarea.x1;
                svu.updest.ly = r->inarea.y1;
                svu.updest.hx = r->inarea.x2;
                svu.updest.hy = r->inarea.y2;
                svu.updest.nlx = r->delarea.x1;
                svu.updest.nly = r->delarea.y1;
                svu.updest.nhx = r->delarea.x2;
                svu.updest.nhy = r->delarea.y2;
            }
            if (r->rtype == LR_TELE || r->rtype == LR_DOWNTELE) {
                svd.dndest.lx = r->inarea.x1;
                svd.dndest.ly = r->inarea.y1;
                svd.dndest.hx = r->inarea.x2;
                svd.dndest.hy = r->inarea.y2;
                svd.dndest.nlx = r->delarea.x1;
                svd.dndest.nly = r->delarea.y1;
                svd.dndest.nhx = r->delarea.x2;
                svd.dndest.nhy = r->delarea.y2;
            }
            /* place_lregion gets called from goto_level() */
            break;
        }

        if (r->rname.str)
            free((genericptr_t) r->rname.str), r->rname.str = 0;
    }

    /* place dungeon branch if not placed above */
    if (!added_branch && Is_branchlev(&u.uz)) {
        place_lregion(0, 0, 0, 0, 0, 0, 0, 0, LR_BRANCH, (d_level *) 0);
    }

    /* Still need to add some stuff to level file */
    if (Is_medusa_level(&u.uz)) {
        struct obj *otmp;
        int tryct;

        croom = &svr.rooms[0]; /* the first room defined on the medusa level */
        for (tryct = rnd(4); tryct; tryct--) {
            x = somex(croom);
            y = somey(croom);
            if (goodpos(x, y, (struct monst *) 0, 0)) {
                int tryct2 = 0;

                otmp = mk_tt_object(STATUE, x, y);
                while (++tryct2 < 100 && otmp
                       && (poly_when_stoned(&mons[otmp->corpsenm])
                                || pm_resistance(&mons[otmp->corpsenm],
                                                 MR_STONE))) {
                    /* set_corpsenm() handles weight too */
                    set_corpsenm(otmp, rndmonnum());
                }
            }
        }

        if (rn2(2))
            otmp = mk_tt_object(STATUE, somex(croom), somey(croom));
        else /* Medusa statues don't contain books */
            otmp =
                mkcorpstat(STATUE, (struct monst *) 0, (struct permonst *) 0,
                           somex(croom), somey(croom), CORPSTAT_NONE);
        if (otmp) {
            tryct = 0;
            while (++tryct < 100
                   && (pm_resistance(&mons[otmp->corpsenm], MR_STONE)
                       || poly_when_stoned(&mons[otmp->corpsenm]))) {
                /* set_corpsenm() handles weight too */
                set_corpsenm(otmp, rndmonnum());
            }
        }
    } else if (Role_if(PM_CLERIC) && In_quest(&u.uz)) {
        /* less chance for undead corpses (lured from lower morgues) */
        svl.level.flags.graveyard = 1;
    } else if (Is_stronghold(&u.uz)) {
        svl.level.flags.graveyard = 1;
    } else if (on_level(&u.uz, &baalzebub_level)) {
        /* custom wallify the "beetle" potion of the level */
        baalz_fixup();
    } else if (u.uz.dnum == mines_dnum && gr.ransacked) {
       stolen_booty();
    }

    if ((sp = Is_special(&u.uz)) != 0 && sp->flags.town) /* Mine Town */
        svl.level.flags.has_town = 1;

    if (gl.lregions)
        free((genericptr_t) gl.lregions), gl.lregions = 0;
    gn.num_lregions = 0;
}

staticfn void
check_ransacked(const char *s)
{
    /* this kludge only works as long as orctown is minetn-1 */
    gr.ransacked = (u.uz.dnum == mines_dnum && !strcmp(s, "minetn-1"));
}

#define ORC_LEADER 1
static const char *const orcfruit[] = { "paddle cactus", "dwarven root" };

staticfn void
migrate_orc(struct monst *mtmp, unsigned long mflags)
{
    int nlev, max_depth, cur_depth;
    d_level dest;

    cur_depth = (int) depth(&u.uz);
    max_depth = dunlevs_in_dungeon(&u.uz)
                + (svd.dungeons[u.uz.dnum].depth_start - 1);
    if (mflags == ORC_LEADER) {
        /* Note that the orc leader will take possession of any
         * remaining stuff not already delivered to other
         * orcs between here and the bottom of the mines.
         */
        nlev = max_depth;
        /* once in a blue moon, he won't be at the very bottom */
        if (!rn2(40))
            nlev--;
        mtmp->migflags |= MIGR_LEFTOVERS;
    } else {
        nlev = rn2((max_depth - cur_depth) + 1) + cur_depth;
        if (nlev == cur_depth)
            nlev++;
        if (nlev > max_depth)
            nlev = max_depth;
        mtmp->migflags = (mtmp->migflags & ~MIGR_LEFTOVERS);
    }
    get_level(&dest, nlev);
    migrate_to_level(mtmp, ledger_no(&dest), MIGR_RANDOM, (coord *) 0);
}

staticfn void
shiny_orc_stuff(struct monst *mtmp)
{
    int gemprob, goldprob, otyp;
    struct obj *otmp;
    boolean is_captain = (mtmp->data == &mons[PM_ORC_CAPTAIN]);

    /* probabilities */
    goldprob = is_captain ? 600 : 300;
    gemprob = goldprob / 4;
    if (rn2(1000) < goldprob) {
        if ((otmp = mksobj(GOLD_PIECE, TRUE, FALSE)) != 0) {
            otmp->quan = 1L + rnd(goldprob);
            otmp->owt = weight(otmp);
            add_to_minv(mtmp, otmp);
        }
    }
    if (rn2(1000) < gemprob) {
        if ((otmp = mkobj(GEM_CLASS, FALSE)) != 0) {
            if (otmp->otyp == ROCK)
                dealloc_obj(otmp);
            else
                add_to_minv(mtmp, otmp);
        }
    }
    if (is_captain || !rn2(8)) {
        otyp = shiny_obj(RING_CLASS);
        if (otyp != STRANGE_OBJECT && (otmp = mksobj(otyp, TRUE, FALSE)) != 0)
            add_to_minv(mtmp, otmp);
    }
}

staticfn void
migr_booty_item(int otyp, const char *gang)
{
    struct obj *otmp;

    otmp = mksobj_migr_to_species(otyp, (unsigned long) M2_ORC, TRUE, FALSE);
    if (otmp && gang) {
        new_oname(otmp, Strlen(gang) + 1); /* removes old name if present */
        Strcpy(ONAME(otmp), gang);
        if (objects[otyp].oc_class == FOOD_CLASS) {
            if (otyp == SLIME_MOLD)
                otmp->spe = fruitadd((char *) ROLL_FROM(orcfruit),
                                     (struct fruit *) 0);
            otmp->quan += (long) rn2(3);
            otmp->owt = weight(otmp);
        }
    }
}

staticfn void
stolen_booty(void)
{
    char *gang, gang_name[BUFSZ];
    struct monst *mtmp;
    int cnt, i, otyp;

    /*
     * --------------------------------------------------------
     * Mythos:
     *
     *      A tragic accident has occurred in Frontier Town...
     *      It has been overrun by orcs.
     *
     *      The booty that the orcs took from the town is now
     *      in the possession of the orcs that did this and
     *      have long since fled the level.
     * --------------------------------------------------------
     */

    gang = rndorcname(gang_name);
    /* create the stuff that the gang took */
    cnt = rnd(4);
    for (i = 0; i < cnt; ++i)
        migr_booty_item(rn2(4) ? TALLOW_CANDLE : WAX_CANDLE, gang);
    cnt = rnd(3);
    for (i = 0; i < cnt; ++i)
        migr_booty_item(SKELETON_KEY, gang);
    otyp = rn1((GAUNTLETS_OF_DEXTERITY - LEATHER_GLOVES) + 1, LEATHER_GLOVES);
    migr_booty_item(otyp, gang);
    cnt = rnd(10);
    for (i = 0; i < cnt; ++i) {
        /* Food items - but no lembas! (or some other weird things) */
        otyp = rn1(TIN - TRIPE_RATION + 1, TRIPE_RATION);
        if (otyp != LEMBAS_WAFER
            /* exclude meat <anything>, globs of <anything>, kelp
               which all have random generation probability of 0
               (K-/C-rations do too, but we want to include those) */
            && (objects[otyp].oc_prob != 0
                || otyp == C_RATION || otyp == K_RATION)
            /* exclude food items which utilize obj->corpsenm because
               that field is going to be overloaded for delivery purposes */
            && otyp != CORPSE && otyp != EGG && otyp != TIN)
            migr_booty_item(otyp, gang);
    }
    migr_booty_item(rn2(2) ? LONG_SWORD : SILVER_SABER, gang);
    /* create the leader of the orc gang */
    mtmp = makemon(&mons[PM_ORC_CAPTAIN], 0, 0, MM_NONAME);
    if (mtmp) {
        mtmp = christen_monst(mtmp, upstart(gang));
        mtmp->mpeaceful = 0;
        set_malign(mtmp);
        shiny_orc_stuff(mtmp);
        migrate_orc(mtmp, ORC_LEADER);
    }
    /* Make most of the orcs on the level be part of the invading gang */
    for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
        if (DEADMONSTER(mtmp))
            continue;

        if (is_orc(mtmp->data) && !has_mgivenname(mtmp) && rn2(10)) {
            /*
             * We'll consider the orc captain from the level
             * description to be the captain of a rival orc horde
             * who is there to see what has transpired, and to
             * contemplate future action.
             *
             * Don't christen the orc captain as a subordinate
             * member of the main orc horde.
             */
            if (mtmp->data != &mons[PM_ORC_CAPTAIN])
                mtmp = christen_orc(mtmp, upstart(gang), "");
        }
    }
    /* Lastly, ensure there's several more orcs from the gang along the way.
     * The mechanics are such that they aren't actually identified as
     * members of the invading gang until they get their spoils assigned
     * to the inventory; handled during that assignment.
     */
    cnt = rn2(10) + 5;
    for (i = 0; i < cnt; ++i) {
        int mtyp;

        mtyp = rn2((PM_ORC_SHAMAN - PM_ORC) + 1) + PM_ORC;
        mtmp = makemon(&mons[mtyp], 0, 0, MM_NONAME);
        if (mtmp) {
            shiny_orc_stuff(mtmp);
            migrate_orc(mtmp, 0UL);
        }
    }
    gr.ransacked = 0;
}

#undef ORC_LEADER

staticfn boolean
maze_inbounds(coordxy x, coordxy y)
{
    return (x >= 2 && y >= 2
            && x < gx.x_maze_max && y < gy.y_maze_max
            /* isok() test is superfluous here (unless something has
               clobbered the static *_maze_max variables) */
            && isok(x, y));
}

staticfn void
maze_remove_deadends(xint16 typ)
{
    char dirok[4];
    coordxy x, y, dir, idx, idx2, dx, dy, dx2, dy2;

    dirok[0] = 0; /* lint suppression */
    for (x = 2; x < gx.x_maze_max; x++)
        for (y = 2; y < gy.y_maze_max; y++)
            if (ACCESSIBLE(levl[x][y].typ) && (x % 2) && (y % 2)) {
                idx = idx2 = 0;
                for (dir = 0; dir < 4; dir++) {
                    /* note: mz_move() is a macro which modifies
                       one of its first two parameters */
                    dx = dx2 = x;
                    dy = dy2 = y;
                    mz_move(dx, dy, dir);
                    if (!maze_inbounds(dx, dy)) {
                        idx2++;
                        continue;
                    }
                    mz_move(dx2, dy2, dir);
                    mz_move(dx2, dy2, dir);
                    if (!maze_inbounds(dx2, dy2)) {
                        idx2++;
                        continue;
                    }
                    if (!ACCESSIBLE(levl[dx][dy].typ)
                        && ACCESSIBLE(levl[dx2][dy2].typ)) {
                        dirok[idx++] = dir;
                        idx2++;
                    }
                }
                if (idx2 >= 3 && idx > 0) {
                    dx = x;
                    dy = y;
                    dir = dirok[rn2(idx)];
                    mz_move(dx, dy, dir);
                    levl[dx][dy].typ = typ;
                }
            }
}

/* Create a maze with specified corridor width and wall thickness
 * TODO: rewrite walkfrom so it works on temp space, not levl
 */
void
create_maze(int corrwid, int wallthick, boolean rmdeadends)
{
    coordxy x,y;
    coord mm;
    int tmp_xmax = gx.x_maze_max;
    int tmp_ymax = gy.y_maze_max;
    int rdx = 0;
    int rdy = 0;
    int scale;

    if (corrwid == -1)
        corrwid = rnd(4);

    if (wallthick == -1)
        wallthick = rnd(4) - corrwid;

    if (wallthick < 1)
        wallthick = 1;
    else if (wallthick > 5)
        wallthick = 5;

    if (corrwid < 1)
        corrwid = 1;
    else if (corrwid > 5)
        corrwid = 5;

    scale = corrwid + wallthick;
    rdx = (gx.x_maze_max / scale);
    rdy = (gy.y_maze_max / scale);

    if (svl.level.flags.corrmaze)
        for (x = 2; x < (rdx * 2); x++)
            for (y = 2; y < (rdy * 2); y++)
                levl[x][y].typ = STONE;
    else
        for (x = 2; x <= (rdx * 2); x++)
            for (y = 2; y <= (rdy * 2); y++)
                levl[x][y].typ = ((x % 2) && (y % 2)) ? STONE : HWALL;

    /* set upper bounds for maze0xy and walkfrom */
    gx.x_maze_max = (rdx * 2);
    gy.y_maze_max = (rdy * 2);

    /* create maze */
    maze0xy(&mm);
    walkfrom((int) mm.x, (int) mm.y, 0);

    if (rmdeadends)
        maze_remove_deadends((svl.level.flags.corrmaze) ? CORR : ROOM);

    /* restore bounds */
    gx.x_maze_max = tmp_xmax;
    gy.y_maze_max = tmp_ymax;

    /* scale maze up if needed */
    if (scale > 2) {
        char tmpmap[COLNO][ROWNO];
        int mx, my, dx, dy, rx = 1, ry = 1;

        /* back up the existing smaller maze */
        for (x = 1; x < gx.x_maze_max; x++)
            for (y = 1; y < gy.y_maze_max; y++) {
                tmpmap[x][y] = levl[x][y].typ;
            }

        /* do the scaling */
        rx = x = 2;
        while (rx < gx.x_maze_max) {
            mx = (x % 2) ? corrwid : (x == 2 || x == rdx * 2) ? 1 : wallthick;
            ry = y = 2;
            while (ry < gy.y_maze_max) {
                my = (y % 2) ? corrwid
                     : (y == 2 || y == rdy * 2) ? 1
                       : wallthick;
                for (dx = 0; dx < mx; dx++)
                    for (dy = 0; dy < my; dy++) {
                        if (rx + dx >= gx.x_maze_max
                            || ry + dy >= gy.y_maze_max)
                            break;
                        levl[rx + dx][ry + dy].typ = tmpmap[x][y];
                    }
                ry += my;
                y++;
            }
            rx += mx;
            x++;
        }

    }
}

void
pick_vibrasquare_location(void)
{
    coordxy x, y;
    stairway *stway;
    int trycnt = 0;
    /* these are also defined in mklev.c and they may not be appropriate
       for mazes with corridors wider than 1 or for cavernous levels */
#define x_maze_min 2
#define y_maze_min 2

    /*
     * Pick a position where the stairs down to Moloch's Sanctum
     * level will ultimately be created.  At that time, an area
     * will be altered:  walls removed, moat and traps generated,
     * boulders destroyed.  The position picked here must ensure
     * that that invocation area won't extend off the map.
     *
     * We actually allow up to 2 squares around the usual edge of
     * the area to get truncated; see mkinvokearea(mklev.c).
     */
#define INVPOS_X_MARGIN (6 - 2)
#define INVPOS_Y_MARGIN (5 - 2)
#define INVPOS_DISTANCE 11
    int x_range = gx.x_maze_max - x_maze_min - 2 * INVPOS_X_MARGIN - 1,
        y_range = gy.y_maze_max - y_maze_min - 2 * INVPOS_Y_MARGIN - 1;

    if (x_range <= INVPOS_X_MARGIN || y_range <= INVPOS_Y_MARGIN
        || (x_range * y_range) <= (INVPOS_DISTANCE * INVPOS_DISTANCE)) {
        debugpline2("svi.inv_pos: maze is too small! (%d x %d)",
                    gx.x_maze_max, gy.y_maze_max);
    }
    svi.inv_pos.x = svi.inv_pos.y = 0; /*{occupied() => invocation_pos()}*/
    do {
        x = rn1(x_range, x_maze_min + INVPOS_X_MARGIN + 1);
        y = rn1(y_range, y_maze_min + INVPOS_Y_MARGIN + 1);
        /* we don't want it to be too near the stairs, nor
           to be on a spot that's already in use (wall|trap) */
        if (++trycnt > 1000)
            break;
    } while (((stway = stairway_find_dir(TRUE)) != 0)
             && (x == stway->sx || y == stway->sy /*(direct line)*/
                 || abs(x - stway->sx) == abs(y - stway->sy)
                 || distmin(x, y, stway->sx, stway->sy) <= INVPOS_DISTANCE
                 || !SPACE_POS(levl[x][y].typ) || occupied(x, y)));
    svi.inv_pos.x = x;
    svi.inv_pos.y = y;
#undef INVPOS_X_MARGIN
#undef INVPOS_Y_MARGIN
#undef INVPOS_DISTANCE
#undef x_maze_min
#undef y_maze_min
}

/* add objects and monsters to random maze */
staticfn void
populate_maze(void)
{
    int i;
    coord mm;

    for (i = rn1(8, 11); i; i--) {
        mazexy(&mm);
        (void) mkobj_at(rn2(2) ? GEM_CLASS : RANDOM_CLASS, mm.x, mm.y, TRUE);
    }
    for (i = rn1(10, 2); i; i--) {
        mazexy(&mm);
        (void) mksobj_at(BOULDER, mm.x, mm.y, TRUE, FALSE);
    }
    for (i = rn2(3); i; i--) {
        mazexy(&mm);
        (void) makemon(&mons[PM_MINOTAUR], mm.x, mm.y, NO_MM_FLAGS);
    }
    for (i = rn1(5, 7); i; i--) {
        mazexy(&mm);
        (void) makemon((struct permonst *) 0, mm.x, mm.y, NO_MM_FLAGS);
    }
    for (i = rn1(6, 7); i; i--) {
        mazexy(&mm);
        (void) mkgold(0L, mm.x, mm.y);
    }
    for (i = rn1(6, 7); i; i--)
        mktrap(0, MKTRAP_MAZEFLAG, (struct mkroom *) 0, (coord *) 0);
}

void
makemaz(const char *s)
{
    char protofile[20];
    s_level *sp = Is_special(&u.uz);
    coord mm;

    if (*s) {
        if (sp && sp->rndlevs)
            Snprintf(protofile, sizeof protofile,
                     "%s-%d", s, rnd((int) sp->rndlevs));
        else
            Strcpy(protofile, s);
    } else if (*(svd.dungeons[u.uz.dnum].proto)) {
        if (dunlevs_in_dungeon(&u.uz) > 1) {
            if (sp && sp->rndlevs)
                Snprintf(protofile, sizeof protofile,
                         "%s%d-%d", svd.dungeons[u.uz.dnum].proto,
                         dunlev(&u.uz), rnd((int) sp->rndlevs));
            else
                Snprintf(protofile, sizeof protofile,
                         "%s%d", svd.dungeons[u.uz.dnum].proto,
                         dunlev(&u.uz));
        } else if (sp && sp->rndlevs) {
            Snprintf(protofile, sizeof protofile,
                     "%s-%d", svd.dungeons[u.uz.dnum].proto,
                     rnd((int) sp->rndlevs));
        } else
            Strcpy(protofile, svd.dungeons[u.uz.dnum].proto);

    } else
        Strcpy(protofile, "");

    /* SPLEVTYPE format is "level-choice,level-choice"... */
    if (wizard && *protofile && sp && sp->rndlevs) {
        char *ep = getenv("SPLEVTYPE"); /* not nh_getenv */

        if (ep) {
            /* strrchr always succeeds due to code in prior block */
            int len = (int) ((strrchr(protofile, '-') - protofile) + 1);

            while (ep && *ep) {
                if (!strncmp(ep, protofile, len)) {
                    int pick = atoi(ep + len);

                    /* use choice only if valid */
                    if (pick > 0 && pick <= (int) sp->rndlevs)
                        Sprintf(protofile + len, "%d", pick);
                    break;
                } else {
                    ep = strchr(ep, ',');
                    if (ep)
                        ++ep;
                }
            }
        }
    }

    if (*protofile) {
        check_ransacked(protofile);
        Strcat(protofile, LEV_EXT);
        gi.in_mk_themerooms = FALSE;
        if (load_special(protofile)) {
            /* some levels can end up with monsters
               on dead mon list, including light source monsters */
            dmonsfree();
            return; /* no mazification right now */
        }
        impossible("Couldn't load \"%s\" - making a maze.", protofile);
    }

    svl.level.flags.is_maze_lev = 1;
    svl.level.flags.corrmaze = !rn2(3);

    if (!Invocation_lev(&u.uz) && rn2(2)) {
        create_maze(-1, -1, !rn2(5));
    } else {
        create_maze(1, 1, FALSE);
    }

    if (!svl.level.flags.corrmaze)
        wallification(2, 2, gx.x_maze_max, gy.y_maze_max);

    mazexy(&mm);
    mkstairs(mm.x, mm.y, 1, (struct mkroom *) 0, FALSE); /* up */
    if (!Invocation_lev(&u.uz)) {
        mazexy(&mm);
        mkstairs(mm.x, mm.y, 0, (struct mkroom *) 0, FALSE); /* down */
    } else { /* choose "vibrating square" location */
        pick_vibrasquare_location();
        maketrap(svi.inv_pos.x, svi.inv_pos.y, VIBRATING_SQUARE);
    }

    /* place branch stair or portal */
    place_branch(Is_branchlev(&u.uz), 0, 0);

    populate_maze();
}

#ifdef MICRO
/* Make the mazewalk iterative by faking a stack.  This is needed to
 * ensure the mazewalk is successful in the limited stack space of
 * the program.  This iterative version uses the minimum amount of stack
 * that is totally safe.
 */
void
walkfrom(coordxy x, coordxy y, schar typ)
{
#define CELLS (ROWNO * COLNO) / 4            /* a maze cell is 4 squares */
    char mazex[CELLS + 1], mazey[CELLS + 1]; /* char's are OK */
    int q, a, dir, pos;
    int dirs[4];

    if (!typ) {
        if (svl.level.flags.corrmaze)
            typ = CORR;
        else
            typ = ROOM;
    }

    pos = 1;
    mazex[pos] = (char) x;
    mazey[pos] = (char) y;
    while (pos) {
        x = (int) mazex[pos];
        y = (int) mazey[pos];
        if (!IS_DOOR(levl[x][y].typ)) {
            /* might still be on edge of MAP, so don't overwrite */
            levl[x][y].typ = typ;
            levl[x][y].flags = 0;
        }
        q = 0;
        for (a = 0; a < 4; a++)
            if (okay(x, y, a))
                dirs[q++] = a;
        if (!q)
            pos--;
        else {
            dir = dirs[rn2(q)];
            mz_move(x, y, dir);
            levl[x][y].typ = typ;
            mz_move(x, y, dir);
            pos++;
            if (pos > CELLS)
                panic("Overflow in walkfrom");
            mazex[pos] = (char) x;
            mazey[pos] = (char) y;
        }
    }
}
#else /* !MICRO */

void
walkfrom(coordxy x, coordxy y, schar typ)
{
    int q, a, dir;
    int dirs[4];

    if (!typ) {
        if (svl.level.flags.corrmaze)
            typ = CORR;
        else
            typ = ROOM;
    }

    if (!IS_DOOR(levl[x][y].typ)) {
        /* might still be on edge of MAP, so don't overwrite */
        levl[x][y].typ = typ;
        levl[x][y].flags = 0;
    }

    while (1) {
        q = 0;
        for (a = 0; a < 4; a++)
            if (okay(x, y, a))
                dirs[q++] = a;
        if (!q)
            return;
        dir = dirs[rn2(q)];
        mz_move(x, y, dir);
        levl[x][y].typ = typ;
        mz_move(x, y, dir);
        walkfrom(x, y, typ);
    }
}
#endif /* ?MICRO */

/* find random point in generated corridors,
   so we don't create items in moats, bunkers, or walls */
void
mazexy(coord *cc)
{
    coordxy x, y;
    int allowedtyp = (svl.level.flags.corrmaze ? CORR : ROOM);
    int cpt = 0;

    do {
        /* once upon a time this only considered odd values greater than 2
           and less than N (for N=={x,y}_maze_max) because even values were
           where maze walls always got placed; when wider maze corridors
           were introduced it was changed to 1+rn2(N) which is just an
           obscure way to get rnd(N); probably ought to be using 2+rn2(N-1)
           to exclude the maze's outer boundary walls; trying and rejecting
           those walls will waste some of the 100 random attempts... */
        x = rnd(gx.x_maze_max);
        y = rnd(gy.y_maze_max);
        if (levl[x][y].typ == allowedtyp) {
            cc->x = x;
            cc->y = y;
            return;
        }
    } while (++cpt < 100);
    /* 100 random attempts failed; systematically try every possibility */
    for (x = 1; x <= gx.x_maze_max; x++)
        for (y = 1; y <= gy.y_maze_max; y++)
            if (levl[x][y].typ == allowedtyp) {
                cc->x = x;
                cc->y = y;
                return;
            }
    /* every spot on the area of map allowed for mazes has been rejected */
    panic("mazexy: can't find a place!");
    /*NOTREACHED*/
    return;
}

void
get_level_extends(
    coordxy *left, coordxy *top,
    coordxy *right, coordxy *bottom)
{
    coordxy x, y;
    unsigned typ;
    struct rm *lev;
    boolean found, nonwall;
    coordxy xmin, xmax, ymin, ymax;

    found = nonwall = FALSE;
    for (xmin = 0; !found && xmin <= COLNO; xmin++) {
        lev = &levl[xmin][0];
        for (y = 0; y <= ROWNO - 1; y++, lev++) {
            typ = lev->typ;
            if (typ != STONE) {
                found = TRUE;
                if (!IS_WALL(typ))
                    nonwall = TRUE;
            }
        }
    }
    xmin -= (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1;
    if (xmin < 0)
        xmin = 0;

    found = nonwall = FALSE;
    for (xmax = COLNO - 1; !found && xmax >= 0; xmax--) {
        lev = &levl[xmax][0];
        for (y = 0; y <= ROWNO - 1; y++, lev++) {
            typ = lev->typ;
            if (typ != STONE) {
                found = TRUE;
                if (!IS_WALL(typ))
                    nonwall = TRUE;
            }
        }
    }
    xmax += (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1;
    if (xmax >= COLNO)
        xmax = COLNO - 1;

    found = nonwall = FALSE;
    for (ymin = 0; !found && ymin <= ROWNO; ymin++) {
        lev = &levl[xmin][ymin];
        for (x = xmin; x <= xmax; x++, lev += ROWNO) {
            typ = lev->typ;
            if (typ != STONE) {
                found = TRUE;
                if (!IS_WALL(typ))
                    nonwall = TRUE;
            }
        }
    }
    ymin -= (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1;

    found = nonwall = FALSE;
    for (ymax = ROWNO - 1; !found && ymax >= 0; ymax--) {
        lev = &levl[xmin][ymax];
        for (x = xmin; x <= xmax; x++, lev += ROWNO) {
            typ = lev->typ;
            if (typ != STONE) {
                found = TRUE;
                if (!IS_WALL(typ))
                    nonwall = TRUE;
            }
        }
    }
    ymax += (nonwall || !svl.level.flags.is_maze_lev) ? 2 : 1;

    *left = xmin;
    *right = xmax;
    *top = ymin;
    *bottom = ymax;
}

/* put a non-diggable/non-phaseable boundary around the initial portion
 * of a level map. assumes that no level will initially put things
 * beyond the isok() range.
 *
 * we can't bound unconditionally on the last line with something in it,
 * because that something might be a niche which was already reachable,
 * so the boundary would be breached
 *
 * we can't bound unconditionally on one beyond the last line, because
 * that provides a window of abuse for wallified special levels
 */
void
bound_digging(void)
{
    coordxy x, y;
    coordxy xmin, xmax, ymin, ymax;

    if (Is_earthlevel(&u.uz))
        return; /* everything diggable here */

    get_level_extends(&xmin, &ymin, &xmax, &ymax);

    for (x = 0; x < COLNO; x++)
        for (y = 0; y < ROWNO; y++)
            if (IS_STWALL(levl[x][y].typ)) {
                /* undiggable walls at edges, ... */
                if (y <= ymin || y >= ymax || x <= xmin || x >= xmax)
                    levl[x][y].wall_info |= W_NONDIGGABLE;
                /* one tile past that, everything is also unphaseable */
                if (y < ymin || y > ymax || x < xmin || x > xmax)
                    levl[x][y].wall_info |= W_NONPASSWALL;
            }
}

void
mkportal(coordxy x, coordxy y, xint16 todnum, xint16 todlevel)
{
    /* a portal "trap" must be matched by a
       portal in the destination dungeon/dlevel */
    struct trap *ttmp = maketrap(x, y, MAGIC_PORTAL);

    if (!ttmp) {
        impossible("portal on top of portal?");
        return;
    }
    debugpline4("mkportal: at <%d,%d>, to %s, level %d", x, y,
                svd.dungeons[todnum].dname, todlevel);
    ttmp->dst.dnum = todnum;
    ttmp->dst.dlevel = todlevel;
    return;
}

/* augment the Plane of Fire; called from goto_level() when arriving and
   moveloop_core() when on the level */
void
fumaroles(void)
{
    xint16 n, nmax = rn2(3);
    int sizemin = 5;
    boolean snd = FALSE, loud = FALSE;

    if (Is_firelevel(&u.uz)) {
        nmax++;
        sizemin += 5;
    }
    if (svl.level.flags.temperature > 0) {
        nmax++;
        sizemin += 5;
    }

    for (n = nmax; n; n--) {
        coordxy x = rn1(COLNO - 4, 3);
        coordxy y = rn1(ROWNO - 4, 3);

        if (levl[x][y].typ == LAVAPOOL) {
            NhRegion *r = create_gas_cloud(x, y, rn1(10, sizemin), rn1(10, 5));

            clear_heros_fault(r);
            snd = TRUE;
            if (distu(x, y) < 15)
                loud = TRUE;
        }
    }
    if (snd && !Deaf)
        Norep("You hear a %swhoosh!", loud ? "loud " : "");  /* Deaf-aware */
}

/*
 * Special waterlevel stuff in endgame (TH).
 *
 * Some of these functions would probably logically belong to some
 * other source files, but they are all so nicely encapsulated here.
 */

/* bubble movement boundaries */
#define gbxmin (svx.xmin + 1)
#define gbymin (svy.ymin + 1)
#define gbxmax (svx.xmax - 1)
#define gbymax (svy.ymax - 1)

/* the bubble hero is in */
static struct bubble *hero_bubble = NULL;

staticfn void set_wportal(void);
staticfn void mk_bubble(coordxy, coordxy, int);
staticfn void mv_bubble(struct bubble *, coordxy, coordxy, boolean);

/* augment the Planes of Water (for bubbles) and Air (for clouds); called
   from goto_level() when arriving and moveloop_core() when on the level */
void
movebubbles(void)
{
    static const struct rm water_pos = {
        cmap_b_to_glyph(S_water), WATER, 0, 0, 0, 0, 0, 0, 0, 0
    }, air_pos = {
        cmap_b_to_glyph(S_cloud), AIR, 0, 0, 0, 1, 0, 0, 0, 0
    };
    static boolean up = FALSE;
    struct bubble *b;
    struct container *cons;
    struct trap *btrap;
    coordxy x, y;
    int i, j, bcpin = 0;

    /* set up the portal the first time bubbles are moved */
    if (!gw.wportal)
        set_wportal();

    vision_recalc(2);

    hero_bubble = NULL;

    if (Is_waterlevel(&u.uz)) {
        /* keep attached ball&chain separate from bubble objects */
        if (Punished)
            bcpin = unplacebc_and_covet_placebc();

        /*
         * Pick up everything inside of a bubble then fill all bubble
         * locations.
         */
        for (b = up ? svb.bbubbles : ge.ebubbles; b;
             b = up ? b->next : b->prev) {
            if (b->cons)
                panic("movebubbles: cons != null");
            for (i = 0, x = b->x; i < (int) b->bm[0]; i++, x++)
                for (j = 0, y = b->y; j < (int) b->bm[1]; j++, y++)
                    if (b->bm[j + 2] & (1 << i)) {
                        if (!isok(x, y)) {
                            impossible("movebubbles: bad pos (%d,%d)", x, y);
                            continue;
                        }

                        /* pick up objects, monsters, hero, and traps */
                        if (OBJ_AT(x, y)) {
                            struct obj *olist = (struct obj *) 0, *otmp;

                            while ((otmp = svl.level.objects[x][y]) != 0) {
                                remove_object(otmp);
                                otmp->ox = otmp->oy = 0;
                                otmp->nexthere = olist;
                                olist = otmp;
                            }

                            cons = (struct container *) alloc(sizeof *cons);
                            cons->x = x;
                            cons->y = y;
                            cons->what = CONS_OBJ;
                            cons->list = (genericptr_t) olist;
                            cons->next = b->cons;
                            b->cons = cons;
                        }
                        if (MON_AT(x, y)) {
                            struct monst *mon = m_at(x, y);

                            cons = (struct container *) alloc(sizeof *cons);
                            cons->x = x;
                            cons->y = y;
                            cons->what = CONS_MON;
                            cons->list = (genericptr_t) mon;

                            cons->next = b->cons;
                            b->cons = cons;

                            if (mon->wormno)
                                remove_worm(mon);
                            else
                                remove_monster(x, y);

                            newsym(x, y); /* clean up old position */
                            mon->mx = mon->my = 0;
                            mon->mstate |= MON_BUBBLEMOVE;
                        }
                        if (!u.uswallow && u_at(x, y)) {
                            cons = (struct container *) alloc(sizeof *cons);
                            cons->x = x;
                            cons->y = y;
                            cons->what = CONS_HERO;
                            cons->list = (genericptr_t) 0;

                            cons->next = b->cons;
                            b->cons = cons;
                            hero_bubble = b;
                        }
                        if ((btrap = t_at(x, y)) != 0) {
                            cons = (struct container *) alloc(sizeof *cons);
                            cons->x = x;
                            cons->y = y;
                            cons->what = CONS_TRAP;
                            cons->list = (genericptr_t) btrap;

                            cons->next = b->cons;
                            b->cons = cons;
                        }

                        levl[x][y] = water_pos;
                        block_point(x, y);
                    }
        }
    } else if (Is_airlevel(&u.uz)) {
        boolean xedge, yedge;

        for (x = 1; x <= (COLNO - 1); x++)
            for (y = 0; y <= (ROWNO - 1); y++) {
                levl[x][y] = air_pos;
                recalc_block_point(x, y);
                /* all air or all cloud around the perimeter of the Air
                   level tends to look strange; break up the pattern */
                xedge = (boolean) (x < gbxmin || x > gbxmax);
                yedge = (boolean) (y < gbymin || y > gbymax);
                if (xedge || yedge) {
                    if (!rn2(xedge ? 3 : 5)) {
                        levl[x][y].typ = CLOUD;
                        block_point(x, y);
                    }
                }
            }
    }

    /*
     * Every second time traverse down.  This is because otherwise
     * all the junk that changes owners when bubbles overlap
     * would eventually end up in the last bubble in the chain.
     */
    up = !up;
    for (b = up ? svb.bbubbles : ge.ebubbles; b; b = up ? b->next : b->prev) {
        int rx = rn2(3), ry = rn2(3);

        mv_bubble(b, b->dx + 1 - (!b->dx ? rx : (rx ? 1 : 0)),
                  b->dy + 1 - (!b->dy ? ry : (ry ? 1 : 0)), FALSE);
    }

    /* put attached ball&chain back */
    if (Is_waterlevel(&u.uz) && Punished)
        lift_covet_and_placebc(bcpin);
    gv.vision_full_recalc = 1;
}

/* when moving in water, possibly (1 in 3) alter the intended destination */
void
water_friction(void)
{
    coordxy x, y, dx, dy;
    boolean eff = FALSE;

    if (Swimming && rn2(4))
        return; /* natural swimmers have advantage */

    if (u.dx && !rn2(!u.dy ? 3 : 6)) { /* 1/3 chance or half that */
        /* cancel delta x and choose an arbitrary delta y value */
        x = u.ux;
        do {
            dy = rn2(3) - 1; /* -1, 0, 1 */
            y = u.uy + dy;
        } while (dy && (!isok(x, y) || !is_pool(x, y)));
        u.dx = 0;
        u.dy = dy;
        eff = TRUE;
    } else if (u.dy && !rn2(!u.dx ? 3 : 5)) { /* 1/3 or 1/5*(5/6) */
        /* cancel delta y and choose an arbitrary delta x value */
        y = u.uy;
        do {
            dx = rn2(3) - 1; /* -1 .. 1 */
            x = u.ux + dx;
        } while (dx && (!isok(x, y) || !is_pool(x, y)));
        u.dy = 0;
        u.dx = dx;
        eff = TRUE;
    }
    if (eff)
        pline("Water turbulence affects your movements.");
}

void
save_waterlevel(NHFILE *nhfp)
{
    struct bubble *b;

    if (!svb.bbubbles)
        return;

    if (update_file(nhfp)) {
        int n = 0;
        for (b = svb.bbubbles; b; b = b->next)
            ++n;
        Sfo_int(nhfp, &n, "waterlevel-bubble_count");
        Sfo_int(nhfp, &svx.xmin, "waterlevel-xmin");
        Sfo_int(nhfp, &svy.ymin, "waterlevel-ymin");
        Sfo_int(nhfp, &svx.xmax, "waterlevel-xmax");
        Sfo_int(nhfp, &svy.ymax, "waterlevel-ymax");
        for (b = svb.bbubbles; b; b = b->next) {
            Sfo_bubble(nhfp, b, "waterlevel-bubble");
        }
    }
    if (release_data(nhfp))
        unsetup_waterlevel();
}
#endif /* !SFCTOOL */

/* restoring air bubbles on Plane of Water or clouds on Plane of Air */
void
restore_waterlevel(NHFILE *nhfp)
{
    struct bubble *b = (struct bubble *) 0, *btmp;
    int i, n = 0;

#ifdef SFCTOOL
    svb.bbubbles = (struct bubble *) 0;
    /* set_wportal(); */
#endif

    Sfi_int(nhfp, &n, "waterlevel-bubble_count");
    Sfi_int(nhfp, &svx.xmin, "waterlevel-xmin");
    Sfi_int(nhfp, &svy.ymin, "waterlevel-ymin");
    Sfi_int(nhfp, &svx.xmax, "waterlevel-xmax");
    Sfi_int(nhfp, &svy.ymax, "waterlevel-ymax");
    for (i = 0; i < n; i++) {
        btmp = b;
        b = (struct bubble *) alloc((unsigned) sizeof *b);
        Sfi_bubble(nhfp, b, "waterlevel-bubble");
        if (btmp) {
            btmp->next = b;
            b->prev = btmp;
        } else {
            svb.bbubbles = b;
            b->prev = (struct bubble *) 0;
        }
#ifndef SFCTOOL
        mv_bubble(b, 0, 0, TRUE);
#endif
    }
#ifndef SFCTOOL
    ge.ebubbles = b;
    if (b) {
        b->next = (struct bubble *) 0;
    } else {
        /* avoid "saving and reloading may fix this" */
        program_state.something_worth_saving = 0;
        /* during restore, information about what level this is might not
           be available so we're wishy-washy about what we describe */
        impossible("No %s to restore?",
                   (Is_waterlevel(&u.uz) || Is_waterlevel(&gu.uz_save))
                   ? "air bubbles"
                   : (Is_airlevel(&u.uz) || Is_airlevel(&gu.uz_save))
                     ? "clouds"
                     : "air bubbles or clouds");
        program_state.something_worth_saving = 1;
    }
#endif
}

#ifndef SFCTOOL
staticfn void
set_wportal(void)
{
    /* there better be only one magic portal on water level... */
    for (gw.wportal = gf.ftrap; gw.wportal; gw.wportal = gw.wportal->ntrap)
        if (gw.wportal->ttyp == MAGIC_PORTAL)
            return;
    impossible("set_wportal(): no portal!");
}

staticfn void
setup_waterlevel(void)
{
    int typ, glyph;
    coordxy x, y, xskip, yskip;

    if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz))
        panic("setup_waterlevel(): [%d:%d] neither 'Water' nor 'Air'",
              (int) u.uz.dnum, (int) u.uz.dlevel);

    /* ouch, hardcoded... (file scope statics and used in bxmin,bymax,&c) */
    svx.xmin = 3;
    svy.ymin = 1;
    /* use separate statements so that compiler won't complain about min()
       comparing two constants; the alternative is to do this in the
       preprocessor: #if (20 > ROWNO-1) ymax=ROWNO-1 #else ymax=20 #endif */
    svx.xmax = 78;
    svx.xmax = min(svx.xmax, (COLNO - 1) - 1);
    svy.ymax = 20;
    svy.ymax = min(svy.ymax, (ROWNO - 1));

    /* entire level is remembered as one glyph and any unspecified portion
       should default to level's base element rather than to usual stone */
    glyph = cmap_to_glyph(Is_waterlevel(&u.uz) ? S_water : S_air);
    typ = Is_waterlevel(&u.uz) ? WATER : AIR;

    /* set unspecified terrain (stone) and hero's memory to water or air */
    for (x = 1; x <= COLNO - 1; x++)
        for (y = 0; y <= ROWNO - 1; y++) {
            levl[x][y].glyph = glyph;
            if (levl[x][y].typ == STONE)
                levl[x][y].typ = typ;
        }

    /* make bubbles */
    if (Is_waterlevel(&u.uz)) {
        xskip = 10 + rn2(10);
        yskip = 4 + rn2(4);
    } else {
        xskip = 6 + rn2(4);
        yskip = 3 + rn2(3);
    }

    for (x = gbxmin; x <= gbxmax; x += xskip)
        for (y = gbymin; y <= gbymax; y += yskip)
            mk_bubble(x, y, rn2(7));
}

staticfn void
unsetup_waterlevel(void)
{
    struct bubble *b, *bb;

    /* free bubbles */
    for (b = svb.bbubbles; b; b = bb) {
        bb = b->next;
        free((genericptr_t) b);
    }
    svb.bbubbles = ge.ebubbles = (struct bubble *) 0;
}

staticfn void
mk_bubble(coordxy x, coordxy y, int n)
{
    /*
     * These bit masks make visually pleasing bubbles on a normal aspect
     * 25x80 terminal, which naturally results in them being mathematically
     * anything but symmetric.  For this reason they cannot be computed
     * in situ, either.  The first two elements tell the dimensions of
     * the bubble's bounding box.
     */
    static const uchar
        bm2[] = { 2, 1, 0x3 },
        bm3[] = { 3, 2, 0x7, 0x7 },
        bm4[] = { 4, 3, 0x6, 0xf, 0x6 },
        bm5[] = { 5, 3, 0xe, 0x1f, 0xe },
        bm6[] = { 6, 4, 0x1e, 0x3f, 0x3f, 0x1e },
        bm7[] = { 7, 4, 0x3e, 0x7f, 0x7f, 0x3e },
        bm8[] = { 8, 4, 0x7e, 0xff, 0xff, 0x7e },
        *const bmask[] = { bm2, bm3, bm4, bm5, bm6, bm7, bm8 };
    struct bubble *b;

    if (x >= gbxmax || y >= gbymax)
        return;
    if (n >= SIZE(bmask)) {
        impossible("n too large (mk_bubble)");
        n = SIZE(bmask) - 1;
    }
    if (bmask[n][1] > MAX_BMASK) {
        panic("bmask size is larger than MAX_BMASK");
    }
    b = (struct bubble *) alloc(sizeof *b);
    if ((x + (int) bmask[n][0] - 1) > gbxmax)
        x = gbxmax - bmask[n][0] + 1;
    if ((y + (int) bmask[n][1] - 1) > gbymax)
        y = gbymax - bmask[n][1] + 1;
    b->x = x;
    b->y = y;
    b->dx = 1 - rn2(3);
    b->dy = 1 - rn2(3);
    /* y dimension is the length of bitmap data - see bmask above */
    (void) memcpy((genericptr_t) b->bm, (genericptr_t) bmask[n],
                  (bmask[n][1] + 2) * sizeof (b->bm[0]));
    b->cons = 0;
    if (!svb.bbubbles)
        svb.bbubbles = b;
    if (ge.ebubbles) {
        ge.ebubbles->next = b;
        b->prev = ge.ebubbles;
    } else
        b->prev = (struct bubble *) 0;
    b->next = (struct bubble *) 0;
    ge.ebubbles = b;
    mv_bubble(b, 0, 0, TRUE);
}

/* maybe change the movement direction of the bubble hero is in */
void
maybe_adjust_hero_bubble(void)
{
    if (!Is_waterlevel(&u.uz))
        return;

    if (!u.dx && !u.dy)
        return;

    if (hero_bubble && !rn2(2)) {
        hero_bubble->dx = u.dx;
        hero_bubble->dy = u.dy;
    }
}

/*
 * The player, the portal and all other objects and monsters
 * float along with their associated bubbles.  Bubbles may overlap
 * freely, and the contents may get associated with other bubbles in
 * the process.  Bubbles are "sticky", meaning that if the player is
 * in the immediate neighborhood of one, he/she may get sucked inside.
 * This property also makes leaving a bubble slightly difficult.
 */
staticfn void
mv_bubble(struct bubble *b, coordxy dx, coordxy dy, boolean ini)
{
    int i, j, colli = 0;
    coordxy x, y;
    struct container *cons, *ctemp;

    /* clouds move slowly */
    if (!Is_airlevel(&u.uz) || !rn2(6)) {
        /* move bubble */
        if (dx < -1 || dx > 1 || dy < -1 || dy > 1) {
            /* pline("mv_bubble: dx = %d, dy = %d", dx, dy); */
            dx = sgn(dx);
            dy = sgn(dy);
        }

        /*
         * collision with level borders?
         *      1 = horizontal border, 2 = vertical, 3 = corner
         */
        if (b->x <= gbxmin)
            colli |= 2;
        if (b->y <= gbymin)
            colli |= 1;
        if ((int) (b->x + b->bm[0] - 1) >= gbxmax)
            colli |= 2;
        if ((int) (b->y + b->bm[1] - 1) >= gbymax)
            colli |= 1;

        if (b->x < gbxmin) {
            pline("bubble xmin: x = %d, xmin = %d", b->x, gbxmin);
            b->x = gbxmin;
        }
        if (b->y < gbymin) {
            pline("bubble ymin: y = %d, ymin = %d", b->y, gbymin);
            b->y = gbymin;
        }
        if ((int) (b->x + b->bm[0] - 1) > gbxmax) {
            pline("bubble xmax: x = %d, xmax = %d", b->x + b->bm[0] - 1,
                  gbxmax);
            b->x = gbxmax - b->bm[0] + 1;
        }
        if ((int) (b->y + b->bm[1] - 1) > gbymax) {
            pline("bubble ymax: y = %d, ymax = %d", b->y + b->bm[1] - 1,
                  gbymax);
            b->y = gbymax - b->bm[1] + 1;
        }

        /* bounce if we're trying to move off the border */
        if (b->x == gbxmin && dx < 0)
            dx = -dx;
        if (b->x + b->bm[0] - 1 == gbxmax && dx > 0)
            dx = -dx;
        if (b->y == gbymin && dy < 0)
            dy = -dy;
        if (b->y + b->bm[1] - 1 == gbymax && dy > 0)
            dy = -dy;

        b->x += dx;
        b->y += dy;
    }

    /* draw the bubbles */
    for (i = 0, x = b->x; i < (int) b->bm[0]; i++, x++)
        for (j = 0, y = b->y; j < (int) b->bm[1]; j++, y++)
            if (b->bm[j + 2] & (1 << i)) {
                if (Is_waterlevel(&u.uz)) {
                    levl[x][y].typ = AIR;
                    levl[x][y].lit = 1;
                    unblock_point(x, y);
                } else if (Is_airlevel(&u.uz)) {
                    levl[x][y].typ = CLOUD;
                    levl[x][y].lit = 1;
                    block_point(x, y);
                }
            }

    if (Is_waterlevel(&u.uz)) {
        /* replace contents of bubble */
        for (cons = b->cons; cons; cons = ctemp) {
            ctemp = cons->next;
            cons->x += dx;
            cons->y += dy;

            switch (cons->what) {
            case CONS_OBJ: {
                struct obj *olist, *otmp;

                for (olist = (struct obj *) cons->list; olist; olist = otmp) {
                    otmp = olist->nexthere;
                    place_object(olist, cons->x, cons->y);
                    stackobj(olist);
                }
                break;
            }

            case CONS_MON: {
                struct monst *mon = (struct monst *) cons->list;

                /* mnearto() might fail. We can jump right to elemental_clog
                   from here rather than deal_with_overcrowding() */
                if (!mnearto(mon, cons->x, cons->y, TRUE, RLOC_NOMSG))
                    elemental_clog(mon);
                break;
            }

            case CONS_HERO: {
                struct monst *mtmp = m_at(cons->x, cons->y);
                int ux0 = u.ux, uy0 = u.uy;

                u_on_newpos(cons->x, cons->y);
                newsym(ux0, uy0); /* clean up old position */

                if (mtmp) {
                    mnexto(mtmp, RLOC_NOMSG);
                }
                break;
            }

            case CONS_TRAP: {
                struct trap *btrap = (struct trap *) cons->list;

                btrap->tx = cons->x;
                btrap->ty = cons->y;
                break;
            }

            default:
                impossible("mv_bubble: unknown bubble contents");
                break;
            }
            free((genericptr_t) cons);
        }
        b->cons = 0;
    }

    /* boing? */
    switch (colli) {
    case 1:
        b->dy = -b->dy;
        break;
    case 3:
        b->dy = -b->dy;
        FALLTHROUGH;
        /*FALLTHRU*/
    case 2:
        b->dx = -b->dx;
        break;
    default:
        /* sometimes alter direction for fun anyway
           (higher probability for stationary bubbles) */
        if (!ini && ((b->dx || b->dy) ? !rn2(20) : !rn2(5))) {
            b->dx = 1 - rn2(3);
            b->dy = 1 - rn2(3);
        }
    }
}
#endif /* !SFCTOOL */

/*mkmaze.c*/
