/* NetHack 3.7	display.c	$NHDT-Date: 1682758082 2023/04/29 08:48:02 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.216 $ */
/* Copyright (c) Dean Luick, with acknowledgements to Kevin Darcy */
/* and Dave Cohrs, 1990.                                          */
/* NetHack may be freely redistributed.  See license for details. */

/*
 *                      THE NEW DISPLAY CODE
 *
 * The old display code has been broken up into three parts: vision, display,
 * and drawing.  Vision decides what locations can and cannot be physically
 * seen by the hero.  Display decides _what_ is displayed at a given location.
 * Drawing decides _how_ to draw a monster, fountain, sword, etc.
 *
 * The display system uses information from the vision system to decide
 * what to draw at a given location.  The routines for the vision system
 * can be found in vision.c and vision.h.  The routines for display can
 * be found in this file (display.c) and display.h.  The drawing routines
 * are part of the window port.  See doc/window.txt for the drawing
 * interface.
 *
 * The display system deals with an abstraction called a glyph.  Anything
 * that could possibly be displayed has a unique glyph identifier.
 *
 * What is seen on the screen is a combination of what the hero remembers
 * and what the hero currently sees.  Objects and dungeon features (walls
 * doors, etc) are remembered when out of sight.  Monsters and temporary
 * effects are not remembered.  Each location on the level has an
 * associated glyph.  This is the hero's _memory_ of what he or she has
 * seen there before.
 *
 * Display rules:
 *
 *      If the location is in sight, display in order:
 *              visible (or sensed) monsters
 *              visible objects
 *              known traps
 *              background
 *
 *      If the location is out of sight, display in order:
 *              sensed monsters (via telepathy or persistent detection)
 *              warning (partly-sensed monster shown as an abstraction)
 *              memory
 *
 *      "Remembered, unseen monster" is handled like an object rather
 *      than a monster, and stays displayed whether or not it is in sight.
 *      It is removed when a visible or sensed or warned-of monster gets
 *      shown at its location or when searching or fighting reveals that
 *      no monster is there.
 *
 *
 * Here is a list of the major routines in this file to be used externally:
 *
 * newsym
 *
 * Possibly update the screen location (x,y).  This is the workhorse routine.
 * It is always correct --- where correct means following the in-sight/out-
 * of-sight rules.  **Most of the code should use this routine.**  This
 * routine updates the map and displays monsters.
 *
 *
 * map_background
 * map_object
 * map_trap or map_engraving
 * map_invisible
 * unmap_object
 *
 * If you absolutely must override the in-sight/out-of-sight rules, there
 * are two possibilities.  First, you can mess with vision to force the
 * location in sight then use newsym(), or you can  use the map_* routines.
 * The first has not been tried [no need] and the second is used in the
 * detect routines --- detect object, magic mapping, etc.  The map_*
 * routines *change* what the hero remembers.  All changes made by these
 * routines will be sticky --- they will survive screen redraws.  Do *not*
 * use these for things that only temporarily change the screen.  These
 * routines are also used directly by newsym().  unmap_object is used to
 * clear a remembered object when/if detection reveals it isn't there.
 *
 *
 * show_glyph
 *
 * This is direct (no processing in between) buffered access to the screen.
 * Temporary screen effects are run through this and its companion,
 * flush_screen().  There is yet a lower level routine, print_glyph(),
 * but this is unbuffered and graphic dependent (i.e. it must be surrounded
 * by graphic set-up and tear-down routines).  Do not use print_glyph().
 *
 *
 * see_monsters
 * see_objects
 * see_traps
 *
 * These are only used when something affects all of the monsters or
 * objects or traps.  For objects and traps, the only thing is hallucination.
 * For monsters, there are hallucination and changing from/to blindness, etc.
 *
 *
 * tmp_at
 *
 * This is a useful interface for displaying temporary items on the screen.
 * Its interface is different than previously, so look at it carefully.
 *
 *
 *
 * Parts of the rm structure that are used:
 *
 *      typ     - What is really there.
 *      glyph   - What the hero remembers.  This will never be a monster.
 *                Monsters "float" above this.
 *      lit     - True if the position is lit.  An optimization for
 *                lit/unlit rooms.
 *      waslit  - True if the position was *remembered* as lit.
 *      seenv   - A vector of bits representing the directions from which the
 *                hero has seen this position.  The vector's primary use is
 *                determining how walls are seen.  E.g. a wall sometimes looks
 *                like stone on one side, but is seen as wall from the other.
 *                Other uses are for unmapping detected objects and felt
 *                locations, where we need to know if the hero has ever
 *                seen the location.
 *      flags   - Additional information for the typ field.  Different for
 *                each typ.
 *      horizontal - Indicates whether the wall or door is horizontal or
 *                vertical.
 */
#include "hack.h"

static void show_mon_or_warn(coordxy, coordxy, int);
static void display_monster(coordxy, coordxy, struct monst *, int, boolean);
static int swallow_to_glyph(int, int);
static void display_warning(struct monst *);

static int check_pos(coordxy, coordxy, int);
static void get_bkglyph_and_framecolor(coordxy x, coordxy y, int *, uint32 *);
static int tether_glyph(coordxy, coordxy);
static void mimic_light_blocking(struct monst *);
#ifdef UNBUFFERED_GLYPHINFO
static glyph_info *glyphinfo_at(coordxy, coordxy, int);
#endif

/*#define WA_VERBOSE*/ /* give (x,y) locations for all "bad" spots */
#ifdef WA_VERBOSE
static boolean more_than_one(coordxy, coordxy, coordxy, coordxy, coordxy);
#endif

static int set_twall(coordxy, coordxy, coordxy, coordxy,
                     coordxy, coordxy, coordxy, coordxy);
static int set_wall(coordxy, coordxy, int);
static int set_corn(coordxy, coordxy, coordxy, coordxy,
                    coordxy, coordxy, coordxy, coordxy);
static int set_crosswall(coordxy, coordxy);
static void set_seenv(struct rm *, coordxy, coordxy, coordxy, coordxy);
static void t_warn(struct rm *);
static int wall_angle(struct rm *);

#define remember_topology(x, y) (gl.lastseentyp[x][y] = levl[x][y].typ)
#define _glyph_at(x, y) gg.gbuf[y][x].glyphinfo.glyph

/*
 *      See display.h for descriptions of tp_sensemon() through
 *      is_safemon().  Some of these were generating an awful lot of
 *      code "behind the curtain", particularly canspotmon() (which is
 *      still a macro but one that now expands to a pair of function
 *      calls rather than to a ton of special case checks).  Return
 *      values are all int 0 or 1, not boolean.
 *
 *      They're still implemented as macros within this file.
 */
int
tp_sensemon(struct monst *mon)
{
    return _tp_sensemon(mon);
}
#define tp_sensemon(mon) _tp_sensemon(mon)

int
sensemon(struct monst *mon)
{
    return _sensemon(mon);
}
#define sensemon(mon) _sensemon(mon)

int
mon_warning(struct monst *mon)
{
    return _mon_warning(mon);
}
#define mon_warning(mon) _mon_warning(mon)

int
mon_visible(struct monst *mon)
{
    return _mon_visible(mon);
}
#define mon_visible(mon) _mon_visible(mon)

int
see_with_infrared(struct monst *mon)
{
    return _see_with_infrared(mon);
}
#define see_with_infrared(mon) _see_with_infrared(mon)

int
canseemon(struct monst *mon)
{
    return _canseemon(mon);
}
#define canseemon(mon) _canseemon(mon)

int
knowninvisible(struct monst *mon)
{
    return _knowninvisible(mon);
}
/* #define knowninvisible() isn't useful here */

int
is_safemon(struct monst *mon)
{
    return _is_safemon(mon);
}
/* #define is_safemon() isn't useful here */

/*
 *      End of former macro-only vision related (mostly) routines
 *      converted to functions.
 */

/*
 * magic_map_background()
 *
 * This function is similar to map_background (see below) except we pay
 * attention to and correct unexplored, lit ROOM and CORR spots.
 */
void
magic_map_background(coordxy x, coordxy y, int show)
{
    int glyph = back_to_glyph(x, y); /* assumes hero can see x,y */
    struct rm *lev = &levl[x][y];

    /*
     * Correct for out of sight lit corridors and rooms that the hero
     * doesn't remember as lit.
     */
    if (!cansee(x, y) && !lev->waslit) {
        /* Floor spaces are dark if unlit.  Corridors are dark if unlit. */
        if (lev->typ == ROOM && glyph == cmap_to_glyph(S_room))
            glyph = (flags.dark_room && iflags.use_color)
                        ? cmap_to_glyph(DARKROOMSYM)
                        : GLYPH_NOTHING;
        else if (lev->typ == CORR && glyph == cmap_to_glyph(S_litcorr))
            glyph = cmap_to_glyph(S_corr);
    }
    if (gl.level.flags.hero_memory)
        lev->glyph = glyph;
    if (show)
        show_glyph(x, y, glyph);

    remember_topology(x, y);
}

/*
 * The routines map_background(), map_object(), and map_trap() could just
 * as easily be:
 *
 *      map_glyph(x,y,glyph,show)
 *
 * Which is called with the xx_to_glyph() in the call.  Then I can get
 * rid of 3 routines that don't do very much anyway.  And then stop
 * having to create fake objects and traps.  However, I am reluctant to
 * make this change.
 */

/*
 * map_background()
 *
 * Make the real background part of our map.  This routine assumes that
 * the hero can physically see the location.  Update the screen if directed.
 */
void
map_background(register coordxy x, register coordxy y, register int show)
{
    register int glyph = back_to_glyph(x, y);

    if (gl.level.flags.hero_memory)
        levl[x][y].glyph = glyph;
    if (show)
        show_glyph(x, y, glyph);
}

/*
 * map_trap()
 *
 * Map the trap and print it out if directed.  This routine assumes that the
 * hero can physically see the location.
 */
void
map_trap(register struct trap *trap, register int show)
{
    register coordxy x = trap->tx, y = trap->ty;
    register int glyph = trap_to_glyph(trap);

    if (gl.level.flags.hero_memory)
        levl[x][y].glyph = glyph;
    if (show)
        show_glyph(x, y, glyph);
}

/*
 * map_engraving()
 *
 * Map the engraving and print it out if directed.
 */
void
map_engraving(struct engr *ep, register int show)
{
    coordxy x = ep->engr_x, y = ep->engr_y;
    int glyph = engraving_to_glyph(ep);

    if (gl.level.flags.hero_memory)
        levl[x][y].glyph = glyph;
    if (show)
        show_glyph(x, y, glyph);
}

/*
 * map_object()
 *
 * Map the given object.  This routine assumes that the hero can physically
 * see the location of the object.  Update the screen if directed.
 * [Note: feel_location() -> map_location() -> map_object() contradicts
 * the claim here that the hero can see obj's <ox,oy>.]
 */
void
map_object(register struct obj *obj, int show)
{
    register coordxy x = obj->ox, y = obj->oy;
    register int glyph = obj_to_glyph(obj, newsym_rn2);

    /* if this object is already displayed as a generic object, it might
       become a specific one now */
    if (glyph_is_generic_object(glyph) && cansee(x, y) && !Hallucination) {
        /* these 'r' and 'neardist' calculations match distant_name(objnam.c)
           and see_nearby_objects(below); we assume that this is a lone
           object or a pile-top, not something below the top of a pile */
        int r = (u.xray_range > 2) ? u.xray_range : 2,
            /* neardist produces a small square with rounded corners */
            neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */

        if (distu(x, y) <= neardist) {
            obj->dknown = 1;
            glyph = obj_to_glyph(obj, newsym_rn2);
        }
    }

    if (gl.level.flags.hero_memory) {
        /* MRKR: While hallucinating, statues are seen as random monsters */
        /*       but remembered as random objects.                        */

        if (Hallucination && obj->otyp == STATUE) {
            levl[x][y].glyph = random_obj_to_glyph(newsym_rn2);
        } else {
            levl[x][y].glyph = glyph;
        }
    }
    if (show)
        show_glyph(x, y, glyph);
}

/*
 * map_invisible()
 *
 * Make the hero remember that a square contains an invisible monster.
 * This is a special case in that the square will continue to be displayed
 * this way even when the hero is close enough to see it.  To get rid of
 * this and display the square's actual contents, use unmap_object() followed
 * by newsym() if necessary.
 */
void
map_invisible(register coordxy x, register coordxy y)
{
    if (x != u.ux || y != u.uy) { /* don't display I at hero's location */
        if (gl.level.flags.hero_memory)
            levl[x][y].glyph = GLYPH_INVISIBLE;
        show_glyph(x, y, GLYPH_INVISIBLE);
    }
}

boolean
unmap_invisible(coordxy x, coordxy y)
{
    if (isok(x,y) && glyph_is_invisible(levl[x][y].glyph)) {
        unmap_object(x, y);
        newsym(x, y);
        return TRUE;
    }
    return FALSE;
}


/*
 * unmap_object()
 *
 * Remove something from the map when the hero realizes it's not there
 * anymore.  Replace it with background or known trap, but not with
 * any other remembered object.  If this is used for detection, a full
 * screen update is imminent anyway; if this is used to get rid of an
 * invisible monster notation, we might have to call newsym().
 */
void
unmap_object(register coordxy x, register coordxy y)
{
    register struct trap *trap;
    struct engr *ep;

    if (!gl.level.flags.hero_memory)
        return;

    if ((trap = t_at(x, y)) != 0 && trap->tseen && !covers_traps(x, y)) {
        map_trap(trap, 0);
    } else if (levl[x][y].seenv) {
        struct rm *lev = &levl[x][y];

        if (spot_shows_engravings(x, y)
               && (ep = engr_at(x, y)) != 0 && !covers_traps(x, y))
            map_engraving(ep, 0);
        else
            map_background(x, y, 0);

        /* turn remembered dark room squares dark */
        if (!lev->waslit && lev->glyph == cmap_to_glyph(S_room)
            && lev->typ == ROOM)
            lev->glyph = cmap_to_glyph(S_stone);
    } else {
        levl[x][y].glyph = cmap_to_glyph(S_stone); /* default val */
    }
}

/*
 * map_location()
 *
 * Make whatever at this location show up.  This is only for non-living
 * things.  This will not handle feeling invisible objects correctly.
 *
 * Internal to display.c, this is a #define for speed.
 */
#define _map_location(x, y, show) \
    {                                                                       \
        register struct obj *obj;                                           \
        register struct trap *trap;                                         \
        struct engr *ep;                                                    \
                                                                            \
        if ((obj = vobj_at(x, y)) && !covers_objects(x, y))                 \
            map_object(obj, show);                                          \
        else if ((trap = t_at(x, y)) && trap->tseen && !covers_traps(x, y)) \
            map_trap(trap, show);                                           \
        else if (spot_shows_engravings(x, y)                                \
                 && (ep = engr_at(x, y)) != 0                               \
                 && !covers_traps(x, y))                                    \
            map_engraving(ep, show);                                        \
        else                                                                \
            map_background(x, y, show);                                     \
                                                                            \
        remember_topology(x, y);                                            \
    }

void
map_location(coordxy x, coordxy y, int show)
{
    _map_location(x, y, show);
}

/* display something on monster layer; may need to fixup object layer */
static void
show_mon_or_warn(coordxy x, coordxy y, int monglyph)
{
    struct obj *o;

    /* "remembered, unseen monster" is tracked by object layer so if we're
       putting something on monster layer at same spot, stop remembering
       that; if an object is in view there, start remembering it instead */
    if (glyph_is_invisible(levl[x][y].glyph)) {
        unmap_object(x, y);
        if (cansee(x, y) && (o = vobj_at(x, y)) != 0)
            map_object(o, FALSE);
    }

    show_glyph(x, y, monglyph);
}

#define DETECTED 2
#define PHYSICALLY_SEEN 1
#define is_worm_tail(mon) ((mon) && ((x != (mon)->mx) || (y != (mon)->my)))

