Files
rgoue/ARCHITECTURE.md
sneak 43c59b7f82 Update module base path to git.eeqj.de/sneak/rgoue
go.mod, term/ and cmd/ imports, the ARCHITECTURE.md module references,
and the in-game version string. User request; the repo already lives
at this remote.
2026-07-06 23:10:39 +02:00

100 KiB
Raw Blame History

Rogue 5.4.4 — Architecture

This document has two parts:

  • Part 1 — The C Program: a complete structural map of the classic C codebase (every file, every function, every type, every global) as it exists on the modern-rogue branch.
  • Part 2 — The Go Port Design: the object-oriented architecture for the Go port (git.eeqj.de/sneak/rgoue), including how every C construct maps onto Go types and methods.

Part 1 — The C Program

1. Overview

Rogue 5.4.4 is a single-process, single-threaded, turn-based terminal game of ~16,800 lines of pre-C99 C across 36 files. Its architecture is that of a classic 1980s BSD game:

  • All state is global. ~120 global variables (defined in extern.c, declared in rogue.h/extern.h) hold the entire game state: the player, the level map, monster and object lists, UI state, and configuration.
  • One data type to rule them all. A single C union thing (THING) represents both creatures (player/monsters) and objects (weapons, potions, ...), discriminated by which union arm the code chooses to access. Doubly linked lists of THINGs (via embedded l_next/l_prev pointers) form the monster list, the level-object list, and every creature's pack.
  • The screen is the data structure. Much game logic reads what character is displayed at a map cell (via curses mvinch or the places[] shadow map) to decide behavior — walls, doors, items, and monsters are identified by their display glyph.
  • Turn engine via daemons and fuses. A 20-slot table of function pointers runs recurring per-turn callbacks ("daemons": healing, hunger, wandering monsters) and one-shot countdowns ("fuses": potion effects wearing off), each in a BEFORE or AFTER phase around the player's command.
  • Determinism by seeded LCG. All randomness flows through rnd() using a hand-rolled linear congruential generator (seed = seed*11109+13849), so a given seed (dungeon number dnum) reproduces a dungeon exactly.

Program flow

main()
  ├─ parse args/env (SEED, ROGUEOPTS, save file to restore, -s scores, -d death demo)
  ├─ init_probs/init_player/init_names/init_colors/init_stones/init_materials
  ├─ curses initscr(), setup() signals/tty
  ├─ new_level()                      # generate level 1
  ├─ start_daemon(runners/doctor/stomach/swander...), fuse(...)
  └─ playit()
       └─ while (playing)
            command()
              ├─ do_daemons(BEFORE); do_fuses(BEFORE)
              ├─ status(); readchar()  # input (with repeat counts, run prefixes)
              ├─ switch(ch) → do_move/search/quaff/read_scroll/wield/... 
              └─ if (after): do_daemons(AFTER); do_fuses(AFTER)
                              # the `runners` AFTER daemon moves all monsters
  death()/total_winner()/quit() → score() → exit

2. Source file inventory

File Lines Role
rogue.h 753 Master header: all constants, macros, types, extern declarations
extern.h 197 Platform feature detection, mdport/mach_dep declarations
score.h 26 Scoreboard entry struct
main.c 395 Entry point, RNG, main loop, signals
command.c 820 Keyboard command dispatch
io.c 277 Message line, status line, input
init.c 447 Per-game randomization and data-table setup
extern.c 391 All global variable definitions + data tables
new_level.c 231 Level orchestration, item placement, treasure rooms
rooms.c 472 Room and maze generation, room enter/leave
passages.c 424 Corridor generation, passage numbering
move.c 425 Player movement, traps
chase.c 541 Monster AI (chase/flee/targeting), visibility
monsters.c 252 Monster creation, saving throws
fight.c 686 Combat resolution, special monster attacks
things.c 713 Item naming, random item creation, discoveries
pack.c 503 Inventory management, item selection UI
potions.c 375 14 potion effects
scrolls.c 329 18 scroll effects
rings.c 204 14 ring types, wearing/removal
sticks.c 431 14 wand/staff types, bolts
weapons.c 288 9 weapon types, throwing
armor.c 89 8 armor types, wearing
misc.c 597 look() display update, direction input, eat, level-up
daemon.c 181 Daemon/fuse scheduler
daemons.c 295 The 11 daemon/fuse callbacks
list.c 113 THING linked-list allocation
rip.c 448 Tombstone, victory, scoreboard display
save.c 390 Save/restore, XOR-encrypted I/O, score file I/O
state.c 2134 Hand-written serialization of every global
options.c 501 Options screen, ROGUEOPTS parsing
wizard.c 284 Debug/wizard commands, item identification
mach_dep.c 457 Score file locking, tty/signal setup, vestigial load checks
mdport.c 1432 Platform abstraction (the 900-line md_readchar keyboard decoder)
xcrypt.c 707 DES crypt(3) for the wizard password only
vers.c 17 Version string + XOR key strings

3. Type system

3.1 coord — position

typedef struct { int x; int y; } coord;

Used for every position: hero, monsters, room corners, exits, deltas.

3.2 str_t — strength

typedef unsigned int str_t;   /* strength 3..31 */

3.3 struct stats — fighting statistics (player and monsters)

struct stats {
    str_t s_str;      /* strength (player: 3-31; monsters: fixed 10) */
    int   s_exp;      /* experience value/points */
    int   s_lvl;      /* level of mastery */
    int   s_arm;      /* armor class (lower = better) */
    int   s_hpt;      /* current hit points */
    char  s_dmg[13];  /* damage dice string, e.g. "1x4/1x2" */
    int   s_maxhp;    /* max hit points */
};

3.4 union thing (THING) — the universal entity

The central type: a union of a creature arm (_t) and an object arm (_o), with the two link pointers common to both. Access is via field macros (t_pos_t._t_pos, o_type_o._o_type, ...).

union thing {
    struct {                            /* creature (player/monster) */
        union thing *_l_next, *_l_prev; /* list links */
        coord  _t_pos;                  /* position */
        bool   _t_turn;                 /* slowed: is it our turn to move */
        char   _t_type;                 /* monster letter 'A'-'Z'; player '@' */
        char   _t_disguise;             /* what a xeroc looks like */
        char   _t_oldch;                /* map char under the monster */
        coord *_t_dest;                 /* chase target (points at hero or gold) */
        short  _t_flags;                /* ISRUN|ISHELD|ISINVIS|... state bits */
        struct stats _t_stats;
        struct room *_t_room;           /* room the creature is in */
        union thing *_t_pack;           /* carried items (list) */
        int    _t_reserved;             /* save-file fixup index */
    } _t;
    struct {                            /* object */
        union thing *_l_next, *_l_prev; /* list links */
        int    _o_type;                 /* POTION/SCROLL/WEAPON/... glyph */
        coord  _o_pos;                  /* where it lies on the floor */
        char  *_o_text;                 /* scroll gibberish text */
        int    _o_launch;               /* weapon needed to launch it */
        char   _o_packch;               /* inventory letter */
        char   _o_damage[8];            /* wield damage "3x4" */
        char   _o_hurldmg[8];           /* thrown damage */
        int    _o_count;                /* stack count */
        int    _o_which;                /* subtype index (P_HEALING, MACE...) */
        int    _o_hplus, _o_dplus;      /* hit/damage enchantment */
        int    _o_arm;                  /* armor class / charges / gold value (o_charges, o_goldval alias) */
        int    _o_flags;                /* ISCURSED|ISKNOW|... */
        int    _o_group;                /* stack group id */
        char  *_o_label;                /* player-applied name */
    } _o;
};
typedef union thing THING;

Key consequences the port must respect:

  • winat(y,x) display logic: a monster's disguise is shown, not its type.
  • o_arm is overloaded three ways: armor class (armor), charge count (sticks, via o_charges), gold value (gold piles, via o_goldval).
  • t_dest aliases other entities' interiors: it points at hero (player position), a room's r_gold coordinate, or another monster's t_pos. The save format encodes this as (kind, index) pairs.

3.5 PLACE — one map cell, and the places[] map

typedef struct {
    char   p_ch;      /* base map character (wall, floor, door, item glyph) */
    char   p_flags;   /* F_PASS|F_SEEN|F_DROPPED|F_LOCKED|F_REAL|F_PNUM|F_TMASK */
    THING *p_monst;   /* monster standing here, if any */
} PLACE;

PLACE places[MAXLINES*MAXCOLS];      /* column-major: INDEX(y,x) = &places[(x<<5)+y] */

