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