Coverage -- more odd actions
[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     /* Bound prefix on the %s would be needed to prevent buffer
98      * overflow.  but we shortstop this more simply by making each
99      * raw-input buffer as long as the enrire inout buffer. */
100     sscanf(raw, "%s%s", cmd->raw1, cmd->raw2);
101
102     // pack the substrings
103     cmd->wd1  = token_to_packed(cmd->raw1);
104     cmd->wd2  = token_to_packed(cmd->raw2);
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, sspeak, get_input, yes) */
125
126 void vspeak(const char* msg, bool blank, 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     if (blank == true)
137         printf("\n");
138
139     int msglen = strlen(msg);
140
141     // Rendered string
142     ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
143     char* rendered = xmalloc(size);
144     char* renderp = rendered;
145
146     // Handle format specifiers (including the custom %C, %L, %S) by
147     // adjusting the parameter accordingly, and replacing the
148     // specifier with %s.
149     long previous_arg = 0;
150     for (int i = 0; i < msglen; i++) {
151         if (msg[i] != '%') {
152             *renderp++ = msg[i];
153             size--;
154         } else {
155             long arg = va_arg(ap, long);
156             if (arg == -1)
157                 arg = 0; // LCOV_EXCL_LINE - don't think we can get here.
158             i++;
159             // Integer specifier. In order to accommodate the fact
160             // that PARMS can have both legitimate integers *and*
161             // packed tokens, stringify everything. Future work may
162             // eliminate the need for this.
163             if (msg[i] == 'd') {
164                 int ret = snprintf(renderp, size, "%ld", arg);
165                 if (ret < size) {
166                     renderp += ret;
167                     size -= ret;
168                 }
169             }
170
171             // Unmodified string specifier.
172             if (msg[i] == 's') {
173                 packed_to_token(arg, renderp); /* unpack directly to destination */
174                 size_t len = strlen(renderp);
175                 renderp += len;
176                 size -= len;
177             }
178
179             // Singular/plural specifier.
180             if (msg[i] == 'S') {
181                 if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
182                     *renderp++ = 's';
183                     size--;
184                 }
185             }
186
187             /* Version specifier */
188             if (msg[i] == 'V') {
189                 strcpy(renderp, VERSION);
190                 size_t len = strlen(VERSION);
191                 renderp += len;
192                 size -= len;
193             }
194
195             previous_arg = arg;
196         }
197     }
198     *renderp = 0;
199
200     // Print the message.
201     printf("%s\n", rendered);
202
203     free(rendered);
204 }
205
206 void speak(const char* msg, ...)
207 {
208     va_list ap;
209     va_start(ap, msg);
210     vspeak(msg, true, ap);
211     va_end(ap);
212 }
213
214 void sspeak(const long msg, ...)
215 {
216     va_list ap;
217     va_start(ap, msg);
218     fputc('\n', stdout);
219     vprintf(arbitrary_messages[msg], ap);
220     fputc('\n', stdout);
221     va_end(ap);
222 }
223
224 void pspeak(vocab_t msg, enum speaktype mode, int skip, bool blank, ...)
225 /* Find the skip+1st message from msg and print it.  Modes are:
226  * feel = for inventory, what you can touch
227  * look = the long description for the state the object is in
228  * listen = the sound for the state the object is in
229  * study = text on the object. */
230 {
231     va_list ap;
232     va_start(ap, blank);
233     switch (mode) {
234     case touch:
235         vspeak(objects[msg].inventory, blank, ap);
236         break;
237     case look:
238         vspeak(objects[msg].descriptions[skip], blank, ap);
239         break;
240     case hear:
241         vspeak(objects[msg].sounds[skip], blank, ap);
242         break;
243     case study:
244         vspeak(objects[msg].texts[skip], blank, ap);
245         break;
246     case change:
247         vspeak(objects[msg].changes[skip], blank, ap);
248         break;
249     }
250     va_end(ap);
251 }
252
253 void rspeak(vocab_t i, ...)
254 /* Print the i-th "random" message (section 6 of database). */
255 {
256     va_list ap;
257     va_start(ap, i);
258     vspeak(arbitrary_messages[i], true, ap);
259     va_end(ap);
260 }
261
262 void echo_input(FILE* destination, const char* input_prompt, const char* input)
263 {
264     size_t len = strlen(input_prompt) + strlen(input) + 1;
265     char* prompt_and_input = (char*) xmalloc(len);
266     strcpy(prompt_and_input, input_prompt);
267     strcat(prompt_and_input, input);
268     fprintf(destination, "%s\n", prompt_and_input);
269     free(prompt_and_input);
270 }
271
272 int word_count(char* str)
273 {
274     char delims[] = " \t";
275     int count = 0;
276     int inblanks = true;
277
278     for (char *s = str; *s; s++)
279         if (inblanks) {
280             if (strchr(delims, *s) == 0) {
281                 ++count;
282                 inblanks = false;
283             }
284         } else {
285             if (strchr(delims, *s) != 0) {
286                 inblanks = true;
287             }
288         }
289
290     return (count);
291 }
292
293 char* get_input()
294 {
295     // Set up the prompt
296     char input_prompt[] = "> ";
297     if (!settings.prompt)
298         input_prompt[0] = '\0';
299
300     // Print a blank line
301     printf("\n");
302
303     char* input;
304     while (true) {
305         input = readline(input_prompt);
306
307         if (input == NULL) // Got EOF; return with it.
308             return (input);
309         else if (input[0] == '#') { // Ignore comments.
310             free(input);
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     add_history(input);
320
321     if (!isatty(0))
322         echo_input(stdout, input_prompt, input);
323
324     if (settings.logfp)
325         echo_input(settings.logfp, "", 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             free(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         free(reply);
349
350         for (int i = 0; i < (int)strlen(firstword); ++i)
351             firstword[i] = tolower(firstword[i]);
352
353         int yes = strncmp("yes", firstword, sizeof("yes") - 1);
354         int y = strncmp("y", firstword, sizeof("y") - 1);
355         int no = strncmp("no", firstword, sizeof("no") - 1);
356         int n = strncmp("n", firstword, sizeof("n") - 1);
357
358         free(firstword);
359
360         if (yes == 0 ||
361             y == 0) {
362             outcome = true;
363             break;
364         } else if (no == 0 ||
365                    n == 0) {
366             outcome = false;
367             break;
368         } else
369             rspeak(PLEASE_ANSWER);
370     }
371     return (outcome);
372 }
373
374
375 bool yes(const char* question, const char* yes_response, const char* no_response)
376 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
377  *  if no, print Z and return false. */
378 {
379     char* reply;
380     bool outcome;
381
382     for (;;) {
383         speak(question);
384
385         reply = get_input();
386         if (reply == NULL) {
387             // LCOV_EXCL_START
388             // Should be unreachable. Reply should never be NULL
389             free(reply);
390             exit(EXIT_SUCCESS);
391             // LCOV_EXCL_STOP
392         }
393
394         char* firstword = (char*) xmalloc(strlen(reply) + 1);
395         sscanf(reply, "%s", firstword);
396
397         free(reply);
398
399         for (int i = 0; i < (int)strlen(firstword); ++i)
400             firstword[i] = tolower(firstword[i]);
401
402         int yes = strncmp("yes", firstword, sizeof("yes") - 1);
403         int y = strncmp("y", firstword, sizeof("y") - 1);
404         int no = strncmp("no", firstword, sizeof("no") - 1);
405         int n = strncmp("n", firstword, sizeof("n") - 1);
406
407         free(firstword);
408
409         if (yes == 0 ||
410             y == 0) {
411             speak(yes_response);
412             outcome = true;
413             break;
414         } else if (no == 0 ||
415                    n == 0) {
416             speak(no_response);
417             outcome = false;
418             break;
419         } else
420             rspeak(PLEASE_ANSWER);
421
422     }
423
424     return (outcome);
425 }
426
427 /*  Data structure  routines */
428
429 int get_motion_vocab_id(const char* word)
430 // Return the first motion number that has 'word' as one of its words.
431 {
432     for (int i = 0; i < NMOTIONS; ++i) {
433         for (int j = 0; j < motions[i].words.n; ++j) {
434             if (strcasecmp(word, motions[i].words.strs[j]) == 0 && (strlen(word) > 1 ||
435                     strchr(ignore, word[0]) == NULL ||
436                     !settings.oldstyle))
437                 return (i);
438         }
439     }
440     // If execution reaches here, we didn't find the word.
441     return (WORD_NOT_FOUND);
442 }
443
444 int get_object_vocab_id(const char* word)
445 // Return the first object number that has 'word' as one of its words.
446 {
447     for (int i = 0; i < NOBJECTS + 1; ++i) { // FIXME: the + 1 should go when 1-indexing for objects is removed
448         for (int j = 0; j < objects[i].words.n; ++j) {
449             if (strcasecmp(word, objects[i].words.strs[j]) == 0)
450                 return (i);
451         }
452     }
453     // If execution reaches here, we didn't find the word.
454     return (WORD_NOT_FOUND);
455 }
456
457 int get_action_vocab_id(const char* word)
458 // Return the first motion number that has 'word' as one of its words.
459 {
460     for (int i = 0; i < NACTIONS; ++i) {
461         for (int j = 0; j < actions[i].words.n; ++j) {
462             if (strcasecmp(word, actions[i].words.strs[j]) == 0 && (strlen(word) > 1 ||
463                     strchr(ignore, word[0]) == NULL ||
464                     !settings.oldstyle))
465                 return (i);
466         }
467     }
468     // If execution reaches here, we didn't find the word.
469     return (WORD_NOT_FOUND);
470 }
471
472 int get_special_vocab_id(const char* word)
473 // Return the first special number that has 'word' as one of its words.
474 {
475     for (int i = 0; i < NSPECIALS; ++i) {
476         for (int j = 0; j < specials[i].words.n; ++j) {
477             if (strcasecmp(word, specials[i].words.strs[j]) == 0)
478                 return (i);
479         }
480     }
481     // If execution reaches here, we didn't find the word.
482     return (WORD_NOT_FOUND);
483 }
484
485 long get_vocab_id(const char* word)
486 // Search the vocab categories in order for the supplied word.
487 {
488     /* Check for an empty string */
489     if (strncmp(word, "", sizeof("")) == 0)
490         return (WORD_EMPTY);
491
492     long ref_num;
493
494     /* FIXME: Magic numbers related to vocabulary */
495     ref_num = get_motion_vocab_id(word);
496     if (ref_num != WORD_NOT_FOUND)
497         return (ref_num + 0); // FIXME: replace with a proper hash
498
499     ref_num = get_object_vocab_id(word);
500     if (ref_num != WORD_NOT_FOUND)
501         return (ref_num + 1000); // FIXME: replace with a proper hash
502
503     ref_num = get_action_vocab_id(word);
504     if (ref_num != WORD_NOT_FOUND)
505         return (ref_num + 2000); // FIXME: replace with a proper hash
506
507     ref_num = get_special_vocab_id(word);
508     if (ref_num != WORD_NOT_FOUND)
509         return (ref_num + 3000); // FIXME: replace with a proper hash
510
511     // Check for the reservoir magic word.
512     if (strcasecmp(word, game.zzword) == 0)
513         return (PART + 2000); // FIXME: replace with a proper hash
514
515     return (WORD_NOT_FOUND);
516 }
517
518 void juggle(long object)
519 /*  Juggle an object by picking it up and putting it down again, the purpose
520  *  being to get the object to the front of the chain of things at its loc. */
521 {
522     long i, j;
523
524     i = game.place[object];
525     j = game.fixed[object];
526     move(object, i);
527     move(object + NOBJECTS, j);
528 }
529
530 void move(long object, long where)
531 /*  Place any object anywhere by picking it up and dropping it.  May
532  *  already be toting, in which case the carry is a no-op.  Mustn't
533  *  pick up objects which are not at any loc, since carry wants to
534  *  remove objects from game.atloc chains. */
535 {
536     long from;
537
538     if (object > NOBJECTS)
539         from = game.fixed[object - NOBJECTS];
540     else
541         from = game.place[object];
542     if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
543         carry(object, from);
544     drop(object, where);
545 }
546
547 long put(long object, long where, long pval)
548 /*  PUT is the same as MOVE, except it returns a value used to set up the
549  *  negated game.prop values for the repository objects. */
550 {
551     move(object, where);
552     return (-1) - pval;;
553 }
554
555 void carry(long object, long where)
556 /*  Start toting an object, removing it from the list of things at its former
557  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
558  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
559 {
560     long temp;
561
562     if (object <= NOBJECTS) {
563         if (game.place[object] == CARRIED)
564             return;
565         game.place[object] = CARRIED;
566         ++game.holdng;
567     }
568     if (game.atloc[where] == object) {
569         game.atloc[where] = game.link[object];
570         return;
571     }
572     temp = game.atloc[where];
573     while (game.link[temp] != object) {
574         temp = game.link[temp];
575     }
576     game.link[temp] = game.link[object];
577 }
578
579 void drop(long object, long where)
580 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
581  *  game.holdng if the object was being toted. */
582 {
583     if (object > NOBJECTS)
584         game.fixed[object - NOBJECTS] = where;
585     else {
586         if (game.place[object] == CARRIED)
587             --game.holdng;
588         game.place[object] = where;
589     }
590     if (where <= 0)
591         return;
592     game.link[object] = game.atloc[where];
593     game.atloc[where] = object;
594 }
595
596 long atdwrf(long where)
597 /*  Return the index of first dwarf at the given location, zero if no dwarf is
598  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
599  *  the pirate (6th dwarf). */
600 {
601     long at;
602
603     at = 0;
604     if (game.dflag < 2)
605         return (at);
606     at = -1;
607     for (long i = 1; i <= NDWARVES - 1; i++) {
608         if (game.dloc[i] == where)
609             return i;
610         if (game.dloc[i] != 0)
611             at = 0;
612     }
613     return (at);
614 }
615
616 /*  Utility routines (setbit, tstbit, set_seed, get_next_lcg_value,
617  *  randrange) */
618
619 long setbit(long bit)
620 /*  Returns 2**bit for use in constructing bit-masks. */
621 {
622     return (1L << bit);
623 }
624
625 bool tstbit(long mask, int bit)
626 /*  Returns true if the specified bit is set in the mask. */
627 {
628     return (mask & (1 << bit)) != 0;
629 }
630
631 void set_seed(long seedval)
632 /* Set the LCG seed */
633 {
634     game.lcg_x = (unsigned long) seedval % game.lcg_m;
635
636     // once seed is set, we need to generate the Z`ZZZ word
637     make_zzword(game.zzword);
638 }
639
640 unsigned long get_next_lcg_value(void)
641 /* Return the LCG's current value, and then iterate it. */
642 {
643     unsigned long old_x = game.lcg_x;
644     game.lcg_x = (game.lcg_a * game.lcg_x + game.lcg_c) % game.lcg_m;
645     return old_x;
646 }
647
648 long randrange(long range)
649 /* Return a random integer from [0, range). */
650 {
651     return range * get_next_lcg_value() / game.lcg_m;
652 }
653
654 void make_zzword(char zzword[TOKLEN + 1])
655 {
656     for (int i = 0; i < 5; ++i) {
657         zzword[i] = 'A' + randrange(26);
658     }
659     zzword[1] = '\''; // force second char to apostrophe
660     zzword[5] = '\0';
661 }
662
663 // LCOV_EXCL_START
664 void bug(enum bugtype num, const char *error_string)
665 {
666     fprintf(stderr, "Fatal error %d, %s.\n", num, error_string);
667     exit(EXIT_FAILURE);
668 }
669 // LCOV_EXCL_STOP
670
671 /* end */