Trying to print out natural-language glyph meanings

Over on the Discord I found out about this wonderful little piece of code, that gave me a reference for what actions have what numbers:

import gym
import nle
env = gym.make('NetHackChallenge-v0')
for i, a in enumerate(env._actions):
    print(f'action {i} is ', a, f' (= keypress {a})')

Now I’m looking for a similar script to print out the meaning of each glyph number. Anyone have code handy?

3 Likes

Thank you for sharing this! For reference for anyone else, here is the output of that command:

action 0 is CompassDirection.N (= keypress 107)
action 1 is CompassDirection.E (= keypress 108)
action 2 is CompassDirection.S (= keypress 106)
action 3 is CompassDirection.W (= keypress 104)
action 4 is CompassDirection.NE (= keypress 117)
action 5 is CompassDirection.SE (= keypress 110)
action 6 is CompassDirection.SW (= keypress 98)
action 7 is CompassDirection.NW (= keypress 121)
action 8 is CompassDirectionLonger.N (= keypress 75)
action 9 is CompassDirectionLonger.E (= keypress 76)
action 10 is CompassDirectionLonger.S (= keypress 74)
action 11 is CompassDirectionLonger.W (= keypress 72)
action 12 is CompassDirectionLonger.NE (= keypress 85)
action 13 is CompassDirectionLonger.SE (= keypress 78)
action 14 is CompassDirectionLonger.SW (= keypress 66)
action 15 is CompassDirectionLonger.NW (= keypress 89)
action 16 is MiscDirection.UP (= keypress 60)
action 17 is MiscDirection.DOWN (= keypress 62)
action 18 is MiscDirection.WAIT (= keypress 46)
action 19 is MiscAction.MORE (= keypress 13)
action 20 is Command.EXTCMD (= keypress 35)
action 21 is Command.EXTLIST (= keypress 191)
action 22 is Command.ADJUST (= keypress 225)
action 23 is Command.ANNOTATE (= keypress 193)
action 24 is Command.APPLY (= keypress 97)
action 25 is Command.ATTRIBUTES (= keypress 24)
action 26 is Command.AUTOPICKUP (= keypress 64)
action 27 is Command.CALL (= keypress 67)
action 28 is Command.CAST (= keypress 90)
action 29 is Command.CHAT (= keypress 227)
action 30 is Command.CLOSE (= keypress 99)
action 31 is Command.CONDUCT (= keypress 195)
action 32 is Command.DIP (= keypress 228)
action 33 is Command.DROP (= keypress 100)
action 34 is Command.DROPTYPE (= keypress 68)
action 35 is Command.EAT (= keypress 101)
action 36 is Command.ESC (= keypress 27)
action 37 is Command.ENGRAVE (= keypress 69)
action 38 is Command.ENHANCE (= keypress 229)
action 39 is Command.FIRE (= keypress 102)
action 40 is Command.FIGHT (= keypress 70)
action 41 is Command.FORCE (= keypress 230)
action 42 is Command.GLANCE (= keypress 59)
action 43 is Command.HISTORY (= keypress 86)
action 44 is Command.INVENTORY (= keypress 105)
action 45 is Command.INVENTTYPE (= keypress 73)
action 46 is Command.INVOKE (= keypress 233)
action 47 is Command.JUMP (= keypress 234)
action 48 is Command.KICK (= keypress 4)
action 49 is Command.KNOWN (= keypress 92)
action 50 is Command.KNOWNCLASS (= keypress 96)
action 51 is Command.LOOK (= keypress 58)
action 52 is Command.LOOT (= keypress 236)
action 53 is Command.MONSTER (= keypress 237)
action 54 is Command.MOVE (= keypress 109)
action 55 is Command.MOVEFAR (= keypress 77)
action 56 is Command.OFFER (= keypress 239)
action 57 is Command.OPEN (= keypress 111)
action 58 is Command.OPTIONS (= keypress 79)
action 59 is Command.OVERVIEW (= keypress 15)
action 60 is Command.PAY (= keypress 112)
action 61 is Command.PICKUP (= keypress 44)
action 62 is Command.PRAY (= keypress 240)
action 63 is Command.PUTON (= keypress 80)
action 64 is Command.QUAFF (= keypress 113)
action 65 is Command.QUIT (= keypress 241)
action 66 is Command.QUIVER (= keypress 81)
action 67 is Command.READ (= keypress 114)
action 68 is Command.REDRAW (= keypress 18)
action 69 is Command.REMOVE (= keypress 82)
action 70 is Command.RIDE (= keypress 210)
action 71 is Command.RUB (= keypress 242)
action 72 is Command.RUSH (= keypress 103)
action 73 is Command.RUSH2 (= keypress 71)
action 74 is Command.SAVE (= keypress 83)
action 75 is Command.SEARCH (= keypress 115)
action 76 is Command.SEEALL (= keypress 42)
action 77 is Command.SEETRAP (= keypress 94)
action 78 is Command.SIT (= keypress 243)
action 79 is Command.SWAP (= keypress 120)
action 80 is Command.TAKEOFF (= keypress 84)
action 81 is Command.TAKEOFFALL (= keypress 65)
action 82 is Command.TELEPORT (= keypress 20)
action 83 is Command.THROW (= keypress 116)
action 84 is Command.TIP (= keypress 212)
action 85 is Command.TRAVEL (= keypress 95)
action 86 is Command.TURN (= keypress 244)
action 87 is Command.TWOWEAPON (= keypress 88)
action 88 is Command.UNTRAP (= keypress 245)
action 89 is Command.VERSION (= keypress 246)
action 90 is Command.VERSIONSHORT (= keypress 118)
action 91 is Command.WEAR (= keypress 87)
action 92 is Command.WHATDOES (= keypress 38)
action 93 is Command.WHATIS (= keypress 47)
action 94 is Command.WIELD (= keypress 119)
action 95 is Command.WIPE (= keypress 247)
action 96 is Command.ZAP (= keypress 122)
action 97 is TextCharacters.PLUS (= keypress 43)
action 98 is TextCharacters.MINUS (= keypress 45)
action 99 is TextCharacters.SPACE (= keypress 32)
action 100 is TextCharacters.APOS (= keypress 39)
action 101 is TextCharacters.QUOTE (= keypress 34)
action 102 is TextCharacters.NUM_0 (= keypress 48)
action 103 is TextCharacters.NUM_1 (= keypress 49)
action 104 is TextCharacters.NUM_2 (= keypress 50)
action 105 is TextCharacters.NUM_3 (= keypress 51)
action 106 is TextCharacters.NUM_4 (= keypress 52)
action 107 is TextCharacters.NUM_5 (= keypress 53)
action 108 is TextCharacters.NUM_6 (= keypress 54)
action 109 is TextCharacters.NUM_7 (= keypress 55)
action 110 is TextCharacters.NUM_8 (= keypress 56)
action 111 is TextCharacters.NUM_9 (= keypress 57)

