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