From d6516388ada98a2e0917e00bda340e1069c4d9d6 Mon Sep 17 00:00:00 2001 From: Woody Folsom Date: Sun, 18 Mar 2012 10:56:25 -0400 Subject: [PATCH] Now with file-based level grammar. --- grammars/overland.grm | 20 +++ src/dk/itu/mario/level/PCGLevel.java | 14 +- src/dk/itu/mario/level/grammar/AndClause.java | 6 +- .../itu/mario/level/grammar/LevelGrammar.java | 40 ++++++ .../level/grammar/LevelGrammarFactory.java | 124 +++++++++++++++++- src/dk/itu/mario/level/grammar/OrClause.java | 10 +- src/dk/itu/mario/level/grammar/Variable.java | 4 + .../grammar/LevelGrammarFactoryTest.java | 25 ++++ 8 files changed, 234 insertions(+), 9 deletions(-) create mode 100644 grammars/overland.grm create mode 100644 test/dk/itu/mario/level/grammar/LevelGrammarFactoryTest.java diff --git a/grammars/overland.grm b/grammars/overland.grm new file mode 100644 index 0000000..92e5afe --- /dev/null +++ b/grammars/overland.grm @@ -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 \ No newline at end of file diff --git a/src/dk/itu/mario/level/PCGLevel.java b/src/dk/itu/mario/level/PCGLevel.java index a250521..808874e 100644 --- a/src/dk/itu/mario/level/PCGLevel.java +++ b/src/dk/itu/mario/level/PCGLevel.java @@ -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); diff --git a/src/dk/itu/mario/level/grammar/AndClause.java b/src/dk/itu/mario/level/grammar/AndClause.java index f2a901a..e5903a5 100644 --- a/src/dk/itu/mario/level/grammar/AndClause.java +++ b/src/dk/itu/mario/level/grammar/AndClause.java @@ -6,6 +6,10 @@ import java.util.List; public class AndClause implements Clause { List subClauses = new ArrayList(); + public AndClause(List 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(")"); diff --git a/src/dk/itu/mario/level/grammar/LevelGrammar.java b/src/dk/itu/mario/level/grammar/LevelGrammar.java index e79d14e..f33ac48 100644 --- a/src/dk/itu/mario/level/grammar/LevelGrammar.java +++ b/src/dk/itu/mario/level/grammar/LevelGrammar.java @@ -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 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(); + } } diff --git a/src/dk/itu/mario/level/grammar/LevelGrammarFactory.java b/src/dk/itu/mario/level/grammar/LevelGrammarFactory.java index 12eb60e..05562d9 100644 --- a/src/dk/itu/mario/level/grammar/LevelGrammarFactory.java +++ b/src/dk/itu/mario/level/grammar/LevelGrammarFactory.java @@ -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 rhsClauseStrings = new ArrayList(); + 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 rhsClauses = new ArrayList(); + + 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); diff --git a/src/dk/itu/mario/level/grammar/OrClause.java b/src/dk/itu/mario/level/grammar/OrClause.java index 4934d67..ea83d8c 100644 --- a/src/dk/itu/mario/level/grammar/OrClause.java +++ b/src/dk/itu/mario/level/grammar/OrClause.java @@ -7,6 +7,14 @@ public class OrClause implements Clause { private double[] chances; private List subClauses = new ArrayList(); + public OrClause(double[] chances, List 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(")"); diff --git a/src/dk/itu/mario/level/grammar/Variable.java b/src/dk/itu/mario/level/grammar/Variable.java index 2034e16..ebc3551 100644 --- a/src/dk/itu/mario/level/grammar/Variable.java +++ b/src/dk/itu/mario/level/grammar/Variable.java @@ -17,6 +17,10 @@ public class Variable implements Clause, Comparable { return type; } + public String getValue() { + return value; + } + public boolean isTerminal() { return terminal; } diff --git a/test/dk/itu/mario/level/grammar/LevelGrammarFactoryTest.java b/test/dk/itu/mario/level/grammar/LevelGrammarFactoryTest.java new file mode 100644 index 0000000..561ba7d --- /dev/null +++ b/test/dk/itu/mario/level/grammar/LevelGrammarFactoryTest.java @@ -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()); + } +}