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 static boolean TESTING = true; 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); int length = 0; if (TESTING) { length = buildStraight(0, width - 64, true); length += buildLemmingTrap(length, width - 64 - length, SpriteTemplate.GOOMPA); length += buildLemmingTrap(length, width - 64 - length, SpriteTemplate.GREEN_TURTLE); length += buildLemmingTrap(length, width - 64 - length, SpriteTemplate.ARMORED_TURTLE); length += buildPlatformJump(length, width - 64 - length); length += buildMaze(length, width - 64 - length); length += buildPipeJump(length, width - 64 - length); // create all of the medium sections while (length < width - 64) { length += buildStraight(length, width - length, true); } } else { System.out.println("Generating level for component list: "); LevelParseTree parseTree = grammar.generateRandomTree(seed, width); List levelTemplate = parseTree.getLevelTemplate(); 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 int buildLemmingTrap(int xo, int maxLength, int enemyType) { if (maxLength >= 14 && (enemyType == SpriteTemplate.GOOMPA || enemyType == SpriteTemplate.GREEN_TURTLE || enemyType == SpriteTemplate.ARMORED_TURTLE)) { for (int x = 0; x < 18; x++) { if (x > 5) { for (int y = 0; y < 5; y++) { setBlock(xo + x, this.height - 1 - y, Level.GROUND); } } else { setBlock(xo + x, this.height - 1 - ((x > 5) ? 6 : 0), Level.GROUND); } if (x > 6 && x % 2 == 0) { setSpriteTemplate(xo + x, this.height - 6, new SpriteTemplate(enemyType, false)); } } return 18; } return 0; } 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 maze = new ArrayList(); 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; LevelComponent.MazeLevel nextDir = LevelComponent.MazeLevel.TOP; LevelComponent.PlatformLevel last; LevelComponent.PlatformLevel next; ArrayList jumps = new ArrayList(); 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 = LevelComponent.MazeLevel.BOT; break; case 2: case 3: nextDir = LevelComponent.MazeLevel.TOP; break; default: nextDir = LevelComponent.MazeLevel.MID; } found = !((last == LevelComponent.PlatformLevel.TOP && nextDir == LevelComponent.MazeLevel.TOP) || (last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.BOT)); } if ((last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.MID) || (last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.MazeLevel.BOT)) { next = LevelComponent.PlatformLevel.BOT; } else if ((last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.MazeLevel.MID) || (last == LevelComponent.PlatformLevel.MID_U && nextDir == LevelComponent.MazeLevel.BOT) || (last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.TOP)) { next = LevelComponent.PlatformLevel.MID_D; } else if ((last == LevelComponent.PlatformLevel.MID_U && nextDir == LevelComponent.MazeLevel.MID) || (last == LevelComponent.PlatformLevel.TOP && nextDir == LevelComponent.MazeLevel.BOT) || (last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.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; } heightMod += (random.nextBoolean() ? random.nextInt(2) : -1 * random.nextInt(2)); 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); } }