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