Merge branch 'master' into actions-arithmetic
[open-adventure.git] / dungeon.c
1 /*
2  * The dungeon compiler. Turns adventure.text into a set of C initializers
3  * defining (mostly) invariant state.  A couple of slots are messed with
4  * at runtime.
5  */
6 #include "common.h"
7
8 #define LINESIZE 100
9 #define RTXSIZ 277
10 #define CLSMAX 12
11 #define LINSIZ 12600
12 #define TRNSIZ 5
13 #define TABSIZ 330
14 #define VRBSIZ 35
15 #define TRVSIZ 885
16 #define TOKLEN 5
17 #define HINTLEN 5
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stdbool.h>
22 #include <unistd.h>
23
24 // Global variables for use in functions below that can gradually disappear as code is cleaned up
25 static long LNLENG;
26 static long LNPOSN;
27 static char INLINE[LINESIZE+1];
28 static long OLDLOC;
29
30 // Storage for what comes out of the database
31 long LINUSE;
32 long TRVS;
33 long CLSSES;
34 long TRNVLS;
35 long TABNDX;
36 long HNTMAX;
37 long PTEXT[NOBJECTS+1];
38 long RTEXT[RTXSIZ + 1];
39 long CTEXT[CLSMAX + 1];
40 long OBJSND[NOBJECTS+1];
41 long OBJTXT[NOBJECTS+1];
42 long STEXT[LOCSIZ + 1];
43 long LTEXT[LOCSIZ + 1];
44 long COND[LOCSIZ + 1];
45 long KEY[LOCSIZ + 1];
46 long LOCSND[LOCSIZ + 1];
47 long LINES[LINSIZ + 1];
48 long CVAL[CLSMAX + 1];
49 long TTEXT[TRNSIZ + 1];
50 long TRNVAL[TRNSIZ + 1];
51 long TRAVEL[TRVSIZ + 1];
52 long KTAB[TABSIZ + 1];
53 long ATAB[TABSIZ + 1];
54 long PLAC[NOBJECTS+1];
55 long FIXD[NOBJECTS+1];
56 long ACTSPK[VRBSIZ + 1];
57 long HINTS[HNTSIZ + 1][HINTLEN];
58
59
60 static bool is_set(long var, long position)
61 {
62    long mask = 1l << position;
63    bool result = (var & mask) == mask;
64    return(result);
65 }
66
67 static long GETTXT(long SKIP,long ONEWRD, long UPPER)
68 {
69    /*  Take characters from an input line and pack them into 30-bit words.
70     *  Skip says to skip leading blanks.  ONEWRD says stop if we come to a
71     *  blank.  UPPER says to map all letters to uppercase.  If we reach the
72     *  end of the line, the word is filled up with blanks (which encode as 0's).
73     *  If we're already at end of line when GETTXT is called, we return -1. */
74
75    long TEXT;
76    static long SPLITTING = -1;
77
78    if(LNPOSN != SPLITTING)
79       SPLITTING = -1;
80    TEXT= -1;
81    while (true) {
82       if(LNPOSN > LNLENG)
83          return(TEXT);
84       if((!SKIP) || INLINE[LNPOSN] != 0)
85          break;
86       LNPOSN=LNPOSN+1;
87    }
88
89    TEXT=0;
90    for (int I=1; I<=TOKLEN; I++) {
91       TEXT=TEXT*64;
92       if(LNPOSN > LNLENG || (ONEWRD && INLINE[LNPOSN] == 0))
93          continue;
94       char current=INLINE[LNPOSN];
95       if(current < 63) {
96          SPLITTING = -1;
97          if(UPPER && current >= 37)
98             current=current-26;
99          TEXT=TEXT+current;
100          LNPOSN=LNPOSN+1;
101          continue;
102       }
103       if(SPLITTING != LNPOSN) {
104          TEXT=TEXT+63;
105          SPLITTING = LNPOSN;
106          continue;
107       }
108
109       TEXT=TEXT+current-63;
110       SPLITTING = -1;
111       LNPOSN=LNPOSN+1;
112    }
113
114    return(TEXT);
115 }
116
117 static void BUG(long NUM) {
118
119    /*  The following conditions are currently considered fatal bugs.  Numbers < 20
120     *  are detected while reading the database; the others occur at "run time".
121     *   0       Message line > 70 characters
122     *   1       Null line in message
123     *   2       Too many words of messages
124     *   3       Too many travel options
125     *   4       Too many vocabulary words
126     *   5       Required vocabulary word not found
127     *   6       Too many RTEXT messages
128     *   7       Too many hints
129     *   8       Location has cond bit being set twice
130     *   9       Invalid section number in database
131     *   10      Too many locations
132     *   11      Too many class or turn messages
133     *   20      Special travel (500>L>300) exceeds goto list
134     *   21      Ran off end of vocabulary table
135     *   22      Vocabulary type (N/1000) not between 0 and 3
136     *   23      Intransitive action verb exceeds goto list
137     *   24      Transitive action verb exceeds goto list
138     *   25      Conditional travel entry with no alternative
139     *   26      Location has no travel entries
140     *   27      Hint number exceeds goto list
141     *   28      Invalid month returned by date function
142     *   29      Too many parameters given to SETPRM */
143
144    fprintf(stderr, "Fatal error %ld.  See source code for interpretation.\n", NUM);
145    exit(EXIT_FAILURE);
146 }
147
148 static void MAPLIN(FILE *OPENED) {
149    /*  Read a line of input, from the specified input source,
150     *  translate the chars to integers in the range 0-126 and store
151     *  them in the common array "INLINE".  Integer values are as follows:
152     *     0   = space [ASCII CODE 40 octal, 32 decimal]
153     *    1-2  = !" [ASCII 41-42 octal, 33-34 decimal]
154     *    3-10 = '()*+,-. [ASCII 47-56 octal, 39-46 decimal]
155     *   11-36 = upper-case letters
156     *   37-62 = lower-case letters
157     *    63   = percent (%) [ASCII 45 octal, 37 decimal]
158     *   64-73 = digits, 0 through 9
159     *  Remaining characters can be translated any way that is convenient;
160     *  The "TYPE" routine below is used to map them back to characters when
161     *  necessary.  The above mappings are required so that certain special
162     *  characters are known to fit in 6 bits and/or can be easily spotted.
163     *  Array elements beyond the end of the line should be filled with 0,
164     *  and LNLENG should be set to the index of the last character.
165     *
166     *  If the data file uses a character other than space (e.g., tab) to
167     *  separate numbers, that character should also translate to 0.
168     *
169     *  This procedure may use the map1,map2 arrays to maintain static data for
170     *  the mapping.  MAP2(1) is set to 0 when the program starts
171     *  and is not changed thereafter unless the routines on this page choose
172     *  to do so. */
173
174    do {
175       if (NULL == fgets(INLINE + 1, sizeof(INLINE) - 1, OPENED)) {
176          printf("Failed fgets()\n");
177       }
178    }
179    while (!feof(OPENED) && INLINE[1] == '#');
180
181    LNLENG = 0;
182    for (size_t i = 1; i < sizeof(INLINE) && INLINE[i] != 0; ++i)
183    {
184       char val = INLINE[i];
185       INLINE[i] = ascii_to_advent[(unsigned)val];
186       if (INLINE[i] != 0)
187          LNLENG = i;
188    }
189    LNPOSN = 1;
190 }
191
192 static long GETNUM(FILE *source) {
193    /*  Obtain the next integer from an input line.  If K>0, we first read a
194     *  new input line from a file; if K<0, we read a line from the keyboard;
195     *  if K=0 we use a line that has already been read (and perhaps partially
196     *  scanned).  If we're at the end of the line or encounter an illegal
197     *  character (not a digit, hyphen, or blank), we return 0. */
198
199    long DIGIT, GETNUM, SIGN;
200
201    if(source != NULL) MAPLIN(source);
202    GETNUM = 0;
203
204    while (INLINE[LNPOSN] == 0) {
205       if (LNPOSN > LNLENG) return(GETNUM);
206       ++LNPOSN;
207    }
208
209    if(INLINE[LNPOSN] != 9)
210    {
211       SIGN=1;
212    }
213    else
214    {
215       SIGN= -1;
216       LNPOSN=LNPOSN+1;
217    }
218    while (!(LNPOSN > LNLENG || INLINE[LNPOSN] == 0))
219    {
220       DIGIT=INLINE[LNPOSN]-64;
221       if(DIGIT < 0 || DIGIT > 9)
222       {
223          GETNUM=0;
224          break;
225       }
226       GETNUM=GETNUM*10+DIGIT;
227       LNPOSN=LNPOSN+1;
228    }
229
230    GETNUM=GETNUM*SIGN;
231    LNPOSN=LNPOSN+1;
232    return(GETNUM);
233 }
234
235 /*  Sections 1, 2, 5, 6, 10, 14.  Read messages and set up pointers. */
236 static void read_messages(FILE* database, long sect)
237 {
238    long KK=LINUSE;
239    while(true)
240    {
241       long loc;
242       LINUSE=KK;
243       loc=GETNUM(database);
244       if(LNLENG >= LNPOSN+70)BUG(0);
245       if(loc == -1) return;
246       if(LNLENG < LNPOSN)BUG(1);
247       do {
248          KK=KK+1;
249          if(KK >= LINSIZ)BUG(2);
250          LINES[KK]=GETTXT(false,false,false);
251       }
252       while(LINES[KK] != -1);
253       LINES[LINUSE]=KK;
254       if(loc == OLDLOC) continue;
255       OLDLOC=loc;
256       LINES[LINUSE]= -KK;
257       if(sect == 14)
258       {
259          TRNVLS=TRNVLS+1;
260          if(TRNVLS > TRNSIZ)BUG(11);
261          TTEXT[TRNVLS]=LINUSE;
262          TRNVAL[TRNVLS]=loc;
263          continue;
264       }
265       if(sect == 10)
266       {
267          CLSSES=CLSSES+1;
268          if(CLSSES > CLSMAX)BUG(11);
269          CTEXT[CLSSES]=LINUSE;
270          CVAL[CLSSES]=loc;
271          continue;
272       }
273       if(sect == 6)
274       {
275          if(loc > RTXSIZ)BUG(6);
276          RTEXT[loc]=LINUSE;
277          continue;
278       }
279       if(sect == 5)
280       {
281          if(loc > 0 && loc <= NOBJECTS)PTEXT[loc]=LINUSE;
282          continue;
283       }
284       if(loc > LOCSIZ)BUG(10);
285       if(sect == 1)
286       {
287          LTEXT[loc]=LINUSE;
288          continue;
289       }
290
291       STEXT[loc]=LINUSE;
292    }
293 }
294
295 /*  The stuff for section 3 is encoded here.  Each "from-location" gets a
296  *  contiguous section of the "TRAVEL" array.  Each entry in travel is
297  *  newloc*1000 + KEYWORD (from section 4, motion verbs), and is negated if
298  *  this is the last entry for this location.  KEY(N) is the index in travel
299  *  of the first option at location N. */
300 static void read_section3_stuff(FILE* database)
301 {
302    long loc;
303    while((loc=GETNUM(database)) != -1)
304    {
305       long newloc=GETNUM(NULL);
306       long L;
307       if(KEY[loc] == 0)
308       {
309          KEY[loc]=TRVS;
310       }
311       else
312       {
313          TRAVEL[TRVS-1]= -TRAVEL[TRVS-1];
314       }
315       while((L=GETNUM(NULL)) != 0)
316       {
317          TRAVEL[TRVS]=newloc*1000+L;
318          TRVS=TRVS+1;
319          if(TRVS == TRVSIZ)BUG(3);
320       }
321       TRAVEL[TRVS-1]= -TRAVEL[TRVS-1];
322    }
323 }
324
325 /*  Here we read in the vocabulary.  KTAB(N) is the word number, ATAB(N) is
326  *  the corresponding word.  The -1 at the end of section 4 is left in KTAB
327  *  as an end-marker. */
328 static void read_vocabulary(FILE* database)
329 {
330    for (TABNDX=1; TABNDX<=TABSIZ; TABNDX++)
331    {
332       KTAB[TABNDX]=GETNUM(database);
333       if(KTAB[TABNDX] == -1) return;
334       ATAB[TABNDX]=GETTXT(true,true,true);
335    } /* end loop */
336    BUG(4);
337 }
338
339 /*  Read in the initial locations for each object.  Also the immovability info.
340  *  plac contains initial locations of objects.  FIXD is -1 for immovable
341  *  objects (including the snake), or = second loc for two-placed objects. */
342 static void read_initial_locations(FILE* database)
343 {
344    long OBJ;
345    while((OBJ=GETNUM(database)) != -1)
346    {
347       PLAC[OBJ]=GETNUM(NULL);
348       FIXD[OBJ]=GETNUM(NULL);
349    }
350 }
351
352 /*  Read default message numbers for action verbs, store in ACTSPK. */
353 static void read_action_verb_message_nr(FILE* database)
354 {
355    long verb;
356    while((verb=GETNUM(database)) != -1)
357    {
358       ACTSPK[verb]=GETNUM(NULL);
359    }
360 }
361
362 /*  Read info about available liquids and other conditions, store in COND. */
363 static void read_conditions(FILE* database)
364 {
365    long K;
366    while((K=GETNUM(database)) != -1)
367    {
368       long loc;
369       while((loc=GETNUM(NULL)) != 0)
370       {
371          if(is_set(COND[loc],K)) BUG(8);
372          COND[loc]=COND[loc] + (1l << K);
373       }
374    }
375 }
376
377
378 /*  Read data for hints. */
379 static void read_hints(FILE* database)
380 {
381    long K;
382    HNTMAX=0;
383    while((K=GETNUM(database)) != -1)
384    {
385       if(K <= 0 || K > HNTSIZ)BUG(7);
386       for (int I=1; I<=4; I++)
387       {
388          HINTS[K][I] =GETNUM(NULL);
389       } /* end loop */
390       HNTMAX=(HNTMAX>K ? HNTMAX : K);
391    }
392 }
393
394 /*  Read the sound/text info, store in OBJSND, OBJTXT, LOCSND. */
395 static void read_sound_text(FILE* database)
396 {
397    long K;
398    while((K=GETNUM(database)) != -1)
399    {
400       long KK=GETNUM(NULL);
401       long I=GETNUM(NULL);
402       if(I != 0)
403       {
404          OBJSND[K]=(KK>0 ? KK : 0);
405          OBJTXT[K]=(I>0 ? I : 0);
406          continue;
407       }
408
409       LOCSND[K]=KK;
410    }
411 }
412
413
414 static int read_database(FILE* database) {
415
416    /*  Clear out the various text-pointer arrays.  All text is stored in array
417     *  lines; each line is preceded by a word pointing to the next pointer (i.e.
418     *  the word following the end of the line).  The pointer is negative if this is
419     *  first line of a message.  The text-pointer arrays contain indices of
420     *  pointer-words in lines.  STEXT(N) is short description of location N.
421     *  LTEXT(N) is long description.  PTEXT(N) points to message for game.prop(N)=0.
422     *  Successive prop messages are found by chasing pointers.  RTEXT contains
423     *  section 6's stuff.  CTEXT(N) points to a player-class message.  TTEXT is for
424     *  section 14.  We also clear COND (see description of section 9 for details). */
425
426    for (int I=1; I<=NOBJECTS; I++) {
427       PTEXT[I] = 0;
428       OBJSND[I] = 0;
429       OBJTXT[I] = 0;
430    }
431    for (int I=1; I<=RTXSIZ; I++) {
432       RTEXT[I] = 0;
433    }
434    for (int I=1; I<=CLSMAX; I++) {
435       CTEXT[I] = 0;
436    }
437    for (int I=1; I<=LOCSIZ; I++) {
438       STEXT[I] = 0;
439       LTEXT[I] = 0;
440       COND[I] = 0;
441       KEY[I] = 0;
442       LOCSND[I] = 0;
443    }
444
445    LINUSE = 1;
446    TRVS = 1;
447    CLSSES = 0;
448    TRNVLS = 0;
449
450    /*  Start new data section.  Sect is the section number. */
451
452    while(true)
453    {
454       long sect=GETNUM(database);
455       OLDLOC= -1;
456       switch (sect)
457       {
458          case 0: return(0);
459          case 1: read_messages(database, sect); break;
460          case 2: read_messages(database, sect); break;
461          case 3: read_section3_stuff(database); break;
462          case 4: read_vocabulary(database); break;
463          case 5: read_messages(database, sect); break;
464          case 6: read_messages(database, sect); break;
465          case 7: read_initial_locations(database); break;
466          case 8: read_action_verb_message_nr(database); break;
467          case 9: read_conditions(database); break;
468          case 10: read_messages(database, sect); break;
469          case 11: read_hints(database); break;
470          case 12: break;
471          case 13: read_sound_text(database); break;
472          case 14: read_messages(database, sect); break;
473          default: BUG(9);
474       }
475    }
476 }
477
478 /*  Finish constructing internal data format */
479
480 /*  Having read in the database, certain things are now constructed.
481  *  game.propS are set to zero.  We finish setting up COND by checking for
482  *  forced-motion travel entries.  The PLAC and FIXD arrays are used
483  *  to set up game.atloc(N) as the first object at location N, and
484  *  game.link(OBJ) as the next object at the same location as OBJ.
485  *  (OBJ>NOBJECTS indicates that game.fixed(OBJ-NOBJECTS)=LOC; game.link(OBJ) is
486  *  still the correct link to use.)  game.abbrev is zeroed; it controls
487  *  whether the abbreviated description is printed.  Counts modulo 5
488  *  unless "LOOK" is used. */
489
490 static void write_0d(FILE* header_file, long single, char* varname)
491 {
492    fprintf(header_file, "LOCATION long %s INITIALIZE(= %ld);\n", varname, single);
493 }
494
495 static void write_1d(FILE* header_file, long array[], long dim, char* varname)
496 {
497    fprintf(header_file, "LOCATION long %s[] INITIALIZE(= {\n", varname);
498    for (int i = 0; i < dim; ++i)
499    {
500       if (i % 10 == 0)
501       {
502          if (i > 0)
503             fprintf(header_file, "\n");
504          fprintf(header_file, "  ");
505       }
506       fprintf(header_file, "%ld, ", array[i]);
507    }
508    fprintf(header_file, "\n});\n");
509 }
510
511 static void write_hints(FILE* header_file, long matrix[][HINTLEN], long dim1, long dim2, char* varname)
512 {
513    fprintf(header_file, "LOCATION long %s[][%ld] INITIALIZE(= {\n", varname, dim2);
514    for (int i = 0; i < dim1; ++i)
515    {
516       fprintf(header_file, "  {");
517       for (int j = 0; j < dim2; ++j)
518       {
519          fprintf(header_file, "%ld, ", matrix[i][j]);
520       }
521       fprintf(header_file, "},\n");
522    }
523    fprintf(header_file, "});\n");
524 }
525
526 static void write_file(FILE* header_file)
527 {
528    int MAXDIE;
529    for (int i=0; i<=4; i++) {
530       long x = 2*i+81;
531       if(RTEXT[x] != 0)
532          MAXDIE=i+1;
533    }
534
535
536    fprintf(header_file, "#ifndef DATABASE_H\n");
537    fprintf(header_file, "#define DATABASE_H\n");
538    fprintf(header_file, "\n");
539
540    fprintf(header_file, "#include \"common.h\"\n");
541    fprintf(header_file, "#define TABSIZ 330\n");
542    fprintf(header_file, "#define HNTSIZ 20\n");
543    fprintf(header_file, "#define TOKLEN %d\n", TOKLEN);
544    fprintf(header_file, "#define MAXDIE %d\n", MAXDIE);
545    fprintf(header_file, "\n");
546
547    fprintf(header_file, "\n");
548    fprintf(header_file, "#ifdef DEFINE_GLOBALS_FROM_INCLUDES\n");
549    fprintf(header_file, "#define LOCATION\n");
550    fprintf(header_file, "#define INITIALIZE(...) __VA_ARGS__\n");
551    fprintf(header_file, "#else\n");
552    fprintf(header_file, "#define LOCATION extern\n");
553    fprintf(header_file, "#define INITIALIZE(...)\n");
554    fprintf(header_file, "#endif\n");
555    fprintf(header_file, "\n");
556
557    // content variables
558    write_0d(header_file, TRNVLS, "TRNVLS");
559    write_0d(header_file, HNTMAX, "HNTMAX");
560    write_1d(header_file, OBJSND, NOBJECTS + 1, "OBJSND");
561    write_1d(header_file, OBJTXT, NOBJECTS + 1, "OBJTXT");
562    write_1d(header_file, COND, LOCSIZ + 1, "COND");
563    write_1d(header_file, KEY, LOCSIZ + 1, "KEY");
564    write_1d(header_file, LOCSND, LOCSIZ + 1, "LOCSND");
565    write_1d(header_file, CVAL, CLSMAX + 1, "CVAL");
566    write_1d(header_file, TRNVAL, TRNSIZ + 1, "TRNVAL");
567    write_1d(header_file, TRAVEL, TRVSIZ + 1, "TRAVEL");
568    write_1d(header_file, KTAB, TABSIZ + 1, "KTAB");
569    write_1d(header_file, ATAB, TABSIZ + 1, "ATAB");
570    write_1d(header_file, PLAC, NOBJECTS + 1, "PLAC");
571    write_1d(header_file, FIXD, NOBJECTS + 1, "FIXD");
572    write_1d(header_file, ACTSPK, VRBSIZ + 1, "ACTSPK");
573    write_hints(header_file, HINTS, HNTSIZ + 1, 5, "HINTS");
574
575    fprintf(header_file, "#undef LOCATION\n");
576    fprintf(header_file, "#undef INITIALIZE\n");
577    fprintf(header_file, "#endif\n");
578 }
579
580 int main(void)
581 {
582    FILE* database = fopen("adventure.text", "r");
583    read_database(database);
584    fclose(database);
585
586    FILE* header_file = fopen("database.h", "w");
587    write_file(header_file);
588    fclose(header_file);
589
590    return(EXIT_SUCCESS);
591 }