0b00992b6e0b1b2342a93258aa7a4db680bcbf41
[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 #include <editline/readline.h>
9
10 #include "advent.h"
11 #include "dungeon.h"
12
13 static void* xmalloc(size_t size)
14 {
15     void* ptr = malloc(size);
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         // LCOV_EXCL_STOP
22     }
23     return (ptr);
24 }
25
26 void packed_to_token(long packed, char token[TOKLEN+1])
27 {
28     // The advent->ascii mapping.
29     const char advent_to_ascii[] = {
30         ' ', '!', '"', '#', '$', '%', '&', '\'',
31         '(', ')', '*', '+', ',', '-', '.', '/',
32         '0', '1', '2', '3', '4', '5', '6', '7',
33         '8', '9', ':', ';', '<', '=', '>', '?',
34         '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
35         'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
36         'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
37         'X', 'Y', 'Z', '\0', '\0', '\0', '\0', '\0',
38     };
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] = 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] == ' ' ||
52             token[i] == '\t')
53             token[i] = '\0';
54         else
55             break;
56     }
57 }
58
59 long token_to_packed(const char token[])
60 {
61     const char ascii_to_advent[] = {
62         63, 63, 63, 63, 63, 63, 63, 63,
63         63, 63, 63, 63, 63, 63, 63, 63,
64         63, 63, 63, 63, 63, 63, 63, 63,
65         63, 63, 63, 63, 63, 63, 63, 63,
66
67         0, 1, 2, 3, 4, 5, 6, 7,
68         8, 9, 10, 11, 12, 13, 14, 15,
69         16, 17, 18, 19, 20, 21, 22, 23,
70         24, 25, 26, 27, 28, 29, 30, 31,
71         32, 33, 34, 35, 36, 37, 38, 39,
72         40, 41, 42, 43, 44, 45, 46, 47,
73         48, 49, 50, 51, 52, 53, 54, 55,
74         56, 57, 58, 59, 60, 61, 62, 63,
75
76         63, 63, 63, 63, 63, 63, 63, 63,
77         63, 63, 63, 63, 63, 63, 63, 63,
78         63, 63, 63, 63, 63, 63, 63, 63,
79         63, 63, 63, 63, 63, 63, 63, 63,
80     };
81
82     size_t t_len = strlen(token);
83     if (t_len > TOKLEN)
84         t_len = TOKLEN;
85     long packed = 0;
86     for (size_t i = 0; i < t_len; ++i) {
87         char mapped = ascii_to_advent[(int) toupper(token[i])];
88         packed |= (mapped << (6 * i));
89     }
90     return (packed);
91 }
92
93 void tokenize(char* raw, struct command_t *cmd)
94 {
95     memset(cmd, '\0', sizeof(struct command_t));
96
97     /* FIXME: put a bound prefix on the %s to prevent buffer overflow */
98     int word_count = sscanf(raw, "%s%s", cmd->raw1, cmd->raw2);
99
100     // pack the substrings
101     cmd->wd1  = token_to_packed(cmd->raw1);
102     cmd->wd2  = token_to_packed(cmd->raw2);
103 }
104
105 /* Hide the fact that wods are corrently packed longs */
106
107 bool wordeq(token_t a, token_t b)
108 {
109     return a == b;
110 }
111
112 bool wordempty(token_t a)
113 {
114     return a == 0;
115 }
116
117 void wordclear(token_t *v)
118 {
119     *v = 0;
120 }
121
122 /*  I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */
123
124 void vspeak(const char* msg, bool blank, va_list ap)
125 {
126     // Do nothing if we got a null pointer.
127     if (msg == NULL)
128         return;
129
130     // Do nothing if we got an empty string.
131     if (strlen(msg) == 0)
132         return;
133
134     if (blank == true)
135         printf("\n");
136
137     int msglen = strlen(msg);
138
139     // Rendered string
140     ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
141     char* rendered = xmalloc(size);
142     char* renderp = rendered;
143
144     // Handle format specifiers (including the custom %C, %L, %S) by
145     // adjusting the parameter accordingly, and replacing the
146     // specifier with %s.
147     long previous_arg = 0;
148     for (int i = 0; i < msglen; i++) {
149         if (msg[i] != '%') {
150             *renderp++ = msg[i];
151             size--;
152         } else {
153             long arg = va_arg(ap, long);
154             if (arg == -1)
155                 arg = 0; // LCOV_EXCL_LINE - don't think we can get here.
156             i++;
157             // Integer specifier. In order to accommodate the fact
158             // that PARMS can have both legitimate integers *and*
159             // packed tokens, stringify everything. Future work may
160             // eliminate the need for this.
161             if (msg[i] == 'd') {
162                 int ret = snprintf(renderp, size, "%ld", arg);
163                 if (ret < size) {
164                     renderp += ret;
165                     size -= ret;
166                 }
167             }
168
169             // Unmodified string specifier.
170             if (msg[i] == 's') {
171                 packed_to_token(arg, renderp); /* unpack directly to destination */
172                 size_t len = strlen(renderp);
173                 renderp += len;
174                 size -= len;
175             }
176
177             // Singular/plural specifier.
178             if (msg[i] == 'S') {
179                 if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
180                     *renderp++ = 's';
181                     size--;
182                 }
183             }
184
185             /* Version specifier */
186             if (msg[i] == 'V') {
187                 strcpy(renderp, VERSION);
188                 size_t len = strlen(VERSION);
189                 renderp += len;
190                 size -= len;
191             }
192
193             // All-lowercase specifier.
194             if (msg[i] == 'L' ||
195                 msg[i] == 'C') {
196                 packed_to_token(arg, renderp); /* unpack directly to destination */
197                 int len = strlen(renderp);
198                 for (int j = 0; j < len; ++j) {
199                     renderp[j] = tolower(renderp[j]);
200                 }
201                 if (msg[i] == 'C') // First char uppercase, rest lowercase.
202                     renderp[0] = toupper(renderp[0]);
203                 renderp += len;
204                 size -= len;
205             }
206
207             previous_arg = arg;
208         }
209     }
210     *renderp = 0;
211
212     // Print the message.
213     printf("%s\n", rendered);
214
215     free(rendered);
216 }
217
218 void speak(const char* msg, ...)
219 {
220     va_list ap;
221     va_start(ap, msg);
222     vspeak(msg, true, ap);
223     va_end(ap);
224 }
225
226 void sspeak(const long msg, ...)
227 {
228     va_list ap;
229     va_start(ap, msg);
230     fputc('\n', stdout);
231     vprintf(arbitrary_messages[msg], ap);
232     fputc('\n', stdout);
233     va_end(ap);
234 }
235
236 void pspeak(vocab_t msg, enum speaktype mode, int skip, bool blank, ...)
237 /* Find the skip+1st message from msg and print it.  Modes are:
238  * feel = for inventory, what you can touch
239  * look = the long description for the state the object is in
240  * listen = the sound for the state the object is in
241  * study = text on the object. */
242 {
243     va_list ap;
244     va_start(ap, blank);
245     switch (mode) {
246     case touch:
247         vspeak(objects[msg].inventory, blank, ap);
248         break;
249     case look:
250         vspeak(objects[msg].descriptions[skip], blank, ap);
251         break;
252     case hear:
253         vspeak(objects[msg].sounds[skip], blank, ap);
254         break;
255     case study:
256         vspeak(objects[msg].texts[skip], blank, ap);
257         break;
258     case change:
259         vspeak(objects[msg].changes[skip], blank, ap);
260         break;
261     }
262     va_end(ap);
263 }
264
265 void rspeak(vocab_t i, ...)
266 /* Print the i-th "random" message (section 6 of database). */
267 {
268     va_list ap;
269     va_start(ap, i);
270     vspeak(arbitrary_messages[i], true, ap);
271     va_end(ap);
272 }
273
274 void echo_input(FILE* destination, const char* input_prompt, const char* input)
275 {
276     size_t len = strlen(input_prompt) + strlen(input) + 1;
277     char* prompt_and_input = (char*) xmalloc(len);
278     strcpy(prompt_and_input, input_prompt);
279     strcat(prompt_and_input, input);
280     fprintf(destination, "%s\n", prompt_and_input);
281     free(prompt_and_input);
282 }
283
284 int word_count(char* str)
285 {
286     char delims[] = " \t";
287     int count = 0;
288     int inblanks = true;
289
290     for (char *s = str; *s; s++)
291         if (inblanks) {
292             if (strchr(delims, *s) == 0) {
293                 ++count;
294                 inblanks = false;
295             }
296         } else {
297             if (strchr(delims, *s) != 0) {
298                 inblanks = true;
299             }
300         }
301
302     return (count);
303 }
304
305 char* get_input()
306 {
307     // Set up the prompt
308     char input_prompt[] = "> ";
309     if (!settings.prompt)
310         input_prompt[0] = '\0';
311
312     // Print a blank line
313     printf("\n");
314
315     char* input;
316     while (true) {
317         input = readline(input_prompt);
318
319         if (input == NULL) // Got EOF; return with it.
320             return (input);
321         else if (input[0] == '#') { // Ignore comments.
322             free(input);
323             continue;
324         } else // We have a 'normal' line; leave the loop.
325             break;
326     }
327
328     // Strip trailing newlines from the input
329     input[strcspn(input, "\n")] = 0;
330
331     add_history(input);
332
333     if (!isatty(0))
334         echo_input(stdout, input_prompt, input);
335
336     if (settings.logfp)
337         echo_input(settings.logfp, "", input);
338
339     return (input);
340 }
341
342 bool silent_yes()
343 {
344     char* reply;
345     bool outcome;
346
347     for (;;) {
348         reply = get_input();
349         if (reply == NULL) {
350             // LCOV_EXCL_START
351             // Should be unreachable. Reply should never be NULL
352             free(reply);
353             exit(EXIT_SUCCESS);
354             // LCOV_EXCL_STOP
355         }
356
357         char* firstword = (char*) xmalloc(strlen(reply) + 1);
358         sscanf(reply, "%s", firstword);
359
360         free(reply);
361
362         for (int i = 0; i < (int)strlen(firstword); ++i)
363             firstword[i] = tolower(firstword[i]);
364
365         int yes = strncmp("yes", firstword, sizeof("yes") - 1);
366         int y = strncmp("y", firstword, sizeof("y") - 1);
367         int no = strncmp("no", firstword, sizeof("no") - 1);
368         int n = strncmp("n", firstword, sizeof("n") - 1);
369
370         free(firstword);
371
372         if (yes == 0 ||
373             y == 0) {
374             outcome = true;
375             break;
376         } else if (no == 0 ||
377                    n == 0) {
378             outcome = false;
379             break;
380         } else
381             rspeak(PLEASE_ANSWER);
382     }
383     return (outcome);
384 }
385
386
387 bool yes(const char* question, const char* yes_response, const char* no_response)
388 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
389  *  if no, print Z and return false. */
390 {
391     char* reply;
392     bool outcome;
393
394     for (;;) {
395         speak(question);
396
397         reply = get_input();
398         if (reply == NULL) {
399             // LCOV_EXCL_START
400             // Should be unreachable. Reply should never be NULL
401             free(reply);
402             exit(EXIT_SUCCESS);
403             // LCOV_EXCL_STOP
404         }
405
406         char* firstword = (char*) xmalloc(strlen(reply) + 1);
407         sscanf(reply, "%s", firstword);
408
409         free(reply);
410
411         for (int i = 0; i < (int)strlen(firstword); ++i)
412             firstword[i] = tolower(firstword[i]);
413
414         int yes = strncmp("yes", firstword, sizeof("yes") - 1);
415         int y = strncmp("y", firstword, sizeof("y") - 1);
416         int no = strncmp("no", firstword, sizeof("no") - 1);
417         int n = strncmp("n", firstword, sizeof("n") - 1);
418
419         free(firstword);
420
421         if (yes == 0 ||
422             y == 0) {
423             speak(yes_response);
424             outcome = true;
425             break;
426         } else if (no == 0 ||
427                    n == 0) {
428             speak(no_response);
429             outcome = false;
430             break;
431         } else
432             rspeak(PLEASE_ANSWER);
433
434     }
435
436     return (outcome);
437 }
438
439 /*  Data structure  routines */
440
441 int get_motion_vocab_id(const char* word)
442 // Return the first motion number that has 'word' as one of its words.
443 {
444     for (int i = 0; i < NMOTIONS; ++i) {
445         for (int j = 0; j < motions[i].words.n; ++j) {
446             if (strcasecmp(word, motions[i].words.strs[j]) == 0 && (strlen(word) > 1 ||
447                     strchr(ignore, word[0]) == NULL ||
448                     !settings.oldstyle))
449                 return (i);
450         }
451     }
452     // If execution reaches here, we didn't find the word.
453     return (WORD_NOT_FOUND);
454 }
455
456 int get_object_vocab_id(const char* word)
457 // Return the first object number that has 'word' as one of its words.
458 {
459     for (int i = 0; i < NOBJECTS + 1; ++i) { // FIXME: the + 1 should go when 1-indexing for objects is removed
460         for (int j = 0; j < objects[i].words.n; ++j) {
461             if (strcasecmp(word, objects[i].words.strs[j]) == 0)
462                 return (i);
463         }
464     }
465     // If execution reaches here, we didn't find the word.
466     return (WORD_NOT_FOUND);
467 }
468
469 int get_action_vocab_id(const char* word)
470 // Return the first motion number that has 'word' as one of its words.
471 {
472     for (int i = 0; i < NACTIONS; ++i) {
473         for (int j = 0; j < actions[i].words.n; ++j) {
474             if (strcasecmp(word, actions[i].words.strs[j]) == 0 && (strlen(word) > 1 ||
475                     strchr(ignore, word[0]) == NULL ||
476                     !settings.oldstyle))
477                 return (i);
478         }
479     }
480     // If execution reaches here, we didn't find the word.
481     return (WORD_NOT_FOUND);
482 }
483
484 int get_special_vocab_id(const char* word)
485 // Return the first special number that has 'word' as one of its words.
486 {
487     for (int i = 0; i < NSPECIALS; ++i) {
488         for (int j = 0; j < specials[i].words.n; ++j) {
489             if (strcasecmp(word, specials[i].words.strs[j]) == 0)
490                 return (i);
491         }
492     }
493     // If execution reaches here, we didn't find the word.
494     return (WORD_NOT_FOUND);
495 }
496
497 long get_vocab_id(const char* word)
498 // Search the vocab categories in order for the supplied word.
499 {
500     long ref_num;
501
502     /* FIXME: Magic numbers related to vocabulary */
503     ref_num = get_motion_vocab_id(word);
504     if (ref_num != WORD_NOT_FOUND)
505         return (ref_num + 0); // FIXME: replace with a proper hash
506
507     ref_num = get_object_vocab_id(word);
508     if (ref_num != WORD_NOT_FOUND)
509         return (ref_num + 1000); // FIXME: replace with a proper hash
510
511     ref_num = get_action_vocab_id(word);
512     if (ref_num != WORD_NOT_FOUND)
513         return (ref_num + 2000); // FIXME: replace with a proper hash
514
515     ref_num = get_special_vocab_id(word);
516     if (ref_num != WORD_NOT_FOUND)
517         return (ref_num + 3000); // FIXME: replace with a proper hash
518
519     // Check for the reservoir magic word.
520     if (strcasecmp(word, game.zzword) == 0)
521         return (PART + 2000); // FIXME: replace with a proper hash
522
523     return (WORD_NOT_FOUND);
524 }
525
526 void juggle(long object)
527 /*  Juggle an object by picking it up and putting it down again, the purpose
528  *  being to get the object to the front of the chain of things at its loc. */
529 {
530     long i, j;
531
532     i = game.place[object];
533     j = game.fixed[object];
534     move(object, i);
535     move(object + NOBJECTS, j);
536 }
537
538 void move(long object, long where)
539 /*  Place any object anywhere by picking it up and dropping it.  May
540  *  already be toting, in which case the carry is a no-op.  Mustn't
541  *  pick up objects which are not at any loc, since carry wants to
542  *  remove objects from game.atloc chains. */
543 {
544     long from;
545
546     if (object > NOBJECTS)
547         from = game.fixed[object - NOBJECTS];
548     else
549         from = game.place[object];
550     if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
551         carry(object, from);
552     drop(object, where);
553 }
554
555 long put(long object, long where, long pval)
556 /*  PUT is the same as MOVE, except it returns a value used to set up the
557  *  negated game.prop values for the repository objects. */
558 {
559     move(object, where);
560     return (-1) - pval;;
561 }
562
563 void carry(long object, long where)
564 /*  Start toting an object, removing it from the list of things at its former
565  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
566  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
567 {
568     long temp;
569
570     if (object <= NOBJECTS) {
571         if (game.place[object] == CARRIED)
572             return;
573         game.place[object] = CARRIED;
574         ++game.holdng;
575     }
576     if (game.atloc[where] == object) {
577         game.atloc[where] = game.link[object];
578         return;
579     }
580     temp = game.atloc[where];
581     while (game.link[temp] != object) {
582         temp = game.link[temp];
583     }
584     game.link[temp] = game.link[object];
585 }
586
587 void drop(long object, long where)
588 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
589  *  game.holdng if the object was being toted. */
590 {
591     if (object > NOBJECTS)
592         game.fixed[object - NOBJECTS] = where;
593     else {
594         if (game.place[object] == CARRIED)
595             --game.holdng;
596         game.place[object] = where;
597     }
598     if (where <= 0)
599         return;
600     game.link[object] = game.atloc[where];
601     game.atloc[where] = object;
602 }
603
604 long atdwrf(long where)
605 /*  Return the index of first dwarf at the given location, zero if no dwarf is
606  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
607  *  the pirate (6th dwarf). */
608 {
609     long at;
610
611     at = 0;
612     if (game.dflag < 2)
613         return (at);
614     at = -1;
615     for (long i = 1; i <= NDWARVES - 1; i++) {
616         if (game.dloc[i] == where)
617             return i;
618         if (game.dloc[i] != 0)
619             at = 0;
620     }
621     return (at);
622 }
623
624 /*  Utility routines (setbit, tstbit, set_seed, get_next_lcg_value,
625  *  randrange) */
626
627 long setbit(long bit)
628 /*  Returns 2**bit for use in constructing bit-masks. */
629 {
630     return (1L << bit);
631 }
632
633 bool tstbit(long mask, int bit)
634 /*  Returns true if the specified bit is set in the mask. */
635 {
636     return (mask & (1 << bit)) != 0;
637 }
638
639 void set_seed(long seedval)
640 /* Set the LCG seed */
641 {
642     game.lcg_x = (unsigned long) seedval % game.lcg_m;
643
644     // once seed is set, we need to generate the Z`ZZZ word
645     make_zzword(game.zzword);
646 }
647
648 unsigned long get_next_lcg_value(void)
649 /* Return the LCG's current value, and then iterate it. */
650 {
651     unsigned long old_x = game.lcg_x;
652     game.lcg_x = (game.lcg_a * game.lcg_x + game.lcg_c) % game.lcg_m;
653     return old_x;
654 }
655
656 long randrange(long range)
657 /* Return a random integer from [0, range). */
658 {
659     return range * get_next_lcg_value() / game.lcg_m;
660 }
661
662 void make_zzword(char zzword[TOKLEN+1])
663 {
664     for (int i = 0; i < 5; ++i) {
665         zzword[i] = 'A' + randrange(26);
666     }
667     zzword[1] = '\''; // force second char to apostrophe
668     zzword[5] = '\0';
669 }
670
671 // LCOV_EXCL_START
672 void bug(enum bugtype num, const char *error_string)
673 {
674     fprintf(stderr, "Fatal error %d, %s.\n", num, error_string);
675     exit(EXIT_FAILURE);
676 }
677 // LCOV_EXCL_STOP
678
679 /* end */