From: Eric S. Raymond Date: Sat, 10 Jun 2017 09:38:47 +0000 (-0400) Subject: Isolate the grotty save/resume code. Most of it will soon go away. X-Git-Tag: 1.1~457 X-Git-Url: https://jxself.org/git/?a=commitdiff_plain;h=3af993abcacd18f114e8dd54f9d3be6a3a62ea2e;p=open-adventure.git Isolate the grotty save/resume code. Most of it will soon go away. --- diff --git a/Makefile b/Makefile index a0b1c70..63aa7f4 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ ifeq ($(UNAME_S),Linux) LIBS=-lrt endif -OBJS=main.o init.o actions.o score.o misc.o +OBJS=main.o init.o actions.o score.o misc.o saveresume.o SOURCES=$(OBJS:.o=.c) dungeon.c advent.h sizes.h adventure.text Makefile control linenoise/linenoise.[ch] .c.o: @@ -38,7 +38,9 @@ actions.o: advent.h database.h database.c sizes.h score.o: advent.h database.h database.c sizes.h -misc.o: advent.h database.h database.c sizes.h +misc.o: advent.h database.h database.c sizes.h + +saveresume.o: advent.h database.h database.c sizes.h database.o: database.c database.h sizes.h $(CC) $(CCFLAGS) $(DBX) -c database.c diff --git a/actions.c b/actions.c index 356b2b9..4a545c3 100644 --- a/actions.c +++ b/actions.c @@ -3,8 +3,6 @@ #include "advent.h" #include "database.h" -#define VRSION 25 /* bump on save format change */ - /* * Action handlers. Eventually we'll do lookup through a method table * that calls these. Absolutely nothing like the original FORTRAN. @@ -776,88 +774,6 @@ static int say(void) } -static int suspendresume(FILE *input, bool resume) -/* Suspend and resume */ -{ - int kk; - long i; - if (!resume) { - /* Suspend. Offer to save things in a file, but charging - * some points (so can't win by using saved games to retry - * battles or to start over after learning zzword). */ - SPK=201; - RSPEAK(260); - if (!YES(input,200,54,54)) return(2012); - game.saved=game.saved+5; - kk= -1; - } - else - { - /* Resume. Read a suspended game back from a file. */ - kk=1; - if (game.loc != 1 || game.abbrev[1] != 1) { - RSPEAK(268); - if (!YES(input,200,54,54)) return(2012); - } - } - - /* Suspend vs resume cases are distinguished by the value of kk - * (-1 for suspend, +1 for resume). */ - - /* - * FIXME: This is way more complicated than it needs to be in C. - * What we ought to do is define a save-block structure that - * includes a game state block and then use a single fread/fwrite - * for I/O. All the SAV* functions can be scrapped. - */ - - DATIME(&i,&K); - K=i+650*K; - SAVWRD(kk,K); - K=VRSION; - SAVWRD(0,K); - if (K != VRSION) { - SETPRM(1,K/10,MOD(K,10)); - SETPRM(3,VRSION/10,MOD(VRSION,10)); - RSPEAK(269); - return(2000); - } - /* Herewith are all the variables whose values can change during a game, - * omitting a few (such as I, J) whose values between turns are - * irrelevant and some whose values when a game is - * suspended or resumed are guaranteed to match. If unsure whether a value - * needs to be saved, include it. Overkill can't hurt. Pad the last savwds - * with junk variables to bring it up to 7 values. */ - SAVWDS(game.abbnum,game.blklin,game.bonus,game.clock1,game.clock2,game.closed,game.closng); - SAVWDS(game.detail,game.dflag,game.dkill,game.dtotal,game.foobar,game.holdng,game.iwest); - SAVWDS(game.knfloc,game.limit,K,game.lmwarn,game.loc,game.newloc,game.numdie); - SAVWDS(K,game.oldlc2,game.oldloc,game.oldobj,game.panic,game.saved,game.setup); - SAVWDS(SPK,game.tally,game.thresh,game.trndex,game.trnluz,game.turns,OBJTXT[OYSTER]); - SAVWDS(K,WD1,WD1X,WD2,game.wzdark,game.zzword,OBJSND[BIRD]); - SAVWDS(OBJTXT[SIGN],game.clshnt,game.novice,K,K,K,K); - SAVARR(game.abbrev,LOCSIZ); - SAVARR(game.atloc,LOCSIZ); - SAVARR(game.dloc,NDWARVES); - SAVARR(game.dseen,NDWARVES); - SAVARR(game.fixed,NOBJECTS); - SAVARR(game.hinted,HNTSIZ); - SAVARR(game.hintlc,HNTSIZ); - SAVARR(game.link,NOBJECTS*2); - SAVARR(game.odloc,NDWARVES); - SAVARR(game.place,NOBJECTS); - SAVARR(game.prop,NOBJECTS); - SAVWRD(kk,K); - if (K != 0) { - RSPEAK(270); - exit(0); - } - K=NUL; - game.zzword=RNDVOC(3,game.zzword); - if (kk > 0) return(8); - RSPEAK(266); - exit(0); -} - static int throw_support(long spk) { RSPEAK(spk); @@ -1021,8 +937,8 @@ int action(FILE *input, enum speechpart part, long verb, long obj) case 26: /* READ */ return read(input, INTRANSITIVE); case 27: /* BREAK */ return(8000); case 28: /* WAKE */ return(8000); - case 29: /* SUSP */ return suspendresume(input, false); - case 30: /* RESU */ return suspendresume(input, true); + case 29: /* SUSP */ return saveresume(input, false); + case 30: /* RESU */ return saveresume(input, true); case 31: /* FLY */ return fly(INTRANSITIVE); case 32: /* LISTE */ return listen(); case 33: /* ZZZZ */ return reservoir(); diff --git a/advent.h b/advent.h index 9e50d9c..df6efdb 100644 --- a/advent.h +++ b/advent.h @@ -98,14 +98,6 @@ extern token_t MAKEWD(long); extern void PUTTXT(token_t,long*,long); extern void SHFTXT(long,long); extern void TYPE0(void); - -extern void fSAVWDS(long*,long*,long*,long*,long*,long*,long*); -#define SAVWDS(W1,W2,W3,W4,W5,W6,W7) fSAVWDS(&W1,&W2,&W3,&W4,&W5,&W6,&W7) -extern void fSAVARR(long*,long); -#define SAVARR(ARR,N) fSAVARR(ARR,N) -extern void fSAVWRD(long,long*); -#define SAVWRD(OP,WORD) fSAVWRD(OP,&WORD) - extern long VOCAB(long,long); extern void DSTROY(long); extern void JUGGLE(long); @@ -120,17 +112,14 @@ extern long RNDVOC(long,long); extern void BUG(long) __attribute__((noreturn)); extern bool MAPLIN(FILE *); extern void TYPE(void); - -extern void fSAVEIO(long,long,long*); -#define SAVEIO(OP,IN,ARR) fSAVEIO(OP,IN,ARR) extern void DATIME(long*, long*); - extern long MOD(long,long); extern void set_seed(long); extern unsigned long get_next_lcg_value(void); extern long randrange(long); extern void score(long); +extern int saveresume(FILE *, bool); /* Statement functions * @@ -183,3 +172,9 @@ extern long AMBER, ATTACK, AXE, BACK, BATTER, BEAR, extern long K, SPK, WD1, WD1X, WD2, WD2X; enum speechpart {unknown, intransitive, transitive}; + +/* hack to ignore GCC Unused Result */ +#define IGNORE(r) do{if (r){}}while(0) + +/* end */ + diff --git a/misc.c b/misc.c index 08fdcfa..546cf48 100644 --- a/misc.c +++ b/misc.c @@ -8,9 +8,6 @@ #include "database.h" #include "linenoise/linenoise.h" -/* hack to ignore GCC Unused Result */ -#define IGNORE(r) do{if (r){}}while(0) - #define PERCENT 63 /* partly hide the packed encoding */ const char advent_to_ascii[] = {0, 32, 33, 34, 39, 40, 41, 42, 43, 44, 45, 46, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 37, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 35, 36, 38, 47, 58, 59, 60, 61, 62, 63, 64, 91, 92, 93, 94, 95, 96, 123, 124, 125, 126, 0}; @@ -382,91 +379,6 @@ void TYPE0(void) return; } -/* Suspend/resume I/O routines (SAVWDS, SAVARR, SAVWRD) */ - -void fSAVWDS(long *W1, long *W2, long *W3, long *W4, - long *W5, long *W6, long *W7) -/* Write or read 7 variables. See SAVWRD. */ -{ - SAVWRD(0,(*W1)); - SAVWRD(0,(*W2)); - SAVWRD(0,(*W3)); - SAVWRD(0,(*W4)); - SAVWRD(0,(*W5)); - SAVWRD(0,(*W6)); - SAVWRD(0,(*W7)); -} - -void fSAVARR(long arr[], long n) -/* Write or read an array of n words. See SAVWRD. */ -{ - long i; - - for (i=1; i<=n; i++) { - SAVWRD(0,arr[i]); - } - return; -} - -void fSAVWRD(long op, long *pword) -/* If OP<0, start writing a file, using word to initialise encryption; save - * word in the file. If OP>0, start reading a file; read the file to find - * the value with which to decrypt the rest. In either case, if a file is - * already open, finish writing/reading it and don't start a new one. If OP=0, - * read/write a single word. Words are buffered in case that makes for more - * efficient disk use. We also compute a simple checksum to catch elementary - * poking within the saved file. When we finish reading/writing the file, - * we store zero into *PWORD if there's no checksum error, else nonzero. */ -{ - static long buf[250], cksum = 0, h1, hash = 0, n = 0, state = 0; - - if (op != 0) - { - long ifvar = state; - switch (ifvar<0 ? -1 : (ifvar>0 ? 1 : 0)) - { - case -1: - case 1: - if (n == 250)SAVEIO(1,state > 0,buf); - n=MOD(n,250)+1; - if (state <= 0) { - n--; buf[n]=cksum; n++; - SAVEIO(1,false,buf); - } - n--; *pword=buf[n]-cksum; n++; - SAVEIO(-1,state > 0,buf); - state=0; - break; - case 0: /* FIXME: Huh? should be impossible */ - state=op; - SAVEIO(0,state > 0,buf); - n=1; - if (state <= 0) { - hash=MOD(*pword,1048576L); - buf[0]=1234L*5678L-hash; - } - SAVEIO(1,true,buf); - hash=MOD(1234L*5678L-buf[0],1048576L); - cksum=buf[0]; - return; - } - } - if (state == 0) - return; - if (n == 250) - SAVEIO(1,state > 0,buf); - n=MOD(n,250)+1; - h1=MOD(hash*1093L+221573L,1048576L); - hash=MOD(h1*1093L+221573L,1048576L); - h1=MOD(h1,1234)*765432+MOD(hash,123); - n--; - if (state > 0) - *pword=buf[n]+h1; - buf[n]=*pword-h1; - n++; - cksum=MOD(cksum*13+*pword,1000000000L); -} - /* Data structure routines */ long VOCAB(long id, long init) @@ -790,40 +702,6 @@ void TYPE(void) return; } -void fSAVEIO(long op, long in, long arr[]) -/* If OP=0, ask for a file name and open a file. (If IN=true, the file is for - * input, else output.) If OP>0, read/write ARR from/into the previously-opened - * file. (ARR is a 250-integer array.) If OP<0, finish reading/writing the - * file. (Finishing writing can be a no-op if a "stop" statement does it - * automatically. Finishing reading can be a no-op as long as a subsequent - * SAVEIO(0,false,X) will still work.) */ -{ - static FILE *fp = NULL; - char* name; - - switch (op < 0 ? -1 : (op > 0 ? 1 : 0)) - { - case -1: - fclose(fp); - break; - case 0: - while (fp == NULL) { - name = linenoise("File name: "); - fp = fopen(name,(in ? READ_MODE : WRITE_MODE)); - if (fp == NULL) - printf("Can't open file %s, try again.\n", name); - } - linenoiseFree(name); - break; - case 1: - if (in) - IGNORE(fread(arr,sizeof(long),250,fp)); - else - IGNORE(fwrite(arr,sizeof(long),250,fp)); - break; - } -} - void DATIME(long* d, long* t) { struct timeval tv; diff --git a/saveresume.c b/saveresume.c new file mode 100644 index 0000000..80375ca --- /dev/null +++ b/saveresume.c @@ -0,0 +1,217 @@ +#include + +#include "advent.h" +#include "database.h" +#include "linenoise/linenoise.h" + +#define VRSION 25 /* bump on save format change */ + +static void fSAVWDS(long*,long*,long*,long*,long*,long*,long*); +#define SAVWDS(W1,W2,W3,W4,W5,W6,W7) fSAVWDS(&W1,&W2,&W3,&W4,&W5,&W6,&W7) +static void fSAVARR(long*,long); +#define SAVARR(ARR,N) fSAVARR(ARR,N) +static void fSAVWRD(long,long*); +#define SAVWRD(OP,WORD) fSAVWRD(OP,&WORD) +static void fSAVEIO(long,long,long*); +#define SAVEIO(OP,IN,ARR) fSAVEIO(OP,IN,ARR) + +/* Suspend/resume I/O routines (SAVWDS, SAVARR, SAVWRD) */ + +static void fSAVWDS(long *W1, long *W2, long *W3, long *W4, + long *W5, long *W6, long *W7) +/* Write or read 7 variables. See SAVWRD. */ +{ + SAVWRD(0,(*W1)); + SAVWRD(0,(*W2)); + SAVWRD(0,(*W3)); + SAVWRD(0,(*W4)); + SAVWRD(0,(*W5)); + SAVWRD(0,(*W6)); + SAVWRD(0,(*W7)); +} + +static void fSAVARR(long arr[], long n) +/* Write or read an array of n words. See SAVWRD. */ +{ + long i; + + for (i=1; i<=n; i++) { + SAVWRD(0,arr[i]); + } + return; +} + +static void fSAVWRD(long op, long *pword) +/* If OP<0, start writing a file, using word to initialise encryption; save + * word in the file. If OP>0, start reading a file; read the file to find + * the value with which to decrypt the rest. In either case, if a file is + * already open, finish writing/reading it and don't start a new one. If OP=0, + * read/write a single word. Words are buffered in case that makes for more + * efficient disk use. We also compute a simple checksum to catch elementary + * poking within the saved file. When we finish reading/writing the file, + * we store zero into *PWORD if there's no checksum error, else nonzero. */ +{ + static long buf[250], cksum = 0, h1, hash = 0, n = 0, state = 0; + + if (op != 0) + { + long ifvar = state; + switch (ifvar<0 ? -1 : (ifvar>0 ? 1 : 0)) + { + case -1: + case 1: + if (n == 250)SAVEIO(1,state > 0,buf); + n=MOD(n,250)+1; + if (state <= 0) { + n--; buf[n]=cksum; n++; + SAVEIO(1,false,buf); + } + n--; *pword=buf[n]-cksum; n++; + SAVEIO(-1,state > 0,buf); + state=0; + break; + case 0: /* FIXME: Huh? should be impossible */ + state=op; + SAVEIO(0,state > 0,buf); + n=1; + if (state <= 0) { + hash=MOD(*pword,1048576L); + buf[0]=1234L*5678L-hash; + } + SAVEIO(1,true,buf); + hash=MOD(1234L*5678L-buf[0],1048576L); + cksum=buf[0]; + return; + } + } + if (state == 0) + return; + if (n == 250) + SAVEIO(1,state > 0,buf); + n=MOD(n,250)+1; + h1=MOD(hash*1093L+221573L,1048576L); + hash=MOD(h1*1093L+221573L,1048576L); + h1=MOD(h1,1234)*765432+MOD(hash,123); + n--; + if (state > 0) + *pword=buf[n]+h1; + buf[n]=*pword-h1; + n++; + cksum=MOD(cksum*13+*pword,1000000000L); +} + +static void fSAVEIO(long op, long in, long arr[]) +/* If OP=0, ask for a file name and open a file. (If IN=true, the file is for + * input, else output.) If OP>0, read/write ARR from/into the previously-opened + * file. (ARR is a 250-integer array.) If OP<0, finish reading/writing the + * file. (Finishing writing can be a no-op if a "stop" statement does it + * automatically. Finishing reading can be a no-op as long as a subsequent + * SAVEIO(0,false,X) will still work.) */ +{ + static FILE *fp = NULL; + char* name; + + switch (op < 0 ? -1 : (op > 0 ? 1 : 0)) + { + case -1: + fclose(fp); + break; + case 0: + while (fp == NULL) { + name = linenoise("File name: "); + fp = fopen(name,(in ? READ_MODE : WRITE_MODE)); + if (fp == NULL) + printf("Can't open file %s, try again.\n", name); + } + linenoiseFree(name); + break; + case 1: + if (in) + IGNORE(fread(arr,sizeof(long),250,fp)); + else + IGNORE(fwrite(arr,sizeof(long),250,fp)); + break; + } +} + +int saveresume(FILE *input, bool resume) +/* Suspend and resume */ +{ + int kk; + long i; + if (!resume) { + /* Suspend. Offer to save things in a file, but charging + * some points (so can't win by using saved games to retry + * battles or to start over after learning zzword). */ + SPK=201; + RSPEAK(260); + if (!YES(input,200,54,54)) return(2012); + game.saved=game.saved+5; + kk= -1; + } + else + { + /* Resume. Read a suspended game back from a file. */ + kk=1; + if (game.loc != 1 || game.abbrev[1] != 1) { + RSPEAK(268); + if (!YES(input,200,54,54)) return(2012); + } + } + + /* Suspend vs resume cases are distinguished by the value of kk + * (-1 for suspend, +1 for resume). */ + + /* + * FIXME: This is way more complicated than it needs to be in C. + * What we ought to do is define a save-block structure that + * includes a game state block and then use a single fread/fwrite + * for I/O. All the SAV* functions can be scrapped. + */ + + DATIME(&i,&K); + K=i+650*K; + SAVWRD(kk,K); + K=VRSION; + SAVWRD(0,K); + if (K != VRSION) { + SETPRM(1,K/10,MOD(K,10)); + SETPRM(3,VRSION/10,MOD(VRSION,10)); + RSPEAK(269); + return(2000); + } + /* Herewith are all the variables whose values can change during a game, + * omitting a few (such as I, J) whose values between turns are + * irrelevant and some whose values when a game is + * suspended or resumed are guaranteed to match. If unsure whether a value + * needs to be saved, include it. Overkill can't hurt. Pad the last savwds + * with junk variables to bring it up to 7 values. */ + SAVWDS(game.abbnum,game.blklin,game.bonus,game.clock1,game.clock2,game.closed,game.closng); + SAVWDS(game.detail,game.dflag,game.dkill,game.dtotal,game.foobar,game.holdng,game.iwest); + SAVWDS(game.knfloc,game.limit,K,game.lmwarn,game.loc,game.newloc,game.numdie); + SAVWDS(K,game.oldlc2,game.oldloc,game.oldobj,game.panic,game.saved,game.setup); + SAVWDS(SPK,game.tally,game.thresh,game.trndex,game.trnluz,game.turns,OBJTXT[OYSTER]); + SAVWDS(K,WD1,WD1X,WD2,game.wzdark,game.zzword,OBJSND[BIRD]); + SAVWDS(OBJTXT[SIGN],game.clshnt,game.novice,K,K,K,K); + SAVARR(game.abbrev,LOCSIZ); + SAVARR(game.atloc,LOCSIZ); + SAVARR(game.dloc,NDWARVES); + SAVARR(game.dseen,NDWARVES); + SAVARR(game.fixed,NOBJECTS); + SAVARR(game.hinted,HNTSIZ); + SAVARR(game.hintlc,HNTSIZ); + SAVARR(game.link,NOBJECTS*2); + SAVARR(game.odloc,NDWARVES); + SAVARR(game.place,NOBJECTS); + SAVARR(game.prop,NOBJECTS); + SAVWRD(kk,K); + if (K != 0) { + RSPEAK(270); + exit(0); + } + K=NUL; + game.zzword=RNDVOC(3,game.zzword); + if (kk > 0) return(8); + RSPEAK(266); + exit(0); +}