/*
 * display_monster()
 *
 * Note that this is *not* a map_XXXX() function!  Monsters sort of float
 * above everything.
 *
 * Yuck.  Display body parts by recognizing that the display position is
 * not the same as the monster position.  Currently the only body part is
 * a worm tail.
 *
 */
static void
display_monster(
    coordxy x, coordxy y,   /* display position */
    struct monst *mon,      /* monster to display */
    int sightflags,         /* 1 if the monster is physically seen;
                             * 2 if detected using Detect_monsters */
    boolean worm_tail)      /* mon is actually a worm tail */
{
    boolean mon_mimic = (M_AP_TYPE(mon) != M_AP_NOTHING);
    int sensed = (mon_mimic && (Protection_from_shape_changers
                                || sensemon(mon))),
        mgendercode = mon->female ? FEMALE : MALE;

    /*
     * We must do the mimic check first.  If the mimic is mimicing something,
     * and the location is in sight, we have to change the hero's memory
     * so that when the position is out of sight, the hero remembers what
     * the mimic was mimicing.
     */
    if (mon_mimic && (sightflags == PHYSICALLY_SEEN)) {
        switch (M_AP_TYPE(mon)) {
        default:
            impossible("display_monster:  bad m_ap_type value [ = %d ]",
                       (int) mon->m_ap_type);
            /*FALLTHRU*/
        case M_AP_NOTHING:
            show_glyph(x, y, mon_to_glyph(mon, newsym_rn2));
            break;

        case M_AP_FURNITURE: {
            /*
             * This is a poor man's version of map_background().  I can't
             * use map_background() because we are overriding what is in
             * the 'typ' field.  Maybe have map_background()'s parameters
             * be (x,y,glyph) instead of just (x,y).
             *
             * mappearance is currently set to an S_ index value in
             * makemon.c.
             */
            int sym = mon->mappearance, glyph = cmap_to_glyph(sym);

            levl[x][y].glyph = glyph;
            if (!sensed) {
                show_glyph(x, y, glyph);
                /* override real topology with mimic's fake one */
                gl.lastseentyp[x][y] = cmap_to_type(sym);
            }
            break;
        }

        case M_AP_OBJECT: {
            /* Make a fake object to send to map_object(). */
            struct obj obj;

            obj = cg.zeroobj;
            obj.ox = x;
            obj.oy = y;
            obj.otyp = mon->mappearance;
            /* might be mimicing a corpse or statue */
            obj.corpsenm = has_mcorpsenm(mon) ? MCORPSENM(mon) : PM_TENGU;
            map_object(&obj, !sensed);
            break;
        }

        case M_AP_MONSTER: {
            int mndx = what_mon((int) mon->mappearance, rn2_on_display_rng);

            show_glyph(x, y, monnum_to_glyph(mndx, mgendercode));
            break;
        } /* case M_AP_MONSTER */
        } /* switch M_AP_TYPE() */
    }

    /* If mimic is unsuccessfully mimicing something, display the monster. */
    if (!mon_mimic || sensed) {
        int num;

        /* [ALI] Only use detected glyphs when monster wouldn't be
         * visible by any other means.
         *
         * There are no glyphs for "detected pets" so we have to
         * decide whether to display such things as detected or as tame.
         * If both are being highlighted in the same way, it doesn't
         * matter, but if not, showing them as pets is preferrable.
         */
        if (mon->mtame && !Hallucination) {
            if (worm_tail)
                num = petnum_to_glyph(PM_LONG_WORM_TAIL, mgendercode);
            else
                num = pet_to_glyph(mon, rn2_on_display_rng);
        } else if (sightflags == DETECTED) {
            if (worm_tail)
                num = detected_monnum_to_glyph(what_mon(PM_LONG_WORM_TAIL,
                                                        rn2_on_display_rng),
                                               mgendercode);
            else
                num = detected_mon_to_glyph(mon, rn2_on_display_rng);
        } else {
            if (worm_tail)
                num = monnum_to_glyph(what_mon(PM_LONG_WORM_TAIL,
                                               rn2_on_display_rng),
                                      mgendercode);
            else
                num = mon_to_glyph(mon, rn2_on_display_rng);
        }
        show_mon_or_warn(x, y, num);
        mon->meverseen = 1;
    }
}

/*
 * display_warning()
 *
 * This is also *not* a map_XXXX() function!  Monster warnings float
 * above everything just like monsters do, but only if the monster
 * is not showing.
 *
 * Do not call for worm tails.
 */
static void
display_warning(struct monst *mon)
{
    coordxy x = mon->mx, y = mon->my;
    int glyph;

    if (mon_warning(mon)) {
        int wl = Hallucination ? rn2_on_display_rng(WARNCOUNT - 1) + 1
                               : warning_of(mon);

        glyph = warning_to_glyph(wl);
    } else if (MATCH_WARN_OF_MON(mon)) {
        glyph = mon_to_glyph(mon, rn2_on_display_rng);
    } else {
        impossible("display_warning did not match warning type?");
        return;
    }
    show_mon_or_warn(x, y, glyph);
}

int
warning_of(struct monst *mon)
{
    int wl = 0, tmp = 0;

    if (mon_warning(mon)) {
        tmp = (int) (mon->m_lev / 4);    /* match display.h */
        wl = (tmp > WARNCOUNT - 1) ? WARNCOUNT - 1 : tmp;
    }
    return wl;
}

/* map or status window might not be ready for output during level creation
   or game restoration (something like u.usteed which affects display of
   the hero and also a status condition might not be set up yet) */
boolean
suppress_map_output(void)
{
    if (gi.in_mklev || gp.program_state.saving || gp.program_state.restoring)
        return TRUE;
#ifdef HANGUPHANDLING
    if (gp.program_state.done_hup)
        return TRUE;
#endif
    return FALSE;
}

/*
 * feel_newsym()
 *
 * When hero knows what happened to location, even when blind.
 */
void
feel_newsym(coordxy x, coordxy y)
{
    if (Blind)
        feel_location(x, y);
    else
        newsym(x, y);
}


/*
 * feel_location()
 *
 * Feel the given location.  This assumes that the hero is blind and that
 * the given position is either the hero's or one of the eight squares
 * adjacent to the hero (except for a boulder push).
 * If an invisible monster has gone away, that will be discovered.  If an
 * invisible monster has appeared, this will _not_ be discovered since
 * searching only finds one monster per turn so we must check that separately.
 */
void
feel_location(coordxy x, coordxy y)
{
    struct rm *lev;
    struct obj *boulder;
    register struct monst *mon;

    /* replicate safeguards used by newsym(); might not be required here */
    if (suppress_map_output())
        return;

    if (!isok(x, y))
        return;
    lev = &(levl[x][y]);
    /* If hero's memory of an invisible monster is accurate, we want to keep
     * him from detecting the same monster over and over again on each turn.
     * We must return (so we don't erase the monster).  (We must also, in the
     * search function, be sure to skip over previously detected 'I's.)
     */
    if (glyph_is_invisible(lev->glyph) && m_at(x, y))
        return;

    /* The hero can't feel non pool locations while under water
       except for lava and ice. */
    if (Underwater && !Is_waterlevel(&u.uz)
        && !is_pool_or_lava(x, y) && !is_ice(x, y))
        return;

    /* Set the seen vector as if the hero had seen it.
       It doesn't matter if the hero is levitating or not. */
    set_seenv(lev, u.ux, u.uy, x, y);

    if (!can_reach_floor(FALSE)) {
        /*
         * Levitation Rules.  It is assumed that the hero can feel the state
         * of the walls around herself and can tell if she is in a corridor,
         * room, or doorway.  Boulders are felt because they are large enough.
         * Anything else is unknown because the hero can't reach the ground.
         * This makes things difficult.
         *
         * Check (and display) in order:
         *
         *      + Stone, walls, and closed doors.
         *      + Boulders.  [see a boulder before a doorway]
         *      + doors.
         *      + Room/water positions
         *      + Everything else (hallways!)
         */
        if (IS_ROCK(lev->typ)
            || (IS_DOOR(lev->typ)
                && (lev->doormask & (D_LOCKED | D_CLOSED)))) {
            map_background(x, y, 1);
        } else if ((boulder = sobj_at(BOULDER, x, y)) != 0) {
            map_object(boulder, 1);
        } else if (IS_DOOR(lev->typ)) {
            map_background(x, y, 1);
        } else if (IS_ROOM(lev->typ) || IS_POOL(lev->typ)) {
            boolean do_room_glyph;

            /*
             * An open room or water location.  Normally we wouldn't touch
             * this, but we have to get rid of remembered boulder symbols.
             * This will only occur in rare occasions when the hero goes
             * blind and doesn't find a boulder where expected (something
             * came along and picked it up).  We know that there is not a
             * boulder at this location.  Show fountains, pools, etc.
             * underneath if already seen.  Otherwise, show the appropriate
             * floor symbol.
             *
             * Similarly, if the hero digs a hole in a wall or feels a
             * location that used to contain an unseen monster.  In these
             * cases, there's no reason to assume anything was underneath,
             * so just show the appropriate floor symbol.  If something was
             * embedded in the wall, the glyph will probably already
             * reflect that.  Don't change the symbol in this case.
             *
             * This isn't quite correct.  If the boulder was on top of some
             * other objects they should be seen once the boulder is removed.
             * However, we have no way of knowing that what is there now
             * was there then.  So we let the hero have a lapse of memory.
             * We could also just display what is currently on the top of the
             * object stack (if anything).
             */
            do_room_glyph = FALSE;
            if (lev->glyph == objnum_to_glyph(BOULDER)
                || glyph_is_invisible(lev->glyph)) {
                if (lev->typ != ROOM && lev->seenv)
                    map_background(x, y, 1);
                else
                    do_room_glyph = TRUE;
            } else if (lev->glyph >= cmap_to_glyph(S_stone)
                       && lev->glyph < cmap_to_glyph(S_darkroom)) {
                do_room_glyph = TRUE;
            }
            if (do_room_glyph) {
                lev->glyph = (flags.dark_room && iflags.use_color
                              && !Is_rogue_level(&u.uz))
                                 ? cmap_to_glyph(S_darkroom)
                                 : (lev->waslit ? cmap_to_glyph(S_room)
                                                : cmap_to_glyph(S_stone));
                show_glyph(x, y, lev->glyph);
            }
        } else {
            /* We feel it (I think hallways are the only things left). */
            map_background(x, y, 1);
            /* Corridors are never felt as lit (unless remembered that way) */
            /* (lit_corridor only).                                         */
            if (lev->typ == CORR && lev->glyph == cmap_to_glyph(S_litcorr)
                && !lev->waslit)
                show_glyph(x, y, lev->glyph = cmap_to_glyph(S_corr));
            else if (lev->typ == ROOM && flags.dark_room && iflags.use_color
                     && lev->glyph == cmap_to_glyph(S_room))
                show_glyph(x, y, lev->glyph = cmap_to_glyph(S_darkroom));
        }
    } else {
        _map_location(x, y, 1);

        if (Punished) {
            /*
             * A ball or chain is only felt if it is first on the object
             * location list.  Otherwise, we need to clear the felt bit ---
             * something has been dropped on the ball/chain.  If the bit is
             * not cleared, then when the ball/chain is moved it will drop
             * the wrong glyph.
             *
             * Note: during unpunish() we can be called by delobj() when
             * destroying uchain while uball hasn't been cleared yet (so
             * Punished will still yield True but uchain might not be part
             * of the floor list anymore).
             */
            if (uchain && uchain->where == OBJ_FLOOR
                && uchain->ox == x && uchain->oy == y
                && gl.level.objects[x][y] == uchain)
                u.bc_felt |= BC_CHAIN;
            else
                u.bc_felt &= ~BC_CHAIN; /* do not feel the chain */

            if (uball && uball->where == OBJ_FLOOR
                && uball->ox == x && uball->oy == y
                && gl.level.objects[x][y] == uball)
                u.bc_felt |= BC_BALL;
            else
                u.bc_felt &= ~BC_BALL; /* do not feel the ball */
        }

        /* Floor spaces are dark if unlit.  Corridors are dark if unlit. */
        if (lev->typ == ROOM && lev->glyph == cmap_to_glyph(S_room)
            && (!lev->waslit || (flags.dark_room && iflags.use_color)))
            show_glyph(x, y, lev->glyph = cmap_to_glyph(
                                 flags.dark_room ? S_darkroom : S_stone));
        else if (lev->typ == CORR && lev->glyph == cmap_to_glyph(S_litcorr)
                 && !lev->waslit)
            show_glyph(x, y, lev->glyph = cmap_to_glyph(S_corr));
    }
    /* draw monster on top if we can sense it */
    if ((x != u.ux || y != u.uy) && (mon = m_at(x, y)) != 0 && sensemon(mon))
        display_monster(x, y, mon,
                        (tp_sensemon(mon) || MATCH_WARN_OF_MON(mon))
                            ? PHYSICALLY_SEEN
                            : DETECTED,
                        is_worm_tail(mon));
}

/*
 * newsym()
 *
 * Possibly put a new glyph at the given location.
 */
void
newsym(coordxy x, coordxy y)
{
    struct monst *mon;
    int see_it;
    boolean worm_tail;
    register struct rm *lev = &(levl[x][y]);

    /* don't try to produce map output when level is in a state of flux */
    if (suppress_map_output())
        return;

    /* only permit updating the hero when swallowed */
    if (u.uswallow) {
        if (u_at(x, y))
            display_self();
        return;
    }
    if (Underwater && !Is_waterlevel(&u.uz)) {
        /* when underwater, don't do anything unless <x,y> is an
           adjacent water or lava or ice position */
        if (!(is_pool_or_lava(x, y) || is_ice(x, y)) || !next2u(x, y))
            return;
    }

    /* Can physically see the location. */
    if (cansee(x, y)) {
        NhRegion *reg = visible_region_at(x, y);
        /*
         * Don't use templit here:  E.g.
         *
         *      lev->waslit = !!(lev->lit || templit(x,y));
         *
         * Otherwise we have the "light pool" problem, where non-permanently
         * lit areas just out of sight stay remembered as lit.  They should
         * re-darken.
         *
         * Perhaps ALL areas should revert to their "unlit" look when
         * out of sight.
         */
        lev->waslit = (lev->lit != 0); /* remember lit condition */

        mon = m_at(x, y);
        worm_tail = is_worm_tail(mon);

        /*
         * Normal region shown only on accessible positions, but
         * poison clouds and steam clouds also shown above lava,
         * pools and moats.
         * However, sensed monsters take precedence over all regions.
         */
        if (reg
            && (ACCESSIBLE(lev->typ)
                || (reg->visible && is_pool_or_lava(x, y)))
            && (!mon || worm_tail || !sensemon(mon))) {
            show_region(reg, x, y);
            return;
        }

        if (u_at(x, y)) {
            int see_self = canspotself();

            /* update map information for <u.ux,u.uy> (remembered topology
               and object/known trap/terrain glyph) but only display it if
               hero can't see him/herself, then show self if appropriate */
            _map_location(x, y, !see_self);
            if (see_self)
                display_self();
        } else {
            see_it = mon && (mon_visible(mon)
                             || (!worm_tail && (tp_sensemon(mon)
                                                || MATCH_WARN_OF_MON(mon))));
            if (mon && (see_it || (!worm_tail && Detect_monsters))) {
                if (mon->mtrapped) {
                    struct trap *trap = t_at(x, y);
                    int tt = trap ? trap->ttyp : NO_TRAP;

                    /* if monster is in a physical trap, you see trap too */
                    if (tt == BEAR_TRAP || is_pit(tt) || tt == WEB)
                        trap->tseen = 1;
                }
                _map_location(x, y, 0); /* map under the monster */
                /* also gets rid of any invisibility glyph */
                display_monster(x, y, mon,
                                see_it ? PHYSICALLY_SEEN : DETECTED,
                                worm_tail);
            } else if (mon && mon_warning(mon) && !is_worm_tail(mon)) {
                display_warning(mon);
            } else if (glyph_is_invisible(lev->glyph)) {
                map_invisible(x, y);
            } else
                _map_location(x, y, 1); /* map the location */
        }

    /* Can't see the location. */
    } else {
        if (u_at(x, y)) {
            feel_location(u.ux, u.uy); /* forces an update */

            if (canspotself())
                display_self();
        } else if ((mon = m_at(x, y)) != 0
                   && ((see_it = (tp_sensemon(mon) || MATCH_WARN_OF_MON(mon)
                                  || (see_with_infrared(mon)
                                      && mon_visible(mon)))) != 0
                       || Detect_monsters)) {
            /* Seen or sensed monsters are printed every time.
               This also gets rid of any invisibility glyph. */
            display_monster(x, y, mon, see_it ? 0 : DETECTED,
                            is_worm_tail(mon) ? TRUE : FALSE);
        } else if (mon && mon_warning(mon) && !is_worm_tail(mon)) {
            display_warning(mon);

        /*
         * If the location is remembered as being both dark (waslit is false)
         * and lit (glyph is a lit room or lit corridor) then it was either:
         *
         *      (1) A dark location that the hero could see through night
         *          vision.
         *      (2) Darkened while out of the hero's sight.  This can happen
         *          when cursed scroll of light is read.
         *
         * In either case, we have to manually correct the hero's memory to
         * match waslit.  Deciding when to change waslit is non-trivial.
         *
         *  Note:  If flags.lit_corridor is set, then corridors act like room
         *         squares.  That is, they light up if in night vision range.
         *         If flags.lit_corridor is not set, then corridors will
         *         remain dark unless lit by a light spell and may darken
         *         again, as discussed above.
         *
         * These checks and changes must be here and not in back_to_glyph().
         * They are dependent on the position being out of sight.
         */
        } else if (Is_rogue_level(&u.uz)) {
            if (lev->glyph == cmap_to_glyph(S_litcorr) && lev->typ == CORR)
                show_glyph(x, y, lev->glyph = cmap_to_glyph(S_corr));
            else if (lev->glyph == cmap_to_glyph(S_room) && lev->typ == ROOM
                     && !lev->waslit)
                show_glyph(x, y, lev->glyph = cmap_to_glyph(S_stone));
            else
                goto show_mem;
        } else if (!lev->waslit || (flags.dark_room && iflags.use_color)) {
            if (lev->glyph == cmap_to_glyph(S_litcorr) && lev->typ == CORR)
                show_glyph(x, y, lev->glyph = cmap_to_glyph(S_corr));
            else if (lev->glyph == cmap_to_glyph(S_room) && lev->typ == ROOM)
                show_glyph(x, y, lev->glyph = cmap_to_glyph(DARKROOMSYM));
            else
                goto show_mem;
        } else {
 show_mem:
            show_glyph(x, y, lev->glyph);
        }
    }
}

