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