Fix typo and type mismatch.
[open-adventure.git] / newdungeon.py
1 #!/usr/bin/python3
2
3 # This is the new open-adventure dungeon generator. It'll eventually
4 # replace the existing dungeon.c It currently outputs a .h and .c pair
5 # for C code.
6
7 import yaml
8
9 yaml_name = "adventure.yaml"
10 h_name = "newdb.h"
11 c_name = "newdb.c"
12
13 statedefines = ""
14
15 h_template = """/* Generated from adventure.yaml - do not hand-hack! */
16 #ifndef NEWDB_H
17 #define NEWDB_H
18
19 #include <stdio.h>
20 #include <stdbool.h>
21
22 #define SILENT  -1      /* no sound */
23
24 /* Symbols for cond bits */
25 #define COND_LIT        0       /* Light */
26 #define COND_OILY       1       /* If bit 2 is on: on for oil, off for water */
27 #define COND_FLUID      2       /* Liquid asset, see bit 1 */
28 #define COND_NOARRR     3       /* Pirate doesn't go here unless following */
29 #define COND_NOBACK     4       /* Cannot use "back" to move away */
30 #define COND_ABOVE      5
31 #define COND_DEEP       6       /* Deep - e.g where dwarves are active */
32 #define COND_FOREST     7       /* In the forest */
33 #define COND_FORCED     8       /* Only one way in or out of here */
34 /* Bits past 10 indicate areas of interest to "hint" routines */
35 #define COND_HBASE      10      /* Base for location hint bits */
36 #define COND_HCAVE      11      /* Trying to get into cave */
37 #define COND_HBIRD      12      /* Trying to catch bird */
38 #define COND_HSNAKE     13      /* Trying to deal with snake */
39 #define COND_HMAZE      14      /* Lost in maze */
40 #define COND_HDARK      15      /* Pondering dark room */
41 #define COND_HWITT      16      /* At Witt's End */
42 #define COND_HCLIFF     17      /* Cliff with urn */
43 #define COND_HWOODS     18      /* Lost in forest */
44 #define COND_HOGRE      19      /* Trying to deal with ogre */
45 #define COND_HJADE      20      /* Found all treasures except jade */
46
47 typedef struct {{
48   const char* inventory;
49   int plac, fixd;
50   bool is_treasure;
51   const char** longs;
52   const char** sounds;
53   const char** texts;
54 }} object_description_t;
55
56 typedef struct {{
57   const char* small;
58   const char* big;
59 }} descriptions_t;
60
61 typedef struct {{
62   descriptions_t description;
63   const long sound;
64   const bool loud;
65 }} location_t;
66
67 typedef struct {{
68   const char* query;
69   const char* yes_response;
70 }} obituary_t;
71
72 typedef struct {{
73   const int threshold;
74   const int point_loss;
75   const char* message;
76 }} turn_threshold_t;
77
78 typedef struct {{
79   const int threshold;
80   const char* message;
81 }} class_t;
82
83 typedef struct {{
84   const int number;
85   const int turns;
86   const int penalty;
87   const char* question;
88   const char* hint;
89 }} hint_t;
90
91 typedef struct {{
92   const char* word;
93   const int type;
94   const int value;
95 }} vocabulary_t;
96
97 extern const location_t locations[];
98 extern const object_description_t object_descriptions[];
99 extern const const char* arbitrary_messages[];
100 extern const const class_t classes[];
101 extern const turn_threshold_t turn_thresholds[];
102 extern const obituary_t obituaries[];
103 extern const hint_t hints[];
104 extern long conditions[];
105 extern const vocabulary_t vocabulary[];
106
107 #define NLOCATIONS              {}
108 #define NOBJECTS        {}
109 #define NHINTS          {}
110 #define NCLASSES        {}
111 #define NDEATHS         {}
112 #define NTHRESHOLDS     {}
113
114 enum arbitrary_messages_refs {{
115 {}
116 }};
117
118 enum locations_refs {{
119 {}
120 }};
121
122 enum object_descriptions_refs {{
123 {}
124 }};
125
126 /* State definitions */
127
128 {}
129 #endif /* end NEWDB_H */
130 """
131
132 c_template = """/* Generated from adventure.yaml - do not hand-hack! */
133
134 #include "common.h"
135 #include "{}"
136
137 const char* arbitrary_messages[] = {{
138 {}
139 }};
140
141 const class_t classes[] = {{
142 {}
143 }};
144
145 const turn_threshold_t turn_thresholds[] = {{
146 {}
147 }};
148
149 const location_t locations[] = {{
150 {}
151 }};
152
153 const object_description_t object_descriptions[] = {{
154 {}
155 }};
156
157 const obituary_t obituaries[] = {{
158 {}
159 }};
160
161 const hint_t hints[] = {{
162 {}
163 }};
164
165 long conditions[] = {{
166 {}
167 }};
168
169 const vocabulary_t vocabulary[] = {{
170 {}
171 }};
172
173 /* end */
174 """
175
176 def make_c_string(string):
177     """Render a Python string into C string literal format."""
178     if string == None:
179         return "NULL"
180     string = string.replace("\n", "\\n")
181     string = string.replace("\t", "\\t")
182     string = string.replace('"', '\\"')
183     string = string.replace("'", "\\'")
184     string = '"' + string + '"'
185     return string
186
187 def get_refs(l):
188     reflist = [x[0] for x in l]
189     ref_str = ""
190     for ref in reflist:
191         ref_str += "    {},\n".format(ref)
192     ref_str = ref_str[:-1] # trim trailing newline
193     return ref_str
194
195 def get_arbitrary_messages(arb):
196     template = """    {},
197 """
198     arb_str = ""
199     for item in arb:
200         arb_str += template.format(make_c_string(item[1]))
201     arb_str = arb_str[:-1] # trim trailing newline
202     return arb_str
203
204 def get_class_messages(cls):
205     template = """    {{
206         .threshold = {},
207         .message = {},
208     }},
209 """
210     cls_str = ""
211     for item in cls:
212         threshold = item["threshold"]
213         message = make_c_string(item["message"])
214         cls_str += template.format(threshold, message)
215     cls_str = cls_str[:-1] # trim trailing newline
216     return cls_str
217
218 def get_turn_thresholds(trn):
219     template = """    {{
220         .threshold = {},
221         .point_loss = {},
222         .message = {},
223     }},
224 """
225     trn_str = ""
226     for item in trn:
227         threshold = item["threshold"]
228         point_loss = item["point_loss"]
229         message = make_c_string(item["message"])
230         trn_str += template.format(threshold, point_loss, message)
231     trn_str = trn_str[:-1] # trim trailing newline
232     return trn_str
233
234 def get_locations(loc):
235     template = """    {{ // {}
236         .description = {{
237             .small = {},
238             .big = {},
239         }},
240         .sound = {},
241         .loud = {},
242     }},
243 """
244     loc_str = ""
245     for (i, item) in enumerate(loc):
246         short_d = make_c_string(item[1]["description"]["short"])
247         long_d = make_c_string(item[1]["description"]["long"])
248         sound = item[1].get("sound", "SILENT")
249         loud = "true" if item[1].get("loud") else "false"
250         loc_str += template.format(i, short_d, long_d, sound, loud)
251     loc_str = loc_str[:-1] # trim trailing newline
252     return loc_str
253
254 def get_object_descriptions(obj):
255     template = """    {{ // {}
256         .inventory = {},
257         .plac = {},
258         .fixd = {},
259         .is_treasure = {},
260         .longs = (const char* []) {{
261 {}
262         }},
263         .sounds = (const char* []) {{
264 {}
265         }},
266         .texts = (const char* []) {{
267 {}
268         }},
269     }},
270 """
271     obj_str = ""
272     for (i, item) in enumerate(obj):
273         attr = item[1]
274         i_msg = make_c_string(attr["inventory"])
275         longs_str = ""
276         if attr["longs"] == None:
277             longs_str = " " * 12 + "NULL,"
278         else:
279             labels = []
280             for l_msg in attr["longs"]:
281                 if not isinstance(l_msg, str):
282                     labels.append(l_msg)
283                     l_msg = l_msg[1]
284                 longs_str += " " * 12 + make_c_string(l_msg) + ",\n"
285             longs_str = longs_str[:-1] # trim trailing newline
286             if labels:
287                 global statedefines
288                 statedefines += "/* States for %s */\n" % item[0]
289                 for (i, (label, message)) in enumerate(labels):
290                     if len(message) >= 45:
291                         message = message[:45] + "..."
292                     statedefines += "#define %s\t%d /* %s */\n" % (label, i, message)
293                 statedefines += "\n"
294         sounds_str = ""
295         if attr.get("sounds") == None:
296             sounds_str = " " * 12 + "NULL,"
297         else:
298              for l_msg in attr["sounds"]:
299                  sounds_str += " " * 12 + make_c_string(l_msg) + ",\n"
300              sounds_str = sounds_str[:-1] # trim trailing newline
301         texts_str = ""
302         if attr.get("texts") == None:
303             texts_str = " " * 12 + "NULL,"
304         else:
305              for l_msg in attr["texts"]:
306                  texts_str += " " * 12 + make_c_string(l_msg) + ",\n"
307              texts_str = texts_str[:-1] # trim trailing newline
308         locs = attr.get("locations", ["LOC_NOWHERE", "LOC_NOWHERE"])
309         immovable = attr.get("immovable", False)
310         try:
311             if type(locs) == str:
312                 locs = [locnames.index(locs), -1 if immovable else 0]
313             else:
314                 locs = [locnames.index(x) for x in locs]
315         except IndexError:
316             sys.stderr.write("dungeon: unknown object location in %s\n" % locs)
317             sys.exit(1)
318         treasure = "true" if attr.get("treasure") else "false"
319         obj_str += template.format(i, i_msg, locs[0], locs[1], treasure, longs_str, sounds_str, texts_str)
320     obj_str = obj_str[:-1] # trim trailing newline
321     return obj_str
322
323 def get_obituaries(obit):
324     template = """    {{
325         .query = {},
326         .yes_response = {},
327     }},
328 """
329     obit_str = ""
330     for o in obit:
331         query = make_c_string(o["query"])
332         yes = make_c_string(o["yes_response"])
333         obit_str += template.format(query, yes)
334     obit_str = obit_str[:-1] # trim trailing newline
335     return obit_str
336
337 def get_hints(hnt, arb):
338     template = """    {{
339         .number = {},
340         .penalty = {},
341         .turns = {},
342         .question = {},
343         .hint = {},
344     }},
345 """
346     hnt_str = ""
347     md = dict(arb)
348     for member in hnt:
349         item = member["hint"]
350         number = item["number"]
351         penalty = item["penalty"]
352         turns = item["turns"]
353         question = make_c_string(item["question"])
354         hint = make_c_string(item["hint"])
355         hnt_str += template.format(number, penalty, turns, question, hint)
356     hnt_str = hnt_str[:-1] # trim trailing newline
357     return hnt_str
358
359 def get_condbits(locations):
360     cnd_str = ""
361     for (name, loc) in locations:
362         conditions = loc["conditions"]
363         hints = loc.get("hints") or []
364         flaglist = []
365         for flag in conditions:
366             if conditions[flag]:
367                 flaglist.append(flag)
368         line = "|".join([("(1<<COND_%s)" % f) for f in flaglist])
369         trail = "|".join([("(1<<COND_H%s)" % f['name']) for f in hints])
370         if trail:
371             line += "|" + trail
372         if line.startswith("|"):
373             line = line[1:]
374         if not line:
375             line = "0"
376         cnd_str += "    " + line + ",\t// " + name + "\n"
377     return cnd_str
378
379 def recompose(type_word, value):
380     "Compose the internal code for a vocabulary word from its YAML entry"
381     parts = ("motion", "action", "object", "special")
382     try:
383         return value + 1000 * parts.index(type_word)
384     except KeyError:
385         sys.stderr.write("dungeon: %s is not a known word\n" % word)
386         sys.exit(1)
387     except IndexError:
388         sys.stderr.write("%s is not a known word classifier" % attrs["type"])
389         sys.exit(1)
390
391 def get_vocabulary(vocabulary):
392     template = """    {{
393         .word = {},
394         .type = {},
395         .value = {},
396     }},
397 """
398     voc_str = ""
399     for vocab in vocabulary:
400         word = make_c_string(vocab["word"])
401         type_code = recompose(vocab["type"], vocab["value"])
402         value = vocab["value"]
403         voc_str += template.format(word, type_code, value)
404     voc_str = voc_str[:-1] # trim trailing newline
405     return voc_str
406
407 if __name__ == "__main__":
408     with open(yaml_name, "r") as f:
409         db = yaml.load(f)
410
411     locnames = [x[0] for x in db["locations"]]
412
413     c = c_template.format(
414         h_name,
415         get_arbitrary_messages(db["arbitrary_messages"]),
416         get_class_messages(db["classes"]),
417         get_turn_thresholds(db["turn_thresholds"]),
418         get_locations(db["locations"]),
419         get_object_descriptions(db["object_descriptions"]),
420         get_obituaries(db["obituaries"]),
421         get_hints(db["hints"], db["arbitrary_messages"]),
422         get_condbits(db["locations"]),
423         get_vocabulary(db["vocabulary"]),
424     )
425
426     h = h_template.format(
427         len(db["locations"])-1,
428         len(db["object_descriptions"])-1,
429         len(db["hints"]),
430         len(db["classes"]),
431         len(db["obituaries"]),
432         len(db["turn_thresholds"]),
433         get_refs(db["arbitrary_messages"]),
434         get_refs(db["locations"]),
435         get_refs(db["object_descriptions"]),
436         statedefines,
437     )
438
439     with open(h_name, "w") as hf:
440         hf.write(h)
441
442     with open(c_name, "w") as cf:
443         cf.write(c)
444
445 # end