#undef is_worm_tail

/*
 * shieldeff()
 *
 * Put magic shield pyrotechnics at the given location.  This *could* be
 * pulled into a platform dependent routine for fancier graphics if desired.
 */
void
shieldeff(coordxy x, coordxy y)
{
    register int i;

    if (!flags.sparkle)
        return;
    if (cansee(x, y)) { /* Don't see anything if can't see the location */
        for (i = 0; i < SHIELD_COUNT; i++) {
            show_glyph(x, y, cmap_to_glyph(shield_static[i]));
            flush_screen(1); /* make sure the glyph shows up */
            nh_delay_output();
        }
        newsym(x, y); /* restore the old information */
    }
}

static int
tether_glyph(coordxy x, coordxy y)
{
    int tdx, tdy;
    tdx = u.ux - x;
    tdy = u.uy - y;
    return zapdir_to_glyph(sgn(tdx),sgn(tdy), 2);
}

/*
 * tmp_at()
 *
 * Temporarily place glyphs on the screen.  Do not call nh_delay_output().  It
 * is up to the caller to decide if it wants to wait [presently, everyone
 * but explode() wants to delay].
 *
 * Call:
 *      (DISP_BEAM,    glyph)   open, initialize glyph
 *      (DISP_FLASH,   glyph)   open, initialize glyph
 *      (DISP_ALWAYS,  glyph)   open, initialize glyph
 *      (DISP_CHANGE,  glyph)   change glyph
 *      (DISP_END,     0)       close & clean up (2nd argument doesn't matter)
 *      (DISP_FREEMEM, 0)       only used to prevent memory leak during exit)
 *      (x, y)                  display the glyph at the location
 *
 * DISP_BEAM   - Display the given glyph at each location, but do not erase
 *               any until the close call.
 * DISP_TETHER - Display a tether glyph at each location, and the tethered
 *               object at the farthest location, but do not erase any
 *               until the return trip or close.
 * DISP_FLASH  - Display the given glyph at each location, but erase the
 *               previous location's glyph.
 * DISP_ALWAYS - Like DISP_FLASH, but vision is not taken into account.
 */

#define TMP_AT_MAX_GLYPHS (COLNO * 2)

static struct tmp_glyph {
    coord saved[TMP_AT_MAX_GLYPHS]; /* previously updated positions */
    int sidx;                       /* index of next unused slot in saved[] */
    int style; /* either DISP_BEAM or DISP_FLASH or DISP_ALWAYS */
    int glyph; /* glyph to use when printing */
    struct tmp_glyph *prev;
} tgfirst;

void
tmp_at(coordxy x, coordxy y)
{
    static struct tmp_glyph *tglyph = (struct tmp_glyph *) 0;
    struct tmp_glyph *tmp;

    switch (x) {
    case DISP_BEAM:
    case DISP_ALL:
    case DISP_TETHER:
    case DISP_FLASH:
    case DISP_ALWAYS:
        if (!tglyph)
            tmp = &tgfirst;
        else /* nested effect; we need dynamic memory */
            tmp = (struct tmp_glyph *) alloc(sizeof *tmp);
        tmp->prev = tglyph;
        tglyph = tmp;
        tglyph->sidx = 0;
        tglyph->style = x;
        tglyph->glyph = y;
        flush_screen(0); /* flush buffered glyphs */
        return;

    case DISP_FREEMEM: /* in case game ends with tmp_at() in progress */
        while (tglyph) {
            tmp = tglyph->prev;
            if (tglyph != &tgfirst)
                free((genericptr_t) tglyph);
            tglyph = tmp;
        }
        return;

    default:
        break;
    }

    if (!tglyph) {
        panic("tmp_at: tglyph not initialized");
    } else {
        switch (x) {
        case DISP_CHANGE:
            tglyph->glyph = y;
            break;

        case DISP_END:
            if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) {
                register int i;

                /* Erase (reset) from source to end */
                for (i = 0; i < tglyph->sidx; i++)
                    newsym(tglyph->saved[i].x, tglyph->saved[i].y);
            } else if (tglyph->style == DISP_TETHER) {
                int i;

                if (y == BACKTRACK && tglyph->sidx > 1) {
                    /* backtrack */
                    for (i = tglyph->sidx - 1; i > 0; i--) {
                        newsym(tglyph->saved[i].x, tglyph->saved[i].y);
                        show_glyph(tglyph->saved[i - 1].x,
                                   tglyph->saved[i - 1].y, tglyph->glyph);
                        flush_screen(0); /* make sure it shows up */
                        nh_delay_output();
                    }
                    tglyph->sidx = 1;
                }
                for (i = 0; i < tglyph->sidx; i++)
                    newsym(tglyph->saved[i].x, tglyph->saved[i].y);
            } else {              /* DISP_FLASH or DISP_ALWAYS */
                if (tglyph->sidx) /* been called at least once */
                    newsym(tglyph->saved[0].x, tglyph->saved[0].y);
            }
            /* tglyph->sidx = 0; -- about to be freed, so not necessary */
            tmp = tglyph->prev;
            if (tglyph != &tgfirst)
                free((genericptr_t) tglyph);
            tglyph = tmp;
            break;

        default: /* do it */
            if (!isok(x, y))
                break;
            if (tglyph->style == DISP_BEAM || tglyph->style == DISP_ALL) {
                if (tglyph->style != DISP_ALL && !cansee(x, y))
                    break;
                if (tglyph->sidx >= TMP_AT_MAX_GLYPHS)
                    break; /* too many locations */
                /* save pos for later erasing */
                tglyph->saved[tglyph->sidx].x = x;
                tglyph->saved[tglyph->sidx].y = y;
                tglyph->sidx += 1;
            } else if (tglyph->style == DISP_TETHER) {
                if (tglyph->sidx >= TMP_AT_MAX_GLYPHS)
                    break; /* too many locations */
                if (tglyph->sidx) {
                    int px, py;

                    px = tglyph->saved[tglyph->sidx - 1].x;
                    py = tglyph->saved[tglyph->sidx - 1].y;
                    show_glyph(px, py, tether_glyph(px, py));
                }
                /* save pos for later use or erasure */
                tglyph->saved[tglyph->sidx].x = x;
                tglyph->saved[tglyph->sidx].y = y;
                tglyph->sidx += 1;
            } else { /* DISP_FLASH/ALWAYS */
                if (tglyph
                        ->sidx) { /* not first call, so reset previous pos */
                    newsym(tglyph->saved[0].x, tglyph->saved[0].y);
                    tglyph->sidx = 0; /* display is presently up to date */
                }
                if (!cansee(x, y) && tglyph->style != DISP_ALWAYS)
                    break;
                tglyph->saved[0].x = x;
                tglyph->saved[0].y = y;
                tglyph->sidx = 1;
            }

            show_glyph(x, y, tglyph->glyph); /* show it */
            flush_screen(0);                 /* make sure it shows up */
            break;
        } /* end switch */
    }
}

/*
 * flash_glyph_at(x, y, glyph, repeatcount)
 *
 * Briefly flash between the passed glyph and the glyph that's
 * meant to be at the location.
 */
void
flash_glyph_at(coordxy x, coordxy y, int tg, int rpt)
{
    int i, glyph[2];

    rpt *= 2; /* two loop iterations per 'count' */
    glyph[0] = tg;
    glyph[1] = (gl.level.flags.hero_memory) ? levl[x][y].glyph
                                         : back_to_glyph(x, y);
    /* even iteration count (guaranteed) ends with glyph[1] showing;
       caller might want to override that, but no newsym() calls here
       in case caller has tinkered with location visibility */
    for (i = 0; i < rpt; i++) {
        show_glyph(x, y, glyph[i % 2]);
        flush_screen(1);
        nh_delay_output();
    }
}

/*
 * swallowed()
 *
 * The hero is swallowed.  Show a special graphics sequence for this.  This
 * bypasses all of the display routines and messes with buffered screen
 * directly.  This method works because both vision and display check for
 * being swallowed.
 */
void
swallowed(int first)
{
    static coordxy lastx, lasty; /* last swallowed position */
    int swallower, left_ok, rght_ok;

    if (first) {
        cls();
        bot();
    } else {
        coordxy x, y;

        /* Clear old location */
        for (y = lasty - 1; y <= lasty + 1; y++)
            for (x = lastx - 1; x <= lastx + 1; x++)
                if (isok(x, y))
                    show_glyph(x, y, GLYPH_UNEXPLORED);
    }

    swallower = monsndx(u.ustuck->data);
    /* assume isok(u.ux,u.uy) */
    left_ok = isok(u.ux - 1, u.uy);
    rght_ok = isok(u.ux + 1, u.uy);
    /*
     *  Display the hero surrounded by the monster's stomach.
     */
    if (isok(u.ux, u.uy - 1)) {
        if (left_ok)
            show_glyph(u.ux - 1, u.uy - 1,
                       swallow_to_glyph(swallower, S_sw_tl));
        show_glyph(u.ux, u.uy - 1, swallow_to_glyph(swallower, S_sw_tc));
        if (rght_ok)
            show_glyph(u.ux + 1, u.uy - 1,
                       swallow_to_glyph(swallower, S_sw_tr));
    }

    if (left_ok)
        show_glyph(u.ux - 1, u.uy, swallow_to_glyph(swallower, S_sw_ml));
    display_self();
    if (rght_ok)
        show_glyph(u.ux + 1, u.uy, swallow_to_glyph(swallower, S_sw_mr));

    if (isok(u.ux, u.uy + 1)) {
        if (left_ok)
            show_glyph(u.ux - 1, u.uy + 1,
                       swallow_to_glyph(swallower, S_sw_bl));
        show_glyph(u.ux, u.uy + 1, swallow_to_glyph(swallower, S_sw_bc));
        if (rght_ok)
            show_glyph(u.ux + 1, u.uy + 1,
                       swallow_to_glyph(swallower, S_sw_br));
    }

    /* Update the swallowed position. */
    lastx = u.ux;
    lasty = u.uy;
}

/*
 * under_water()
 *
 * Similar to swallowed() in operation.  Shows hero when underwater
 * except when in water level.  Special routines exist for that.
 */
void
under_water(int mode)
{
    static coordxy lastx, lasty;
    static boolean dela;
    coordxy x, y;

    /* swallowing has a higher precedence than under water */
    if (Is_waterlevel(&u.uz) || u.uswallow)
        return;

    /* full update */
    if (mode == 1 || dela) {
        cls();
        dela = FALSE;

    /* delayed full update */
    } else if (mode == 2) {
        dela = TRUE;
        return;

    /* limited update */
    } else {
        for (y = lasty - 1; y <= lasty + 1; y++)
            for (x = lastx - 1; x <= lastx + 1; x++)
                if (isok(x, y))
                    show_glyph(x, y, GLYPH_UNEXPLORED);
    }

    /*
     * TODO?  Should this honor Xray radius rather than force radius 1?
     */

    for (x = u.ux - 1; x <= u.ux + 1; x++)
        for (y = u.uy - 1; y <= u.uy + 1; y++)
            if (isok(x, y) && (is_pool_or_lava(x, y) || is_ice(x, y))) {
                if (Blind && !u_at(x, y))
                    show_glyph(x, y, GLYPH_UNEXPLORED);
                else
                    newsym(x, y);
            }
    lastx = u.ux;
    lasty = u.uy;
}

/*
 *      under_ground()
 *
 *      Very restricted display.  You can only see yourself.
 */
void
under_ground(int mode)
{
    static boolean dela;

    /* swallowing has a higher precedence than under ground */
    if (u.uswallow)
        return;

    /* full update */
    if (mode == 1 || dela) {
        cls();
        dela = FALSE;

    /* delayed full update */
    } else if (mode == 2) {
        dela = TRUE;
        return;

    /* limited update */
    } else {
        newsym(u.ux, u.uy);
    }
}

/* ======================================================================== */

/*
 * Loop through all of the monsters and update them.  Called when:
 *      + going blind & telepathic
 *      + regaining sight & telepathic
 *      + getting and losing infravision
 *      + hallucinating
 *      + doing a full screen redraw
 *      + see invisible times out or a ring of see invisible is taken off
 *        or intrinsic see invisible is stolen by a gremlin
 *      + when a potion of see invisible is quaffed or a ring of see
 *        invisible is put on
 *      + gaining telepathy when blind [givit() in eat.c, pleased() in pray.c]
 *      + losing telepathy while blind [xkilled() in mon.c, attrcurse() in
 *        sit.c]
 */
void
see_monsters(void)
{
    register struct monst *mon;
    int new_warn_obj_cnt = 0;

    if (gd.defer_see_monsters)
        return;

    /* steed and unseen engulfer/holder/holdee are recognized via touch
       even if they aren't going to be rendered; other monsters
       may get flagged as having been seen by display_monster() if it's
       called by newsym() */
    if (u.usteed)
        u.usteed->meverseen = 1;
    if (u.ustuck)
        u.ustuck->meverseen = 1;

    /* loop through level.monsters (aka fmon) */
    for (mon = fmon; mon; mon = mon->nmon) {
        if (DEADMONSTER(mon))
            continue;
        newsym(mon->mx, mon->my);
        if (mon->wormno)
            see_wsegs(mon);
        if (Warn_of_mon
            && (gc.context.warntype.obj & mon->data->mflags2) != 0L)
            new_warn_obj_cnt++;
    }

    /*
     * Make Sting glow blue or stop glowing if required.
     */
    if (new_warn_obj_cnt != gw.warn_obj_cnt) {
        Sting_effects(new_warn_obj_cnt);
        gw.warn_obj_cnt = new_warn_obj_cnt;
    }

    /* when mounted, hero's location gets caught by monster loop */
    if (!u.usteed)
        newsym(u.ux, u.uy);
}

