Renamed MyNewLevel -> PCGLevel.
This commit is contained in:
543
src/dk/itu/mario/level/PCGLevel.java
Normal file
543
src/dk/itu/mario/level/PCGLevel.java
Normal file
@@ -0,0 +1,543 @@
|
||||
package dk.itu.mario.level;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import dk.itu.mario.MarioInterface.GamePlay;
|
||||
import dk.itu.mario.MarioInterface.LevelInterface;
|
||||
import dk.itu.mario.engine.DataRecorder;
|
||||
import dk.itu.mario.engine.sprites.Enemy;
|
||||
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.matcher.ArchetypeMatcher;
|
||||
import dk.itu.mario.level.matcher.LevelArchetype;
|
||||
import dk.itu.mario.level.matcher.PlayerProfile;
|
||||
import dk.itu.mario.level.matcher.ProfileMatcher;
|
||||
|
||||
public class PCGLevel extends Level {
|
||||
public enum MazeLevel {
|
||||
BOT, MID, TOP
|
||||
}
|
||||
|
||||
public static long lastSeed;
|
||||
private static Random levelSeedRandom = new Random();
|
||||
|
||||
public int BLOCKS_COINS = 0; // the number of coin blocks
|
||||
public int BLOCKS_EMPTY = 0; // the number of empty blocks
|
||||
public int BLOCKS_POWER = 0; // the number of power blocks
|
||||
public int COINS = 0; // These are the coins in boxes that Mario collect
|
||||
|
||||
private DataRecorder dataRecorder;
|
||||
private int difficulty;
|
||||
// Store information about the level
|
||||
public int ENEMIES = 0; // the number of enemies the level contains
|
||||
private int gaps;
|
||||
private int type;
|
||||
private Random random;
|
||||
|
||||
public PCGLevel(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public PCGLevel(int width, int height, long seed, int difficulty,
|
||||
int type, GamePlay playerMetrics) {
|
||||
this(width, height);
|
||||
|
||||
System.out
|
||||
.println("Generating level based on previous GamePlay metrics ONLY.");
|
||||
this.dataRecorder = DataRecorder.BLANK_RECORD;
|
||||
|
||||
generateLevel(seed, playerMetrics);
|
||||
}
|
||||
|
||||
public PCGLevel(int width, int height, long seed, int difficulty,
|
||||
int type, GamePlay playerMetrics, DataRecorder dataRecorder) {
|
||||
this(width, height);
|
||||
|
||||
System.out
|
||||
.println("Generating level based on previous GamePlay AND DataRecorder metrics.");
|
||||
this.dataRecorder = dataRecorder;
|
||||
|
||||
generateLevel(seed, playerMetrics);
|
||||
}
|
||||
|
||||
private void generateLevel(long seed, GamePlay playerMetrics) {
|
||||
|
||||
PlayerProfile profile = ProfileMatcher.getMatchingProfile(
|
||||
playerMetrics, dataRecorder);
|
||||
System.out.println("PlayerProfile: " + profile);
|
||||
|
||||
LevelArchetype archetype = ArchetypeMatcher.getMatchingArchetype(
|
||||
playerMetrics, dataRecorder);
|
||||
System.out.println("LevelArchetype: " + archetype);
|
||||
|
||||
System.out.println("Creating level grammar");
|
||||
LevelGrammar grammar = LevelGrammarFactory.createGrammar(profile,
|
||||
archetype);
|
||||
|
||||
System.out
|
||||
.println("Tuning grammar for PlayerProfile & LevelArchetype using RETE");
|
||||
grammar = GrammarTuner.tune(grammar, profile, archetype);
|
||||
|
||||
System.out.println("Creating level.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomLevel clone() throws CloneNotSupportedException {
|
||||
|
||||
RandomLevel clone = new RandomLevel(width, height);
|
||||
|
||||
clone.xExit = xExit;
|
||||
clone.yExit = yExit;
|
||||
byte[][] map = getMap();
|
||||
SpriteTemplate[][] st = getSpriteTemplate();
|
||||
|
||||
for (int i = 0; i < map.length; i++)
|
||||
for (int j = 0; j < map[i].length; j++) {
|
||||
clone.setBlock(i, j, map[i][j]);
|
||||
clone.setSpriteTemplate(i, j, st[i][j]);
|
||||
}
|
||||
clone.BLOCKS_COINS = BLOCKS_COINS;
|
||||
clone.BLOCKS_EMPTY = BLOCKS_EMPTY;
|
||||
clone.BLOCKS_POWER = BLOCKS_POWER;
|
||||
clone.ENEMIES = ENEMIES;
|
||||
clone.COINS = COINS;
|
||||
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public 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);
|
||||
|
||||
// create the start location
|
||||
int length = buildStraight(0, width, true);
|
||||
length += buildPipeJump(length, width - length);
|
||||
// length += buildMaze(length, width - length);
|
||||
|
||||
// create all of the medium sections
|
||||
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;
|
||||
|
||||
// fills the end piece
|
||||
for (int x = length; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (y >= floor) {
|
||||
setBlock(x, y, GROUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == LevelInterface.TYPE_CASTLE
|
||||
|| type == LevelInterface.TYPE_UNDERGROUND) {
|
||||
int ceiling = 0;
|
||||
int run = 0;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (run-- <= 0 && x > 4) {
|
||||
ceiling = random.nextInt(4);
|
||||
run = random.nextInt(4) + 4;
|
||||
}
|
||||
for (int y = 0; y < height; y++) {
|
||||
if ((x > 4 && y <= ceiling) || x < 1) {
|
||||
setBlock(x, y, GROUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fixWalls();
|
||||
|
||||
}
|
||||
|
||||
private void addEnemyLine(int x0, int x1, int y) {
|
||||
for (int x = x0; x < x1; x++) {
|
||||
if (random.nextInt(35) < difficulty + 1) {
|
||||
int type = random.nextInt(4);
|
||||
|
||||
if (difficulty < 1) {
|
||||
type = Enemy.ENEMY_GOOMBA;
|
||||
} else if (difficulty < 3) {
|
||||
type = random.nextInt(3);
|
||||
}
|
||||
|
||||
setSpriteTemplate(x, y,
|
||||
new SpriteTemplate(type,
|
||||
random.nextInt(35) < difficulty));
|
||||
ENEMIES++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void blockify(Level level, boolean[][] blocks, int width, int height) {
|
||||
int to = 0;
|
||||
if (type == LevelInterface.TYPE_CASTLE) {
|
||||
to = 4 * 2;
|
||||
} else if (type == LevelInterface.TYPE_UNDERGROUND) {
|
||||
to = 4 * 3;
|
||||
}
|
||||
|
||||
boolean[][] b = new boolean[2][2];
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int xx = x; xx <= x + 1; xx++) {
|
||||
for (int yy = y; yy <= y + 1; yy++) {
|
||||
int _xx = xx;
|
||||
int _yy = yy;
|
||||
if (_xx < 0)
|
||||
_xx = 0;
|
||||
if (_yy < 0)
|
||||
_yy = 0;
|
||||
if (_xx > width - 1)
|
||||
_xx = width - 1;
|
||||
if (_yy > height - 1)
|
||||
_yy = height - 1;
|
||||
b[xx - x][yy - y] = blocks[_xx][_yy];
|
||||
}
|
||||
}
|
||||
|
||||
if (b[0][0] == b[1][0] && b[0][1] == b[1][1]) {
|
||||
if (b[0][0] == b[0][1]) {
|
||||
if (b[0][0]) {
|
||||
level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
|
||||
} else {
|
||||
// KEEP OLD BLOCK!
|
||||
}
|
||||
} else {
|
||||
if (b[0][0]) {
|
||||
// down grass top?
|
||||
level.setBlock(x, y, (byte) (1 + 10 * 16 + to));
|
||||
} else {
|
||||
// up grass top
|
||||
level.setBlock(x, y, (byte) (1 + 8 * 16 + to));
|
||||
}
|
||||
}
|
||||
} else if (b[0][0] == b[0][1] && b[1][0] == b[1][1]) {
|
||||
if (b[0][0]) {
|
||||
// right grass top
|
||||
level.setBlock(x, y, (byte) (2 + 9 * 16 + to));
|
||||
} else {
|
||||
// left grass top
|
||||
level.setBlock(x, y, (byte) (0 + 9 * 16 + to));
|
||||
}
|
||||
} else if (b[0][0] == b[1][1] && b[0][1] == b[1][0]) {
|
||||
level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
|
||||
} else if (b[0][0] == b[1][0]) {
|
||||
if (b[0][0]) {
|
||||
if (b[0][1]) {
|
||||
level.setBlock(x, y, (byte) (3 + 10 * 16 + to));
|
||||
} else {
|
||||
level.setBlock(x, y, (byte) (3 + 11 * 16 + to));
|
||||
}
|
||||
} else {
|
||||
if (b[0][1]) {
|
||||
// right up grass top
|
||||
level.setBlock(x, y, (byte) (2 + 8 * 16 + to));
|
||||
} else {
|
||||
// left up grass top
|
||||
level.setBlock(x, y, (byte) (0 + 8 * 16 + to));
|
||||
}
|
||||
}
|
||||
} else if (b[0][1] == b[1][1]) {
|
||||
if (b[0][1]) {
|
||||
if (b[0][0]) {
|
||||
// left pocket grass
|
||||
level.setBlock(x, y, (byte) (3 + 9 * 16 + to));
|
||||
} else {
|
||||
// right pocket grass
|
||||
level.setBlock(x, y, (byte) (3 + 8 * 16 + to));
|
||||
}
|
||||
} else {
|
||||
if (b[0][0]) {
|
||||
level.setBlock(x, y, (byte) (2 + 10 * 16 + to));
|
||||
} else {
|
||||
level.setBlock(x, y, (byte) (0 + 10 * 16 + to));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
level.setBlock(x, y, (byte) (0 + 1 * 16 + to));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int buildMaze(int xo, int maxLength) {
|
||||
int length = random.nextInt(maxLength - 19) + 20;
|
||||
int soFar = 0;
|
||||
int next;
|
||||
// MazeLevel last = MazeLevel.BOT;
|
||||
|
||||
class Stretch {
|
||||
public int len;
|
||||
public PCGLevel.MazeLevel lvl;
|
||||
|
||||
public Stretch(int lngth) {
|
||||
len = lngth;
|
||||
|
||||
switch (random.nextInt(3)) {
|
||||
case 0:
|
||||
lvl = MazeLevel.TOP;
|
||||
break;
|
||||
case 1:
|
||||
lvl = MazeLevel.MID;
|
||||
break;
|
||||
default:
|
||||
lvl = MazeLevel.BOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Stretch> maze = new ArrayList<Stretch>();
|
||||
|
||||
while (soFar < length - 3) {
|
||||
next = random.nextInt(length / 2) + 1;
|
||||
|
||||
if (soFar + next > length - 3) {
|
||||
next = length - soFar;
|
||||
}
|
||||
|
||||
maze.add(new Stretch(next));
|
||||
|
||||
soFar += next;
|
||||
}
|
||||
|
||||
soFar = 0;
|
||||
setBlock(xo, this.height - 1, Level.GROUND);
|
||||
setBlock(xo + 1, this.height - 1, Level.GROUND);
|
||||
setBlock(xo + 2, this.height - 1, Level.GROUND);
|
||||
|
||||
for (Stretch str : maze) {
|
||||
for (int x = 0; x < str.len; x++) {
|
||||
setBlock(xo + 3 + soFar + x, this.height - 1, Level.GROUND);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 4, Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 7, Level.BLOCK_EMPTY);
|
||||
|
||||
if (x == str.len / 2) {
|
||||
|
||||
if (str.lvl != MazeLevel.BOT) {
|
||||
setBlock(xo + 3 + soFar + x, this.height - 2,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 3,
|
||||
Level.BLOCK_EMPTY);
|
||||
}
|
||||
if (str.lvl != MazeLevel.MID) {
|
||||
setBlock(xo + 3 + soFar + x, this.height - 5,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 6,
|
||||
Level.BLOCK_EMPTY);
|
||||
}
|
||||
if (str.lvl != MazeLevel.TOP) {
|
||||
setBlock(xo + 3 + soFar + x, this.height - 8,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 9,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 10,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 11,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 12,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 13,
|
||||
Level.BLOCK_EMPTY);
|
||||
setBlock(xo + 3 + soFar + x, this.height - 14,
|
||||
Level.BLOCK_EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private int buildPipeJump(int xo, int maxLength) {
|
||||
int numPipes = 4;
|
||||
int length = numPipes * 2;
|
||||
int lastHeight = 4;
|
||||
int localHeight;
|
||||
int pipeHeight;
|
||||
int gap = 0;
|
||||
|
||||
while (length > maxLength) {
|
||||
numPipes--;
|
||||
length = numPipes * 2;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numPipes && length < maxLength; i++) {
|
||||
length += random.nextInt(4);
|
||||
}
|
||||
|
||||
if (length > maxLength) {
|
||||
length = maxLength;
|
||||
}
|
||||
|
||||
for (int soFar = 0; numPipes > 0; numPipes--) {
|
||||
localHeight = (soFar == 0) ? random.nextInt(2) + 4 : random
|
||||
.nextInt(7) + 4;
|
||||
|
||||
while (Math.abs(localHeight - lastHeight) > 3) {
|
||||
localHeight += localHeight > lastHeight ? -1 : 1;
|
||||
}
|
||||
|
||||
lastHeight = localHeight;
|
||||
pipeHeight = localHeight > 5 ? 4 + random.nextInt(localHeight - 4)
|
||||
: 4;
|
||||
|
||||
for (int y = 0; y < localHeight; y++) {
|
||||
setBlock(xo + soFar, this.height - 1 - y, Level.GROUND);
|
||||
setBlock(xo + soFar + 1, this.height - 1 - y, Level.GROUND);
|
||||
|
||||
if (y == localHeight - 1) {
|
||||
setBlock(xo + soFar, this.height - 1 - y,
|
||||
Level.TUBE_TOP_LEFT);
|
||||
setBlock(xo + soFar + 1, this.height - 1 - y,
|
||||
Level.TUBE_TOP_RIGHT);
|
||||
} else if (y > localHeight - pipeHeight) {
|
||||
setBlock(xo + soFar, this.height - 1 - y,
|
||||
Level.TUBE_SIDE_LEFT);
|
||||
setBlock(xo + soFar + 1, this.height - 1 - y,
|
||||
Level.TUBE_SIDE_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
gap = random.nextInt(4);
|
||||
while (soFar + gap + 2 > length && gap >= 0) {
|
||||
gap--;
|
||||
}
|
||||
|
||||
soFar += (2 + gap);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private int buildStraight(int xo, int maxLength, boolean safe) {
|
||||
int length = random.nextInt(10) + 2;
|
||||
|
||||
if (safe)
|
||||
length = 10 + random.nextInt(5);
|
||||
|
||||
if (length > maxLength)
|
||||
length = maxLength;
|
||||
|
||||
int floor = height - 1 - random.nextInt(4);
|
||||
|
||||
// runs from the specified x position to the length of the segment
|
||||
for (int x = xo; x < xo + length; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (y >= floor) {
|
||||
setBlock(x, y, GROUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!safe) {
|
||||
if (length > 5) {
|
||||
decorate(xo, xo + length, floor);
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private void decorate(int xStart, int xLength, int floor) {
|
||||
// if its at the very top, just return
|
||||
if (floor < 1)
|
||||
return;
|
||||
|
||||
// boolean coins = random.nextInt(3) == 0;
|
||||
boolean rocks = true;
|
||||
|
||||
// add an enemy line above the box
|
||||
addEnemyLine(xStart + 1, xLength - 1, floor - 1);
|
||||
|
||||
int s = random.nextInt(4);
|
||||
int e = random.nextInt(4);
|
||||
|
||||
if (floor - 2 > 0) {
|
||||
if ((xLength - 1 - e) - (xStart + 1 + s) > 1) {
|
||||
for (int x = xStart + 1 + s; x < xLength - 1 - e; x++) {
|
||||
setBlock(x, floor - 2, COIN);
|
||||
COINS++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s = random.nextInt(4);
|
||||
e = random.nextInt(4);
|
||||
|
||||
// this fills the set of blocks and the hidden objects inside them
|
||||
if (floor - 4 > 0) {
|
||||
if ((xLength - 1 - e) - (xStart + 1 + s) > 2) {
|
||||
for (int x = xStart + 1 + s; x < xLength - 1 - e; x++) {
|
||||
if (rocks) {
|
||||
if (x != xStart + 1 && x != xLength - 2
|
||||
&& random.nextInt(3) == 0) {
|
||||
if (random.nextInt(4) == 0) {
|
||||
setBlock(x, floor - 4, BLOCK_POWERUP);
|
||||
BLOCKS_POWER++;
|
||||
} else { // the fills a block with a hidden coin
|
||||
setBlock(x, floor - 4, BLOCK_COIN);
|
||||
BLOCKS_COINS++;
|
||||
}
|
||||
} else if (random.nextInt(4) == 0) {
|
||||
if (random.nextInt(4) == 0) {
|
||||
setBlock(x, floor - 4, (byte) (2 + 1 * 16));
|
||||
} else {
|
||||
setBlock(x, floor - 4, (byte) (1 + 1 * 16));
|
||||
}
|
||||
} else {
|
||||
setBlock(x, floor - 4, BLOCK_EMPTY);
|
||||
BLOCKS_EMPTY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fixWalls() {
|
||||
boolean[][] blockMap = new boolean[width + 1][height + 1];
|
||||
|
||||
for (int x = 0; x < width + 1; x++) {
|
||||
for (int y = 0; y < height + 1; y++) {
|
||||
int blocks = 0;
|
||||
for (int xx = x - 1; xx < x + 1; xx++) {
|
||||
for (int yy = y - 1; yy < y + 1; yy++) {
|
||||
if (getBlockCapped(xx, yy) == GROUND) {
|
||||
blocks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
blockMap[x][y] = blocks == 4;
|
||||
}
|
||||
}
|
||||
blockify(this, blockMap, width + 1, height + 1);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user