/* * This file includes various "convert to text form" functions. Since * they are debugging code, invalid values get printed in decimal * (usually into a static buffer) and returned rather than provoking * panics. */ #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "obj.h" #include "vars.h" #include "dice.h" #include "time.h" #include "util.h" #include "pline.h" #include "trail.h" #include "gates.h" #include "screen.h" #include "display.h" #include "structs.h" #include "signals.h" #include "montypes.h" #include "mon-@-you.h" #include "digdungeon.h" #include "stdio-util.h" #include "debug.h" #define TRACK_EXIT (-1) /* pseudo value used only here */ typedef enum { TT_SEED = 1, TT_GETCH_CHAR, TT_GETCH_INT, TT_TEXT, } TRCTYPE; typedef struct trcrec TRCREC; typedef struct espc ESPC; struct espc { unsigned char *b; int a; int l; } ; struct trcrec { TRCREC *link; TRCTYPE type; union { // TT_SEED unsigned long long int seed; // TT_GETCH_CHAR ESPC getch; // TT_GETCH_INT int ch_int; // TT_TEXT ESPC text; } u; } ; /* * The game's trace. This is always kept, even if tracing is off, so * it can be dumped in case of a panic. */ static TRCREC *tracehead = 0; static TRCREC *tracetail = 0; /* * The FILE * to which tracing output is to be written, or nil if * tracing is not turned on. */ static FILE *trcf = 0; /* * The FILE * on the file being replayed, or nil if no file is being * replayed. */ static FILE *repf = 0; /* * Replaying state. * * repstep is the number of single-character steps we should do before * interacting with the user; negative values count as infinity. */ static int repstep = 0; /* * Flag, meaningful only when tracetail exists and tracetail->type is * TT_TEXT. Then, it indicates that we are at the beginning of a line * (and thus should generate the sharp-space prefix). */ static int text_atnl; /* * Check that a LOC is a legitimate cell on a LEVEL. * * This is not possible to do with reasonable efficiency in fully * portable C; the only way I know of to do that is to scan every LOC * of the LEVEL and see if it equals the argument. * * This version depends on nonportable properties of pointer->integer * conversion. */ static int ok_loc_(LOC *l, LEVEL *lv) { return( ((unsigned long int)l >= (unsigned long int)&lv->cells[0][0]) && ((unsigned long int)l <= (unsigned long int)&lv->cells[LEV_X-1][LEV_Y-1]) ); } /* * See if the LOC is a valid location. The LEVEL is checked first; it * can be thought of as an `expected' level. */ static int ok_loc(LOC *l, LEVEL *lv) { int n; if (ok_loc_(l,lv)) return(1); for (n=0;n 127) && (c < 160))) { fprintf(f,"^%c",c^64); } else { fprintf(f,"%c",c); } } /* * Print a text-form trace of getch() returning [0..255]. */ static void print_trace_getch_1(FILE *f, unsigned char c) { fprintf(f,"getch: 0x%02x (",c); print_1c(f,c); fprintf(f,")\n"); } /* * Print a text-form trace of getch() returning something outside * [0..255]. */ static void print_trace_getch_int(FILE *f, int c) { fprintf(f,"getch %d\n",c); } /* * Print a text-form trace of arbitrary text. */ static void print_trace_text(FILE *f, const char *s, int l) { void *nl; int n; while (l > 0) { if (text_atnl) { fprintf(f,"# "); text_atnl = 0; } nl = memchr(s,'\n',l); if (nl) { n = (1+(const char *)nl) - s; fwrite(s,1,n,f); s += n; l -= n; text_atnl = 1; continue; } fwrite(s,1,l,f); text_atnl = 0; break; } } /* * Dump out trace text in a form suitable for a crash dump. Arg is the * FILE * to write the result to. */ static void dump_trace(FILE *to) { FILE *f; int atnl; TRCREC *r; int i; int w(void *cookie __attribute__((__unused__)), const char *data, int len0) { int len; int n; void *nl; len = len0; while (len > 0) { if (atnl) { putc('\t',to); atnl = 0; } nl = memchr(data,'\n',len); if (nl) { n = (1+(const char *)nl) - data; fwrite(data,1,n,to); data += n; len -= n; atnl = 1; continue; } else { fwrite(data,1,len,to); atnl = 0; break; } } return(len0); } atnl = 1; f = funopen(0,0,&w,0,0); for (r=tracehead;r;r=r->link) { switch (r->type) { case TT_SEED: print_trace_seed(f,r->u.seed); break; case TT_GETCH_CHAR: for (i=0;iu.getch.l;i++) print_trace_getch_1(f,r->u.getch.b[i]); break; case TT_GETCH_INT: print_trace_getch_int(f,r->u.ch_int); break; case TT_TEXT: text_atnl = 1; print_trace_text(f,r->u.text.b,r->u.text.l); break; default: fflush(f); fprintf(to,"? trace record bad type %d\n",(int)r->type); break; } } fclose(f); if (! atnl) putc('\n',to); } /* * Catch a signal and dump debugging information. If the signal is * SIGQUIT, do this in a forked child. If sig is zero, this is a call * from panic(). * * The major thing worthy of note here is the way nested signals are * caught; this is to deal with corrupted pointers in data structures. */ void debugsig(int sig) { FILE *f; FILE *o; MONST *m; int i; static jmp_buf bad; static volatile int inhere = 0; if (inhere) longjmp(bad,1); inhere = 1; o = fdopen(dup(1),"w"); setbuf(o,(char *)0); fprintf(o,"Caught signal %d\r\n",sig); if (sig == SIGQUIT) { fprintf(o,"Dumping in child...\r\n"); if (fork() != 0) { wait(0); fprintf(o,"Dump done\r\n"); fclose(o); inhere = 0; return; } } signal(SIGQUIT,SIG_DFL); f = fopen("heck.debug","w"); if (f == 0) f = fopen("/tmp/heck.debug","w"); if (f) { int lno; LEVEL *lv; int x; int y; LOC *l; if (setjmp(bad)) { unexsig:; write(fileno(f),"Caught unexpected signal during dump\n",37); _exit(1); } fprintf(f,"Game trace:\n"); dump_trace(f); fprintf(o,"Checking on pointers and coordinates...\r\n"); for (lno=0;lnocells[x][0]; fprintf(o,"%d: %d \r",lno,x); for (y=0;yon != lv) { fprintf(f,"on wrong: (%d,%d,%d)\n",lv->levelno,x,y); l->on = lv; } if ((l->x != x) || (l->y != y)) { fprintf(f,"xy wrong: (%d,%d,%d) (%d,%d)\n",lv->levelno,x,y,l->x,l->y); l->x = x; l->y = y; } l ++; } } } fprintf(o,"Dumping levels...\r\n"); for (lno=0;lnolevelno); fprintf(f,"flags"); if (lv->flags & LVF_WRAPAROUND) fprintf(f," WRAPAROUND"); if (lv->flags & LVF_DRIFT) fprintf(f," DRIFT"); if (lv->flags & LVF_SEEN) fprintf(f," SEEN"); fprintf(f,"\n"); if (setjmp(bad)) goto unexsig; if (lv->up) { if (ok_lev(lv->up)) { fprintf(f,"up %d\n",lv->up->levelno); } else { fprintf(f,"up bad\n"); } } else { fprintf(f,"up null\n"); } if (lv->down) { if (ok_lev(lv->down)) { fprintf(f,"down %d\n",lv->down->levelno); } else { fprintf(f,"down bad\n"); } } else { fprintf(f,"down null\n"); } for (x=0;xcells[x][0]; for (y=0;ytype) { case LOC_ROCK: fprintf(f,"R-"); break; case LOC_WALL: fprintf(f,"W"); if (0) { case LOC_SDOOR: fprintf(f,"S"); } if (0) { case LOC_DOOR: fprintf(f,"D"); } if (l->walldoor > 15) { fprintf(f,"(%x)",l->walldoor); } else { fprintf(f,"%x",l->walldoor); } break; case LOC_CAVE: switch (l->cavetype) { case LOC_JUSTCAVE: fprintf(f,"C-"); break; case LOC_STAIRS_U: fprintf(f,"C<"); break; case LOC_STAIRS_D: fprintf(f,"C>"); break; default: fprintf(f,"C(%d)",(int)l->cavetype); break; } break; case LOC_GATE: switch (l->gatetype) { case LOC_GATE_DUNGEON: fprintf(f,"Gd"); break; case LOC_GATE_UWORLD_DUNGEON: fprintf(f,"Gud"); break; case LOC_GATE_UWORLD_HELL: fprintf(f,"Guh"); break; case LOC_GATE_HELL: fprintf(f,"Gh"); break; case LOC_GATE_ELEM_DUNGEON: fprintf(f,"Ged"); break; case LOC_GATE_ELEM_UWORLD: fprintf(f,"Geh"); break; default: fprintf(f,"G(%d)",(int)l->gatetype); break; } break; default: fprintf(f,"(%d)",(int)l->type); break; } if (l->flags & LF_FLAG) putc('F',f); if (l->flags & LF_FLAGB) putc('B',f); if (l->flags & LF_NOCROWN) putc('C',f); if (l->flags & LF_VISIBLE) putc('V',f); for (t=0;ttracks[t] == 0) { putc('0',f); } else if (ok_loc(l->tracks[t],lv)) { if (l->tracks[t]->on != l->on) { fprintf(f,"(%d,%d,%d)",l->tracks[t]->on->levelno,l->tracks[t]->x,l->tracks[t]->y); } else { fprintf(f,"(%d,%d)",l->tracks[t]->x,l->tracks[t]->y); } } else { fprintf(f,"(bad:%p)",(void *)l->tracks[t]); } } if (l->objs.inv) { fprintf(f,"o(%p)",(void *)l->objs.inv); /* XXX dump objects here */ } if (l->monst) { fprintf(f,"m(%p)",(void *)l->monst); } if (l->to) { if (ok_loc(l->to,l->to->on)) { fprintf(f,"t(%d,%d,%d)",l->to->on->levelno,l->to->x,l->to->y); } else { fprintf(f,"t(bad)"); } } l ++; } putc('\n',f); } if (lv->shops) { SHOP *sp; SHOP s; fprintf(f,"shops\n"); for (sp=lv->shops;sp;sp=sp->link) { fprintf(f,"%p",(void *)sp); if (setjmp(bad)) { fprintf(f,"\n"); continue; } else { s = *sp; } if (s.on == 0) { fprintf(f," on null"); } else if (s.on != lv) { fprintf(f," on %p (want %p)",(void *)s.on,(void *)lv); } else { fprintf(f," on correct"); } fprintf(f," shkname "); if (setjmp(bad)) { fprintf(f,""); } else { fprintf(f,"%s",s.shkname); } fprintf(f," type %d",s.type); switch (s.type) { case SHOP_TYPE_GENERAL: fprintf(f," [GENERAL]"); break; default: fprintf(f," (unknown)"); break; } fprintf(f," at (%d,%d) size (%d,%d) ([%d..%d] X [%d..%d]), door (%d,%d)\n",s.pos.x,s.pos.y,s.size.x,s.size.y,s.pos.x,s.pos.x+s.size.x-1,s.pos.y,s.pos.y+s.size.y-1,s.door.x,s.door.y); } } else { fprintf(f,"no shops\n"); } } fprintf(o,"Dumping monster types...\r\n"); fprintf(f,"Monster types:\n"); for (i=0;i"); else fprintf(f,"%s",mt->name); fprintf(f," letter %c new %p monctl %p\n",mt->letter,(void *)mt->info->new,(void *)mt->info->monctl); } fprintf(o,"Dumping monsters...\r\n"); fprintf(f,"Monsters:\n"); if (setjmp(bad)) { fprintf(f,"\n"); } else { for (m=mchain;m;m=m->link) { MONST M; struct mflag { int flag; const char *name; } ; typedef struct mflag MFLAG; static MFLAG mbflags[] = { { MBF_DEAD, "DEAD" }, { MBF_ASLEEP, "ASLEEP" }, { MBF_INVISIBLE, "INVISIBLE" }, { MBF_TELEPATHIC, "TELEPATHIC" }, { MBF_SAWYOU, "SAWYOU" }, { MBF_TRACKING, "TRACKING" }, { MBF_CROWN, "~" }, { 0, 0 } }; static MFLAG meflags[] = { { MEF_INVISIBLE, "INVISIBLE" }, { MEF_SEE_INVISIBLE, "SEE_INVISIBLE" }, { MEF_VAMP_REGEN, "VAMP_REGEN" }, { MEF_PHASING, "PHASING" }, { MEF_BREATHE_WATER, "BREATHE_WATER" }, { MEF_RESIST_FIRE, "RESIST_FIRE" }, { MEF_PLANE_E, "E" }, { MEF_PLANE_A, "A" }, { MEF_PLANE_F, "F" }, { MEF_PLANE_W, "W" }, { 0, 0 } }; int first; int flg; fprintf(f,"%p:",(void *)m); M = *m; fprintf(f," type "); for (i=0;i",(void *)M.type); } fprintf(f," sym %c hp %d",M.symbol,M.hp); fprintf(f," bflags %x <",M.baseflags); first = 1; flg = M.baseflags; for (i=0;mbflags[i].name;i++) { if (flg & mbflags[i].flag) { fprintf(f,"%s%s",first?"":",",mbflags[i].name); first = 0; flg &= ~mbflags[i].flag; } } if (flg) fprintf(f,"%s%x",first?"":",",flg); fprintf(f,"> eflags %x <",M.effflags); first = 1; flg = M.effflags; for (i=0;meflags[i].name;i++) { if (flg & meflags[i].flag) { fprintf(f,"%s%s",first?"":",",meflags[i].name); first = 0; flg &= ~meflags[i].flag; } } if (flg) fprintf(f,"%s%x",first?"":",",flg); fprintf(f,"> speed %g age %d you (%d,%d) priv %p ops %p invent ",M.speed/(double)TIMESCALE,M.age,M.you.x,M.you.y,M.priv,(void *)M.ops); fprintf(f,M.invent.inv?"%p loc ":"null loc ",(void *)M.invent.inv); /* XXX dump objects here */ if (M.loc) { if (ok_loc(M.loc,&Levels[0])) { fprintf(f,"%d,%d,%d",M.loc->on->levelno,M.loc->x,M.loc->y); } else { fprintf(f,"bad"); } } else { fprintf(f,"null"); } fprintf(f," lastloc "); if (M.lastloc) { if (ok_loc(M.lastloc,&Levels[0])) { fprintf(f,"%d,%d,%d",M.lastloc->on->levelno,M.lastloc->x,M.lastloc->y); } else { fprintf(f,"bad"); } } else { fprintf(f,"null"); } fprintf(f,"\n"); } fclose(f); } fprintf(o,"Dumping core...\r\n"); } abort(); } /* * Check that a monster is in a sane location; if not, bugcheck. */ void debug_checkm(MONST *m) { if (! ok_loc(m->loc,&Levels[0])) { debugsig(0); } } /* * Set various fatal signals to call the handler passed as argument. */ static void setdebugsigs(void (*how)(int)) { signal(SIGQUIT,how); signal(SIGILL,how); signal(SIGEMT,how); signal(SIGFPE,how); signal(SIGBUS,how); signal(SIGSEGV,how); signal(SIGSYS,how); signal(SIGPIPE,how); } /* * Catch various fatal signals with debugsig (see above). */ void debugcatch(void) { setdebugsigs(&debugsig); } /* * Return the text name of a LOC type. */ static const char *typename(LOC *lc) { static char buf[10]; switch (lc->type) { case LOC_ROCK: return("ROCK"); break; case LOC_WALL: return("WALL"); break; case LOC_SDOOR: return("SDOOR"); break; case LOC_CAVE: return("CAVE"); break; case LOC_GATE: return("GATE"); break; case LOC_DOOR: return("DOOR"); break; } sprintf(&buf[0],"%d",lc->type); return(&buf[0]); } /* * Return the text name of a LOC's subtype. */ static const char *subtype(LOC *lc) { static char buf[10]; switch (lc->type) { case LOC_WALL: case LOC_SDOOR: switch (lc->walldoor) { case LOC_UWALL: return("UWALL"); break; case LOC_HWALL: return("HWALL"); break; case LOC_VWALL: return("VWALL"); break; case LOC_TWALL: return("TWALL"); break; } sprintf(&buf[0],"%d",lc->walldoor); break; case LOC_CAVE: switch (lc->cavetype) { case LOC_JUSTCAVE: return("JUSTCAVE"); break; case LOC_STAIRS_U: return("STAIRS U"); break; case LOC_STAIRS_D: return("STAIRS D"); break; } sprintf(&buf[0],"%d",(int)lc->cavetype); break; case LOC_GATE: switch (lc->gatetype) { case LOC_GATE_DUNGEON: return("GATE DUNGEON"); break; case LOC_GATE_UWORLD_DUNGEON: return("GATE UWORLD->DUNGEON"); break; case LOC_GATE_UWORLD_HELL: return("GATE UWORLD->HELL"); break; case LOC_GATE_HELL: return("GATE HELL"); break; case LOC_GATE_ELEM_DUNGEON: return("GATE ELEM->DUNGEON"); break; case LOC_GATE_ELEM_UWORLD: return("GATE ELEM->UWORLD"); break; } sprintf(&buf[0],"%d",(int)lc->gatetype); break; case LOC_DOOR: switch (lc->walldoor) { case LOC_CHDOOR: return("CHDOOR"); break; case LOC_CVDOOR: return("CVDOOR"); break; case LOC_OHDOOR: return("OHDOOR"); break; case LOC_OVDOOR: return("OVDOOR"); break; } sprintf(&buf[0],"%d",lc->walldoor); break; default: sprintf(&buf[0],"(type%d)",(int)lc->type); break; } return(&buf[0]); } /* * Return (via a static buffer) a flag bits string for a LOC. This * does not cover all the flag bits, just the ones I think worth * mentioning in debugging output. */ static const char *flags(LOC *lc) { static char buf[256]; static struct { int bit; const char *name; } list[] = { { LF_SHOP, "SHOP" }, { LF_VAULT, "VAULT" }, { LF_NOCROWN, "NOCROWN" }, { LF_FLAG, "FLAG" }, { LF_FLAGB, "FLAGB" }, { LF_VISIBLE, "VISIBLE" }, { LF_WATER, "WATER" }, { LF_MEMORIZED, "MEMORIZED" } }; char *cp; int bits; int i; cp = &buf[0]; bits = lc->flags; for (i=0;i= 0) return(lc->tracks[tracktype]); switch (tracktype) { case TRACK_EXIT: { LOC *best; int nb; LOC *lc2; int i; void maybesave(LOC *l) { if ( (nb == 0) || (l->exitdist < best->exitdist) ) { best = l; nb = 1; } else if (l->exitdist == best->exitdist) { nb ++; if (onein(nb)) best = l; } } if (lc->exitdist == 0) return(0); nb = 0; for (i=0;i<8;i++) { lc2 = movecell(lc,delta[i][0],delta[i][1],1); if (! lc2) continue; if ( (lc2->type == LOC_GATE) || (lc2->type == LOC_DOOR) || (lc2->type == LOC_SDOOR) || (lc2->type == LOC_CAVE) ) maybesave(lc2); } if ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_DEBUG))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) maybesave(lc2); return(nb?best:0); } break; } panic("bad tracktype %d in debug follow_track",tracktype); } /* * Dump out info on a monster. This is designed for in-game player use * from debugging code. */ static void dump_monster(MONST *m) { int lno; INVOBJ *io; const char *prompt; DLLS line(FILE *f) { switch (lno++) { case 1: fprintf(f,"%s (%c) HP=%d/%d heal=%d age=%d", m->type->name, m->symbol, m->hp, m->maxhp, m->heal, m->age); break; case 2: fprintf(f," speed=%g created=%g you=(%d,%d)", m->speed/(double)TIMESCALE, m->created/(double)TIMESCALE, m->you.x, m->you.y ); break; case 3: fprintf(f," flags ="); if (m->baseflags & MBF_DEAD) fprintf(f," DEAD"); if (m->baseflags & MBF_ASLEEP) fprintf(f," ASLEEP"); if (m->baseflags & MBF_INVISIBLE) fprintf(f," INVISIBLE"); if (m->baseflags & MBF_TELEPATHIC) fprintf(f," TELEPATHIC"); if (m->baseflags & MBF_SAWYOU) fprintf(f," SAWYOU"); if (m->baseflags & MBF_TRACKING) fprintf(f," TRACKING"); if (m->baseflags & MBF_CROWN) fprintf(f," ~"); if (m->baseflags & MBF_CANCEL) fprintf(f," CANCEL"); fprintf(f," /"); if (m->effflags & MEF_INVISIBLE) fprintf(f," INVISIBLE"); if (m->effflags & MEF_SEE_INVISIBLE) fprintf(f," SEE_INVISIBLE"); if (m->effflags & MEF_VAMP_REGEN) fprintf(f," VAMP_REGEN"); if (m->effflags & MEF_PHASING) fprintf(f," PHASING"); if (m->effflags & MEF_BREATHE_WATER) fprintf(f," BREATHE_WATER"); if (m->effflags & MEF_RESIST_FIRE) fprintf(f," RESIST_FIRE"); if (m->effflags & MEF_RESIST_COLD) fprintf(f," RESIST_COLD"); if (m->effflags & MEF_PERCEPTION) fprintf(f," PERCEPTION"); if (m->effflags & MEF_TELEPORT_CTL) fprintf(f," TELEPORT_CTL"); if (m->effflags & MEF_MEMORY) fprintf(f," MEMORY"); if (m->effflags & MEF_SPEED) fprintf(f," SPEED"); if (m->effflags & MEF_REGENERATION) fprintf(f," REGENERATION"); if (m->effflags & MEF_CONFUSION) fprintf(f," CONFUSION"); if (m->effflags & MEF_PLANE_E) fprintf(f," E"); if (m->effflags & MEF_PLANE_A) fprintf(f," A"); if (m->effflags & MEF_PLANE_F) fprintf(f," F"); if (m->effflags & MEF_PLANE_W) fprintf(f," W"); if (m->effflags & MEF_INVULNERABLE) fprintf(f," INVULNERABLE"); break; default: if (! io) return(DL_L_DONE); format_inv_line(io,f); io = io->link; break; } return(DL_L_MORE); } if (! m) return; lno = 1; io = m->invent.inv; prompt = ""; display_list(&line,prompt,&dl_justmore); } static const char *trapstring(unsigned char trap) { static char badbuf[64]; switch (trap & TRAP_KIND) { case TRAP_KIND_NONE: return("NONE"); break; case TRAP_KIND_DUMMY: return((trap&TRAP_KNOWN)?"DUMMY|KNOWN":"DUMMY"); break; } sprintf(&badbuf[0],"?%d%s",(int)(trap&TRAP_KIND),(trap&TRAP_KNOWN)?"|KNOWN":""); return(&badbuf[0]); } /* * This is an interactive look-around-the-dungeon debugging interface. * It lets the player inspect the entire dungeon, with time frozen. * * The interface can be seen in the code below following the tget(). */ void probe(void) { LOC *lc; LOC *trk; LOC *lc2; LEVEL *lastlevel; int ch; int x; int y; static int tracktype = TRACK_SMART; lastlevel = 0; lc = you->loc; while (1) { if (lc->on != lastlevel) { clear(); lvdisp(lc->on); mvprintw(0,MXO,"%s",lc->on->longname); mvprintw(1,MXO,"%c %c %c %c", levels[elem_order[0]].shortname[0], levels[elem_order[1]].shortname[0], levels[elem_order[2]].shortname[0], levels[elem_order[3]].shortname[0]); lastlevel = lc->on; } mvprintw(LINES-2,0,"x %d y %d %s track ",lc->x,lc->y,trackname(tracktype)); trk = follow_track(lc,tracktype); if (! trk) { printw("null"); } else { if (lc->on == trk->on) { printw("(%d,%d)",trk->x,trk->y); } else { printw("(%d,%d) on %s",trk->x,trk->y,trk->on->shortname); } } if (tracktype >= 0) printw(" tracktime %g",lc->tracktime[tracktype]/(double)TIMESCALE); clrtoeol(); mvprintw(LINES-1,0,"type %s subtype %s trap %s flags %s", typename(lc),subtype(lc),trapstring(lc->trap),flags(lc)); if (lc->objs.inv) printw(" (objs)"); clrtoeol(); move(lc->y,lc->x); ch = tget(); if (dirkey(ch,&x,&y,0,0,0)) { lc = movecell(lc,x,y,1); } else { switch (ch) { case 'i': show_inventory(&lc->objs,"",&invtest_all,"Nothing there"); lastlevel = 0; break; case 'f': if (lc->to) { lc = lc->to; } else if (lc->type == LOC_GATE) { lc = gate_other_end(lc,GH_DEBUG); } break; case 'm': dump_monster(lc->monst); lvdisp(lc->on); break; case 't': if (trk) lc = trk; break; case 'T': move(LINES-2,0); clrtoeol(); mvprintw(LINES-1,0,"Track xit, umb, or mart? "); clrtoeol(); while (1) { switch (tget()) { case 'e': case 'E': tracktype = TRACK_EXIT; break; case 'd': case 'D': tracktype = TRACK_DUMB; break; case 's': case 'S': tracktype = TRACK_SMART; break; case '\33': break; default: continue; break; } break; } break; case 'W': if (occupiable(lc)) { clear(); teleport_you(lc); return; } break; case '<': if ((lc->on->index > 0) && (lc->on->index < DUNGEON_LEVELS)) { lc = &levels[lc->on->index-1].cells[lc->x][lc->y]; } else { LEVEL *lv; int x; int y; lv = lc->on; for (x=0;xcells[x][y]; if (lc2->type == LOC_GATE) { switch (lc2->gatetype) { case LOC_GATE_UWORLD_DUNGEON: case LOC_GATE_HELL: case LOC_GATE_ELEM_DUNGEON: lc = gate_other_end(lc2,GH_DEBUG); x = LEV_X; /* aka "break 2" */ y = LEV_Y; break; default: break; } } } } } break; case '>': if ((lc->on->index >= 0) && (lc->on->index < DUNGEON_LEVELS-1)) { lc = &levels[lc->on->index+1].cells[lc->x][lc->y]; } else { LEVEL *lv; int x; int y; lv = lc->on; for (x=0;xcells[x][y]; if (lc2->type == LOC_GATE) { switch (lc2->gatetype) { case LOC_GATE_UWORLD_HELL: case LOC_GATE_ELEM_UWORLD: lc = gate_other_end(lc2,GH_DEBUG); x = LEV_X; /* aka "break 2" */ y = LEV_Y; break; default: break; } } } } } break; case '\33': clear(); return; break; } } } } /* * Compute and display the topology of the dungeon. * * This works on just the standard dungeon levels. It effectively * collapses all connected cells into a single point, with up and down * stairs being links between points. It then displays one line per * level, giving, for the points on that level, the number of cells in * that point and the links up (^) and down (v), with destinations * given in terms of per-level serial numbers for points. For * example, * * 12 1 0[312]:^0/0/0/0/1/0/0v1/0/0 * 13 2 1[21]:^0v0 0[326]:^0/0v0/0 * 14 1 0[346]:^1/0/0v0 * 15 1 0[296]:^0v0 * * says that level 13 (the 12 line - note the numbers are 0-origin, * whereas conventional level numbers are 1-origin) consists of a * single 312-LOC blob, level 14 consists of two blobs, one (#0) of * 326 LOCs and the other (#1) of 21 LOCs, level 15 a single 346-LOC * blob, and level 16 a single 296-LOC blob. Level 13 has seven * stairs up, six to level 12's blob 0 and 1 to blob 1; it has three * stairs down, two to level 14's blob 0 and one to its blob 1. On * level 14, blob 0 has two stairs up and two down; each of the four * leads to blob on the relevant level. Blob 1 has one stair each up * and down, leading to blob 0. (In this case, since levels 13 and 15 * consist of only a single blob, all stairs to either must lead to * blob 0.) Level 15 has three stairs up, two to blob 0 and one to * blob 1; it has only one stair down, leading to level 16 blob 0. We * can perhaps draw this fragment as * * Lv.13 Lv.14 Lv.15 Lv.16 * * 312 326 346 296 * <---+---------------+---------------+---------------+---> * <---+---------------+---------------+ * <---+ | * <---+ 21 | * <---+---------------+---------------+ * <---+ * | * <---+ * * As another example, in * * 13 1 0[294]:^0v3/0 * 14 4 3[108]:^0v1 2[14]:v1 1[14]:v1 0[130]:^0v1 * 15 3 2[11]: 1[315]:^3/2/1/0v2/1/0 0[7]:v0/0 * 16 3 2[42]:^1 1[33]:^1 0[225]:^1/0/0v0 * 17 2 1[125]:v0 0[220]:^0v0 * 18 1 0[325]:^1/0v0/0/0 * * we have a more complex sturcture. Level 14 is a single blob; it * has only one stair up and two down. The stairs down lead to level * 15 blobs 0 and 3. Level 15 has four blobs; blobs 0 and 3 are very * roughly equal in size (130 and 108 LOCs) and each one has a link up * to level 14 blob 0 and a link down to level 16 blob 1. It also has * two small (14-LOC) blobs, each of which has a link down only, to * level 16 blob 1. Level 16 has three blobs. Blob 2 has 11 LOCs and * has no stairs at all (it's a vault). Blob 1 has 315 LOCs and links * to everything above and below, one stairwell each. Blob 0 is small * (only 7 LOCs) and has two stairs, both down to blob 0 on level 17. * Level 17 has three blobs, with the bulk of the level (225 LOCs) in * blob 0, linked to blob 1 (one stair) and blob 0 (two stairs) above * and blob 0 (one stair) below; blobs 1 and 2 are small and have just * one stair, to blob 1 above. Etc. Drawing this (more compactly), * * Lv.14 Lv.15 Lv.16 Lv.17 Lv.18 Lv.19 * * * <---+-------+ 7-------+------220------+---> * | 130 +------225 325--> * | | | | * | +-------+-------+ 125------+---> * | | * 294 14------+-------33 * | 315 * | 14------+-------42 * | | * +------108------+ * * 11 * * (In these examples, the graph turns out to be not only planar but * easy to draw. Consider the last example and imagine if level 16's * blob 2 had stairs up to level 15 blob 3 and down to level 17 blob * 0; it would have been impossible to draw without rerouting lines or * reordering blobs, and slightly more complex cases can actually lead * to nonplanar graphs; consider * * 10 3 2[50]^0v2/1/0 1[50]^0v2/1/0 0[50]^0v2/1/0 * 11 3 2[50]^2/1/0v0 1[50]^2/1/0v0 0[50]^2/1/0v0 * * which is possible, if unlikely, and contains K(3,3), one of the two * basic nonplanar graphs, in the resulting graph.) * * The code that's #if 0 here appears to be some sort of attempt to * mechanically generate boxology, conceptually like the above * examples. Presumably it doesn't work. */ void structure(void) { __label__ throw_out; typedef struct part PART; typedef struct link LINK; struct part { PART *link; PART *levlink; LEVEL *lev; unsigned char id; unsigned int size; int xo; LINK *up; LINK *dn; } ; struct link { LINK *link; LINK *uplink; LINK *dnlink; PART *upp; LOC *uploc; PART *dnp; LOC *dnloc; } ; int x; int y; int z; LEVEL *lev; LOC *lc; unsigned char id[DUNGEON_LEVELS][LEV_X][LEV_Y]; unsigned char nid[DUNGEON_LEVELS]; unsigned char curid; PART *lparts[DUNGEON_LEVELS]; TRAIL *q; TRAIL **qt; TRAIL *t; PART *parts; PART *p; LINK *links; LINK *ln; PART *part0; #if 0 int z0; int z1; int i; int j; #endif int rock(LOC *l) { switch (l->type) { case LOC_ROCK: case LOC_WALL: return(1); break; default: return(0); break; } } void maybe_q_loc(LOC *l) { TRAIL *n; if ((l->flags & LF_FLAG) || rock(l)) return; n = newtrail(); *qt = n; n->link = 0; qt = &n->link; n->loc = l; l->flags |= LF_FLAG; id[z][l->x][l->y] = curid; p->size ++; } PART *part_for_loc(LOC *l) { PART *p; int i; if (! (l->flags & LF_FLAG)) panic("impossible stairs"); i = id[l->on->index][l->x][l->y]; for (p=parts;p;p=p->link) { if ((p->lev == l->on) && (p->id == i)) return(p); } panic("nonexistent part"); } void get(void) { if (tget() == 3) goto throw_out; } q = 0; qt = &q; parts = 0; links = 0; for (z=DUNGEON_LEVELS-1;z>=0;z--) { lev = &levels[z]; lparts[z] = 0; for (x=LEV_X-1;x>=0;x--) { for (y=LEV_Y-1;y>=0;y--) { lev->cells[x][y].flags &= ~LF_FLAG; } } nid[z] = 0; for (x=LEV_X-1;x>=0;x--) { for (y=LEV_Y-1;y>=0;y--) { lc = &lev->cells[x][y]; if (lc->flags & LF_FLAG) continue; if (rock(lc)) continue; curid = nid[z]++; p = malloc(sizeof(PART)); p->link = parts; parts = p; p->levlink = lparts[z]; lparts[z] = p; p->lev = lc->on; p->id = curid; p->size = 0; p->up = 0; p->dn = 0; maybe_q_loc(lc); while (q) { t = q; q = q->link; if (! q) qt = &q; lc = t->loc; freetrail(t); all_neighbours(lc,&maybe_q_loc); } } } } part0 = 0; for (z=DUNGEON_LEVELS-1;z>=0;z--) { lev = &levels[z]; for (x=LEV_X-1;x>=0;x--) { for (y=LEV_Y-1;y>=0;y--) { lc = &lev->cells[x][y]; if ((lc->type == LOC_CAVE) && (lc->cavetype == LOC_STAIRS_U)) { ln = malloc(sizeof(LINK)); ln->link = links; links = ln; ln->dnloc = lc; ln->uploc = lc->to; ln->dnp = part_for_loc(lc); if (lc->to) { ln->upp = part_for_loc(lc->to); } else { if (part0) panic("multiple part0s"); part0 = malloc(sizeof(PART)); part0->link = parts; parts = part0; part0->lev = 0; part0->id = 0; part0->size = 0; part0->up = 0; part0->dn = 0; ln->upp = part0; } ln->uplink = ln->dnp->up; ln->dnp->up = ln; ln->dnlink = ln->upp->dn; ln->upp->dn = ln; } } } } if (! part0) panic("no part0"); clear(); for (z=DUNGEON_LEVELS-1;z>=0;z--) { const char *pref; lev = &levels[z]; move(z,0); clrtoeol(); printw(" %d %d",z,nid[z]); for (p=lparts[z];p;p=p->levlink) { printw(" %d[%d]:",p->id,p->size); pref = "^"; for (ln=p->up;ln;ln=ln->uplink) { printw("%s%d",pref,ln->upp->id); pref = "/"; } pref = "v"; for (ln=p->dn;ln;ln=ln->dnlink) { printw("%s%d",pref,ln->dnp->id); pref = "/"; } } } move(LINES-1,0); get(); #if 0 for (z1=DUNGEON_LEVELS-1;z1>=0;) { if (nid[z1] == 1) { z1 --; continue; } i = 0; for (z0=z1;(z0>=0)&&(nid[z0]>1);z0--) i += nid[z0]; z0 ++; if (z1 > z0) { __label__ abort_enumeration; int n[z1+1-z0]; int bestordervec[i]; int bestcrossings; int ordervec[i]; int *orders[z1+1-z0]; PART *partvec0[i]; PART *partvec[i]; int px0[z1+1-z0]; int crossings(void) { int crosscount; int z; int i; PART **pv; PART *p; LINK *l; int i2; PART *p2; LINK *l2; int *o; crosscount = 0; for (z=z0;z0;i--) { p = pv[i]; for (l=p->dn;l;l=l->dnlink) { for (i2=i-1;i2>=0;i2--) { p2 = pv[i2]; for (l2=p2->dn;l2;l2=l2->dnlink) { if ( ( (p2->xo - p->xo) * (l2->dnp->xo - l->dnp->xo) ) < 0) crosscount ++; } } } } } return(crosscount); } void loadpartvec(int z) { int i; int x; x = px0[z]; for (i=n[z]-1;i>=0;i--) { (partvec[x+i]=partvec0[x+orders[z][i]])->xo = i; } } void enumerated(void) { int z; int i; for (z=z0;z<=z1;z++) loadpartvec(z-z0); i = crossings(); if ((bestcrossings < 0) || (i < bestcrossings)) { bestcrossings = i; bcopy(&ordervec,&bestordervec,sizeof(ordervec)); if (i == 0) goto abort_enumeration; } } void enumerate(int m, int *v, int next) { char taken[m]; void try(int x) { int i; if (x < 0) { if (next < 0) { enumerated(); } else { enumerate(n[next],orders[next],next-1); } return; } for (i=m-1;i>=0;i--) { if (! taken[i]) { taken[i] = 1; v[x] = i; try(x-1); taken[i] = 0; } } } bzero(&taken[0],m); try(m-1); } j = 0; for (z=z0;z<=z1;z++) { n[z-z0] = nid[z]; orders[z-z0] = &ordervec[j]; px0[z-z0] = j; for (p=lparts[z],i=0;p;p=p->levlink,i++) partvec0[j+i] = p; if (i != nid[z]) panic("count wrong"); j += i; } for (z=DUNGEON_LEVELS-1;z>=0;z--) { mvaddch(z,0,((z>=z0)&&(z<=z1))?'*':' '); } move(LINES-1,0); clrtoeol(); refresh(); get(); bestcrossings = -1; enumerate(n[z1-z0],orders[z1-z0],z1-z0-1); abort_enumeration:; j = 0; for (z=z0;z<=z1;z++) { orders[z-z0] = &bestordervec[j]; j += nid[z]; } for (z=z0;z<=z1;z++) loadpartvec(z-z0); for (z=z0;z<=z1;z++) { move(z,0); clrtoeol(); printw(" %d %d",z,nid[z]); for (i=0;iid,p->size); pref = "^"; for (ln=p->up;ln;ln=ln->uplink) { printw("%s%d",pref,ln->upp->id); pref = "/"; } pref = "v"; for (ln=p->dn;ln;ln=ln->dnlink) { printw("%s%d",pref,ln->dnp->id); pref = "/"; } } } move(LINES-1,0); clrtoeol(); for (z=z0;z<=z1;z++) { printw(" %d:",z); for (x=0;x %d",bestcrossings); clrtoeol(); get(); } z1 = z0 - 1; } #endif throw_out:; clear(); } /* * Set up tracing in response to -trace. Arg is -trace's arg. */ void trace_open(const char *arg) { int fd; if (trcf) { fclose(trcf); trcf = 0; } fd = open(arg,O_WRONLY|O_CREAT|O_TRUNC|O_APPEND,0666); if (fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,arg,strerror(errno)); exit(1); } trcf = fdopen(fd,"a"); if (! trcf) { fprintf(stderr,"%s: fdopen %s: %s\n",__progname,arg,strerror(errno)); exit(1); } setlinebuf(trcf); } /* * Initialize an ESPC, given the maximum size it should have. */ static void espc_init(ESPC *e, int max) { e->b = malloc(max); e->a = max; e->l = 0; } /* * Return true if the ESPC is full, false if it can accept at least one * more byte of data. */ static int espc_full(ESPC *e) { return(e->l>=e->a); } /* * Append a single byte to an ESPC. The caller must have ensured there * is space available. */ static void espc_append_1(ESPC *e, unsigned char c) { if (espc_full(e)) panic("invalid espc_append_1"); e->b[e->l++] = c; } /* * Append (potentially) multiple bytes to an ESPC. The caller must * have ensured there is at least one byte of space available. The * number of bytes appended is returned. */ static int espc_append_n(ESPC *e, const void *data, int len) { int n; n = e->a - e->l; if (n < 1) panic("invalid espc_append_n"); if (n > len) n = len; bcopy(data,e->b+e->l,n); e->l += n; return(n); } /* * Switch if necessary, preparatory to generating a specific type of * tracing. */ static void trace_switch(TRCTYPE tt) { TRCREC *r; if ((tt != TT_TEXT) && tracetail && (tracetail->type == TT_TEXT)) { if (! text_atnl) { trace_switch(TT_TEXT); espc_append_1(&tracetail->u.text,'\n'); } } switch (tt) { case TT_SEED: break; case TT_GETCH_CHAR: if (tracetail && (tracetail->type == TT_GETCH_CHAR) && !espc_full(&tracetail->u.getch)) return; break; case TT_GETCH_INT: break; case TT_TEXT: if (tracetail && (tracetail->type == TT_TEXT)) { if (! espc_full(&tracetail->u.text)) return; } else { text_atnl = 1; } break; default: panic("impossible type %d to trace_switch\n",(int)tt); break; } r = malloc(sizeof(TRCREC)); r->link = 0; r->type = tt; switch (tt) { case TT_SEED: break; case TT_GETCH_CHAR: espc_init(&r->u.getch,65500); break; case TT_GETCH_INT: break; case TT_TEXT: espc_init(&r->u.text,65500); break; default: panic("impossible type %d within trace_switch\n",(int)tt); break; } if (tracetail) tracetail->link = r; else tracehead = r; tracetail = r; } /* * Generate a trace record for the randomness seed. */ void trace_seed(unsigned long long int s) { if (trcf) { print_trace_seed(trcf,s); fflush(trcf); } trace_switch(TT_SEED); tracetail->u.seed = s; } /* * Generate a trace record for a case of getch() returning a value * within [0..255]. */ static void trace_getch_char(unsigned char c) { if (trcf) { print_trace_getch_1(trcf,c); fflush(trcf); } trace_switch(TT_GETCH_CHAR); espc_append_1(&tracetail->u.getch,c); } /* * Generate a trace record for a case of getch() returning something * outside [0..255]. */ static void trace_getch_int(int c) { if (trcf) { print_trace_getch_int(trcf,c); fflush(trcf); } trace_switch(TT_GETCH_INT); tracetail->u.ch_int = c; } /* * Generate trace otuput. */ void trace_text(const char *fmt, ...) { va_list ap; char *s; int l; int n; char *sp; block_int(); va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); if (l < 1) { free(s); return; } if (trcf) { print_trace_text(trcf,s,l); if (memchr(s,'\n',l)) fflush(trcf); } sp = s; while (l > 0) { trace_switch(TT_TEXT); n = espc_append_n(&tracetail->u.text,sp,l); sp += n; l -= n; } unblock_int(); } /* * Read the next line out of the replay file. This is called only when * replaying is in progress, so there _is_ a replay file. The * returned string is owned by rep_line and may go invalid as it is * called again. */ static const char *rep_line(void) { static char *b = 0; static int a = 0; int l; int c; void app(int ch) { if (l >= a) b = realloc(b,a=l+8); b[l++] = ch; } l = 0; while (1) { c = getc(repf); if (c == EOF) return(0); if (c == '\n') { app(0); return(b); } app(c); } } /* * Set up to replay a trace file. */ void trace_replay(const char *arg) { int fd; const char *l; unsigned long long int s; int n; if (repf) { fclose(repf); repf = 0; } fd = open(arg,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,arg,strerror(errno)); exit(1); } repf = fdopen(fd,"r"); if (! repf) { fprintf(stderr,"%s: fdopen %s: %s\n",__progname,arg,strerror(errno)); exit(1); } // First line must be a random seed line l = rep_line(); if (! l) { fprintf(stderr,"%s: trace file %s has no first line\n",__progname,arg); exit(1); } n = -1; sscanf(l,"dice seed is %llu%n",&s,&n); if (n < 0) { fprintf(stderr,"%s: trace file %s first line is malformed\n",__progname,arg); exit(1); } set_dice_seed(s); repstep = 0; } /* * Like getch(), but logs input characters to the trace. RWSIZE is the * size in characters of the tiny replay-interaction window. */ #define RWSIZE 15 int tget(void) { int c; int dx; int dy; int dirflag; refresh(); do <"get"> { while <"interacting"> (repf) { const char *l; static WINDOW *repwin = 0; char repin[20]; static int gwx = 0; static int gwy = 0; int replen; int rv; int n; if (! repwin) { repwin = newwin(1,RWSIZE,0,0); leaveok(repwin,FALSE); } do l = rep_line(); while (l && (l[0] == '#')); if (l) { n = -1; sscanf(l,"getch: 0x%x (%n",&rv,&n); if (n < 0) { sscanf(l,"getch %d%n",&rv,&n); if (n < 0) { pline("Malformatted replay line: %s",l); continue; } } if (! repstep) { static char *cs = 0; static int csl; FILE *f; free(cs); f = fopenmalloc(&cs,&csl); if ((rv >= 0) && (rv <= 255)) { print_1c(f,rv); } else { fprintf(f,"%d",rv); } fclose(f); replen = 0; while <"step"> (1) { wmove(repwin,0,0); if (replen >= RWSIZE-1) { waddnstr(repwin,&repin[replen-(RWSIZE-1)],RWSIZE-1); } else { if (3+replen >= RWSIZE-1) { waddstr(repwin,")> "+(3+replen-(RWSIZE-1))); } else { if (csl+3+replen >= RWSIZE-1) { waddstr(repwin,cs+(csl+3+replen-(RWSIZE-1))); } else { if (6+csl+3+replen >= RWSIZE-1) { waddstr(repwin,"Step ("+(6+csl+3+replen-(RWSIZE-1))); } else { waddstr(repwin,"Step ("); } waddnstr(repwin,cs,csl); } waddstr(repwin,")> "); } waddnstr(repwin,&repin[0],replen); } wclrtoeol(repwin); touchwin(repwin); wrefresh(repwin); c = getch(); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (replen >= sizeof(repin)-1) { beep(); } else { repin[replen++] = c; } break; case 's': if (replen > 0) { repin[replen] = '\0'; repstep = atoi(&repin[0]); if (repstep <= 0) { replen = 0; continue <"step">; } } else { repstep = 1; } break <"step">; case 'g': if (replen > 0) { beep(); } else { repstep = -1; break <"step">; } break; case 'A': wmove(repwin,0,0); waddstr(repwin,"Aborted"); wclrtoeol(repwin); wrefresh(repwin); getch(); touchoverlap(repwin,stdscr); fclose(repf); repf = 0; break <"interacting">; case 'Q': cleanupscreen(); exit(0); break; case '\b': case '\177': if (replen > 0) replen --; break; default: if (dirkey(c,&dx,&dy,0,&dirflag,DK_NOCTL)) { gwx += dx * ((dirflag & DK_CAPS) ? COLS : 1); gwy += dy * ((dirflag & DK_CAPS) ? LINES : 1); if (gwx > COLS-RWSIZE) gwx = COLS - RWSIZE; if (gwx < 0) gwx = 0; if (gwy > LINES-1) gwy = LINES - 1; if (gwy < 0) gwy = 0; touchoverlap(repwin,stdscr); mvwin(repwin,gwy,gwx); } else { beep(); } break; } } touchoverlap(repwin,stdscr); refresh(); } repstep --; c = rv; break <"get">; } else { wmove(repwin,0,0); waddstr(repwin,"Done!"); wclrtoeol(repwin); wrefresh(repwin); getch(); touchoverlap(repwin,stdscr); fclose(repf); repf = 0; } } c = getch(); } while (0); if ((c < 0) || (c > 255)) { trace_getch_int(c); } else { trace_getch_char(c); } return(c); }