Jettison MAKEWD(), GETTXT(), vocab(), GETIN(), and the old db compiler.
[open-adventure.git] / misc.c
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <stdarg.h>
6 #include <sys/time.h>
7 #include <ctype.h>
8
9 #include "advent.h"
10 #include "linenoise/linenoise.h"
11 #include "newdb.h"
12
13 char* xstrdup(const char* s)
14 {
15   char* ptr = strdup(s);
16   if (ptr == NULL) {
17     // LCOV_EXCL_START
18     // exclude from coverage analysis because we can't simulate an out of memory error in testing
19     fprintf(stderr, "Out of memory!\n");
20     exit(EXIT_FAILURE);
21   }
22   return(ptr);
23 }
24
25 void* xmalloc(size_t size)
26 {
27     void* ptr = malloc(size);
28     if (ptr == NULL) {
29         // LCOV_EXCL_START
30         // exclude from coverage analysis because we can't simulate an out of memory error in testing
31         fprintf(stderr, "Out of memory!\n");
32         exit(EXIT_FAILURE);
33         // LCOV_EXCL_STOP 
34     }
35     return (ptr);
36 }
37
38 void packed_to_token(long packed, char token[6])
39 {
40     // Unpack and map back to ASCII.
41     for (int i = 0; i < 5; ++i) {
42       char advent = (packed >> i * 6) & 63;
43         token[i] = new_advent_to_ascii[(int) advent];
44     }
45
46     // Ensure the last character is \0.
47     token[5] = '\0';
48
49     // Replace trailing whitespace with \0.
50     for (int i = 4; i >= 0; --i) {
51         if (token[i] == ' ' || token[i] == '\t')
52             token[i] = '\0';
53         else
54             break;
55     }
56 }
57
58 long token_to_packed(const char token[6])
59 {
60   size_t t_len = strlen(token);
61   long packed = 0;
62   for (size_t i = 0; i < t_len; ++i)
63     {
64       char mapped = new_ascii_to_advent[(int) token[i]];
65       packed |= (mapped << (6 * i));
66     }
67   return(packed);
68 }
69
70 void tokenize(char* raw, long tokens[4])
71 {
72   // set each token to 0
73   for (int i = 0; i < 4; ++i)
74     tokens[i] = 0;
75   
76   // grab the first two words
77   char* words[2];
78   words[0] = (char*) xmalloc(strlen(raw));
79   words[1] = (char*) xmalloc(strlen(raw));
80   int word_count = sscanf(raw, "%s%s", words[0], words[1]);
81
82   // make space for substrings and zero it out
83   char chunk_data[][6] = {
84     {"\0\0\0\0\0"},
85     {"\0\0\0\0\0"},
86     {"\0\0\0\0\0"},
87     {"\0\0\0\0\0"},
88   };
89
90   // break the words into up to 4 5-char substrings
91   sscanf(words[0], "%5s%5s", chunk_data[0], chunk_data[1]);
92   if (word_count == 2)
93     sscanf(words[1], "%5s%5s", chunk_data[2], chunk_data[3]);
94   free(words[0]);
95   free(words[1]);
96
97   // uppercase all the substrings
98   for (int i = 0; i < 4; ++i)
99     for (unsigned int j = 0; j < strlen(chunk_data[i]); ++j)
100       chunk_data[i][j] = (char) toupper(chunk_data[i][j]);
101
102   // pack the substrings
103   for (int i = 0; i < 4; ++i)
104     tokens[i] = token_to_packed(chunk_data[i]);
105 }
106
107 /* Hide the fact that wods are corrently packed longs */
108
109 bool wordeq(token_t a, token_t b)
110 {
111     return a == b;
112 }
113
114 bool wordempty(token_t a)
115 {
116     return a == 0;
117 }
118
119 void wordclear(token_t *v)
120 {
121     *v = 0;
122 }
123
124 /*  I/O routines (speak, pspeak, rspeak, GETIN, YES) */
125
126 void vspeak(const char* msg, va_list ap)
127 {
128     // Do nothing if we got a null pointer.
129     if (msg == NULL)
130         return;
131
132     // Do nothing if we got an empty string.
133     if (strlen(msg) == 0)
134         return;
135
136     // Print a newline if the global game.blklin says to.
137     if (game.blklin == true)
138         printf("\n");
139
140     int msglen = strlen(msg);
141
142     // Rendered string
143     ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
144     char* rendered = xmalloc(size);
145     char* renderp = rendered;
146
147     // Handle format specifiers (including the custom %C, %L, %S) by adjusting the parameter accordingly, and replacing the specifier with %s.
148     long previous_arg = 0;
149     for (int i = 0; i < msglen; i++) {
150         if (msg[i] != '%') {
151             *renderp++ = msg[i];
152             size--;
153         } else {
154             long arg = va_arg(ap, long);
155             if (arg == -1)
156               arg = 0;
157             i++;
158             // Integer specifier. In order to accommodate the fact that PARMS can have both legitimate integers *and* packed tokens, stringify everything. Future work may eliminate the need for this.
159             if (msg[i] == 'd') {
160                 int ret = snprintf(renderp, size, "%ld", arg);
161                 if (ret < size) {
162                     renderp += ret;
163                     size -= ret;
164                 }
165             }
166
167             // Unmodified string specifier.
168             if (msg[i] == 's') {
169                 packed_to_token(arg, renderp); /* unpack directly to destination */
170                 size_t len = strlen(renderp);
171                 renderp += len;
172                 size -= len;
173             }
174
175             // Singular/plural specifier.
176             if (msg[i] == 'S') {
177                 if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
178                     *renderp++ = 's';
179                     size--;
180                 }
181             }
182
183             // All-lowercase specifier.
184             if (msg[i] == 'L' || msg[i] == 'C') {
185                 packed_to_token(arg, renderp); /* unpack directly to destination */
186                 int len = strlen(renderp);
187                 for (int j = 0; j < len; ++j) {
188                     renderp[j] = tolower(renderp[j]);
189                 }
190                 if (msg[i] == 'C') // First char uppercase, rest lowercase.
191                     renderp[0] = toupper(renderp[0]);
192                 renderp += len;
193                 size -= len;
194             }
195
196             previous_arg = arg;
197         }
198     }
199     *renderp = 0;
200
201     // Print the message.
202     printf("%s\n", rendered);
203
204     free(rendered);
205 }
206
207 void speak(const char* msg, ...)
208 {
209     va_list ap;
210     va_start(ap, msg);
211     vspeak(msg, ap);
212     va_end(ap);
213 }
214
215 void pspeak(vocab_t msg, enum speaktype mode, int skip, ...)
216 /* Find the skip+1st message from msg and print it.  Modes are:
217  * feel = for inventory, what you can touch
218  * look = the long description for the state the object is in
219  * listen = the sound for the state the object is in
220  * study = text on the object. */
221 {
222     va_list ap;
223     va_start(ap, skip);
224     switch (mode) {
225     case touch:
226         vspeak(objects[msg].inventory, ap);
227         break;
228     case look: 
229         vspeak(objects[msg].descriptions[skip], ap);
230         break;
231     case hear:
232         vspeak(objects[msg].sounds[skip], ap);
233         break;
234     case study:
235         vspeak(objects[msg].texts[skip], ap);
236         break;
237     case change:
238         vspeak(objects[msg].changes[skip], ap);
239         break;
240     }
241     va_end(ap);
242 }
243
244 void rspeak(vocab_t i, ...)
245 /* Print the i-th "random" message (section 6 of database). */
246 {
247     va_list ap;
248     va_start(ap, i);
249     vspeak(arbitrary_messages[i], ap);
250     va_end(ap);
251 }
252
253 void echo_input(FILE* destination, char* input_prompt, char* input)
254 {
255     size_t len = strlen(input_prompt) + strlen(input) + 1;
256     char* prompt_and_input = (char*) xmalloc(len);
257     strcpy(prompt_and_input, input_prompt);
258     strcat(prompt_and_input, input);
259     fprintf(destination, "%s\n", prompt_and_input);
260     free(prompt_and_input);
261 }
262
263 int word_count(char* s)
264 {
265   char* copy = xstrdup(s);
266   char delims[] = " \t";
267   int count = 0;
268   char* word;
269
270   word = strtok(copy, delims);
271   while(word != NULL)
272     {
273       word = strtok(NULL, delims);
274       ++count;
275     }
276   free(copy);
277   return(count);
278 }
279
280 char* get_input()
281 {
282     // Set up the prompt
283     char input_prompt[] = "> ";
284     if (!prompt)
285         input_prompt[0] = '\0';
286
287     // Print a blank line if game.blklin tells us to.
288     if (game.blklin == true)
289         printf("\n");
290
291     char* input;
292     while (true) {
293         if (editline)
294             input = linenoise(input_prompt);
295         else {
296             input = NULL;
297             size_t n = 0;
298             if (isatty(0))
299             // LCOV_EXCL_START
300             // Should be unreachable in tests, as they will use a non-interactive shell.
301                 printf("%s", input_prompt);
302             // LCOV_EXCL_STOP 
303             ssize_t numread = getline(&input, &n, stdin);
304             if (numread == -1) // Got EOF; return with it.
305               return(NULL);
306         }
307
308         if (input == NULL) // Got EOF; return with it.
309             return(input);
310         else if (input[0] == '#') // Ignore comments.
311             continue;
312         else // We have a 'normal' line; leave the loop.
313             break;
314     }
315
316     // Strip trailing newlines from the input
317     input[strcspn(input, "\n")] = 0;
318
319     linenoiseHistoryAdd(input);
320
321     if (!isatty(0))
322         echo_input(stdout, input_prompt, input);
323
324     if (logfp)
325         echo_input(logfp, input_prompt, input);
326
327     return (input);
328 }
329
330 bool silent_yes()
331 {
332   char* reply;
333   bool outcome;
334   
335   for (;;) {
336     reply = get_input();
337     if (reply == NULL) {
338       // LCOV_EXCL_START
339       // Should be unreachable. Reply should never be NULL
340       linenoiseFree(reply);
341       exit(EXIT_SUCCESS);
342       // LCOV_EXCL_STOP 
343     }
344
345     char* firstword = (char*) xmalloc(strlen(reply)+1);
346     sscanf(reply, "%s", firstword);
347
348     for (int i = 0; i < (int)strlen(firstword); ++i)
349       firstword[i] = tolower(firstword[i]);
350
351     int yes = strncmp("yes", firstword, sizeof("yes") - 1);
352     int y = strncmp("y", firstword, sizeof("y") - 1);
353     int no = strncmp("no", firstword, sizeof("no") - 1);
354     int n = strncmp("n", firstword, sizeof("n") - 1);
355
356     free(firstword);
357
358     if (yes == 0 || y == 0) {
359       outcome = true;
360       break;
361     } else if (no == 0 || n == 0) {
362       outcome = false;
363       break;
364     } else
365       rspeak(PLEASE_ANSWER);
366   }
367   linenoiseFree(reply);
368   return (outcome);
369 }
370
371
372 bool yes(const char* question, const char* yes_response, const char* no_response)
373 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
374  *  if no, print Z and return false. */
375 {
376     char* reply;
377     bool outcome;
378
379     for (;;) {
380         speak(question);
381
382         reply = get_input();
383         if (reply == NULL) {
384             // LCOV_EXCL_START
385             // Should be unreachable. Reply should never be NULL
386             linenoiseFree(reply);
387             exit(EXIT_SUCCESS);
388             // LCOV_EXCL_STOP 
389         }
390
391         char* firstword = (char*) xmalloc(strlen(reply)+1);
392         sscanf(reply, "%s", firstword);
393
394         for (int i = 0; i < (int)strlen(firstword); ++i)
395             firstword[i] = tolower(firstword[i]);
396
397         int yes = strncmp("yes", firstword, sizeof("yes") - 1);
398         int y = strncmp("y", firstword, sizeof("y") - 1);
399         int no = strncmp("no", firstword, sizeof("no") - 1);
400         int n = strncmp("n", firstword, sizeof("n") - 1);
401
402         free(firstword);
403
404         if (yes == 0 || y == 0) {
405             speak(yes_response);
406             outcome = true;
407             break;
408         } else if (no == 0 || n == 0) {
409             speak(no_response);
410             outcome = false;
411             break;
412         } else
413             rspeak(PLEASE_ANSWER);
414     }
415     linenoiseFree(reply);
416     return (outcome);
417 }
418
419 /*  Data structure  routines */
420
421 int get_motion_vocab_id(const char* word)
422 // Return the first motion number that has 'word' as one of its words.
423 {
424   for (int i = 0; i < NMOTIONS; ++i)
425     {
426       for (int j = 0; j < motions[i].words.n; ++j)
427         {
428           if (strcasecmp(word, motions[i].words.strs[j]) == 0)
429             return(i);
430         }
431     }
432   // If execution reaches here, we didn't find the word.
433   return(WORD_NOT_FOUND);
434 }
435
436 int get_object_vocab_id(const char* word)
437 // Return the first object number that has 'word' as one of its words.
438 {
439   for (int i = 0; i < NOBJECTS + 1; ++i) // FIXME: the + 1 should go when 1-indexing for objects is removed
440     {
441       for (int j = 0; j < objects[i].words.n; ++j)
442         {
443           if (strcasecmp(word, objects[i].words.strs[j]) == 0)
444             return(i);
445         }
446     }
447   // If execution reaches here, we didn't find the word.
448   return(WORD_NOT_FOUND);
449 }
450
451 int get_action_vocab_id(const char* word)
452 // Return the first motion number that has 'word' as one of its words.
453 {
454   for (int i = 0; i < NACTIONS; ++i)
455     {
456       for (int j = 0; j < actions[i].words.n; ++j)
457         {
458           if (strcasecmp(word, actions[i].words.strs[j]) == 0)
459             return(i);
460         }
461     }
462   // If execution reaches here, we didn't find the word.
463   return(WORD_NOT_FOUND);
464 }
465
466 int get_special_vocab_id(const char* word)
467 // Return the first special number that has 'word' as one of its words.
468 {
469   for (int i = 0; i < NSPECIALS; ++i)
470     {
471       for (int j = 0; j < specials[i].words.n; ++j)
472         {
473           if (strcasecmp(word, specials[i].words.strs[j]) == 0)
474             return(i);
475         }
476     }
477   // If execution reaches here, we didn't find the word.
478   return(WORD_NOT_FOUND);
479 }
480
481 long get_vocab_id(const char* word)
482 // Search the vocab categories in order for the supplied word.
483 {
484   long ref_num;
485   
486   ref_num = get_motion_vocab_id(word);
487   if (ref_num != WORD_NOT_FOUND)
488     return(ref_num + 0); // FIXME: replace with a proper hash
489
490   ref_num = get_object_vocab_id(word);
491   if (ref_num != WORD_NOT_FOUND)
492     return(ref_num + 1000); // FIXME: replace with a proper hash
493
494   ref_num = get_action_vocab_id(word);
495   if (ref_num != WORD_NOT_FOUND)
496     return(ref_num + 2000); // FIXME: replace with a proper hash
497
498   ref_num = get_special_vocab_id(word);
499   if (ref_num != WORD_NOT_FOUND)
500     return(ref_num + 3000); // FIXME: replace with a proper hash
501
502   // Check for the reservoir magic word.
503   if (strcasecmp(word, game.zzword) == 0)
504     return(PART + 2000); // FIXME: replace with a proper hash
505
506   return(WORD_NOT_FOUND);
507 }
508
509 void juggle(long object)
510 /*  Juggle an object by picking it up and putting it down again, the purpose
511  *  being to get the object to the front of the chain of things at its loc. */
512 {
513     long i, j;
514
515     i = game.place[object];
516     j = game.fixed[object];
517     move(object, i);
518     move(object + NOBJECTS, j);
519 }
520
521 void move(long object, long where)
522 /*  Place any object anywhere by picking it up and dropping it.  May
523  *  already be toting, in which case the carry is a no-op.  Mustn't
524  *  pick up objects which are not at any loc, since carry wants to
525  *  remove objects from game.atloc chains. */
526 {
527     long from;
528
529     if (object > NOBJECTS)
530         from = game.fixed[object - NOBJECTS];
531     else
532         from = game.place[object];
533     if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
534         carry(object, from);
535     drop(object, where);
536 }
537
538 long put(long object, long where, long pval)
539 /*  PUT is the same as MOVE, except it returns a value used to set up the
540  *  negated game.prop values for the repository objects. */
541 {
542     move(object, where);
543     return (-1) - pval;;
544 }
545
546 void carry(long object, long where)
547 /*  Start toting an object, removing it from the list of things at its former
548  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
549  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
550 {
551     long temp;
552
553     if (object <= NOBJECTS) {
554         if (game.place[object] == CARRIED)
555             return;
556         game.place[object] = CARRIED;
557         ++game.holdng;
558     }
559     if (game.atloc[where] == object) {
560         game.atloc[where] = game.link[object];
561         return;
562     }
563     temp = game.atloc[where];
564     while (game.link[temp] != object) {
565         temp = game.link[temp];
566     }
567     game.link[temp] = game.link[object];
568 }
569
570 void drop(long object, long where)
571 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
572  *  game.holdng if the object was being toted. */
573 {
574     if (object > NOBJECTS)
575         game.fixed[object - NOBJECTS] = where;
576     else {
577         if (game.place[object] == CARRIED)
578             --game.holdng;
579         game.place[object] = where;
580     }
581     if (where <= 0)
582         return;
583     game.link[object] = game.atloc[where];
584     game.atloc[where] = object;
585 }
586
587 long atdwrf(long where)
588 /*  Return the index of first dwarf at the given location, zero if no dwarf is
589  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
590  *  the pirate (6th dwarf). */
591 {
592     long at;
593
594     at = 0;
595     if (game.dflag < 2)
596         return (at);
597     at = -1;
598     for (long i = 1; i <= NDWARVES - 1; i++) {
599         if (game.dloc[i] == where)
600             return i;
601         if (game.dloc[i] != 0)
602             at = 0;
603     }
604     return (at);
605 }
606
607 /*  Utility routines (SETBIT, TSTBIT, set_seed, get_next_lcg_value,
608  *  randrange, RNDVOC) */
609
610 long setbit(long bit)
611 /*  Returns 2**bit for use in constructing bit-masks. */
612 {
613     return (1L << bit);
614 }
615
616 bool tstbit(long mask, int bit)
617 /*  Returns true if the specified bit is set in the mask. */
618 {
619     return (mask & (1 << bit)) != 0;
620 }
621
622 void set_seed(long seedval)
623 /* Set the LCG seed */
624 {
625     game.lcg_x = (unsigned long) seedval % game.lcg_m;
626 }
627
628 unsigned long get_next_lcg_value(void)
629 /* Return the LCG's current value, and then iterate it. */
630 {
631     unsigned long old_x = game.lcg_x;
632     game.lcg_x = (game.lcg_a * game.lcg_x + game.lcg_c) % game.lcg_m;
633     return old_x;
634 }
635
636 long randrange(long range)
637 /* Return a random integer from [0, range). */
638 {
639     return range * get_next_lcg_value() / game.lcg_m;
640 }
641
642 void make_zzword(char zzword[6])
643 {
644   for (int i = 0; i < 5; ++i)
645     {
646       zzword[i] = 'A' + randrange(26);
647     }
648   zzword[1] = '\''; // force second char to apostrophe
649   zzword[5] = '\0';
650 }
651
652 /*  Machine dependent routines (MAPLIN, SAVEIO) */
653
654 bool MAPLIN(FILE *fp)
655 {
656     bool eof;
657
658     /* Read a line of input, from the specified input source.
659      * This logic is complicated partly because it has to serve
660      * several cases with different requirements and partly because
661      * of a quirk in linenoise().
662      *
663      * The quirk shows up when you paste a test log from the clipboard
664      * to the program's command prompt.  While fgets (as expected)
665      * consumes it a line at a time, linenoise() returns the first
666      * line and discards the rest.  Thus, there needs to be an
667      * editline (-s) option to fall back to fgets while still
668      * prompting.  Note that linenoise does behave properly when
669      * fed redirected stdin.
670      *
671      * The logging is a bit of a mess because there are two distinct cases
672      * in which you want to echo commands.  One is when shipping them to
673      * a log under the -l option, in which case you want to suppress
674      * prompt generation (so test logs are unadorned command sequences).
675      * On the other hand, if you redirected stdin and are feeding the program
676      * a logfile, you *do* want prompt generation - it makes checkfiles
677      * easier to read when the commands are marked by a preceding prompt.
678      */
679     do {
680         if (!editline) {
681             if (prompt)
682                 fputs("> ", stdout);
683             IGNORE(fgets(rawbuf, sizeof(rawbuf) - 1, fp));
684             eof = (feof(fp));
685         } else {
686             char *cp = linenoise("> ");
687             eof = (cp == NULL);
688             if (!eof) {
689                 strncpy(rawbuf, cp, sizeof(rawbuf) - 1);
690                 linenoiseHistoryAdd(rawbuf);
691                 strncat(rawbuf, "\n", sizeof(rawbuf) - strlen(rawbuf) - 1);
692                 linenoiseFree(cp);
693             }
694         }
695     } while
696     (!eof && rawbuf[0] == '#');
697     if (eof) {
698         if (logfp && fp == stdin)
699             fclose(logfp);
700         return false;
701     } else {
702         FILE *efp = NULL;
703         if (logfp && fp == stdin)
704             efp = logfp;
705         else if (!isatty(0))
706             efp = stdout;
707         if (efp != NULL) {
708             if (prompt && efp == stdout)
709                 fputs("> ", efp);
710             IGNORE(fputs(rawbuf, efp));
711         }
712         strcpy(INLINE + 1, rawbuf);
713         /*  translate the chars to integers in the range 0-126 and store
714          *  them in the common array "INLINE".  Integer values are as follows:
715          *     0   = space [ASCII CODE 40 octal, 32 decimal]
716          *    1-2  = !" [ASCII 41-42 octal, 33-34 decimal]
717          *    3-10 = '()*+,-. [ASCII 47-56 octal, 39-46 decimal]
718          *   11-36 = upper-case letters
719          *   37-62 = lower-case letters
720          *    63   = percent (%) [ASCII 45 octal, 37 decimal]
721          *   64-73 = digits, 0 through 9
722          *  Remaining characters can be translated any way that is convenient;
723          *  The above mappings are required so that certain special
724          *  characters are known to fit in 6 bits and/or can be easily spotted.
725          *  Array elements beyond the end of the line should be filled with 0,
726          *  and LNLENG should be set to the index of the last character.
727          *
728          *  If the data file uses a character other than space (e.g., tab) to
729          *  separate numbers, that character should also translate to 0.
730          *
731          *  This procedure may use the map1,map2 arrays to maintain
732          *  static data for he mapping.  MAP2(1) is set to 0 when the
733          *  program starts and is not changed thereafter unless the
734          *  routines in this module choose to do so. */
735         LNLENG = 0;
736         for (long i = 1; i <= (long)sizeof(INLINE) && INLINE[i] != 0; i++) {
737             long val = INLINE[i];
738             INLINE[i] = ascii_to_advent[val];
739             if (INLINE[i] != 0)
740                 LNLENG = i;
741         }
742         LNPOSN = 1;
743         return true;
744     }
745 }
746
747 void datime(long* d, long* t)
748 {
749     struct timeval tv;
750     gettimeofday(&tv, NULL);
751     *d = (long) tv.tv_sec;
752     *t = (long) tv.tv_usec;
753 }
754
755 // LCOV_EXCL_START
756 void bug(enum bugtype num, const char *error_string)
757 {
758     fprintf(stderr, "Fatal error %d, %s.\n", num, error_string);
759     exit(EXIT_FAILURE);
760 }
761 // LCOV_EXCL_STOP
762
763 /* end */