X-Git-Url: https://jxself.org/git/?p=open-adventure.git;a=blobdiff_plain;f=make_dungeon.py;h=63eee94ed241863772ee71465c6a25b7d07e7c7c;hp=9b729cc2505193b54a81e576ed47bb2c2cd3c253;hb=HEAD;hpb=6af7db8cd9bbac9b238897a03fe1e6a04b26b944 diff --git a/make_dungeon.py b/make_dungeon.py index 9b729cc..8d7f719 100755 --- a/make_dungeon.py +++ b/make_dungeon.py @@ -1,259 +1,33 @@ -#!/usr/bin/python3 - -# This is the open-adventure dungeon generator. It consumes a YAML description of -# the dungeon and outputs a dungeon.h and dungeon.c pair of C code files. -# -# The nontrivial part of this is the compilation of the YAML for -# movement rules to the travel array that's actually used by -# playermove(). - -import sys, yaml - -yaml_name = "adventure.yaml" -h_name = "dungeon.h" -c_name = "dungeon.c" - -statedefines = "" - -h_template = """/* Generated from adventure.yaml - do not hand-hack! */ -#ifndef DUNGEON_H -#define DUNGEON_H - -#include -#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 char* message; -}} action_t; - -typedef struct {{ - const string_group_t words; - const char* message; -}} special_t; - -enum condtype_t {{cond_goto, cond_pct, cond_carry, cond_with, cond_not}}; -enum desttype_t {{dest_goto, dest_special, dest_speak}}; - -typedef struct {{ - const long motion; - const long condtype; - const long condarg1; - const long condarg2; - const enum desttype_t desttype; - const long destval; - const bool nodwarves; - 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_TERMINATE(entry) ((entry).motion == 1) - -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 special_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 {} - -#define BIRD_ENDSTATE {} - -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 */ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: (C) Eric S. Raymond +# SPDX-License-Identifier: BSD-2-Clause """ +This is the open-adventure dungeon generator. It consumes a YAML description of +the dungeon and outputs a dungeon.h and dungeon.c pair of C code files. -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[] = {{ -{} -}}; +The nontrivial part of this is the compilation of the YAML for +movement rules to the travel array that's actually used by +playermove(). +""" -const motion_t motions[] = {{ -{} -}}; +# pylint: disable=consider-using-f-string,line-too-long,invalid-name,missing-function-docstring,too-many-branches,global-statement,multiple-imports,too-many-locals,too-many-statements,too-many-nested-blocks,no-else-return,raise-missing-from,redefined-outer-name -const action_t actions[] = {{ -{} -}}; - -const special_t specials[] = {{ -{} -}}; +import sys, yaml -const long tkey[] = {{{}}}; +YAML_NAME = "adventure.yaml" +H_NAME = "dungeon.h" +C_NAME = "dungeon.c" +H_TEMPLATE_PATH = "templates/dungeon.h.tpl" +C_TEMPLATE_PATH = "templates/dungeon.c.tpl" -const travelop_t travel[] = {{ -{} -}}; +DONOTEDIT_COMMENT = "/* Generated from adventure.yaml - do not hand-hack! */\n\n" -const char *ignore = \"{}\"; +statedefines = "" -/* end */ -""" def make_c_string(string): """Render a Python string into C string literal format.""" - if string == None: + if string is None: return "NULL" string = string.replace("\n", "\\n") string = string.replace("\t", "\\t") @@ -262,14 +36,16 @@ def make_c_string(string): 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 + ref_str = ref_str[:-1] # trim trailing newline return ref_str + def get_string_group(strings): template = """{{ .strs = {}, @@ -278,20 +54,24 @@ def get_string_group(strings): if strings == []: strs = "NULL" else: - strs = "(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}" + 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 + arb_str = arb_str[:-1] # trim trailing newline return arb_str + def get_class_messages(cls): template = """ {{ .threshold = {}, @@ -303,9 +83,10 @@ def get_class_messages(cls): threshold = item["threshold"] message = make_c_string(item["message"]) cls_str += template.format(threshold, message) - cls_str = cls_str[:-1] # trim trailing newline + cls_str = cls_str[:-1] # trim trailing newline return cls_str + def get_turn_thresholds(trn): template = """ {{ .threshold = {}, @@ -319,9 +100,10 @@ def get_turn_thresholds(trn): 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 + trn_str = trn_str[:-1] # trim trailing newline return trn_str + def get_locations(loc): template = """ {{ // {}: {} .description = {{ @@ -339,9 +121,10 @@ def get_locations(loc): sound = item[1].get("sound", "SILENT") loud = "true" if item[1].get("loud") else "false" loc_str += template.format(i, item[0], short_d, long_d, sound, loud) - loc_str = loc_str[:-1] # trim trailing newline + loc_str = loc_str[:-1] # trim trailing newline return loc_str + def get_objects(obj): template = """ {{ // {}: {} .words = {}, @@ -363,6 +146,7 @@ def get_objects(obj): }}, }}, """ + max_state = 0 obj_str = "" for (i, item) in enumerate(obj): attr = item[1] @@ -372,7 +156,7 @@ def get_objects(obj): words_str = get_string_group([]) i_msg = make_c_string(attr["inventory"]) descriptions_str = "" - if attr["descriptions"] == None: + if attr["descriptions"] is None: descriptions_str = " " * 12 + "NULL," else: labels = [] @@ -380,47 +164,62 @@ def get_objects(obj): descriptions_str += " " * 12 + make_c_string(l_msg) + ",\n" for label in attr.get("states", []): labels.append(label) - descriptions_str = descriptions_str[:-1] # trim trailing newline + descriptions_str = descriptions_str[:-1] # trim trailing newline if labels: global statedefines statedefines += "/* States for %s */\n" % item[0] - for (i, label) in enumerate(labels): - statedefines += "#define %s\t%d\n" % (label, i) + for (n, label) in enumerate(labels): + statedefines += "#define %s\t%d\n" % (label, n) + max_state = max(max_state, n) statedefines += "\n" sounds_str = "" - if attr.get("sounds") == None: + if attr.get("sounds") is 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 + 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: + if attr.get("texts") is 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 + 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: + if attr.get("changes") is 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 + 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: + if isinstance(locs, str): locs = [locs, -1 if immovable else 0] 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, item[0], 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 + obj_str += template.format( + i, + item[0], + 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 + statedefines += "/* Maximum state value */\n#define MAX_STATE %d\n" % max_state return obj_str + def get_obituaries(obit): template = """ {{ .query = {}, @@ -432,10 +231,11 @@ def get_obituaries(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 + obit_str = obit_str[:-1] # trim trailing newline return obit_str -def get_hints(hnt, arb): + +def get_hints(hnt): template = """ {{ .number = {}, .penalty = {}, @@ -445,7 +245,6 @@ def get_hints(hnt, arb): }}, """ hnt_str = "" - md = dict(arb) for member in hnt: item = member["hint"] number = item["number"] @@ -454,9 +253,10 @@ def get_hints(hnt, arb): 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 + hnt_str = hnt_str[:-1] # trim trailing newline return hnt_str + def get_condbits(locations): cnd_str = "" for (name, loc) in locations: @@ -467,7 +267,7 @@ def get_condbits(locations): if conditions[flag]: flaglist.append(flag) line = "|".join([("(1<500 message N-500 from section 6 is printed, - # and he stays wherever he is. + # 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 500: - desttype = "dest_speak"; + desttype = "dest_speak" destval = msgnames[dest - 500] else: - desttype = "dest_special"; + desttype = "dest_special" destval = locnames[dest - 300] - travel.append([len(tkey)-1, - locnames[len(tkey)-1], - rule.pop(0), - condtype, - condarg1, - condarg2, - desttype, - destval, - "true" if nodwarves else "false", - "false"]) + travel.append( + [ + len(tkey) - 1, + locnames[len(tkey) - 1], + rule.pop(0), + condtype, + condarg1, + condarg2, + desttype, + destval, + "true" if nodwarves else "false", + "false", + ] + ) travel[-1][-1] = "true" return (travel, tkey) + def get_travel(travel): template = """ {{ // from {}: {} .motion = {}, @@ -771,37 +581,52 @@ def get_travel(travel): out = "" for entry in travel: out += template.format(*entry) - out = out[:-1] # trim trailing newline + out = out[:-1] # trim trailing newline return out + if __name__ == "__main__": - with open(yaml_name, "r") as f: - db = yaml.load(f) + with open(YAML_NAME, "r", encoding="ascii", errors="surrogateescape") as f: + db = yaml.safe_load(f) locnames = [x[0] for x in db["locations"]] msgnames = [el[0] for el in db["arbitrary_messages"]] objnames = [el[0] for el in db["objects"]] motionnames = [el[0] for el in db["motions"]] - (travel, tkey) = buildtravel(db["locations"], - db["objects"]) + (travel, tkey) = buildtravel(db["locations"], db["objects"]) ignore = "" + try: + with open( + H_TEMPLATE_PATH, "r", encoding="ascii", errors="surrogateescape" + ) as htf: + # read in dungeon.h template + h_template = DONOTEDIT_COMMENT + htf.read() + with open( + C_TEMPLATE_PATH, "r", encoding="ascii", errors="surrogateescape" + ) as ctf: + # read in dungeon.c template + c_template = DONOTEDIT_COMMENT + ctf.read() + except IOError as e: + print("ERROR: reading template failed ({})".format(e.strerror)) + sys.exit(-1) + c = c_template.format( - h_name, - get_arbitrary_messages(db["arbitrary_messages"]), - get_class_messages(db["classes"]), - get_turn_thresholds(db["turn_thresholds"]), - get_locations(db["locations"]), - get_objects(db["objects"]), - get_obituaries(db["obituaries"]), - get_hints(db["hints"], db["arbitrary_messages"]), - get_condbits(db["locations"]), - get_motions(db["motions"]), - get_specials(db["actions"]), - get_specials(db["specials"]), - bigdump(tkey), - get_travel(travel), - ignore, + h_file=H_NAME, + arbitrary_messages=get_arbitrary_messages(db["arbitrary_messages"]), + classes=get_class_messages(db["classes"]), + turn_thresholds=get_turn_thresholds(db["turn_thresholds"]), + locations=get_locations(db["locations"]), + objects=get_objects(db["objects"]), + obituaries=get_obituaries(db["obituaries"]), + hints=get_hints(db["hints"]), + conditions=get_condbits(db["locations"]), + motions=get_motions(db["motions"]), + actions=get_actions(db["actions"]), + tkeys=bigdump(tkey), + travel=get_travel(travel), + ignore=ignore, + dwarflocs=", ".join(db["dwarflocs"]) + ",", ) # 0-origin index of birds's last song. Bird should @@ -809,31 +634,30 @@ if __name__ == "__main__": deathbird = len(dict(db["objects"])["BIRD"]["sounds"]) - 1 h = h_template.format( - len(db["locations"])-1, - len(db["objects"])-1, - len(db["hints"]), - len(db["classes"])-1, - len(db["obituaries"]), - len(db["turn_thresholds"]), - len(db["motions"]), - len(db["actions"]), - len(db["specials"]), - len(travel), - len(tkey), - deathbird, - get_refs(db["arbitrary_messages"]), - get_refs(db["locations"]), - get_refs(db["objects"]), - get_refs(db["motions"]), - get_refs(db["actions"]), - get_refs(db["specials"]), - statedefines, + num_locations=len(db["locations"]) - 1, + num_objects=len(db["objects"]) - 1, + num_hints=len(db["hints"]), + num_classes=len(db["classes"]) - 1, + num_deaths=len(db["obituaries"]), + num_thresholds=len(db["turn_thresholds"]), + num_motions=len(db["motions"]), + num_actions=len(db["actions"]), + num_travel=len(travel), + num_keys=len(tkey), + bird_endstate=deathbird, + arbitrary_messages=get_refs(db["arbitrary_messages"]), + locations=get_refs(db["locations"]), + objects=get_refs(db["objects"]), + motions=get_refs(db["motions"]), + actions=get_refs(db["actions"]), + state_definitions=statedefines, + ndwarflocs=str(len(db["dwarflocs"])), ) - with open(h_name, "w") as hf: + with open(H_NAME, "w", encoding="ascii", errors="surrogateescape") as hf: hf.write(h) - with open(c_name, "w") as cf: + with open(C_NAME, "w", encoding="ascii", errors="surrogateescape") as cf: cf.write(c) # end