diff --git a/README.stateless b/README.stateless new file mode 100644 index 0000000..642d381 --- /dev/null +++ b/README.stateless @@ -0,0 +1,26 @@ +This is version 1.1 of the stateless patch. + +This patch aims at making the bot maintainer's life easier by making +games a bit more reproducible. Just define the NETHACKSTATE +environment variable with the RNG seed you want used instead of time() +prior to launching the game. + +For example, the following command line always yields a lawful male +human knight, with a pet pony and a fountain in the starting room. +Going straight to the fountain to quaff gives the see invisible +intrinsic and dries out the fountain. + + NETHACKSTATE=2 nethack + +Obviously, if you use a different RNG, you'll get a different result +on this one, but at least it'll be stable. + + +LIMITATIONS + - no bones for you + - time-of-day-related behavior (undead, gremlins) is static per game + +BUGS + - state isn't preserved across savefiles + - moreover, attempting to restore a stateless game would mess up the + log file--just don't diff --git a/include/config.h b/include/config.h index 3efbfa2..4d724f6 100644 --- a/include/config.h +++ b/include/config.h @@ -351,6 +351,8 @@ typedef unsigned char uchar; /*#define GOLDOBJ */ /* Gold is kept on obj chains - Helge Hafting */ /*#define AUTOPICKUP_EXCEPTIONS */ /* exceptions to autopickup */ +#define STATELESS /* reproducible games */ + /* End of Section 5 */ #include "global.h" /* Define everything else according to choices above */ diff --git a/include/you.h b/include/you.h index 2ca496d..a1eb8ad 100644 --- a/include/you.h +++ b/include/you.h @@ -365,4 +365,10 @@ struct you { #define Upolyd (u.umonnum != u.umonster) +#ifdef STATELESS +extern time_t realbirthday; +#else +#define realbirthday u.ubirthday +#endif + #endif /* YOU_H */ diff --git a/src/bones.c b/src/bones.c index 585200a..b725722 100644 --- a/src/bones.c +++ b/src/bones.c @@ -163,6 +163,10 @@ can_make_bones() { register struct trap *ttmp; +#ifdef STATELESS + if(getenv("NETHACKSTATE")) + return FALSE; +#endif if (ledger_no(&u.uz) <= 0 || ledger_no(&u.uz) > maxledgerno()) return FALSE; if (no_bones_level(&u.uz)) @@ -381,6 +385,11 @@ getbones() if(discover) /* save bones files for real games */ return(0); +#ifdef STATELESS + if (getenv("NETHACKSTATE")) + return 0; +#endif + /* wizard check added by GAN 02/05/87 */ if(rn2(3) /* only once in three times do we find bones */ #ifdef WIZARD diff --git a/src/hacklib.c b/src/hacklib.c index 0d08270..302b139 100644 --- a/src/hacklib.c +++ b/src/hacklib.c @@ -460,6 +460,12 @@ static struct tm *NDECL(getlt); void setrandom() { +#ifdef STATELESS + char *seedopt = getenv("NETHACKSTATE"); + if (seedopt) srandom(atol(seedopt)); + else { +#endif + /* the types are different enough here that sweeping the different * routine names into one via #defines is even more confusing */ @@ -483,6 +489,10 @@ setrandom() # endif # endif #endif + +#ifdef STATELESS + } +#endif } static struct tm * @@ -580,6 +590,10 @@ phase_of_the_moon() /* 0-7, with 0: new, 4: full */ register struct tm *lt = getlt(); register int epact, diy, goldn; +#ifdef STATELESS + if (getenv("NETHACKSTATE")) return u.ubirthday % 8; +#endif + diy = lt->tm_yday; goldn = (lt->tm_year % 19) + 1; epact = (11 * goldn + 18) % 30; @@ -594,6 +608,10 @@ friday_13th() { register struct tm *lt = getlt(); +#ifdef STATELESS + if (getenv("NETHACKSTATE")) return (u.ubirthday % 210) == 0; +#endif + return((boolean)(lt->tm_wday == 5 /* friday */ && lt->tm_mday == 13)); } @@ -602,12 +620,19 @@ night() { register int hour = getlt()->tm_hour; +#ifdef STATELESS + if (getenv("NETHACKSTATE")) hour = u.ubirthday % 24; +#endif + return(hour < 6 || hour > 21); } int midnight() { +#ifdef STATELESS + if (getenv("NETHACKSTATE")) return (u.ubirthday % 24) == 0; +#endif return(getlt()->tm_hour == 0); } #endif /* OVL2 */ diff --git a/src/restore.c b/src/restore.c index aaabbed..cbc1758 100644 --- a/src/restore.c +++ b/src/restore.c @@ -383,6 +383,10 @@ unsigned int *stuckid, *steedid; /* STEED */ amii_setpens(amii_numcolors); /* use colors from save file */ #endif mread(fd, (genericptr_t) &u, sizeof(struct you)); +#ifdef STATELESS + realbirthday = u.ubirthday; /* WILL mess up record file on restored + * "stateless" games. So don't. */ +#endif set_uasmon(); #ifdef CLIPPING cliparound(u.ux, u.uy); diff --git a/src/save.c b/src/save.c index 9291eb9..aceb884 100644 --- a/src/save.c +++ b/src/save.c @@ -55,6 +55,10 @@ int dosave() { clear_nhwindow(WIN_MESSAGE); +#ifdef STATELESS + if (getenv("NETHACKSTATE")) + pline("Warning: restoring stateless games kills kittens!"); +#endif if(yn("Really save?") == 'n') { clear_nhwindow(WIN_MESSAGE); if(multi > 0) nomul(0); diff --git a/src/topten.c b/src/topten.c index 19a0488..42c3378 100644 --- a/src/topten.c +++ b/src/topten.c @@ -331,7 +331,7 @@ int how; (void) strncat(t0->death, killer, DTHSZ); break; } - t0->birthdate = yyyymmdd(u.ubirthday); + t0->birthdate = yyyymmdd(realbirthday); t0->deathdate = yyyymmdd((time_t)0L); t0->tt_next = 0; #ifdef UPDATE_RECORD_IN_PLACE diff --git a/src/u_init.c b/src/u_init.c index 77c92b0..dfc2077 100644 --- a/src/u_init.c +++ b/src/u_init.c @@ -491,6 +491,9 @@ static const struct def_skill Skill_W[] = { { P_NONE, 0 } }; +#ifdef STATELESS +time_t realbirthday; +#endif STATIC_OVL void knows_object(obj) @@ -583,10 +586,34 @@ u_init() aligns[flags.initalign].value; u.ulycn = NON_PM; + /* ubirthday had two uses: + * - a random value constant over a game, used for "static + * randomness" like anthole type selection, T-shirt writing + * erosion, gem pricing and shopkeeper naming. + * - actual game timestamping (record file) + * + * Since the STATELESS patch doesn't support savefiles yet, + * save actual game start in a global for the record file, and + * consume a Rand() for the static randomness. + * + * If STATELESS is undef'd, both identifiers should resolve to + * the same. (you.h) + */ #if defined(BSD) && !defined(POSIX_TYPES) - (void) time((long *)&u.ubirthday); + (void) time((long *)&realbirthday); #else - (void) time(&u.ubirthday); + (void) time(&realbirthday); +#endif +#ifdef STATELESS + { + char *state = getenv("NETHACKSTATE"); + if (state) { + state = strchr(state, '/'); + if (state++) u.ubirthday = atol(state); + else u.ubirthday = Rand(); + } + else u.ubirthday = realbirthday; + } #endif /*