Access macros: chat(y,x) (char), flat(y,x) (flags), moat(y,x) (monster), winat(y,x) (what's visible: monster disguise or map char). Note the column-major (x << 5) + y layout (32 = MAXLINES rows per column).

3.6 struct room — rooms and passages

struct room {
    coord r_pos;       /* upper-left corner */
    coord r_max;       /* size */
    coord r_gold;      /* gold location */
    int   r_goldval;
    short r_flags;     /* ISDARK | ISGONE | ISMAZE */
    int   r_nexits;
    coord r_exit[12];  /* door positions */
};

rooms[MAXROOMS=9] on a 3×3 grid; passages[MAXPASS=13] reuses the same struct for corridor networks (exits = doors touched, flags = ISGONE|ISDARK).

3.7 struct monster — bestiary entry

struct monster {
    char *m_name;            /* "aquator", "bat", ... */
    int   m_carry;           /* % chance of carrying an item */
    short m_flags;           /* ISMEAN|ISFLY|ISREGEN|ISGREED|ISINVIS|CANHUH */
    struct stats m_stats;    /* starting stats incl. damage string */
};
extern struct monster monsters[26];   /* indexed by letter - 'A' */

3.8 struct obj_info — item class descriptor

struct obj_info {
    char *oi_name;    /* true name ("healing", "mace") */
    int   oi_prob;    /* generation probability (converted to cumulative) */
    int   oi_worth;   /* score value */
    char *oi_guess;   /* player's "call it" name */
    bool  oi_know;    /* has the player identified this type */
};

Seven tables: things[7] (category weights), pot_info[14], scr_info[18], ring_info[14], ws_info[14], weap_info[9+1], arm_info[8].

3.9 struct delayed_action — daemon/fuse slot

struct delayed_action {
    int d_type;            /* EMPTY / BEFORE / AFTER */
    void (*d_func)(int);   /* callback — identity of the entry */
    int d_arg;
    int d_time;            /* DAEMON sentinel (-1) or countdown */
} d_list[MAXDAEMONS=20];

3.10 Others

  • struct h_list { char h_ch; char *h_desc; bool h_print; } — help table rows.
  • STONE { char *st_name; int st_value; } — ring stones with worth.
  • SCORE (score.h) — scoreboard entry (uid, score, flags, monster, name, level, time).
  • OPTION (options.c) — option descriptor with get/put function pointers.
  • PACT (potions.c) — potion type → flag/fuse-callback/duration.
  • SPOT (rooms.c) — maze generation cell.

4. Constants and enumerations

All are #defines in rogue.h:

  • Geometry: NUMLINES 24, NUMCOLS 80, MAXLINES 32, MAXCOLS 80, STATLINE 23.
  • Limits: MAXROOMS 9, MAXTHINGS 9, MAXOBJ 9, MAXPACK 23, MAXTRAPS 10, MAXPASS 13, MAXDAEMONS 20, AMULETLEVEL 26, NUMTHINGS 7, BORE_LEVEL 50.
  • Object type glyphs (o_type/map chars): PASSAGE #, DOOR +, FLOOR ., PLAYER @, TRAP ^, STAIRS %, GOLD *, POTION !, SCROLL ?, MAGIC $, FOOD :, WEAPON ), ARMOR ], AMULET ,, RING =, STICK /; pseudo-types CALLABLE -1, R_OR_S -2 for item-prompt filters.
  • Subtype enums: 8 traps (T_DOOR..T_MYST), 14 potions (P_), 18 scrolls (S_), 9 weapons (MACE..SPEAR, +FLAME pseudo), 8 armors (LEATHER..PLATE_MAIL), 14 rings (R_), 14 sticks (WS_).
  • Creature flag bits: CANHUH, CANSEE, ISBLIND, ISCANC, ISLEVIT, ISFOUND, ISGREED, ISHASTE, ISTARGET, ISHELD, ISHUH, ISINVIS, ISMEAN, ISHALU, ISREGEN, ISRUN, SEEMONST, ISFLY, ISSLOW (note deliberate collisions: ISCANC=ISLEVIT, ISMEAN=ISHALU, SEEMONST=ISFLY — one set applies to monsters, other to hero).
  • Object flag bits: ISCURSED, ISKNOW, ISMISL, ISMANY, ISFOUND (shared), ISPROT.
  • Room flags: ISDARK, ISGONE, ISMAZE.
  • Place flags: F_PASS 0x80, F_SEEN 0x40, F_DROPPED/F_LOCKED 0x20, F_REAL 0x10, F_PNUM 0x0f (passage number), F_TMASK 0x07 (trap type).
  • Durations/tuning: HEALTIME 30, HUHDURATION 20, SEEDURATION 850, HUNGERTIME 1300, MORETIME 150, STOMACHSIZE 2000, STARVETIME 850, BOLT_LENGTH 6, LAMPDIST 3, BEARTIME/SLEEPTIME/HOLDTIME/WANDERTIME/BEFORE/ AFTER as spread() randomized durations.
  • Saving throws: VS_POISON 0, VS_PARALYZATION 0, VS_DEATH 0, VS_BREATH 2, VS_MAGIC 3.
  • Get-function returns: NORM 0, QUIT 1, MINUS 2. Inventory styles: INV_OVER 0, INV_SLOW 1, INV_CLEAR 2.

5. The macro layer

rogue.h defines a thick macro layer the port must translate to methods:

Macro Expansion Port meaning
when / otherwise / until(e) break;case / break;default / while(!e) plain Go control flow
hero player.t_pos g.Player.Pos
pstats player.t_stats g.Player.Stats
pack player.t_pack g.Player.Pack
proom player.t_room g.Player.Room
max_hp player.t_stats.s_maxhp g.Player.Stats.MaxHP
on(thing,flag) flag bit test t.Flags.Has(...)
INDEX/chat/flat/moat(y,x) places[] cell access g.Level.At(p).Ch/Flags/Monster
winat(y,x) monster disguise or map char g.Level.VisibleChar(p)
ce(a,b) coord equality a == b (value type)
ISRING(h,r) / ISWEARING(r) ring-worn tests g.Player.IsWearing(ring)
ISMULT(type) stackable type test method on ObjType
GOLDCALC rnd(50+10*level)+2 method on Level
RN LCG step Rng.Next()
DISTANCE/dist squared distance geometry helper
attach/detach/free_list list ops via &list slice operations

6. Global variable census

Everything below lives in extern.c (game state) or as file-scope statics (module state). In the Go port all of these become fields of the RogueGame struct (user directive: no package-level globals).

  • Player & world: player (THING), places[], rooms[9], passages[13], mlist, lvl_obj, stairs, level, max_level, purse, amulet, cur_armor, cur_weapon, cur_ring[2], food_left, hungry_state, ntraps, max_stats, oldpos, oldrp, delta, take.
  • RNG/identity: seed, dnum, whoami, fruit, home, file_name.
  • UI/messaging: huh, prbuf, mpos, msgbuf+newpos (io.c statics), hw, status-line shadow statics (io.c), discovery pagination statics (things.c).
  • Command engine: count, after, again, door_stop, firstmove, running, runch, last_comm/last_dir/last_pick (+ l_* backups), no_command, no_move, quiet, to_death, kamikaze, max_hit, has_hit, move_on, dir_ch, countch/direction/newcount (command.c statics).
  • Options: terse, fight_flush, jump, see_floor, passgo, tombstone, inv_type, inv_describe, lower_msg, msg_esc, save_msg, q_comm, stat_msg.
  • Item identity tables: p_colors[], s_names[], r_stones[], ws_made[], ws_type[], a_class[], 7 obj_info tables, pack_used[26], group, inpack, n_objs, no_food.
  • Daemons: d_list[20], between (daemons.c static).
  • Scores/system: scoreboard, noscore, lastscore, numscores, allscore, wizard, playing, in_shell, got_ltc, orig_dsusp, seenstairs, vf_hit, e_levels[].
  • Static name data (immutable): monsters[26], rainbow[], stones[], wood[], metal[], sylls[], inv_t_name[], tr_name[], helpstr[], release, encstr, statlist, version, h_names[]/m_names[] (fight.c), init_dam[] (weapons.c), lvl_mons[]/wand_mons[] (monsters.c), uses[] (rings.c), p_actions[] (potions.c).

7. Function catalog by file

main.c (396 lines) — Entry point and main game loop control

Function Signature Description
main int main(int argc, char **argv, char **envp) Entry point; initializes game state, terminal, initializes all subsystems (probabilities, player, names, colors, stones, materials), sets up windows and daemons, then calls playit(). Sets globals: wizard, whoami, dnum, seed, file_name, home
endit void endit(int sig) Signal handler for abnormal termination; calls fatal() with goodbye message
fatal void fatal(char *s) Prints message to status line, refreshes screen, ends curses, and exits
rnd int rnd(int range) Returns random number 0 to range-1 using macro RN; core randomness function
roll int roll(int number, int sides) Simulates rolling dice; calls rnd() for each die
tstp void tstp(int ignored) Signal handler for SIGTSTP/SIGCONT; saves cursor position, suspends curses, calls md_tstpsignal() and md_tstpresume(), restores screen state
playit void playit() Main game loop; runs daemons/fuses before and after each turn, reads ROGUEOPTS to set options, loops on playing flag calling command()
quit void quit(int sig) Confirm quit prompt; on 'y' displays final score and calls score(), reads/writes globals purse, q_comm, count, to_death
leave void leave(int sig) Quick exit during error; ends curses and exits cleanly
shell void shell() Shell escape; saves cursor, ends curses, calls md_shellescape(), restores curses; reads/writes global in_shell
my_exit void my_exit(int st) Proper exit wrapper; calls resetltchars() then exit()

Notes: Globals initialized here from environment: whoami, dnum, seed, file_name, home. Signal handlers registered: endit(), tstp(), quit(), leave(). Heavy curses use (initscr(), endwin(), newwin()). Platform shims: md_init(), md_normaluser(), md_tstpsignal(), md_tstpresume().

command.c (821 lines) — User command processing and game actions

Function Signature Description
command void command() Main command dispatcher; reads user input, handles counts/prefixes (digits for repeat), processes movement (hjklyubn), items (pick, drop, quaff, read, eat, wield, wear, take_off, ring_on, ring_off), special commands (search, help, identify, etc.). Uses static variables countch, direction, newcount. Handles daemons/fuses before/after. Modifies globals: running, count, after, to_death, noscore, wizard, last_comm, last_dir, etc.
illcom void illcom(int ch) Error handler for illegal commands; displays message via msg(), modifies save_msg, count
search void search() Search current square and adjacent 8; finds hidden doors/traps/passages with probability adjusted for hallucination/blindness; modifies running, count on discovery
help void help() Displays command help; reads from helpstr[] array, uses hw window for full help display
identify void identify() Identify objects/monsters by character; uses local ident_list[] and monsters[] array
d_level void d_level() Go down stairs; checks levit_check(), verifies stairs at current position, increments level, calls new_level()
u_level void u_level() Go up stairs; checks for stairs, checks amulet flag, decrements level, checks win condition level==0, calls new_level()
levit_check bool levit_check() Check levitation status; returns TRUE if player has ISLEVIT flag (and messages that you float up)
call void call() Name/rename items; calls get_item(), prompts for new name, allocates memory via malloc(), updates oi_guess in object info structures
current void current(THING *cur, char *how, char *where) Print currently equipped item (weapon/armor/rings); formats output using inv_name()

Notes: Static variables countch, direction, newcount in command() track repeat count state. Uses when macro for switch cases. Wizard mode (MASTER) adds debug commands (Ctrl-key commands: create object, show map, teleport, etc.). Command parsing via nested switch statements. Uses readchar() for input, msg()/addmsg() for output.

io.c (278 lines) — Input/output and terminal display

Function Signature Description
msg int msg(char *fmt, ...) Display message at top of screen; variadic wrapper using va_list, calls doadd(), endmsg(). Returns ESCAPE or ~ESCAPE. Clears line if format is empty string
addmsg void addmsg(char *fmt, ...) Append to current message buffer; variadic, calls doadd()
endmsg int endmsg() Finish message display; handles "--More--" continuation, auto-capitalizes first character unless lower_msg, saves message to huh for redisplay
doadd void doadd(char *fmt, va_list args) Internal: vsprintf into message buffer; checks overflow, concatenates to msgbuf
step_ok int step_ok(int ch) Check if terrain character allows stepping; returns FALSE for walls/space, TRUE for floor/doors/items (non-alpha chars)
readchar char readchar() Read character with error checking; calls md_readchar(), intercepts Ctrl-C (sends to quit())
status void status() Display status line at STATLINE; shows level/gold/HP/armor/strength/experience/hunger; uses static tracking variables (s_hp, s_pur, etc.) to avoid redundant redraws
wait_for void wait_for(int ch) Block until specific character typed (or newline if ch is newline)
show_win void show_win(char *message) Display message in hw window and wait for space; used for full-screen displays

Notes: Static msgbuf[2*MAXMSG+1], newpos track accumulating multi-part messages (addmsg/msg/endmsg protocol). huh[] global stores last message for the Ctrl-R repeat command. Status line optimized with 8 static shadow variables to minimize terminal writes.

init.c (448 lines) — Initialization of game state and object tables

Function Signature Description
init_player void init_player() Roll up player character; copies max_stats to pstats, creates starting items (food, ring mail +1, mace +1/+1, bow +1, 25-39 arrows), initializes food_left, sets cur_armor and cur_weapon
init_colors void init_colors() Randomize potion colors; assigns p_colors[] randomly from rainbow[] list, no duplicates
init_names void init_names() Generate random scroll names from sylls[] syllable fragments; stores mallocated names in s_names[]
init_stones void init_stones() Randomize ring stones; assigns r_stones[] from stones[] array (no duplicates), adds stone value to ring_info[].oi_worth
init_materials void init_materials() Initialize wand/staff materials; randomly picks from metal[] (wand) or wood[] (staff), sets ws_type[] and ws_made[]
sumprobs void sumprobs(struct obj_info *info, int bound) Converts item probability arrays to cumulative distributions (running sum)
init_probs void init_probs() Calls sumprobs() for all seven obj_info tables
badcheck void badcheck(char *name, struct obj_info *info, int bound) (MASTER) Verifies probabilities sum to 100; aborts with message if not
pick_color char *pick_color(char *col) Returns given color, or a random one if hero is hallucinating

Notes: Static data defined here: rainbow[] (27 colors) + cNCOLORS, sylls[] (syllables), stones[] (26 STONE entries with worth) + cNSTONES, wood[] (33 woods) + cNWOOD, metal[] (22 metals) + cNMETAL. The name arrays p_colors[], s_names[], r_stones[], ws_type[], ws_made[] are defined in extern.c but populated here at game start (per-game randomization of item appearance).

extern.c (392 lines) — Global variable definitions

Boolean flags: after (want after-daemons), again (repeat last command), seenstairs, amulet (found the Amulet), door_stop (stop running at doors), fight_flush (flush typeahead on fight), firstmove, got_ltc, has_hit, in_shell, inv_describe, jump (show running as jumps), kamikaze, lower_msg, move_on, msg_esc, passgo (follow passage turns), playing, q_comm, running, save_msg, see_floor, stat_msg, terse, to_death, tombstone, wizard (MASTER), pack_used[26].

Char/string state: dir_ch, file_name[MAXSTR], huh[MAXSTR] (last message), prbuf[2*MAXSTR] (sprintf scratch), runch, take, whoami[MAXSTR], fruit[MAXSTR] ("slime-mold"), home[MAXSTR], l_last_comm, l_last_dir, last_comm, last_dir; appearance-name arrays p_colors[MAXPOTIONS], r_stones[MAXRINGS], s_names[MAXSCROLLS], ws_made[MAXSTICKS], ws_type[MAXSTICKS]; fixed string tables inv_t_name[] (Overwrite/Slow/Clear), tr_name[] (8 trap names), release (version).

Int state: n_objs, ntraps, hungry_state (0-3), inpack, inv_type, level, max_hit, max_level, mpos (cursor pos on msg line), no_food, count (command repeat), food_left, lastscore, no_command (turns asleep), no_move (turns held), noscore, purse (gold), quiet (rest turns), vf_hit (flytrap hits), dnum (dungeon number), seed (RNG seed), numscores, a_class[MAXARMORS] (armor class table: 8 5 6 4 3 3 4 3... actually per-type base AC), e_levels[] (experience thresholds: 10 20 40 80 ... doubling to level 19), orig_dsusp.

Coords/structural: delta (last direction), oldpos, stairs; places[MAXLINES*MAXCOLS] (the level map: PLACE = ch, flags, monster ptr); player (THING); cur_armor, cur_ring[2], cur_weapon, l_last_pick, last_pick (THING ptrs); lvl_obj (level object list), mlist (monster list).

Tables: rooms[MAXROOMS], passages[MAXPASS] (12 passage "rooms"), max_stats (initial player stats: str 16, 0 exp, lvl 1, ac 10, 12 hp, "1x4"), oldrp, monsters[26] (full A-Z monster table with names, carry %, flags, stats), 7 obj_info tables: things[NUMTHINGS] (potion 26%, scroll 36%, food 16%, weapon 7%, armor 7%, ring 4%, stick 4%), pot_info[MAXPOTIONS], scr_info[MAXSCROLLS], ring_info[MAXRINGS], ws_info[MAXSTICKS], weap_info[MAXWEAPONS+1], arm_info[MAXARMORS] — each entry: name, probability, worth, guess ptr, know flag. helpstr[] (help text). hw (WINDOW* scratch), scoreboard (FILE*).

Notes: Pure data file, no functions. The monsters[] table is the canonical A-Z bestiary (Aquator, Bat, Centaur, Dragon, Emu, Venus Flytrap, Griffin, Hobgoblin, Ice monster, Jabberwock, Kestrel, Leprechaun, Medusa, Nymph, Orc, Phantom, Quagga, Rattlesnake, Snake, Troll, Black unicorn, Vampire, Wraith, Xeroc, Yeti, Zombie) with per-monster carry probability, flag word (ISMEAN/ISFLY/ISREGEN/ISGREED/ISINVIS/CANHUH), and stats (exp, level, AC, HP dice, damage strings).

mach_dep.c (458 lines) — Installation and machine-dependent setup

Function Signature Description
init_check void init_check() Check if system load/user count too high (compile-time optional; mostly vestigial)
open_score void open_score() Open score file for read/write; creates if missing; sets global scoreboard FILE*
setup void setup() Install signal handlers (md_onsignal_autosave), start checkout timer, set curses raw/noecho modes, fetch local tty chars
getltchars void getltchars() Save original tty dsusp char for later restore; sets got_ltc, orig_dsusp
resetltchars void resetltchars() Restore original tty chars (on exit/shell)
playltchars void playltchars() Set tty chars to in-game values
start_score void start_score() Stop checkout timer before scoring (vestigial)
is_symlink bool is_symlink(char *sp) lstat() check whether path is a symbolic link (save-file anti-cheat)
too_much/author/checkout/chmsg/ucount various Compile-time-guarded system-load limiting and author-exemption checks (vestigial on modern systems)
lock_sc bool lock_sc() Lock score file via lock file with retry/prompt and stale-lock aging
unlock_sc void unlock_sc() Release score file lock
flush_type void flush_type() Flush keyboard typeahead buffer (flushinp())

Notes: Almost everything here is either curses/tty plumbing or vestigial 1980s multi-user courtesy features. A Go port needs only: score-file open/lock (use os.OpenFile + advisory locking or just best-effort), signal handling (os/signal), and typeahead flush (tcell handles).

mdport.c (1433 lines) — Platform abstraction layer for Unix/Windows/DOS

All functions are platform shims replaceable with Go stdlib:

Function Go equivalent
md_init none needed (tcell handles terminal init)
md_onsignal_default/exit/autosave os/signal.Notify
md_hasclreol, md_putchar, md_raw_standout/standend tcell rendering
md_unlink, md_unlink_open_file, md_chmod os.Remove, os.Chmod
md_normaluser omit (no setuid in Go port)
md_getuid, md_getpid os.Getuid(), os.Getpid()
md_getusername, md_getrealname os/user.Current()
md_gethomedir os.UserHomeDir()
md_sleep time.Sleep
md_getshell, md_shellescape os/exec (or omit shell escape)
md_crypt, md_getpass not needed (wizard password via env; no crypt)
md_erasechar, md_killchar, md_dsuspchar, md_setdsuspchar, md_suspchar tcell key events
md_readchar tcell PollEvent (the 900-line escape-sequence state machine disappears entirely)
md_loadav, md_ignoreallsignals, md_tstp*, checkout timers omit / os/signal

Notes: The dominant complexity is md_readchar() — a 900-line keypad/escape-sequence decoder state machine for a dozen terminal types. tcell's event model replaces all of it. Nothing in this file needs a line-by-line port.

vers.c (18 lines) — Version and build information

Variable Value Purpose
release "5.4.4" Version number string (also written into save files for validation)
encstr obfuscated bytes XOR key stream #1 for save/score encryption
statlist obfuscated bytes XOR key stream #2 for save/score encryption
version "rogue (rogueforge) 09/05/07" Full version/build string

Notes: encstr/statlist are the two XOR key strings consumed by encwrite()/encread() in save.c.

new_level.c (232 lines) — Dig and draw a new level

Function Signature Description
new_level void new_level() Initializes a new dungeon level by clearing previous monsters and objects, drawing rooms and passages, placing traps and stairs, positioning monsters in their rooms, and placing the player. Updates max_level and resets hero position.
rnd_room int rnd_room() Selects and returns a random room index that actually exists (where ISGONE flag is not set).
put_things void put_things() Places potions, scrolls, and other items on the current level with 36% probability for each of MAXOBJ slots. Handles treasure rooms (1 in TREAS_ROOM chance) and places the amulet at level AMULETLEVEL if not yet found.
treas_room void treas_room() Creates a treasure room by selecting a random existing room and filling it with MINTREAS to MAXTREAS treasure items plus monsters from the next level down (incremented temporarily).

Notes: File-scope constants: TREAS_ROOM (1/20 chance), MAXTREAS (10 items max), MINTREAS (2 items min), MAXTRIES (10 tries limit). No static tables. Uses macros: chat() for character at position, flat() for flags, F_REAL flag. Key side effects: modifies global level, max_level, places[], mlist; calls helper functions do_rooms(), do_passages(), put_things().

rooms.c (473 lines) — Create the layout for the new level

Function Signature Description
do_rooms void do_rooms() Creates all rooms on a level by partitioning space into 3x3 grid and generating random room sizes/positions. Marks some rooms as ISGONE, sets ISDARK and ISMAZE flags, places gold and monsters in each room based on probabilities.
draw_room void draw_room(struct room *rp) Draws walls (-, |) around a room and lays down FLOOR characters inside; handles maze rooms by calling do_maze() instead.
vert void vert(struct room *rp, int startx) Draws a vertical wall line of '|' characters from top to bottom of room.
horiz void horiz(struct room *rp, int starty) Draws a horizontal wall line of '-' characters across room width.
do_maze void do_maze(struct room *rp) Generates a maze within a room using recursive depth-first digging. Initializes maze SPOT array tracking visited positions and exits, sets up starting position, and calls dig() to carve passages.
dig void dig(int y, int x) Recursively digs maze passages by choosing random unvisited neighbors, accounting for exits, and calling putpass() to mark passage cells. Returns when no more valid neighbors exist.
accnt_maze void accnt_maze(int y, int x, int ny, int nx) Records a maze exit connection between adjacent SPOT cells, checking for duplicates before adding.
rnd_pos void rnd_pos(struct room *rp, coord *cp) Fills coord with random x,y within room bounds (excluding walls).
find_floor bool find_floor(struct room *rp, coord *cp, int limit, bool monst) Finds a valid floor/passage spot in given room or random room (if rp==NULL) within limit attempts. Returns TRUE if found, FALSE if limit exceeded. If monst==TRUE, checks for monster-valid position (empty with step_ok() char).
enter_room void enter_room(coord *cp) Called when player enters a room; sets proom, opens doors, and redraws non-dark rooms by displaying correct characters and monsters. Updates terminal display.
leave_room void leave_room(coord *cp) Called when player exits a room; clears room display by replacing FLOOR with space or passage chars, handles monsters with standout mode, restores doors.

Notes: File-scope static variables: Maxy, Maxx, Starty, Startx (maze bounds), and maze[NUMLINES/3+1][NUMCOLS/3+1] array (2D array of SPOT structures tracking maze generation). SPOT typedef contains: nexits (count of exits), exits[4] (array of exit coordinates), used (boolean flag). Uses "until" macro with do-while loops for retries. Key macros: chat() and flat() for accessing places[] array, FLOOR/DOOR/PASSAGE character constants, ISMAZE/ISDARK/ISGONE room flags.

passages.c (425 lines) — Draw the connecting passages

Function Signature Description
do_passages void do_passages() Draws all passages on a level using a connectivity graph algorithm. Builds spanning tree connecting all MAXROOMS rooms, then adds 0-4 random extra connections. Initializes rdes[] array and calls conn() to draw each passage, then calls passnum() to number them.
conn void conn(int r1, int r2) Draws a corridor between two rooms, determining direction (right or down based on room indices), finding door positions, and drawing passage tiles with optional turn points. Places DOOR characters at room entries.
putpass void putpass(coord *cp) Sets F_PASS flag at position and marks as PASSAGE character; randomly makes secret passages (unreal) at deeper levels.
door void door(struct room *rm, coord *cp) Records door position in room's r_exit array, increments r_nexits. For non-maze rooms, randomly creates secret doors (|/- unreal) or regular DOOR characters.
add_pass void add_pass() [MASTER ifdef] Wizard command that displays all passages and doors on screen by iterating places[] and showing passages as PASSAGE, secret doors with standout.
passnum void passnum() Assigns passage numbers (0 to MAXPASS-1) by iterating through room exits and calling numpass() on each, resetting passage r_nexits. Uses static pnum and newpnum tracking.
numpass void numpass(int y, int x) Recursively numbers a passageway region by flood-fill; sets F_PNUM bits in flat flags, records door/secret door exits in passages[] array, and recurses on adjacent cells.

Notes: File-scope static variables: pnum (current passage number), newpnum (boolean flag). Static rdes[] array contains MAXROOMS room description structures with conn[] (adjacency matrix for possible connections), isconn[] (boolean array of actual connections made), and ingraph (boolean). The connectivity matrix hardcodes a 3x3 room grid topology. Uses macros: chat()/flat() for places[] access, F_PASS/F_PNUM flags, INDEX() macro for array indexing, ce() for coordinate equality testing.

move.c (426 lines) — Hero movement commands

Function Signature Description
do_run void do_run(char ch) Initiates running mode by setting running=TRUE, runch=ch (direction), and after=FALSE.
do_move void do_move(int dy, int dx) Main movement handler; validates move legality (bounds, diagonal ok), handles confused movement, checks for traps/doors/monsters. Updates hero position, calls fight() for combat, handles room changes, updates display. Complex switch on destination cell character (DOOR/TRAP/PASSAGE/FLOOR/STAIRS/monster).
turn_ok bool turn_ok(int y, int x) Checks if position is a DOOR or both F_REAL and F_PASS flags set (valid turning point in corridors during runs).
turnref void turnref() Marks position F_SEEN and optionally refreshes screen if jump mode enabled (for passage turn display).
door_open void door_open(struct room *rp) Illuminates a room by iterating through all positions and waking any monsters found (calling wake_monster()).
be_trapped char be_trapped(coord *tc) Handles trap activation with switch on trap type: T_DOOR (level down), T_BEAR (no_move delay), T_MYST (random messages), T_SLEEP (no_command delay), T_ARROW (damage/rolls), T_TELEP (teleport), T_DART (poisoned damage), T_RUST (armor damage). Returns trap type.
rndmove coord *rndmove(THING *who) Generates random movement by adding rnd(3)-1 to current position; validates legality with diag_ok() and step_ok(), returns position or original if invalid.
rust_armor void rust_armor(THING *arm) Decrements armor AC (increases o_arm value) unless protected or wearing R_SUSTARM ring, with message output varying by terse flag. Returns if armor NULL, not ARMOR type, or already very rusty.

Notes: File-scope global variable: nh (coord holding new hero position). Uses macros: winat() for visible character, chat() for actual cell character, flat() for flags, moat() for monster at position, INDEX() for place lookup. Trap type constants: T_DOOR, T_BEAR (BEARTIME), T_MYST, T_SLEEP (SLEEPTIME), T_ARROW, T_TELEP, T_DART, T_RUST. Uses "when" macro for compact case statements and "goto over" for retry after confused move calculation.

chase.c (542 lines) — Code for one creature to chase another

Function Signature Description
runners void runners() Iterates through all monsters in mlist and moves each running monster (ISRUN flag) via move_monst(), handling fast monsters with second move and target flag updates. Clears has_hit message flag.
move_monst int move_monst(THING *tp) Executes one monster turn: calls do_chase() once for ISSLOW/ISHASTE alternate turns, toggles t_turn flag. Returns -1 if monster died.
relocate void relocate(THING *th, coord *new_loc) Updates monster position from th->t_pos to new_loc, refreshes moat() pointers, calls set_oldch() for display character, updates room pointer, recalculates destination if room changed, and redraws monster on screen.
do_chase int do_chase(THING *th) Primary chase logic: finds room of chaser and chasee, navigates to target by exiting current room and approaching chasee. For dragons, checks line-of-sight and fires flame bolt at hero if in range (DRAGONSHOT odds). Calls chase() to find best move, handles combat when goal reached, calls relocate(). Returns 0.
set_oldch void set_oldch(THING *tp, coord *cp) Sets tp->t_oldch to the screen character at cp for proper redrawing; accounts for darkness, lamp range, and blind status to choose correct background.
see_monst bool see_monst(THING *mp) Returns TRUE if hero can see monster: checks ISBLIND, ISINVIS (requires CANSEE), LAMPDIST proximity, and room visibility (same room and not dark).
runto void runto(coord *runner) Sets monster at runner position to ISRUN flag, clears ISHELD, sets t_dest via find_dest(), starts monster chasing.
chase bool chase(THING *tp, coord *ee) Finds best move for tp toward target ee; if confused (ISHUH), makes random move via rndmove(); otherwise iterates 3x3 surrounding cells to find closest approach, avoiding scare scrolls and Xerocs. Returns TRUE if still chasing (curdist != 0), FALSE if reached goal.
roomin struct room *roomin(coord *cp) Locates which room contains coordinate cp by checking F_PASS flag first (returns passage room), then iterating rooms[] for overlap; returns NULL/aborts if not found.
diag_ok bool diag_ok(coord *sp, coord *ep) Validates move from sp to ep: checks bounds and allows non-diagonals always; for diagonal moves, checks that both adjacent cells (same row and same column) are step_ok().
cansee bool cansee(int y, int x) Returns TRUE if hero can see coordinate: checks ISBLIND, LAMPDIST range with passage blocking checks, or same room with lit/lit conditions.
find_dest coord *find_dest(THING *tp) Determines monster target: returns &hero if carrying probability is low, monster in proom, or monster is visible; otherwise searches lvl_obj for items in monster's room not already targeted by other monsters, with probability check; defaults to &hero.
dist int dist(int y1, int x1, int y2, int x2) Returns squared Euclidean distance (x delta squared plus y delta squared) for comparison purposes.
dist_cp int dist_cp(coord *c1, coord *c2) Wrapper calling dist() with coordinate components extracted from c1 and c2.

Notes: File-scope static variable: ch_ret (coord holding where chasing takes you, returned by chase() and used by do_chase()). DRAGONSHOT macro constant (5, odds for dragon breath). Uses macros: moat() for monster at position, chat()/flat() for cell access, on() for flag testing, ce() for coordinate equality. Key dragon logic: checks 8-directional line-of-sight (row match, column match, or diagonal), BOLT_LENGTH range squared, and calls fire_bolt(). Monster types referenced: 'D' (dragon), 'P' (invisible stalker), 'B' (bat), 'F' (flytrap), 'X' (xeroc).

monsters.c (253 lines) — File with various monster functions

Function Signature Description
randmonster char randmonster(bool wander) Selects a monster type from either lvl_mons[] or wand_mons[] array based on wander flag. Calculates index from level offset with noise (rnd(10)-6), clamps to valid range, skips nulls in array. Returns monster letter 'A'-'Z'.
new_monster void new_monster(THING *tp, char type, coord *cp) Initializes new monster: sets type, disguise, position; attaches to mlist; looks up stats from monsters[] table using type-'A' index; adjusts level based on depth (level > AMULETLEVEL); sets flags, hit points via roll(). If ISWEARING R_AGGR ring, calls runto(). For 'X' (xeroc), randomizes disguise.
exp_add int exp_add(THING *tp) Calculates experience reward based on monster level and max HP; modifies by 20x for level > 9, 4x for level > 6, and divides HP by 6 (or 8 if level 1).
wanderer void wanderer() Creates a wandering monster at random floor position in different room than proom. Allocates THING via new_item(), calls randmonster(TRUE), new_monster(), displays if SEEMONST, and sets running via runto().
wake_monster THING *wake_monster(int y, int x) Activates monster at position: checks 1/3 chance to start chasing if ISMEAN and conditions met (not held, not wearing stealth, etc.); handles Medusa ('M') gaze attack (confusion if not saved). For ISGREED monsters, sets guard target to gold position or hero. Returns monster pointer.
give_pack void give_pack(THING *tp) If at max_level and random check (%) against m_carry probability succeeds, attaches one random item to monster's pack via new_thing().
save_throw int save_throw(int which, THING *tp) Calculates saving throw: need value = 14 + which - (s_lvl / 2), rolls 1d20, returns TRUE if roll >= need.
save int save(int which) Hero's save check: for VS_MAGIC, reduces difficulty by protection ring bonuses (left and right ring armor values), then calls save_throw() with &player.

Notes: File-scope static arrays: lvl_mons[] (26 monsters by level 0-25: K E B S H I R O Z L C Q A N Y F T W P X U M V G J D), wand_mons[] (same with 0s for F U D where wands unavailable). Monster type encoding: 'A' through 'Z' map to indices 0-25 via t_type-'A'. The monsters[] global array (defined elsewhere) is indexed this way. Key monster types: 'M' (Medusa), 'X' (Xeroc), 'F' (Flytrap), 'D' (Dragon). References AMULETLEVEL constant for depth-scaling experience. Uses macros: ISWEARING() for ring checks, on() for flag testing, rnd() for probabilities, roll() for hit dice.

wizard.c (285 lines) — Special wizard commands (some also non-wizard under strange circumstances)

Function Signature Description
whatis void whatis(bool insist, int type) Identifies an item from pack by prompting with get_item() and setting ISKNOW flag. Calls set_know() with appropriate info array (scr_info, pot_info, ws_info, ring_info) based on o_type. If insist, loops until valid selection or user cancels.
set_know void set_know(THING *obj, struct obj_info *info) Sets oi_know=TRUE for object type in info array, sets obj ISKNOW flag, and frees/clears any oi_guess string for that type.
type_name char *type_name(int type) Returns string description for object type by searching static tlist[] array: "potion", "scroll", "food", "ring, wand or staff", "ring", "wand or staff", "weapon", "suit of armor".
create_obj void create_obj() [MASTER ifdef] Wizard command to create arbitrary items: prompts for type and which (0-f hex), handles blessing for weapons/armor/rings, calls init_weapon() or sets o_arm, processes special ring logic, calls fix_stick() for staves, and adds to pack.
teleport void teleport() Instantly moves hero to random valid floor position, redraws old position with floor_at(), updates proom if changed rooms (leave_room/enter_room), and resets movement/combat flags (no_move, count, running). Resets ISHELD flag if held by flytrap (resets vf_hit and damage dice).
passwd int passwd() [MASTER ifdef] Prompts for wizard password, reads line with backspace/kill character handling, returns TRUE if strcmp(input, md_crypt(input, "mT")) matches PASSWD constant.
show_map void show_map() [MASTER ifdef] Displays full level map in hw window: iterates places[] drawing each cell with standout for non-real (F_REAL unset) cells, then shows "---More---" prompt.

Notes: File-scope static data: tlist[] array in type_name() with h_list structures mapping type constants to descriptions. Functions marked [MASTER ifdef] are wizard-only debug/cheat commands (create_obj, passwd, show_map). The teleport() function is non-wizard and can be invoked by T_TELEP trap or other game mechanics. Uses macros: on() for flag testing, chat()/flat()/moat() for places[] access, INDEX() for place lookup, ISWEARING() for ring checks. References global variables: pack (player inventory), hero (player position), proom (player's current room), player (player THING), monsters[] (for name lookups in wizard feedback).

things.c (714 lines) — Item naming and creation

Function Signature Description
inv_name char* inv_name(THING *obj, bool drop) Formats item name for inventory display; handles all item types with modifiers like "(being worn)" or "(weapon in hand)"; writes to global prbuf
drop void drop() Player drops item from pack; checks floor availability, calls leave_pack(), attaches to level object list, updates display and global amulet flag
dropcheck bool dropcheck(THING *obj) Validates item can be dropped (checks cursed status and equipment status); handles ring stat adjustments like R_ADDSTR; returns FALSE if cursed
new_thing THING* new_thing() Creates random item for dungeon level; probabilistically selects type (potion/scroll/food/weapon/armor/ring/stick); initializes enchantments and cursing
pick_one int pick_one(struct obj_info *info, int nitems) Selects random item from cumulative probability table; returns index of selected item
discovered void discovered() Prompts player for which item type to list discoveries for; calls print_disc() and end_line()
print_disc void print_disc(char type) Lists all discovered items of given type (known or guessed); builds temporary THING for name formatting
set_order void set_order(int *order, int numthings) Fisher-Yates shuffle of array indices for randomized discovery list order
add_line char add_line(char *fmt, char *arg) Adds line to discovery window display with pagination; handles INV_SLOW vs INV_OVER modes; manages window creation and "Press space" prompts
end_line void end_line() Finalizes discovery list display; clears pagination state
nothing char* nothing(char type) Constructs "nothing found" message for specific item type
nameit void nameit(THING *obj, char *type, char *which, struct obj_info *op, char *(*prfunc)(THING *)) Helper to format potion/ring/stick names with color/material and charge status via function pointer
nullstr char* nullstr(THING *ignored) Returns empty string; used as null function pointer in nameit() calls
pr_list void pr_list() MASTER-only: lists all possible items by type for wizard debugging
pr_spec void pr_spec(struct obj_info *info, int nitems) MASTER-only: prints all items in category with probability percentages

Notes: Static file-scope variables line_cnt, newpage, lastfmt, lastarg manage discovery display pagination. Function-pointer callback pattern in nameit() (nullstr, ring_num, charge_str).

pack.c (504 lines) — Pack management and item selection interface

Function Signature Description
add_pack void add_pack(THING *obj, bool silent) Adds item to player pack or picks up from floor; handles grouped items by incrementing count; keeps pack sorted by type; updates monster dest pointers and amulet flag
pack_room bool pack_room(bool from_floor, THING *obj) Validates pack space (inpack vs MAXPACK); removes from floor and updates display if successful
leave_pack THING* leave_pack(THING *obj, bool newobj, bool all) Removes item from pack; decrements inpack; splits stacks if not removing all; updates pack_used
pack_char char pack_char() Returns next unused pack slot letter (a-z); marks in pack_used array
inventory bool inventory(THING *list, int type) Lists pack items filtered by type; uses add_line() pagination; sets n_objs
pick_up void pick_up(char ch) Handles item pickup by type (gold vs objects); calls add_pack() or money()
move_msg void move_msg(THING *obj) Prints message for stepping onto object without picking it up (move_on mode)
picky_inven void picky_inven() Shows single item from pack by inventory letter
get_item THING* get_item(char *purpose, int type) Prompts player to select item from pack by letter or '*' for full list; handles ESCAPE and again repeat via last_pick
money void money(int value) Adds/subtracts gold from purse; updates display floor character
floor_ch char floor_ch() Returns appropriate floor display character based on room state and show_floor()
floor_at char floor_at() Returns character at hero position accounting for floor visibility
reset_last void reset_last() Resets saved last command state (last_comm, last_dir, last_pick) from backup copies

Notes: Pack is a linked list anchored at global pack (macro for player.t_pack); pack letters stored in o_packch, allocation tracked in pack_used[26]. Grouped items (arrows etc.) stack by o_group. Uses goto for pack insertion flow.

potions.c (376 lines) — Potion effects

Function Signature Description
quaff void quaff() Drinks potion from pack; switch dispatching all 14 potion types (confusion, LSD, poison, strength, see-invisible, healing, monster-find, magic-find, raise-level, extra-heal, haste, restore, blind, levitate); calls do_pot() for fuse-based effects; updates pot_info known/guess state
is_magic bool is_magic(THING *obj) Tests if object radiates magic (enchanted armor/weapons, or any potion/scroll/stick/ring/amulet)
invis_on void invis_on() Enables CANSEE flag; re-displays invisible monsters
turn_see bool turn_see(bool turn_off) Toggles SEEMONST magic monster detection; redisplays monsters; handles hallucination display
seen_stairs bool seen_stairs() Checks if stairs are visible on map, under hero, or held by a visible monster
raise_level void raise_level() Sets experience to next level threshold; calls check_level()
do_pot void do_pot(int type, bool knowit) Standard potion setup: marks known, sets player flag from p_actions[] table, starts fuse or lengthens if already active

Notes: PACT struct array p_actions[] maps potion types → player flag, cleanup daemon function pointer (unconfuse, come_down, sight, etc.), and duration.

scrolls.c (330 lines) — Scroll effects

Function Signature Description
read_scroll void read_scroll() Reads scroll from pack; switch on all 18 scroll types: confuse-monster (sets CANHUH on hero), magic-map (reveals level), hold-monster, sleep-self, enchant-armor, identify x5 varieties, scare-monster (message only when read), food-detection, teleport, enchant-weapon, create-monster, remove-curse, aggravate, protect-armor; calls call_it() to offer naming
uncurse void uncurse(THING *obj) Clears ISCURSED flag on item

Notes: S_MAP iterates the entire level updating passage/door/floor tiles into seen state; S_HOLD scans 3x3 neighborhood for monsters to hold; S_CREATE places monster adjacent; S_SCARE when picked up is handled in pack code (scroll on floor scares monsters via chase.c check).

rings.c (205 lines) — Ring equipping and effects

Function Signature Description
ring_on void ring_on() Equips ring on left or right hand; calls gethand() if needed; switch dispatch for immediate effects (R_ADDSTR strength bonus, R_SEEINVIS, R_AGGR awakening)
ring_off void ring_off() Removes ring; validates which hand; dropcheck() for curse validation
gethand int gethand() Prompts player for left or right hand; loops until valid L/R or ESCAPE
ring_eat int ring_eat(int hand) Returns extra food consumption for ring on given hand; static uses[] table maps ring type to hunger impact (R_DIGEST negative/saves, R_REGEN +2, etc.)
ring_num char* ring_num(THING *obj) Returns bonus string "[+n]" if known (protection/add-str/add-dam/add-hit rings)

Notes: Static uses[] array encodes per-ring metabolism cost. Rings worn tracked in cur_ring[LEFT]/cur_ring[RIGHT].

sticks.c (432 lines) — Wand and staff zapping

Function Signature Description
fix_stick void fix_stick(THING *cur) Initializes wand/staff damage strings and charges; staffs 2x3 melee damage, wands 1x1; WS_LIGHT gets 10-19 charges, others 3-7
do_zap void do_zap() Zaps wand in aimed direction; switch over all 14 stick types: light (illuminate room), drain-life, invisibility (make monster invisible), polymorph, teleport-away, teleport-to, cancellation, magic missile, haste-monster, slow-monster, lightning/fire/cold bolts, nop; decrements charges
drain void drain() Drains half hero's HP and spreads equal damage across all monsters in room/passage; static drainee[] target array
fire_bolt void fire_bolt(coord *start, coord *dir, char *name) Fires elemental bolt: travels BOLT_LENGTH, bounces off walls (reversing delta components), damages monsters (with save for half... actually save to avoid), can hit hero on bounce-back; animates with directional characters (
charge_str char* charge_str(THING *obj) Returns "[n charges]" string if known

Notes: fire_bolt uses static bolt THING and spotpos[] path array; bounce logic flips direction on wall hit. WS_POLYMORPH preserves monster's pack through transformation. Used both by player zaps and dragon breath ("flame").

weapons.c (289 lines) — Weapon initialization and throwing

Function Signature Description
missile void missile(int ydelta, int xdelta) Throws selected weapon: validates, leaves pack (splitting stacks), animates via do_motion(), tests hit with hit_monster(), falls to ground on miss
do_motion void do_motion(THING *obj, int ydelta, int xdelta) Animates projectile across screen one cell at a time until blocked (wall/door/monster)
fall void fall(THING *obj, bool pr) Places fallen projectile on ground via fallpos(); attaches to lvl_obj or discards if no space
init_weapon void init_weapon(THING *weap, int which) Sets weapon properties from static init_dam[] table (wield damage, hurl damage, launcher, flags); arrows/darts/shurikens get count 8-15 and shared group id
hit_monster int hit_monster(int y, int x, THING *obj) Wrapper calling fight() for missile combat at position
num char* num(int n1, int n2, char type) Formats bonus as "+h,+d" / "+a" strings for inventory
wield void wield() Equips weapon; validates not wearing/cursed current; updates cur_weapon
fallpos bool fallpos(coord *pos, coord *newpos) Finds random empty adjacent square (9 cells) for dropped item; reservoir-samples among valid spots

Notes: Static init_dam[] table: mace 2x4, sword 3x4, bow 1x1, arrow 1x1/2x3 (launched by bow), dagger 1x6/1x4, two-handed sword 4x4, dart 1x1/1x3, shuriken 1x2/2x4, spear 2x3/1x6. Global group counter assigns stack ids.

armor.c (90 lines) — Armor wearing

Function Signature Description
wear void wear() Equips armor from pack; rejects if already wearing; sets ISKNOW; calls waste_time() (1 turn)
take_off void take_off() Removes armor; validates exists and not cursed via dropcheck()
waste_time void waste_time() Runs one turn of daemons/fuses (BEFORE and AFTER) without player action — lets hunger/healing tick during multi-turn actions

misc.c (598 lines) — Look/display update, direction input, utilities

Function Signature Description
look void look(bool wakeup) Core visibility update: redraws area around player (3x3 in corridors/dark, or whole room), erases old lamp area, handles door-stop run termination logic, wakes monsters if wakeup, applies hallucination via trip_ch(); maintains oldpos/oldrp
trip_ch int trip_ch(int y, int x, int ch) If hallucinating, replaces item characters with random rnd_thing() character
erase_lamp void erase_lamp(coord *pos, struct room *rp) Erases 3x3 lamp-lit area around old position in dark rooms
show_floor bool show_floor() Returns whether floor under hero should be shown (dark room + not blind + see_floor option)
find_obj THING* find_obj(int y, int x) Linear search of lvl_obj for object at position (fatal error if none in MASTER)
eat void eat() Eats food from pack; sets food_left to HUNGERTIME (with random reduction); 30% chance fruit vs ration message; may grant experience
check_level void check_level() Checks experience against e_levels[]; on level-up increases HP by roll(diff,10) and messages "Welcome to level N"
chg_str void chg_str(int amt) Modifies strength with R_SUSTSTR/ring accounting; tracks max_stats.s_str
add_str void add_str(str_t *sp, int amt) Adds to strength value with bounds (min 3, max 31)
add_haste bool add_haste(bool potion) Adds ISHASTE; if already hasted, faint 1-8 turns instead; starts nohaste fuse if potion
aggravate void aggravate() Calls runto() on all monsters — everything wakes and hunts
vowelstr char* vowelstr(char *str) Returns "n" if string starts with vowel (for "a"/"an")
is_current bool is_current(THING *obj) Checks if item is currently equipped; messages "in use"
get_dir bool get_dir() Prompts for direction key (hjklyubn); sets delta/dir_ch; handles again repeat; randomizes if confused/hallucinating; FALSE on ESCAPE
sign int sign(int nm) Returns -1/0/1
spread int spread(int nm) Returns nm ±10% random spread (used for durations)
call_it void call_it(struct obj_info *info) Prompts player to name an unidentified item; stores in oi_guess
rnd_thing char rnd_thing() Picks random thing display character (includes AMULET only at deep levels); static thing_list[]
choose_str char* choose_str(char *ts, char *ns) Returns first string if hallucinating, else second

Notes: look() is the heart of FOV/display maintenance and the trickiest function here (passage-count door-stop logic using sumhero/diffhero diagonal tests). Static last_dt in get_dir() for repeats.

daemon.c (182 lines) — Daemon and fuse scheduler

Function Signature Description
d_slot struct delayed_action* d_slot() Finds first EMPTY slot in d_list[20]
find_slot struct delayed_action* find_slot(void (*func)()) Finds occupied slot by function pointer
start_daemon void start_daemon(void (*func)(int), int arg, int type) Registers repeating daemon (d_time = DAEMON sentinel)
kill_daemon void kill_daemon(void (*func)()) Removes daemon by function pointer
do_daemons void do_daemons(int flag) Runs all daemons matching BEFORE/AFTER phase
fuse void fuse(void (*func)(int), int arg, int time, int type) Registers one-shot countdown fuse
lengthen void lengthen(void (*func)(), int xtime) Extends existing fuse's remaining time
extinguish void extinguish(void (*func)()) Cancels fuse by function pointer
do_fuses void do_fuses(int flag) Decrements matching fuses; fires and empties any reaching zero

Notes: d_list[MAXDAEMONS=20] of struct delayed_action {type, func ptr, arg, time}. Function pointers are the identity of daemons/fuses — the Go port must replace this with named identifiers (also required by save format, which already maps func ptrs to small ints).

daemons.c (296 lines) — Daemon and fuse callbacks

Function Signature Description
doctor void doctor() Healing daemon: heals based on level and quiet rest counter; R_REGEN ring bonus; caps at max_hp
swander void swander() Fuse that starts the rollwand daemon
rollwand void rollwand() Every turn after 4: 1-in-6 roll to spawn wandering monster via wanderer(), then re-fuse swander
unconfuse void unconfuse() Fuse: clears ISHUH; message
unsee void unsee() Fuse: clears CANSEE; re-hides invisible monsters
sight void sight() Fuse: cures blindness; re-lights room
nohaste void nohaste() Fuse: clears ISHASTE
stomach void stomach() Hunger daemon: decrements food_left (+1 per ring worn, adjusted by ring_eat); thresholds → hungry (300)/weak (150)/faint (0 = random paralysis, starvation death)
come_down void come_down() Fuse: ends hallucination; kills visuals daemon; redraws everything correctly
visuals void visuals() Hallucination daemon: continuously randomizes displayed item/monster glyphs
land void land() Fuse: ends levitation

Notes: Static between counter in rollwand(). These 11 functions are exactly the set the save format maps to integer ids 1-9 (+2).

list.c (114 lines) — Doubly-linked list allocation

Function Signature Description
_detach void _detach(THING **list, THING *item) Removes item from linked list (head pointer updated via double pointer)
_attach void _attach(THING **list, THING *item) Pushes item onto head of list
_free_list void _free_list(THING **ptr) Discards entire list
discard void discard(THING *item) free()s one THING
new_item THING* new_item() calloc()s a zeroed THING with NULL links

Notes: Macro API attach(a,b)/detach(a,b)/free_list(a) pass &list. In Go this becomes slices or container/list — the port will use slices of typed pointers.

fight.c (687 lines) — Combat system

Function Signature Description
fight int fight(coord *mp, THING *weap, bool thrown) Player attacks monster at coordinates with optional weapon. Xeroc disguise revelation, hit/miss messaging, confuse-monster ring/scroll effect (CANHUH), monster death via killed()
attack int attack(THING *mp) Monster attacks player. Handles damage, player death, and per-monster special effects: A=rust armor, I=freeze (no_command), R=weaken strength (poison, save or -1 STR), W=wraith XP drain, V=vampire maxhp drain, F=flytrap hold (accumulating vf_hit damage), L=leprechaun gold steal, N=nymph item steal
set_mname char *set_mname(THING *tp) Returns display name: "something" if unseen, random monster if hallucinating, else "the "; static buffer
swing int swing(int at_lvl, int op_arm, int wplus) d20 to-hit roll: res + wplus >= (20 - at_lvl) - op_arm
roll_em bool roll_em(THING *thatt, THING *thdef, THING *weap, bool hurl) Rolls damage for an attack. Parses damage strings "NxM/NxM", strength to-hit/damage bonus tables, launcher matching for hurled ammo, ring add-hit/add-dam for player; applies to defender's s_hpt (min 0). Returns true if any swing hit
prname char *prname(char *mname, bool upper) Combatant name: "you" for NULL, optionally capitalized; static buffer
thunk void thunk(THING *weap, char *mname, bool noend) "the dagger hits " message for thrown weapons
hit void hit(char *er, char *ee, bool noend) Prints hit message from h_names[] variants (terse-aware)
miss void miss(char *er, char *ee, bool noend) Prints miss message from m_names[] variants
bounce void bounce(THING *weap, char *mname, bool noend) "the dagger misses" message for thrown weapons
remove_mon void remove_mon(coord *mp, THING *tp, bool waskill) Removes monster from level: drops its pack (fall if killed), clears map cell + moat, detaches from mlist, resets to_death/kamikaze if it was target
killed void killed(THING *tp, bool pr) Awards experience, special deaths (F releases held hero, L drops gold), removes monster, "defeated the X" message, check_level()

Notes: Static strength adjustment tables: str_plus[] (to-hit bonus for STR 3-31: -7..+3) and add_dam[] (damage bonus: -7..+6). h_names[]/m_names[] message variant arrays. Careful NULL discipline where L/N monsters remove themselves during their own attack (use-after-free avoidance).

rip.c (449 lines) — Death, victory, and scoreboard

Function Signature Description
score void score(int amount, int flags, char monst) Scoreboard maintenance: reads top-ten via rd_score(), inserts player entry if it qualifies (one score per uid unless allscore), prints ranked list with standout for new entry, writes back via wr_score() under lock_sc()
death void death(char monst) Player death: purse -= 10%, draws tombstone ASCII art with name/gold/killer/year, calls score(), exits
center int center(char *str) Column to center a string on the tombstone
total_winner void total_winner() Victory: banner, itemized inventory with gold values (worth calculations per item type: weapons +bonus*100, armor by AC delta, unidentified halved, etc.), score(flags=2), exit
killname char *killname(char monst, bool doart) Death cause → string: A-Z monster names; lowercase specials from nlist[] (a=arrow, b=bolt, d=dart, h=hypothermia, s=starvation)
death_monst char death_monst() Random death cause for the -d demo flag

Notes: Static rip[] tombstone art; nlist[] special death causes. SCORE fields: uid, score, flags (0=killed 1=quit 2=winner 3=killed-with-amulet), monster, name, level, time. Scoreboard file XOR-encrypted with same scheme as saves.

save.c (391 lines) — Save/restore and encrypted I/O

Function Signature Description
save_game void save_game() Interactive save: prompt for filename (default file_name), confirm overwrite, open and save_file(), exit
auto_save void auto_save(int sig) SIGHUP/SIGTERM handler: silently saves to file_name and exits
save_file void save_file(FILE *savef) Writes version string + "LINES x COLS" header (encrypted), then rs_save_file(); chmod 0400; exits
restore bool restore(char *file, char **envp) Validates version + screen size, rejects symlinks/multi-link files (anti-cheat), deletes save file (no re-use), rs_restore_file(), re-enters playit()
encwrite size_t encwrite(char *start, size_t size, FILE *outf) XOR-encrypts stream: byte ^ encstr[i] ^ statlist[j] ^ feedback byte, cycling both key strings
encread size_t encread(char *start, size_t size, FILE *inf) Mirror decryption of encwrite
rd_score void rd_score(SCORE *top_ten) Reads/decrypts scoreboard entries (name string + sscanf'd numeric line)
wr_score void wr_score(SCORE *top_ten) Writes/encrypts scoreboard entries

Notes: Save anti-cheat: file deleted on restore, symlink/hardlink rejected. Go port decision point: keep byte-compatible format vs. clean format (see Part 2).

state.c (2135 lines) — Save-state serialization layer

Machine-generated-style marshaling of every global into an endian-stable, XOR-encrypted binary stream with per-block magic markers (0xABCD0001-style RSID_* ids).

Family Functions Notes
Primitives rs_write/rs_read (raw), _int, _short, _uint, _chars, _boolean(s), _shorts, _marker Little-endian normalized; count-prefixed arrays; format_error/read_error/write_error flags
Strings _string, _new_string, _string_index, _strings Length-prefixed; _string_index stores index into a master table (e.g. rainbow colors) instead of text
Game types _coord, _str_t, _stats, _room(s), _monsters, _obj_info, _daemons, _window _daemons maps function pointers ↔ integer ids (rollwand=1, doctor=2, stomach=3, runners=4, swander=5, nohaste=6, unconfuse=7, unsee=8, sight=9); _window dumps curses screen contents cell by cell
THINGs _object, _object_list, _object_reference, _thing, _thing_list, rs_fix_thing(_list) Lists serialized with size prefix and rebuilt; intra-structure pointers (cur_weapon, monster t_dest) stored as list indices; monster chase targets encoded as (kind, index) pairs and fixed up post-load via t_reserved
Map _places All MAXLINES*MAXCOLS cells: ch, flags, monster reference index
Helpers get_list_item, find_list_ptr, list_size, find_room_coord, find_thing_coord, find_object_coord Pointer→index translation for serialization
Top level rs_save_file, rs_restore_file Fixed ordered sequence of ALL globals: 27 bools, ~25 ints, strings/name arrays, coords, player THING, equipment references, lvl_obj list, mlist, places[], rooms/passages, monsters[], all 7 obj_info tables, d_list daemons, and the curses screen

Notes: This file exists because C can't reflect. The Go port replaces nearly all of it with encoding/gob (or JSON) over a serializable snapshot struct — only the pointer→index encoding of references (equipment, chase targets) needs explicit code.

options.c (502 lines) — Options menu and ROGUEOPTS parsing

Function Signature Description
option void option() Interactive options screen editing the optlist[] entries in place; per-option get/put function pointers; MINUS backs up, ESCAPE quits
pr_optname void pr_optname(OPTION *op) Prints "prompt (name): "
put_bool / put_str / put_inv_t void put_*(void *) Display current value (True/False, string, inventory-type name)
get_bool int get_bool(void *vp, WINDOW *win) Interactive boolean input (t/f/Enter/Esc/-)
get_sf int get_sf(void *vp, WINDOW *win) see_floor wrapper around get_bool with lamp redraw side effects
get_str int get_str(void *vopt, WINDOW *win) Line editor: backspace, kill-char, ~ home expansion; MAXINP=50 cap
get_inv_t int get_inv_t(void *vp, WINDOW *win) Inventory style selection (Overwrite/Slow/Clear)
get_num int get_num(void *vp, WINDOW *win) (MASTER) numeric via get_str + atoi
parse_opts void parse_opts(char *str) Parses ROGUEOPTS env var: comma-separated flag, noflag, name=value
strucpy void strucpy(char *s1, char *s2, int len) Copies only printable chars, capped

Notes: OPTION struct: name, prompt, value pointer, put function pointer, get function pointer. Options: terse, flush, jump, seefloor, passgo, tombstone, inven, name, fruit, file. In Go this becomes a slice of interface-typed option descriptors or simple closures.

xcrypt.c (708 lines) — DES crypt(3) implementation

Full portable DES implementation (xcrypt(key, setting)) with precomputed permutation tables (~100KB of lookup tables), supporting classic 2-char-salt crypt and extended-format crypt.

Usage in the game: only md_crypt() → wizard-password check (passwd() in wizard.c). NOT used for save encryption (that's the simple XOR in save.c). The Go port can drop this file entirely and gate wizard mode on an environment variable instead.

score.h (27 lines) — Scoreboard entry structure

struct sc_ent {
    unsigned int sc_uid;        /* user id */
    int sc_score;               /* final gold */
    unsigned int sc_flags;      /* 0=killed, 1=quit, 2=winner, 3=killed with amulet */
    unsigned short sc_monster;  /* killer: A-Z monster, or a/b/d/h/s special */
    char sc_name[MAXSTR];       /* player name */
    int sc_level;               /* deepest level */
    unsigned int sc_time;       /* timestamp */
};
typedef struct sc_ent SCORE;

Declares rd_score(SCORE*) / wr_score(SCORE*) (implemented in save.c).


Part 2 — The Go Port Design

Module: git.eeqj.de/sneak/rgoue

1. Goals and principles

  1. Faithful gameplay. The port reproduces Rogue 5.4.4 behavior exactly: same dungeon generation for a given seed, same combat math, same item tables, same messages. The unit of porting is the C function; every C function becomes a Go method (or function) with a recognizable name, ported line by line unless a rule below says otherwise.
  2. No globals. All formerly-global state lives in a single instance type, RogueGame, which owns per-game state and exposes the game's main entrypoint (Run). Two RogueGame values are two independent games. File-scope C statics (pagination counters, shadow copies, scratch buffers) become fields too — either on RogueGame or on the subsystem struct that replaces their file.
  3. Modern data structures. Basic containers are NOT ported. The THING doubly-linked lists become slices ([]*Object, []*Monster). list.c disappears; where the C code's shape depends on list semantics (attach-to-front, detach), small compat helpers keep the ported call sites readable. calloc/free vanish under GC.
  4. OOP where the domain has objects, not everywhere. The THING union splits into Object and Creature (with Player and Monster variants). Behavior that is intrinsically about one entity (naming an object, testing a flag) is a method on that entity. Everything that touches wider game state (most of the game) is a method on *RogueGame, mirroring the C call graph one-to-one.
  5. Standard library first. Exactly one third-party dependency: github.com/gdamore/tcell/v2 — the de facto standard Go terminal cell library — replacing curses. Everything else (saves, scores, RNG, signals, options) is stdlib.
  6. Dead weight is dropped, not ported. mdport.c's 900-line keyboard decoder (tcell events replace it), xcrypt.c's DES (wizard mode gates on an env var instead), load-average checks, tty dsusp-char juggling, and the XOR save encryption all disappear. Each drop is documented in §9.

2. Repository and package layout

The C reference implementation lives on the master/modern-rogue branches; this branch is Go-only. The tcell wrapper ended up in its own term package so that game itself has no third-party imports:

go.mod                        module git.eeqj.de/sneak/rgoue
cmd/rogue/main.go             package main — flag parsing, constructs RogueGame, calls Run
game/                         package game — the port, one .go file per .c file
    game.go                   RogueGame struct, NewGame, Run  (main.c)
    rng.go                    seeded LCG                      (main.c: rnd/roll + RN macro)
    types.go                  Coord, Stats, flags, constants  (rogue.h)
    object.go                 Object type                     (rogue.h THING _o arm)
    creature.go               Creature/Monster/Player         (rogue.h THING _t arm)
    level.go                  Place, Level, map access        (rogue.h places[] + macros)
    tables.go                 monsters[26], obj_info tables, e_levels, rainbow/stones/wood/metal (extern.c)
    init.go                   per-game randomization          (init.c)
    command.go                command dispatch                (command.c)
    io.go                     message/status line             (io.c)
    screen.go                 Screen: tcell wrapper + Window  (curses + mdport display shims)
    newlevel.go               level orchestration             (new_level.c)
    rooms.go                  room/maze generation            (rooms.c)
    passages.go               corridors                       (passages.c)
    move.go                   player movement, traps          (move.c)
    chase.go                  monster AI                      (chase.c)
    monsters.go               monster creation                (monsters.c)
    fight.go                  combat                          (fight.c)
    things.go                 item naming/creation            (things.c)
    pack.go                   inventory                       (pack.c)
    potions.go, scrolls.go, rings.go, sticks.go, weapons.go, armor.go
    misc.go                   look(), eat, direction input    (misc.c)
    daemon.go                 scheduler                       (daemon.c)
    daemons.go                the 11 callbacks                (daemons.c)
    options.go                options screen + ROGUEOPTS      (options.c)
    wizard.go                 wizard commands, identify       (wizard.c)
    rip.go                    tombstone, victory, scores      (rip.c)
    save.go                   save/restore via gob            (save.c + state.c)
    score.go                  scoreboard                      (score.h + rip.c/save.c score I/O)

mach_dep.c, mdport.c, xcrypt.c, state.c, list.c, vers.c have no counterpart files: their few live responsibilities are absorbed (signals → game.go; score file locking → score.go; version string → game.go; serialization → save.go).

3. The RogueGame type

RogueGame is deliberately a god object: it is the C global namespace given a receiver. Fields are grouped into embedded sub-structs where a subsystem is coherent, but everything is reachable from g.

// RogueGame is one complete game of Rogue: all state that was global in the
// C implementation, plus the terminal it plays on. Construct with NewGame,
// then call Run.
type RogueGame struct {
    // --- identity / RNG ---
    Rng      *Rng      // the LCG; seed == C `seed`
    Dnum     int       // dungeon number (the seed as shown to the user)
    Whoami   string    // player name
    Fruit    string    // favorite fruit
    Home     string    // home directory (save default)
    FileName string    // save file path
    Wizard   bool      // debug mode
    NoScore  bool      // wizard was used / scoring disabled

    // --- the world ---
    Player   Player
    Level    Level     // map, rooms, passages, level objects, monsters
    Depth    int       // C `level`
    MaxDepth int       // C `max_level`
    Amulet   bool
    SeenStairs bool

    // --- turn/command engine ---
    Playing  bool
    After    bool      // this action consumes a turn
    Again    bool      // repeating last command
    Count    int       // command repeat count
    NoCommand int      // turns asleep
    NoMove   int       // turns held
    Quiet    int       // consecutive restful turns (heal rate)
    Running  bool
    RunCh    byte
    DoorStop, Firstmove, MoveOn bool
    ToDeath, Kamikaze, HasHit bool
    MaxHit   int
    Take     byte      // glyph of thing picked up this move
    Delta    Coord     // last direction from GetDir
    DirCh    byte
    LastComm, LastDir     byte   // command repeat state ('a' command)
    LLastComm, LLastDir   byte
    LastPick, LLastPick   *Object
    countCh, direction, newCount byte // command.c statics (as bool for newCount)

    // --- daemons/fuses ---
    Daemons  DaemonList // d_list[20] + `between` counter

    // --- messages / UI ---
    scr      *Screen   // tcell-backed terminal (stdscr + hw scratch window)
    Msgs     MsgLine   // msgbuf/newpos/mpos/huh/save state
    Options  Options   // terse, jump, seefloor, passgo, flush, tombstone, inv_type...
    StatMsg  bool
    InvDescribe bool
    Oldpos   Coord     // hero position before last look()
    Oldrp    *Room

    // --- item identity (per-game randomized appearance + discovery) ---
    Items    ItemLore  // p_colors, s_names, r_stones, ws_made, ws_type,
                       // the 7 ObjInfo tables, `group` counter

    // --- scores ---
    LastScore int
    AllScore  bool
    ScorePath string
}

Player-centric C globals move onto Player, world-centric onto Level (see §4/§5); the remainder sit directly on RogueGame with the original semantics. The command.c/io.c/things.c file-statics become lowercase fields of RogueGame, MsgLine, and the inventory pagination helper respectively.

Entrypoints

func NewGame(opts Config) *RogueGame   // Config: seed, name, ROGUEOPTS string, score path, wizard
func (g *RogueGame) Run() error        // main.c main()+playit(): init tables, first level, daemons, loop
func (g *RogueGame) command()          // one turn (command.c)
func Restore(path string, opts Config) (*RogueGame, error)  // save.c restore()

cmd/rogue/main.go is a thin shell: flags (-s scores, -d death demo, save-file arg, SEED/ROGUEOPTS env), NewGame(...) or Restore(...), Run(), exit code.

4. Core types

4.1 Geometry and stats

type Coord struct{ X, Y int }          // value type; C `ce(a,b)` == `a == b`

type Stats struct {
    Str    int      // str_t; 3..31
    Exp    int
    Lvl    int
    Arm    int      // armor class, lower is better
    HP     int      // s_hpt
    Dmg    string   // damage dice "1x4/1x2" — kept as string, parsed by rollEm
    MaxHP  int
}

4.2 The THING union → two types

// Creature is the _t arm of the C THING union: the player or a monster.
type Creature struct {
    Pos      Coord
    Turn     bool        // slowed: is it our turn
    Type     byte        // 'A'..'Z' monster letter; '@' for the player
    Disguise byte        // what a xeroc shows as
    OldCh    byte        // map char underneath
    Dest     *Coord      // chase target — aliases hero pos, another monster's pos, or room gold
    Flags    CreatureFlags
    Stats    Stats
    Room     *Room
    Pack     []*Object
}

type Monster struct{ Creature }

// Player embeds Creature and owns the player-only state that was global in C.
type Player struct {
    Creature
    CurArmor  *Object      // worn armor
    CurWeapon *Object      // wielded weapon
    CurRing   [2]*Object   // LEFT, RIGHT
    PackUsed  [26]bool     // inventory letters in use
    Inpack    int          // pack item count
    Purse     int          // gold
    FoodLeft  int
    HungryState int        // 0..3
    NoFood    int          // levels since food seen
    MaxStats  Stats        // potential (restore-strength target)
    VfHit     int          // venus flytrap grip counter
}

The hero/pstats/pack/proom/max_hp macros become g.Player.Pos / g.Player.Stats / g.Player.Pack / g.Player.Room / g.Player.Stats.MaxHP.

Dest *Coord keeps the C aliasing on purpose: monster targeting works by pointer identity (m.Dest == &g.Player.Pos means "hunting the hero"), which both the AI and the save format rely on.

4.3 Object

type Object struct {
    Type    byte      // POTION '!', SCROLL '?', WEAPON ')', ...
    Pos     Coord
    Text    string    // scroll gibberish
    Launch  int       // launcher weapon index, -1 none
    PackCh  byte      // inventory letter
    Damage  string    // wield dice
    HurlDmg string    // thrown dice
    Count   int       // stack size
    Which   int       // subtype (P_HEALING, MACE, R_PROTECT, ...)
    HPlus, DPlus int
    Arm     int       // OVERLOADED, as in C: armor class | charges | gold value
    Flags   ObjFlags
    Group   int       // stack group id
    Label   string    // player-applied name
}

func (o *Object) Charges() int     { return o.Arm }   // sticks
func (o *Object) SetCharges(n int) { o.Arm = n }
func (o *Object) GoldVal() int     { return o.Arm }   // gold piles

The overload stays (one field, named accessors) so ported arithmetic like "rust: o.Arm++" keeps its original shape.

4.4 Flags

type CreatureFlags uint32
type ObjFlags uint32
type RoomFlags uint16
type PlaceFlags uint8

const (
    CanHuh CreatureFlags = 1 << iota // creature can confuse
    CanSee; IsBlind; IsCanc; IsFound; IsGreed; IsHaste; IsTarget
    IsHeld; IsHuh; IsInvis; IsMean; IsRegen; IsRun; IsFly; IsSlow
    // hero aliases sharing C bit values are separate named constants:
    IsLevit = IsCanc; IsHalu = IsMean; SeeMonst = IsFly
)

func (f CreatureFlags)  Has(b CreatureFlags) bool
func (f *CreatureFlags) Set(b CreatureFlags)
func (f *CreatureFlags) Clear(b CreatureFlags)

C's on(player, ISHALU) ports to g.Player.Flags.Has(IsHalu). The C bit collisions (ISCANC/ISLEVIT, ISMEAN/ISHALU, ISFLY/SEEMONST) are preserved as equal values — they are disjoint by owner (monster vs hero), and the save format keeps working.

4.5 Level and Place

type Place struct {
    Ch    byte        // base map character
    Flags PlaceFlags  // FPass|FSeen|FDropped|FReal|FPnum|FTmask
    Monst *Monster
}

// Level is the current dungeon level. It is reset in place by NewLevel,
// matching the C reuse of the global arrays.
type Level struct {
    Places   [MaxLines * MaxCols]Place   // same column-major INDEX(y,x) layout
    Rooms    [MaxRooms]Room
    Passages [MaxPass]Room
    Objects  []*Object     // lvl_obj
    Monsters []*Monster    // mlist
    Stairs   Coord
    NTraps   int
}

func (l *Level) At(y, x int) *Place          // INDEX
func (l *Level) Char(y, x int) byte          // chat
func (l *Level) SetChar(y, x int, ch byte)
func (l *Level) FlagsAt(y, x int) *PlaceFlags // flat
func (l *Level) MonsterAt(y, x int) *Monster  // moat
func (l *Level) VisibleChar(y, x int) byte    // winat: disguise if monster, else Ch

4.6 Room

type Room struct {
    Pos     Coord     // upper-left
    Max     Coord     // size
    Gold    Coord
    GoldVal int
    Flags   RoomFlags // IsDark | IsGone | IsMaze
    Exits   []Coord   // r_exit[12] + r_nexits → slice
}

4.7 Static tables

tables.go holds the immutable data as package-level constants and var tables that are never written after init (the only permitted package-level data — they are ROM, not state): monsterTable [26]MonsterKind (name, carry %, flags, stats), initDam weapon table, base objInfo tables (copied into RogueGame.Items at NewGame so per-game mutation — probability resumming, oi_know, oi_guess — stays instance-local), eLevels, rainbow, stones, woods, metals, sylls, trap names, help text, lvlMons/wandMons spawn strings, hNames/mNames combat messages.

type ObjInfo struct {
    Name  string
    Prob  int      // cumulative after initProbs
    Worth int
    Guess string   // player's "call it" label
    Know  bool
}

type ItemLore struct {
    PotColors  [MaxPotions]string   // p_colors
    ScrNames   [MaxScrolls]string   // s_names
    RingStones [MaxRings]string     // r_stones
    WandMade   [MaxSticks]string    // ws_made
    WandType   [MaxSticks]string    // ws_type ("wand"/"staff")
    Things     [NumThings]ObjInfo
    Potions    [MaxPotions]ObjInfo
    Scrolls    [MaxScrolls]ObjInfo
    Rings      [MaxRings]ObjInfo
    Sticks     [MaxSticks]ObjInfo
    Weapons    [MaxWeapons + 1]ObjInfo
    Armors     [MaxArmors]ObjInfo
    Group      int                  // stack group counter
}

5. Subsystems

5.1 RNG — exact LCG compatibility

type Rng struct{ Seed int32 }

func (r *Rng) next() int {            // the RN macro
    r.Seed = r.Seed*11109 + 13849     // 32-bit wraparound, as C int
    return int(r.Seed>>16) & 0xffff
}
func (g *RogueGame) Rnd(rng int) int  // rnd(): 0 if range==0 else abs(RN)%range
func (g *RogueGame) Roll(n, sides int) int
func (g *RogueGame) Spread(n int) int // misc.c: n ± 10%

Same seed → same dungeon, byte for byte. This is the port's master correctness oracle (§10).

5.2 Daemons and fuses — function pointers → identifiers

C identifies daemons by function pointer; the save format already maps them to small integers. The port makes that mapping primary:

type DaemonID int
const (
    DRollwand DaemonID = iota + 1 // matches state.c save ids
    DDoctor; DStomach; DRunners; DSwander
    DNohaste; DUnconfuse; DUnsee; DSight
    DVisuals; DComeDown; DLand    // extra fuses beyond the C save map
)

type delayedAction struct {
    Type int       // EMPTY / BEFORE / AFTER
    Func DaemonID
    Arg  int
    Time int       // DAEMON sentinel or fuse countdown
}

type DaemonList struct {
    List    [MaxDaemons]delayedAction
    Between int    // daemons.c rollwand static
}

func (g *RogueGame) StartDaemon(f DaemonID, arg, typ int)
func (g *RogueGame) Fuse(f DaemonID, arg, time, typ int)
func (g *RogueGame) Lengthen(f DaemonID, xtime int)
func (g *RogueGame) Extinguish(f DaemonID)
func (g *RogueGame) KillDaemon(f DaemonID)
func (g *RogueGame) DoDaemons(flag int)   // dispatches via one switch on DaemonID
func (g *RogueGame) DoFuses(flag int)

The 11 callbacks in daemons.go become methods (g.doctor(), g.stomach(), ...) invoked from the dispatch switch. This is more robust than function values because it is comparable and serializable — the two properties the C design needed pointers for.

5.3 Screen — curses → tcell

// Screen wraps tcell and exposes the curses vocabulary the ported code speaks.
type Screen struct {
    T       tcell.Screen
    Std     *Window   // stdscr equivalent (the game map is drawn here)
    Hw      *Window   // the scratch window for inventory/help overlays
}

// Window is an in-memory cell buffer with a cursor, standout attribute, and
// the handful of curses ops the game uses.
type Window struct{ ... }
func (w *Window) Move(y, x int); func (w *Window) AddCh(ch byte)
func (w *Window) MvAddCh(y, x int, ch byte); func (w *Window) MvAddStr(y, x int, s string)
func (w *Window) Printw(format string, a ...any); func (w *Window) Inch(y, x int) byte
func (w *Window) Clear(); func (w *Window) Clrtoeol(); func (w *Window) Standout(on bool)
func (s *Screen) Refresh(w *Window)         // blit buffer to tcell
func (s *Screen) ReadChar() byte            // event loop → C char codes (arrows→hjkl, ^C→quit)

Ported drawing code keeps its structure: mvaddch(y, x, ch)g.scr.Std.MvAddCh(y, x, ch). Curses mvinch reads come from the Window buffer, preserving the "screen is a data structure" idiom without touching the real terminal. md_readchar's escape decoding is deleted; tcell's EventKey provides decoded keys and we translate to the byte codes command() already handles (KeyUp → 'k', etc.). SIGTSTP/resume and resize are handled by tcell; SIGHUP/SIGTERM → autoSave via os/signal.

5.4 Messaging

type MsgLine struct {
    buf      strings.Builder // msgbuf/newpos
    Mpos     int             // cursor col on the message line
    Huh      string          // last message (Ctrl-R repeat)
    SaveMsg  bool; LowerMsg bool; MsgEsc bool
}
func (g *RogueGame) Msg(format string, a ...any) bool  // msg(); returns ESC-pressed
func (g *RogueGame) AddMsg(format string, a ...any)
func (g *RogueGame) EndMsg() bool

C's %s+vowelstr message assembly ports directly on fmt. The io.c status-line shadow statics become fields of an unexported statusCache struct on RogueGame.

5.5 Options

type Options struct {
    Terse, FightFlush, Jump, SeeFloor, PassGo, Tombstone bool
    InvType int  // INV_OVER / INV_SLOW / INV_CLEAR
}

ROGUEOPTS parsing (parse_opts) and the interactive option screen port as-is; the C per-option get/put function pointers become a slice of option descriptors holding closures.

5.6 Save/restore — gob replaces state.c

The 2,134-line hand-rolled serializer exists because C cannot reflect. Go can: the save file is encoding/gob of a SaveState snapshot struct (version-tagged, gzip-compressed). C-format saves are not readable — the formats were never cross-version compatible anyway (the file embeds the exact version string).

What still needs explicit code is exactly what state.c's rs_fix_thing did: pointers that alias other structures are encoded as indices —

type SaveState struct {
    Version  string
    Game     RogueGame          // gob-friendly: exported fields
    // pointer fixups:
    CurArmorIdx, CurWeaponIdx int // index into Player.Pack, -1 = nil
    CurRingIdx  [2]int
    MonstDest   []DestRef       // per monster: {Kind: hero|monster|object|gold, Index int}
    PlaceMonst  []int           // per map cell: index into Level.Monsters
}

Save-file hygiene keeps the C behavior: the file is deleted on restore, and Run exits after a successful save. The XOR encryption and symlink checks are dropped (they were 1980s shared-machine anti-cheat).

5.7 Scoreboard

SCOREScoreEnt struct; the score file is a gob (or JSON) list at ~/.rogue_scores (overridable). Locking via os.OpenFile(O_CREATE|O_EXCL) lock file with stale-age takeover, porting lock_sc/unlock_sc semantics. Tombstone/victory screens port verbatim from rip.c.

6. C construct → Go construct map

C construct Go translation
global variable RogueGame field (or Player/Level/subsystem field)
file-scope static unexported field on RogueGame or subsystem struct
THING union Creature / Object structs
linked lists (l_next/l_prev, attach/detach) slices + attach(&s, x)-style helpers (prepend, remove by identity)
coord* aliasing (t_dest) *Coord pointers into live structs (kept)
function pointers (daemons, options, nameit) enum IDs + dispatch switch (daemons, save-relevant); closures (options, nameit)
when/otherwise/until macros case/default/for !cond
goto (retry loops in pack.c, move.c, passages.c) labeled loops / restructured for
char-indexed tables (monsters[type-'A']) same, monsterTable[t.Type-'A']
damage strings "3x4/1x2" kept as strings; parsed by rollEm (strings/strconv)
curses stdscr/hw windows Window cell buffers over tcell
mvinch screen reads Window.Inch from the buffer
signal handlers (SIGHUP autosave, SIGTSTP, SIGINT) os/signal goroutine → channel checked in ReadChar; tcell handles TSTP/resize
setjmp-free exits (my_exit, exit() everywhere) error returns from Run + a small gameOver sentinel; no os.Exit inside game logic
vsprintf message building fmt.Sprintf
XOR-encrypted binary saves (state.c) gob snapshot (§5.6)
DES wizard password ROGUE_WIZARD=1 env check
md_* platform shims stdlib (os, os/user, time) or deleted

Exit discipline deserves a note: the C code calls exit() from deep inside call chains (death, victory, save). The port threads a g.gameOver(reason) that sets Playing=false and unwinds via normal returns to Run — the one structural change made everywhere, since os.Exit would skip tcell teardown and defers.

7. Naming conventions

  • C function do_move → method (g *RogueGame) DoMove(dy, dx int); the C name is preserved in CamelCase so cross-referencing Part 1 is mechanical. Statics/internal helpers are lowerCamel (g.doaddg.addMsgV).
  • Entity-intrinsic functions become entity methods where they read no game state: inv_name(obj, drop) needs ItemLore → (g *RogueGame) InvName(o *Object, drop bool) string; but is_magic(obj)(o *Object) IsMagic() bool.
  • Boolean C bool/int returns stay bool; char returns become byte.
  • coord* out-params become Coord returns (or (Coord, bool) for find-style functions like fallpos/find_floor).

8. Porting order (each step compiles, is committed, and is testable)

  1. Foundation: go.mod, types.go, object.go, creature.go, level.go, rng.go, tables.go, game.go skeleton. Unit tests: RNG sequence vs C formula, table sanity (probabilities sum to 100).
  2. Dungeon generation: rooms.go, passages.go, newlevel.go + enough of misc.go/monsters.go/things.go to place things. Test: fixed-seed level render to text, eyeballed against C build.
  3. Items: things.go, pack.go, init.go + ItemLore.
  4. Creatures & combat: monsters.go, chase.go, fight.go, daemon(s).go.
  5. UI: screen.go, io.go, options.go, misc.go look(), command.go, move.go. First playable build.
  6. Effects: potions.go, scrolls.go, rings.go, sticks.go, weapons.go, armor.go, wizard.go.
  7. Endgame: rip.go, score.go, save.go, cmd/rogue. Full game loop, save, scores.

9. Dropped C functionality (deliberate)

Dropped Why Replacement
md_readchar escape decoding tcell decodes keys key-event translation table
XOR save/score encryption obscurity, not security plain gob (file perms 0600)
save-file symlink/hardlink checks single-user era anti-cheat none
DES crypt wizard password ditto ROGUE_WIZARD env var
load-average / user-count gating (too_much, ucount, CHECKTIME) 1980s timesharing courtesy none
tty dsusp/ltc character juggling tcell owns the tty none
shell escape (!) setuid dance no privileges to drop plain os/exec shell
curses window save in save file screen is derivable redraw on restore

10. Testing strategy

  • RNG oracle: golden test locking the LCG sequence for known seeds.
  • Generation goldens: for a set of seeds, render generated levels (map chars + room/passage metadata) to text fixtures; any diff = a porting bug in rooms/passages/newlevel. Fixtures generated once from instrumented C, or accepted from the first correct Go run and guarded thereafter.
  • Combat math tests: swing/rollEm against hand-computed cases, str_plus/add_dam table spot checks.
  • Determinism test: scripted input sequence against a fixed seed must produce identical final state across runs (guards hidden nondeterminism — map iteration, time dependence).
  • Save round-trip: NewGame → play N scripted turns → save → restore → compare full state.
  • go vet + gofmt clean at every commit.