Address Gitlab issue #35
[open-adventure.git] / make_dungeon.py
1 #!/usr/bin/env python
2
3 # This is the open-adventure dungeon generator. It consumes a YAML description of
4 # the dungeon and outputs a dungeon.h and dungeon.c pair of C code files.
5 #
6 # The nontrivial part of this is the compilation of the YAML for
7 # movement rules to the travel array that's actually used by
8 # playermove().
9
10 import sys, yaml
11
12 yaml_name = "adventure.yaml"
13 h_name = "dungeon.h"
14 c_name = "dungeon.c"
15
16 statedefines = ""
17
18 h_template = """/* Generated from adventure.yaml - do not hand-hack! */
19 #ifndef DUNGEON_H
20 #define DUNGEON_H
21
22 #include <stdio.h>
23 #include <stdbool.h>
24
25 #define SILENT  -1      /* no sound */
26
27 /* Symbols for cond bits */
28 #define COND_LIT        0       /* Light */
29 #define COND_OILY       1       /* If bit 2 is on: on for oil, off for water */
30 #define COND_FLUID      2       /* Liquid asset, see bit 1 */
31 #define COND_NOARRR     3       /* Pirate doesn't go here unless following */
32 #define COND_NOBACK     4       /* Cannot use "back" to move away */
33 #define COND_ABOVE      5
34 #define COND_DEEP       6       /* Deep - e.g where dwarves are active */
35 #define COND_FOREST     7       /* In the forest */
36 #define COND_FORCED     8       /* Only one way in or out of here */
37 /* Bits past 10 indicate areas of interest to "hint" routines */
38 #define COND_HBASE      10      /* Base for location hint bits */
39 #define COND_HCAVE      11      /* Trying to get into cave */
40 #define COND_HBIRD      12      /* Trying to catch bird */
41 #define COND_HSNAKE     13      /* Trying to deal with snake */
42 #define COND_HMAZE      14      /* Lost in maze */
43 #define COND_HDARK      15      /* Pondering dark room */
44 #define COND_HWITT      16      /* At Witt's End */
45 #define COND_HCLIFF     17      /* Cliff with urn */
46 #define COND_HWOODS     18      /* Lost in forest */
47 #define COND_HOGRE      19      /* Trying to deal with ogre */
48 #define COND_HJADE      20      /* Found all treasures except jade */
49
50 typedef struct {{
51   const char** strs;
52   const int n;
53 }} string_group_t;
54
55 typedef struct {{
56   const string_group_t words;
57   const char* inventory;
58   int plac, fixd;
59   bool is_treasure;
60   const char** descriptions;
61   const char** sounds;
62   const char** texts;
63   const char** changes;
64 }} object_t;
65
66 typedef struct {{
67   const char* small;
68   const char* big;
69 }} descriptions_t;
70
71 typedef struct {{
72   descriptions_t description;
73   const long sound;
74   const bool loud;
75 }} location_t;
76
77 typedef struct {{
78   const char* query;
79   const char* yes_response;
80 }} obituary_t;
81
82 typedef struct {{
83   const int threshold;
84   const int point_loss;
85   const char* message;
86 }} turn_threshold_t;
87
88 typedef struct {{
89   const int threshold;
90   const char* message;
91 }} class_t;
92
93 typedef struct {{
94   const int number;
95   const int turns;
96   const int penalty;
97   const char* question;
98   const char* hint;
99 }} hint_t;
100
101 typedef struct {{
102   const string_group_t words;
103 }} motion_t;
104
105 typedef struct {{
106   const string_group_t words;
107   const char* message;
108   const bool noaction;
109 }} action_t;
110
111 enum condtype_t {{cond_goto, cond_pct, cond_carry, cond_with, cond_not}};
112 enum desttype_t {{dest_goto, dest_special, dest_speak}};
113
114 typedef struct {{
115   const long motion;
116   const long condtype;
117   const long condarg1;
118   const long condarg2;
119   const enum desttype_t desttype;
120   const long destval;
121   const bool nodwarves;
122   const bool stop;
123 }} travelop_t;
124
125 /* Abstract out the encoding of words in the travel array.  Gives us
126  * some hope of getting to a less cryptic representation than we
127  * inherited from FORTRAN, someday. To understand these, read the
128  * encoding description for travel.
129  */
130 #define T_TERMINATE(entry)      ((entry).motion == 1)
131
132 extern const location_t locations[];
133 extern const object_t objects[];
134 extern const char* arbitrary_messages[];
135 extern const class_t classes[];
136 extern const turn_threshold_t turn_thresholds[];
137 extern const obituary_t obituaries[];
138 extern const hint_t hints[];
139 extern long conditions[];
140 extern const motion_t motions[];
141 extern const action_t actions[];
142 extern const travelop_t travel[];
143 extern const long tkey[];
144 extern const char *ignore;
145
146 #define NLOCATIONS      {}
147 #define NOBJECTS        {}
148 #define NHINTS          {}
149 #define NCLASSES        {}
150 #define NDEATHS         {}
151 #define NTHRESHOLDS     {}
152 #define NMOTIONS    {}
153 #define NACTIONS        {}
154 #define NTRAVEL         {}
155 #define NKEYS           {}
156
157 #define BIRD_ENDSTATE   {}
158
159 enum arbitrary_messages_refs {{
160 {}
161 }};
162
163 enum locations_refs {{
164 {}
165 }};
166
167 enum object_refs {{
168 {}
169 }};
170
171 enum motion_refs {{
172 {}
173 }};
174
175 enum action_refs {{
176 {}
177 }};
178
179 /* State definitions */
180
181 {}
182 #endif /* end DUNGEON_H */
183 """
184
185 c_template = """/* Generated from adventure.yaml - do not hand-hack! */
186
187 #include "{}"
188
189 const char* arbitrary_messages[] = {{
190 {}
191 }};
192
193 const class_t classes[] = {{
194 {}
195 }};
196
197 const turn_threshold_t turn_thresholds[] = {{
198 {}
199 }};
200
201 const location_t locations[] = {{
202 {}
203 }};
204
205 const object_t objects[] = {{
206 {}
207 }};
208
209 const obituary_t obituaries[] = {{
210 {}
211 }};
212
213 const hint_t hints[] = {{
214 {}
215 }};
216
217 long conditions[] = {{
218 {}
219 }};
220
221 const motion_t motions[] = {{
222 {}
223 }};
224
225 const action_t actions[] = {{
226 {}
227 }};
228
229 const long tkey[] = {{{}}};
230
231 const travelop_t travel[] = {{
232 {}
233 }};
234
235 const char *ignore = \"{}\";
236
237 /* end */
238 """
239
240 def make_c_string(string):
241     """Render a Python string into C string literal format."""
242     if string == None:
243         return "NULL"
244     string = string.replace("\n", "\\n")
245     string = string.replace("\t", "\\t")
246     string = string.replace('"', '\\"')
247     string = string.replace("'", "\\'")
248     string = '"' + string + '"'
249     return string
250
251 def get_refs(l):
252     reflist = [x[0] for x in l]
253     ref_str = ""
254     for ref in reflist:
255         ref_str += "    {},\n".format(ref)
256     ref_str = ref_str[:-1] # trim trailing newline
257     return ref_str
258
259 def get_string_group(strings):
260     template = """{{
261             .strs = {},
262             .n = {},
263         }}"""
264     if strings == []:
265         strs = "NULL"
266     else:
267         strs = "(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}"
268     n = len(strings)
269     sg_str = template.format(strs, n)
270     return sg_str
271
272 def get_arbitrary_messages(arb):
273     template = """    {},
274 """
275     arb_str = ""
276     for item in arb:
277         arb_str += template.format(make_c_string(item[1]))
278     arb_str = arb_str[:-1] # trim trailing newline
279     return arb_str
280
281 def get_class_messages(cls):
282     template = """    {{
283         .threshold = {},
284         .message = {},
285     }},
286 """
287     cls_str = ""
288     for item in cls:
289         threshold = item["threshold"]
290         message = make_c_string(item["message"])
291         cls_str += template.format(threshold, message)
292     cls_str = cls_str[:-1] # trim trailing newline
293     return cls_str
294
295 def get_turn_thresholds(trn):
296     template = """    {{
297         .threshold = {},
298         .point_loss = {},
299         .message = {},
300     }},
301 """
302     trn_str = ""
303     for item in trn:
304         threshold = item["threshold"]
305         point_loss = item["point_loss"]
306         message = make_c_string(item["message"])
307         trn_str += template.format(threshold, point_loss, message)
308     trn_str = trn_str[:-1] # trim trailing newline
309     return trn_str
310
311 def get_locations(loc):
312     template = """    {{ // {}: {}
313         .description = {{
314             .small = {},
315             .big = {},
316         }},
317         .sound = {},
318         .loud = {},
319     }},
320 """
321     loc_str = ""
322     for (i, item) in enumerate(loc):
323         short_d = make_c_string(item[1]["description"]["short"])
324         long_d = make_c_string(item[1]["description"]["long"])
325         sound = item[1].get("sound", "SILENT")
326         loud = "true" if item[1].get("loud") else "false"
327         loc_str += template.format(i, item[0], short_d, long_d, sound, loud)
328     loc_str = loc_str[:-1] # trim trailing newline
329     return loc_str
330
331 def get_objects(obj):
332     template = """    {{ // {}: {}
333         .words = {},
334         .inventory = {},
335         .plac = {},
336         .fixd = {},
337         .is_treasure = {},
338         .descriptions = (const char* []) {{
339 {}
340         }},
341         .sounds = (const char* []) {{
342 {}
343         }},
344         .texts = (const char* []) {{
345 {}
346         }},
347         .changes = (const char* []) {{
348 {}
349         }},
350     }},
351 """
352     obj_str = ""
353     for (i, item) in enumerate(obj):
354         attr = item[1]
355         try:
356             words_str = get_string_group(attr["words"])
357         except KeyError:
358             words_str = get_string_group([])
359         i_msg = make_c_string(attr["inventory"])
360         descriptions_str = ""
361         if attr["descriptions"] == None:
362             descriptions_str = " " * 12 + "NULL,"
363         else:
364             labels = []
365             for l_msg in attr["descriptions"]:
366                 descriptions_str += " " * 12 + make_c_string(l_msg) + ",\n"
367             for label in attr.get("states", []):
368                 labels.append(label)
369             descriptions_str = descriptions_str[:-1] # trim trailing newline
370             if labels:
371                 global statedefines
372                 statedefines += "/* States for %s */\n" % item[0]
373                 for (j, label) in enumerate(labels):
374                     statedefines += "#define %s\t%d\n" % (label, j)
375                 statedefines += "\n"
376         sounds_str = ""
377         if attr.get("sounds") == None:
378             sounds_str = " " * 12 + "NULL,"
379         else:
380              for l_msg in attr["sounds"]:
381                  sounds_str += " " * 12 + make_c_string(l_msg) + ",\n"
382              sounds_str = sounds_str[:-1] # trim trailing newline
383         texts_str = ""
384         if attr.get("texts") == None:
385             texts_str = " " * 12 + "NULL,"
386         else:
387              for l_msg in attr["texts"]:
388                  texts_str += " " * 12 + make_c_string(l_msg) + ",\n"
389              texts_str = texts_str[:-1] # trim trailing newline
390         changes_str = ""
391         if attr.get("changes") == None:
392             changes_str = " " * 12 + "NULL,"
393         else:
394              for l_msg in attr["changes"]:
395                  changes_str += " " * 12 + make_c_string(l_msg) + ",\n"
396              changes_str = changes_str[:-1] # trim trailing newline
397         locs = attr.get("locations", ["LOC_NOWHERE", "LOC_NOWHERE"])
398         immovable = attr.get("immovable", False)
399         try:
400             if type(locs) == str:
401                 locs = [locs, -1 if immovable else 0]
402         except IndexError:
403             sys.stderr.write("dungeon: unknown object location in %s\n" % locs)
404             sys.exit(1)
405         treasure = "true" if attr.get("treasure") else "false"
406         obj_str += template.format(i, item[0], words_str, i_msg, locs[0], locs[1], treasure, descriptions_str, sounds_str, texts_str, changes_str)
407     obj_str = obj_str[:-1] # trim trailing newline
408     return obj_str
409
410 def get_obituaries(obit):
411     template = """    {{
412         .query = {},
413         .yes_response = {},
414     }},
415 """
416     obit_str = ""
417     for o in obit:
418         query = make_c_string(o["query"])
419         yes = make_c_string(o["yes_response"])
420         obit_str += template.format(query, yes)
421     obit_str = obit_str[:-1] # trim trailing newline
422     return obit_str
423
424 def get_hints(hnt, arb):
425     template = """    {{
426         .number = {},
427         .penalty = {},
428         .turns = {},
429         .question = {},
430         .hint = {},
431     }},
432 """
433     hnt_str = ""
434     md = dict(arb)
435     for member in hnt:
436         item = member["hint"]
437         number = item["number"]
438         penalty = item["penalty"]
439         turns = item["turns"]
440         question = make_c_string(item["question"])
441         hint = make_c_string(item["hint"])
442         hnt_str += template.format(number, penalty, turns, question, hint)
443     hnt_str = hnt_str[:-1] # trim trailing newline
444     return hnt_str
445
446 def get_condbits(locations):
447     cnd_str = ""
448     for (name, loc) in locations:
449         conditions = loc["conditions"]
450         hints = loc.get("hints") or []
451         flaglist = []
452         for flag in conditions:
453             if conditions[flag]:
454                 flaglist.append(flag)
455         line = "|".join([("(1<<COND_%s)" % f) for f in flaglist])
456         trail = "|".join([("(1<<COND_H%s)" % f['name']) for f in hints])
457         if trail:
458             line += "|" + trail
459         if line.startswith("|"):
460             line = line[1:]
461         if not line:
462             line = "0"
463         cnd_str += "    " + line + ",\t// " + name + "\n"
464     return cnd_str
465
466 def get_motions(motions):
467     template = """    {{
468         .words = {},
469     }},
470 """
471     mot_str = ""
472     for motion in motions:
473         contents = motion[1]
474         if contents["words"] == None:
475             words_str = get_string_group([])
476         else:
477             words_str = get_string_group(contents["words"])
478         mot_str += template.format(words_str)
479         global ignore
480         if contents.get("oldstyle", True) == False:
481             for word in contents["words"]:
482                 if len(word) == 1:
483                     ignore += word.upper()
484     return mot_str
485
486 def get_actions(actions):
487     template = """    {{
488         .words = {},
489         .message = {},
490         .noaction = {},
491     }},
492 """
493     act_str = ""
494     for action in actions:
495         contents = action[1]
496
497         if contents["words"] == None:
498             words_str = get_string_group([])
499         else:
500             words_str = get_string_group(contents["words"])
501
502         if contents["message"] == None:
503             message = "NULL"
504         else:
505             message = make_c_string(contents["message"])
506
507         if contents.get("noaction") == None:
508             noaction = "false"
509         else:
510             noaction = "true"
511
512         act_str += template.format(words_str, message, noaction)
513         global ignore
514         if contents.get("oldstyle", True) == False:
515             for word in contents["words"]:
516                 if len(word) == 1:
517                     ignore += word.upper()
518     act_str = act_str[:-1] # trim trailing newline
519     return act_str
520
521 def bigdump(arr):
522     out = ""
523     for (i, entry) in enumerate(arr):
524         if i % 10 == 0:
525             if out and out[-1] == ' ':
526                 out = out[:-1]
527             out += "\n    "
528         out += str(arr[i]).lower() + ", "
529     out = out[:-2] + "\n"
530     return out
531
532 def buildtravel(locs, objs):
533     assert len(locs) <= 300
534     assert len(objs) <= 100
535     # THIS CODE IS WAAAY MORE COMPLEX THAN IT NEEDS TO BE.  It's the
536     # result of a massive refactoring exercise that concentrated all
537     # the old nastiness in one spot. It hasn't been finally simplified
538     # because there's no need to do it until one of the asserions
539     # fails. Hint: if you try cleaning this up, the acceptance test is
540     # simple - the output dungeon.c must not change.
541     #
542     # This function first compiles the YAML to a form identical to the
543     # data in section 3 of the old adventure.text file, then a second
544     # stage unpacks that data into the travel array.  Here are the
545     # rules of that intermediate form:
546     #
547     # Each row of data contains a location number (X), a second
548     # location number (Y), and a list of motion numbers (see section 4).
549     # each motion represents a verb which will go to Y if currently at X.
550     # Y, in turn, is interpreted as follows.  Let M=Y/1000, N=Y mod 1000.
551     #           If N<=300       it is the location to go to.
552     #           If 300<N<=500   N-300 is used in a computed goto to
553     #                                   a section of special code.
554     #           If N>500        message N-500 from section 6 is printed,
555     #                                   and he stays wherever he is.
556     # Meanwhile, M specifies the conditions on the motion.
557     #           If M=0          it's unconditional.
558     #           If 0<M<100      it is done with M% probability.
559     #           If M=100        unconditional, but forbidden to dwarves.
560     #           If 100<M<=200   he must be carrying object M-100.
561     #           If 200<M<=300   must be carrying or in same room as M-200.
562     #           If 300<M<=400   game.prop(M % 100) must *not* be 0.
563     #           If 400<M<=500   game.prop(M % 100) must *not* be 1.
564     #           If 500<M<=600   game.prop(M % 100) must *not* be 2, etc.
565     # If the condition (if any) is not met, then the next *different*
566     # "destination" value is used (unless it fails to meet *its* conditions,
567     # in which case the next is found, etc.).  Typically, the next dest will
568     # be for one of the same verbs, so that its only use is as the alternate
569     # destination for those verbs.  For instance:
570     #           15      110022  29      31      34      35      23      43
571     #           15      14      29
572     # This says that, from loc 15, any of the verbs 29, 31, etc., will take
573     # him to 22 if he's carrying object 10, and otherwise will go to 14.
574     #           11      303008  49
575     #           11      9       50
576     # This says that, from 11, 49 takes him to 8 unless game.prop[3]=0, in which
577     # case he goes to 9.  Verb 50 takes him to 9 regardless of game.prop[3].
578     ltravel = []
579     verbmap = {}
580     for i, motion in enumerate(db["motions"]):
581         try:
582             for word in motion[1]["words"]:
583                 verbmap[word.upper()] = i
584         except TypeError:
585             pass
586     def dencode(action, name):
587         "Decode a destination number"
588         if action[0] == "goto":
589             try:
590                 return locnames.index(action[1])
591             except ValueError:
592                 sys.stderr.write("dungeon: unknown location %s in goto clause of %s\n" % (action[1], name))
593         elif action[0] == "special":
594             return 300 + action[1]
595         elif action[0] == "speak":
596             try:
597                 return 500 + msgnames.index(action[1])
598             except ValueError:
599                 sys.stderr.write("dungeon: unknown location %s in carry clause of %s\n" % (cond[1], name))
600         else:
601             print(cond)
602             raise ValueError
603     def cencode(cond, name):
604         if cond is None:
605             return 0
606         elif cond == ["nodwarves"]:
607             return 100
608         elif cond[0] == "pct":
609             return cond[1]
610         elif cond[0] == "carry":
611             try:
612                 return 100 + objnames.index(cond[1])
613             except ValueError:
614                 sys.stderr.write("dungeon: unknown object name %s in carry clause of %s\n" % (cond[1], name))
615                 sys.exit(1)
616         elif cond[0] == "with":
617             try:
618                 return 200 + objnames.index(cond[1])
619             except IndexError:
620                 sys.stderr.write("dungeon: unknown object name %s in with clause of \n" % (cond[1], name))
621                 sys.exit(1)
622         elif cond[0] == "not":
623             try:
624                 obj = objnames.index(cond[1])
625                 if type(cond[2]) == int:
626                     state = cond[2]
627                 elif cond[2] in objs[obj][1].get("states", []):
628                     state = objs[obj][1].get("states").index(cond[2])
629                 else:
630                     for (i, stateclause) in enumerate(objs[obj][1]["descriptions"]):
631                         if type(stateclause) == list:
632                             if stateclause[0] == cond[2]:
633                                 state = i
634                                 break
635                     else:
636                         sys.stderr.write("dungeon: unmatched state symbol %s in not clause of %s\n" % (cond[2], name))
637                         sys.exit(0);
638                 return 300 + obj + 100 * state
639             except ValueError:
640                 sys.stderr.write("dungeon: unknown object name %s in not clause of %s\n" % (cond[1], name))
641                 sys.exit(1)
642         else:
643             print(cond)
644             raise ValueError
645
646     for (i, (name, loc)) in enumerate(locs):
647         if "travel" in loc:
648             for rule in loc["travel"]:
649                 tt = [i]
650                 dest = dencode(rule["action"], name) + 1000 * cencode(rule.get("cond"), name)
651                 tt.append(dest)
652                 tt += [motionnames[verbmap[e]].upper() for e in rule["verbs"]]
653                 if not rule["verbs"]:
654                     tt.append(1)        # Magic dummy entry for null rules
655                 ltravel.append(tuple(tt))
656
657     # At this point the ltravel data is in the Section 3
658     # representation from the FORTRAN version.  Next we perform the
659     # same mapping into wgat used to be the runtime format.
660
661     travel = [[0, "LOC_NOWHERE", 0, 0, 0, 0, 0, 0, "false", "false"]]
662     tkey = [0]
663     oldloc = 0
664     while ltravel:
665         rule = list(ltravel.pop(0))
666         loc = rule.pop(0)
667         newloc = rule.pop(0)
668         if loc != oldloc:
669             tkey.append(len(travel))
670             oldloc = loc 
671         elif travel:
672             travel[-1][-1] = "false" if travel[-1][-1] == "true" else "true" 
673         while rule:
674             cond = newloc // 1000
675             nodwarves = (cond == 100)
676             if cond == 0:
677                 condtype = "cond_goto"
678                 condarg1 = condarg2 = 0
679             elif cond < 100:
680                 condtype = "cond_pct"
681                 condarg1 = cond
682                 condarg2 = 0
683             elif cond == 100:
684                 condtype = "cond_goto"
685                 condarg1 = 100
686                 condarg2 = 0
687             elif cond <= 200:
688                 condtype = "cond_carry"
689                 condarg1 = objnames[cond - 100]
690                 condarg2 = 0
691             elif cond <= 300:
692                 condtype = "cond_with"
693                 condarg1 = objnames[cond - 200]
694                 condarg2 = 0
695             else:
696                 condtype = "cond_not"
697                 condarg1 = cond % 100
698                 condarg2 = (cond - 300) // 100.
699             dest = newloc % 1000
700             if dest <= 300:
701                 desttype = "dest_goto";
702                 destval = locnames[dest]
703             elif dest > 500:
704                 desttype = "dest_speak";
705                 destval = msgnames[dest - 500]
706             else:
707                 desttype = "dest_special";
708                 destval = locnames[dest - 300]
709             travel.append([len(tkey)-1,
710                            locnames[len(tkey)-1],
711                            rule.pop(0),
712                            condtype,
713                            condarg1,
714                            condarg2,
715                            desttype,
716                            destval,
717                            "true" if nodwarves else "false",
718                            "false"])
719         travel[-1][-1] = "true"
720     return (travel, tkey)
721
722 def get_travel(travel):
723     template = """    {{ // from {}: {}
724         .motion = {},
725         .condtype = {},
726         .condarg1 = {},
727         .condarg2 = {},
728         .desttype = {},
729         .destval = {},
730         .nodwarves = {},
731         .stop = {},
732     }},
733 """
734     out = ""
735     for entry in travel:
736         out += template.format(*entry)
737     out = out[:-1] # trim trailing newline
738     return out
739
740 if __name__ == "__main__":
741     with open(yaml_name, "r") as f:
742         db = yaml.load(f)
743
744     locnames = [x[0] for x in db["locations"]]
745     msgnames = [el[0] for el in db["arbitrary_messages"]]
746     objnames = [el[0] for el in db["objects"]]
747     motionnames = [el[0] for el in db["motions"]]
748
749     (travel, tkey) = buildtravel(db["locations"],
750                                  db["objects"])
751     ignore = ""
752     c = c_template.format(
753         h_name,
754         get_arbitrary_messages(db["arbitrary_messages"]),
755         get_class_messages(db["classes"]),
756         get_turn_thresholds(db["turn_thresholds"]),
757         get_locations(db["locations"]),
758         get_objects(db["objects"]),
759         get_obituaries(db["obituaries"]),
760         get_hints(db["hints"], db["arbitrary_messages"]),
761         get_condbits(db["locations"]),
762         get_motions(db["motions"]),
763         get_actions(db["actions"]),
764         bigdump(tkey),
765         get_travel(travel), 
766         ignore,
767     )
768
769     # 0-origin index of birds's last song.  Bird should
770     # die after player hears this.
771     deathbird = len(dict(db["objects"])["BIRD"]["sounds"]) - 1
772
773     h = h_template.format(
774         len(db["locations"])-1,
775         len(db["objects"])-1,
776         len(db["hints"]),
777         len(db["classes"])-1,
778         len(db["obituaries"]),
779         len(db["turn_thresholds"]),
780         len(db["motions"]),
781         len(db["actions"]),
782         len(travel),
783         len(tkey),
784         deathbird,
785         get_refs(db["arbitrary_messages"]),
786         get_refs(db["locations"]),
787         get_refs(db["objects"]),
788         get_refs(db["motions"]),
789         get_refs(db["actions"]),
790         statedefines,
791     )
792
793     with open(h_name, "w") as hf:
794         hf.write(h)
795
796     with open(c_name, "w") as cf:
797         cf.write(c)
798
799 # end