Skip to content

Instantly share code, notes, and snippets.

@anissen
Created January 29, 2016 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anissen/d55733e322bea688247e to your computer and use it in GitHub Desktop.
Save anissen/d55733e322bea688247e to your computer and use it in GitHub Desktop.
Generative grammar for procedural quest generation
// Inspired by
// http://www.jorisdormans.nl/pdf/dormans2010_AdventuresInLevelDesign.pdf
// http://larc.unt.edu/ian/pubs/pcg2011.pdf
// http://larc.unt.edu/ian/pubs/DoranParberryQuests2015.pdf
// Idea: Use a macro to extract Symbol and Terminal information and populate enums
enum Symbol {
Quest;
SubQuest;
GoTo;
Retrieve;
TalkTo;
Kill;
Reward;
}
enum Terminal {
Person;
Monster;
Treasure;
Location;
Return;
}
enum Construct {
Symbol(s :Symbol);
Terminal(t :Terminal);
List(l :Array<Construct>);
}
enum Tree<T> {
Leaf(v :T);
Node(s :T, list :Array<Tree<T>>);
}
class Rules {
var rules :Map<Symbol, Array<Construct>>;
public function new() {
rules = new Map();
}
public function add_rule(symbol :Symbol, construct :Construct) {
if (!rules.exists(symbol)) rules[symbol] = [];
rules[symbol].push(construct);
}
public function get_replacements(symbol :Symbol) :Array<Construct> {
return rules[symbol];
}
}
class Generator {
var rules :Rules;
public function new(rules :Rules) {
this.rules = rules;
}
public function generate(symbol :Symbol) :Tree<Construct> {
var replacements = rules.get_replacements(symbol);
var replacement = replacements[Math.floor(replacements.length * Math.random())];
return Node(Symbol(symbol), unwrap(replacement));
}
function unwrap(c :Construct) :Array<Tree<Construct>> {
return switch (c) {
case Terminal(t): [ Leaf(Terminal(t)) ];
case Symbol(s): [ generate(s) ];
case List(list): var ll = []; for (l in list) ll = ll.concat(unwrap(l)); return ll;
}
}
}
class Test {
static function main() {
/*
Quest -> SubQuest + Return + Reward
SubQuest -> TalkTo + Kill
SubQuest -> GoTo + Retrieve
SubQuest -> SubQuest + SubQuest
TalkTo -> Person
GoTo -> Location
Kill -> Monster
Kill -> Person
Reward -> Treasure
Reward -> Treasure + Reward
Retrieve -> Treasure
*/
var rules = new Rules();
rules.add_rule(Quest, List([Symbol(SubQuest), Terminal(Return), Symbol(Reward)]));
rules.add_rule(SubQuest, List([Symbol(TalkTo), Symbol(Kill)]));
rules.add_rule(SubQuest, List([Symbol(GoTo), Symbol(Retrieve)]));
rules.add_rule(SubQuest, List([Symbol(SubQuest), Symbol(SubQuest)]));
rules.add_rule(TalkTo, Terminal(Person));
rules.add_rule(GoTo, Terminal(Location));
rules.add_rule(Kill, Terminal(Monster));
rules.add_rule(Kill, Terminal(Person));
rules.add_rule(Reward, Terminal(Treasure));
rules.add_rule(Reward, List([Terminal(Treasure), Symbol(Reward)]));
rules.add_rule(Retrieve, Terminal(Treasure));
var generator = new Generator(rules);
function pad(size :Int) {
var s = '';
for (i in 0 ... size) s += ' ·';
return s + ' ';
}
function print(t :Tree<Construct>, index :Int = 0) {
return switch (t) {
case Leaf(Terminal(t)): trace(pad(index) + t);
case Node(Symbol(s), list): trace(pad(index) + s); for (l in list) print(l, index + 1);
default: trace(pad(index) + 'Invalid!');
}
}
var result = generator.generate(Quest);
print(result);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment