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