static void
mimic_light_blocking(struct monst *mtmp)
{
    if (mtmp->minvis && is_lightblocker_mappear(mtmp)) {
        if (See_invisible)
            block_point(mtmp->mx, mtmp->my);
        else
            unblock_point(mtmp->mx, mtmp->my);
    }
}

/*
 * Block/unblock light depending on what a mimic is mimicing and if it's
 * invisible or not.  Should be called only when the state of See_invisible
 * changes.
 */
void
set_mimic_blocking(void)
{
    iter_mons(mimic_light_blocking);
}

/*
 * Loop through all of the object *locations* and update them.  Called when
 *      + hallucinating.
 */
void
see_objects(void)
{
    register struct obj *obj;

    for (obj = fobj; obj; obj = obj->nobj)
        if (vobj_at(obj->ox, obj->oy) == obj)
            newsym(obj->ox, obj->oy);

    /* Qt's "paper doll" subset of persistent inventory shows map tiles
       for objects which aren't on the floor so not handled by above loop;
       inventory which includes glyphs should also be affected, so do this
       for all interfaces in case any feature that for persistent inventory */
    update_inventory();
}

/* mark the top object of nearby stacks as having been seen, and if
   that object was being displayed as generic, redisplay it as specific */
void
see_nearby_objects(void)
{
    struct obj *obj;
    int glyph;
    coordxy ix, iy, x = u.ux, y = u.uy;
    /* these 'r' and 'neardist' calculations match distant_name(objnam.c) */
    int r = (u.xray_range > 2) ? u.xray_range : 2,
        /* neardist produces a small square with rounded corners */
        neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */

    /* [note: this could be optimized to avoid the distu() calculations] */
    for (iy = y - r; iy <= y + r; ++iy)
        for (ix = x - r; ix <= x + r; ++ix) {
            if (!isok(ix, iy))
                continue;
            /* skip if no object or the object has already been marked as
               having been seen up close */
            if ((obj = vobj_at(ix, iy)) == 0 || obj->dknown)
                continue;
            /* skip if the spot can't be seen or is too far (diagonal) */
            if (!cansee(ix, iy) || distu(ix, iy) > neardist)
                continue;

            obj->dknown = 1; /* near enough to see it */
            /* operate on remembered glyph rather than current one */
            glyph = levl[ix][iy].glyph;
            if (glyph_is_generic_object(glyph))
                newsym_force(ix, iy);
        }
}

/*
 * Update hallucinated traps.
 */
void
see_traps(void)
{
    struct trap *trap;
    int glyph;

    for (trap = gf.ftrap; trap; trap = trap->ntrap) {
        glyph = _glyph_at(trap->tx, trap->ty);
        if (glyph_is_trap(glyph))
            newsym(trap->tx, trap->ty);
    }
}

/*  glyph, ttychar, { glyphflags, { sym.color, sym.symidx },
                      tileidx, u } */
static glyph_info no_ginfo = {
    NO_GLYPH, ' ', NO_COLOR, { MG_BADXY, { NO_COLOR, 0 }, 0
#ifdef ENHANCED_SYMBOLS
                                                 , 0
#endif
    }
};
#ifndef UNBUFFERED_GLYPHINFO
#define Glyphinfo_at(x, y, glyph) \
    (((x) < 0 || (y) < 0 || (x) >= COLNO || (y) >= ROWNO) ? &no_ginfo   \
     : &gg.gbuf[(y)][(x)].glyphinfo)
#else
static glyph_info ginfo;
#define Glyphinfo_at(x, y, glyph) glyphinfo_at(x, y, glyph)
#endif

#ifdef TILES_IN_GLYPHMAP
extern const glyph_info nul_glyphinfo; /* tile.c */
#else
/* glyph, ttychar, { glyphflags, { sym.color, sym.symidx },
                     tileidx, 0} */
const glyph_info nul_glyphinfo = {
    NO_GLYPH, ' ', NO_COLOR,
        {  /* glyph_map */
            MG_UNEXPL,
            { NO_COLOR, SYM_UNEXPLORED + SYM_OFF_X },
            0
#ifdef ENHANCED_SYMBOLS
             , 0
#endif
        }
};
#endif

#ifdef TILES_IN_GLYPHMAP
extern glyph_map glyphmap[MAX_GLYPH]; /* from tile.c */
#else
glyph_map glyphmap[MAX_GLYPH] = {
    { 0U, { 0, 0}, 0
#ifdef ENHANCED_SYMBOLS
            , 0
#endif
    }
};
#endif

/*
 * Put the cursor on the hero.  Flush all accumulated glyphs before doing it.
 */
void
curs_on_u(void)
{
    flush_screen(1); /* Flush waiting glyphs & put cursor on hero */
}

/* the #redraw command */
int
doredraw(void)
{
    docrt();
    return ECMD_OK;
}

/* the main refresh-the-screen routine */
void
docrt(void)
{
    docrt_flags(docrtRecalc);
}

/* docrt() with finer control */
void
docrt_flags(int refresh_flags)
{
    coordxy x, y;
    register struct rm *lev;
    boolean maponly = (refresh_flags & docrtMapOnly) != 0,
            redrawonly = (refresh_flags & docrtRefresh) != 0;

    if (!u.ux || gp.program_state.in_docrt)
        return; /* display isn't ready yet */

    gp.program_state.in_docrt = TRUE;

    if (redrawonly) {
        redraw_map(FALSE);
        goto post_map;
    }
    if (u.uswallow) {
        swallowed(1);
        goto post_map;
    }
    if (Underwater && !Is_waterlevel(&u.uz)) {
        under_water(1);
        goto post_map;
    }
    if (u.uburied) { /* [not implemented] */
        under_ground(1);
        goto post_map;
    }

    /* shut down vision */
    vision_recalc(2);

    /*
     * This routine assumes that cls() does the following:
     *      + fills the physical screen with the symbol for rock
     *      + clears the glyph buffer
     */
    cls();

    /* display memory */
    for (x = 1; x < COLNO; x++) {
        lev = &levl[x][0];
        for (y = 0; y < ROWNO; y++, lev++)
            show_glyph(x, y, lev->glyph);
    }

    /* see what is to be seen */
    vision_recalc(0);

    /* overlay with monsters */
    see_monsters();

 post_map:

    if (!maponly) {
        /* perm_invent */
        update_inventory();
        /* status */
        gc.context.botlx = 1; /* force a redraw of the bottom lines */
        /* note: caller needs to call bot() to actually redraw status */
    }
    gp.program_state.in_docrt = FALSE;
}

/* for panning beyond a clipped region; resend the current map data to
   the interface rather than use docrt()'s regeneration of that data */
void
redraw_map(boolean cursor_on_u)
{
    coordxy x, y;
    int glyph;
    glyph_info bkglyphinfo = nul_glyphinfo;

    /*
     * Not sure whether this is actually necessary; save and restore did
     * used to get much too involved with each dungeon level as it was
     * read and written.
     *
     * !u.ux: display isn't ready yet; (restoring || !on_level()): was part
     * of cliparound() but interface shouldn't access this much internals
     */
    if (!u.ux || suppress_map_output() || !on_level(&u.uz0, &u.uz))
        return;

    /*
     * This yields sensible clipping when #terrain+getpos is in
     * progress and the screen displays something other than what
     * the map would currently be showing.
     */
    for (y = 0; y < ROWNO; ++y)
        for (x = 1; x < COLNO; ++x) {
            glyph = _glyph_at(x, y); /* not levl[x][y].glyph */
            get_bkglyph_and_framecolor(x, y, &bkglyphinfo.glyph,
                                       &bkglyphinfo.framecolor);
            print_glyph(WIN_MAP, x, y,
                        Glyphinfo_at(x, y, glyph), &bkglyphinfo);
        }
    flush_screen(cursor_on_u);
#ifndef UNBUFFERED_GLYPHINFO
    nhUse(glyph);
#endif
}

/*
 * =======================================================
 */
void
reglyph_darkroom(void)
{
    coordxy x, y;

    for (x = 1; x < COLNO; x++)
        for (y = 0; y < ROWNO; y++) {
            struct rm *lev = &levl[x][y];

            if (!flags.dark_room) {
                if (lev->glyph == cmap_to_glyph(S_corr)
                    && lev->waslit)
                    lev->glyph = cmap_to_glyph(S_litcorr);
            } else {
                if (lev->glyph == cmap_to_glyph(S_litcorr)
                    && !cansee(x, y))
                    lev->glyph = cmap_to_glyph(S_corr);
            }

            if (!flags.dark_room || !iflags.use_color
                || Is_rogue_level(&u.uz)) {
                if (lev->glyph == cmap_to_glyph(S_darkroom))
                    lev->glyph = lev->waslit ? cmap_to_glyph(S_room)
                                             : GLYPH_NOTHING;
            } else {
                if (lev->glyph == cmap_to_glyph(S_room) && lev->seenv
                    && lev->waslit && !cansee(x, y))
                    lev->glyph = cmap_to_glyph(S_darkroom);
                else if (lev->glyph == GLYPH_NOTHING
                         && lev->typ == ROOM && lev->seenv && !cansee(x, y))
                    lev->glyph = cmap_to_glyph(S_darkroom);
            }
        }
    if (flags.dark_room && iflags.use_color)
        gs.showsyms[S_darkroom] = gs.showsyms[S_room];
    else
        gs.showsyms[S_darkroom] = gs.showsyms[SYM_NOTHING + SYM_OFF_X];
}

/* ======================================================================== */
/* Glyph Buffering (3rd screen) =========================================== */

/* FIXME: This is a dirty hack, because newsym() doesn't distinguish
 * between object piles and single objects, it doesn't mark the location
 * for update. */
void
newsym_force(coordxy x, coordxy y)
{
    newsym(x, y);
    gg.gbuf[y][x].gnew = 1;
    if (gg.gbuf_start[y] > x)
        gg.gbuf_start[y] = x;
    if (gg.gbuf_stop[y] < x)
        gg.gbuf_stop[y] = x;
}

/*
 * Store the glyph in the 3rd screen for later flushing.
 */
void
show_glyph(coordxy x, coordxy y, int glyph)
{
#ifndef UNBUFFERED_GLYPHINFO
    glyph_info glyphinfo;
#endif

    /*
     * Check for bad positions and glyphs.
     */
    if (!isok(x, y)) {
        const char *text = "";
        int offset = -1;

        /* column 0 is invalid, but it's often used as a flag, so ignore it */
        if (x == 0)
            return;

        /*
         *  This assumes an ordering of the offsets.  See display.h for
         *  the definition.
         */
        if (glyph < 0 || glyph >= MAX_GLYPH) {
            /* invalid location and also invalid glyph */
            text = "invalid";
        } else if ((offset = (glyph - GLYPH_NOTHING_OFF)) >= 0) {
            text = "nothing";
        } else if ((offset = (glyph - GLYPH_UNEXPLORED_OFF)) >= 0) {
            text = "unexplored";
        } else if ((offset = (glyph - GLYPH_STATUE_FEM_PILETOP_OFF)) >= 0) {
            text = "statue of a female monster at top of a pile";
        } else if ((offset = (glyph - GLYPH_STATUE_MALE_PILETOP_OFF)) >= 0) {
            text = "statue of a male monster at top of a pile";
        } else if ((offset = (glyph - GLYPH_BODY_PILETOP_OFF)) >= 0) {
            text = "body at top of a pile";
        } else if ((offset = (glyph - GLYPH_OBJ_PILETOP_OFF)) >= 0) {
            text = (glyph_is_piletop_generic_obj(glyph)
                    ? "generic object at top of a pile"
                    : "object at top of a pile");
        } else if ((offset = (glyph - GLYPH_STATUE_FEM_OFF)) >= 0) {
            text = "statue of female monster";
        } else if ((offset = (glyph - GLYPH_STATUE_MALE_OFF)) >= 0) {
            text = "statue of male monster";
        } else if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) {
            /* warn flash */
            text = "warning explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_FROSTY_OFF)) >= 0) {
            text = "frosty explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_FIERY_OFF)) >= 0) {
            text = "fiery explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_MAGICAL_OFF)) >= 0) {
            text = "magical explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_WET_OFF)) >= 0) {
            text = "wet explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_MUDDY_OFF)) >= 0) {
            text = "muddy explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_NOXIOUS_OFF)) >= 0) {
            text = "noxious explosion";
        } else if ((offset = (glyph - GLYPH_EXPLODE_DARK_OFF)) >= 0) {
            text = "dark explosion";
        } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) {
            text = "swallow";
        } else if ((offset = (glyph - GLYPH_CMAP_C_OFF)) >= 0) {
            text = "cmap C";
        } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) {
            text = "zap";
        } else if ((offset = (glyph - GLYPH_CMAP_B_OFF)) >= 0) {
            text = "cmap B";
        } else if ((offset = (glyph - GLYPH_ALTAR_OFF)) >= 0) {
            text = "altar";
        } else if ((offset = (glyph - GLYPH_CMAP_A_OFF)) >= 0) {
            text = "cmap A";
        } else if ((offset = (glyph - GLYPH_CMAP_SOKO_OFF)) >= 0) {
            text = "sokoban dungeon walls";
        } else if ((offset = (glyph - GLYPH_CMAP_KNOX_OFF)) >= 0) {
            text = "knox dungeon walls";
        } else if ((offset = (glyph - GLYPH_CMAP_GEH_OFF)) >= 0) {
            text = "gehennom dungeon walls";
        } else if ((offset = (glyph - GLYPH_CMAP_MINES_OFF)) >= 0) {
            text = "gnomish mines dungeon walls";
        } else if ((offset = (glyph - GLYPH_CMAP_MAIN_OFF)) >= 0) {
            text = "main dungeon walls";
        } else if ((offset = (glyph - GLYPH_CMAP_STONE_OFF)) >= 0) {
            text = "stone";
        } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) {
            text = (glyph_is_normal_generic_obj(glyph)
                    ? "generic object"
                    : "object");
        } else if ((offset = (glyph - GLYPH_RIDDEN_FEM_OFF)) >= 0) {
            text = "ridden female monster";
        } else if ((offset = (glyph - GLYPH_RIDDEN_MALE_OFF)) >= 0) {
            text = "ridden male monster";
        } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) {
            text = "body";
        } else if ((offset = (glyph - GLYPH_DETECT_FEM_OFF)) >= 0) {
            text = "detected female monster";
        } else if ((offset = (glyph - GLYPH_DETECT_MALE_OFF)) >= 0) {
            text = "detected male monster";
        } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) {
            text = "invisible monster";
        } else if ((offset = (glyph - GLYPH_PET_FEM_OFF)) >= 0) {
            text = "female pet";
        } else if ((offset = (glyph - GLYPH_PET_MALE_OFF)) >= 0) {
            text = "male pet";
        } else if ((offset = (glyph - GLYPH_MON_FEM_OFF)) >= 0) {
            text = "female monster";
        } else if ((offset = (glyph - GLYPH_MON_MALE_OFF)) >= 0) {
            text = "male monster";
        }
        impossible("show_glyph:  bad pos <%d,%d> with glyph %d [%s %d].",
                   x, y, glyph, text, offset);
        return;
    } else if (glyph < 0 || glyph >= MAX_GLYPH) {
        /* valid location but invalid glyph */
        impossible("show_glyph:  bad glyph %d [max %d] at <%d,%d>.",
                   glyph, MAX_GLYPH, x, y);
        return;
    }
