Now with file-based level grammar.

This commit is contained in:
Woody Folsom
2012-03-18 10:56:25 -04:00
parent b431d5efeb
commit d6516388ad
8 changed files with 234 additions and 9 deletions

20
grammars/overland.grm Normal file
View File

@@ -0,0 +1,20 @@
#VAR name = LevelComponent.TYPE
VAR S = LEVEL
VAR LAND_SEGMENT = LEVEL_SEGMENT
VAR LO_HI = LO_HI
VAR HI_LO = HI_LO
VAR LO_PATH = LO_PATH
VAR HI_PATH = HI_PATH
VAR lo_path = FLAT_LO
VAR hi_path = FLAT_HI
#RULE name -> {probabilities}, (clause) [+,|] (clause)...
RULE S -> LAND_SEGMENT + LAND_SEGMENT
RULE LAND_SEGMENT -> {0.25,0.65,0.10}, (LO_HI + HI_LO) | (LO_PATH) | (LAND_SEGMENT + LAND_SEGMENT)
RULE LO_HI -> LO_PATH + HI_PATH
RULE HI_LO -> HI_PATH + LO_PATH
RULE HI_PATH -> {0.25,0.75}, (HI_PATH + HI_PATH) | (hi_path)
RULE LO_PATH -> {0.25,0.75}, (LO_PATH + LO_PATH) | (lo_path)
#START variable name
START = S

View File

