package dk.itu.mario.level; import java.io.File; 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.SpriteTemplate; import dk.itu.mario.level.LevelComponent.TYPE; 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.ProfileMatcher; public class PCGLevel extends Level { public static double POWERUP_PROBABILITY = .1; public static double BLOCKS_PROBABILITY = .3; public static double COINS_PROBABILITY = .5; public static int COIN_REWARD = 1; public static int POWERUP_REWARD = 2; // disable TESTING - enable grammar-based level generation public static boolean TESTING = false; 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 enum ChallengeType { GAP, ENEMY, HARDER_ENEMY, JUMP }; 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; try { String grammarFileName = "grammars/overland.grm"; grammar = LevelGrammarFactory.createGrammar(new File( grammarFileName)); System.out.println("Read grammar from file: " + grammarFileName); System.out.println("==== LEVEL GRAMMAR ===="); System.out.println(grammar); System.out.println("======================="); } catch (Exception ex) { System.out .println("Failed to parse grammar file due to exception: " + ex.getMessage()); System.out.println("Defaulting to basic overland grammar."); grammar = LevelGrammarFactory.createGrammar(); } System.out .println("Tuning grammar for PlayerProfile & LevelArchetype using RETE"); 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) { System.out.println("PlayerProfile.getProbability(COINS): " + profile.getProbability(TYPE.COINS)); 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) { int minElements = 5; int maxLen; int elementsSoFar = 0; length = buildStraight(0, width - 64, false); while (length < width - 64) { maxLen = elementsSoFar < minElements ? (width - 64 - length) / (minElements - elementsSoFar) : width - 64 - length; switch (random.nextInt(9)) { case 0: length += buildStraight(length, maxLen, true); break; case 1: length += buildFreebie(length, maxLen); break; case 2: length += buildBowlingAlley(length, maxLen); break; case 3: length += buildLemmingTrap(length, maxLen); break; case 4: length += buildPipeJump(length, maxLen); break; case 5: length += buildPlatformJump(length, maxLen); break; case 6: length += buildCoinDive(length, maxLen); break; case 7: length += buildCannonLine(length, maxLen); break; case 8: length += buildSinglePit(length, maxLen); break; default: length += buildMaze(length, maxLen); } elementsSoFar++; } } else { System.out.println("Generating level for component list: "); LevelParseTree parseTree = grammar.generateRandomTree(seed, width); int MAX_REGENS = 10; int nRegens = 0; while (!FitnessEvaluator.isFit(parseTree, profile, archetype) && nRegens < MAX_REGENS) { System.out .println("Generated level is NOT fit. Regenerating..."); parseTree = grammar.generateRandomTree(seed, width); nRegens++; } if (nRegens == MAX_REGENS) { System.out.println("Failed to generate a fit level after " + nRegens + " attempts. Proceeding with unfit level."); } List levelTemplate = parseTree.getLevelTemplate(); for (LevelComponent lcomp : levelTemplate) { LevelComponent.TYPE lctype = lcomp.getType(); System.out.println("Building for: " + lcomp); while (length < Math.min(width - 64, lcomp.getEnd())) { switch (lctype) { case FLAT_LO: case FLAT_HI: 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); // set the end piece int floor = height - 1 - random.nextInt(4); xExit = length + 8; yExit = floor; fillEndPiece(length, floor); } private int buildSinglePit(int xo, int maxLength) { if (maxLength >= 13) { int length = 13; int floor = height - 1 - random.nextInt(4); for (int x = 0; x < length; x++) { if (x != 5 && x != 6 && x != 7) { for (int y = floor; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } } if ((x >= 2 && x <= 4) || (x >= 8 && x <= 10)) { setBlock(xo + x, floor - 1, Level.ROCK); if (x == 3 || x == 4 || x == 8 || x == 9) { setBlock(xo + x, floor - 2, Level.ROCK); } if (x == 4 || x == 8) { setBlock(xo + x, floor - 3, Level.ROCK); } } } return length; } return 0; } private int buildCannonLine(int xo, int maxLength) { if (maxLength >= 6) { int floor = height - 1 - random.nextInt(4); int length = 6; for (int x = 0; x < length; x++) { for (int y = floor; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } if (x == 3) { setBlock(xo + x, floor - 1, Level.CANNON_MIDDLE); setBlock(xo + x, floor - 2, Level.CANNON_TOP); setBlock( xo + x, floor - 3, shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? Level.CANNON_TOP : Level.CANNON_MIDDLE); setBlock(xo + x, floor - 4, Level.CANNON_TOP); } } return length; } return 0; } private int buildCoinDive(int xo, int maxLength) { if (maxLength >= 18) { int length = 18; int floor = height - 1 - random.nextInt(4); for (int x = 0; x < length; x++) { for (int y = floor; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } if (x > 2 && x < 6) { setBlock(xo + x, floor - 3, Level.BLOCK_EMPTY); } else if (x > 2 && x < 11) { setBlock(xo + x, floor - 5, Level.BLOCK_EMPTY); } else if (x == 11 || x == 12) { setBlock(xo + x, floor - 6, Level.COIN); } else if (x == 13) { setBlock(xo + x, floor - 5, Level.COIN); } else if (x == 14) { setBlock(xo + x, floor - 4, Level.COIN); } else if (x == 15) { setBlock(xo + x, floor - 3, Level.COIN); } } return length; } return 0; } private int buildFreebie(int xo, int maxLength) { if (maxLength >= 9) { int floor = height - 1 - random.nextInt(4); for (int x = 0; x < 9; x++) { for (int y = 0; y < height; y++) { if (y >= floor) { setBlock(xo + x, y, GROUND); } } } if (random.nextBoolean()) { setBlock(xo + 1, floor - 1, Level.BLOCK_EMPTY); setBlock(xo + 7, floor - 1, Level.BLOCK_POWERUP); setSpriteTemplate(xo + 6, floor - 1, new SpriteTemplate( SpriteTemplate.GREEN_TURTLE, false)); } else { int powerupLoc = random.nextInt(5); setBlock(xo + 2, floor - 3, powerupLoc == 0 ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock(xo + 3, floor - 3, powerupLoc == 1 ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock(xo + 4, floor - 3, powerupLoc == 2 ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock(xo + 5, floor - 3, powerupLoc == 3 ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock(xo + 6, floor - 3, powerupLoc == 4 ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } return 9; } else if (maxLength >= 5) { int floor = height - 1 - random.nextInt(4); for (int x = 0; x < 5; x++) { for (int y = floor; y < height; y++) { setBlock(xo + x, y, GROUND); } } if (random.nextBoolean()) { setBlock(xo, floor - 1, Level.BLOCK_EMPTY); setBlock(xo + 4, floor - 1, Level.BLOCK_POWERUP); setSpriteTemplate(xo + 3, floor - 1, new SpriteTemplate( SpriteTemplate.GREEN_TURTLE, false)); } else { setBlock(xo + 2, floor - 3, Level.BLOCK_POWERUP); } return 5; } return 0; } private int buildBowlingAlley(int xo, int maxLength) { if (maxLength >= 26) { int floor = height - 1 - random.nextInt(4); int enemyType = shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA; int reward = shouldAddReward(); boolean arc = random.nextBoolean(); int numEnemies = 0; for (int i = 0; i < 10; i++) { numEnemies += shouldAddChallenge(ChallengeType.ENEMY) ? 1 : 0; } // Create the pit. for (int y = floor - 1; y < height; y++) { setBlock(xo, y, Level.GROUND); setBlock(xo + 1, y, Level.GROUND); } for (int y = floor; y < height; y++) { setBlock(xo + 2, y, Level.GROUND); setBlock(xo + 3, y, Level.GROUND); setBlock(xo + 4, y, Level.GROUND); } setSpriteTemplate(xo + 2, floor - 1, new SpriteTemplate( SpriteTemplate.RED_TURTLE, false)); for (int x = 5; x < 26; x++) { for (int y = floor - 3; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } if (arc && reward == 1 && x >= 11 && x <= 23) { if (x == 15) { setBlock(xo + x, floor - 6, Level.COIN); } else if (x == 16) { setBlock(xo + x, floor - 7, Level.COIN); } else if (x == 18) { setBlock(xo + x, floor - 8, Level.COIN); } else if (x == 19) { setBlock(xo + x, floor - 8, Level.COIN); } else if (x == 20) { setBlock(xo + x, floor - 8, Level.COIN); } else if (x == 22) { setBlock(xo + x, floor - 7, Level.COIN); } else if (x == 23) { setBlock(xo + x, floor - 6, Level.COIN); } } else if (!arc && reward == 1 && x >= 10 && x <= 20) { setBlock(xo + x, floor - 6, Level.COIN); } else if (reward == 2 && x >= 13 && x <= 17) { setBlock( xo + x, floor - 6, (random.nextDouble() <= POWERUP_PROBABILITY) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } if (x >= 26 - numEnemies) { setSpriteTemplate(xo + x, floor - 4, new SpriteTemplate( enemyType, false)); } } return 26; } return 0; } private int buildLemmingTrap(int xo, int maxLength) { if (maxLength >= 14) { int floor = height - 1 - random.nextInt(4); int enemyType = shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA; boolean flying = shouldAddChallenge(ChallengeType.HARDER_ENEMY) && shouldAddChallenge(ChallengeType.HARDER_ENEMY) && shouldAddChallenge(ChallengeType.HARDER_ENEMY); int reward = shouldAddReward(); for (int x = 0; x < 18; x++) { if (x > 5) { for (int y = floor - 4; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } } else { for (int y = floor; y < height; y++) { setBlock(xo + x, y, Level.GROUND); } } if (x > 6 && x % 2 == 0) { setSpriteTemplate(xo + x, floor - 5, new SpriteTemplate( enemyType, flying)); } if (reward == 1 && x >= 7 && x <= 16) { setBlock(xo + x, floor - 7, Level.COIN); } else if (reward == 2 && x >= 10 && x <= 13) { setBlock( xo + x, floor - 7, random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } } 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 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)); } } } } // This is basically just a randomizer function to call whenever I need to // randomly add difficulty based on the user's skill level. private boolean shouldAddChallenge(ChallengeType ct) { return random.nextInt(11) + 1 <= difficulty; } private int shouldAddReward() { double guess = random.nextDouble(); if (guess < COINS_PROBABILITY) { return 1; } else if (guess < COINS_PROBABILITY + BLOCKS_PROBABILITY) { return 2; } else { return 0; } } private int buildMaze(int xo, int maxLength) { if (maxLength >= 19) { 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; boolean addEnemy; int heightMod; int enemyType; for (int i = 0; i < maze.size(); i++) { str = maze.get(i); 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); addEnemy = !midLine && !stretchEnd && (random.nextDouble() < .25); if // ((stretchEnd && nxt != null && nxt.lvl != // MazeLevel.BOT) // || (midLine && str.lvl != LevelComponent.MazeLevel.BOT) // ) { setBlock(xo + soFar + x, this.height - 2, Level.ROCK); setBlock(xo + soFar + x, this.height - 3, Level.ROCK); } if // ((stretchEnd && nxt != null && nxt.lvl != // MazeLevel.MID) // || (midLine && str.lvl != LevelComponent.MazeLevel.MID)// ) { setBlock(xo + soFar + x, this.height - 5, Level.ROCK); setBlock(xo + soFar + x, this.height - 6, Level.ROCK); } if // ((stretchEnd && nxt != null && nxt.lvl != // MazeLevel.TOP) // || (midLine && str.lvl != LevelComponent.MazeLevel.TOP)// ) { setBlock(xo + soFar + x, this.height - 8, Level.ROCK); setBlock(xo + soFar + x, this.height - 9, Level.ROCK); setBlock(xo + soFar + x, this.height - 10, Level.ROCK); setBlock(xo + soFar + x, this.height - 11, Level.ROCK); setBlock(xo + soFar + x, this.height - 12, Level.ROCK); setBlock(xo + soFar + x, this.height - 13, Level.ROCK); setBlock(xo + soFar + x, this.height - 14, Level.ROCK); setBlock(xo + soFar + x, this.height - 15, Level.ROCK); } if (!stretchEnd) { setBlock(xo + soFar + x, this.height - 7, Level.ROCK); } if (!stretchEnd) { setBlock(xo + soFar + x, this.height - 4, Level.ROCK); } if (addEnemy && shouldAddChallenge(ChallengeType.ENEMY)) { enemyType = shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.RED_TURTLE) : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA; switch (random.nextInt(3)) { case 0: heightMod = 8; break; case 1: heightMod = 5; break; default: heightMod = 2; } setSpriteTemplate(xo + soFar + x, this.height - heightMod, new SpriteTemplate(enemyType, false)); } } 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; } return 0; } private int buildPipeJump(int xo, int maxLength) { int length = 0; int lastHeight = 4; int localHeight; int midFloor; int pipeHeight; int gap = 0; boolean space; boolean deadGaps = shouldAddChallenge(ChallengeType.GAP); int numPipes; for (numPipes = 0; shouldAddChallenge(deadGaps ? ChallengeType.GAP : ChallengeType.JUMP); numPipes++) { } localHeight = random.nextInt(2) + 1; loop: for (int i = 0; i < numPipes; i++) { space = (length + 2) <= maxLength; if (space) { 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 + length, this.height - 1 - y, Level.GROUND); setBlock(xo + length + 1, this.height - 1 - y, Level.GROUND); if (y == localHeight - 1) { setBlock(xo + length, this.height - 1 - y, Level.TUBE_TOP_LEFT); setBlock(xo + length + 1, this.height - 1 - y, Level.TUBE_TOP_RIGHT); if (shouldAddChallenge(ChallengeType.ENEMY)) { setSpriteTemplate(xo + length, this.height - y, new SpriteTemplate( SpriteTemplate.JUMP_FLOWER, false)); } } else if (y > localHeight - pipeHeight) { setBlock(xo + length, this.height - 1 - y, Level.TUBE_SIDE_LEFT); setBlock(xo + length + 1, this.height - 1 - y, Level.TUBE_SIDE_RIGHT); } } length += 2; localHeight = random.nextInt(7) + 4; midFloor = localHeight - (random.nextInt(4) + 1); if (midFloor <= 0) { midFloor = 1; } for (gap = 0; gap < 4 && length + gap <= maxLength && shouldAddChallenge(ChallengeType.JUMP); gap++) { if (!deadGaps) { for (int y = 0; y < midFloor; y++) { setBlock(xo + length, this.height - 1 - y, Level.GROUND); } } length++; } } else { break loop; } } return length; } private int buildPlatformJump(int xo, int maxLength) { int length = 0; int gapLength; for (gapLength = 1; shouldAddChallenge(ChallengeType.JUMP) && gapLength < 4; gapLength++) { } int maxNumPlatforms = (maxLength - 3) / (4 + gapLength); if (maxNumPlatforms == 0) { return 0; } int numPlatforms; for (numPlatforms = 0; numPlatforms < maxNumPlatforms && shouldAddChallenge(ChallengeType.GAP); numPlatforms++) { } if (numPlatforms > 1) { boolean found = false; LevelComponent.MazeLevel nextDir = LevelComponent.MazeLevel.TOP; LevelComponent.PlatformLevel last; LevelComponent.PlatformLevel next; ArrayList jumps = new ArrayList(); int heightMod = 0; int lastHeightMod = 0; int enemyType; int reward; LevelComponent.PlatformLevel hold; jumps.add(LevelComponent.PlatformLevel.BOT); 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++) { if (x > 0) { lastHeightMod = heightMod; } hold = jumps.get(x); switch (hold) { case TOP: heightMod = 12; break; case MID_U: heightMod = 9; break; case MID_D: heightMod = 6; break; default: heightMod = 3; } heightMod += (x > 0 && random.nextBoolean() ? random.nextInt(2) : -1 * random.nextInt(2)); while (x > 0 && heightMod - lastHeightMod >= 5) { heightMod--; } setBlock(xo + length, this.height - heightMod, Level.ROCK); setBlock(xo + length + 1, this.height - heightMod, Level.ROCK); setBlock(xo + length + 2, this.height - heightMod, Level.ROCK); setBlock(xo + length + 3, this.height - heightMod, Level.ROCK); if (shouldAddChallenge(ChallengeType.ENEMY)) { enemyType = random.nextBoolean() ? SpriteTemplate.RED_TURTLE : (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA); setSpriteTemplate( xo + length + 3, this.height - heightMod - 1, new SpriteTemplate( enemyType, (enemyType == SpriteTemplate.RED_TURTLE && shouldAddChallenge(ChallengeType.HARDER_ENEMY)) || enemyType != SpriteTemplate.RED_TURTLE)); } reward = shouldAddReward(); if (reward == COIN_REWARD && hold != LevelComponent.PlatformLevel.TOP) { setBlock(xo + length, this.height - heightMod - 3, Level.COIN); setBlock(xo + length + 1, this.height - heightMod - 3, Level.COIN); setBlock(xo + length + 2, this.height - heightMod - 3, Level.COIN); setBlock(xo + length + 3, this.height - heightMod - 3, Level.COIN); } else if (reward == POWERUP_REWARD && hold != LevelComponent.PlatformLevel.TOP) { setBlock( xo + length + 1, this.height - heightMod - 3, random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + length + 2, this.height - heightMod - 3, random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } length += 4 + gapLength; } } return length; } private int buildStraight(int xo, int maxLength, boolean allowEnemies) { int length = random.nextInt(15) + 2; // if (safe) // length = 10 + random.nextInt(5); if (length > maxLength) length = maxLength; int floor = height - 1 - random.nextInt(4); int reward = shouldAddReward(); // 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 (shouldAddChallenge(ChallengeType.ENEMY) && allowEnemies) { int enemyType = shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) || (reward > 0 && length >= 5) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.CANNON_BALL) : SpriteTemplate.RED_TURTLE) : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA; if (enemyType == SpriteTemplate.CANNON_BALL) { int height = random.nextInt(4) + 1; if (height == 1) { setBlock(xo + (length / 2), floor - 1, Level.CANNON_TOP); } else if (height == 2) { setBlock(xo + (length / 2), floor - 1, Level.CANNON_MIDDLE); setBlock(xo + (length / 2), floor - 2, Level.CANNON_TOP); } else if (height == 3) { setBlock(xo + (length / 2), floor - 1, Level.CANNON_BOTTOM); setBlock(xo + (length / 2), floor - 2, Level.CANNON_MIDDLE); setBlock(xo + (length / 2), floor - 3, Level.CANNON_TOP); } else { setBlock(xo + (length / 2), floor - 1, Level.CANNON_BOTTOM); setBlock(xo + (length / 2), floor - 2, Level.CANNON_BOTTOM); setBlock(xo + (length / 2), floor - 3, Level.CANNON_MIDDLE); setBlock(xo + (length / 2), floor - 4, Level.CANNON_TOP); } } else { setSpriteTemplate(xo + (length / 2), floor - 1, new SpriteTemplate(enemyType, shouldAddChallenge(ChallengeType.HARDER_ENEMY))); } } if (reward > 0) { if (length >= 13) { setBlock(xo + (length - 11), floor - 3, Level.COIN); setBlock(xo + (length - 10), floor - 4, Level.COIN); setBlock(xo + (length - 8), floor - 5, Level.COIN); setBlock(xo + (length - 7), floor - 5, Level.COIN); setBlock(xo + (length - 6), floor - 5, Level.COIN); setBlock(xo + (length - 4), floor - 4, Level.COIN); setBlock(xo + (length - 3), floor - 3, Level.COIN); } else if (length >= 7) { int start = length / 2; boolean coins = (reward == 1); setBlock( xo + (start - 2), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start - 1), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + start, floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start + 2), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start + 1), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } else if (length >= 5) { int start = length / 2; boolean coins = (reward == 1); setBlock( xo + (start - 1), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + start, floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start + 1), floor - 3, coins ? Level.COIN : random.nextDouble() < POWERUP_PROBABILITY ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } } return length; } 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); } }