A boring overland world was generated from a grammar!

This commit is contained in:
Woody Folsom
2012-03-17 14:15:38 -04:00
parent 8bedf88fd3
commit 653c7fc3df
7 changed files with 231 additions and 18 deletions

View File

@@ -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)) {

View File

@@ -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 + "]";
}
}

View File

@@ -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<LevelComponent> 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) {

View File

@@ -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<Variable> 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);
}

View File

@@ -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<LevelComponent> getLevelTemplate() {
List<LevelComponent> levelCompList = new ArrayList<LevelComponent>();
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<LevelComponent> 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;
}
}
}
}

View File

@@ -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<ParseNode> children = new ArrayList<ParseNode>();
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;
}
}

View File

@@ -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 + ")";
}
}