So it looks like we can’t press arbitrary letter keys – will that make it difficult to interact with inventory items that are assigned to letters that don’t already overlap with another action…?

I.E., if the Amulet of Yendor is assigned to inventory letter “X”, because there is no command associated with a capital X, then will we not be able to drop it, #offer it, nor swap it into another inventory slot?

Nethack wiki says “X” is mapped to two weapon combat, so action 87 should be what you’re looking for to refer to the Amulet.

Practically every key on the keyboard (including both uppercase and lowercase letters) is mapped to SOMETHING in nethack. +, -, space, ', ", and the numbers are probably the only exceptions.

Aaah, right you are – thanks! So maybe we really can do everything here. Much appreciated!

Answer:

Glyphs are divided into classes and not all glyphs have easily accessible ‘natural language’ descriptions. For instance, the 4 glyphs that represent the beam of a wand (in each direction) do not have special string in NetHack associated with them. However, for monsters and objects there are ready descriptions. To get something printed out for all the glyphs, use something like:

NB: This answer applies from nle==0.7.2 - before this release, objects give the wrong descriptions

from nle import nethack as nh

obj_classes = {getattr(nh, x): x for x in dir(nh) if x.endswith('_CLASS')}
glyph_classes = sorted((getattr(nh, x), x) 
                       for x in dir(nh) if x.endswith('_OFF'))

for i in range(nh.MAX_GLYPH):
    desc = ''
    if glyph_classes and i == glyph_classes[0][0]:
        cls = glyph_classes.pop(0)[1]
    
    if nh.glyph_is_monster(i):
        desc = f': "{nh.permonst(nh.glyph_to_mon(i)).mname}"'
    
    if nh.glyph_is_normal_object(i):
        obj = nh.objclass(nh.glyph_to_obj(i))
        appearance = nh.OBJ_DESCR(obj) or nh.OBJ_NAME(obj) 
        oclass = ord(obj.oc_class)
        desc = f': {obj_classes[oclass]}: "{appearance}"'

    print(f'Glyph {i} Type: {cls.replace("_OFF","")} {desc}'  )

