Now with file-based level grammar.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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(")");
|
||||
|
||||
@@ -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;
|
||||
@@ -67,6 +68,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(")");
|
||||
|
||||
@@ -17,6 +17,10 @@ public class Variable implements Clause, Comparable<Variable> {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user