#ifndef UNBUFFERED_GLYPHINFO
    /* without UNBUFFERED_GLYPHINFO defined the glyphinfo values are buffered
       alongside the glyphs themselves for better performance, increased
       buffer size */
    map_glyphinfo(x, y, glyph, 0, &glyphinfo);
#endif

    if (gg.gbuf[y][x].glyphinfo.glyph != glyph
#ifndef UNBUFFERED_GLYPHINFO
        /* flags might change (single object vs pile, monster tamed or pet
           gone feral), color might change (altar's alignment converted by
           invisible hero), but ttychar normally won't change unless the
           glyph does too (changing boulder symbol would be an exception,
           but that triggers full redraw so doesn't matter here); still,
           be thorough and check everything */
        || gg.gbuf[y][x].glyphinfo.ttychar != glyphinfo.ttychar
        || gg.gbuf[y][x].glyphinfo.gm.glyphflags != glyphinfo.gm.glyphflags
        || gg.gbuf[y][x].glyphinfo.gm.sym.color != glyphinfo.gm.sym.color
        || gg.gbuf[y][x].glyphinfo.gm.tileidx != glyphinfo.gm.tileidx
#endif
        || iflags.use_background_glyph) {
        gg.gbuf[y][x].glyphinfo.glyph = glyph;
        gg.gbuf[y][x].gnew = 1;
#ifndef UNBUFFERED_GLYPHINFO
        gg.gbuf[y][x].glyphinfo.glyph = glyphinfo.glyph;
        gg.gbuf[y][x].glyphinfo.ttychar = glyphinfo.ttychar;
        gg.gbuf[y][x].glyphinfo.gm = glyphinfo.gm;
#endif
        if (gg.gbuf_start[y] > x)
            gg.gbuf_start[y] = x;
        if (gg.gbuf_stop[y] < x)
            gg.gbuf_stop[y] = x;
    }
}

/*
 * Reset the changed glyph borders so that none of the 3rd screen has
 * changed.
 */
#define reset_glyph_bbox()                \
    {                                     \
        int i;                            \
                                          \
        for (i = 0; i < ROWNO; i++) {     \
            gg.gbuf_start[i] = COLNO - 1; \
            gg.gbuf_stop[i] = 0;          \
        }                                 \
    }

static gbuf_entry nul_gbuf = {
    0,                                 /* gnew */
    { GLYPH_UNEXPLORED, (unsigned) ' ', NO_COLOR, /* glyphinfo.glyph */
        /* glyphinfo.gm */
        { MG_UNEXPL, { (unsigned) NO_COLOR, 0 }, 0
#ifdef ENHANCED_SYMBOLS
                                                  , 0
#endif
        }
    }
};

/*
 * Turn the 3rd screen into UNEXPLORED that needs to be refreshed.
 */
void
clear_glyph_buffer(void)
{
    register coordxy x, y;
    gbuf_entry *gptr = &gg.gbuf[0][0];
    glyph_info *giptr =
#ifndef UNBUFFERED_GLYPHINFO
                        &gptr->glyphinfo;
#else
                        &ginfo;

    map_glyphinfo(0, 0, GLYPH_UNEXPLORED, 0, giptr);
#endif
#ifndef UNBUFFERED_GLYPHINFO
    nul_gbuf.gnew = (giptr->ttychar != nul_gbuf.glyphinfo.ttychar
                     || giptr->gm.sym.color != nul_gbuf.glyphinfo.gm.sym.color
                     || giptr->gm.glyphflags
                        != nul_gbuf.glyphinfo.gm.glyphflags
                     || giptr->gm.tileidx != nul_gbuf.glyphinfo.gm.tileidx)
#else
    nul_gbuf.gnew = (giptr->glyphinfo.ttychar != ' '
                     || giptr->gm.sym.color != NO_COLOR
                     || (giptr->gm.glyphflags & ~MG_UNEXPL) != 0)
#endif
                         ? 1 : 0;
    for (y = 0; y < ROWNO; y++) {
        gptr = &gg.gbuf[y][0];
        for (x = COLNO; x; x--) {
            *gptr++ = nul_gbuf;
        }
        gg.gbuf_start[y] = 1;
        gg.gbuf_stop[y] = COLNO - 1;
    }
}

/* used by tty after menu or text popup has temporarily overwritten the map
   and it has been erased so shows spaces, not necessarily S_unexplored */
void
row_refresh(coordxy start, coordxy stop, coordxy y)
{
    register coordxy x;
    int glyph;
    register boolean force;
    gbuf_entry *gptr = &gg.gbuf[0][0];
    glyph_info bkglyphinfo = nul_glyphinfo;
    glyph_info *giptr =
#ifndef UNBUFFERED_GLYPHINFO
                        &gptr->glyphinfo;
#else
                        &ginfo;

    map_glyphinfo(0, 0, GLYPH_UNEXPLORED, 0U, giptr);
#endif
#ifndef UNBUFFERED_GLYPHINFO
    force = (giptr->ttychar != nul_gbuf.glyphinfo.ttychar
                 || giptr->gm.sym.color != nul_gbuf.glyphinfo.gm.sym.color
                 || giptr->gm.glyphflags != nul_gbuf.glyphinfo.gm.glyphflags
                 || giptr->gm.tileidx != nul_gbuf.glyphinfo.gm.tileidx)
#else
    force = (giptr->ttychar != ' '
                 || giptr->gm.sym.color != NO_COLOR
                 || (giptr->gm.glyphflags & ~MG_UNEXPL) != 0)
#endif
                 ? 1 : 0;
    for (x = start; x <= stop; x++) {
        gptr = &gg.gbuf[y][x];
        glyph = gptr->glyphinfo.glyph;
        get_bkglyph_and_framecolor(x, y, &bkglyphinfo.glyph,
                                   &bkglyphinfo.framecolor);
        if (force || glyph != GLYPH_UNEXPLORED
            || bkglyphinfo.framecolor != NO_COLOR) {
            print_glyph(WIN_MAP, x, y,
                        Glyphinfo_at(x, y, glyph), &bkglyphinfo);
        }
    }
}

void
cls(void)
{
    static boolean in_cls = 0;

    if (in_cls)
        return;
    in_cls = TRUE;
    display_nhwindow(WIN_MESSAGE, FALSE); /* flush messages */
    gc.context.botlx = 1;                 /* force update of botl window */
    clear_nhwindow(WIN_MAP);              /* clear physical screen */

    clear_glyph_buffer(); /* force gbuf[][].glyph to unexplored */
    in_cls = FALSE;
}

/*
 * Synch the third screen with the display.
 */
void
flush_screen(int cursor_on_u)
{
    /* Prevent infinite loops on errors:
     *      flush_screen->print_glyph->impossible->pline->flush_screen
     */
    static int flushing = 0;
    static int delay_flushing = 0;
    register coordxy x, y;
    glyph_info bkglyphinfo = nul_glyphinfo;
    int bkglyph;

    /* 3.7: don't update map, status, or perm_invent during save/restore */
    if (suppress_map_output())
        return;

    if (cursor_on_u == -1)
        delay_flushing = !delay_flushing;
    if (delay_flushing)
        return;
    if (flushing)
        return; /* if already flushing then return */
    flushing = 1;
#ifdef HANGUPHANDLING
    if (gp.program_state.done_hup)
        return;
#endif

    /* get this done now, before we place the cursor on the hero */
    if (gc.context.botl || gc.context.botlx)
        bot();
    else if (iflags.time_botl)
        timebot();

    for (y = 0; y < ROWNO; y++) {
        register gbuf_entry *gptr = &gg.gbuf[y][x = gg.gbuf_start[y]];

        for (; x <= gg.gbuf_stop[y]; gptr++, x++) {
            get_bkglyph_and_framecolor(x, y, &bkglyph, &bkglyphinfo.framecolor);
            if (gptr->gnew
                || (gw.wsettings.map_frame_color != NO_COLOR
                    && bkglyphinfo.framecolor != NO_COLOR)) {
                map_glyphinfo(x, y, bkglyph, 0, &bkglyphinfo); /* won't touch framecolor */
                print_glyph(WIN_MAP, x, y,
                            Glyphinfo_at(x, y, gptr->glyph), &bkglyphinfo);
                gptr->gnew = 0;
            }
        }
    }
    reset_glyph_bbox();

    /* after map update, before display_nhwindow(WIN_MAP) */
    if (cursor_on_u)
        curs(WIN_MAP, u.ux, u.uy); /* move cursor to the hero */

    display_nhwindow(WIN_MAP, FALSE);
    flushing = 0;
}

/* ======================================================================== */

/*
 * back_to_glyph()
 *
 * Use the information in the rm structure at the given position to create
 * a glyph of a background.
 *
 * I had to add a field in the rm structure (horizontal) so that we knew
 * if open doors and secret doors were horizontal or vertical.  Previously,
 * the screen symbol had the horizontal/vertical information set at
 * level generation time.
 *
 * I used the 'ladder' field (really doormask) for deciding if stairwells
 * were up or down.  I didn't want to check the upstairs and dnstairs
 * variables.
 */
int
back_to_glyph(coordxy x, coordxy y)
{
    int idx, bypass_glyph = NO_GLYPH;
    struct rm *ptr = &(levl[x][y]);
    struct stairway *sway;

    switch (ptr->typ) {
    case SCORR:
    case STONE:
        idx = gl.level.flags.arboreal ? S_tree : S_stone;
        break;
    case ROOM:
        idx = S_room;
        break;
    case CORR:
        idx = (ptr->waslit || flags.lit_corridor) ? S_litcorr : S_corr;
        break;
    case HWALL:
    case VWALL:
    case TLCORNER:
    case TRCORNER:
    case BLCORNER:
    case BRCORNER:
    case CROSSWALL:
    case TUWALL:
    case TDWALL:
    case TLWALL:
    case TRWALL:
    case SDOOR:
        idx = ptr->seenv ? wall_angle(ptr) : S_stone;
        break;
    case DOOR:
        if (ptr->doormask) {
            if (ptr->doormask & D_BROKEN)
                idx = S_ndoor;
            else if (ptr->doormask & D_ISOPEN)
                idx = (ptr->horizontal) ? S_hodoor : S_vodoor;
            else /* else is closed */
                idx = (ptr->horizontal) ? S_hcdoor : S_vcdoor;
        } else
            idx = S_ndoor;
        break;
    case IRONBARS:
        idx = S_bars;
        break;
    case TREE:
        idx = S_tree;
        break;
    case POOL:
    case MOAT:
        idx = S_pool;
        break;
    case STAIRS:
        sway = stairway_at(x, y);
        if (known_branch_stairs(sway))
            idx = (ptr->ladder & LA_DOWN) ? S_brdnstair : S_brupstair;
        else
            idx = (ptr->ladder & LA_DOWN) ? S_dnstair : S_upstair;
        break;
    case LADDER:
        sway = stairway_at(x, y);
        if (known_branch_stairs(sway))
            idx = (ptr->ladder & LA_DOWN) ? S_brdnladder : S_brupladder;
        else
            idx = (ptr->ladder & LA_DOWN) ? S_dnladder : S_upladder;
        break;
    case FOUNTAIN:
        idx = S_fountain;
        break;
    case SINK:
        idx = S_sink;
        break;
    case ALTAR:
        idx = S_altar;  /* not really used */
        bypass_glyph = altar_to_glyph(ptr->altarmask);
        break;
    case GRAVE:
        idx = S_grave;
        break;
    case THRONE:
        idx = S_throne;
        break;
    case LAVAPOOL:
        idx = S_lava;
        break;
    case LAVAWALL:
        idx = S_lavawall;
        break;
    case ICE:
        idx = S_ice;
        break;
    case AIR:
        idx = S_air;
        break;
    case CLOUD:
        idx = S_cloud;
        break;
    case WATER:
        idx = S_water;
        break;
    case DBWALL:
        idx = (ptr->horizontal) ? S_hcdbridge : S_vcdbridge;
        break;
    case DRAWBRIDGE_UP:
        switch (ptr->drawbridgemask & DB_UNDER) {
        case DB_MOAT:
            idx = S_pool;
            break;
        case DB_LAVA:
            idx = S_lava;
            break;
        case DB_ICE:
            idx = S_ice;
            break;
        case DB_FLOOR:
            idx = S_room;
            break;
        default:
            impossible("Strange db-under: %d",
                       ptr->drawbridgemask & DB_UNDER);
            idx = S_room; /* something is better than nothing */
            break;
        }
        break;
    case DRAWBRIDGE_DOWN:
        idx = (ptr->horizontal) ? S_hodbridge : S_vodbridge;
        break;
    default:
        impossible("back_to_glyph:  unknown level type [ = %d ]", ptr->typ);
        idx = S_room;
        break;
    }

    return (bypass_glyph != NO_GLYPH) ? bypass_glyph : cmap_to_glyph(idx);
}

/*
 * swallow_to_glyph()
 *
 * Convert a monster number and a swallow location into the correct glyph.
 * If you don't want a patchwork monster while hallucinating, decide on
 * a random monster in swallowed() and don't use what_mon() here.
 */
static int
swallow_to_glyph(int mnum, int loc)
{
    int m_3 = what_mon(mnum, rn2_on_display_rng) << 3;

    if (loc < S_sw_tl || S_sw_br < loc) {
        impossible("swallow_to_glyph: bad swallow location");
        loc = S_sw_br;
    }
    return (m_3 | (loc - S_sw_tl)) + GLYPH_SWALLOW_OFF;
}

/*
 * zapdir_to_glyph()
 *
 * Change the given zap direction and beam type into a glyph.  Each beam
 * type has four glyphs, one for each of the symbols below.  The order of
 * the zap symbols [0-3] as defined in rm.h are:
 *
 *      |  S_vbeam      ( 0, 1) or ( 0,-1)
 *      -  S_hbeam      ( 1, 0) or (-1, 0)
 *      \  S_lslant     ( 1, 1) or (-1,-1)
 *      /  S_rslant     (-1, 1) or ( 1,-1)
 */
int
zapdir_to_glyph(int dx, int dy, int beam_type)
{
    if (beam_type >= NUM_ZAP) {
        impossible("zapdir_to_glyph:  illegal beam type");
        beam_type = 0;
    }
    dx = (dx == dy) ? 2 : (dx && dy) ? 3 : dx ? 1 : 0;

    return ((int) ((beam_type << 2) | dx)) + GLYPH_ZAP_OFF;
}

/*
 * Utility routine for dowhatis() used to find out the glyph displayed at
 * the location.  This isn't necessarily the same as the glyph in the levl
 * structure, so we must check the "third screen".
 */
int
glyph_at(coordxy x, coordxy y)
{
    if (x < 0 || y < 0 || x >= COLNO || y >= ROWNO)
        return cmap_to_glyph(S_room); /* XXX */
    return gg.gbuf[y][x].glyphinfo.glyph; /* _glyph_at(x,y) */
}

#ifdef UNBUFFERED_GLYPHINFO
glyph_info *
glyphinfo_at(coordxy x, coordxy y, int glyph)
{
    map_glyphinfo(x, y, glyph, 0, &ginfo);
    return &ginfo;
}
#endif

/*
 * Get the glyph for the background so that it can potentially
 * be merged into graphical window ports to improve the appearance
 * of stuff on dark room squares and the plane of air etc.
 * [This should be using background as recorded for #overview rather
 * than current data from the map.]
 *
 * Also get the frame color to use for highlighting the map location
 * for some purpose.
 *
 */

