After doing a couple of test runs, I'm a little disappointed with appendix A. Not enough stuff (monsters/treatures/traps), too many freaking wide passages. Too many levels up/down (for my purposes).
For v2 I'll change passages widths to 50% 5', 30% 10', 20% other. Make Traps/Treasures/Monsters more common. Add in room types and dungeon dressings from the other appendixes. Maybe simplified tables to generate magic items, wandering monsters.
There's probably a million of these, this one is mine. Python source dmg_dungeon_generator.py
Python is keen, by far my favorite programming language. It's a bit like modern rules lite RPG's, there's very little syntax and "rules" to it and what there is has a consistency. Makes it easier to tinker with. For instance the ability to redefine how objects are converted to strings, __str__, lets me define random tables that "roll" themselves when printed. Like so
Door = table_d20( ( 6, "Door left leading to a", Beyond_Door, ), (12, "Door right leading to a", Beyond_Door, ), (20, "Door ahead leading to a", Beyond_Door, ), ) Beyond_Door = table_d20( ( 4, Passage_Width, "parallel passage extending 30' in both directions. Or 10'x10'.", ), ( 8, Passage_Width, "passage straight ahead.", ), ( 9, Passage_Width, "passage ahead/behind 45deg.", ), (10, Passage_Width, "passage behind/ahead 45deg.", ), (18, Room, Contents, ), (20, Chamber, Contents, ), )The simple python statement print Door will randomly roll a d20, say 11, look up matching row in Door table print "Door right leading to a ". Then roll another d20 this time on Beyond_Door table, which leads to other tables and text and so on.
Those are interpreted (along with a few helper classes/functions) by this relatively short class. Not stupendous but I like how simple/clean/flexible the table definitions are.
class Table(object): """Object that when evaluated into string will return random result from table""" def __init__(self, dice, *table): """@param dice: callable that returns something comparable to x. @param *table: list of tuples (x,foo1,foo2,fooN) where x is comparable to return value of dice and foo? are evaluatable into strings. """ self.dice = dice self.table = table def __str__(self): """@return: foo1,foo2,fooN stringified and space separated, from row in table where x >= return value of dice. """ return " ".join(str(s) for s in self.get_result()) def get_result(self): roll = self.dice() for row in self.table: if row >= roll: return row[1:] return self.table[-1][1:] def table_d20(*table): return Table(lambda: random.randint(1, 20), *table)The table_d20 is a helper function to return a Table object preset to use d20 for its dice rolls. It takes a list of arguments, *table, which in our case are the rows of the table. It passes those along with a lambda function that returns a number between 1 and 20 (our d20 die). We need to wrap the random.randint in a lambda so it's callable, since we want a new random number every time we "roll" on the table.
The Table classes __init__ is nothing special. And the __str__ function just applies the str builtin to each item in the list that self.get_result() returns. It then compiles them all into one string, added a space between each one. If you did now know __str__ gets called whenever the object is converted to a string. e.g str(obj) is basically obj.__str__()
The get_result has all the action. It "rolls the dice" then iterates over the table rows looking at the first item until it finds a match. It then returns all but the first of that row's items. There's a fail safe, if no matching row is found it returns items from the last row.