@@ -1,5 +1,6 @@
package dk.itu.mario.level;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -76,9 +77,16 @@ public class PCGLevel extends Level {
System.out.println("LevelArchetype: " + archetype);
System.out.println("Creating level grammar");
LevelGrammar grammar = LevelGrammarFactory.createGrammar(profile,
archetype);
LevelGrammar grammar;
try {
String grammarFileName = "grammars/overland.grm";
grammar = LevelGrammarFactory.createGrammar(new File(grammarFileName));
System.out.println("Read grammar from file: " + grammarFileName);
} catch (Exception ex) {
System.out.println("Failed to parse grammar file due to exception: " + ex.getMessage());
System.out.println("Defaulting to basic overland grammar.");
grammar = LevelGrammarFactory.createGrammar();
}
System.out
.println("Tuning grammar for PlayerProfile & LevelArchetype using RETE");
GrammarTuner.tune(grammar, profile, archetype);

View File

@@ -6,6 +6,10 @@ import java.util.List;
public class AndClause implements Clause {
List<Clause> subClauses = new ArrayList<Clause>();
public AndClause(List<Clause> subClauses) {
this.subClauses.addAll(subClauses);
}
public AndClause(Clause... subClauses) {
for (Clause clause : subClauses) {
this.subClauses.add(clause);
@@ -50,7 +54,7 @@ public class AndClause implements Clause {
sb.append("(");
for (int i = 0; i < subClauses.size() - 1; i ++) {
sb.append(subClauses.get(i).toString());
sb.append(" AND ");
sb.append(" + ");
}
sb.append(subClauses.get(subClauses.size()-1).toString());
sb.append(")");

View File

@@ -3,6 +3,7 @@ package dk.itu.mario.level.grammar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
@@ -68,6 +69,15 @@ public class LevelGrammar {
return parseNode;
}
public Variable getVariable(String varName) {
for (Variable var : variables) {
if (varName.equals(var.getValue())) {
return var;
}
}
return null;
}
public ProductionRule getRule(Variable var) {
return ruleMap.get(var);
}
@@ -76,7 +86,37 @@ public class LevelGrammar {
return start;
}
public void setStart(String startVar) {
for (Variable var : variables) {
if (startVar.equals(var.getValue())) {
start = var;
return;
}
}
}
public void setStart(Variable start) {
this.start = start;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Variables:\n");
for (Variable var : variables) {
sb.append("VAR ");
sb.append(var.getValue());
sb.append(" = ");
sb.append(var.getType());
sb.append("\n");
}
sb.append("\n");
for (Entry<Variable,ProductionRule> entry : ruleMap.entrySet()) {
//could as easily have used LHS instead of getKey().getValue()
sb.append("RULE " + entry.getKey().getValue() + " -> " + entry.getValue().getRHS());
sb.append("\n");
}
sb.append("\n");
sb.append("START = " + start.getValue());
return sb.toString();
}
}

View File

@@ -1,12 +1,128 @@
package dk.itu.mario.level.grammar;
import dk.itu.mario.level.LevelArchetype;
import dk.itu.mario.level.LevelComponent;
import dk.itu.mario.level.PlayerProfile;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import dk.itu.mario.level.LevelComponent;
public class LevelGrammarFactory {
public static LevelGrammar createGrammar(PlayerProfile playerProfile, LevelArchetype archetype) {
public static LevelGrammar createGrammar(File grammarFile) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(grammarFile);
LevelGrammar levelGrammar = new LevelGrammar();
try {
InputStreamReader reader = new InputStreamReader(fis);
BufferedReader buf = new BufferedReader(reader);
String line;
while ((line = buf.readLine()) != null) {
line = line.trim(); // trim off newline/whitespace
if (line.length() == 0 || line.startsWith("#")) {
continue;
}
System.out.println("Read: " + line);
//split on whitespace
String[] fields;
if (line.startsWith("VAR")) {
fields = line.split("\\s");
if ("=".equals(fields[2]) && fields.length == 4) {
levelGrammar.addVariable(new Variable(fields[1],LevelComponent.TYPE.valueOf(fields[3])));
} else {
throw new RuntimeException("Invalid VAR syntax: " + line);
}
} else if (line.startsWith("RULE")) {
fields = line.split("->");
String ruleName = fields[0].split("\\s")[1];
System.out.println("Rule name: " + ruleName);
Variable lhs = levelGrammar.getVariable(ruleName);
if (lhs == null) {
throw new RuntimeException("LHS variable not found: " + ruleName);
}
Clause rhs = getClause(fields[1].trim(),levelGrammar);
levelGrammar.addProductionRule(new ProductionRule(lhs,rhs));
} else if (line.startsWith("START")) {
fields = line.split("\\s");
if ("=".equals(fields[1]) && fields.length == 3) {
levelGrammar.setStart(fields[2]);
} else {
throw new RuntimeException("Invalid START syntax: " + line);
}
} else {
throw new RuntimeException("Unable to parse grammar file " + grammarFile.getName() + " due to invalid line: " + line);
}
}
return levelGrammar;
} finally {
fis.close();
}
}
private static Clause getClause(String clause, LevelGrammar grammar) {
if (!clause.contains("|") && !clause.contains("+")) {
return grammar.getVariable(clause);
}
int lBraceIndex = clause.indexOf("{");
boolean isOrClause = false;
int rBraceIndex = 0;
double[] chances = {};
if (lBraceIndex != -1) {
isOrClause = true;
rBraceIndex = clause.indexOf("}");
System.out.println("Read OR-clause probabilities from: " + lBraceIndex + " to " + rBraceIndex);
String[] doubleFields = clause.substring(lBraceIndex+1,rBraceIndex).split(",");
chances = new double[doubleFields.length];
for (int i = 0; i < doubleFields.length; i++) {
chances[i] = Double.valueOf(doubleFields[i]);
}
}
String remainder = clause.substring(rBraceIndex);
List<String> rhsClauseStrings = new ArrayList<String>();
if (remainder.contains("(")) {
int nextLeftIndex = -1;
do {
nextLeftIndex = remainder.indexOf("(", nextLeftIndex+1);
if (nextLeftIndex != -1) {
int nextRightIndex = remainder.indexOf(")", nextLeftIndex+1);
if (nextRightIndex == -1) {
throw new RuntimeException("Unmatched left '(' in clause");
}
rhsClauseStrings.add(remainder.substring(nextLeftIndex+1,nextRightIndex));
nextLeftIndex = nextRightIndex;
}
} while (nextLeftIndex != -1);
} else {
isOrClause = remainder.contains("|");
if (isOrClause) {
for (String subclause : remainder.split("\\|")) {
rhsClauseStrings.add(subclause.trim());
}
} else {
for (String subclause : remainder.split("\\+")) {
rhsClauseStrings.add(subclause.trim());
}
}
}
List<Clause> rhsClauses = new ArrayList<Clause>();
for (String subclause : rhsClauseStrings) {
rhsClauses.add(getClause(subclause,grammar));
}
if (isOrClause) {
return new OrClause(chances, rhsClauses);
} else {
return new AndClause(rhsClauses);
}
}
public static LevelGrammar createGrammar() {
LevelGrammar grammar = new LevelGrammar();
Variable v_START = new Variable("S", LevelComponent.TYPE.LEVEL);

View File

@@ -7,6 +7,14 @@ public class OrClause implements Clause {
private double[] chances;
private List<Clause> subClauses = new ArrayList<Clause>();
public OrClause(double[] chances, List<Clause> subClauses) {
if (chances.length != subClauses.size()) {
throw new IllegalArgumentException("List of probabilities does not match size of subclause list in OrClause constructor.");
}
this.chances = chances;
this.subClauses.addAll(subClauses);
}
public OrClause(double[] chances, Clause... subClauses) {
this.chances = chances;
double chanceTotal = 0.0;
@@ -66,7 +74,7 @@ public class OrClause implements Clause {
sb.append("(");
for (int i = 0; i < subClauses.size() - 1; i ++) {
sb.append(subClauses.get(i).toString());
sb.append(" OR ");
sb.append(" | ");
}
sb.append(subClauses.get(subClauses.size()-1).toString());
sb.append(")");

View File

@@ -17,6 +17,10 @@ public class Variable implements Clause, Comparable<Variable> {
return type;
}
public String getValue() {
return value;
}
public boolean isTerminal() {
return terminal;
}

View File

@@ -0,0 +1,25 @@
package dk.itu.mario.level.grammar;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import org.junit.Test;
public class LevelGrammarFactoryTest {
@Test
public void testCreateGrammar() {
LevelGrammar levelGrammar = LevelGrammarFactory.createGrammar();
assertNotNull(levelGrammar);
System.out.println(levelGrammar.toString());
}
@Test
public void testReadFile() throws Exception {
File grammarFile = new File("grammars/overland.grm");
LevelGrammar levelGrammar = LevelGrammarFactory.createGrammar(grammarFile);
assertNotNull(levelGrammar);
System.out.println("Read Grammar from file:");
System.out.println(levelGrammar.toString());
}
}