static void
get_bkglyph_and_framecolor(
    coordxy x, coordxy y,
    int *bkglyph,
    uint32 *framecolor)
{
    int idx, tmp_bkglyph = GLYPH_UNEXPLORED;
    struct rm *lev = &levl[x][y];

    if (iflags.use_background_glyph && lev->seenv != 0
        && (gg.gbuf[y][x].glyphinfo.glyph != GLYPH_UNEXPLORED)) {
        switch (lev->typ) {
        case SCORR:
        case STONE:
            idx = gl.level.flags.arboreal ? S_tree : S_stone;
            break;
        case ROOM:
           idx = S_room;
           break;
        case CORR:
           idx = (lev->waslit || flags.lit_corridor) ? S_litcorr : S_corr;
           break;
        case ICE:
           idx = S_ice;
           break;
        case AIR:
           idx = S_air;
           break;
        case CLOUD:
           idx = S_cloud;
           break;
        case POOL:
        case MOAT:
           idx = S_pool;
           break;
        case WATER:
           idx = S_water;
           break;
        case LAVAPOOL:
           idx = S_lava;
           break;
        case LAVAWALL:
            idx = S_lavawall;
            break;
        default:
           idx = S_room;
           break;
        }

        if (!cansee(x, y) && (!lev->waslit || flags.dark_room)) {
            /* Floor spaces are dark if unlit.  Corridors are dark if unlit. */
            if (lev->typ == CORR && idx == S_litcorr)
                idx = S_corr;
            else if (idx == S_room)
                idx = (flags.dark_room && iflags.use_color)
                         ? DARKROOMSYM : S_stone;
        }
        if (idx != S_room)
            tmp_bkglyph = cmap_to_glyph(idx);
    }
    *bkglyph = tmp_bkglyph;
#if 0
    /* this guard should be unnecessary */
    if (!framecolor) {
        impossible("null framecolor passed to get_bkglyph_and_framecolor");
        return;
    }
#endif
    if (iflags.bgcolors
        && gw.wsettings.map_frame_color != NO_COLOR && mapxy_valid(x, y))
        *framecolor = gw.wsettings.map_frame_color;
    else
        *framecolor = NO_COLOR;
}

#if defined(TILES_IN_GLYPHMAP) && defined(MSDOS)
#define HAS_ROGUE_IBM_GRAPHICS \
    (gc.currentgraphics == ROGUESET && SYMHANDLING(H_IBM) && !iflags.grmode)
#else
#define HAS_ROGUE_IBM_GRAPHICS \
    (gc.currentgraphics == ROGUESET && SYMHANDLING(H_IBM))
#endif

/* masks for per-level variances kept in gg.glyphmap_perlevel_flags */
#define GMAP_SET            0x0001
#define GMAP_ROGUELEVEL     0x0002

void
map_glyphinfo(
    coordxy x, coordxy y,
    int glyph,
    unsigned mgflags,
    glyph_info *glyphinfo)
{
    int offset;
    boolean is_you = (u_at(x, y)
                      /* verify hero or steed (not something underneath
                         when hero is invisible without see invisible,
                         or a temporary display effect like an explosion);
                         this is only approximate, because hero might be
                         mimicking an object (after eating mimic corpse or
                         if polyd into mimic) or furniture (only if polyd) */
                      && glyph_is_monster(glyph));
    const glyph_map *gmap = &glyphmap[glyph];

    glyphinfo->gm = *gmap; /* glyphflags, sym.symidx, sym.color, tileidx */
    /*
     * Only hero tinkering permitted on-the-fly (who).
     * Unique glyphs in glyphmap[] determine everything else (what).
     *
     * (Note: if hero is invisible without see invisible, he/she usually
     * can't see himself/herself.  This applies to accessibility hacks
     * as well as to regular display.)
     */
    if (is_you) {
#ifdef TEXTCOLOR
        if (!iflags.use_color || Upolyd || glyph != hero_glyph) {
            ; /* color tweak not needed (!use_color) or not wanted (poly'd
                 or riding--which uses steed's color, not hero's) */
        } else if (HAS_ROGUE_IBM_GRAPHICS
                   && gs.symset[gc.currentgraphics].nocolor == 0) {
            /* actually player should be yellow-on-gray if in corridor */
            glyphinfo->gm.sym.color = CLR_YELLOW;
        } else if (flags.showrace) {
            /* for showrace, non-human hero is displayed by the symbol of
               corresponding type of monster rather than by '@' (handled
               by newsym()); we change the color to same as human hero */
            glyphinfo->gm.sym.color = HI_DOMESTIC;
        }
#endif
        /* accessibility
          This unchanging display character for hero was requested by
          a blind player to enhance screen reader use.
          Turn on override symbol if caller hasn't specified NOOVERRIDE. */
        if (sysopt.accessibility == 1 && !(mgflags & MG_FLAG_NOOVERRIDE)) {
            offset = SYM_HERO_OVERRIDE + SYM_OFF_X;
            if ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL)
                    ? go.ov_rogue_syms[offset]
                    : go.ov_primary_syms[offset])
                glyphinfo->gm.sym.symidx = offset;
        }
        glyphinfo->gm.glyphflags |= MG_HERO;
    }
    if (sysopt.accessibility == 1
        && (mgflags & MG_FLAG_NOOVERRIDE) && glyph_is_pet(glyph)) {
        /* one more accessibility kludge;
           turn off override symbol if caller has specified NOOVERRIDE */
        glyphinfo->gm.sym.symidx = mons[glyph_to_mon(glyph)].mlet + SYM_OFF_M;
    }
    glyphinfo->ttychar = gs.showsyms[glyphinfo->gm.sym.symidx];
    glyphinfo->glyph = glyph;
}

#ifdef TEXTCOLOR
/*
 *  This must be the same order as used for buzz() in zap.c.
 *  The zap_color_ and altar_color_ enums are in decl.h.
 */
const int zapcolors[NUM_ZAP] = {
    zap_color_missile,    zap_color_fire,  zap_color_frost,
    zap_color_sleep,      zap_color_death, zap_color_lightning,
    zap_color_poison_gas, zap_color_acid,
};
const int altarcolors[] = {
    altar_color_unaligned, altar_color_chaotic, altar_color_neutral,
    altar_color_lawful, altar_color_other
};
const int explodecolors[7] = {
    explode_color_dark,   explode_color_noxious, explode_color_muddy,
    explode_color_wet,    explode_color_magical, explode_color_fiery,
    explode_color_frosty,
};

/* main_walls, mines_walls, gehennom_walls, knox_walls, sokoban_walls */
int wallcolors[sokoban_walls + 1] = {
    /* default init value is to match defsym[S_vwall + n].color (CLR_GRAY) */
    CLR_GRAY, CLR_GRAY, CLR_GRAY, CLR_GRAY, CLR_GRAY,
    /* CLR_GRAY, CLR_BROWN, CLR_RED, CLR_GRAY, CLR_BRIGHT_BLUE, */
};

#endif /* text color */

#ifdef TEXTCOLOR
#define zap_color(n) color = iflags.use_color ? zapcolors[n] : NO_COLOR
#define cmap_color(n) color = iflags.use_color ? defsyms[n].color : NO_COLOR
#define obj_color(n) color = iflags.use_color ? objects[n].oc_color : NO_COLOR
#define mon_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR
#define invis_color(n) color = NO_COLOR
#define pet_color(n) color = iflags.use_color ? mons[n].mcolor : NO_COLOR
#define warn_color(n) \
    color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR
#define explode_color(n) \
    color = iflags.use_color ? explodecolors[n] : NO_COLOR
#define wall_color(n) color = iflags.use_color ? wallcolors[n] : NO_COLOR
#define altar_color(n) color = iflags.use_color ? altarcolors[n] : NO_COLOR
#else /* no text color */

#define zap_color(n)
#define cmap_color(n)
#define obj_color(n)
#define mon_color(n)
#define invis_color(n)
#define pet_color(c)
#define warn_color(n)
#define explode_color(n)
#define wall_color(n)
#define altar_color(n)
#endif

#if 0
#define is_objpile(x, y)                          \
    (!Hallucination && gl.level.objects[(x)][(y)] \
     && gl.level.objects[(x)][(y)]->nexthere)
#endif

static int cmap_to_roguecolor(int);

static int
cmap_to_roguecolor(int cmap)
{
    int color = NO_COLOR;

    if (gs.symset[gc.currentgraphics].nocolor)
        return NO_COLOR;

    if (cmap >= S_vwall && cmap <= S_hcdoor)
        color = CLR_BROWN;
    else if (cmap >= S_arrow_trap && cmap <= S_polymorph_trap)
        color = CLR_MAGENTA;
    else if (cmap == S_corr || cmap == S_litcorr)
        color = CLR_GRAY;
    else if (cmap >= S_room && cmap <= S_water
                && cmap != S_darkroom)
        color = CLR_GREEN;
    else
        color = NO_COLOR;

    return color;
}

/*
   reset_glyphmap(trigger)

   The glyphmap likely needs to be re-calculated for the following triggers:

   gm_levelchange    called when the player has gone to a new level.

   gm_symchange      called if someone has interactively altered the symbols
                     for the game, most likely at the options menu.

   gm_optionchange   The game settings have been toggled and some of them
                     are configured to trigger a reset_glyphmap()

   gm_accessibility_change  One of the accessibility flags has changed.

*/

