diff --git a/src/dk/itu/mario/engine/ParsedArgs.java b/src/dk/itu/mario/engine/ParsedArgs.java index 8065f12..5de6ea8 100644 --- a/src/dk/itu/mario/engine/ParsedArgs.java +++ b/src/dk/itu/mario/engine/ParsedArgs.java @@ -3,7 +3,7 @@ package dk.itu.mario.engine; import dk.itu.mario.MarioInterface.LevelGenerator; import dk.itu.mario.level.generator.CustomizedLevelGenerator; import dk.itu.mario.level.generator.MyLevelGenerator; -import dk.itu.mario.level.generator.MyNewLevelGenerator; +import dk.itu.mario.level.generator.PCGLevelGenerator; import dk.itu.mario.level.generator.RandomLevelGenerator; public class ParsedArgs { @@ -23,7 +23,7 @@ public class ParsedArgs { if ("MyLevel".equals(generatorClass)) { return new MyLevelGenerator(); } else if ("MyNewLevel".equals(generatorClass)) { - return new MyNewLevelGenerator(); + return new PCGLevelGenerator(); } else if ("CustomizedLevel".equalsIgnoreCase(generatorClass)) { return new CustomizedLevelGenerator(); } else if ("RandomLevel".equalsIgnoreCase(generatorClass)) { diff --git a/src/dk/itu/mario/level/LevelComponent.java b/src/dk/itu/mario/level/LevelComponent.java new file mode 100644 index 0000000..161c838 --- /dev/null +++ b/src/dk/itu/mario/level/LevelComponent.java @@ -0,0 +1,32 @@ +package dk.itu.mario.level; + +public class LevelComponent { + public enum TYPE { FLAT, PIPES, MAZE}; + + private TYPE type; + private int start; + private int end; + + public LevelComponent(TYPE type, int start, int end) { + this.type = type; + this.end = end; + this.start = start; + } + + public TYPE getType() { + return type; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + @Override + public String toString() { + return type + "[" + start + ".." + end + "]"; + } +} diff --git a/src/dk/itu/mario/level/PCGLevel.java b/src/dk/itu/mario/level/PCGLevel.java index 3c550c5..e261698 100644 --- a/src/dk/itu/mario/level/PCGLevel.java +++ b/src/dk/itu/mario/level/PCGLevel.java @@ -1,6 +1,7 @@ package dk.itu.mario.level; import java.util.ArrayList; +import java.util.List; import java.util.Random; import dk.itu.mario.MarioInterface.GamePlay; @@ -11,6 +12,7 @@ import dk.itu.mario.engine.sprites.SpriteTemplate; import dk.itu.mario.level.grammar.GrammarTuner; import dk.itu.mario.level.grammar.LevelGrammar; import dk.itu.mario.level.grammar.LevelGrammarFactory; +import dk.itu.mario.level.grammar.LevelParseTree; import dk.itu.mario.level.matcher.ArchetypeMatcher; import dk.itu.mario.level.matcher.LevelArchetype; import dk.itu.mario.level.matcher.PlayerProfile; @@ -82,6 +84,7 @@ public class PCGLevel extends Level { grammar = GrammarTuner.tune(grammar, profile, archetype); System.out.println("Creating level."); + create(seed, profile, archetype, grammar); } @Override @@ -109,22 +112,42 @@ public class PCGLevel extends Level { } - public void create(long seed, PlayerProfile profile, + private void create(long seed, PlayerProfile profile, LevelArchetype archetype, LevelGrammar grammar) { if (dataRecorder == DataRecorder.BLANK_RECORD) { System.out .println("DataRecorder record is BLANK - using GamePlay metrics only."); } - System.out.println("Sample level parameters: " - + grammar.generateRandom(seed)); - this.type = archetype.getTypeInt(); this.difficulty = archetype.getDifficultyLevel(); lastSeed = seed; random = new Random(seed); + System.out.println("Generating level for component list: "); + LevelParseTree parseTree = grammar.generateRandomTree(seed, width); + List levelTemplate = parseTree.getLevelTemplate(); + + int length = 0; + + for (LevelComponent lcomp : levelTemplate) { + LevelComponent.TYPE lctype = lcomp.getType(); + System.out.println("Building for: " + lcomp); + switch (lctype) { + case FLAT: + while (length < Math.min(width-64,lcomp.getEnd())) { + length += buildStraight(length, lcomp.getEnd(), true); + } + break; + default : + System.out.println("Cannot build level segment for unrecognized LevelComponent type: " + type); + } + } + + System.out.println("Total length built: " + length); + + /* Original non-PCG code: // create the start location int length = buildStraight(0, width, true); length += buildPipeJump(length, width - length); @@ -134,13 +157,17 @@ public class PCGLevel extends Level { while (length < width - 64) { length += buildStraight(length, width - length, true); } - + */ // set the end piece int floor = height - 1 - random.nextInt(4); xExit = length + 8; yExit = floor; + + fillEndPiece(length, floor); + } + private void fillEndPiece(int length, int floor) { // fills the end piece for (int x = length; x < width; x++) { for (int y = 0; y < height; y++) { @@ -168,9 +195,7 @@ public class PCGLevel extends Level { } fixWalls(); - } - private void addEnemyLine(int x0, int x1, int y) { for (int x = x0; x < x1; x++) { if (random.nextInt(35) < difficulty + 1) { diff --git a/src/dk/itu/mario/level/grammar/LevelGrammar.java b/src/dk/itu/mario/level/grammar/LevelGrammar.java index eed33cb..8280622 100644 --- a/src/dk/itu/mario/level/grammar/LevelGrammar.java +++ b/src/dk/itu/mario/level/grammar/LevelGrammar.java @@ -7,6 +7,8 @@ import java.util.Random; import java.util.Set; import java.util.TreeSet; +import dk.itu.mario.level.LevelComponent.TYPE; + public class LevelGrammar { private Variable start; @@ -45,6 +47,24 @@ public class LevelGrammar { return generateRandom(new Random().nextLong()); } + //TODO: refactor to recursively add children to the current node based on production rule + public LevelParseTree generateRandomTree(long randomSeed, int width) { + System.out.println("Generating random level parameters using seed: " + + randomSeed); + List startRuleRHS = getRule(getStart()).getRHS(); + + ParseNode rootNode = new ParseNode(TYPE.FLAT,1.0); + LevelParseTree parseTree = new LevelParseTree(rootNode, 0, width); + + for (Variable var : startRuleRHS) { + if (var.isTerminal()) { + rootNode.addChild(new ParseNode(TYPE.FLAT,0.5)); + } + } + + return parseTree; + } + public ProductionRule getRule(Variable var) { return ruleMap.get(var); } diff --git a/src/dk/itu/mario/level/grammar/LevelParseTree.java b/src/dk/itu/mario/level/grammar/LevelParseTree.java new file mode 100644 index 0000000..e062e93 --- /dev/null +++ b/src/dk/itu/mario/level/grammar/LevelParseTree.java @@ -0,0 +1,52 @@ +package dk.itu.mario.level.grammar; + +import java.util.ArrayList; +import java.util.List; + +import dk.itu.mario.level.LevelComponent; + +public class LevelParseTree { + private ParseNode root; + private int start; + private int width; + + public LevelParseTree(ParseNode root, int start, int width) { + this.root = root; + this.start = start; + this.width = width; + } + + public List getLevelTemplate() { + List levelCompList = new ArrayList(); + getComponents(root,start,width, levelCompList); + return levelCompList; + } + + /** + * Enumerate the leaf nodes (level components) in depth-first order. + * @param node + * @param start + * @param end + */ + private void getComponents(ParseNode node, int start, int end, List levelCompList) { + int numChildren = node.getNumChildren(); + if (numChildren == 0) { + LevelComponent.TYPE lcType = node.getType(); + levelCompList.add(new LevelComponent(lcType, start, end)); + } else { + int width = end - start; + int childOffset = 0; + for (int childIndex = 0; childIndex < numChildren; childIndex++) { + ParseNode childNode = node.getChild(childIndex); + int childStart = start + childOffset; + int childWidth = (int)(width * childNode.getPercentLength()); + if (childIndex > 0) { + getComponents(childNode,childStart+1,childStart + childWidth,levelCompList); + } else { // don't offset by 1 if this is the first child component. + getComponents(childNode,childStart,childStart + childWidth,levelCompList); + } + childOffset += childWidth; + } + } + } +} \ No newline at end of file diff --git a/src/dk/itu/mario/level/grammar/ParseNode.java b/src/dk/itu/mario/level/grammar/ParseNode.java new file mode 100644 index 0000000..92ea206 --- /dev/null +++ b/src/dk/itu/mario/level/grammar/ParseNode.java @@ -0,0 +1,62 @@ +package dk.itu.mario.level.grammar; + +import java.util.ArrayList; +import java.util.List; + +import dk.itu.mario.level.LevelComponent; + +public class ParseNode { + private LevelComponent.TYPE levelCompType; + private double percentLength; + private List children = new ArrayList(); + + public ParseNode(LevelComponent.TYPE levelCompType, double percentLength) { + this.levelCompType = levelCompType; + this.percentLength = percentLength; + } + + public void addChild(ParseNode child) { + children.add(child); + } + + public LevelComponent.TYPE getType() { + return levelCompType; + } + + public ParseNode getChild(int index) { + return children.get(index); + } + + public int getNumChildren() { + return children.size(); + } + + public double getPercentLength() { + return percentLength; + } + + public void setChildLengthRatios(double... childPercentLengths) { + double sumLength = 0.0; + for (double cpl : childPercentLengths) { + sumLength += cpl; + } + if (childPercentLengths.length != children.size()) { + throw new IllegalArgumentException( + "Error - number of requested child percent lengths to set does not match the number of children: " + + childPercentLengths.length + + " != " + + children.size()); + } + if (Math.abs(sumLength - 1.0) > 0.01) { + throw new IllegalArgumentException( + "Error - requested child percent lengths do not sum to 100% within an error of 1%."); + } + for (int i = 0; i < childPercentLengths.length; i++) { + children.get(i).setPercentLength(childPercentLengths[i]); + } + } + + public void setPercentLength(double percentLength) { + this.percentLength = percentLength; + } +} \ No newline at end of file diff --git a/src/dk/itu/mario/level/matcher/LevelArchetype.java b/src/dk/itu/mario/level/matcher/LevelArchetype.java index f77d8e9..7aebcfb 100644 --- a/src/dk/itu/mario/level/matcher/LevelArchetype.java +++ b/src/dk/itu/mario/level/matcher/LevelArchetype.java @@ -1,31 +1,53 @@ package dk.itu.mario.level.matcher; +import dk.itu.mario.MarioInterface.LevelInterface; + public class LevelArchetype { public static final int MAX_DIFFICULTY_LEVEL = 10; - //The canonical Super Mario level types. - public enum TYPE {OVERGROUND, UNDERGROUND, CASTLE} - + + // The canonical Super Mario level types. + public enum TYPE { + OVERGROUND, UNDERGROUND, CASTLE + } + private int difficultyLevel; private TYPE type; - + public LevelArchetype(TYPE type, int difficultyLevel) { if (difficultyLevel < 1 || difficultyLevel > MAX_DIFFICULTY_LEVEL) { - throw new IllegalArgumentException("DifficultyLevel must be in the range [1.." + MAX_DIFFICULTY_LEVEL +"]"); + throw new IllegalArgumentException( + "DifficultyLevel must be in the range [1.." + + MAX_DIFFICULTY_LEVEL + "]"); } this.difficultyLevel = difficultyLevel; this.type = type; } - + public int getDifficultyLevel() { return difficultyLevel; } - + public TYPE getType() { return type; } - + + public int getTypeInt() { + switch (type) { + case CASTLE: + return LevelInterface.TYPE_CASTLE; + case OVERGROUND: + return LevelInterface.TYPE_OVERGROUND; + case UNDERGROUND: + return LevelInterface.TYPE_UNDERGROUND; + default: + throw new UnsupportedOperationException("LevelArchetype.TYPE " + + type + " does not have a known integer value"); + } + } + @Override public String toString() { - return type + " (Difficulty " + difficultyLevel + "/" + MAX_DIFFICULTY_LEVEL+")"; + return type + " (Difficulty " + difficultyLevel + "/" + + MAX_DIFFICULTY_LEVEL + ")"; } }