Don Wood's 430-point Adventure 2.5 from 1995, from rec.games.int-fiction.
[open-adventure.git] / misc.c
1 #include "main.h"
2 #include "misc.h"
3 #include <stdio.h>
4
5 #define TRUE  (0==0)
6 #define FALSE (0!=0)
7
8 /*  I/O ROUTINES (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
9
10 #undef SPEAK
11 void fSPEAK(N)long N; {
12 long BLANK, CASE, I, K, L, NEG, NPARMS, PARM, PRMTYP, STATE;
13
14 /*  PRINT THE MESSAGE WHICH STARTS AT LINES(N).  PRECEDE IT WITH A BLANK LINE
15  *  UNLESS BLKLIN IS FALSE. */
16
17
18         if(N == 0)return;
19         BLANK=BLKLIN;
20         K=N;
21         NPARMS=1;
22 L10:    L=IABS(LINES[K])-1;
23         K=K+1;
24         LNLENG=0;
25         LNPOSN=1;
26         STATE=0;
27         /* 20 */ for (I=K; I<=L; I++) {
28 L20:    PUTTXT(LINES[I],STATE,2,I);
29         } /* end loop */
30         LNPOSN=0;
31 L30:    LNPOSN=LNPOSN+1;
32 L32:    if(LNPOSN > LNLENG) goto L40;
33         if(INLINE[LNPOSN] != 63) goto L30;
34         {long x = LNPOSN+1; PRMTYP=INLINE[x];}
35 /*  63 IS A "%"; THE NEXT CHARACTER DETERMINE THE TYPE OF PARAMETER:  1 (!) =
36  *  SUPPRESS MESSAGE COMPLETELY, 29 (S) = NULL IF PARM=1, ELSE 'S' (OPTIONAL
37  *  PLURAL ENDING), 33 (W) = WORD (TWO 30-BIT VALUES) WITH TRAILING SPACES
38  *  SUPPRESSED, 22 (L) OR 31 (U) = WORD BUT MAP TO LOWER/UPPER CASE, 13 (C) =
39  *  WORD IN LOWER CASE WITH FIRST LETTER CAPITALISED, 30 (T) = TEXT ENDING
40  *  WITH A WORD OF -1, 65-73 (1-9) = NUMBER USING THAT MANY CHARACTERS,
41  *  12 (B) = VARIABLE NUMBER OF BLANKS. */
42         if(PRMTYP == 1)return;
43         if(PRMTYP == 29) goto L320;
44         if(PRMTYP == 30) goto L340;
45         if(PRMTYP == 12) goto L360;
46         if(PRMTYP == 33 || PRMTYP == 22 || PRMTYP == 31 || PRMTYP == 13) goto
47                 L380;
48         PRMTYP=PRMTYP-64;
49         if(PRMTYP < 1 || PRMTYP > 9) goto L30;
50         SHFTXT(LNPOSN+2,PRMTYP-2);
51         LNPOSN=LNPOSN+PRMTYP;
52         PARM=IABS(PARMS[NPARMS]);
53         NEG=0;
54         if(PARMS[NPARMS] < 0)NEG=9;
55         /* 390 */ for (I=1; I<=PRMTYP; I++) {
56         LNPOSN=LNPOSN-1;
57         INLINE[LNPOSN]=MOD(PARM,10)+64;
58         if(I == 1 || PARM != 0) goto L390;
59         INLINE[LNPOSN]=NEG;
60         NEG=0;
61 L390:   PARM=PARM/10;
62         } /* end loop */
63         LNPOSN=LNPOSN+PRMTYP;
64 L395:   NPARMS=NPARMS+1;
65          goto L32;
66
67 L320:   SHFTXT(LNPOSN+2,-1);
68         INLINE[LNPOSN]=55;
69         if(PARMS[NPARMS] == 1)SHFTXT(LNPOSN+1,-1);
70          goto L395;
71
72 L340:   SHFTXT(LNPOSN+2,-2);
73         STATE=0;
74         CASE=2;
75 L345:   if(PARMS[NPARMS] < 0) goto L395;
76         {long x = NPARMS+1; if(PARMS[x] < 0)CASE=0;}
77         PUTTXT(PARMS[NPARMS],STATE,CASE,0);
78         NPARMS=NPARMS+1;
79          goto L345;
80
81 L360:   PRMTYP=PARMS[NPARMS];
82         SHFTXT(LNPOSN+2,PRMTYP-2);
83         if(PRMTYP == 0) goto L395;
84         /* 365 */ for (I=1; I<=PRMTYP; I++) {
85         INLINE[LNPOSN]=0;
86 L365:   LNPOSN=LNPOSN+1;
87         } /* end loop */
88          goto L395;
89
90 L380:   SHFTXT(LNPOSN+2,-2);
91         STATE=0;
92         CASE= -1;
93         if(PRMTYP == 31)CASE=1;
94         if(PRMTYP == 33)CASE=0;
95         I=LNPOSN;
96         PUTTXT(PARMS[NPARMS],STATE,CASE,0);
97         {long x = NPARMS+1; PUTTXT(PARMS[x],STATE,CASE,0);}
98         if(PRMTYP == 13 && INLINE[I] >= 37 && INLINE[I] <=
99                 62)INLINE[I]=INLINE[I]-26;
100         NPARMS=NPARMS+2;
101          goto L32;
102
103 L40:    if(BLANK)TYPE0();
104         BLANK=FALSE;
105         TYPE();
106         K=L+1;
107         if(LINES[K] >= 0) goto L10;
108         return;
109 }
110
111
112
113 #define SPEAK(N) fSPEAK(N)
114 #undef PSPEAK
115 void fPSPEAK(MSG,SKIP)long MSG, SKIP; {
116 long I, M;
117
118 /*  FIND THE SKIP+1ST MESSAGE FROM MSG AND PRINT IT.  MSG SHOULD BE THE INDEX OF
119  *  THE INVENTORY MESSAGE FOR OBJECT.  (INVEN+N+1 MESSAGE IS PROP=N MESSAGE). */
120
121
122         M=PTEXT[MSG];
123         if(SKIP < 0) goto L9;
124         /* 3 */ for (I=0; I<=SKIP; I++) {
125 L1:     M=IABS(LINES[M]);
126         if(LINES[M] >= 0) goto L1;
127 L3:     /*etc*/ ;
128         } /* end loop */
129 L9:     SPEAK(M);
130         return;
131 }
132
133
134
135 #define PSPEAK(MSG,SKIP) fPSPEAK(MSG,SKIP)
136 #undef RSPEAK
137 void fRSPEAK(I)long I; {
138 ;
139
140 /*  PRINT THE I-TH "RANDOM" MESSAGE (SECTION 6 OF DATABASE). */
141
142
143         if(I != 0)SPEAK(RTEXT[I]);
144         return;
145 }
146
147
148
149 #define RSPEAK(I) fRSPEAK(I)
150 #undef SETPRM
151 void fSETPRM(FIRST,P1,P2)long FIRST, P1, P2; {
152 ;
153
154 /*  STORES PARAMETERS INTO THE PRMCOM PARMS ARRAY FOR USE BY SPEAK.  P1 AND P2
155  *  ARE STORED INTO PARMS(FIRST) AND PARMS(FIRST+1). */
156
157
158         if(FIRST >= 25)BUG(29);
159         PARMS[FIRST]=P1;
160         {long x = FIRST+1; PARMS[x]=P2;}
161         return;
162 }
163
164
165
166 #define SETPRM(FIRST,P1,P2) fSETPRM(FIRST,P1,P2)
167 #undef GETIN
168 #define WORD1 (*wORD1)
169 #define WORD1X (*wORD1X)
170 #define WORD2 (*wORD2)
171 #define WORD2X (*wORD2X)
172 void fGETIN(wORD1,wORD1X,wORD2,wORD2X)long *wORD1, *wORD1X, *wORD2, *wORD2X; {
173 long JUNK;
174
175 /*  GET A COMMAND FROM THE ADVENTURER.  SNARF OUT THE FIRST WORD, PAD IT WITH
176  *  BLANKS, AND RETURN IT IN WORD1.  CHARS 6 THRU 10 ARE RETURNED IN WORD1X, IN
177  *  CASE WE NEED TO PRINT OUT THE WHOLE WORD IN AN ERROR MESSAGE.  ANY NUMBER OF
178  *  BLANKS MAY FOLLOW THE WORD.  IF A SECOND WORD APPEARS, IT IS RETURNED IN
179  *  WORD2 (CHARS 6 THRU 10 IN WORD2X), ELSE WORD2 IS -1. */
180
181
182 L10:    if(BLKLIN)TYPE0();
183         MAPLIN(FALSE);
184         WORD1=GETTXT(TRUE,TRUE,TRUE,0);
185         if(BLKLIN && WORD1 < 0) goto L10;
186         WORD1X=GETTXT(FALSE,TRUE,TRUE,0);
187 L12:    JUNK=GETTXT(FALSE,TRUE,TRUE,0);
188         if(JUNK > 0) goto L12;
189         WORD2=GETTXT(TRUE,TRUE,TRUE,0);
190         WORD2X=GETTXT(FALSE,TRUE,TRUE,0);
191 L22:    JUNK=GETTXT(FALSE,TRUE,TRUE,0);
192         if(JUNK > 0) goto L22;
193         if(GETTXT(TRUE,TRUE,TRUE,0) <= 0)return;
194         RSPEAK(53);
195          goto L10;
196 }
197
198
199
200 #undef WORD1
201 #undef WORD1X
202 #undef WORD2
203 #undef WORD2X
204 #define GETIN(WORD1,WORD1X,WORD2,WORD2X) fGETIN(&WORD1,&WORD1X,&WORD2,&WORD2X)
205 #undef YES
206 long fYES(X,Y,Z)long X, Y, Z; {
207
208 long YES, REPLY, JUNK1, JUNK2, JUNK3;
209
210 /*  PRINT MESSAGE X, WAIT FOR YES/NO ANSWER.  IF YES, PRINT Y AND RETURN TRUE;
211  *  IF NO, PRINT Z AND RETURN FALSE. */
212
213 L1:     RSPEAK(X);
214         GETIN(REPLY,JUNK1,JUNK2,JUNK3);
215         if(REPLY == MAKEWD(250519) || REPLY == MAKEWD(25)) goto L10;
216         if(REPLY == MAKEWD(1415) || REPLY == MAKEWD(14)) goto L20;
217         RSPEAK(185);
218          goto L1;
219 L10:    YES=TRUE;
220         RSPEAK(Y);
221         return(YES);
222 L20:    YES=FALSE;
223         RSPEAK(Z);
224         return(YES);
225 }
226
227
228
229
230
231 /*  LINE-PARSING ROUTINES (GETNUM, GETTXT, MAKEWD, PUTTXT, SHFTXT, TYPE0)
232                 */
233
234 /*  THE ROUTINES ON THIS PAGE HANDLE ALL THE STUFF THAT WOULD NORMALLY BE
235  *  TAKEN CARE OF BY FORMAT STATEMENTS.  WE DO IT THIS WAY INSTEAD SO THAT
236  *  WE CAN HANDLE TEXTUAL DATA IN A MACHINE INDEPENDENT FASHION.  ALL THE
237  *  MACHINE DEPENDENT I/O STUFF IS ON THE FOLLOWING PAGE.  SEE THAT PAGE
238  *  FOR A DESCRIPTION OF MAPCOM'S INLINE ARRAY. */
239
240 #define YES(X,Y,Z) fYES(X,Y,Z)
241 #undef GETNUM
242 long fGETNUM(K)long K; {
243 long DIGIT, GETNUM, SIGN;
244
245 /*  OBTAIN THE NEXT INTEGER FROM AN INPUT LINE.  IF K>0, WE FIRST READ A
246  *  NEW INPUT LINE FROM A FILE; IF K<0, WE READ A LINE FROM THE KEYBOARD;
247  *  IF K=0 WE USE A LINE THAT HAS ALREADY BEEN READ (AND PERHAPS PARTIALLY
248  *  SCANNED).  IF WE'RE AT THE END OF THE LINE OR ENCOUNTER AN ILLEGAL
249  *  CHARACTER (NOT A DIGIT, HYPHEN, OR BLANK), WE RETURN 0. */
250
251
252         if(K != 0)MAPLIN(K > 0);
253         GETNUM=0;
254 L10:    if(LNPOSN > LNLENG)return(GETNUM);
255         if(INLINE[LNPOSN] != 0) goto L20;
256         LNPOSN=LNPOSN+1;
257          goto L10;
258
259 L20:    SIGN=1;
260         if(INLINE[LNPOSN] != 9) goto L32;
261         SIGN= -1;
262 L30:    LNPOSN=LNPOSN+1;
263 L32:    if(LNPOSN > LNLENG || INLINE[LNPOSN] == 0) goto L42;
264         DIGIT=INLINE[LNPOSN]-64;
265         if(DIGIT < 0 || DIGIT > 9) goto L40;
266         GETNUM=GETNUM*10+DIGIT;
267          goto L30;
268
269 L40:    GETNUM=0;
270 L42:    GETNUM=GETNUM*SIGN;
271         LNPOSN=LNPOSN+1;
272         return(GETNUM);
273 }
274
275
276
277 #define GETNUM(K) fGETNUM(K)
278 #undef GETTXT
279 long fGETTXT(SKIP,ONEWRD,UPPER,HASH)long HASH, ONEWRD, SKIP, UPPER; {
280 long CHAR, GETTXT, I; static long SPLITTING = -1;
281
282 /*  TAKE CHARACTERS FROM AN INPUT LINE AND PACK THEM INTO 30-BIT WORDS.
283  *  SKIP SAYS TO SKIP LEADING BLANKS.  ONEWRD SAYS STOP IF WE COME TO A
284  *  BLANK.  UPPER SAYS TO MAP ALL LETTERS TO UPPERCASE.  HASH MAY BE USED
285  *  AS A PARAMETER FOR ENCRYPTING THE TEXT IF DESIRED; HOWEVER, A HASH OF 0
286  *  SHOULD RESULT IN UNMODIFIED BYTES BEING PACKED.  IF WE REACH THE
287  *  END OF THE LINE, THE WORD IS FILLED UP WITH BLANKS (WHICH ENCODE AS 0'S).
288  *  IF WE'RE ALREADY AT END OF LINE WHEN GETTXT IS CALLED, WE RETURN -1. */
289
290         if(LNPOSN != SPLITTING)SPLITTING = -1;
291         GETTXT= -1;
292 L10:    if(LNPOSN > LNLENG)return(GETTXT);
293         if((!SKIP) || INLINE[LNPOSN] != 0) goto L11;
294         LNPOSN=LNPOSN+1;
295          goto L10;
296
297 L11:    GETTXT=0;
298         /* 15 */ for (I=1; I<=5; I++) {
299         GETTXT=GETTXT*64;
300         if(LNPOSN > LNLENG || (ONEWRD && INLINE[LNPOSN] == 0)) goto L15;
301         CHAR=INLINE[LNPOSN];
302         if(CHAR >= 63) goto L12;
303         SPLITTING = -1;
304         if(UPPER && CHAR >= 37)CHAR=CHAR-26;
305         GETTXT=GETTXT+CHAR;
306          goto L14;
307
308 L12:    if(SPLITTING == LNPOSN) goto L13;
309         GETTXT=GETTXT+63;
310         SPLITTING = LNPOSN;
311          goto L15;
312
313 L13:    GETTXT=GETTXT+CHAR-63;
314         SPLITTING = -1;
315 L14:    LNPOSN=LNPOSN+1;
316 L15:    /*etc*/ ;
317         } /* end loop */
318
319         if(HASH)GETTXT=GETTXT+MOD(HASH*13579L+5432L,97531L)*12345L+HASH;
320         return(GETTXT);
321 }
322
323
324
325 #define GETTXT(SKIP,ONEWRD,UPPER,HASH) fGETTXT(SKIP,ONEWRD,UPPER,HASH)
326 #undef MAKEWD
327 long fMAKEWD(LETTRS)long LETTRS; {
328 long I, L, MAKEWD;
329
330 /*  COMBINE FIVE UPPERCASE LETTERS (REPRESENTED BY PAIRS OF DECIMAL DIGITS
331  *  IN LETTRS) TO FORM A 30-BIT VALUE MATCHING THE ONE THAT GETTXT WOULD
332  *  RETURN GIVEN THOSE CHARACTERS PLUS TRAILING BLANKS AND HASH=0.  CAUTION:
333  *  LETTRS WILL OVERFLOW 31 BITS IF 5-LETTER WORD STARTS WITH V-Z.  AS A
334  *  KLUDGEY WORKAROUND, YOU CAN INCREMENT A LETTER BY 5 BY ADDING 50 TO
335  *  THE NEXT PAIR OF DIGITS. */
336
337
338         MAKEWD=0;
339         I=1;
340         L=LETTRS;
341 L10:    MAKEWD=MAKEWD+I*(MOD(L,50)+10);
342         I=I*64;
343         if(MOD(L,100) > 50)MAKEWD=MAKEWD+I*5;
344         L=L/100;
345         if(L != 0) goto L10;
346         I=64L*64L*64L*64L*64L/I;
347         MAKEWD=MAKEWD*I;
348         return(MAKEWD);
349 }
350
351
352
353 #define MAKEWD(LETTRS) fMAKEWD(LETTRS)
354 #undef PUTTXT
355 #define STATE (*sTATE)
356 void fPUTTXT(WORD,sTATE,CASE,HASH)long CASE, HASH, *sTATE, WORD; {
357 long ALPH1, ALPH2, BYTE, DIV, I, W;
358
359 /*  UNPACK THE 30-BIT VALUE IN WORD TO OBTAIN UP TO 5 INTEGER-ENCODED CHARS,
360  *  AND STORE THEM IN INLINE STARTING AT LNPOSN.  IF LNLENG>=LNPOSN, SHIFT
361  *  EXISTING CHARACTERS TO THE RIGHT TO MAKE ROOM.  HASH MUST BE THE SAME
362  *  AS IT WAS WHEN GETTXT CREATED THE 30-BIT WORD.  STATE WILL BE ZERO WHEN
363  *  PUTTXT IS CALLED WITH THE FIRST OF A SEQUENCE OF WORDS, BUT IS THEREAFTER
364  *  UNCHANGED BY THE CALLER, SO PUTTXT CAN USE IT TO MAINTAIN STATE ACROSS
365  *  CALLS.  LNPOSN AND LNLENG ARE INCREMENTED BY THE NUMBER OF CHARS STORED.
366  *  IF CASE=1, ALL LETTERS ARE MADE UPPERCASE; IF -1, LOWERCASE; IF 0, AS IS.
367  *  ANY OTHER VALUE FOR CASE IS THE SAME AS 0 BUT ALSO CAUSES TRAILING BLANKS
368  *  TO BE INCLUDED (IN ANTICIPATION OF SUBSEQUENT ADDITIONAL TEXT). */
369
370
371         ALPH1=13*CASE+24;
372         ALPH2=26*IABS(CASE)+ALPH1;
373         if(IABS(CASE) > 1)ALPH1=ALPH2;
374 /*  ALPH1&2 DEFINE RANGE OF WRONG-CASE CHARS, 11-36 OR 37-62 OR EMPTY. */
375         DIV=64L*64L*64L*64L;
376         W=WORD;
377         if(HASH)W=W-MOD(HASH*13579L+5432L,97531L)*12345L-HASH;
378         /* 18 */ for (I=1; I<=5; I++) {
379         if(W <= 0 && STATE == 0 && IABS(CASE) <= 1)return;
380         BYTE=W/DIV;
381         if(STATE != 0 || BYTE != 63) goto L12;
382         STATE=63;
383          goto L18;
384
385 L12:    SHFTXT(LNPOSN,1);
386         STATE=STATE+BYTE;
387         if(STATE < ALPH2 && STATE >= ALPH1)STATE=STATE-26*CASE;
388         INLINE[LNPOSN]=STATE;
389         LNPOSN=LNPOSN+1;
390         STATE=0;
391 L18:    W=(W-BYTE*DIV)*64;
392         } /* end loop */
393         return;
394 }
395
396
397
398 #undef STATE
399 #define PUTTXT(WORD,STATE,CASE,HASH) fPUTTXT(WORD,&STATE,CASE,HASH)
400 #undef SHFTXT
401 void fSHFTXT(FROM,DELTA)long DELTA, FROM; {
402 long I, II, JJ;
403
404 /*  MOVE INLINE(N) TO INLINE(N+DELTA) FOR N=FROM,LNLENG.  DELTA CAN BE
405  *  NEGATIVE.  LNLENG IS UPDATED; LNPOSN IS NOT CHANGED. */
406
407
408         if(LNLENG < FROM || DELTA == 0) goto L2;
409         /* 1 */ for (I=FROM; I<=LNLENG; I++) {
410         II=I;
411         if(DELTA > 0)II=FROM+LNLENG-I;
412         JJ=II+DELTA;
413 L1:     INLINE[JJ]=INLINE[II];
414         } /* end loop */
415 L2:     LNLENG=LNLENG+DELTA;
416         return;
417 }
418
419
420
421 #define SHFTXT(FROM,DELTA) fSHFTXT(FROM,DELTA)
422 #undef TYPE0
423 void fTYPE0() {
424 long TEMP;
425
426 /*  TYPE A BLANK LINE.  THIS PROCEDURE IS PROVIDED AS A CONVENIENCE FOR CALLERS
427  *  WHO OTHERWISE HAVE NO USE FOR MAPCOM. */
428
429
430         TEMP=LNLENG;
431         LNLENG=0;
432         TYPE();
433         LNLENG=TEMP;
434         return;
435 }
436
437
438
439 #define TYPE0() fTYPE0()
440
441
442 /*  SUSPEND/RESUME I/O ROUTINES (SAVWDS, SAVARR, SAVWRD) */
443
444 #undef SAVWDS
445 void fSAVWDS(W1,W2,W3,W4,W5,W6,W7)long *W1, *W2, *W3, *W4, *W5, *W6, *W7; {
446 ;
447
448 /*  WRITE OR READ 7 VARIABLES.  SEE SAVWRD. */
449
450
451         SAVWRD(0,(*W1));
452         SAVWRD(0,(*W2));
453         SAVWRD(0,(*W3));
454         SAVWRD(0,(*W4));
455         SAVWRD(0,(*W5));
456         SAVWRD(0,(*W6));
457         SAVWRD(0,(*W7));
458         return;
459 }
460
461
462 #define SAVWDS(W1,W2,W3,W4,W5,W6,W7) fSAVWDS(&W1,&W2,&W3,&W4,&W5,&W6,&W7)
463 #undef SAVARR
464 void fSAVARR(ARR,N)long ARR[], N; {
465 long I;
466
467 /*  WRITE OR READ AN ARRAY OF N WORDS.  SEE SAVWRD. */
468
469
470         /* 1 */ for (I=1; I<=N; I++) {
471 L1:     SAVWRD(0,ARR[I]);
472         } /* end loop */
473         return;
474 }
475
476
477
478 #define SAVARR(ARR,N) fSAVARR(ARR,N)
479 #undef SAVWRD
480 #define WORD (*wORD)
481 void fSAVWRD(OP,wORD)long OP, *wORD; {
482 static long BUF[250], CKSUM = 0, H1, HASH = 0, N = 0, STATE = 0;
483
484 /*  IF OP<0, START WRITING A FILE, USING WORD TO INITIALISE ENCRYPTION; SAVE
485  *  WORD IN THE FILE.  IF OP>0, START READING A FILE; READ THE FILE TO FIND
486  *  THE VALUE WITH WHICH TO DECRYPT THE REST.  IN EITHER CASE, IF A FILE IS
487  *  ALREADY OPEN, FINISH WRITING/READING IT AND DON'T START A NEW ONE.  IF OP=0,
488  *  READ/WRITE A SINGLE WORD.  WORDS ARE BUFFERED IN CASE THAT MAKES FOR MORE
489  *  EFFICIENT DISK USE.  WE ALSO COMPUTE A SIMPLE CHECKSUM TO CATCH ELEMENTARY
490  *  POKING WITHIN THE SAVED FILE.  WHEN WE FINISH READING/WRITING THE FILE,
491  *  WE STORE ZERO INTO WORD IF THERE'S NO CHECKSUM ERROR, ELSE NONZERO. */
492
493
494         if(OP != 0){long ifvar; ifvar=(STATE); switch (ifvar<0? -1 : ifvar>0? 1 :
495                 0) { case -1: goto L30; case 0: goto L10; case 1: goto L30; }}
496         if(STATE == 0)return;
497         if(N == 250)SAVEIO(1,STATE > 0,BUF);
498         N=MOD(N,250)+1;
499         H1=MOD(HASH*1093L+221573L,1048576L);
500         HASH=MOD(H1*1093L+221573L,1048576L);
501         H1=MOD(H1,1234)*765432+MOD(HASH,123);
502         N--;
503         if(STATE > 0)WORD=BUF[N]+H1;
504         BUF[N]=WORD-H1;
505         N++;
506         CKSUM=MOD(CKSUM*13+WORD,1000000000L);
507         return;
508
509 L10:    STATE=OP;
510         SAVEIO(0,STATE > 0,BUF);
511         N=1;
512         if(STATE > 0) goto L15;
513         HASH=MOD(WORD,1048576L);
514         BUF[0]=1234L*5678L-HASH;
515 L13:    CKSUM=BUF[0];
516         return;
517
518 L15:    SAVEIO(1,TRUE,BUF);
519         HASH=MOD(1234L*5678L-BUF[0],1048576L);
520          goto L13;
521
522 L30:    if(N == 250)SAVEIO(1,STATE > 0,BUF);
523         N=MOD(N,250)+1;
524         if(STATE > 0) goto L32;
525         N--; BUF[N]=CKSUM; N++;
526         SAVEIO(1,FALSE,BUF);
527 L32:    N--; WORD=BUF[N]-CKSUM; N++;
528         SAVEIO(-1,STATE > 0,BUF);
529         STATE=0;
530         return;
531 }
532
533
534
535
536
537 /*  DATA STRUC. ROUTINES (VOCAB, DSTROY, JUGGLE, MOVE, PUT, CARRY, DROP, ATDWRF)
538                 */
539
540 #undef WORD
541 #define SAVWRD(OP,WORD) fSAVWRD(OP,&WORD)
542 #undef VOCAB
543 long fVOCAB(ID,INIT)long ID, INIT; {
544 long HASH, I, VOCAB;
545
546 /*  LOOK UP ID IN THE VOCABULARY (ATAB) AND RETURN ITS "DEFINITION" (KTAB), OR
547  *  -1 IF NOT FOUND.  IF INIT IS POSITIVE, THIS IS AN INITIALISATION CALL SETTING
548  *  UP A KEYWORD VARIABLE, AND NOT FINDING IT CONSTITUTES A BUG.  IT ALSO MEANS
549  *  THAT ONLY KTAB VALUES WHICH TAKEN OVER 1000 EQUAL INIT MAY BE CONSIDERED.
550  *  (THUS "STEPS", WHICH IS A MOTION VERB AS WELL AS AN OBJECT, MAY BE LOCATED
551  *  AS AN OBJECT.)  AND IT ALSO MEANS THE KTAB VALUE IS TAKEN MOD 1000. */
552
553         HASH=10000;
554         /* 1 */ for (I=1; I<=TABSIZ; I++) {
555         if(KTAB[I] == -1) goto L2;
556         HASH=HASH+7;
557         if(INIT >= 0 && KTAB[I]/1000 != INIT) goto L1;
558         if(ATAB[I] == ID+HASH*HASH) goto L3;
559 L1:     /*etc*/ ;
560         } /* end loop */
561         BUG(21);
562
563 L2:     VOCAB= -1;
564         if(INIT < 0)return(VOCAB);
565         BUG(5);
566
567 L3:     VOCAB=KTAB[I];
568         if(INIT >= 0)VOCAB=MOD(VOCAB,1000);
569         return(VOCAB);
570 }
571
572
573
574 #define VOCAB(ID,INIT) fVOCAB(ID,INIT)
575 #undef DSTROY
576 void fDSTROY(OBJECT)long OBJECT; {
577 ;
578
579 /*  PERMANENTLY ELIMINATE "OBJECT" BY MOVING TO A NON-EXISTENT LOCATION. */
580
581
582         MOVE(OBJECT,0);
583         return;
584 }
585
586
587
588 #define DSTROY(OBJECT) fDSTROY(OBJECT)
589 #undef JUGGLE
590 void fJUGGLE(OBJECT)long OBJECT; {
591 long I, J;
592
593 /*  JUGGLE AN OBJECT BY PICKING IT UP AND PUTTING IT DOWN AGAIN, THE PURPOSE
594  *  BEING TO GET THE OBJECT TO THE FRONT OF THE CHAIN OF THINGS AT ITS LOC. */
595
596
597         I=PLACE[OBJECT];
598         J=FIXED[OBJECT];
599         MOVE(OBJECT,I);
600         MOVE(OBJECT+100,J);
601         return;
602 }
603
604
605
606 #define JUGGLE(OBJECT) fJUGGLE(OBJECT)
607 #undef MOVE
608 void fMOVE(OBJECT,WHERE)long OBJECT, WHERE; {
609 long FROM;
610
611 /*  PLACE ANY OBJECT ANYWHERE BY PICKING IT UP AND DROPPING IT.  MAY ALREADY BE
612  *  TOTING, IN WHICH CASE THE CARRY IS A NO-OP.  MUSTN'T PICK UP OBJECTS WHICH
613  *  ARE NOT AT ANY LOC, SINCE CARRY WANTS TO REMOVE OBJECTS FROM ATLOC CHAINS. */
614
615
616         if(OBJECT > 100) goto L1;
617         FROM=PLACE[OBJECT];
618          goto L2;
619 L1:     {long x = OBJECT-100; FROM=FIXED[x];}
620 L2:     if(FROM > 0 && FROM <= 300)CARRY(OBJECT,FROM);
621         DROP(OBJECT,WHERE);
622         return;
623 }
624
625
626
627 #define MOVE(OBJECT,WHERE) fMOVE(OBJECT,WHERE)
628 #undef PUT
629 long fPUT(OBJECT,WHERE,PVAL)long OBJECT, PVAL, WHERE; {
630 long PUT;
631
632 /*  PUT IS THE SAME AS MOVE, EXCEPT IT RETURNS A VALUE USED TO SET UP THE
633  *  NEGATED PROP VALUES FOR THE REPOSITORY OBJECTS. */
634
635
636         MOVE(OBJECT,WHERE);
637         PUT=(-1)-PVAL;
638         return(PUT);
639 }
640
641
642
643 #define PUT(OBJECT,WHERE,PVAL) fPUT(OBJECT,WHERE,PVAL)
644 #undef CARRY
645 void fCARRY(OBJECT,WHERE)long OBJECT, WHERE; {
646 long TEMP;
647
648 /*  START TOTING AN OBJECT, REMOVING IT FROM THE LIST OF THINGS AT ITS FORMER
649  *  LOCATION.  INCR HOLDNG UNLESS IT WAS ALREADY BEING TOTED.  IF OBJECT>100
650  *  (MOVING "FIXED" SECOND LOC), DON'T CHANGE PLACE OR HOLDNG. */
651
652
653         if(OBJECT > 100) goto L5;
654         if(PLACE[OBJECT] == -1)return;
655         PLACE[OBJECT]= -1;
656         HOLDNG=HOLDNG+1;
657 L5:     if(ATLOC[WHERE] != OBJECT) goto L6;
658         ATLOC[WHERE]=LINK[OBJECT];
659         return;
660 L6:     TEMP=ATLOC[WHERE];
661 L7:     if(LINK[TEMP] == OBJECT) goto L8;
662         TEMP=LINK[TEMP];
663          goto L7;
664 L8:     LINK[TEMP]=LINK[OBJECT];
665         return;
666 }
667
668
669
670 #define CARRY(OBJECT,WHERE) fCARRY(OBJECT,WHERE)
671 #undef DROP
672 void fDROP(OBJECT,WHERE)long OBJECT, WHERE; {
673 ;
674
675 /*  PLACE AN OBJECT AT A GIVEN LOC, PREFIXING IT ONTO THE ATLOC LIST.  DECR
676  *  HOLDNG IF THE OBJECT WAS BEING TOTED. */
677
678
679         if(OBJECT > 100) goto L1;
680         if(PLACE[OBJECT] == -1)HOLDNG=HOLDNG-1;
681         PLACE[OBJECT]=WHERE;
682          goto L2;
683 L1:     {long x = OBJECT-100; FIXED[x]=WHERE;}
684 L2:     if(WHERE <= 0)return;
685         LINK[OBJECT]=ATLOC[WHERE];
686         ATLOC[WHERE]=OBJECT;
687         return;
688 }
689
690
691
692 #define DROP(OBJECT,WHERE) fDROP(OBJECT,WHERE)
693 #undef ATDWRF
694 long fATDWRF(WHERE)long WHERE; {
695 long ATDWRF, I;
696
697 /*  RETURN THE INDEX OF FIRST DWARF AT THE GIVEN LOCATION, ZERO IF NO DWARF IS
698  *  THERE (OR IF DWARVES NOT ACTIVE YET), -1 IF ALL DWARVES ARE DEAD.  IGNORE
699  *  THE PIRATE (6TH DWARF). */
700
701
702         ATDWRF=0;
703         if(DFLAG < 2)return(ATDWRF);
704         ATDWRF= -1;
705         /* 1 */ for (I=1; I<=5; I++) {
706         if(DLOC[I] == WHERE) goto L2;
707 L1:     if(DLOC[I] != 0)ATDWRF=0;
708         } /* end loop */
709         return(ATDWRF);
710
711 L2:     ATDWRF=I;
712         return(ATDWRF);
713 }
714
715
716
717
718 #define ATDWRF(WHERE) fATDWRF(WHERE)
719
720
721
722 /*  UTILITY ROUTINES (SETBIT, TSTBIT, RAN, RNDVOC, BUG) */
723
724 #undef SETBIT
725 long fSETBIT(BIT)long BIT; {
726 long I, SETBIT;
727
728 /*  RETURNS 2**BIT FOR USE IN CONSTRUCTING BIT-MASKS. */
729
730
731         SETBIT=1;
732         if(BIT <= 0)return(SETBIT);
733         /* 1 */ for (I=1; I<=BIT; I++) {
734 L1:     SETBIT=SETBIT+SETBIT;
735         } /* end loop */
736         return(SETBIT);
737 }
738
739
740
741 #define SETBIT(BIT) fSETBIT(BIT)
742 #undef TSTBIT
743 long fTSTBIT(MASK,BIT)long BIT, MASK; {
744 long TSTBIT;
745
746 /*  RETURNS TRUE IF THE SPECIFIED BIT IS SET IN THE MASK. */
747
748
749         TSTBIT=MOD(MASK/SETBIT(BIT),2) != 0;
750         return(TSTBIT);
751 }
752
753
754
755 #define TSTBIT(MASK,BIT) fTSTBIT(MASK,BIT)
756 #undef RAN
757 long fRAN(RANGE)long RANGE; {
758 static long D, R = 0, RAN, T;
759
760 /*  SINCE THE RAN FUNCTION IN LIB40 SEEMS TO BE A REAL LOSE, WE'LL USE ONE OF
761  *  OUR OWN.  IT'S BEEN RUN THROUGH MANY OF THE TESTS IN KNUTH VOL. 2 AND
762  *  SEEMS TO BE QUITE RELIABLE.  RAN RETURNS A VALUE UNIFORMLY SELECTED
763  *  BETWEEN 0 AND RANGE-1. */
764
765
766         D=1;
767         if(R != 0 && RANGE >= 0) goto L1;
768         DATIME(D,T);
769         R=MOD(T+5,1048576L);
770         D=1000+MOD(D,1000);
771 L1:     /* 2 */ for (T=1; T<=D; T++) {
772 L2:     R=MOD(R*1093L+221587L,1048576L);
773         } /* end loop */
774         RAN=(RANGE*R)/1048576;
775         return(RAN);
776 }
777
778
779
780 #define RAN(RANGE) fRAN(RANGE)
781 #undef RNDVOC
782 long fRNDVOC(CHAR,FORCE)long CHAR, FORCE; {
783 long DIV, I, J, RNDVOC;
784
785 /*  SEARCHES THE VOCABULARY FOR A WORD WHOSE SECOND CHARACTER IS CHAR, AND
786  *  CHANGES THAT WORD SUCH THAT EACH OF THE OTHER FOUR CHARACTERS IS A
787  *  RANDOM LETTER.  IF FORCE IS NON-ZERO, IT IS USED AS THE NEW WORD.
788  *  RETURNS THE NEW WORD. */
789
790
791         RNDVOC=FORCE;
792         if(RNDVOC != 0) goto L3;
793         /* 1 */ for (I=1; I<=5; I++) {
794         J=11+RAN(26);
795         if(I == 2)J=CHAR;
796 L1:     RNDVOC=RNDVOC*64+J;
797         } /* end loop */
798 L3:     J=10000;
799         DIV=64L*64L*64L;
800         /* 5 */ for (I=1; I<=TABSIZ; I++) {
801         J=J+7;
802         if(MOD((ATAB[I]-J*J)/DIV,64L) == CHAR) goto L8;
803 L5:     /*etc*/ ;
804         } /* end loop */
805         BUG(5);
806
807 L8:     ATAB[I]=RNDVOC+J*J;
808         return(RNDVOC);
809 }
810
811
812
813 #define RNDVOC(CHAR,FORCE) fRNDVOC(CHAR,FORCE)
814 #undef BUG
815 void fBUG(NUM)long NUM; {
816
817 /*  THE FOLLOWING CONDITIONS ARE CURRENTLY CONSIDERED FATAL BUGS.  NUMBERS < 20
818  *  ARE DETECTED WHILE READING THE DATABASE; THE OTHERS OCCUR AT "RUN TIME".
819  *      0       MESSAGE LINE > 70 CHARACTERS
820  *      1       NULL LINE IN MESSAGE
821  *      2       TOO MANY WORDS OF MESSAGES
822  *      3       TOO MANY TRAVEL OPTIONS
823  *      4       TOO MANY VOCABULARY WORDS
824  *      5       REQUIRED VOCABULARY WORD NOT FOUND
825  *      6       TOO MANY RTEXT MESSAGES
826  *      7       TOO MANY HINTS
827  *      8       LOCATION HAS COND BIT BEING SET TWICE
828  *      9       INVALID SECTION NUMBER IN DATABASE
829  *      10      TOO MANY LOCATIONS
830  *      11      TOO MANY CLASS OR TURN MESSAGES
831  *      20      SPECIAL TRAVEL (500>L>300) EXCEEDS GOTO LIST
832  *      21      RAN OFF END OF VOCABULARY TABLE
833  *      22      VOCABULARY TYPE (N/1000) NOT BETWEEN 0 AND 3
834  *      23      INTRANSITIVE ACTION VERB EXCEEDS GOTO LIST
835  *      24      TRANSITIVE ACTION VERB EXCEEDS GOTO LIST
836  *      25      CONDITIONAL TRAVEL ENTRY WITH NO ALTERNATIVE
837  *      26      LOCATION HAS NO TRAVEL ENTRIES
838  *      27      HINT NUMBER EXCEEDS GOTO LIST
839  *      28      INVALID MONTH RETURNED BY DATE FUNCTION
840  *      29      TOO MANY PARAMETERS GIVEN TO SETPRM */
841
842         printf("Fatal error %d.  See source code for interpretation.\n",
843            NUM);
844         exit(FALSE);
845 }
846
847
848
849
850
851 /*  MACHINE DEPENDENT ROUTINES (MAPLIN, TYPE, MPINIT, SAVEIO) */
852
853 #define BUG(NUM) fBUG(NUM)
854 #undef MAPLIN
855 void fMAPLIN(FIL)long FIL; {
856 long I, VAL; static FILE *OPENED = NULL;
857
858 /*  READ A LINE OF INPUT, EITHER FROM A FILE (IF FIL=.TRUE.) OR FROM THE
859  *  KEYBOARD, TRANSLATE THE CHARS TO INTEGERS IN THE RANGE 0-126 AND STORE
860  *  THEM IN THE COMMON ARRAY "INLINE".  INTEGER VALUES ARE AS FOLLOWS:
861  *     0   = SPACE [ASCII CODE 40 OCTAL, 32 DECIMAL]
862  *    1-2  = !" [ASCII 41-42 OCTAL, 33-34 DECIMAL]
863  *    3-10 = '()*+,-. [ASCII 47-56 OCTAL, 39-46 DECIMAL]
864  *   11-36 = UPPER-CASE LETTERS
865  *   37-62 = LOWER-CASE LETTERS
866  *    63   = PERCENT (%) [ASCII 45 OCTAL, 37 DECIMAL]
867  *   64-73 = DIGITS, 0 THROUGH 9
868  *  REMAINING CHARACTERS CAN BE TRANSLATED ANY WAY THAT IS CONVENIENT;
869  *  THE "TYPE" ROUTINE BELOW IS USED TO MAP THEM BACK TO CHARACTERS WHEN
870  *  NECESSARY.  THE ABOVE MAPPINGS ARE REQUIRED SO THAT CERTAIN SPECIAL
871  *  CHARACTERS ARE KNOWN TO FIT IN 6 BITS AND/OR CAN BE EASILY SPOTTED.
872  *  ARRAY ELEMENTS BEYOND THE END OF THE LINE SHOULD BE FILLED WITH 0,
873  *  AND LNLENG SHOULD BE SET TO THE INDEX OF THE LAST CHARACTER.
874  *
875  *  IF THE DATA FILE USES A CHARACTER OTHER THAN SPACE (E.G., TAB) TO
876  *  SEPARATE NUMBERS, THAT CHARACTER SHOULD ALSO TRANSLATE TO 0.
877  *
878  *  THIS PROCEDURE MAY USE THE MAP1,MAP2 ARRAYS TO MAINTAIN STATIC DATA FOR
879  *  THE MAPPING.  MAP2(1) IS SET TO 0 WHEN THE PROGRAM STARTS
880  *  AND IS NOT CHANGED THEREAFTER UNLESS THE ROUTINES ON THIS PAGE CHOOSE
881  *  TO DO SO.
882  *
883  *  NOTE THAT MAPLIN IS EXPECTED TO OPEN THE FILE THE FIRST TIME IT IS
884  *  ASKED TO READ A LINE FROM IT.  THAT IS, THERE IS NO OTHER PLACE WHERE
885  *  THE DATA FILE IS OPENED. */
886
887
888         if(MAP2[1] == 0)MPINIT();
889
890         if(FIL) goto L15;
891         gets(INLINE+1);
892         if(feof(stdin)) score(1);
893          goto L20;
894
895 L15:    if(!OPENED){
896 #ifdef AMIGA
897                 OPENED=fopen("ram:adventure.text","r" /* NOT binary */);
898                 if(!OPENED)
899 #endif
900                 OPENED=fopen("adventure.text","r" /* NOT binary */);
901                 if(!OPENED){printf("Can't read adventure.text!\n"); exit(FALSE);}
902                 }
903         fgets(INLINE+1,100,OPENED);
904
905 L20:    LNLENG=0;
906         /* 25 */ for (I=1; I<=100 && INLINE[I]!=0; I++) {
907         VAL=INLINE[I]+1;
908         INLINE[I]=MAP1[VAL];
909 L25:    if(INLINE[I] != 0)LNLENG=I;
910         } /* end loop */
911         LNPOSN=1;
912         if(FIL && LNLENG == 0) goto L15;
913 /*  ABOVE IS TO GET AROUND AN F40 COMPILER BUG WHEREIN IT READS A BLANK
914  *  LINE WHENEVER A CRLF IS BROKEN ACROSS A RECORD BOUNDARY. */
915         return;
916 }
917
918
919
920 #define MAPLIN(FIL) fMAPLIN(FIL)
921 #undef TYPE
922 void fTYPE() {
923 long I, VAL;
924
925 /*  TYPE THE FIRST "LNLENG" CHARACTERS STORED IN INLINE, MAPPING THEM
926  *  FROM INTEGERS TO TEXT PER THE RULES DESCRIBED ABOVE.  INLINE(I),
927  *  I=1,LNLENG MAY BE CHANGED BY THIS ROUTINE. */
928
929
930         if(LNLENG != 0) goto L10;
931         printf("\n");
932         return;
933
934 L10:    if(MAP2[1] == 0)MPINIT();
935         /* 20 */ for (I=1; I<=LNLENG; I++) {
936         VAL=INLINE[I];
937 L20:    {long x = VAL+1; INLINE[I]=MAP2[x];}
938         } /* end loop */
939         {long x = LNLENG+1; INLINE[x]=0;}
940         printf("%s\n",INLINE+1);
941         return;
942 }
943
944
945
946 #define TYPE() fTYPE()
947 #undef MPINIT
948 void fMPINIT() {
949 long FIRST, I, J, LAST, VAL;
950 static long RUNS[7][2] = {32,34, 39,46, 65,90, 97,122, 37,37, 48,57, 0,126};
951
952
953         /* 10 */ for (I=1; I<=128; I++) {
954 L10:    MAP1[I]= -1;
955         } /* end loop */
956         VAL=0;
957         /* 20 */ for (I=0; I<7; I++) {
958         FIRST=RUNS[I][0];
959         LAST=RUNS[I][1];
960         /* 22 */ for (J=FIRST; J<=LAST; J++) {
961         J++; if(MAP1[J] >= 0) goto L22;
962         MAP1[J]=VAL;
963         VAL=VAL+1;
964 L22:    J--;
965         } /* end loop */
966 L20:    /*etc*/ ;
967         } /* end loop */
968         MAP1[128]=MAP1[10];
969 /*  FOR THIS VERSION, TAB (9) MAPS TO SPACE (32), SO DEL (127) USES TAB'S VALUE */
970         MAP1[10]=MAP1[33];
971         MAP1[11]=MAP1[33];
972
973         /* 30 */ for (I=0; I<=126; I++) {
974         I++; VAL=MAP1[I]+1; I--;
975         MAP2[VAL]=I*('B'-'A');
976 L30:    if(I >= 64)MAP2[VAL]=(I-64)*('B'-'A')+'@';
977         } /* end loop */
978
979         return;
980 }
981
982
983
984 #define MPINIT() fMPINIT()
985 #undef SAVEIO
986 void fSAVEIO(OP,IN,ARR)long ARR[], IN, OP; {
987 static FILE *F; char NAME[50];
988
989 /*  IF OP=0, ASK FOR A FILE NAME AND OPEN A FILE.  (IF IN=.TRUE., THE FILE IS FOR
990  *  INPUT, ELSE OUTPUT.)  IF OP>0, READ/WRITE ARR FROM/INTO THE PREVIOUSLY-OPENED
991  *  FILE.  (ARR IS A 250-INTEGER ARRAY.)  IF OP<0, FINISH READING/WRITING THE
992  *  FILE.  (FINISHING WRITING CAN BE A NO-OP IF A "STOP" STATEMENT DOES IT
993  *  AUTOMATICALLY.  FINISHING READING CAN BE A NO-OP AS LONG AS A SUBSEQUENT
994  *  SAVEIO(0,.FALSE.,X) WILL STILL WORK.)  IF YOU CAN CATCH ERRORS (E.G., NO SUCH
995  *  FILE) AND TRY AGAIN, GREAT.  DEC F40 CAN'T. */
996
997
998         {long ifvar; ifvar=(OP); switch (ifvar<0? -1 : ifvar>0? 1 : 0) { case -1:
999                 goto L10; case 0: goto L20; case 1: goto L30; }}
1000
1001 L10:    fclose(F);
1002         return;
1003
1004 L20:    printf("\nFile name: ");
1005         gets(NAME);
1006         F=fopen(NAME,(IN ? READ_MODE : WRITE_MODE));
1007         if(F == NULL) {printf("Can't open file, try again.\n"); goto L20;}
1008         return;
1009
1010 L30:    if(IN)fread(ARR,4,250,F);
1011         if(!IN)fwrite(ARR,4,250,F);
1012         return;
1013
1014 }
1015
1016
1017
1018 long fIABS(N)long N; {return(N<0? -N : N);}
1019 long fMOD(N,M)long N, M; {return(N%M);}