Files
cs8803p4/src/dk/itu/mario/level/PCGLevel.java

732 lines
19 KiB
Java

package dk.itu.mario.level;
import java.util.ArrayList;
import java.util.List;
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.grammar.LevelParseTree;
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.");
create(seed, profile, archetype, grammar);
}
@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;
}
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.");
}
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:
length += buildStraight(length, lcomp.getEnd(), true);
break;
case PIPE_JUMP:
length += buildPipeJump(length, width-64-length);
break;
case PLATFORM_JUMP:
length += buildPlatformJump(length, width-64-length);
break;
case MAZE:
length += buildMaze(length, width-64-length);
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); // 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;
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++) {
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 = 6;
int next;
// boolean skipUp = false;
// boolean skipDown = false;
class Stretch {
public int len;
public LevelComponent.MazeLevel lvl;
public Stretch(int lngth) {
len = lngth;
switch (random.nextInt(3)) {
case 0:
lvl = LevelComponent.MazeLevel.TOP;
break;
case 1:
lvl = LevelComponent.MazeLevel.MID;
break;
default:
lvl = LevelComponent.MazeLevel.BOT;
}
}
}
ArrayList<Stretch> maze = new ArrayList<Stretch>();
loop: while (soFar < length) {
if (soFar + 3 > length) {
length = soFar;
break loop;
}
next = random.nextInt(18) + 5;
if (soFar + next > length) {
next = length - soFar;
}
maze.add(new Stretch(next));
soFar += next;
}
setBlock(xo, this.height - 1, Level.GROUND);
setBlock(xo + 1, this.height - 1, Level.GROUND);
setBlock(xo + 2, this.height - 1, Level.GROUND);
soFar = 3;
Stretch str;
// Stretch nxt;
boolean stretchEnd;
boolean midLine;
for (int i = 0; i < maze.size(); i++) {
str = maze.get(i);
// if (i < maze.size() - 1) {
// nxt = maze.get(i + 1);
// } else {
// nxt = null;
// }
// if (nxt != null) {
// skipUp = ((nxt.lvl != MazeLevel.TOP) && (str.lvl ==
// MazeLevel.TOP))
// || ((nxt.lvl == MazeLevel.TOP) && (str.lvl != MazeLevel.TOP));
//
// skipDown = ((nxt.lvl != MazeLevel.BOT) && (str.lvl ==
// MazeLevel.BOT))
// || ((str.lvl != MazeLevel.BOT) && (nxt.lvl == MazeLevel.BOT));
// }
//
// else {
// skipUp = false;
// skipDown = false;
// }
for (int x = 0; x < str.len; x++) {
setBlock(xo + soFar + x, this.height - 1, Level.GROUND);
// skipUp = (skipUp && (x == str.len - 2))
// || (str.len >= 5 && x == (str.len / 2));
// skipDown = (skipDown && (x == str.len - 2))
// || (str.len >= 5 && x == (str.len / 2));
midLine = (str.len >= 5 && x == (str.len / 2) - 1);
stretchEnd = (x == str.len - 1);
if // ((stretchEnd && nxt != null && nxt.lvl != MazeLevel.BOT)
// ||
(midLine && str.lvl != LevelComponent.MazeLevel.BOT) // )
{
setBlock(xo + soFar + x, this.height - 2, Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 3, Level.BLOCK_EMPTY);
}
if // ((stretchEnd && nxt != null && nxt.lvl != MazeLevel.MID)
// ||
(midLine && str.lvl != LevelComponent.MazeLevel.MID)// )
{
setBlock(xo + soFar + x, this.height - 5, Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 6, Level.BLOCK_EMPTY);
}
if // ((stretchEnd && nxt != null && nxt.lvl != MazeLevel.TOP)
// ||
(midLine && str.lvl != LevelComponent.MazeLevel.TOP)// )
{
setBlock(xo + soFar + x, this.height - 8, Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 9, Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 10,
Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 11,
Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 12,
Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 13,
Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 14,
Level.BLOCK_EMPTY);
setBlock(xo + soFar + x, this.height - 15,
Level.BLOCK_EMPTY);
}
if (!stretchEnd) {
setBlock(xo + soFar + x, this.height - 7, Level.BLOCK_EMPTY);
}
if (!stretchEnd) {
setBlock(xo + soFar + x, this.height - 4, Level.BLOCK_EMPTY);
}
}
soFar += str.len;
}
setBlock(xo + length - 1, this.height - 1, Level.GROUND);
setBlock(xo + length - 2, this.height - 1, Level.GROUND);
setBlock(xo + length - 3, this.height - 1, Level.GROUND);
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 buildPlatformJump(int xo, int maxLength) {
int length = 0;
int numPlatforms = random.nextInt((maxLength - 3) / 8);
if (numPlatforms > 1) {
boolean found = false;
MazeLevel nextDir = MazeLevel.TOP;
LevelComponent.PlatformLevel last;
LevelComponent.PlatformLevel next;
ArrayList<LevelComponent.PlatformLevel> jumps = new ArrayList<LevelComponent.PlatformLevel>();
int heightMod;
jumps.add(random.nextBoolean() ? LevelComponent.PlatformLevel.BOT
: LevelComponent.PlatformLevel.MID_D);
for (int i = 1; i < numPlatforms; i++) {
last = jumps.get(i - 1);
found = false;
while (!found) {
switch (random.nextInt(5)) {
case 0:
case 1:
nextDir = MazeLevel.BOT;
break;
case 2:
case 3:
nextDir = MazeLevel.TOP;
break;
default:
nextDir = MazeLevel.MID;
}
found = !((last == LevelComponent.PlatformLevel.TOP && nextDir == MazeLevel.TOP) || (last == LevelComponent.PlatformLevel.BOT && nextDir == MazeLevel.BOT));
}
if ((last == LevelComponent.PlatformLevel.BOT && nextDir == MazeLevel.MID)
|| (last == LevelComponent.PlatformLevel.MID_D && nextDir == MazeLevel.BOT)) {
next = LevelComponent.PlatformLevel.BOT;
}
else if ((last == LevelComponent.PlatformLevel.MID_D && nextDir == MazeLevel.MID)
|| (last == LevelComponent.PlatformLevel.MID_U && nextDir == MazeLevel.BOT)
|| (last == LevelComponent.PlatformLevel.BOT && nextDir == MazeLevel.TOP)) {
next = LevelComponent.PlatformLevel.MID_D;
}
else if ((last == LevelComponent.PlatformLevel.MID_U && nextDir == MazeLevel.MID)
|| (last == LevelComponent.PlatformLevel.TOP && nextDir == MazeLevel.BOT)
|| (last == LevelComponent.PlatformLevel.MID_D && nextDir == MazeLevel.TOP)) {
next = LevelComponent.PlatformLevel.MID_U;
}
else // if ((last == PlatformLevel.TOP && nextDir ==
// MazeLevel.MID)
// || (last == PlatformLevel.MID_U && nextDir ==
// MazeLevel.TOP))
{
next = LevelComponent.PlatformLevel.TOP;
}
jumps.add(next);
}
setBlock(xo, this.height - 1, Level.GROUND);
setBlock(xo + 1, this.height - 1, Level.GROUND);
setBlock(xo + 2, this.height - 1, Level.GROUND);
length = 3;
for (int x = 0; x < jumps.size(); x++) {
switch (jumps.get(x)) {
case TOP:
heightMod = 12;
break;
case MID_U:
heightMod = 9;
break;
case MID_D:
heightMod = 6;
break;
default:
heightMod = 3;
}
setBlock(xo + length, this.height - heightMod,
Level.BLOCK_EMPTY);
setBlock(xo + length + 1, this.height - heightMod,
Level.BLOCK_EMPTY);
setBlock(xo + length + 2, this.height - heightMod,
Level.BLOCK_EMPTY);
setBlock(xo + length + 3, this.height - heightMod,
Level.BLOCK_EMPTY);
length += 8;
}
}
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);
}
}