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.
1497 lines
100 KiB
Markdown
1497 lines
100 KiB
Markdown
# 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
|
||
|
||
```c
|
||
typedef struct { int x; int y; } coord;
|
||
```
|
||
|
||
Used for every position: hero, monsters, room corners, exits, deltas.
|
||
|
||
### 3.2 `str_t` — strength
|
||
|
||
```c
|
||
typedef unsigned int str_t; /* strength 3..31 */
|
||
```
|
||
|
||
### 3.3 `struct stats` — fighting statistics (player and monsters)
|
||
|
||
```c
|
||
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`, ...).
|
||
|
||
```c
|
||
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
|
||
|
||
```c
|
||
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
|
||
|
||
```c
|
||
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
|
||
|
||
```c
|
||
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
|
||
|
||
```c
|
||
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
|
||
|
||
```c
|
||
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 `#define`s 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 (| - / \\); dragons immune to flame |
|
||
| `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 <name>"; 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 <m>" 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
|
||
|
||
```c
|
||
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`.
|
||
|
||
```go
|
||
// 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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
// 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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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.
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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:
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
// 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
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
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 —
|
||
|
||
```go
|
||
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
|
||
|
||
`SCORE` → `ScoreEnt` 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.doadd` → `g.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.
|