Add seedable PRNG using an adaptation the original LCG algorithm.
[open-adventure.git] / misc.c
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include "main.h"
5 #include "share.h"      /* for SETUP */
6 #include "misc.h"
7 #include "funcs.h"
8
9 /* hack to ignore GCC Unused Result */
10 #define IGNORE(r) do{if(r){}}while(0)
11
12 /*  I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
13
14 #undef SPEAK
15 void fSPEAK(long N) {
16 long BLANK, CASE, I, K, L, NEG, NPARMS, PARM, PRMTYP, STATE;
17
18 /*  Print the message which starts at LINES(N).  Precede it with a blank line
19  *  unless BLKLIN is false. */
20
21
22         if(N == 0)return;
23         BLANK=BLKLIN;
24         K=N;
25         NPARMS=1;
26 L10:    L=IABS(LINES[K])-1;
27         K=K+1;
28         LNLENG=0;
29         LNPOSN=1;
30         STATE=0;
31         for (I=K; I<=L; I++) {
32         PUTTXT(LINES[I],STATE,2,I);
33         } /* end loop */
34         LNPOSN=0;
35 L30:    LNPOSN=LNPOSN+1;
36 L32:    if(LNPOSN > LNLENG) goto L40;
37         if(INLINE[LNPOSN] != 63) goto L30;
38         {long x = LNPOSN+1; PRMTYP=INLINE[x];}
39 /*  63 is a "%"; the next character determine the type of parameter:  1 (!) =
40  *  suppress message completely, 29 (S) = NULL If PARM=1, else 'S' (optional
41  *  plural ending), 33 (W) = word (two 30-bit values) with trailing spaces
42  *  suppressed, 22 (L) or 31 (U) = word but map to lower/upper case, 13 (C) =
43  *  word in lower case with first letter capitalised, 30 (T) = text ending
44  *  with a word of -1, 65-73 (1-9) = number using that many characters,
45  *  12 (B) = variable number of blanks. */
46         if(PRMTYP == 1)return;
47         if(PRMTYP == 29) goto L320;
48         if(PRMTYP == 30) goto L340;
49         if(PRMTYP == 12) goto L360;
50         if(PRMTYP == 33 || PRMTYP == 22 || PRMTYP == 31 || PRMTYP == 13) goto
51                 L380;
52         PRMTYP=PRMTYP-64;
53         if(PRMTYP < 1 || PRMTYP > 9) goto L30;
54         SHFTXT(LNPOSN+2,PRMTYP-2);
55         LNPOSN=LNPOSN+PRMTYP;
56         PARM=IABS(PARMS[NPARMS]);
57         NEG=0;
58         if(PARMS[NPARMS] < 0)NEG=9;
59         /* 390 */ for (I=1; I<=PRMTYP; I++) {
60         LNPOSN=LNPOSN-1;
61         INLINE[LNPOSN]=MOD(PARM,10)+64;
62         if(I == 1 || PARM != 0) goto L390;
63         INLINE[LNPOSN]=NEG;
64         NEG=0;
65 L390:   PARM=PARM/10;
66         } /* end loop */
67         LNPOSN=LNPOSN+PRMTYP;
68 L395:   NPARMS=NPARMS+1;
69          goto L32;
70
71 L320:   SHFTXT(LNPOSN+2,-1);
72         INLINE[LNPOSN]=55;
73         if(PARMS[NPARMS] == 1)SHFTXT(LNPOSN+1,-1);
74          goto L395;
75
76 L340:   SHFTXT(LNPOSN+2,-2);
77         STATE=0;
78         CASE=2;
79 L345:   if(PARMS[NPARMS] < 0) goto L395;
80         {long x = NPARMS+1; if(PARMS[x] < 0)CASE=0;}
81         PUTTXT(PARMS[NPARMS],STATE,CASE,0);
82         NPARMS=NPARMS+1;
83          goto L345;
84
85 L360:   PRMTYP=PARMS[NPARMS];
86         SHFTXT(LNPOSN+2,PRMTYP-2);
87         if(PRMTYP == 0) goto L395;
88         for (I=1; I<=PRMTYP; I++) {
89         INLINE[LNPOSN]=0;
90         LNPOSN=LNPOSN+1;
91         } /* end loop */
92          goto L395;
93
94 L380:   SHFTXT(LNPOSN+2,-2);
95         STATE=0;
96         CASE= -1;
97         if(PRMTYP == 31)CASE=1;
98         if(PRMTYP == 33)CASE=0;
99         I=LNPOSN;
100         PUTTXT(PARMS[NPARMS],STATE,CASE,0);
101         {long x = NPARMS+1; PUTTXT(PARMS[x],STATE,CASE,0);}
102         if(PRMTYP == 13 && INLINE[I] >= 37 && INLINE[I] <=
103                 62)INLINE[I]=INLINE[I]-26;
104         NPARMS=NPARMS+2;
105          goto L32;
106
107 L40:    if(BLANK)TYPE0();
108         BLANK=false;
109         TYPE();
110         K=L+1;
111         if(LINES[K] >= 0) goto L10;
112         return;
113 }
114
115
116
117 #define SPEAK(N) fSPEAK(N)
118 #undef PSPEAK
119 void fPSPEAK(long MSG,long SKIP) {
120 long I, M;
121
122 /*  Find the skip+1st message from msg and print it.  MSG should be the index of
123  *  the inventory message for object.  (INVEN+N+1 message is PROP=N message). */
124
125
126         M=PTEXT[MSG];
127         if(SKIP < 0) goto L9;
128         for (I=0; I<=SKIP; I++) {
129 L1:     M=IABS(LINES[M]);
130         if(LINES[M] >= 0) goto L1;
131         /*etc*/ ;
132         } /* end loop */
133 L9:     SPEAK(M);
134         return;
135 }
136
137
138
139 #define PSPEAK(MSG,SKIP) fPSPEAK(MSG,SKIP)
140 #undef RSPEAK
141 void fRSPEAK(long I) {
142 ;
143
144 /*  Print the I-TH "random" message (section 6 of database). */
145
146
147         if(I != 0)SPEAK(RTEXT[I]);
148         return;
149 }
150
151
152
153 #define RSPEAK(I) fRSPEAK(I)
154 #undef SETPRM
155 void fSETPRM(long FIRST, long P1, long P2) {
156 ;
157
158 /*  Stores parameters into the PRMCOM parms array for use by speak.  P1 and P2
159  *  are stored into PARMS(FIRST) and PARMS(FIRST+1). */
160
161
162         if(FIRST >= 25)BUG(29);
163         PARMS[FIRST]=P1;
164         {long x = FIRST+1; PARMS[x]=P2;}
165         return;
166 }
167
168
169
170 #define SETPRM(FIRST,P1,P2) fSETPRM(FIRST,P1,P2)
171 #undef GETIN
172 #define WORD1 (*wORD1)
173 #define WORD1X (*wORD1X)
174 #define WORD2 (*wORD2)
175 #define WORD2X (*wORD2X)
176 void fGETIN(FILE *input, long *wORD1, long *wORD1X, long *wORD2, long *wORD2X) {
177 long JUNK;
178
179 /*  Get a command from the adventurer.  Snarf out the first word, pad it with
180  *  blanks, and return it in WORD1.  Chars 6 thru 10 are returned in WORD1X, in
181  *  case we need to print out the whole word in an error message.  Any number of
182  *  blanks may follow the word.  If a second word appears, it is returned in
183  *  WORD2 (chars 6 thru 10 in WORD2X), else WORD2 is -1. */
184
185
186 L10:    if(BLKLIN)TYPE0();
187         MAPLIN(input);
188         if(input == stdin && feof(stdin)) score(1);
189         WORD1=GETTXT(true,true,true,0);
190         if(BLKLIN && WORD1 < 0) goto L10;
191         WORD1X=GETTXT(false,true,true,0);
192 L12:    JUNK=GETTXT(false,true,true,0);
193         if(JUNK > 0) goto L12;
194         WORD2=GETTXT(true,true,true,0);
195         WORD2X=GETTXT(false,true,true,0);
196 L22:    JUNK=GETTXT(false,true,true,0);
197         if(JUNK > 0) goto L22;
198         if(GETTXT(true,true,true,0) <= 0)return;
199         RSPEAK(53);
200          goto L10;
201 }
202
203
204
205 #undef WORD1
206 #undef WORD1X
207 #undef WORD2
208 #undef WORD2X
209 #define GETIN(SRC,WORD1,WORD1X,WORD2,WORD2X) fGETIN(SRC,&WORD1,&WORD1X,&WORD2,&WORD2X)
210 #undef YES
211 long fYES(FILE *input, long X, long Y, long Z) {
212
213 long YES, REPLY, JUNK1, JUNK2, JUNK3;
214
215 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
216  *  if no, print Z and return false. */
217
218 L1:     RSPEAK(X);
219         GETIN(input, REPLY,JUNK1,JUNK2,JUNK3);
220         if(REPLY == MAKEWD(250519) || REPLY == MAKEWD(25)) goto L10;
221         if(REPLY == MAKEWD(1415) || REPLY == MAKEWD(14)) goto L20;
222         RSPEAK(185);
223          goto L1;
224 L10:    YES=true;
225         RSPEAK(Y);
226         return(YES);
227 L20:    YES=false;
228         RSPEAK(Z);
229         return(YES);
230 }
231
232
233
234
235
236 /*  Line-parsing routines (GETNUM, GETTXT, MAKEWD, PUTTXT, SHFTXT, TYPE0)
237                 */
238
239 /*  The routines on this page handle all the stuff that would normally be
240  *  taken care of by format statements.  We do it this way instead so that
241  *  we can handle textual data in a machine independent fashion.  All the
242  *  machine dependent i/o stuff is on the following page.  See that page
243  *  for a description of MAPCOM's inline array. */
244
245 #define YES(X,Y,Z) fYES(X,Y,Z)
246 #undef GETNUM
247 long fGETNUM(FILE *source) {
248 long DIGIT, GETNUM, SIGN;
249
250 /*  Obtain the next integer from an input line.  If K>0, we first read a
251  *  new input line from a file; if K<0, we read a line from the keyboard;
252  *  if K=0 we use a line that has already been read (and perhaps partially
253  *  scanned).  If we're at the end of the line or encounter an illegal
254  *  character (not a digit, hyphen, or blank), we return 0. */
255
256
257         if(source != NULL)MAPLIN(source);
258         GETNUM=0;
259 L10:    if(LNPOSN > LNLENG)return(GETNUM);
260         if(INLINE[LNPOSN] != 0) goto L20;
261         LNPOSN=LNPOSN+1;
262          goto L10;
263
264 L20:    SIGN=1;
265         if(INLINE[LNPOSN] != 9) goto L32;
266         SIGN= -1;
267 L30:    LNPOSN=LNPOSN+1;
268 L32:    if(LNPOSN > LNLENG || INLINE[LNPOSN] == 0) goto L42;
269         DIGIT=INLINE[LNPOSN]-64;
270         if(DIGIT < 0 || DIGIT > 9) goto L40;
271         GETNUM=GETNUM*10+DIGIT;
272          goto L30;
273
274 L40:    GETNUM=0;
275 L42:    GETNUM=GETNUM*SIGN;
276         LNPOSN=LNPOSN+1;
277         return(GETNUM);
278 }
279
280
281
282 #define GETNUM(K) fGETNUM(K)
283 #undef GETTXT
284 long fGETTXT(long SKIP,long ONEWRD, long UPPER, long HASH) {
285 long CHAR, GETTXT, I; static long SPLITTING = -1;
286
287 /*  Take characters from an input line and pack them into 30-bit words.
288  *  Skip says to skip leading blanks.  ONEWRD says stop if we come to a
289  *  blank.  UPPER says to map all letters to uppercase.  HASH may be used
290  *  as a parameter for encrypting the text if desired; however, a hash of 0
291  *  should result in unmodified bytes being packed.  If we reach the
292  *  end of the line, the word is filled up with blanks (which encode as 0's).
293  *  If we're already at end of line when GETTXT is called, we return -1. */
294
295         if(LNPOSN != SPLITTING)SPLITTING = -1;
296         GETTXT= -1;
297 L10:    if(LNPOSN > LNLENG)return(GETTXT);
298         if((!SKIP) || INLINE[LNPOSN] != 0) goto L11;
299         LNPOSN=LNPOSN+1;
300          goto L10;
301
302 L11:    GETTXT=0;
303         /* 15 */ for (I=1; I<=5; I++) {
304         GETTXT=GETTXT*64;
305         if(LNPOSN > LNLENG || (ONEWRD && INLINE[LNPOSN] == 0)) goto L15;
306         CHAR=INLINE[LNPOSN];
307         if(CHAR >= 63) goto L12;
308         SPLITTING = -1;
309         if(UPPER && CHAR >= 37)CHAR=CHAR-26;
310         GETTXT=GETTXT+CHAR;
311          goto L14;
312
313 L12:    if(SPLITTING == LNPOSN) goto L13;
314         GETTXT=GETTXT+63;
315         SPLITTING = LNPOSN;
316          goto L15;
317
318 L13:    GETTXT=GETTXT+CHAR-63;
319         SPLITTING = -1;
320 L14:    LNPOSN=LNPOSN+1;
321 L15:    /*etc*/ ;
322         } /* end loop */
323
324         if(HASH)GETTXT=GETTXT+MOD(HASH*13579L+5432L,97531L)*12345L+HASH;
325         return(GETTXT);
326 }
327
328
329
330 #define GETTXT(SKIP,ONEWRD,UPPER,HASH) fGETTXT(SKIP,ONEWRD,UPPER,HASH)
331 #undef MAKEWD
332 long fMAKEWD(long LETTRS) {
333 long I, L, MAKEWD;
334
335 /*  Combine five uppercase letters (represented by pairs of decimal digits
336  *  in lettrs) to form a 30-bit value matching the one that GETTXT would
337  *  return given those characters plus trailing blanks and HASH=0.  Caution:
338  *  lettrs will overflow 31 bits if 5-letter word starts with V-Z.  As a
339  *  kludgey workaround, you can increment a letter by 5 by adding 50 to
340  *  the next pair of digits. */
341
342
343         MAKEWD=0;
344         I=1;
345         L=LETTRS;
346 L10:    MAKEWD=MAKEWD+I*(MOD(L,50)+10);
347         I=I*64;
348         if(MOD(L,100) > 50)MAKEWD=MAKEWD+I*5;
349         L=L/100;
350         if(L != 0) goto L10;
351         I=64L*64L*64L*64L*64L/I;
352         MAKEWD=MAKEWD*I;
353         return(MAKEWD);
354 }
355
356
357
358 #define MAKEWD(LETTRS) fMAKEWD(LETTRS)
359 #undef PUTTXT
360 #define STATE (*sTATE)
361 void fPUTTXT(long WORD, long *sTATE, long CASE, long HASH) {
362 long ALPH1, ALPH2, BYTE, DIV, I, W;
363
364 /*  Unpack the 30-bit value in word to obtain up to 5 integer-encoded chars,
365  *  and store them in inline starting at LNPOSN.  If LNLENG>=LNPOSN, shift
366  *  existing characters to the right to make room.  HASH must be the same
367  *  as it was when gettxt created the 30-bit word.  STATE will be zero when
368  *  puttxt is called with the first of a sequence of words, but is thereafter
369  *  unchanged by the caller, so PUTTXT can use it to maintain state across
370  *  calls.  LNPOSN and LNLENG are incremented by the number of chars stored.
371  *  If CASE=1, all letters are made uppercase; if -1, lowercase; if 0, as is.
372  *  any other value for case is the same as 0 but also causes trailing blanks
373  *  to be included (in anticipation of subsequent additional text). */
374
375
376         ALPH1=13*CASE+24;
377         ALPH2=26*IABS(CASE)+ALPH1;
378         if(IABS(CASE) > 1)ALPH1=ALPH2;
379 /*  ALPH1&2 DEFINE RANGE OF WRONG-CASE CHARS, 11-36 OR 37-62 OR EMPTY. */
380         DIV=64L*64L*64L*64L;
381         W=WORD;
382         if(HASH)W=W-MOD(HASH*13579L+5432L,97531L)*12345L-HASH;
383         /* 18 */ for (I=1; I<=5; I++) {
384         if(W <= 0 && STATE == 0 && IABS(CASE) <= 1)return;
385         BYTE=W/DIV;
386         if(STATE != 0 || BYTE != 63) goto L12;
387         STATE=63;
388          goto L18;
389
390 L12:    SHFTXT(LNPOSN,1);
391         STATE=STATE+BYTE;
392         if(STATE < ALPH2 && STATE >= ALPH1)STATE=STATE-26*CASE;
393         INLINE[LNPOSN]=STATE;
394         LNPOSN=LNPOSN+1;
395         STATE=0;
396 L18:    W=(W-BYTE*DIV)*64;
397         } /* end loop */
398         return;
399 }
400
401
402
403 #undef STATE
404 #define PUTTXT(WORD,STATE,CASE,HASH) fPUTTXT(WORD,&STATE,CASE,HASH)
405 #undef SHFTXT
406 void fSHFTXT(long FROM, long DELTA) {
407 long I, II, JJ;
408
409 /*  Move INLINE(N) to INLINE(N+DELTA) for N=FROM,LNLENG.  Delta can be
410  *  negative.  LNLENG is updated; LNPOSN is not changed. */
411
412
413         if(LNLENG < FROM || DELTA == 0) goto L2;
414         for (I=FROM; I<=LNLENG; I++) {
415         II=I;
416         if(DELTA > 0)II=FROM+LNLENG-I;
417         JJ=II+DELTA;
418         INLINE[JJ]=INLINE[II];
419         } /* end loop */
420 L2:     LNLENG=LNLENG+DELTA;
421         return;
422 }
423
424
425
426 #define SHFTXT(FROM,DELTA) fSHFTXT(FROM,DELTA)
427 #undef TYPE0
428 void fTYPE0() {
429 long TEMP;
430
431 /*  Type a blank line.  This procedure is provided as a convenience for callers
432  *  who otherwise have no use for MAPCOM. */
433
434
435         TEMP=LNLENG;
436         LNLENG=0;
437         TYPE();
438         LNLENG=TEMP;
439         return;
440 }
441
442
443
444 #define TYPE0() fTYPE0()
445
446
447 /*  Suspend/resume I/O routines (SAVWDS, SAVARR, SAVWRD) */
448
449 #undef SAVWDS
450 void fSAVWDS(long *W1, long *W2, long *W3, long *W4, long *W5, long *W6, long *W7) {
451
452 /*  Write or read 7 variables.  See SAVWRD. */
453
454
455         SAVWRD(0,(*W1));
456         SAVWRD(0,(*W2));
457         SAVWRD(0,(*W3));
458         SAVWRD(0,(*W4));
459         SAVWRD(0,(*W5));
460         SAVWRD(0,(*W6));
461         SAVWRD(0,(*W7));
462         return;
463 }
464
465
466 #define SAVWDS(W1,W2,W3,W4,W5,W6,W7) fSAVWDS(&W1,&W2,&W3,&W4,&W5,&W6,&W7)
467 #undef SAVARR
468 void fSAVARR(long ARR[], long N) {
469 long I;
470
471 /*  Write or read an array of N words.  See SAVWRD. */
472
473
474         for (I=1; I<=N; I++) {
475         SAVWRD(0,ARR[I]);
476         } /* end loop */
477         return;
478 }
479
480
481
482 #define SAVARR(ARR,N) fSAVARR(ARR,N)
483 #undef SAVWRD
484 #define WORD (*wORD)
485 void fSAVWRD(long OP, long *wORD) {
486 static long BUF[250], CKSUM = 0, H1, HASH = 0, N = 0, STATE = 0;
487
488 /*  If OP<0, start writing a file, using word to initialise encryption; save
489  *  word in the file.  If OP>0, start reading a file; read the file to find
490  *  the value with which to decrypt the rest.  In either case, if a file is
491  *  already open, finish writing/reading it and don't start a new one.  If OP=0,
492  *  read/write a single word.  Words are buffered in case that makes for more
493  *  efficient disk use.  We also compute a simple checksum to catch elementary
494  *  poking within the saved file.  When we finish reading/writing the file,
495  *  we store zero into WORD if there's no checksum error, else nonzero. */
496
497
498         if(OP != 0){long ifvar; ifvar=(STATE); switch (ifvar<0? -1 : ifvar>0? 1 :
499                 0) { case -1: goto L30; case 0: goto L10; case 1: goto L30; }}
500         if(STATE == 0)return;
501         if(N == 250)SAVEIO(1,STATE > 0,BUF);
502         N=MOD(N,250)+1;
503         H1=MOD(HASH*1093L+221573L,1048576L);
504         HASH=MOD(H1*1093L+221573L,1048576L);
505         H1=MOD(H1,1234)*765432+MOD(HASH,123);
506         N--;
507         if(STATE > 0)WORD=BUF[N]+H1;
508         BUF[N]=WORD-H1;
509         N++;
510         CKSUM=MOD(CKSUM*13+WORD,1000000000L);
511         return;
512
513 L10:    STATE=OP;
514         SAVEIO(0,STATE > 0,BUF);
515         N=1;
516         if(STATE > 0) goto L15;
517         HASH=MOD(WORD,1048576L);
518         BUF[0]=1234L*5678L-HASH;
519 L13:    CKSUM=BUF[0];
520         return;
521
522 L15:    SAVEIO(1,true,BUF);
523         HASH=MOD(1234L*5678L-BUF[0],1048576L);
524          goto L13;
525
526 L30:    if(N == 250)SAVEIO(1,STATE > 0,BUF);
527         N=MOD(N,250)+1;
528         if(STATE > 0) goto L32;
529         N--; BUF[N]=CKSUM; N++;
530         SAVEIO(1,false,BUF);
531 L32:    N--; WORD=BUF[N]-CKSUM; N++;
532         SAVEIO(-1,STATE > 0,BUF);
533         STATE=0;
534         return;
535 }
536
537
538
539
540
541 /*  Data struc. routines (VOCAB, DSTROY, JUGGLE, MOVE, PUT, CARRY, DROP, ATDWRF)
542                 */
543
544 #undef WORD
545 #define SAVWRD(OP,WORD) fSAVWRD(OP,&WORD)
546 #undef VOCAB
547 long fVOCAB(long ID, long INIT) {
548 long HASH, I, VOCAB;
549
550 /*  Look up ID in the vocabulary (ATAB) and return its "definition" (KTAB), or
551  *  -1 if not found.  If INIT is positive, this is an initialisation call setting
552  *  up a keyword variable, and not finding it constitutes a bug.  It also means
553  *  that only KTAB values which taken over 1000 equal INIT may be considered.
554  *  (Thus "STEPS", which is a motion verb as well as an object, may be located
555  *  as an object.)  And it also means the KTAB value is taken modulo 1000. */
556
557         HASH=10000;
558         /* 1 */ for (I=1; I<=TABSIZ; I++) {
559         if(KTAB[I] == -1) goto L2;
560         HASH=HASH+7;
561         if(INIT >= 0 && KTAB[I]/1000 != INIT) goto L1;
562         if(ATAB[I] == ID+HASH*HASH) goto L3;
563 L1:     /*etc*/ ;
564         } /* end loop */
565         BUG(21);
566
567 L2:     VOCAB= -1;
568         if(INIT < 0)return(VOCAB);
569         BUG(5);
570
571 L3:     VOCAB=KTAB[I];
572         if(INIT >= 0)VOCAB=MOD(VOCAB,1000);
573         return(VOCAB);
574 }
575
576
577
578 #define VOCAB(ID,INIT) fVOCAB(ID,INIT)
579 #undef DSTROY
580 void fDSTROY(long OBJECT) {
581 ;
582
583 /*  Permanently eliminate "OBJECT" by moving to a non-existent location. */
584
585
586         MOVE(OBJECT,0);
587         return;
588 }
589
590
591
592 #define DSTROY(OBJECT) fDSTROY(OBJECT)
593 #undef JUGGLE
594 void fJUGGLE(long OBJECT) {
595 long I, J;
596
597 /*  Juggle an object by picking it up and putting it down again, the purpose
598  *  being to get the object to the front of the chain of things at its loc. */
599
600
601         I=PLACE[OBJECT];
602         J=FIXED[OBJECT];
603         MOVE(OBJECT,I);
604         MOVE(OBJECT+100,J);
605         return;
606 }
607
608
609
610 #define JUGGLE(OBJECT) fJUGGLE(OBJECT)
611 #undef MOVE
612 void fMOVE(long OBJECT, long WHERE) {
613 long FROM;
614
615 /*  Place any object anywhere by picking it up and dropping it.  May already be
616  *  toting, in which case the carry is a no-op.  Mustn't pick up objects which
617  *  are not at any loc, since carry wants to remove objects from ATLOC chains. */
618
619
620         if(OBJECT > 100) goto L1;
621         FROM=PLACE[OBJECT];
622          goto L2;
623 L1:     {long x = OBJECT-100; FROM=FIXED[x];}
624 L2:     if(FROM > 0 && FROM <= 300)CARRY(OBJECT,FROM);
625         DROP(OBJECT,WHERE);
626         return;
627 }
628
629
630
631 #define MOVE(OBJECT,WHERE) fMOVE(OBJECT,WHERE)
632 #undef PUT
633 long fPUT(long OBJECT, long WHERE, long PVAL) {
634 long PUT;
635
636 /*  PUT is the same as MOVE, except it returns a value used to set up the
637  *  negated PROP values for the repository objects. */
638
639
640         MOVE(OBJECT,WHERE);
641         PUT=(-1)-PVAL;
642         return(PUT);
643 }
644
645
646
647 #define PUT(OBJECT,WHERE,PVAL) fPUT(OBJECT,WHERE,PVAL)
648 #undef CARRY
649 void fCARRY(long OBJECT, long WHERE) {
650 long TEMP;
651
652 /*  Start toting an object, removing it from the list of things at its former
653  *  location.  Incr holdng unless it was already being toted.  If OBJECT>100
654  *  (moving "fixed" second loc), don't change PLACE or HOLDNG. */
655
656
657         if(OBJECT > 100) goto L5;
658         if(PLACE[OBJECT] == -1)return;
659         PLACE[OBJECT]= -1;
660         HOLDNG=HOLDNG+1;
661 L5:     if(ATLOC[WHERE] != OBJECT) goto L6;
662         ATLOC[WHERE]=LINK[OBJECT];
663         return;
664 L6:     TEMP=ATLOC[WHERE];
665 L7:     if(LINK[TEMP] == OBJECT) goto L8;
666         TEMP=LINK[TEMP];
667          goto L7;
668 L8:     LINK[TEMP]=LINK[OBJECT];
669         return;
670 }
671
672
673
674 #define CARRY(OBJECT,WHERE) fCARRY(OBJECT,WHERE)
675 #undef DROP
676 void fDROP(long OBJECT, long WHERE) {
677 ;
678
679 /*  Place an object at a given loc, prefixing it onto the ATLOC list.  Decr
680  *  HOLDNG if the object was being toted. */
681
682
683         if(OBJECT > 100) goto L1;
684         if(PLACE[OBJECT] == -1)HOLDNG=HOLDNG-1;
685         PLACE[OBJECT]=WHERE;
686          goto L2;
687 L1:     {long x = OBJECT-100; FIXED[x]=WHERE;}
688 L2:     if(WHERE <= 0)return;
689         LINK[OBJECT]=ATLOC[WHERE];
690         ATLOC[WHERE]=OBJECT;
691         return;
692 }
693
694
695
696 #define DROP(OBJECT,WHERE) fDROP(OBJECT,WHERE)
697 #undef ATDWRF
698 long fATDWRF(long WHERE) {
699 long ATDWRF, I;
700
701 /*  Return the index of first dwarf at the given location, zero if no dwarf is
702  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
703  *  the pirate (6th dwarf). */
704
705
706         ATDWRF=0;
707         if(DFLAG < 2)return(ATDWRF);
708         ATDWRF= -1;
709         for (I=1; I<=5; I++) {
710         if(DLOC[I] == WHERE) goto L2;
711         if(DLOC[I] != 0)ATDWRF=0;
712         } /* end loop */
713         return(ATDWRF);
714
715 L2:     ATDWRF=I;
716         return(ATDWRF);
717 }
718
719
720
721
722 #define ATDWRF(WHERE) fATDWRF(WHERE)
723
724
725
726 /*  Utility routines (SETBIT, TSTBIT, set_seed_from_time, get_next_lcg_value, randrange, RNDVOC, BUG) */
727
728 #undef SETBIT
729 long fSETBIT(long BIT) {
730 long I, SETBIT;
731
732 /*  Returns 2**bit for use in constructing bit-masks. */
733
734
735         SETBIT=1;
736         if(BIT <= 0)return(SETBIT);
737         for (I=1; I<=BIT; I++) {
738         SETBIT=SETBIT+SETBIT;
739         } /* end loop */
740         return(SETBIT);
741 }
742
743
744
745 #define SETBIT(BIT) fSETBIT(BIT)
746 #undef TSTBIT
747 long fTSTBIT(long MASK, long BIT) {
748 long TSTBIT;
749
750 /*  Returns true if the specified bit is set in the mask. */
751
752
753         TSTBIT=MOD(MASK/SETBIT(BIT),2) != 0;
754         return(TSTBIT);
755 }
756
757
758
759 #define TSTBIT(MASK,BIT) fTSTBIT(MASK,BIT)
760 #undef RNDVOC
761
762 void set_seed_from_time(void)
763 {
764   /* Use the current system time to get seed the ISO rand() function, from which we get a seed for the LCG. */
765   struct timespec ts;
766   clock_gettime(CLOCK_REALTIME, &ts);
767   srand(ts.tv_nsec);
768   lcgstate.x = (unsigned long) rand() % lcgstate.m;
769 }
770
771 unsigned long get_next_lcg_value(void)
772 {
773   /* Return the LCG's current value, and then iterate it. */
774   unsigned long old_x = lcgstate.x;
775   lcgstate.x = (lcgstate.a * lcgstate.x + lcgstate.c) % lcgstate.m;
776   return(old_x);
777 }
778
779 long randrange(long range)
780 {
781   /* Return a random integer from [0, range). */
782   long result = range * get_next_lcg_value() / lcgstate.m;
783   return(result);
784 }
785
786 long fRNDVOC(long CHAR, long FORCE) {
787 long DIV, I, J, RNDVOC;
788
789 /*  Searches the vocabulary for a word whose second character is char, and
790  *  changes that word such that each of the other four characters is a
791  *  random letter.  If force is non-zero, it is used as the new word.
792  *  Returns the new word. */
793
794
795         RNDVOC=FORCE;
796         if(RNDVOC != 0) goto L3;
797         for (I=1; I<=5; I++) {
798         J=11+randrange(26);
799         if(I == 2)J=CHAR;
800         RNDVOC=RNDVOC*64+J;
801         } /* end loop */
802 L3:     J=10000;
803         DIV=64L*64L*64L;
804         for (I=1; I<=TABSIZ; I++) {
805         J=J+7;
806         if(MOD((ATAB[I]-J*J)/DIV,64L) == CHAR) goto L8;
807         /*etc*/ ;
808         } /* end loop */
809         BUG(5);
810
811 L8:     ATAB[I]=RNDVOC+J*J;
812         return(RNDVOC);
813 }
814
815
816
817 #define RNDVOC(CHAR,FORCE) fRNDVOC(CHAR,FORCE)
818 #undef BUG
819 void fBUG(long NUM) {
820
821 /*  The following conditions are currently considered fatal bugs.  Numbers < 20
822  *  are detected while reading the database; the others occur at "run time".
823  *      0       Message line > 70 characters
824  *      1       Null line in message
825  *      2       Too many words of messages
826  *      3       Too many travel options
827  *      4       Too many vocabulary words
828  *      5       Required vocabulary word not found
829  *      6       Too many RTEXT messages
830  *      7       Too many hints
831  *      8       Location has cond bit being set twice
832  *      9       Invalid section number in database
833  *      10      Too many locations
834  *      11      Too many class or turn messages
835  *      20      Special travel (500>L>300) exceeds goto list
836  *      21      Ran off end of vocabulary table
837  *      22      Vocabulary type (N/1000) not between 0 and 3
838  *      23      Intransitive action verb exceeds goto list
839  *      24      Transitive action verb exceeds goto list
840  *      25      Conditional travel entry with no alternative
841  *      26      Location has no travel entries
842  *      27      Hint number exceeds goto list
843  *      28      Invalid month returned by date function
844  *      29      Too many parameters given to SETPRM */
845
846         printf("Fatal error %ld.  See source code for interpretation.\n",
847            NUM);
848         exit(0);
849 }
850
851
852
853
854
855 /*  Machine dependent routines (MAPLIN, TYPE, MPINIT, SAVEIO) */
856
857 #define BUG(NUM) fBUG(NUM)
858 #undef MAPLIN
859 void fMAPLIN(FILE *OPENED) {
860 long I, VAL;
861
862 /*  Read a line of input, from the specified input source,
863  *  translate the chars to integers in the range 0-126 and store
864  *  them in the common array "INLINE".  Integer values are as follows:
865  *     0   = space [ASCII CODE 40 octal, 32 decimal]
866  *    1-2  = !" [ASCII 41-42 octal, 33-34 decimal]
867  *    3-10 = '()*+,-. [ASCII 47-56 octal, 39-46 decimal]
868  *   11-36 = upper-case letters
869  *   37-62 = lower-case letters
870  *    63   = percent (%) [ASCII 45 octal, 37 decimal]
871  *   64-73 = digits, 0 through 9
872  *  Remaining characters can be translated any way that is convenient;
873  *  The "TYPE" routine below is used to map them back to characters when
874  *  necessary.  The above mappings are required so that certain special
875  *  characters are known to fit in 6 bits and/or can be easily spotted.
876  *  Array elements beyond the end of the line should be filled with 0,
877  *  and LNLENG should be set to the index of the last character.
878  *
879  *  If the data file uses a character other than space (e.g., tab) to
880  *  separate numbers, that character should also translate to 0.
881  *
882  *  This procedure may use the map1,map2 arrays to maintain static data for
883  *  the mapping.  MAP2(1) is set to 0 when the program starts
884  *  and is not changed thereafter unless the routines on this page choose
885  *  to do so. */
886
887         if(MAP2[1] == 0)MPINIT();
888
889         if (!oldstyle && SETUP)
890             fputs("> ", stdout);
891         IGNORE(fgets(INLINE+1,sizeof(INLINE)-1,OPENED));
892         if (feof(OPENED)) {
893                 if (logfp)
894                         fclose(logfp);
895         } else {
896                 if (logfp)
897                         IGNORE(fputs(INLINE+1, logfp));
898                 else if (!isatty(0))
899                         IGNORE(fputs(INLINE+1, stdout));
900                 LNLENG=0;
901                 for (I=1; I<=sizeof(INLINE) && INLINE[I]!=0; I++) {
902                 VAL=INLINE[I]+1;
903                 INLINE[I]=MAP1[VAL];
904                 if(INLINE[I] != 0)LNLENG=I;
905                 } /* end loop */
906                 LNPOSN=1;
907         }
908 }
909 #define MAPLIN(FIL) fMAPLIN(FIL)
910
911 #undef TYPE
912 void fTYPE(void) {
913 long I, VAL;
914
915 /*  Type the first "LNLENG" characters stored in inline, mapping them
916  *  from integers to text per the rules described above.  INLINE(I),
917  *  I=1,LNLENG may be changed by this routine. */
918
919
920         if(LNLENG != 0) goto L10;
921         printf("\n");
922         return;
923
924 L10:    if(MAP2[1] == 0)MPINIT();
925         for (I=1; I<=LNLENG; I++) {
926         VAL=INLINE[I];
927         {long x = VAL+1; INLINE[I]=MAP2[x];}
928         } /* end loop */
929         {long x = LNLENG+1; INLINE[x]=0;}
930         printf("%s\n",INLINE+1);
931         return;
932 }
933
934
935
936 #define TYPE() fTYPE()
937 #undef MPINIT
938 void fMPINIT(void) {
939 long FIRST, I, J, LAST, VAL;
940 static long RUNS[7][2] = {32,34, 39,46, 65,90, 97,122, 37,37, 48,57, 0,126};
941
942
943         for (I=1; I<=128; I++) {
944         MAP1[I]= -1;
945         } /* end loop */
946         VAL=0;
947         for (I=0; I<7; I++) {
948         FIRST=RUNS[I][0];
949         LAST=RUNS[I][1];
950         /* 22 */ for (J=FIRST; J<=LAST; J++) {
951         J++; if(MAP1[J] >= 0) goto L22;
952         MAP1[J]=VAL;
953         VAL=VAL+1;
954 L22:    J--;
955         } /* end loop */
956         /*etc*/ ;
957         } /* end loop */
958         MAP1[128]=MAP1[10];
959 /*  For this version, tab (9) maps to space (32), so del (127) uses tab's value */
960         MAP1[10]=MAP1[33];
961         MAP1[11]=MAP1[33];
962
963         for (I=0; I<=126; I++) {
964         I++; VAL=MAP1[I]+1; I--;
965         MAP2[VAL]=I*('B'-'A');
966         if(I >= 64)MAP2[VAL]=(I-64)*('B'-'A')+'@';
967         } /* end loop */
968
969         return;
970 }
971
972
973
974 #define MPINIT() fMPINIT()
975 #undef SAVEIO
976 void fSAVEIO(long OP, long IN, long ARR[]) {
977 static FILE *F; char NAME[50];
978
979 /*  If OP=0, ask for a file name and open a file.  (If IN=true, the file is for
980  *  input, else output.)  If OP>0, read/write ARR from/into the previously-opened
981  *  file.  (ARR is a 250-integer array.)  If OP<0, finish reading/writing the
982  *  file.  (Finishing writing can be a no-op if a "stop" statement does it
983  *  automatically.  Finishing reading can be a no-op as long as a subsequent
984  *  SAVEIO(0,false,X) will still work.)  If you can catch errors (e.g., no such
985  *  file) and try again, great.  DEC F40 can't. */
986
987
988         {long ifvar; ifvar=(OP); switch (ifvar<0? -1 : ifvar>0? 1 : 0) { case -1:
989                 goto L10; case 0: goto L20; case 1: goto L30; }}
990
991 L10:    fclose(F);
992         return;
993
994 L20:    printf("\nFile name: ");
995         IGNORE(fgets(NAME, sizeof(NAME), stdin));
996         F=fopen(NAME,(IN ? READ_MODE : WRITE_MODE));
997         if(F == NULL) {printf("Can't open file, try again.\n"); goto L20;}
998         return;
999
1000 L30:    if(IN)IGNORE(fread(ARR,sizeof(long),250,F));
1001         if(!IN)fwrite(ARR,sizeof(long),250,F);
1002         return;
1003
1004 }
1005
1006
1007
1008 long fIABS(N)long N; {return(N<0? -N : N);}
1009 long fMOD(N,M)long N, M; {return(N%M);}