Yet more gotoectomy.
[open-adventure.git] / misc.c
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <sys/time.h>
6 #include "advent.h"
7 #include "funcs.h"
8 #include "database.h"
9
10 /* hack to ignore GCC Unused Result */
11 #define IGNORE(r) do{if (r){}}while(0)
12
13 #define PERCENT 63      /* partly hide the packed encoding */
14
15 /*  I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
16
17 void SPEAK(vocab_t msg)
18 /*  Print the message which starts at LINES[N].  Precede it with a blank line
19  *  unless game.blklin is false. */
20 {
21     long blank, casemake, i, nxt, neg, nparms, param, prmtyp, state;
22
23     if (msg == 0)
24         return;
25     blank=game.blklin;
26     nparms=1;
27     do {
28         nxt=labs(LINES[msg])-1;
29         msg=msg+1;
30         LNLENG=0;
31         LNPOSN=1;
32         state=0;
33         for (i = msg; i <= nxt; i++) {
34             PUTTXT(LINES[i],state,2);
35         }
36         LNPOSN=0;
37 L30:
38         ++LNPOSN;
39 L32:
40         if (LNPOSN > LNLENG) 
41             goto L40;
42         if (INLINE[LNPOSN] != PERCENT) 
43             goto L30;
44         prmtyp = INLINE[LNPOSN+1];
45         /*  A "%"; the next character determine the type of
46          *  parameter: 1 (!) = suppress message completely, 29 (S) = NULL
47          *  If PARAM=1, else 'S' (optional plural ending), 33 (W) = word
48          *  (two 30-bit values) with trailing spaces suppressed, 22 (L) or
49          *  31 (U) = word but map to lower/upper case, 13 (C) = word in
50          *  lower case with first letter capitalised, 30 (T) = text ending
51          *  with a word of -1, 65-73 (1-9) = number using that many
52          *  characters, 12 (B) = variable number of blanks. */
53         if (prmtyp == 1)
54             return;
55         if (prmtyp == 29) {
56             SHFTXT(LNPOSN+2,-1);
57             INLINE[LNPOSN] = 55;
58             if (PARMS[nparms] == 1)
59                 SHFTXT(LNPOSN+1,-1);
60             goto L395;
61         }
62         if (prmtyp == 30) {
63             SHFTXT(LNPOSN+2,-2);
64             state=0;
65             casemake=2;
66
67             for (;;) {
68                 if (PARMS[nparms] < 0)
69                     goto L395;
70                 if (PARMS[nparms+1] < 0)
71                     casemake=0;
72                 PUTTXT(PARMS[nparms],state,casemake);
73                 nparms=nparms+1;
74             }
75         }
76         if (prmtyp == 12) {
77             prmtyp=PARMS[nparms];
78             SHFTXT(LNPOSN+2,prmtyp-2);
79             if (prmtyp != 0) {
80                 for (i=1; i<=prmtyp; i++) {
81                     INLINE[LNPOSN]=0;
82                     LNPOSN=LNPOSN+1;
83                 }
84             }
85             goto L395;
86         }
87         if (prmtyp == 33 || prmtyp == 22 || prmtyp == 31 || prmtyp == 13) {
88             SHFTXT(LNPOSN+2,-2);
89             state = 0;
90             casemake = -1;
91             if (prmtyp == 31)
92                 casemake=1;
93             if (prmtyp == 33)
94                 casemake=0;
95             i = LNPOSN;
96             PUTTXT(PARMS[nparms],state,casemake);
97             PUTTXT(PARMS[nparms+1],state,casemake);
98             if (prmtyp == 13 && INLINE[i] >= 37 && INLINE[i] <= 62)
99                 INLINE[i] -= 26;
100             nparms += 2;
101             goto L32;
102         }
103         prmtyp=prmtyp-64;
104         if (prmtyp < 1 || prmtyp > 9)
105             goto L30;
106         SHFTXT(LNPOSN+2,prmtyp-2);
107         LNPOSN += prmtyp;
108         param=labs(PARMS[nparms]);
109         neg=0;
110         if (PARMS[nparms] < 0)
111             neg=9;
112         for (i=1; i <= prmtyp; i++) {
113             --LNPOSN;
114             INLINE[LNPOSN]=MOD(param,10)+64;
115             if (i != 1 && param == 0) {
116                 INLINE[LNPOSN]=neg;
117                 neg=0;
118             }
119             param=param/10;
120         }
121         LNPOSN += prmtyp;
122 L395:
123         ++nparms;
124         goto L32;
125
126 L40:
127         if (blank)
128             TYPE0();
129         blank=false;
130         TYPE();
131         msg = nxt + 1;
132     } while
133         (LINES[msg] >= 0);
134 }
135
136 void PSPEAK(vocab_t msg,int skip)
137 /*  Find the skip+1st message from msg and print it.  msg should be
138  *  the index of the inventory message for object.  (INVEN+N+1 message
139  *  is game.prop=N message). */
140 {
141     long i, m;
142
143     m=PTEXT[msg];
144     if (skip >= 0) {
145         for (i=0; i <=skip; i++) {
146             do {
147                 m=labs(LINES[m]);
148             } while
149                 (LINES[m] >= 0);
150         }
151     }
152     SPEAK(m);
153 }
154
155 void RSPEAK(vocab_t i)
156 /* Print the i-th "random" message (section 6 of database). */
157 {
158     if (i != 0)
159         SPEAK(RTEXT[i]);
160 }
161
162 void SETPRM(long first, long p1, long p2)
163 /*  Stores parameters into the PRMCOM parms array for use by speak.  P1 and P2
164  *  are stored into PARMS(first) and PARMS(first+1). */
165 {
166     if (first >= MAXPARMS)
167         BUG(29);
168     else {
169         PARMS[first] = p1;
170         PARMS[first+1] = p2;
171     }
172 }
173
174 bool fGETIN(FILE *input, 
175             long *pword1, long *pword1x, 
176             long *pword2, long *pword2x) 
177 /*  Get a command from the adventurer.  Snarf out the first word, pad it with
178  *  blanks, and return it in WORD1.  Chars 6 thru 10 are returned in WORD1X, in
179  *  case we need to print out the whole word in an error message.  Any number of
180  *  blanks may follow the word.  If a second word appears, it is returned in
181  *  WORD2 (chars 6 thru 10 in WORD2X), else WORD2 is -1. */
182 {
183     long junk;
184
185     for (;;) {
186         if (game.blklin)
187             TYPE0();
188         MAPLIN(input);
189         if (feof(input))
190             return false;
191         *pword1=GETTXT(true,true,true);
192         if (game.blklin && *pword1 < 0)
193             continue;
194         *pword1x=GETTXT(false,true,true);
195         do {    
196             junk=GETTXT(false,true,true);
197         } while 
198             (junk > 0);
199         *pword2=GETTXT(true,true,true);
200         *pword2x=GETTXT(false,true,true);
201         do {
202             junk=GETTXT(false,true,true);
203         } while 
204             (junk > 0);
205         if (GETTXT(true,true,true) <= 0)
206             return true;
207         RSPEAK(53);
208     }
209 }
210
211 long YES(FILE *input, vocab_t x, vocab_t y, vocab_t z)
212 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
213  *  if no, print Z and return false. */
214 {
215     token_t reply, junk1, junk2, junk3;
216
217     for (;;) {
218         RSPEAK(x);
219         GETIN(input, reply,junk1,junk2,junk3);
220         if (reply == MAKEWD(250519) || reply == MAKEWD(25)) {
221             RSPEAK(y);
222             return true;
223         }
224         if (reply == MAKEWD(1415) || reply == MAKEWD(14)) {
225             RSPEAK(z);
226             return false;
227         }
228         RSPEAK(185);
229     }
230 }
231
232 /*  Line-parsing routines (GETTXT, MAKEWD, PUTTXT, SHFTXT, TYPE0) */
233
234 long GETTXT(bool skip, bool onewrd, bool upper)
235 /*  Take characters from an input line and pack them into 30-bit words.
236  *  Skip says to skip leading blanks.  ONEWRD says stop if we come to a
237  *  blank.  UPPER says to map all letters to uppercase.  If we reach the
238  *  end of the line, the word is filled up with blanks (which encode as 0's).
239  *  If we're already at end of line when TEXT is called, we return -1. */
240 {
241     long text;
242     static long splitting = -1;
243
244     if (LNPOSN != splitting)
245         splitting = -1;
246     text= -1;
247     while (true) {
248         if (LNPOSN > LNLENG)
249             return(text);
250         if ((!skip) || INLINE[LNPOSN] != 0)
251             break;
252         LNPOSN=LNPOSN+1;
253     }
254
255     text=0;
256     for (int I=1; I<=TOKLEN; I++) {
257         text=text*64;
258         if (LNPOSN > LNLENG || (onewrd && INLINE[LNPOSN] == 0))
259             continue;
260         char current=INLINE[LNPOSN];
261         if (current < PERCENT) {
262             splitting = -1;
263             if (upper && current >= 37)
264                 current=current-26;
265             text=text+current;
266             LNPOSN=LNPOSN+1;
267             continue;
268         }
269         if (splitting != LNPOSN) {
270             text=text+PERCENT;
271             splitting = LNPOSN;
272             continue;
273         }
274
275         text=text+current-PERCENT;
276         splitting = -1;
277         LNPOSN=LNPOSN+1;
278     }
279
280     return text;
281 }
282
283 token_t MAKEWD(long letters)
284 /*  Combine TOKLEN (currently 5) uppercase letters (represented by
285  *  pairs of decimal digits in lettrs) to form a 30-bit value matching
286  *  the one that GETTXT would return given those characters plus
287  *  trailing blanks.  Caution: lettrs will overflow 31 bits if
288  *  5-letter word starts with V-Z.  As a kludgey workaround, you can
289  *  increment a letter by 5 by adding 50 to the next pair of
290  *  digits. */
291 {
292     long i = 1, word = 0;
293
294     for (long k=letters; k != 0; k=k/100) {
295         word=word+i*(MOD(k,50)+10);
296         i=i*64;
297         if (MOD(k,100) > 50)word=word+i*5;
298     }
299     i=64L*64L*64L*64L*64L/i;
300     word=word*i;
301     return word;
302 }
303
304 void fPUTTXT(token_t word, long *state, long casemake)
305 /*  Unpack the 30-bit value in word to obtain up to TOKLEN (currently
306  *  5) integer-encoded chars, and store them in inline starting at
307  *  LNPOSN.  If LNLENG>=LNPOSN, shift existing characters to the right
308  *  to make room.  STATE will be zero when puttxt is called with the
309  *  first of a sequence of words, but is thereafter unchanged by the
310  *  caller, so PUTTXT can use it to maintain state across calls.
311  *  LNPOSN and LNLENG are incremented by the number of chars stored.
312  *  If CASEMAKE=1, all letters are made uppercase; if -1, lowercase; if 0,
313  *  as is.  any other value for case is the same as 0 but also causes
314  *  trailing blanks to be included (in anticipation of subsequent
315  *  additional text). */
316 {
317     long alph1, alph2, byte, div, i, w;
318
319     alph1=13*casemake+24;
320     alph2=26*labs(casemake)+alph1;
321     if (labs(casemake) > 1)
322         alph1=alph2;
323     /*  alph1&2 define range of wrong-case chars, 11-36 or 37-62 or empty. */
324     div=64L*64L*64L*64L;
325     w=word;
326     for (i=1; i<=TOKLEN; i++) 
327     {
328         if (w <= 0 && *state == 0 && labs(casemake) <= 1)
329             return;
330         byte=w/div;
331         w=(w-byte*div)*64;
332         if (!(*state != 0 || byte != PERCENT)) {
333             *state=PERCENT;
334             continue;
335         }
336         SHFTXT(LNPOSN,1);
337         *state=*state+byte;
338         if (*state < alph2 && *state >= alph1)*state=*state-26*casemake;
339         INLINE[LNPOSN]=*state;
340         LNPOSN=LNPOSN+1;
341         *state=0;
342     }
343 }
344 #define PUTTXT(WORD,STATE,CASE) fPUTTXT(WORD,&STATE,CASE)
345
346 void SHFTXT(long from, long delta) 
347 /*  Move INLINE(N) to INLINE(N+DELTA) for N=FROM,LNLENG.  Delta can be
348  *  negative.  LNLENG is updated; LNPOSN is not changed. */
349 {
350     long I, k, j;
351
352     if (!(LNLENG < from || delta == 0)) {
353         for (I=from; I<=LNLENG; I++) {
354             k=I;
355             if (delta > 0)
356                 k=from+LNLENG-I;
357             j=k+delta;
358             INLINE[j]=INLINE[k];
359         }
360     }
361     LNLENG=LNLENG+delta;
362 }
363
364 void TYPE0(void)
365 /*  Type a blank line.  This procedure is provided as a convenience for callers
366  *  who otherwise have no use for MAPCOM. */
367 {
368     long temp;
369
370     temp=LNLENG;
371     LNLENG=0;
372     TYPE();
373     LNLENG=temp;
374     return;
375 }
376
377 /*  Suspend/resume I/O routines (SAVWDS, SAVARR, SAVWRD) */
378
379 void fSAVWDS(long *W1, long *W2, long *W3, long *W4,
380              long *W5, long *W6, long *W7)
381 /* Write or read 7 variables.  See SAVWRD. */
382 {
383     SAVWRD(0,(*W1));
384     SAVWRD(0,(*W2));
385     SAVWRD(0,(*W3));
386     SAVWRD(0,(*W4));
387     SAVWRD(0,(*W5));
388     SAVWRD(0,(*W6));
389     SAVWRD(0,(*W7));
390 }
391
392 void fSAVARR(long arr[], long n)
393 /* Write or read an array of n words.  See SAVWRD. */
394 {
395     long i;
396
397     for (i=1; i<=n; i++) {
398         SAVWRD(0,arr[i]);
399     }
400     return;
401 }
402
403 void fSAVWRD(long op, long *pword) 
404 /*  If OP<0, start writing a file, using word to initialise encryption; save
405  *  word in the file.  If OP>0, start reading a file; read the file to find
406  *  the value with which to decrypt the rest.  In either case, if a file is
407  *  already open, finish writing/reading it and don't start a new one.  If OP=0,
408  *  read/write a single word.  Words are buffered in case that makes for more
409  *  efficient disk use.  We also compute a simple checksum to catch elementary
410  *  poking within the saved file.  When we finish reading/writing the file,
411  *  we store zero into *PWORD if there's no checksum error, else nonzero. */
412 {
413     static long buf[250], cksum = 0, h1, hash = 0, n = 0, state = 0;
414
415     if (op != 0)
416     {
417         long ifvar = state; 
418         switch (ifvar<0 ? -1 : (ifvar>0 ? 1 : 0)) 
419         { 
420         case -1:
421         case 1:
422             if (n == 250)SAVEIO(1,state > 0,buf);
423             n=MOD(n,250)+1;
424             if (state <= 0) {
425                 n--; buf[n]=cksum; n++;
426                 SAVEIO(1,false,buf);
427             }
428             n--; *pword=buf[n]-cksum; n++;
429             SAVEIO(-1,state > 0,buf);
430             state=0;
431             break;
432         case 0: /* FIXME: Huh? should be impossible */
433             state=op;
434             SAVEIO(0,state > 0,buf);
435             n=1;
436             if (state <= 0) {
437                 hash=MOD(*pword,1048576L);
438                 buf[0]=1234L*5678L-hash;
439             }
440             SAVEIO(1,true,buf);
441             hash=MOD(1234L*5678L-buf[0],1048576L);
442             cksum=buf[0];
443             return;
444         }
445     }
446     if (state == 0)
447         return;
448     if (n == 250)
449         SAVEIO(1,state > 0,buf);
450     n=MOD(n,250)+1;
451     h1=MOD(hash*1093L+221573L,1048576L);
452     hash=MOD(h1*1093L+221573L,1048576L);
453     h1=MOD(h1,1234)*765432+MOD(hash,123);
454     n--;
455     if (state > 0)
456         *pword=buf[n]+h1;
457     buf[n]=*pword-h1;
458     n++;
459     cksum=MOD(cksum*13+*pword,1000000000L);
460 }
461
462 /*  Data structure  routines */
463
464 long VOCAB(long id, long init) 
465 /*  Look up ID in the vocabulary (ATAB) and return its "definition" (KTAB), or
466  *  -1 if not found.  If INIT is positive, this is an initialisation call setting
467  *  up a keyword variable, and not finding it constitutes a bug.  It also means
468  *  that only KTAB values which taken over 1000 equal INIT may be considered.
469  *  (Thus "STEPS", which is a motion verb as well as an object, may be located
470  *  as an object.)  And it also means the KTAB value is taken modulo 1000. */
471 {
472     long i, lexeme;
473
474     for (i=1; i<=TABSIZ; i++) {
475         if (KTAB[i] == -1) {
476             lexeme= -1;
477             if (init < 0)
478                 return(lexeme);
479             BUG(5);
480         }
481         if (init >= 0 && KTAB[i]/1000 != init) 
482             continue;
483         if (ATAB[i] == id) {
484             lexeme=KTAB[i];
485             if (init >= 0)
486                 lexeme=MOD(lexeme,1000);
487             return(lexeme);
488         }
489     }
490     BUG(21);
491 }
492
493 void DSTROY(long object)
494 /*  Permanently eliminate "object" by moving to a non-existent location. */
495 {
496     MOVE(object,0);
497 }
498
499 void JUGGLE(long object)
500 /*  Juggle an object by picking it up and putting it down again, the purpose
501  *  being to get the object to the front of the chain of things at its loc. */
502 {
503     long i, j;
504
505     i=game.place[object];
506     j=game.fixed[object];
507     MOVE(object,i);
508     MOVE(object+NOBJECTS,j);
509 }
510
511 void MOVE(long object, long where)
512 /*  Place any object anywhere by picking it up and dropping it.  May
513  *  already be toting, in which case the carry is a no-op.  Mustn't
514  *  pick up objects which are not at any loc, since carry wants to
515  *  remove objects from game.atloc chains. */
516 {
517     long from;
518
519     if (object > NOBJECTS) 
520         from=game.fixed[object-NOBJECTS];
521     else
522         from=game.place[object];
523     if (from > 0 && from <= 300)
524         CARRY(object,from);
525     DROP(object,where);
526 }
527
528 long PUT(long object, long where, long pval)
529 /*  PUT is the same as MOVE, except it returns a value used to set up the
530  *  negated game.prop values for the repository objects. */
531 {
532     MOVE(object,where);
533     return (-1)-pval;;
534 }
535
536 void CARRY(long object, long where) 
537 /*  Start toting an object, removing it from the list of things at its former
538  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
539  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
540 {
541     long temp;
542
543     if (object <= NOBJECTS) {
544         if (game.place[object] == -1)
545             return;
546         game.place[object]= -1;
547         game.holdng=game.holdng+1;
548     }
549     if (game.atloc[where] == object) {
550         game.atloc[where]=game.link[object];
551         return;
552     }
553     temp=game.atloc[where];
554     while (game.link[temp] != object) {
555         temp=game.link[temp];
556     }
557     game.link[temp]=game.link[object];
558 }
559
560 void DROP(long object, long where)
561 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
562  *  game.holdng if the object was being toted. */
563 {
564     if (object > NOBJECTS)
565         game.fixed[object-NOBJECTS] = where;
566     else
567     {
568         if (game.place[object] == -1)
569             --game.holdng;
570         game.place[object] = where;
571     }
572     if (where <= 0)
573         return;
574     game.link[object] = game.atloc[where];
575     game.atloc[where] = object;
576 }
577
578 long ATDWRF(long where)
579 /*  Return the index of first dwarf at the given location, zero if no dwarf is
580  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
581  *  the pirate (6th dwarf). */
582 {
583     long at, i;
584
585     at =0;
586     if (game.dflag < 2)
587         return(at);
588     at = -1;
589     for (i=1; i<=NDWARVES-1; i++) {
590         if (game.dloc[i] == where)
591             return i;
592         if (game.dloc[i] != 0)
593             at=0;
594     }
595     return(at);
596 }
597
598 /*  Utility routines (SETBIT, TSTBIT, set_seed, get_next_lcg_value,
599  *  randrange, RNDVOC, BUG) */
600
601 long SETBIT(long bit)
602 /*  Returns 2**bit for use in constructing bit-masks. */
603 {
604     return(2 << bit);
605 }
606
607 bool TSTBIT(long mask, int bit)
608 /*  Returns true if the specified bit is set in the mask. */
609 {
610     return (mask & (1 << bit)) != 0;
611 }
612
613 void set_seed(long seedval)
614 /* Set the LCG seed */
615 {
616     lcgstate.x = (unsigned long) seedval % lcgstate.m;
617 }
618
619 unsigned long get_next_lcg_value(void)
620 /* Return the LCG's current value, and then iterate it. */
621 {
622     unsigned long old_x = lcgstate.x;
623     lcgstate.x = (lcgstate.a * lcgstate.x + lcgstate.c) % lcgstate.m;
624     return old_x;
625 }
626
627 long randrange(long range)
628 /* Return a random integer from [0, range). */
629 {
630     return range * get_next_lcg_value() / lcgstate.m;
631 }
632
633 long RNDVOC(long second, long force)
634 /*  Searches the vocabulary ATAB for a word whose second character is
635  *  char, and changes that word such that each of the other four
636  *  characters is a random letter.  If force is non-zero, it is used
637  *  as the new word.  Returns the new word. */
638 {
639     long rnd = force;
640
641     if (rnd == 0) {
642         for (int i = 1; i <= 5; i++) {
643             long j = 11 + randrange(26);
644             if (i == 2)
645                 j = second;
646             rnd = rnd * 64 + j;
647         }
648     }
649
650     long div = 64L * 64L * 64L;
651     for (int i = 1; i <= TABSIZ; i++) {
652         if (MOD(ATAB[i]/div, 64L) == second)
653         {
654             ATAB[i] = rnd;
655             break;
656         }
657     }
658
659     return rnd;
660 }
661
662 void BUG(long num)
663 /*  The following conditions are currently considered fatal bugs.  Numbers < 20
664  *  are detected while reading the database; the others occur at "run time".
665  *      0       Message line > 70 characters
666  *      1       Null line in message
667  *      2       Too many words of messages
668  *      3       Too many travel options
669  *      4       Too many vocabulary words
670  *      5       Required vocabulary word not found
671  *      6       Too many RTEXT messages
672  *      7       Too many hints
673  *      8       Location has cond bit being set twice
674  *      9       Invalid section number in database
675  *      10      Too many locations
676  *      11      Too many class or turn messages
677  *      20      Special travel (500>L>300) exceeds goto list
678  *      21      Ran off end of vocabulary table
679  *      22      Vocabulary type (N/1000) not between 0 and 3
680  *      23      Intransitive action verb exceeds goto list
681  *      24      Transitive action verb exceeds goto list
682  *      25      Conditional travel entry with no alternative
683  *      26      Location has no travel entries
684  *      27      Hint number exceeds goto list
685  *      28      Invalid month returned by date function
686  *      29      Too many parameters given to SETPRM */
687 {
688
689     printf("Fatal error %ld.  See source code for interpretation.\n", num);
690     exit(0);
691 }
692
693 /*  Machine dependent routines (MAPLIN, TYPE, MPINIT, SAVEIO) */
694
695 void MAPLIN(FILE *fp)
696 {
697     long i, val;
698
699     /*  Read a line of input, from the specified input source,
700      *  translate the chars to integers in the range 0-126 and store
701      *  them in the common array "INLINE".  Integer values are as follows:
702      *     0   = space [ASCII CODE 40 octal, 32 decimal]
703      *    1-2  = !" [ASCII 41-42 octal, 33-34 decimal]
704      *    3-10 = '()*+,-. [ASCII 47-56 octal, 39-46 decimal]
705      *   11-36 = upper-case letters
706      *   37-62 = lower-case letters
707      *    63   = percent (%) [ASCII 45 octal, 37 decimal]
708      *   64-73 = digits, 0 through 9
709      *  Remaining characters can be translated any way that is convenient;
710      *  The "TYPE" routine below is used to map them back to characters when
711      *  necessary.  The above mappings are required so that certain special
712      *  characters are known to fit in 6 bits and/or can be easily spotted.
713      *  Array elements beyond the end of the line should be filled with 0,
714      *  and LNLENG should be set to the index of the last character.
715      *
716      *  If the data file uses a character other than space (e.g., tab) to
717      *  separate numbers, that character should also translate to 0.
718      *
719      *  This procedure may use the map1,map2 arrays to maintain static data for
720      *  the mapping.  MAP2(1) is set to 0 when the program starts
721      *  and is not changed thereafter unless the routines on this page choose
722      *  to do so. */
723
724     if (MAP2[1] == 0)
725         MPINIT();
726
727     if (!oldstyle && fp == stdin)
728         fputs("> ", stdout);
729     do {
730         IGNORE(fgets(rawbuf,sizeof(rawbuf)-1,fp));
731     } while
732             (!feof(fp) && rawbuf[0] == '#');
733     if (feof(fp)) {
734         if (logfp && fp == stdin)
735             fclose(logfp);
736     } else {
737         if (logfp && fp == stdin)
738             IGNORE(fputs(rawbuf, logfp));
739         else if (!isatty(0))
740             IGNORE(fputs(rawbuf, stdout));
741         strcpy(INLINE+1, rawbuf);
742         LNLENG=0;
743         for (i=1; i<=(long)sizeof(INLINE) && INLINE[i]!=0; i++) {
744             val=INLINE[i]+1;
745             INLINE[i]=MAP1[val];
746             if (INLINE[i] != 0)
747                 LNLENG=i;
748         }
749         LNPOSN=1;
750     }
751 }
752
753 void TYPE(void)
754 /*  Type the first "LNLENG" characters stored in inline, mapping them
755  *  from integers to text per the rules described above.  INLINE
756  *  may be changed by this routine. */
757 {
758     long i;
759
760     if (LNLENG == 0) {
761         printf("\n");
762         return;
763     }
764
765     if (MAP2[1] == 0)
766         MPINIT();
767     for (i=1; i<=LNLENG; i++) {
768         INLINE[i]=MAP2[INLINE[i]+1];
769     }
770     INLINE[LNLENG+1]=0;
771     printf("%s\n", INLINE+1);
772     return;
773 }
774
775 void MPINIT(void) 
776 {
777     long first, i, j, last, val;
778     static long RUNS[7][2] = { {32,34}, {39,46}, {65,90}, {97,122}, 
779                                {37,37}, {48,57}, {0,126} };
780     for (i=1; i<=128; i++) {
781         MAP1[i]= -1;
782     }
783     val=0;
784     for (i=0; i<7; i++) {
785         first =RUNS[i][0];
786         last = RUNS[i][1];
787         for (j=first; j<=last; j++) {
788             j++; 
789             if (MAP1[j] < 0) {
790                 MAP1[j]=val;
791                 ++val;
792             }       
793             j--;
794         }
795     }
796     MAP1[128]=MAP1[10];
797     /*  For this version, tab (9) maps to space (32), so del (127)
798      *  uses tab's value */
799     MAP1[10]=MAP1[33];
800     MAP1[11]=MAP1[33];
801
802     for (i=0; i<=126; i++) {
803         i++; val=MAP1[i]+1; i--;
804         MAP2[val] = i*('B'-'A');
805         if (i >= 64)
806             MAP2[val]=(i-64)*('B'-'A')+'@';
807     }
808 }
809
810 void fSAVEIO(long op, long in, long arr[]) 
811 /*  If OP=0, ask for a file name and open a file.  (If IN=true, the file is for
812  *  input, else output.)  If OP>0, read/write ARR from/into the previously-opened
813  *  file.  (ARR is a 250-integer array.)  If OP<0, finish reading/writing the
814  *  file.  (Finishing writing can be a no-op if a "stop" statement does it
815  *  automatically.  Finishing reading can be a no-op as long as a subsequent
816  *  SAVEIO(0,false,X) will still work.) */
817 {
818     static FILE *fp = NULL; 
819     char name[50];
820
821     switch (op < 0 ? -1 : (op > 0 ? 1 : 0)) 
822     { 
823     case -1:
824         fclose(fp);
825         break;
826     case 0:
827         while (fp == NULL) {
828             printf("\nFile name: ");
829             IGNORE(fgets(name, sizeof(name), stdin));
830             fp = fopen(name,(in ? READ_MODE : WRITE_MODE));
831             if (fp == NULL)
832                 printf("Can't open file %s, try again.\n", name); 
833         }
834         break;
835     case 1: 
836         if (in)
837             IGNORE(fread(arr,sizeof(long),250,fp));
838         else
839             IGNORE(fwrite(arr,sizeof(long),250,fp));
840         break;
841     }
842 }
843
844 void DATIME(long* d, long* t)
845 {
846     struct timeval tv;
847     gettimeofday(&tv, NULL);
848     *d = (long) tv.tv_sec;
849     *t = (long) tv.tv_usec;
850 }
851
852 long MOD(long n, long m) 
853 {
854     return(n%m);
855 }