Improve test coverage.
[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 char* xstrdup(const char* s)
14 {
15     char* ptr = strdup(s);
16     if (ptr == NULL) {
17         fprintf(stderr, "Out of memory!\n");
18         exit(EXIT_FAILURE);
19     }
20     return (ptr);
21 }
22
23 void packed_to_token(long packed, char token[6])
24 {
25     // Unpack and map back to ASCII.
26     for (int i = 0; i < 5; ++i) {
27         char advent = (packed >> i * 6) & 63;
28         token[4 - i] = advent_to_ascii[(int) advent];
29     }
30
31     // Ensure the last character is \0.
32     token[5] = '\0';
33
34     // Replace trailing whitespace with \0.
35     for (int i = 4; i >= 0; --i) {
36         if (token[i] == ' ' || token[i] == '\t')
37             token[i] = '\0';
38         else
39             break;
40     }
41 }
42
43 /*  I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
44
45 void speak(const char* msg)
46 {
47     // Do nothing if we got a null pointer.
48     if (msg == NULL)
49         return;
50
51     // Do nothing if we got an empty string.
52     if (strlen(msg) == 0)
53         return;
54
55     // Print a newline if the global game.blklin says to.
56     if (game.blklin == true)
57         printf("\n");
58
59     // Create a copy of our string, so we can edit it.
60     char* copy = xstrdup(msg);
61
62     // Staging area for stringified parameters.
63     char parameters[5][100]; // FIXME: to be replaced with dynamic allocation
64
65     // Handle format specifiers (including the custom %C, %L, %S) by adjusting the parameter accordingly, and replacing the specifier with %s.
66     int pi = 0; // parameter index
67     for (int i = 0; i < (int)strlen(msg); ++i) {
68         if (msg[i] == '%') {
69             ++pi;
70
71             // 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.
72             if (msg[i + 1] == 'd') {
73                 copy[i + 1] = 's';
74                 sprintf(parameters[pi], "%ld", PARMS[pi]);
75             }
76
77             // Unmodified string specifier.
78             if (msg[i + 1] == 's') {
79                 packed_to_token(PARMS[pi], parameters[pi]);
80             }
81
82             // Singular/plural specifier.
83             if (msg[i + 1] == 'S') {
84                 copy[i + 1] = 's';
85                 if (PARMS[pi - 1] > 1) { // look at the *previous* parameter (which by necessity must be numeric)
86                     sprintf(parameters[pi], "%s", "s");
87                 } else {
88                     sprintf(parameters[pi], "%s", "");
89                 }
90             }
91
92             // All-lowercase specifier.
93             if (msg[i + 1] == 'L') {
94                 copy[i + 1] = 's';
95                 packed_to_token(PARMS[pi], parameters[pi]);
96                 for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
97                     parameters[pi][j] = tolower(parameters[pi][j]);
98                 }
99             }
100
101             // First char uppercase, rest lowercase.
102             if (msg[i + 1] == 'C') {
103                 copy[i + 1] = 's';
104                 packed_to_token(PARMS[pi], parameters[pi]);
105                 for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
106                     parameters[pi][j] = tolower(parameters[pi][j]);
107                 }
108                 parameters[pi][0] = toupper(parameters[pi][0]);
109             }
110         }
111     }
112
113     // Render the final string.
114     char rendered[2000]; // FIXME: to be replaced with dynamic allocation
115     sprintf(rendered, copy, parameters[1], parameters[2], parameters[3], parameters[4]); // FIXME: to be replaced with vsprintf()
116
117     // Print the message.
118     printf("%s\n", rendered);
119
120     free(copy);
121 }
122
123 void PSPEAK(vocab_t msg, int skip)
124 /*  Find the skip+1st message from msg and print it.  msg should be
125  *  the index of the inventory message for object.  (INVEN+N+1 message
126  *  is game.prop=N message). */
127 {
128     if (skip >= 0)
129         speak(object_descriptions[msg].longs[skip]);
130     else
131         speak(object_descriptions[msg].inventory);
132 }
133
134 void RSPEAK(vocab_t i)
135 /* Print the i-th "random" message (section 6 of database). */
136 {
137     speak(arbitrary_messages[i]);
138 }
139
140 void SETPRM(long first, long p1, long p2)
141 /*  Stores parameters into the PRMCOM parms array for use by speak.  P1 and P2
142  *  are stored into PARMS(first) and PARMS(first+1). */
143 {
144     if (first >= MAXPARMS)
145         BUG(29);
146     else {
147         PARMS[first] = p1;
148         PARMS[first + 1] = p2;
149     }
150 }
151
152 bool GETIN(FILE *input,
153            long *pword1, long *pword1x,
154            long *pword2, long *pword2x)
155 /*  Get a command from the adventurer.  Snarf out the first word, pad it with
156  *  blanks, and return it in WORD1.  Chars 6 thru 10 are returned in WORD1X, in
157  *  case we need to print out the whole word in an error message.  Any number of
158  *  blanks may follow the word.  If a second word appears, it is returned in
159  *  WORD2 (chars 6 thru 10 in WORD2X), else WORD2 is -1. */
160 {
161     long junk;
162
163     for (;;) {
164         if (game.blklin)
165             fputc('\n', stdout);;
166         if (!MAPLIN(input))
167             return false;
168         *pword1 = GETTXT(true, true, true);
169         if (game.blklin && *pword1 < 0)
170             continue;
171         *pword1x = GETTXT(false, true, true);
172         do {
173             junk = GETTXT(false, true, true);
174         } while
175         (junk > 0);
176         *pword2 = GETTXT(true, true, true);
177         *pword2x = GETTXT(false, true, true);
178         do {
179             junk = GETTXT(false, true, true);
180         } while
181         (junk > 0);
182         if (GETTXT(true, true, true) <= 0)
183             return true;
184         RSPEAK(TWO_WORDS);
185     }
186 }
187
188 long YES(FILE *input, vocab_t x, vocab_t y, vocab_t z)
189 /*  Print message X, wait for yes/no answer.  If yes, print Y and return true;
190  *  if no, print Z and return false. */
191 {
192     token_t reply, junk1, junk2, junk3;
193
194     for (;;) {
195         RSPEAK(x);
196         GETIN(input, &reply, &junk1, &junk2, &junk3);
197         if (reply == MAKEWD(250519) || reply == MAKEWD(25)) {
198             RSPEAK(y);
199             return true;
200         }
201         if (reply == MAKEWD(1415) || reply == MAKEWD(14)) {
202             RSPEAK(z);
203             return false;
204         }
205         RSPEAK(PLEASE_ANSWER);
206     }
207 }
208
209 /*  Line-parsing routines (GETTXT, MAKEWD, PUTTXT, SHFTXT) */
210
211 long GETTXT(bool skip, bool onewrd, bool upper)
212 /*  Take characters from an input line and pack them into 30-bit words.
213  *  Skip says to skip leading blanks.  ONEWRD says stop if we come to a
214  *  blank.  UPPER says to map all letters to uppercase.  If we reach the
215  *  end of the line, the word is filled up with blanks (which encode as 0's).
216  *  If we're already at end of line when TEXT is called, we return -1. */
217 {
218     long text;
219     static long splitting = -1;
220
221     if (LNPOSN != splitting)
222         splitting = -1;
223     text = -1;
224     while (true) {
225         if (LNPOSN > LNLENG)
226             return (text);
227         if ((!skip) || INLINE[LNPOSN] != 0)
228             break;
229         ++LNPOSN;
230     }
231
232     text = 0;
233     for (int I = 1; I <= TOKLEN; I++) {
234         text = text * 64;
235         if (LNPOSN > LNLENG || (onewrd && INLINE[LNPOSN] == 0))
236             continue;
237         char current = INLINE[LNPOSN];
238         if (current < ascii_to_advent['%']) {
239             splitting = -1;
240             if (upper && current >= ascii_to_advent['a'])
241                 current = current - 26;
242             text = text + current;
243             ++LNPOSN;
244             continue;
245         }
246         if (splitting != LNPOSN) {
247             text = text + ascii_to_advent['%'];
248             splitting = LNPOSN;
249             continue;
250         }
251
252         text = text + current - ascii_to_advent['%'];
253         splitting = -1;
254         ++LNPOSN;
255     }
256
257     return text;
258 }
259
260 token_t MAKEWD(long letters)
261 /*  Combine TOKLEN (currently 5) uppercase letters (represented by
262  *  pairs of decimal digits in lettrs) to form a 30-bit value matching
263  *  the one that GETTXT would return given those characters plus
264  *  trailing blanks.  Caution: lettrs will overflow 31 bits if
265  *  5-letter word starts with V-Z.  As a kludgey workaround, you can
266  *  increment a letter by 5 by adding 50 to the next pair of
267  *  digits. */
268 {
269     long i = 1, word = 0;
270
271     for (long k = letters; k != 0; k = k / 100) {
272         word = word + i * (MOD(k, 50) + 10);
273         i = i * 64;
274         if (MOD(k, 100) > 50)word = word + i * 5;
275     }
276     i = 64L * 64L * 64L * 64L * 64L / i;
277     word = word * i;
278     return word;
279 }
280
281 /*  Data structure  routines */
282
283 long VOCAB(long id, long init)
284 /*  Look up ID in the vocabulary (ATAB) and return its "definition" (KTAB), or
285  *  -1 if not found.  If INIT is positive, this is an initialisation call setting
286  *  up a keyword variable, and not finding it constitutes a bug.  It also means
287  *  that only KTAB values which taken over 1000 equal INIT may be considered.
288  *  (Thus "STEPS", which is a motion verb as well as an object, may be located
289  *  as an object.)  And it also means the KTAB value is taken modulo 1000. */
290 {
291     long lexeme;
292
293     for (long i = 1; i <= TABSIZ; i++) {
294         if (KTAB[i] == -1) {
295             lexeme = -1;
296             if (init < 0)
297                 return (lexeme);
298             BUG(5);
299         }
300         if (init >= 0 && KTAB[i] / 1000 != init)
301             continue;
302         if (ATAB[i] == id) {
303             lexeme = KTAB[i];
304             if (init >= 0)
305                 lexeme = MOD(lexeme, 1000);
306             return (lexeme);
307         }
308     }
309     BUG(21);
310 }
311
312 void JUGGLE(long object)
313 /*  Juggle an object by picking it up and putting it down again, the purpose
314  *  being to get the object to the front of the chain of things at its loc. */
315 {
316     long i, j;
317
318     i = game.place[object];
319     j = game.fixed[object];
320     MOVE(object, i);
321     MOVE(object + NOBJECTS, j);
322 }
323
324 void MOVE(long object, long where)
325 /*  Place any object anywhere by picking it up and dropping it.  May
326  *  already be toting, in which case the carry is a no-op.  Mustn't
327  *  pick up objects which are not at any loc, since carry wants to
328  *  remove objects from game.atloc chains. */
329 {
330     long from;
331
332     if (object > NOBJECTS)
333         from = game.fixed[object - NOBJECTS];
334     else
335         from = game.place[object];
336     if (from != NOWHERE && from != CARRIED && !SPECIAL(from))
337         CARRY(object, from);
338     DROP(object, where);
339 }
340
341 long PUT(long object, long where, long pval)
342 /*  PUT is the same as MOVE, except it returns a value used to set up the
343  *  negated game.prop values for the repository objects. */
344 {
345     MOVE(object, where);
346     return (-1) - pval;;
347 }
348
349 void CARRY(long object, long where)
350 /*  Start toting an object, removing it from the list of things at its former
351  *  location.  Incr holdng unless it was already being toted.  If object>NOBJECTS
352  *  (moving "fixed" second loc), don't change game.place or game.holdng. */
353 {
354     long temp;
355
356     if (object <= NOBJECTS) {
357         if (game.place[object] == CARRIED)
358             return;
359         game.place[object] = CARRIED;
360         ++game.holdng;
361     }
362     if (game.atloc[where] == object) {
363         game.atloc[where] = game.link[object];
364         return;
365     }
366     temp = game.atloc[where];
367     while (game.link[temp] != object) {
368         temp = game.link[temp];
369     }
370     game.link[temp] = game.link[object];
371 }
372
373 void DROP(long object, long where)
374 /*  Place an object at a given loc, prefixing it onto the game.atloc list.  Decr
375  *  game.holdng if the object was being toted. */
376 {
377     if (object > NOBJECTS)
378         game.fixed[object - NOBJECTS] = where;
379     else {
380         if (game.place[object] == CARRIED)
381             --game.holdng;
382         game.place[object] = where;
383     }
384     if (where <= 0)
385         return;
386     game.link[object] = game.atloc[where];
387     game.atloc[where] = object;
388 }
389
390 long ATDWRF(long where)
391 /*  Return the index of first dwarf at the given location, zero if no dwarf is
392  *  there (or if dwarves not active yet), -1 if all dwarves are dead.  Ignore
393  *  the pirate (6th dwarf). */
394 {
395     long at;
396
397     at = 0;
398     if (game.dflag < 2)
399         return (at);
400     at = -1;
401     for (long i = 1; i <= NDWARVES - 1; i++) {
402         if (game.dloc[i] == where)
403             return i;
404         if (game.dloc[i] != 0)
405             at = 0;
406     }
407     return (at);
408 }
409
410 /*  Utility routines (SETBIT, TSTBIT, set_seed, get_next_lcg_value,
411  *  randrange, RNDVOC, BUG) */
412
413 long SETBIT(long bit)
414 /*  Returns 2**bit for use in constructing bit-masks. */
415 {
416     return (1 << bit);
417 }
418
419 bool TSTBIT(long mask, int bit)
420 /*  Returns true if the specified bit is set in the mask. */
421 {
422     return (mask & (1 << bit)) != 0;
423 }
424
425 void set_seed(long seedval)
426 /* Set the LCG seed */
427 {
428     game.lcg_x = (unsigned long) seedval % game.lcg_m;
429 }
430
431 unsigned long get_next_lcg_value(void)
432 /* Return the LCG's current value, and then iterate it. */
433 {
434     unsigned long old_x = game.lcg_x;
435     game.lcg_x = (game.lcg_a * game.lcg_x + game.lcg_c) % game.lcg_m;
436     return old_x;
437 }
438
439 long randrange(long range)
440 /* Return a random integer from [0, range). */
441 {
442     return range * get_next_lcg_value() / game.lcg_m;
443 }
444
445 long RNDVOC(long second, long force)
446 /*  Searches the vocabulary ATAB for a word whose second character is
447  *  char, and changes that word such that each of the other four
448  *  characters is a random letter.  If force is non-zero, it is used
449  *  as the new word.  Returns the new word. */
450 {
451     long rnd = force;
452
453     if (rnd == 0) {
454         for (int i = 1; i <= 5; i++) {
455             long j = 11 + randrange(26);
456             if (i == 2)
457                 j = second;
458             rnd = rnd * 64 + j;
459         }
460     }
461
462     long div = 64L * 64L * 64L;
463     for (int i = 1; i <= TABSIZ; i++) {
464         if (MOD(ATAB[i] / div, 64L) == second) {
465             ATAB[i] = rnd;
466             break;
467         }
468     }
469
470     return rnd;
471 }
472
473 void BUG(long num)
474 /*  The following conditions are currently considered fatal bugs.  Numbers < 20
475  *  are detected while reading the database; the others occur at "run time".
476  *      0       Message line > 70 characters
477  *      1       Null line in message
478  *      2       Too many words of messages
479  *      3       Too many travel options
480  *      4       Too many vocabulary words
481  *      5       Required vocabulary word not found
482  *      6       Too many RTEXT messages
483  *      7       Too many hints
484  *      8       Location has cond bit being set twice
485  *      9       Invalid section number in database
486  *      10      Too many locations
487  *      11      Too many class or turn messages
488  *      20      Special travel (500>L>300) exceeds goto list
489  *      21      Ran off end of vocabulary table
490  *      22      Vocabulary type (N/1000) not between 0 and 3
491  *      23      Intransitive action verb exceeds goto list
492  *      24      Transitive action verb exceeds goto list
493  *      25      Conditional travel entry with no alternative
494  *      26      Location has no travel entries
495  *      27      Hint number exceeds goto list
496  *      28      Invalid month returned by date function
497  *      29      Too many parameters given to SETPRM */
498 {
499
500     printf("Fatal error %ld.  See source code for interpretation.\n", num);
501     exit(0);
502 }
503
504 /*  Machine dependent routines (MAPLIN, SAVEIO) */
505
506 bool MAPLIN(FILE *fp)
507 {
508     bool eof;
509
510     /* Read a line of input, from the specified input source.
511      * This logic is complicated partly because it has to serve
512      * several cases with different requirements and partly because
513      * of a quirk in linenoise().
514      *
515      * The quirk shows up when you paste a test log from the clipboard
516      * to the program's command prompt.  While fgets (as expected)
517      * consumes it a line at a time, linenoise() returns the first
518      * line and discards the rest.  Thus, there needs to be an
519      * editline (-s) option to fall back to fgets while still
520      * prompting.  Note that linenoise does behave properly when
521      * fed redirected stdin.
522      *
523      * The logging is a bit of a mess because there are two distinct cases
524      * in which you want to echo commands.  One is when shipping them to
525      * a log under the -l option, in which case you want to suppress
526      * prompt generation (so test logs are unadorned command sequences).
527      * On the other hand, if you redirected stdin and are feeding the program
528      * a logfile, you *do* want prompt generation - it makes checkfiles
529      * easier to read when the commands are marked by a preceding prompt.
530      */
531     do {
532         if (!editline) {
533             if (prompt)
534                 fputs("> ", stdout);
535             IGNORE(fgets(rawbuf, sizeof(rawbuf) - 1, fp));
536             eof = (feof(fp));
537         } else {
538             char *cp = linenoise("> ");
539             eof = (cp == NULL);
540             if (!eof) {
541                 strncpy(rawbuf, cp, sizeof(rawbuf) - 1);
542                 linenoiseHistoryAdd(rawbuf);
543                 strncat(rawbuf, "\n", sizeof(rawbuf) - strlen(rawbuf) - 1);
544                 linenoiseFree(cp);
545             }
546         }
547     } while
548     (!eof && rawbuf[0] == '#');
549     if (eof) {
550         if (logfp && fp == stdin)
551             fclose(logfp);
552         return false;
553     } else {
554         FILE *efp = NULL;
555         if (logfp && fp == stdin)
556             efp = logfp;
557         else if (!isatty(0))
558             efp = stdout;
559         if (efp != NULL) {
560             if (prompt && efp == stdout)
561                 fputs("> ", efp);
562             IGNORE(fputs(rawbuf, efp));
563         }
564         strcpy(INLINE + 1, rawbuf);
565         /*  translate the chars to integers in the range 0-126 and store
566          *  them in the common array "INLINE".  Integer values are as follows:
567          *     0   = space [ASCII CODE 40 octal, 32 decimal]
568          *    1-2  = !" [ASCII 41-42 octal, 33-34 decimal]
569          *    3-10 = '()*+,-. [ASCII 47-56 octal, 39-46 decimal]
570          *   11-36 = upper-case letters
571          *   37-62 = lower-case letters
572          *    63   = percent (%) [ASCII 45 octal, 37 decimal]
573          *   64-73 = digits, 0 through 9
574          *  Remaining characters can be translated any way that is convenient;
575          *  The above mappings are required so that certain special
576          *  characters are known to fit in 6 bits and/or can be easily spotted.
577          *  Array elements beyond the end of the line should be filled with 0,
578          *  and LNLENG should be set to the index of the last character.
579          *
580          *  If the data file uses a character other than space (e.g., tab) to
581          *  separate numbers, that character should also translate to 0.
582          *
583          *  This procedure may use the map1,map2 arrays to maintain static data for
584          *  the mapping.  MAP2(1) is set to 0 when the program starts
585          *  and is not changed thereafter unless the routines on this page choose
586          *  to do so. */
587         LNLENG = 0;
588         for (long i = 1; i <= (long)sizeof(INLINE) && INLINE[i] != 0; i++) {
589             long val = INLINE[i];
590             INLINE[i] = ascii_to_advent[val];
591             if (INLINE[i] != 0)
592                 LNLENG = i;
593         }
594         LNPOSN = 1;
595         return true;
596     }
597 }
598
599 void DATIME(long* d, long* t)
600 {
601     struct timeval tv;
602     gettimeofday(&tv, NULL);
603     *d = (long) tv.tv_sec;
604     *t = (long) tv.tv_usec;
605 }
606
607 /* end */