Inline the hint messages.
[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 extern const location_t locations[];
92 extern const object_description_t object_descriptions[];
93 extern const const char* arbitrary_messages[];
94 extern const const class_t classes[];
95 extern const turn_threshold_t turn_thresholds[];
96 extern const obituary_t obituaries[];
97 extern const hint_t hints[];
98 extern long conditions[];
99
100 #define NLOCATIONS              {}
101 #define NOBJECTS        {}
102 #define NHINTS          {}
103 #define NCLASSES        {}
104 #define NDEATHS         {}
105 #define NTHRESHOLDS     {}
106
107 enum arbitrary_messages_refs {{
108 {}
109 }};
110
111 enum locations_refs {{
112 {}
113 }};
114
115 enum object_descriptions_refs {{
116 {}
117 }};
118
119 /* State definitions */
120
121 {}
122 #endif /* end NEWDB_H */
123 """
124
125 c_template = """/* Generated from adventure.yaml - do not hand-hack! */
126
127 #include "common.h"
128 #include "{}"
129
130 const char* arbitrary_messages[] = {{
131 {}
132 }};
133
134 const class_t classes[] = {{
135 {}
136 }};
137
138 const turn_threshold_t turn_thresholds[] = {{
139 {}
140 }};
141
142 const location_t locations[] = {{
143 {}
144 }};
145
146 const object_description_t object_descriptions[] = {{
147 {}
148 }};
149
150 const obituary_t obituaries[] = {{
151 {}
152 }};
153
154 const hint_t hints[] = {{
155 {}
156 }};
157
158 long conditions[] = {{
159 {}
160 }};
161
162 /* end */
163 """
164
165 def make_c_string(string):
166     """Render a Python string into C string literal format."""
167     if string == None:
168         return "NULL"
169     string = string.replace("\n", "\\n")
170     string = string.replace("\t", "\\t")
171     string = string.replace('"', '\\"')
172     string = string.replace("'", "\\'")
173     string = '"' + string + '"'
174     return string
175
176 def get_refs(l):
177     reflist = [x[0] for x in l]
178     ref_str = ""
179     for ref in reflist:
180         ref_str += "    {},\n".format(ref)
181     ref_str = ref_str[:-1] # trim trailing newline
182     return ref_str
183
184 def get_arbitrary_messages(arb):
185     template = """    {},
186 """
187     arb_str = ""
188     for item in arb:
189         arb_str += template.format(make_c_string(item[1]))
190     arb_str = arb_str[:-1] # trim trailing newline
191     return arb_str
192
193 def get_class_messages(cls):
194     template = """    {{
195         .threshold = {},
196         .message = {},
197     }},
198 """
199     cls_str = ""
200     for item in cls:
201         threshold = item["threshold"]
202         message = make_c_string(item["message"])
203         cls_str += template.format(threshold, message)
204     cls_str = cls_str[:-1] # trim trailing newline
205     return cls_str
206
207 def get_turn_thresholds(trn):
208     template = """    {{
209         .threshold = {},
210         .point_loss = {},
211         .message = {},
212     }},
213 """
214     trn_str = ""
215     for item in trn:
216         threshold = item["threshold"]
217         point_loss = item["point_loss"]
218         message = make_c_string(item["message"])
219         trn_str += template.format(threshold, point_loss, message)
220     trn_str = trn_str[:-1] # trim trailing newline
221     return trn_str
222
223 def get_locations(loc):
224     template = """    {{ // {}
225         .description = {{
226             .small = {},
227             .big = {},
228         }},
229         .sound = {},
230         .loud = {},
231     }},
232 """
233     loc_str = ""
234     for (i, item) in enumerate(loc):
235         short_d = make_c_string(item[1]["description"]["short"])
236         long_d = make_c_string(item[1]["description"]["long"])
237         sound = item[1].get("sound", "SILENT")
238         loud = "true" if item[1].get("loud") else "false"
239         loc_str += template.format(i, short_d, long_d, sound, loud)
240     loc_str = loc_str[:-1] # trim trailing newline
241     return loc_str
242
243 def get_object_descriptions(obj):
244     template = """    {{ // {}
245         .inventory = {},
246         .plac = {},
247         .fixd = {},
248         .is_treasure = {},
249         .longs = (const char* []) {{
250 {}
251         }},
252         .sounds = (const char* []) {{
253 {}
254         }},
255         .texts = (const char* []) {{
256 {}
257         }},
258     }},
259 """
260     obj_str = ""
261     for (i, item) in enumerate(obj):
262         attr = item[1]
263         i_msg = make_c_string(attr["inventory"])
264         longs_str = ""
265         if attr["longs"] == None:
266             longs_str = " " * 12 + "NULL,"
267         else:
268             labels = []
269             for l_msg in attr["longs"]:
270                 if not isinstance(l_msg, str):
271                     labels.append(l_msg)
272                     l_msg = l_msg[1]
273                 longs_str += " " * 12 + make_c_string(l_msg) + ",\n"
274             longs_str = longs_str[:-1] # trim trailing newline
275             if labels:
276                 global statedefines
277                 statedefines += "/* States for %s */\n" % item[0]
278                 for (i, (label, message)) in enumerate(labels):
279                     if len(message) >= 45:
280                         message = message[:45] + "..."
281                     statedefines += "#define %s\t%d /* %s */\n" % (label, i, message)
282                 statedefines += "\n"
283         sounds_str = ""
284         if attr.get("sounds") == None:
285             sounds_str = " " * 12 + "NULL,"
286         else:
287              for l_msg in attr["sounds"]:
288                  sounds_str += " " * 12 + make_c_string(l_msg) + ",\n"
289              sounds_str = sounds_str[:-1] # trim trailing newline
290         texts_str = ""
291         if attr.get("texts") == None:
292             texts_str = " " * 12 + "NULL,"
293         else:
294              for l_msg in attr["texts"]:
295                  texts_str += " " * 12 + make_c_string(l_msg) + ",\n"
296              texts_str = texts_str[:-1] # trim trailing newline
297         locs = attr.get("locations", ["LOC_NOWHERE", "LOC_NOWHERE"])
298         immovable = attr.get("immovable", False)
299         try:
300             if type(locs) == str:
301                 locs = [locnames.index(locs), -1 if immovable else 0]
302             else:
303                 locs = [locnames.index(x) for x in locs]
304         except IndexError:
305             sys.stderr.write("dungeon: unknown object location in %s\n" % locs)
306             sys.exit(1)
307         treasure = "true" if attr.get("treasure") else "false"
308         obj_str += template.format(i, i_msg, locs[0], locs[1], treasure, longs_str, sounds_str, texts_str)
309     obj_str = obj_str[:-1] # trim trailing newline
310     return obj_str
311
312 def get_obituaries(obit):
313     template = """    {{
314         .query = {},
315         .yes_response = {},
316     }},
317 """
318     obit_str = ""
319     for o in obit:
320         query = make_c_string(o["query"])
321         yes = make_c_string(o["yes_response"])
322         obit_str += template.format(query, yes)
323     obit_str = obit_str[:-1] # trim trailing newline
324     return obit_str
325
326 def get_hints(hnt, arb):
327     template = """    {{
328         .number = {},
329         .penalty = {},
330         .turns = {},
331         .question = {},
332         .hint = {},
333     }},
334 """
335     hnt_str = ""
336     md = dict(arb)
337     for member in hnt:
338         item = member["hint"]
339         number = item["number"]
340         penalty = item["penalty"]
341         turns = item["turns"]
342         question = make_c_string(item["question"])
343         hint = make_c_string(item["hint"])
344         hnt_str += template.format(number, penalty, turns, question, hint)
345     hnt_str = hnt_str[:-1] # trim trailing newline
346     return hnt_str
347
348 def get_condbits(locations):
349     cnd_str = ""
350     for (name, loc) in locations:
351         conditions = loc["conditions"]
352         hints = loc.get("hints") or []
353         flaglist = []
354         for flag in conditions:
355             if conditions[flag]:
356                 flaglist.append(flag)
357         line = "|".join([("(1<<COND_%s)" % f) for f in flaglist])
358         trail = "|".join([("(1<<COND_H%s)" % f['name']) for f in hints])
359         if trail:
360             line += "|" + trail
361         if line.startswith("|"):
362             line = line[1:]
363         if not line:
364             line = "0"
365         cnd_str += "    " + line + ",\t// " + name + "\n"
366     return cnd_str
367
368 def recompose(word):
369     "Compose the internal code for a vocabulary word from its YAML entry"
370     parts = ("motion", "action", "object", "special")
371     try:
372         attrs = db["vocabulary"][word]
373         return attrs["value"] + 1000 * parts.index(attrs["type"])
374     except KeyError:
375         sys.stderr.write("dungeon: %s is not a known word\n" % word)
376         sys.exit(1)
377     except IndexError:
378         sys.stderr.write("%s is not a known word classifier" % attrs["type"])
379         sys.exit(1)
380
381 if __name__ == "__main__":
382     with open(yaml_name, "r") as f:
383         db = yaml.load(f)
384
385     locnames = [x[0] for x in db["locations"]]
386
387     c = c_template.format(
388         h_name,
389         get_arbitrary_messages(db["arbitrary_messages"]),
390         get_class_messages(db["classes"]),
391         get_turn_thresholds(db["turn_thresholds"]),
392         get_locations(db["locations"]),
393         get_object_descriptions(db["object_descriptions"]),
394         get_obituaries(db["obituaries"]),
395         get_hints(db["hints"], db["arbitrary_messages"]),
396         get_condbits(db["locations"]),
397     )
398
399     h = h_template.format(
400         len(db["locations"])-1,
401         len(db["object_descriptions"])-1,
402         len(db["hints"]),
403         len(db["classes"]),
404         len(db["obituaries"]),
405         len(db["turn_thresholds"]),
406         get_refs(db["arbitrary_messages"]),
407         get_refs(db["locations"]),
408         get_refs(db["object_descriptions"]),
409         statedefines,
410     )
411
412     with open(h_name, "w") as hf:
413         hf.write(h)
414
415     with open(c_name, "w") as cf:
416         cf.write(c)
417
418 # end