Finally, it’s worth noting that the glyph does not give the full state of the object - just how what tile the window should use to render the image. The glyph text above will give you ‘short sword’ but not ‘+1 blessed rusted short sword’. In NLE’s case the glyph will tell you what the appearance of a potion is, but not what you might have identified it as. For that you should use the GLANCE or LOOK commands, where you ask the game to give you a description of whats on a certain tile.

More About Glyph Classes:

What is a Glyph?

From display.h:

/*
 * A glyph is an abstraction that represents a _unique_ monster, object,
 * dungeon part, or effect.  The uniqueness is important.  For example,
 * It is not enough to have four (one for each "direction") zap beam glyphs,
 * we need a set of four for each beam type.  Why go to so much trouble?
 * Because it is possible that any given window dependent display driver
 * [print_glyph()] can produce something different for each type of glyph.
 * That is, a beam of cold and a beam of fire would not only be different
 * colors, but would also be represented by different symbols.
 *
 * Glyphs are grouped for easy accessibility:
 *
 * monster      Represents all the wild (not tame) monsters.  Count: NUMMONS.
 *
 * pet          Represents all of the tame monsters.  Count: NUMMONS
 *
 * invisible    Invisible monster placeholder.  Count: 1
 *
 * detect       Represents all detected monsters.  Count: NUMMONS
 *
 * corpse       One for each monster.  Count: NUMMONS
 *
 * ridden       Represents all monsters being ridden.  Count: NUMMONS
 *
 * object       One for each object.  Count: NUM_OBJECTS
 *
 * cmap         One for each entry in the character map.  The character map
 *              is the dungeon features and other miscellaneous things.
 *              Count: MAXPCHARS
 *
 * explosions   A set of nine for each of the following seven explosion types:
 *                   dark, noxious, muddy, wet, magical, fiery, frosty.
 *              The nine positions represent those surrounding the hero.
 *              Count: MAXEXPCHARS * EXPL_MAX (EXPL_MAX is defined in hack.h)
 *
 * zap beam     A set of four (there are four directions) for each beam type.
 *              The beam type is shifted over 2 positions and the direction
 *              is stored in the lower 2 bits.  Count: NUM_ZAP << 2
 *
 * swallow      A set of eight for each monster.  The eight positions rep-
 *              resent those surrounding the hero.  The monster number is
 *              shifted over 3 positions and the swallow position is stored
 *              in the lower three bits.  Count: NUMMONS << 3
 *
 * warning      A set of six representing the different warning levels.
 *
 * statue       One for each monster.  Count: NUMMONS

A glyph is simply a unique id that codes for a visual representation of the dungeon. NetHack provides these to allow different ports of the game to display things however they like. For instance, when you are playing iNetHack, or BrowserHack - two NetHack ports/versions that use image based tiles for the graphical interface - you could make a pet dragon looks could look cuter than a non-pet dragon, and the same for a ridden dragon. The core source code allows developers to easily port to a visual version of the game as they please.

So the glyph id is for unique visualisation of the dungeon, and they are composed as a giant enum. All off the enum offsets are available on the nethack object in NLE, and you can more closesly look at the static functions and enums by ready /win/rl/pynethack.cc in the NLE repo.

With objects, the situation gets a little more complicated, as each glyph which is an object (eg ring of polymorph) also has a unique description/material (iron ring) defined statically, but which are then dynamically shuffled at the start of the game. The functions on nethack are all defined statically, so if the identities have been shuffled (eg for scrolls) OBJ_DESCR will return the correct description, but OBJ_NAME will not. So OBJ_NAME will only be guaranteed to be the correct string, when OBJ_DESCR is None (i.e. not used/there is no shuffling).

Learning More

If you want to learn more about glyphs, the best thing to do is to read /win/rl/pynethack.cc or poke around the nethack object, or if you want even more complete descriptions try the GLANCE or LOOK commands, or (cheat) you can get a text description of an object by picking it up and checking inv_strs

4 Likes

I have generated some (preliminary) docs based on the information in this thread (including output from eric_hammy’s code) that people may or may not find helpful:

3 Likes