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.MarioInterface.LevelGenerator;
|
||||||
import dk.itu.mario.level.generator.CustomizedLevelGenerator;
|
import dk.itu.mario.level.generator.CustomizedLevelGenerator;
|
||||||
import dk.itu.mario.level.generator.MyLevelGenerator;
|
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;
|
import dk.itu.mario.level.generator.RandomLevelGenerator;
|
||||||
|
|
||||||
public class ParsedArgs {
|
public class ParsedArgs {
|
||||||
@@ -23,7 +23,7 @@ public class ParsedArgs {
|
|||||||
if ("MyLevel".equals(generatorClass)) {
|
if ("MyLevel".equals(generatorClass)) {
|
||||||
return new MyLevelGenerator();
|
return new MyLevelGenerator();
|
||||||
} else if ("MyNewLevel".equals(generatorClass)) {
|
} else if ("MyNewLevel".equals(generatorClass)) {
|
||||||
return new MyNewLevelGenerator();
|
return new PCGLevelGenerator();
|
||||||
} else if ("CustomizedLevel".equalsIgnoreCase(generatorClass)) {
|
} else if ("CustomizedLevel".equalsIgnoreCase(generatorClass)) {
|
||||||
return new CustomizedLevelGenerator();
|
return new CustomizedLevelGenerator();
|
||||||
} else if ("RandomLevel".equalsIgnoreCase(generatorClass)) {
|
} 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;
|
package dk.itu.mario.level;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import dk.itu.mario.MarioInterface.GamePlay;
|
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.GrammarTuner;
|
||||||
import dk.itu.mario.level.grammar.LevelGrammar;
|
import dk.itu.mario.level.grammar.LevelGrammar;
|
||||||
import dk.itu.mario.level.grammar.LevelGrammarFactory;
|
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.ArchetypeMatcher;
|
||||||
import dk.itu.mario.level.matcher.LevelArchetype;
|
import dk.itu.mario.level.matcher.LevelArchetype;
|
||||||
import dk.itu.mario.level.matcher.PlayerProfile;
|
import dk.itu.mario.level.matcher.PlayerProfile;
|
||||||
@@ -82,6 +84,7 @@ public class PCGLevel extends Level {
|
|||||||
grammar = GrammarTuner.tune(grammar, profile, archetype);
|
grammar = GrammarTuner.tune(grammar, profile, archetype);
|
||||||
|
|
||||||
System.out.println("Creating level.");
|
System.out.println("Creating level.");
|
||||||
|
create(seed, profile, archetype, grammar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
LevelArchetype archetype, LevelGrammar grammar) {
|
||||||
if (dataRecorder == DataRecorder.BLANK_RECORD) {
|
if (dataRecorder == DataRecorder.BLANK_RECORD) {
|
||||||
System.out
|
System.out
|
||||||
.println("DataRecorder record is BLANK - using GamePlay metrics only.");
|
.println("DataRecorder record is BLANK - using GamePlay metrics only.");
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("Sample level parameters: "
|
|
||||||
+ grammar.generateRandom(seed));
|
|
||||||
|
|
||||||
this.type = archetype.getTypeInt();
|
this.type = archetype.getTypeInt();
|
||||||
this.difficulty = archetype.getDifficultyLevel();
|
this.difficulty = archetype.getDifficultyLevel();
|
||||||
|
|
||||||
lastSeed = seed;
|
lastSeed = seed;
|
||||||
random = new Random(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
|
// create the start location
|
||||||
int length = buildStraight(0, width, true);
|
int length = buildStraight(0, width, true);
|
||||||
length += buildPipeJump(length, width - length);
|
length += buildPipeJump(length, width - length);
|
||||||
@@ -134,13 +157,17 @@ public class PCGLevel extends Level {
|
|||||||
while (length < width - 64) {
|
while (length < width - 64) {
|
||||||
length += buildStraight(length, width - length, true);
|
length += buildStraight(length, width - length, true);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// set the end piece
|
// set the end piece
|
||||||
int floor = height - 1 - random.nextInt(4);
|
int floor = height - 1 - random.nextInt(4);
|
||||||
|
|
||||||
xExit = length + 8;
|
xExit = length + 8;
|
||||||
yExit = floor;
|
yExit = floor;
|
||||||
|
|
||||||
|
fillEndPiece(length, floor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillEndPiece(int length, int floor) {
|
||||||
// fills the end piece
|
// fills the end piece
|
||||||
for (int x = length; x < width; x++) {
|
for (int x = length; x < width; x++) {
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
@@ -168,9 +195,7 @@ public class PCGLevel extends Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fixWalls();
|
fixWalls();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEnemyLine(int x0, int x1, int y) {
|
private void addEnemyLine(int x0, int x1, int y) {
|
||||||
for (int x = x0; x < x1; x++) {
|
for (int x = x0; x < x1; x++) {
|
||||||
if (random.nextInt(35) < difficulty + 1) {
|
if (random.nextInt(35) < difficulty + 1) {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.util.Random;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import dk.itu.mario.level.LevelComponent.TYPE;
|
||||||
|
|
||||||
|
|
||||||
public class LevelGrammar {
|
public class LevelGrammar {
|
||||||
private Variable start;
|
private Variable start;
|
||||||
@@ -45,6 +47,24 @@ public class LevelGrammar {
|
|||||||
return generateRandom(new Random().nextLong());
|
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) {
|
public ProductionRule getRule(Variable var) {
|
||||||
return ruleMap.get(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;
|
package dk.itu.mario.level.matcher;
|
||||||
|
|
||||||
|
import dk.itu.mario.MarioInterface.LevelInterface;
|
||||||
|
|
||||||
public class LevelArchetype {
|
public class LevelArchetype {
|
||||||
public static final int MAX_DIFFICULTY_LEVEL = 10;
|
public static final int MAX_DIFFICULTY_LEVEL = 10;
|
||||||
|
|
||||||
// The canonical Super Mario level types.
|
// The canonical Super Mario level types.
|
||||||
public enum TYPE {OVERGROUND, UNDERGROUND, CASTLE}
|
public enum TYPE {
|
||||||
|
OVERGROUND, UNDERGROUND, CASTLE
|
||||||
|
}
|
||||||
|
|
||||||
private int difficultyLevel;
|
private int difficultyLevel;
|
||||||
private TYPE type;
|
private TYPE type;
|
||||||
|
|
||||||
public LevelArchetype(TYPE type, int difficultyLevel) {
|
public LevelArchetype(TYPE type, int difficultyLevel) {
|
||||||
if (difficultyLevel < 1 || difficultyLevel > MAX_DIFFICULTY_LEVEL) {
|
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.difficultyLevel = difficultyLevel;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -24,8 +31,23 @@ public class LevelArchetype {
|
|||||||
return type;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return type + " (Difficulty " + difficultyLevel + "/" + MAX_DIFFICULTY_LEVEL+")";
|
return type + " (Difficulty " + difficultyLevel + "/"
|
||||||
|
+ MAX_DIFFICULTY_LEVEL + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user