void
reset_glyphmap(enum glyphmap_change_triggers trigger)
{
    int glyph;
    register int offset;
    int color = NO_COLOR;

    /* condense multiple tests in macro version down to single */
    boolean has_rogue_ibm_graphics = HAS_ROGUE_IBM_GRAPHICS,
            has_rogue_color = (has_rogue_ibm_graphics
                               && gs.symset[gc.currentgraphics].nocolor == 0);
    if (trigger == gm_levelchange)
        gg.glyphmap_perlevel_flags = 0;

    if (!gg.glyphmap_perlevel_flags) {
        /*
         *    GMAP_SET                0x00000001
         *    GMAP_ROGUELEVEL         0x00000002
         */
        gg.glyphmap_perlevel_flags |= GMAP_SET;

        if (Is_rogue_level(&u.uz)) {
            gg.glyphmap_perlevel_flags |= GMAP_ROGUELEVEL;
        }
    }

    for (glyph = 0; glyph < MAX_GLYPH; ++glyph) {
        glyph_map *gmap = &glyphmap[glyph];

        gmap->glyphflags = 0U;
        /*
         *  Map the glyph to a character and color.
         *
         *  Warning:  For speed, this makes an assumption on the order of
         *            offsets.  The order is set in display.h.
         */
        if ((offset = (glyph - GLYPH_NOTHING_OFF)) >= 0) {
            gmap->sym.symidx = SYM_NOTHING + SYM_OFF_X;
            color = NO_COLOR;
            gmap->glyphflags |= MG_NOTHING;
        } else if ((offset = (glyph - GLYPH_UNEXPLORED_OFF)) >= 0) {
            gmap->sym.symidx = SYM_UNEXPLORED + SYM_OFF_X;
            color = NO_COLOR;
            gmap->glyphflags |= MG_UNEXPL;
        } else if ((offset = (glyph - GLYPH_STATUE_FEM_PILETOP_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = CLR_RED;
            else
                obj_color(STATUE);
            gmap->glyphflags |= (MG_STATUE | MG_FEMALE | MG_OBJPILE);
        } else if ((offset = (glyph - GLYPH_STATUE_MALE_PILETOP_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = CLR_RED;
            else
                obj_color(STATUE);
            gmap->glyphflags |= (MG_STATUE | MG_MALE | MG_OBJPILE);
        } else if ((offset = (glyph - GLYPH_BODY_PILETOP_OFF)) >= 0) {
            gmap->sym.symidx = objects[CORPSE].oc_class + SYM_OFF_O;
            if (has_rogue_color)
                color = CLR_RED;
            else
                mon_color(offset);
            gmap->glyphflags |= (MG_CORPSE | MG_OBJPILE);
        } else if ((offset = (glyph - GLYPH_OBJ_PILETOP_OFF)) >= 0) {
            gmap->sym.symidx = objects[offset].oc_class + SYM_OFF_O;
            if (offset == BOULDER)
                gmap->sym.symidx = SYM_BOULDER + SYM_OFF_X;
            if (has_rogue_color) {
                switch (objects[offset].oc_class) {
                case COIN_CLASS:
                    color = CLR_YELLOW;
                    break;
                case FOOD_CLASS:
                    color = CLR_RED;
                    break;
                default:
                    color = CLR_BRIGHT_BLUE;
                    break;
                }
            } else
                obj_color(offset);
            gmap->glyphflags |= MG_OBJPILE;
        } else if ((offset = (glyph - GLYPH_STATUE_FEM_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = CLR_RED;
            else
                obj_color(STATUE);
            gmap->glyphflags |= (MG_STATUE | MG_FEMALE);
        } else if ((offset = (glyph - GLYPH_STATUE_MALE_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = CLR_RED;
            else
                obj_color(STATUE);
            gmap->glyphflags |= (MG_STATUE | MG_MALE);
        } else if ((offset = (glyph - GLYPH_WARNING_OFF))
                   >= 0) { /* warn flash */
            gmap->sym.symidx = offset + SYM_OFF_W;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                warn_color(offset);
        } else if ((offset = (glyph - GLYPH_EXPLODE_FROSTY_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_frosty);
        } else if ((offset = (glyph - GLYPH_EXPLODE_FIERY_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_fiery);
        } else if ((offset = (glyph - GLYPH_EXPLODE_MAGICAL_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_magical);
        } else if ((offset = (glyph - GLYPH_EXPLODE_WET_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_wet);
        } else if ((offset = (glyph - GLYPH_EXPLODE_MUDDY_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_muddy);
        } else if ((offset = (glyph - GLYPH_EXPLODE_NOXIOUS_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_noxious);
        } else if ((offset = (glyph - GLYPH_EXPLODE_DARK_OFF)) >= 0) {
            gmap->sym.symidx = S_expl_tl + offset + SYM_OFF_P;
            explode_color(expl_dark);
        } else if ((offset = (glyph - GLYPH_SWALLOW_OFF)) >= 0) {
            /* see swallow_to_glyph() in display.c */
            gmap->sym.symidx = (S_sw_tl + (offset & 0x7)) + SYM_OFF_P;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                mon_color(offset >> 3);
        } else if ((offset = (glyph - GLYPH_CMAP_C_OFF)) >= 0) {
            gmap->sym.symidx = S_digbeam + offset + SYM_OFF_P;
            if (has_rogue_color)
                color = cmap_to_roguecolor(S_digbeam + offset);
            else
                cmap_color(S_digbeam + offset);
        } else if ((offset = (glyph - GLYPH_ZAP_OFF)) >= 0) {
            /* see zapdir_to_glyph() in display.c */
            gmap->sym.symidx = (S_vbeam + (offset & 0x3)) + SYM_OFF_P;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                zap_color((offset >> 2));
        } else if ((offset = (glyph - GLYPH_CMAP_B_OFF)) >= 0) {
            int cmap = S_grave + offset;
            int sym = gs.showsyms[cmap + SYM_OFF_P];

            gmap->sym.symidx = cmap + SYM_OFF_P;
            cmap_color(cmap);
            if (!iflags.use_color) {
                unsigned spec_cmap = 0;

                /* try to provide a visible difference between water and lava
                   if they use the same symbol and color is disabled;
                   similar for floor and ice, for fountain vs sink, and for
                   corridor engravings (CMAP_A below) */
                switch (cmap) {
                case S_lava:
                case S_lavawall:
                    if (sym == gs.showsyms[S_pool + SYM_OFF_P]
                        || sym == gs.showsyms[S_water + SYM_OFF_P])
                        spec_cmap = MG_BW_LAVA;
                    break;
                case S_ice:
                    if (sym == gs.showsyms[S_room + SYM_OFF_P]
                        || sym == gs.showsyms[S_darkroom + SYM_OFF_P])
                        spec_cmap = MG_BW_ICE;
                    break;
                case S_sink:
                    if (sym == gs.showsyms[S_fountain + SYM_OFF_P])
                        spec_cmap = MG_BW_SINK;
                    break;
                }
                gmap->glyphflags |= spec_cmap;
            } else if (has_rogue_color) {
                color = cmap_to_roguecolor(cmap);
            }
        } else if ((offset = (glyph - GLYPH_ALTAR_OFF)) >= 0) {
            /* unaligned, chaotic, neutral, lawful, other altar */
            gmap->sym.symidx = S_altar + SYM_OFF_P;
            if (has_rogue_color)
                color = cmap_to_roguecolor(S_altar);
            else
                altar_color(offset);
        } else if ((offset = (glyph - GLYPH_CMAP_A_OFF)) >= 0) {
            int sym, cmap = S_ndoor + offset;

            gmap->sym.symidx = cmap + SYM_OFF_P;
            cmap_color(cmap);
            sym = gs.showsyms[gmap->sym.symidx];
            /*
             *   Some specialty color mappings not hardcoded in data init
             */
            if (has_rogue_color) {
                color = cmap_to_roguecolor(cmap);
#ifdef TEXTCOLOR
            /* provide a visible difference if normal and lit corridor
               use the same symbol */
            } else if (cmap == S_litcorr
                       && sym == gs.showsyms[S_corr + SYM_OFF_P]) {
                color = CLR_WHITE;
#endif
            /* likewise for corridor and engraving-in-corridor */
            } else if (cmap == S_engrcorr
                       && (sym == gs.showsyms[S_corr + SYM_OFF_P]
                           || sym == gs.showsyms[S_litcorr + SYM_OFF_P])) {
                gmap->glyphflags |= MG_BW_ENGR;
            }
        } else if ((offset = (glyph - GLYPH_CMAP_SOKO_OFF)) >= 0) {
            gmap->sym.symidx = S_vwall + offset + SYM_OFF_P;
            wall_color(sokoban_walls);
        } else if ((offset = (glyph - GLYPH_CMAP_KNOX_OFF)) >= 0) {
            gmap->sym.symidx = S_vwall + offset + SYM_OFF_P;
            wall_color(knox_walls);
        } else if ((offset = (glyph - GLYPH_CMAP_GEH_OFF)) >= 0) {
            gmap->sym.symidx = S_vwall + offset + SYM_OFF_P;
            wall_color(gehennom_walls);
        } else if ((offset = (glyph - GLYPH_CMAP_MINES_OFF)) >= 0) {
            gmap->sym.symidx = S_vwall + offset + SYM_OFF_P;
            wall_color(mines_walls);
        } else if ((offset = (glyph - GLYPH_CMAP_MAIN_OFF)) >= 0) {
            gmap->sym.symidx = S_vwall + offset + SYM_OFF_P;
            if (has_rogue_color)
                color = cmap_to_roguecolor(S_vwall + offset);
            else
                wall_color(main_walls);
        } else if ((offset = (glyph - GLYPH_CMAP_STONE_OFF)) >= 0) {
            gmap->sym.symidx = SYM_OFF_P;
            cmap_color(S_stone);
        } else if ((offset = (glyph - GLYPH_OBJ_OFF)) >= 0) {
            gmap->sym.symidx = objects[offset].oc_class + SYM_OFF_O;
            if (offset == BOULDER)
                gmap->sym.symidx = SYM_BOULDER + SYM_OFF_X;
            if (has_rogue_color) {
                switch (objects[offset].oc_class) {
                case COIN_CLASS:
                    color = CLR_YELLOW;
                    break;
                case FOOD_CLASS:
                    color = CLR_RED;
                    break;
                default:
                    color = CLR_BRIGHT_BLUE;
                    break;
                }
            } else
                obj_color(offset);
        } else if ((offset = (glyph - GLYPH_RIDDEN_FEM_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                /* This currently implies that the hero is here -- monsters */
                /* don't ride (yet...).  Should we set it to yellow like in */
                /* the monster case below?  There is no equivalent in rogue.
                 */
                color = NO_COLOR;
            else
                mon_color(offset);
            gmap->glyphflags |= (MG_RIDDEN | MG_FEMALE);
        } else if ((offset = (glyph - GLYPH_RIDDEN_MALE_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                mon_color(offset);
            gmap->glyphflags |= (MG_RIDDEN | MG_MALE);
        } else if ((offset = (glyph - GLYPH_BODY_OFF)) >= 0) {
            gmap->sym.symidx = objects[CORPSE].oc_class + SYM_OFF_O;
            if (has_rogue_color)
                color = CLR_RED;
            else
                mon_color(offset);
            gmap->glyphflags |= MG_CORPSE;
        } else if ((offset = (glyph - GLYPH_DETECT_FEM_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                mon_color(offset);
            /* Disabled for now; anyone want to get reverse video to work? */
            /* is_reverse = TRUE; */
            gmap->glyphflags |= (MG_DETECT | MG_FEMALE);
        } else if ((offset = (glyph - GLYPH_DETECT_MALE_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                mon_color(offset);
            /* Disabled for now; anyone want to get reverse video to work? */
            /* is_reverse = TRUE; */
            gmap->glyphflags |= (MG_DETECT | MG_MALE);
        } else if ((offset = (glyph - GLYPH_INVIS_OFF)) >= 0) {
            gmap->sym.symidx = SYM_INVISIBLE + SYM_OFF_X;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                invis_color(offset);
            gmap->glyphflags |= MG_INVIS;
        } else if ((offset = (glyph - GLYPH_PET_FEM_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                pet_color(offset);
            gmap->glyphflags |= (MG_PET | MG_FEMALE);
        } else if ((offset = (glyph - GLYPH_PET_MALE_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color)
                color = NO_COLOR;
            else
                pet_color(offset);
            gmap->glyphflags |= (MG_PET | MG_MALE);
        } else if ((offset = (glyph - GLYPH_MON_FEM_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color) {
                color = NO_COLOR;
            } else {
                mon_color(offset);
            }
            gmap->glyphflags |= MG_FEMALE;
        } else if ((offset = (glyph - GLYPH_MON_MALE_OFF)) >= 0) {
            gmap->sym.symidx = mons[offset].mlet + SYM_OFF_M;
            if (has_rogue_color) {
                color = CLR_YELLOW;
            } else {
                mon_color(offset);
            }
            gmap->glyphflags |= MG_MALE;
        }
        /* This was requested by a blind player to enhance screen reader use
         */
        if (sysopt.accessibility == 1 && (gmap->glyphflags & MG_PET) != 0) {
            int pet_override = ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL)
                         ? go.ov_rogue_syms[SYM_PET_OVERRIDE + SYM_OFF_X]
                         : go.ov_primary_syms[SYM_PET_OVERRIDE + SYM_OFF_X]);

            if (gs.showsyms[pet_override] != ' ')
                gmap->sym.symidx = SYM_PET_OVERRIDE + SYM_OFF_X;
        }
#ifdef TEXTCOLOR
        /* Turn off color if no color defined, or rogue level w/o PC graphics.
         */
        if ((!has_color(color)
             || ((gg.glyphmap_perlevel_flags & GMAP_ROGUELEVEL)
                 && !has_rogue_color)) || !iflags.use_color)
#endif
            color = NO_COLOR;
        gmap->sym.color = color;
    }
    gg.glyph_reset_timestamp = gm.moves;
}

/* ------------------------------------------------------------------------ */
/* Wall Angle ------------------------------------------------------------- */

#ifdef WA_VERBOSE

static const char *type_to_name(int);
static void error4(coordxy, coordxy, int, int, int, int);

static int bad_count[MAX_TYPE]; /* count of positions flagged as bad */
static const char *const type_names[MAX_TYPE] = {
    "STONE", "VWALL", "HWALL", "TLCORNER", "TRCORNER", "BLCORNER", "BRCORNER",
    "CROSSWALL", "TUWALL", "TDWALL", "TLWALL", "TRWALL", "DBWALL", "TREE",
    "SDOOR", "SCORR", "POOL", "MOAT", "WATER", "DRAWBRIDGE_UP", "LAVAPOOL",
    "LAVAWALL",
    "IRON_BARS", "DOOR", "CORR", "ROOM", "STAIRS", "LADDER", "FOUNTAIN",
    "THRONE", "SINK", "GRAVE", "ALTAR", "ICE", "DRAWBRIDGE_DOWN", "AIR",
    "CLOUD"
};

static const char *
type_to_name(int type)
{
    return (type < 0 || type >= MAX_TYPE) ? "unknown" : type_names[type];
}

static void
error4(coordxy x, coordxy y, int a, int b, int c, int dd)
{
    pline("set_wall_state: %s @ (%d,%d) %s%s%s%s",
          type_to_name(levl[x][y].typ), x, y,
          a ? "1" : "", b ? "2" : "", c ? "3" : "", dd ? "4" : "");
    bad_count[levl[x][y].typ]++;
}
#endif /* WA_VERBOSE */

/*
 * Return 'which' if position is implies an unfinished exterior.  Return
 * zero otherwise.  Unfinished implies outer area is rock or a corridor.
 *
 * Things that are ambiguous: lava
 */
static int
check_pos(coordxy x, coordxy y, int which)
{
    int type;

    if (!isok(x, y))
        return which;
    type = levl[x][y].typ;
    if (IS_ROCK(type) || type == CORR || type == SCORR)
        return which;
    return 0;
}

/* Return TRUE if more than one is non-zero. */
/*ARGSUSED*/
#ifdef WA_VERBOSE
static boolean
more_than_one(coordxy x, coordxy y, coordxy a, coordxy b, coordxy c)
{
    if ((a && (b | c)) || (b && (a | c)) || (c && (a | b))) {
        error4(x, y, a, b, c, 0);
        return TRUE;
    }
    return FALSE;
}
#else
#define more_than_one(x, y, a, b, c) \
    (((a) && ((b) | (c))) || ((b) && ((a) | (c))) || ((c) && ((a) | (b))))
#endif

/* Return the wall mode for a T wall. */
static int
set_twall(
#ifdef WA_VERBOSE
          coordxy x0, coordxy y0, /* used #if WA_VERBOSE */
#else
          coordxy x0 UNUSED, coordxy y0 UNUSED,
#endif
          coordxy x1, coordxy y1, coordxy x2, coordxy y2, coordxy x3, coordxy y3)
{
    int wmode, is_1, is_2, is_3;

    is_1 = check_pos(x1, y1, WM_T_LONG);
    is_2 = check_pos(x2, y2, WM_T_BL);
    is_3 = check_pos(x3, y3, WM_T_BR);
    if (more_than_one(x0, y0, is_1, is_2, is_3)) {
        wmode = 0;
    } else {
        wmode = is_1 + is_2 + is_3;
    }
    return wmode;
}

/* Return wall mode for a horizontal or vertical wall. */
static int
set_wall(coordxy x, coordxy y, int horiz)
{
    int wmode, is_1, is_2;

    if (horiz) {
        is_1 = check_pos(x, y - 1, WM_W_TOP);
        is_2 = check_pos(x, y + 1, WM_W_BOTTOM);
    } else {
        is_1 = check_pos(x - 1, y, WM_W_LEFT);
        is_2 = check_pos(x + 1, y, WM_W_RIGHT);
    }
    if (more_than_one(x, y, is_1, is_2, 0)) {
        wmode = 0;
    } else {
        wmode = is_1 + is_2;
    }
    return wmode;
}

/* Return a wall mode for a corner wall. (x4,y4) is the "inner" position. */
static int
set_corn(coordxy x1, coordxy y1, coordxy x2, coordxy y2, coordxy x3, coordxy y3, coordxy x4, coordxy y4)
{
    coordxy wmode, is_1, is_2, is_3, is_4;

    is_1 = check_pos(x1, y1, 1);
    is_2 = check_pos(x2, y2, 1);
    is_3 = check_pos(x3, y3, 1);
    is_4 = check_pos(x4, y4, 1); /* inner location */

    /*
     * All 4 should not be true.  So if the inner location is rock,
     * use it.  If all of the outer 3 are true, use outer.  We currently
     * can't cover the case where only part of the outer is rock, so
     * we just say that all the walls are finished (if not overridden
     * by the inner section).
     */
    if (is_4) {
        wmode = WM_C_INNER;
    } else if (is_1 && is_2 && is_3)
        wmode = WM_C_OUTER;
    else
        wmode = 0; /* finished walls on all sides */

    return wmode;
}

/* Return mode for a crosswall. */
static int
set_crosswall(coordxy x, coordxy y)
{
    coordxy wmode, is_1, is_2, is_3, is_4;

    is_1 = check_pos(x - 1, y - 1, 1);
    is_2 = check_pos(x + 1, y - 1, 1);
    is_3 = check_pos(x + 1, y + 1, 1);
    is_4 = check_pos(x - 1, y + 1, 1);

    wmode = is_1 + is_2 + is_3 + is_4;
    if (wmode > 1) {
        if (is_1 && is_3 && (is_2 + is_4 == 0)) {
            wmode = WM_X_TLBR;
        } else if (is_2 && is_4 && (is_1 + is_3 == 0)) {
            wmode = WM_X_BLTR;
        } else {
#ifdef WA_VERBOSE
            error4(x, y, is_1, is_2, is_3, is_4);
#endif
            wmode = 0;
        }
    } else if (is_1)
        wmode = WM_X_TL;
    else if (is_2)
        wmode = WM_X_TR;
    else if (is_3)
        wmode = WM_X_BR;
    else if (is_4)
        wmode = WM_X_BL;

    return wmode;
}

/* called for every <x,y> by set_wall_state() and for specific <x,y> during
   vault wall repair */
void
xy_set_wall_state(coordxy x, coordxy y)
{
    coordxy wmode;
    struct rm *lev = &levl[x][y];

    switch (lev->typ) {
    case SDOOR:
        wmode = set_wall(x, y, (coordxy) lev->horizontal);
        break;
    case VWALL:
        wmode = set_wall(x, y, 0);
        break;
    case HWALL:
        wmode = set_wall(x, y, 1);
        break;
    case TDWALL:
        wmode = set_twall(x, y, x, y - 1, x - 1, y + 1, x + 1, y + 1);
        break;
    case TUWALL:
        wmode = set_twall(x, y, x, y + 1, x + 1, y - 1, x - 1, y - 1);
        break;
    case TLWALL:
        wmode = set_twall(x, y, x + 1, y, x - 1, y - 1, x - 1, y + 1);
        break;
    case TRWALL:
        wmode = set_twall(x, y, x - 1, y, x + 1, y + 1, x + 1, y - 1);
        break;
    case TLCORNER:
        wmode = set_corn(x - 1, y - 1, x, y - 1, x - 1, y, x + 1, y + 1);
        break;
    case TRCORNER:
        wmode = set_corn(x, y - 1, x + 1, y - 1, x + 1, y, x - 1, y + 1);
        break;
    case BLCORNER:
        wmode = set_corn(x, y + 1, x - 1, y + 1, x - 1, y, x + 1, y - 1);
        break;
    case BRCORNER:
        wmode = set_corn(x + 1, y, x + 1, y + 1, x, y + 1, x - 1, y - 1);
        break;
    case CROSSWALL:
        wmode = set_crosswall(x, y);
        break;

    default:
        wmode = -1; /* don't set wall info */
        break;
    }

    if (wmode >= 0)
        lev->wall_info = (lev->wall_info & ~WM_MASK) | wmode;
}

/* Called from mklev.  Scan the level and set the wall modes. */
void
set_wall_state(void)
{
    coordxy x, y;

#ifdef WA_VERBOSE
    for (x = 0; x < MAX_TYPE; x++)
        bad_count[x] = 0;
#endif

    for (x = 0; x < COLNO; x++)
        for (y = 0; y < ROWNO; y++)
            xy_set_wall_state(x, y);

#ifdef WA_VERBOSE
    /* check if any bad positions found */
    for (x = y = 0; x < MAX_TYPE; x++)
        if (bad_count[x]) {
            if (y == 0) {
                y = 1; /* only prcoordxy once */
                pline("set_wall_type: wall mode problems with: ");
            }
            pline("%s %d;", type_names[x], bad_count[x]);
        }
#endif /* WA_VERBOSE */
}

/* ------------------------------------------------------------------------ */
/* This matrix is used here and in vision.c. */
const seenV seenv_matrix[3][3] = {
    { SV2, SV1,   SV0 },
    { SV3, SVALL, SV7 },
    { SV4, SV5,   SV6 }
};

/* negative: -1, zero: 0, positive +1; same as sgn() function (hacklib.c) */
#define sign(z) ((z) < 0 ? -1 : ((z) != 0))

/* Set the seen vector of lev as if seen from (x0,y0) to (x,y). */
static void
set_seenv(
    struct rm *lev,
    coordxy x0, coordxy y0, /* from */
    coordxy x, coordxy y)   /*  to  */
{
    coordxy dx = x - x0, dy = y0 - y;

    lev->seenv |= seenv_matrix[sign(dy) + 1][sign(dx) + 1];
}

#undef sign

/* Called by blackout(vault.c) when vault guard removes temporary corridor,
   turning spot <x0,y0> back into stone; <x1,y1> is an adjacent spot. */
void
unset_seenv(
    struct rm *lev,         /* &levl[x1][y1] */
    coordxy x0, coordxy y0, /* from */
    coordxy x1, coordxy y1) /*  to; abs(x1-x0)==1 && abs(y0-y1)==1 */

{
    coordxy dx = x1 - x0, dy = y0 - y1;

    lev->seenv &= ~seenv_matrix[dy + 1][dx + 1];
}

/* ------------------------------------------------------------------------ */

/* T wall types, one for each row in wall_matrix[][]. */
#define T_d 0
#define T_l 1
#define T_u 2
#define T_r 3

/*
 * These are the column names of wall_matrix[][].  They are the "results"
 * of a tdwall pattern match.  All T walls are rotated so they become
 * a tdwall.  Then we do a single pattern match, but return the
 * correct result for the original wall by using different rows for
 * each of the wall types.
 */
#define T_stone 0
#define T_tlcorn 1
#define T_trcorn 2
#define T_hwall 3
#define T_tdwall 4

static const int wall_matrix[4][5] = {
    { S_stone, S_tlcorn, S_trcorn, S_hwall, S_tdwall }, /* tdwall */
    { S_stone, S_trcorn, S_brcorn, S_vwall, S_tlwall }, /* tlwall */
    { S_stone, S_brcorn, S_blcorn, S_hwall, S_tuwall }, /* tuwall */
    { S_stone, S_blcorn, S_tlcorn, S_vwall, S_trwall }, /* trwall */
};

/* Cross wall types, one for each "solid" quarter.  Rows of cross_matrix[][].
 */
#define C_bl 0
#define C_tl 1
#define C_tr 2
#define C_br 3

/*
 * These are the column names for cross_matrix[][].  They express results
 * in C_br (bottom right) terms.  All crosswalls with a single solid
 * quarter are rotated so the solid section is at the bottom right.
 * We pattern match on that, but return the correct result depending
 * on which row we're looking at.
 */
#define C_trcorn 0
#define C_brcorn 1
#define C_blcorn 2
#define C_tlwall 3
#define C_tuwall 4
#define C_crwall 5

static const int cross_matrix[4][6] = {
    { S_brcorn, S_blcorn, S_tlcorn, S_tuwall, S_trwall, S_crwall },
    { S_blcorn, S_tlcorn, S_trcorn, S_trwall, S_tdwall, S_crwall },
    { S_tlcorn, S_trcorn, S_brcorn, S_tdwall, S_tlwall, S_crwall },
    { S_trcorn, S_brcorn, S_blcorn, S_tlwall, S_tuwall, S_crwall },
};

/* Print out a T wall warning and all interesting info. */
static void
t_warn(struct rm *lev)
{
    static const char warn_str[] = "wall_angle: %s: case %d: seenv = 0x%x";
    const char *wname;

    /* 3.7: non-T_wall cases added after shop repair (via breaching a wall,
       using locking magic to put a door there, then unlocking the door;
       D_CLOSED carried over to the wall) triggered warning for "unknown" */
    switch (lev->typ) {
    case TUWALL:
        wname = "tuwall";
        break;
    case TLWALL:
        wname = "tlwall";
        break;
    case TRWALL:
        wname = "trwall";
        break;
    case TDWALL:
        wname = "tdwall";
        break;
    case VWALL:
        wname = "vwall";
        break;
    case HWALL:
        wname = "hwall";
        break;
    case TLCORNER:
        wname = "tlcorner";
        break;
    case TRCORNER:
        wname = "trcorner";
        break;
    case BLCORNER:
        wname = "blcorner";
        break;
    case BRCORNER:
        wname = "brcorner";
        break;
    default:
        wname = "unknown";
        break;
    }
    impossible(warn_str, wname, lev->wall_info & WM_MASK,
               (unsigned int) lev->seenv);
}

/*
 * Return the correct graphics character index using wall type, wall mode,
 * and the seen vector.  It is expected that seenv is non zero.
 *
 * All T-wall vectors are rotated to be TDWALL.  All single crosswall
 * blocks are rotated to bottom right.  All double crosswall are rotated
 * to W_X_BLTR.  All results are converted back.
 *
 * The only way to understand this is to take out pen and paper and
 * draw diagrams.  See rm.h for more details on the wall modes and
 * seen vector (SV).
 */
static int
wall_angle(struct rm *lev)
{
    register unsigned int seenv = lev->seenv & 0xff;
    const int *row;
    int col, idx;

#define only(sv, bits) (((sv) & (bits)) && !((sv) & ~(bits)))
    switch (lev->typ) {
    case TUWALL:
        row = wall_matrix[T_u];
        seenv = (seenv >> 4 | seenv << 4) & 0xff; /* rotate to tdwall */
        goto do_twall;
    case TLWALL:
        row = wall_matrix[T_l];
        seenv = (seenv >> 2 | seenv << 6) & 0xff; /* rotate to tdwall */
        goto do_twall;
    case TRWALL:
        row = wall_matrix[T_r];
        seenv = (seenv >> 6 | seenv << 2) & 0xff; /* rotate to tdwall */
        goto do_twall;
    case TDWALL:
        row = wall_matrix[T_d];
 do_twall:
        switch (lev->wall_info & WM_MASK) {
        case 0:
            if (seenv == SV4) {
                col = T_tlcorn;
            } else if (seenv == SV6) {
                col = T_trcorn;
            } else if (seenv & (SV3 | SV5 | SV7)
                       || ((seenv & SV4) && (seenv & SV6))) {
                col = T_tdwall;
            } else if (seenv & (SV0 | SV1 | SV2)) {
                col = (seenv & (SV4 | SV6) ? T_tdwall : T_hwall);
            } else {
                t_warn(lev);
                col = T_stone;
            }
            break;
        case WM_T_LONG:
            if (seenv & (SV3 | SV4) && !(seenv & (SV5 | SV6 | SV7))) {
                col = T_tlcorn;
            } else if (seenv & (SV6 | SV7) && !(seenv & (SV3 | SV4 | SV5))) {
                col = T_trcorn;
            } else if ((seenv & SV5)
                       || ((seenv & (SV3 | SV4)) && (seenv & (SV6 | SV7)))) {
                col = T_tdwall;
            } else {
                /* only SV0|SV1|SV2 */
                if (!only(seenv, SV0 | SV1 | SV2))
                    t_warn(lev);
                col = T_stone;
            }
            break;
        case WM_T_BL:
#if 0  /* older method, fixed */
            if (only(seenv, SV4 | SV5)) {
                col = T_tlcorn;
            } else if ((seenv & (SV0 | SV1 | SV2))
                       && only(seenv, SV0 | SV1 | SV2 | SV6 | SV7)) {
                col = T_hwall;
            } else if ((seenv & SV3)
                       || ((seenv & (SV0 | SV1 | SV2))
                           && (seenv & (SV4 | SV5)))) {
                col = T_tdwall;
            } else {
                if (seenv != SV6)
                    t_warn(lev);
                col = T_stone;
            }
#endif /* 0 */
            if (only(seenv, SV4 | SV5))
                col = T_tlcorn;
            else if ((seenv & (SV0 | SV1 | SV2 | SV7))
                     && !(seenv & (SV3 | SV4 | SV5)))
                col = T_hwall;
            else if (only(seenv, SV6))
                col = T_stone;
            else
                col = T_tdwall;
            break;
        case WM_T_BR:
#if 0  /* older method, fixed */
            if (only(seenv, SV5 | SV6)) {
                col = T_trcorn;
            } else if ((seenv & (SV0 | SV1 | SV2))
                       && only(seenv, SV0 | SV1 | SV2 | SV3 | SV4)) {
                col = T_hwall;
            } else if ((seenv & SV7)
                       || ((seenv & (SV0 | SV1 | SV2))
                           && (seenv & (SV5 | SV6)))) {
                col = T_tdwall;
            } else {
                if (seenv != SV4)
                    t_warn(lev);
                col = T_stone;
            }
#endif /* 0 */
            if (only(seenv, SV5 | SV6))
                col = T_trcorn;
            else if ((seenv & (SV0 | SV1 | SV2 | SV3))
                     && !(seenv & (SV5 | SV6 | SV7)))
                col = T_hwall;
            else if (only(seenv, SV4))
                col = T_stone;
            else
                col = T_tdwall;

            break;
        default:
            impossible("wall_angle: unknown T wall mode %d",
                       lev->wall_info & WM_MASK);
            col = T_stone;
            break;
        }
        idx = row[col];
        break;

    case SDOOR:
        if (lev->horizontal)
            goto horiz;
        /*FALLTHRU*/
    case VWALL:
        switch (lev->wall_info & WM_MASK) {
        case 0:
            idx = seenv ? S_vwall : S_stone;
            break;
        case 1:
            idx = seenv & (SV1 | SV2 | SV3 | SV4 | SV5) ? S_vwall : S_stone;
            break;
        case 2:
            idx = seenv & (SV0 | SV1 | SV5 | SV6 | SV7) ? S_vwall : S_stone;
            break;
        default:
            impossible("wall_angle: unknown vwall mode %d",
                       lev->wall_info & WM_MASK);
            idx = S_stone;
            break;
        }
        break;

    case HWALL:
 horiz:
        switch (lev->wall_info & WM_MASK) {
        case 0:
            idx = seenv ? S_hwall : S_stone;
            break;
        case 1:
            idx = seenv & (SV3 | SV4 | SV5 | SV6 | SV7) ? S_hwall : S_stone;
            break;
        case 2:
            idx = seenv & (SV0 | SV1 | SV2 | SV3 | SV7) ? S_hwall : S_stone;
            break;
        default:
            impossible("wall_angle: unknown hwall mode %d",
                       lev->wall_info & WM_MASK);
            idx = S_stone;
            break;
        }
        break;

#define set_corner(idx, lev, which, outer, inner, name)    \
    switch ((lev)->wall_info & WM_MASK) {                  \
    case 0:                                                \
        idx = which;                                       \
        break;                                             \
    case WM_C_OUTER:                                       \
        idx = seenv & (outer) ? which : S_stone;           \
        break;                                             \
    case WM_C_INNER:                                       \
        idx = seenv & ~(inner) ? which : S_stone;          \
        break;                                             \
    default:                                               \
        impossible("wall_angle: unknown %s mode %d", name, \
                   (lev)->wall_info &WM_MASK);             \
        idx = S_stone;                                     \
        break;                                             \
    }

    case TLCORNER:
        set_corner(idx, lev, S_tlcorn, (SV3 | SV4 | SV5), SV4, "tlcorn");
        break;
    case TRCORNER:
        set_corner(idx, lev, S_trcorn, (SV5 | SV6 | SV7), SV6, "trcorn");
        break;
    case BLCORNER:
        set_corner(idx, lev, S_blcorn, (SV1 | SV2 | SV3), SV2, "blcorn");
        break;
    case BRCORNER:
        set_corner(idx, lev, S_brcorn, (SV7 | SV0 | SV1), SV0, "brcorn");
        break;

    case CROSSWALL:
        switch (lev->wall_info & WM_MASK) {
        case 0:
            if (seenv == SV0)
                idx = S_brcorn;
            else if (seenv == SV2)
                idx = S_blcorn;
            else if (seenv == SV4)
                idx = S_tlcorn;
            else if (seenv == SV6)
                idx = S_trcorn;
            else if (!(seenv & ~(SV0 | SV1 | SV2))
                     && (seenv & SV1 || seenv == (SV0 | SV2)))
                idx = S_tuwall;
            else if (!(seenv & ~(SV2 | SV3 | SV4))
                     && (seenv & SV3 || seenv == (SV2 | SV4)))
                idx = S_trwall;
            else if (!(seenv & ~(SV4 | SV5 | SV6))
                     && (seenv & SV5 || seenv == (SV4 | SV6)))
                idx = S_tdwall;
            else if (!(seenv & ~(SV0 | SV6 | SV7))
                     && (seenv & SV7 || seenv == (SV0 | SV6)))
                idx = S_tlwall;
            else
                idx = S_crwall;
            break;

        case WM_X_TL:
            row = cross_matrix[C_tl];
            seenv = (seenv >> 4 | seenv << 4) & 0xff;
            goto do_crwall;
        case WM_X_TR:
            row = cross_matrix[C_tr];
            seenv = (seenv >> 6 | seenv << 2) & 0xff;
            goto do_crwall;
        case WM_X_BL:
            row = cross_matrix[C_bl];
            seenv = (seenv >> 2 | seenv << 6) & 0xff;
            goto do_crwall;
        case WM_X_BR:
            row = cross_matrix[C_br];
 do_crwall:
            if (seenv == SV4) {
                idx = S_stone;
            } else {
                seenv = seenv & ~SV4; /* strip SV4 */
                if (seenv == SV0) {
                    col = C_brcorn;
                } else if (seenv & (SV2 | SV3)) {
                    if (seenv & (SV5 | SV6 | SV7))
                        col = C_crwall;
                    else if (seenv & (SV0 | SV1))
                        col = C_tuwall;
                    else
                        col = C_blcorn;
                } else if (seenv & (SV5 | SV6)) {
                    if (seenv & (SV1 | SV2 | SV3))
                        col = C_crwall;
                    else if (seenv & (SV0 | SV7))
                        col = C_tlwall;
                    else
                        col = C_trcorn;
                } else if (seenv & SV1) {
                    col = seenv & SV7 ? C_crwall : C_tuwall;
                } else if (seenv & SV7) {
                    col = seenv & SV1 ? C_crwall : C_tlwall;
                } else {
                    impossible("wall_angle: bottom of crwall check");
                    col = C_crwall;
                }

                idx = row[col];
            }
            break;

        case WM_X_TLBR:
            if (only(seenv, SV1 | SV2 | SV3))
                idx = S_blcorn;
            else if (only(seenv, SV5 | SV6 | SV7))
                idx = S_trcorn;
            else if (only(seenv, SV0 | SV4))
                idx = S_stone;
            else
                idx = S_crwall;
            break;

        case WM_X_BLTR:
            if (only(seenv, SV0 | SV1 | SV7))
                idx = S_brcorn;
            else if (only(seenv, SV3 | SV4 | SV5))
                idx = S_tlcorn;
            else if (only(seenv, SV2 | SV6))
                idx = S_stone;
            else
                idx = S_crwall;
            break;

        default:
            impossible("wall_angle: unknown crosswall mode");
            idx = S_stone;
            break;
        }
        break;

    default:
        impossible("wall_angle: unexpected wall type %d", lev->typ);
        idx = S_stone;
    }
    return idx;
}

/*
 * c++ 20 has problems with some of the display.h macros because
 * comparisons and bit-fiddling and math between different enums
 * is deprecated.
 * Create function versions of some of the macros used in some
 * NetHack c++ source files (Qt) for use there.
 */
int
fn_cmap_to_glyph(int cmap)
{
    return cmap_to_glyph(cmap);
}

/* for 'onefile' processing where end of this file isn't necessarily the
   end of the source code seen by the compiler (there are lots of other
   macros defined above...) */
#undef remember_topology
#undef _glyph_at
#undef DETECTED
#undef PHYSICALLY_SEEN
#undef is_worm_tail
#undef TMP_AT_MAX_GLYPHS
#undef Glyphinfo_at
#undef reset_glyph_bbox
#undef HAS_ROGUE_IBM_GRAPHICS
#undef GMAP_SET
#undef GMAP_ROGUELEVEL

/*display.c*/
