A boring overland world was generated from a grammar!
This commit is contained in:
@@ -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)) {
|
||||
|
||||
32
src/dk/itu/mario/level/LevelComponent.java
Normal file
32
src/dk/itu/mario/level/LevelComponent.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
52
src/dk/itu/mario/level/grammar/LevelParseTree.java
Normal file
52
src/dk/itu/mario/level/grammar/LevelParseTree.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/dk/itu/mario/level/grammar/ParseNode.java
Normal file
62
src/dk/itu/mario/level/grammar/ParseNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
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}
|
||||
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;
|
||||
@@ -24,8 +31,23 @@ public class LevelArchetype {
|
||||
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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user