#!/usr/bin/python3 # 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. # # The nontrivial part of this is the compilation of the YAML for # movement rules to the travel array that's actually used by # playermove(). This program first compiles the YAML to a form # identical to the data in section 3 of the old adventure.text file, # then a second stage unpacks that data into the travel array. # # Here are the rules of the intermediate form: # # Each row of data contains a location number (X), a second # location number (Y), and a list of motion numbers (see section 4). # each motion represents a verb which will go to Y if currently at X. # Y, in turn, is interpreted as follows. Let M=Y/1000, N=Y mod 1000. # If N<=300 it is the location to go to. # If 300500 message N-500 from section 6 is printed, # and he stays wherever he is. # Meanwhile, M specifies the conditions on the motion. # If M=0 it's unconditional. # If 0 #include #define SILENT -1 /* no sound */ /* Symbols for cond bits */ #define COND_LIT 0 /* Light */ #define COND_OILY 1 /* If bit 2 is on: on for oil, off for water */ #define COND_FLUID 2 /* Liquid asset, see bit 1 */ #define COND_NOARRR 3 /* Pirate doesn't go here unless following */ #define COND_NOBACK 4 /* Cannot use "back" to move away */ #define COND_ABOVE 5 #define COND_DEEP 6 /* Deep - e.g where dwarves are active */ #define COND_FOREST 7 /* In the forest */ #define COND_FORCED 8 /* Only one way in or out of here */ /* Bits past 10 indicate areas of interest to "hint" routines */ #define COND_HBASE 10 /* Base for location hint bits */ #define COND_HCAVE 11 /* Trying to get into cave */ #define COND_HBIRD 12 /* Trying to catch bird */ #define COND_HSNAKE 13 /* Trying to deal with snake */ #define COND_HMAZE 14 /* Lost in maze */ #define COND_HDARK 15 /* Pondering dark room */ #define COND_HWITT 16 /* At Witt's End */ #define COND_HCLIFF 17 /* Cliff with urn */ #define COND_HWOODS 18 /* Lost in forest */ #define COND_HOGRE 19 /* Trying to deal with ogre */ #define COND_HJADE 20 /* Found all treasures except jade */ typedef struct {{ const char** strs; const int n; }} string_group_t; typedef struct {{ const string_group_t words; const char* inventory; int plac, fixd; bool is_treasure; const char** descriptions; const char** sounds; const char** texts; const char** changes; }} object_t; typedef struct {{ const char* small; const char* big; }} descriptions_t; typedef struct {{ descriptions_t description; const long sound; const bool loud; }} location_t; typedef struct {{ const char* query; const char* yes_response; }} obituary_t; typedef struct {{ const int threshold; const int point_loss; const char* message; }} turn_threshold_t; typedef struct {{ const int threshold; const char* message; }} class_t; typedef struct {{ const int number; const int turns; const int penalty; const char* question; const char* hint; }} hint_t; typedef struct {{ const string_group_t words; }} motion_t; typedef struct {{ const string_group_t words; const long message; }} action_t; typedef struct {{ const long motion; const long cond; const long dest; const bool stop; }} travelop_t; /* Abstract out the encoding of words in the travel array. Gives us * some hope of getting to a less cryptic representation than we * inherited from FORTRAN, someday. To understand these, read the * encoding description for travel. */ #define T_DESTINATION(entry) (entry).dest #define T_CONDITION(entry) (entry).cond #define T_NODWARVES(entry) (T_CONDITION(entry) == 100) #define T_TERMINATE(entry) ((entry).motion == 1) #define L_SPEAK(loc) ((loc) - 500) extern const location_t locations[]; extern const object_t objects[]; extern const char* arbitrary_messages[]; extern const class_t classes[]; extern const turn_threshold_t turn_thresholds[]; extern const obituary_t obituaries[]; extern const hint_t hints[]; extern long conditions[]; extern const motion_t motions[]; extern const action_t actions[]; extern const action_t specials[]; extern const travelop_t travel[]; extern const long tkey[]; extern const char *ignore; #define NLOCATIONS {} #define NOBJECTS {} #define NHINTS {} #define NCLASSES {} #define NDEATHS {} #define NTHRESHOLDS {} #define NMOTIONS {} #define NACTIONS {} #define NSPECIALS {} #define NTRAVEL {} #define NKEYS {} enum arbitrary_messages_refs {{ {} }}; enum locations_refs {{ {} }}; enum object_refs {{ {} }}; enum motion_refs {{ {} }}; enum action_refs {{ {} }}; enum special_refs {{ {} }}; /* State definitions */ {} #endif /* end DUNGEON_H */ """ c_template = """/* Generated from adventure.yaml - do not hand-hack! */ #include "{}" const char* arbitrary_messages[] = {{ {} }}; const class_t classes[] = {{ {} }}; const turn_threshold_t turn_thresholds[] = {{ {} }}; const location_t locations[] = {{ {} }}; const object_t objects[] = {{ {} }}; const obituary_t obituaries[] = {{ {} }}; const hint_t hints[] = {{ {} }}; long conditions[] = {{ {} }}; const motion_t motions[] = {{ {} }}; const action_t actions[] = {{ {} }}; const action_t specials[] = {{ {} }}; const long tkey[] = {{{}}}; const travelop_t travel[] = {{ {} }}; const char *ignore = \"{}\"; /* end */ """ def make_c_string(string): """Render a Python string into C string literal format.""" if string == None: return "NULL" string = string.replace("\n", "\\n") string = string.replace("\t", "\\t") string = string.replace('"', '\\"') string = string.replace("'", "\\'") string = '"' + string + '"' return string def get_refs(l): reflist = [x[0] for x in l] ref_str = "" for ref in reflist: ref_str += " {},\n".format(ref) ref_str = ref_str[:-1] # trim trailing newline return ref_str def get_string_group(strings): template = """{{ .strs = {}, .n = {}, }}""" if strings == []: strs = "NULL" else: strs = "(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}" n = len(strings) sg_str = template.format(strs, n) return sg_str def get_arbitrary_messages(arb): template = """ {}, """ arb_str = "" for item in arb: arb_str += template.format(make_c_string(item[1])) arb_str = arb_str[:-1] # trim trailing newline return arb_str def get_class_messages(cls): template = """ {{ .threshold = {}, .message = {}, }}, """ cls_str = "" for item in cls: threshold = item["threshold"] message = make_c_string(item["message"]) cls_str += template.format(threshold, message) cls_str = cls_str[:-1] # trim trailing newline return cls_str def get_turn_thresholds(trn): template = """ {{ .threshold = {}, .point_loss = {}, .message = {}, }}, """ trn_str = "" for item in trn: threshold = item["threshold"] point_loss = item["point_loss"] message = make_c_string(item["message"]) trn_str += template.format(threshold, point_loss, message) trn_str = trn_str[:-1] # trim trailing newline return trn_str def get_locations(loc): template = """ {{ // {} .description = {{ .small = {}, .big = {}, }}, .sound = {}, .loud = {}, }}, """ loc_str = "" for (i, item) in enumerate(loc): short_d = make_c_string(item[1]["description"]["short"]) long_d = make_c_string(item[1]["description"]["long"]) sound = item[1].get("sound", "SILENT") loud = "true" if item[1].get("loud") else "false" loc_str += template.format(i, short_d, long_d, sound, loud) loc_str = loc_str[:-1] # trim trailing newline return loc_str def get_objects(obj): template = """ {{ // {} .words = {}, .inventory = {}, .plac = {}, .fixd = {}, .is_treasure = {}, .descriptions = (const char* []) {{ {} }}, .sounds = (const char* []) {{ {} }}, .texts = (const char* []) {{ {} }}, .changes = (const char* []) {{ {} }}, }}, """ obj_str = "" for (i, item) in enumerate(obj): attr = item[1] try: words_str = get_string_group(attr["words"]) except KeyError: words_str = get_string_group([]) i_msg = make_c_string(attr["inventory"]) descriptions_str = "" if attr["descriptions"] == None: descriptions_str = " " * 12 + "NULL," else: labels = [] for l_msg in attr["descriptions"]: if not isinstance(l_msg, str): labels.append(l_msg) l_msg = l_msg[1] descriptions_str += " " * 12 + make_c_string(l_msg) + ",\n" descriptions_str = descriptions_str[:-1] # trim trailing newline if labels: global statedefines statedefines += "/* States for %s */\n" % item[0] for (i, (label, message)) in enumerate(labels): if len(message) >= 45: message = message[:45] + "..." statedefines += "#define %s\t%d /* %s */\n" % (label, i, message) statedefines += "\n" sounds_str = "" if attr.get("sounds") == None: sounds_str = " " * 12 + "NULL," else: for l_msg in attr["sounds"]: sounds_str += " " * 12 + make_c_string(l_msg) + ",\n" sounds_str = sounds_str[:-1] # trim trailing newline texts_str = "" if attr.get("texts") == None: texts_str = " " * 12 + "NULL," else: for l_msg in attr["texts"]: texts_str += " " * 12 + make_c_string(l_msg) + ",\n" texts_str = texts_str[:-1] # trim trailing newline changes_str = "" if attr.get("changes") == None: changes_str = " " * 12 + "NULL," else: for l_msg in attr["changes"]: changes_str += " " * 12 + make_c_string(l_msg) + ",\n" changes_str = changes_str[:-1] # trim trailing newline locs = attr.get("locations", ["LOC_NOWHERE", "LOC_NOWHERE"]) immovable = attr.get("immovable", False) try: if type(locs) == str: locs = [locnames.index(locs), -1 if immovable else 0] else: locs = [locnames.index(x) for x in locs] except IndexError: sys.stderr.write("dungeon: unknown object location in %s\n" % locs) sys.exit(1) treasure = "true" if attr.get("treasure") else "false" obj_str += template.format(i, words_str, i_msg, locs[0], locs[1], treasure, descriptions_str, sounds_str, texts_str, changes_str) obj_str = obj_str[:-1] # trim trailing newline return obj_str def get_obituaries(obit): template = """ {{ .query = {}, .yes_response = {}, }}, """ obit_str = "" for o in obit: query = make_c_string(o["query"]) yes = make_c_string(o["yes_response"]) obit_str += template.format(query, yes) obit_str = obit_str[:-1] # trim trailing newline return obit_str def get_hints(hnt, arb): template = """ {{ .number = {}, .penalty = {}, .turns = {}, .question = {}, .hint = {}, }}, """ hnt_str = "" md = dict(arb) for member in hnt: item = member["hint"] number = item["number"] penalty = item["penalty"] turns = item["turns"] question = make_c_string(item["question"]) hint = make_c_string(item["hint"]) hnt_str += template.format(number, penalty, turns, question, hint) hnt_str = hnt_str[:-1] # trim trailing newline return hnt_str def get_condbits(locations): cnd_str = "" for (name, loc) in locations: conditions = loc["conditions"] hints = loc.get("hints") or [] flaglist = [] for flag in conditions: if conditions[flag]: flaglist.append(flag) line = "|".join([("(1<