From 1f1f23a6c3a510d6a78467eb742842704b85a4f6 Mon Sep 17 00:00:00 2001 From: Marshall Date: Sun, 18 Mar 2012 14:51:27 -0400 Subject: [PATCH] - Greatly improved the challenge/reward probability function and usage. Removed some unnecessary fields and resorted the members of PCGLevel to make it easier to read. --- src/dk/itu/mario/level/PCGLevel.java | 972 +++++++++++----------- src/dk/itu/mario/level/PlayerProfile.java | 52 +- 2 files changed, 544 insertions(+), 480 deletions(-) diff --git a/src/dk/itu/mario/level/PCGLevel.java b/src/dk/itu/mario/level/PCGLevel.java index 4e5c0aa..368763d 100644 --- a/src/dk/itu/mario/level/PCGLevel.java +++ b/src/dk/itu/mario/level/PCGLevel.java @@ -2,6 +2,7 @@ package dk.itu.mario.level; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Random; @@ -9,7 +10,6 @@ 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; @@ -17,33 +17,21 @@ import dk.itu.mario.level.matcher.ArchetypeMatcher; import dk.itu.mario.level.matcher.ProfileMatcher; public class PCGLevel extends Level { - public double powerupProbability; - public double blocksProbability; - public double coinsProbability; - public static int COIN_REWARD = 1; - public static int POWERUP_REWARD = 2; - + public static long lastSeed; // 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 DataRecorder dataRecorder; + private HashMap probability = new HashMap(); private Random random; - - public enum ChallengeType { - GAP, ENEMY, HARDER_ENEMY, JUMP - }; + private int type; public PCGLevel(int width, int height) { super(width, height); @@ -71,48 +59,6 @@ public class PCGLevel extends Level { 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."); - - // TODO Refactor into a probability setter function so that we can set - // the challenge probabilities, too. - blocksProbability = profile.getProbability(TYPE.BLOCKS); - coinsProbability = profile.getProbability(TYPE.COINS); - powerupProbability = profile.getProbability(TYPE.POWER_UP); - - create(seed, profile, archetype, grammar); - } - @Override public RandomLevel clone() throws CloneNotSupportedException { @@ -138,156 +84,188 @@ public class PCGLevel extends Level { } - 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."); + 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; } - this.type = archetype.getTypeInt(); - this.difficulty = archetype.getDifficultyLevel(); + boolean[][] b = new boolean[2][2]; - 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); + 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)); + } } } - - 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); + private int buildBowlingAlley(int xo, int maxLength) { + if (maxLength >= 26) { - 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); + int floor = height - 1 - random.nextInt(4); + int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE + : SpriteTemplate.GREEN_TURTLE) + : SpriteTemplate.GOOMPA; + PlayerProfile.ChallengeRewardType reward = shouldAddReward(); + boolean arc = random.nextBoolean(); + int numEnemies = 0; + for (int i = 0; i < 10; i++) { + numEnemies += shouldAddChallenge(PlayerProfile.ChallengeRewardType.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 == PlayerProfile.ChallengeRewardType.COIN + && 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); } } - if ((x >= 2 && x <= 4) || (x >= 8 && x <= 10)) { - setBlock(xo + x, floor - 1, Level.ROCK); + else if (!arc + && reward == PlayerProfile.ChallengeRewardType.COIN + && x >= 10 && x <= 20) { + setBlock(xo + x, floor - 6, Level.COIN); + } - if (x == 3 || x == 4 || x == 8 || x == 9) { - setBlock(xo + x, floor - 2, Level.ROCK); - } + else if (reward == PlayerProfile.ChallengeRewardType.BLOCK + && x >= 13 && x <= 17) { + setBlock( + xo + x, + floor - 6, + (random.nextDouble() <= probability + .get(PlayerProfile.ChallengeRewardType.POWERUP)) ? Level.BLOCK_POWERUP + : Level.BLOCK_COIN); + } - if (x == 4 || x == 8) { - setBlock(xo + x, floor - 3, Level.ROCK); - } + if (x >= 26 - numEnemies) { + setSpriteTemplate(xo + x, floor - 4, new SpriteTemplate( + enemyType, false)); } } - return length; + return 26; } return 0; @@ -310,7 +288,7 @@ public class PCGLevel extends Level { setBlock( xo + x, floor - 3, - shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? Level.CANNON_TOP + shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? Level.CANNON_TOP : Level.CANNON_MIDDLE); setBlock(xo + x, floor - 4, Level.CANNON_TOP); } @@ -422,104 +400,16 @@ public class PCGLevel extends Level { 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() <= powerupProbability) ? 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 + int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.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(); + boolean flying = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) + && shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) + && shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY); + PlayerProfile.ChallengeRewardType reward = shouldAddReward(); for (int x = 0; x < 18; x++) { if (x > 5) { @@ -539,15 +429,18 @@ public class PCGLevel extends Level { enemyType, flying)); } - if (reward == 1 && x >= 7 && x <= 16) { + if (reward == PlayerProfile.ChallengeRewardType.COIN && x >= 7 + && x <= 16) { setBlock(xo + x, floor - 7, Level.COIN); } - else if (reward == 2 && x >= 10 && x <= 13) { + else if (reward == PlayerProfile.ChallengeRewardType.BLOCK + && x >= 10 && x <= 13) { setBlock( xo + x, floor - 7, - random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } } @@ -557,148 +450,6 @@ public class PCGLevel extends Level { 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 < coinsProbability) { - return 1; - } else if (guess < coinsProbability + blocksProbability) { - return 2; - } else { - return 0; - } - - } - private int buildMaze(int xo, int maxLength) { if (maxLength >= 19) { @@ -816,9 +567,10 @@ public class PCGLevel extends Level { setBlock(xo + soFar + x, this.height - 4, Level.ROCK); } - if (addEnemy && shouldAddChallenge(ChallengeType.ENEMY)) { + if (addEnemy + && shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) { - enemyType = shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE + enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.RED_TURTLE) : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA; @@ -860,11 +612,11 @@ public class PCGLevel extends Level { int pipeHeight; int gap = 0; boolean space; - boolean deadGaps = shouldAddChallenge(ChallengeType.GAP); + boolean deadGaps = shouldAddChallenge(PlayerProfile.ChallengeRewardType.GAP); int numPipes; - for (numPipes = 0; shouldAddChallenge(deadGaps ? ChallengeType.GAP - : ChallengeType.JUMP); numPipes++) { + for (numPipes = 0; shouldAddChallenge(deadGaps ? PlayerProfile.ChallengeRewardType.GAP + : PlayerProfile.ChallengeRewardType.JUMP); numPipes++) { } localHeight = random.nextInt(2) + 1; @@ -891,7 +643,7 @@ public class PCGLevel extends Level { setBlock(xo + length + 1, this.height - 1 - y, Level.TUBE_TOP_RIGHT); - if (shouldAddChallenge(ChallengeType.ENEMY)) { + if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) { setSpriteTemplate(xo + length, this.height - y, new SpriteTemplate( SpriteTemplate.JUMP_FLOWER, false)); @@ -913,8 +665,9 @@ public class PCGLevel extends Level { midFloor = 1; } - for (gap = 0; gap < 4 && length + gap <= maxLength - && shouldAddChallenge(ChallengeType.JUMP); gap++) { + for (gap = 0; gap < 4 + && length + gap <= maxLength + && shouldAddChallenge(PlayerProfile.ChallengeRewardType.JUMP); gap++) { if (!deadGaps) { for (int y = 0; y < midFloor; y++) { @@ -938,7 +691,7 @@ public class PCGLevel extends Level { int length = 0; int gapLength; - for (gapLength = 1; shouldAddChallenge(ChallengeType.JUMP) + for (gapLength = 1; shouldAddChallenge(PlayerProfile.ChallengeRewardType.JUMP) && gapLength < 4; gapLength++) { } @@ -950,7 +703,7 @@ public class PCGLevel extends Level { int numPlatforms; for (numPlatforms = 0; numPlatforms < maxNumPlatforms - && shouldAddChallenge(ChallengeType.GAP); numPlatforms++) { + && shouldAddChallenge(PlayerProfile.ChallengeRewardType.GAP); numPlatforms++) { } if (numPlatforms > 1) { @@ -962,7 +715,7 @@ public class PCGLevel extends Level { int heightMod = 0; int lastHeightMod = 0; int enemyType; - int reward; + PlayerProfile.ChallengeRewardType reward; LevelComponent.PlatformLevel hold; jumps.add(LevelComponent.PlatformLevel.BOT); @@ -1053,10 +806,10 @@ public class PCGLevel extends Level { setBlock(xo + length + 2, this.height - heightMod, Level.ROCK); setBlock(xo + length + 3, this.height - heightMod, Level.ROCK); - if (shouldAddChallenge(ChallengeType.ENEMY)) { + if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) { enemyType = random.nextBoolean() ? SpriteTemplate.RED_TURTLE - : (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? (shouldAddChallenge(ChallengeType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE + : (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.GREEN_TURTLE) : SpriteTemplate.GOOMPA); @@ -1065,12 +818,12 @@ public class PCGLevel extends Level { this.height - heightMod - 1, new SpriteTemplate( enemyType, - (enemyType == SpriteTemplate.RED_TURTLE && shouldAddChallenge(ChallengeType.HARDER_ENEMY)) + (enemyType == SpriteTemplate.RED_TURTLE && shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)) || enemyType != SpriteTemplate.RED_TURTLE)); } reward = shouldAddReward(); - if (reward == COIN_REWARD + if (reward == PlayerProfile.ChallengeRewardType.COIN && hold != LevelComponent.PlatformLevel.TOP) { setBlock(xo + length, this.height - heightMod - 3, Level.COIN); @@ -1082,17 +835,19 @@ public class PCGLevel extends Level { Level.COIN); } - else if (reward == POWERUP_REWARD + else if (reward == PlayerProfile.ChallengeRewardType.BLOCK && hold != LevelComponent.PlatformLevel.TOP) { setBlock( xo + length + 1, this.height - heightMod - 3, - random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + length + 2, this.height - heightMod - 3, - random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } @@ -1103,6 +858,37 @@ public class PCGLevel extends Level { return length; } + 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 buildStraight(int xo, int maxLength, boolean allowEnemies) { int length = random.nextInt(15) + 2; @@ -1113,7 +899,7 @@ public class PCGLevel extends Level { length = maxLength; int floor = height - 1 - random.nextInt(4); - int reward = shouldAddReward(); + PlayerProfile.ChallengeRewardType reward = shouldAddReward(); // runs from the specified x position to the length of the segment for (int x = xo; x < xo + length; x++) { @@ -1124,9 +910,10 @@ public class PCGLevel extends Level { } } - 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 + if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY) + && allowEnemies) { + int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) + || (reward != null && length >= 5) ? SpriteTemplate.ARMORED_TURTLE : SpriteTemplate.CANNON_BALL) : SpriteTemplate.RED_TURTLE) : SpriteTemplate.GREEN_TURTLE) @@ -1159,13 +946,16 @@ public class PCGLevel extends Level { } else { - setSpriteTemplate(xo + (length / 2), floor - 1, - new SpriteTemplate(enemyType, - shouldAddChallenge(ChallengeType.HARDER_ENEMY))); + setSpriteTemplate( + xo + (length / 2), + floor - 1, + new SpriteTemplate( + enemyType, + shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY))); } } - if (reward > 0) { + if (reward != null) { if (length >= 13) { setBlock(xo + (length - 11), floor - 3, Level.COIN); setBlock(xo + (length - 10), floor - 4, Level.COIN); @@ -1178,63 +968,71 @@ public class PCGLevel extends Level { else if (length >= 7) { int start = length / 2; - boolean coins = (reward == 1); + boolean coins = (reward == PlayerProfile.ChallengeRewardType.COIN); setBlock( xo + (start - 2), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start - 1), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + start, floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start + 1), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); if (length % 2 != 0) { setBlock( xo + (start + 2), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } } else if (length >= 5) { int start = length / 2; - boolean coins = (reward == 1); + boolean coins = (reward == PlayerProfile.ChallengeRewardType.COIN); setBlock( xo + (start - 1), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + start, floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); setBlock( xo + (start + 1), floor - 3, coins ? Level.COIN - : random.nextDouble() < powerupProbability ? Level.BLOCK_POWERUP + : random.nextDouble() < probability + .get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP : Level.BLOCK_COIN); } } @@ -1242,6 +1040,161 @@ public class PCGLevel extends Level { return length; } + private void create(long seed, PlayerProfile profile, + LevelArchetype archetype, LevelGrammar grammar) { + + System.out + .println("PlayerProfile.getProbability(COINS): " + + profile + .getProbability(PlayerProfile.ChallengeRewardType.COIN)); + + if (dataRecorder == DataRecorder.BLANK_RECORD) { + System.out + .println("DataRecorder record is BLANK - using GamePlay metrics only."); + } + + this.type = archetype.getTypeInt(); + + 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 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 fixWalls() { boolean[][] blockMap = new boolean[width + 1][height + 1]; @@ -1261,4 +1214,91 @@ public class PCGLevel extends Level { blockify(this, blockMap, width + 1, height + 1); } + 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."); + + initProbabilities(profile); + + create(seed, profile, archetype, grammar); + } + + private void initProbabilities(PlayerProfile profile) { + probability + .put(PlayerProfile.ChallengeRewardType.BLOCK, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.BLOCK))); + probability + .put(PlayerProfile.ChallengeRewardType.COIN, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.COIN))); + probability + .put(PlayerProfile.ChallengeRewardType.POWERUP, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.POWERUP))); + + probability + .put(PlayerProfile.ChallengeRewardType.ENEMY, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.ENEMY))); + probability.put(PlayerProfile.ChallengeRewardType.GAP, new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.GAP))); + probability + .put(PlayerProfile.ChallengeRewardType.HARDER_ENEMY, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.HARDER_ENEMY))); + probability + .put(PlayerProfile.ChallengeRewardType.JUMP, + new Double( + profile.getProbability(PlayerProfile.ChallengeRewardType.JUMP))); + } + + private boolean shouldAddChallenge(PlayerProfile.ChallengeRewardType crt) { + return random.nextDouble() <= probability.get(crt); + } + + private PlayerProfile.ChallengeRewardType shouldAddReward() { + double guess = random.nextDouble(); + + if (guess < probability.get(PlayerProfile.ChallengeRewardType.COIN)) { + return PlayerProfile.ChallengeRewardType.COIN; + } else if (guess < probability + .get(PlayerProfile.ChallengeRewardType.COIN) + + probability.get(PlayerProfile.ChallengeRewardType.BLOCK)) { + return PlayerProfile.ChallengeRewardType.BLOCK; + } else { + return null; + } + + } + } \ No newline at end of file diff --git a/src/dk/itu/mario/level/PlayerProfile.java b/src/dk/itu/mario/level/PlayerProfile.java index 8cb2a3a..5854ef7 100644 --- a/src/dk/itu/mario/level/PlayerProfile.java +++ b/src/dk/itu/mario/level/PlayerProfile.java @@ -13,6 +13,16 @@ public class PlayerProfile { BUMPER, COLLECTOR, RUNNER, SHOOTER, JUMPER } + /* + * Certain probabilities are boosted by this constant based on the player's + * type. + */ + public static final double TYPE_MULTIPLIER = 1.25; + + public enum ChallengeRewardType { + GAP, ENEMY, HARDER_ENEMY, JUMP, COIN, BLOCK, POWERUP + }; + // Dreyfus model of skill acquisition: public enum SKILL_LEVEL { NOVICE, BEGINNER, COMPETENT, PROFICIENT, EXPERT @@ -64,20 +74,34 @@ public class PlayerProfile { } } - public double getProbability(LevelComponent.TYPE type) { - if (!isEnabled(type)) { - return 0.0; - } - - // SKILLs: { BUMP, COLLECT, JUMP, RUN, SHOOT, STOMP } - // TYPEs: { BUMPER, COLLECTOR, RUNNER, SHOOTER, JUMPER} - - switch (type) { - case BLOCKS: - return (.5) * (skillVector.get(SKILL.BUMP) / 100.0); - case COINS: - return (.5) * (skillVector.get(SKILL.COLLECT) / 100.0); - case POWER_UP: + public double getProbability(ChallengeRewardType crt) { + // if (!isEnabled(type)) { + // return 0.0; + // } + + switch (crt) { + case GAP: + return Math.min(.5, (.5) * (skillVector.get(SKILL.JUMP) / 100.0) + * ((type == TYPE.JUMPER) ? TYPE_MULTIPLIER : 1)); + case ENEMY: + double skillSet = ((100 - skillVector.get(SKILL.RUN)) / 100.0) + * ((type == TYPE.RUNNER) ? -1 * TYPE_MULTIPLIER : 1); + skillSet += (skillVector.get(SKILL.STOMP) / 100.0); + skillSet /= 2; + return Math.min(.5, (.5) * skillSet); + case HARDER_ENEMY: + return Math.min(.5, (.5) * (skillVector.get(SKILL.SHOOT) / 100.0) + * ((type == TYPE.SHOOTER) ? TYPE_MULTIPLIER : 1)); + case JUMP: + return Math.min(.5, (.5) * (skillVector.get(SKILL.JUMP) / 100.0) + * ((type == TYPE.JUMPER) ? TYPE_MULTIPLIER : 1)); + case BLOCK: + return Math.min(.5, (.5) * (skillVector.get(SKILL.BUMP) / 100.0) + * ((type == TYPE.BUMPER) ? TYPE_MULTIPLIER : 1)); + case COIN: + return Math.min(.5, (.5) * (skillVector.get(SKILL.COLLECT) / 100.0) + * ((type == TYPE.COLLECTOR) ? TYPE_MULTIPLIER : 1)); + case POWERUP: double skillMod = 0; switch (skillLevel) { case BEGINNER: