All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pacman.game.Game Maven / Gradle / Ivy

There is a newer version: 2.0.1.0
Show newest version
package pacman.game;

import pacman.game.Constants.*;
import pacman.game.comms.Messenger;
import pacman.game.info.GameInfo;
import pacman.game.internal.*;

import java.util.BitSet;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import static pacman.game.Constants.*;

/**
 * The implementation of Ms Pac-Man. This class contains the game engine and all methods required to
 * query the state of the game. First, the mazes are loaded once only as they are immutable. The game
 * then proceeds to initialise all variables using default values. The game class also provides numerous
 * methods to extract the game state as a string (used for replays and for communication via pipes during
 * the competition) and to create copies. Care has been taken to implement the game efficiently to ensure
 * that copies can be created quickly.
 * 

* The game has a central update method called advanceGame which takes a move for Ms Pac-Man and up to * 4 moves for the ghosts. It then updates the positions of all characters, check whether pills or power * pills have been eaten and updates the game state accordingly. *

* All other methods are to access the gamestate and to compute numerous aspects such as directions to taken * given a target or a shortest path from a to b. All shortest path distances from any node to any other node * are pre-computed and loaded from file. This makes these methods more efficient. Note about the ghosts: ghosts * are not allowed to reverse. Hence it is not possible to simply look up the shortest path distance. Instead, * one can approximate this greedily or use A* to compute it properly. The former is somewhat quicker and has * a low error rate. The latter takes a bit longer but is absolutely accurate. We use the pre-computed shortest * path distances as admissable heuristic so it is very efficient. *

* Cloning *

* The game can be cloned to produce a CO Forward Model *

* The game can be cloned and given PO constraints - it cannot be forwarded from here unless * it has been provided with a GameInfo. Exact details tbc */ public final class Game { public static PathsCache[] caches = new PathsCache[NUM_MAZES]; //mazes are only loaded once since they don't change over time private static Maze[] mazes = new Maze[NUM_MAZES]; static { for (int i = 0; i < mazes.length; i++) { mazes[i] = new Maze(i); } } static { for (int i = 0; i < mazes.length; i++) { caches[i] = new PathsCache(i); } } // TODO Fix these into constructor so they can be final and not fiddleable public final POType PO_TYPE; public final int SIGHT_LIMIT; private boolean ghostsPresent = true; private boolean pillsPresent = true; private boolean powerPillsPresent = true; //pills stored as bitsets for efficient copying private BitSet pills, powerPills; //all the game's variables private int mazeIndex, levelCount, currentLevelTime, totalTime, score, ghostEatMultiplier, timeOfLastGlobalReversal; private boolean gameOver, pacmanWasEaten, pillWasEaten, powerPillWasEaten; private EnumMap ghostsEaten; //the data relating to pacman and the ghosts are stored in respective data structures for clarity private PacMan pacman; private EnumMap ghosts; // PO State private boolean po; private boolean beenBlanked; // either ID of a ghost or higher for pacman private int agent = 0; private Maze currentMaze; private Random rnd; private long seed; // Messenger - null if not available private Messenger messenger; /** * Instantiates a new game. The seed is used to initialise the pseudo-random * number generator. This way, a game may be replicated exactly by using identical * seeds. Note: in the competition, the games received from the game server are * using different seeds. Otherwise global reversal events would be predictable. * * @param seed The seed for the pseudo-random number generator */ public Game(long seed) { this(seed, null); } /** * Initiates a new game specifying the maze to start with. * * @param seed Seed used for the pseudo-random numbers * @param initialMaze The maze to start the game with */ public Game(long seed, int initialMaze) { this(seed, initialMaze, null); } public Game(long seed, Messenger messenger) { this(seed, 0, messenger); } public Game(long seed, int initialMaze, Messenger messenger) { this(seed, initialMaze, messenger, POType.LOS, 100); } public Game(long seed, int initialMaze, Messenger messenger, POType poType, int sightLimit) { this.seed = seed; rnd = new Random(seed); this.messenger = messenger; _init(initialMaze); this.PO_TYPE = poType; this.SIGHT_LIMIT = sightLimit; } ///////////////////////////////////////////////////////////////////////////// /////////////////// Constructors and initialisers ///////////////////////// ///////////////////////////////////////////////////////////////////////////// /** * Empty constructor used by the copy method. */ private Game(POType poType, int sightLimit) { this.PO_TYPE = poType; this.SIGHT_LIMIT = sightLimit; } private int getNodeIndexOfOwner() { if (agent >= NUM_GHOSTS) { return pacman.currentNodeIndex; } else { return ghosts.get(GHOST.values()[agent]).currentNodeIndex; } } public boolean isNodeObservable(int nodeIndex) { if (!po) { return true; } if (nodeIndex == -1) { return false; } Node currentNode = (mazes[mazeIndex]).graph[getNodeIndexOfOwner()]; Node check = (mazes[mazeIndex]).graph[nodeIndex]; switch (PO_TYPE) { case LOS: if (currentNode.x == check.x || currentNode.y == check.y) { // The nodes are in a line // If shortest path to the nodes is equal to direct distance, no obstacles return straightRouteBlocked(currentNode, check); } return false; case RADIUS: double manhattan = getManhattanDistance(currentNode.nodeIndex, check.nodeIndex); return (manhattan <= SIGHT_LIMIT); case FF_LOS: if (currentNode.x == check.x || currentNode.y == check.y) { // Get direction currently going in MOVE previousMove = (agent >= NUM_GHOSTS) ? pacman.lastMoveMade : ghosts.get(GHOST.values()[agent]).lastMoveMade; switch (previousMove) { case UP: if (currentNode.x == check.x && currentNode.y >= check.y) { return straightRouteBlocked(currentNode, check); } break; case DOWN: if (currentNode.x == check.x && currentNode.y <= check.y) { return straightRouteBlocked(currentNode, check); } break; case LEFT: if (currentNode.y == check.y && currentNode.x >= check.x) { return straightRouteBlocked(currentNode, check); } break; case RIGHT: if (currentNode.y == check.y && currentNode.x <= check.x) { return straightRouteBlocked(currentNode, check); } break; } } break; } return false; } private boolean straightRouteBlocked(Node startNode, Node endNode) { double manhattan = getManhattanDistance(startNode.nodeIndex, endNode.nodeIndex); if (manhattan <= SIGHT_LIMIT) { double shortestPath = getShortestPathDistance(startNode.nodeIndex, endNode.nodeIndex); return (manhattan == shortestPath); } return false; } /** * _init. * * @param initialMaze the initial maze */ private void _init(int initialMaze) { mazeIndex = initialMaze; score = currentLevelTime = levelCount = totalTime = 0; ghostEatMultiplier = 1; gameOver = false; timeOfLastGlobalReversal = -1; pacmanWasEaten = false; pillWasEaten = false; powerPillWasEaten = false; ghostsEaten = new EnumMap(GHOST.class); for (GHOST ghost : GHOST.values()) { ghostsEaten.put(ghost, false); } _setPills(currentMaze = mazes[mazeIndex]); _initGhosts(); pacman = new PacMan(currentMaze.initialPacManNodeIndex, MOVE.LEFT, NUM_LIVES, false); } /** * _new level reset. */ private void _newLevelReset() { mazeIndex = ++mazeIndex % NUM_MAZES; levelCount++; currentMaze = mazes[mazeIndex]; currentLevelTime = 0; ghostEatMultiplier = 1; _setPills(currentMaze); _levelReset(); } /** * _level reset. */ private void _levelReset() { ghostEatMultiplier = 1; _initGhosts(); pacman.currentNodeIndex = currentMaze.initialPacManNodeIndex; pacman.lastMoveMade = MOVE.LEFT; } /** * _set pills. * * @param maze the maze */ private void _setPills(Maze maze) { if (pillsPresent) { pills = new BitSet(currentMaze.pillIndices.length); pills.set(0, currentMaze.pillIndices.length); } if (powerPillsPresent) { powerPills = new BitSet(currentMaze.powerPillIndices.length); powerPills.set(0, currentMaze.powerPillIndices.length); } } /** * _init ghosts. */ private void _initGhosts() { ghosts = new EnumMap(GHOST.class); for (GHOST ghostType : GHOST.values()) { ghosts.put(ghostType, new Ghost(ghostType, currentMaze.lairNodeIndex, 0, (int) (ghostType.initialLairTime * (Math.pow(LAIR_REDUCTION, levelCount % LEVEL_RESET_REDUCTION))), MOVE.NEUTRAL)); } } /** * Gets the game state as a string: all variables are written to a string in a pre-determined * order. The string may later be used to recreate a game state using the setGameState() method. *

* Variables not included: enableGlobalReversals * * @return The game state as a string */ public String getGameState() { if (po) { return ""; } StringBuilder sb = new StringBuilder(); sb.append(mazeIndex + "," + totalTime + "," + score + "," + currentLevelTime + "," + levelCount + "," + pacman.currentNodeIndex + "," + pacman.lastMoveMade + "," + pacman.numberOfLivesRemaining + "," + pacman.hasReceivedExtraLife + ","); for (Ghost ghost : ghosts.values()) { sb.append(ghost.currentNodeIndex + "," + ghost.edibleTime + "," + ghost.lairTime + "," + ghost.lastMoveMade + ","); } for (int i = 0; i < currentMaze.pillIndices.length; i++) { if (pills.get(i)) { sb.append("1"); } else { sb.append("0"); } } sb.append(","); for (int i = 0; i < currentMaze.powerPillIndices.length; i++) { if (powerPills.get(i)) { sb.append("1"); } else { sb.append("0"); } } sb.append(","); sb.append(timeOfLastGlobalReversal); sb.append(","); sb.append(pacmanWasEaten); sb.append(","); for (GHOST ghost : GHOST.values()) { sb.append(ghostsEaten.get(ghost)); sb.append(","); } sb.append(pillWasEaten); sb.append(","); sb.append(powerPillWasEaten); return sb.toString(); } /** * Sets the game state from a string: the inverse of getGameState(). It reconstructs * all the game's variables from the string. * * @param gameState The game state represented as a string */ public void setGameState(String gameState) { String[] values = gameState.split(","); int index = 0; mazeIndex = Integer.parseInt(values[index++]); totalTime = Integer.parseInt(values[index++]); score = Integer.parseInt(values[index++]); currentLevelTime = Integer.parseInt(values[index++]); levelCount = Integer.parseInt(values[index++]); pacman = new PacMan(Integer.parseInt(values[index++]), MOVE.valueOf(values[index++]), Integer.parseInt(values[index++]), Boolean.parseBoolean(values[index++])); ghosts = new EnumMap(GHOST.class); for (GHOST ghostType : GHOST.values()) { ghosts.put(ghostType, new Ghost(ghostType, Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), MOVE.valueOf(values[index++]))); } _setPills(currentMaze = mazes[mazeIndex]); for (int i = 0; i < values[index].length(); i++) { if (values[index].charAt(i) == '1') { pills.set(i); } else { pills.clear(i); } } index++; for (int i = 0; i < values[index].length(); i++) { if (values[index].charAt(i) == '1') { powerPills.set(i); } else { powerPills.clear(i); } } timeOfLastGlobalReversal = Integer.parseInt(values[++index]); pacmanWasEaten = Boolean.parseBoolean(values[++index]); ghostsEaten = new EnumMap(GHOST.class); for (GHOST ghost : GHOST.values()) { ghostsEaten.put(ghost, Boolean.parseBoolean(values[++index])); } pillWasEaten = Boolean.parseBoolean(values[++index]); powerPillWasEaten = Boolean.parseBoolean(values[++index]); } /** * Returns an exact copy of the game. This may be used for forward searches * such as minimax. The copying is relatively efficient. *

* Copy will respect previous PO constraints *

* Need to dissallow the forwarding of games that have PO enabled. * Then allow a copy of the game to be made with some information provided. * * @param copyMessenger should the messenger be deep copied or not * @return the game */ public Game copy(boolean copyMessenger) { Game copy = new Game(this.PO_TYPE, this.SIGHT_LIMIT); copy.seed = seed; copy.rnd = new Random(); copy.currentMaze = currentMaze; copy.pills = (BitSet) pills.clone(); copy.powerPills = (BitSet) powerPills.clone(); copy.mazeIndex = mazeIndex; copy.levelCount = levelCount; copy.currentLevelTime = currentLevelTime; copy.totalTime = totalTime; copy.score = score; copy.ghostEatMultiplier = ghostEatMultiplier; copy.gameOver = gameOver; copy.timeOfLastGlobalReversal = timeOfLastGlobalReversal; copy.pacmanWasEaten = pacmanWasEaten; copy.pillWasEaten = pillWasEaten; copy.powerPillWasEaten = powerPillWasEaten; copy.pacman = pacman.copy(); copy.ghostsPresent = ghostsPresent; copy.pillsPresent = pillsPresent; copy.powerPillsPresent = powerPillsPresent; copy.ghostsEaten = new EnumMap(GHOST.class); copy.ghosts = new EnumMap(GHOST.class); for (GHOST ghostType : GHOST.values()) { copy.ghosts.put(ghostType, ghosts.get(ghostType).copy()); copy.ghostsEaten.put(ghostType, ghostsEaten.get(ghostType)); } copy.po = this.po; copy.agent = this.agent; if (hasMessaging()) { copy.messenger = (copyMessenger) ? messenger.copy() : this.messenger; } return copy; } public Game copy() { return copy(false); } public Game copy(GHOST ghost) { return copy(ghost, false); } public Game copy(GHOST ghost, boolean copyMessenger) { Game game = copy(copyMessenger); game.po = true; game.agent = ghost.ordinal(); return game; } public Game copy(PacMan pacman) { Game game = copy(); game.po = true; game.agent = GHOST.values().length + 1; return game; } public Game copy(int agent) { Game game = copy(); if (agent == -1) { return game; } game.po = true; game.agent = agent; return game; } private boolean canBeForwarded() { return !po || beenBlanked; } ///////////////////////////////////////////////////////////////////////////// /////////////////////////// Game-engine ////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// /** * Central method that advances the game state using the moves supplied by * the controllers. It first updates Ms Pac-Man, then the ghosts and then * the general game logic. * * @param pacManMove The move supplied by the Ms Pac-Man controller * @param ghostMoves The moves supplied by the ghosts controller */ public void advanceGame(MOVE pacManMove, EnumMap ghostMoves) { if (!canBeForwarded()) { return; } updatePacMan(pacManMove); updateGhosts(ghostMoves); updateGame(); } public void advanceGameWithoutReverse(MOVE pacManMove, EnumMap ghostMoves) { if (!canBeForwarded()) { return; } updatePacMan(pacManMove); updateGhostsWithoutReverse(ghostMoves); updateGame(); } public void advanceGameWithForcedReverse(MOVE pacManMove, EnumMap ghostMoves) { if (!canBeForwarded()) { return; } updatePacMan(pacManMove); updateGhostsWithForcedReverse(ghostMoves); updateGame(); } public void advanceGameWithPowerPillReverseOnly(MOVE pacManMove, EnumMap ghostMoves) { if (!canBeForwarded()) { return; } updatePacMan(pacManMove); if (powerPillWasEaten) { updateGhostsWithForcedReverse(ghostMoves); } else { updateGhostsWithoutReverse(ghostMoves); } updateGame(); } /** * Updates the state of Ms Pac-Man given the move returned by the controller. * * @param pacManMove The move supplied by the Ms Pac-Man controller */ public void updatePacMan(MOVE pacManMove) { if (!canBeForwarded()) { return; } _updatePacMan(pacManMove); //move pac-man _eatPill(); //eat a pill _eatPowerPill(); //eat a power pill } /** * Updates the states of the ghosts given the moves returned by the controller. * * @param ghostMoves The moves supplied by the ghosts controller */ public void updateGhosts(EnumMap ghostMoves) { if (!canBeForwarded()) { return; } if (!ghostsPresent) { return; } ghostMoves = _completeGhostMoves(ghostMoves); if (!_reverseGhosts(ghostMoves, false)) { _updateGhosts(ghostMoves); } } public void updateGhostsWithoutReverse(EnumMap ghostMoves) { if (!canBeForwarded()) { return; } if (!ghostsPresent) { return; } ghostMoves = _completeGhostMoves(ghostMoves); _updateGhosts(ghostMoves); } public void updateGhostsWithForcedReverse(EnumMap ghostMoves) { if (!canBeForwarded()) { return; } if (!ghostsPresent) { return; } ghostMoves = _completeGhostMoves(ghostMoves); _reverseGhosts(ghostMoves, true); } /** * Updates the game once the individual characters have been updated: check if anyone * can eat anyone else. Then update the lair times and check if Ms Pac-Man should be * awarded the extra live. Then update the time and see if the level or game is over. */ public void updateGame() { if (!canBeForwarded()) { return; } _feast(); //ghosts eat pac-man or vice versa _updateLairTimes(); _updatePacManExtraLife(); totalTime++; currentLevelTime++; _checkLevelState(); //check if level/game is over if (messenger != null) { messenger.update(); } } /** * This method is for specific purposes such as searching a tree in a specific manner. It has to be used cautiously as it might * create an unstable game state and may cause the game to crash. * * @param feast Whether or not to enable feasting * @param updateLairTimes Whether or not to update the lair times * @param updateExtraLife Whether or not to update the extra life * @param updateTotalTime Whether or not to update the total time * @param updateLevelTime Whether or not to update the level time */ public void updateGame(boolean feast, boolean updateLairTimes, boolean updateExtraLife, boolean updateTotalTime, boolean updateLevelTime) { if (!canBeForwarded()) { return; } if (feast) { _feast(); //ghosts eat pac-man or vice versa } if (updateLairTimes) { _updateLairTimes(); } if (updateExtraLife) { _updatePacManExtraLife(); } if (updateTotalTime) { totalTime++; } if (updateLevelTime) { currentLevelTime++; } _checkLevelState(); //check if level/game is over if (messenger != null) { messenger.update(); } } /** * _update lair times. */ private void _updateLairTimes() { if (!ghostsPresent) { return; } for (Ghost ghost : ghosts.values()) { if (ghost.lairTime > 0) { if (--ghost.lairTime == 0) { ghost.currentNodeIndex = currentMaze.initialGhostNodeIndex; } } } } /** * _update pac man extra life. */ private void _updatePacManExtraLife() { if (!pacman.hasReceivedExtraLife && score >= EXTRA_LIFE_SCORE) //award 1 extra life at 10000 points { pacman.hasReceivedExtraLife = true; pacman.numberOfLivesRemaining++; } } /** * _update pac man. * * @param move the move */ private void _updatePacMan(MOVE move) { pacman.lastMoveMade = _correctPacManDir(move); pacman.currentNodeIndex = pacman.lastMoveMade == MOVE.NEUTRAL ? pacman.currentNodeIndex : currentMaze.graph[pacman.currentNodeIndex].neighbourhood.get(pacman.lastMoveMade); } /** * _correct pac man dir. * * @param direction the direction * @return the mOVE */ private MOVE _correctPacManDir(MOVE direction) { Node node = currentMaze.graph[pacman.currentNodeIndex]; //direction is correct, return it if (node.neighbourhood.containsKey(direction)) { return direction; } else { //try to use previous direction (i.e., continue in the same direction) if (node.neighbourhood.containsKey(pacman.lastMoveMade)) { return pacman.lastMoveMade; //else stay put } else { return MOVE.NEUTRAL; } } } /** * _update ghosts. * * @param moves the moves */ private void _updateGhosts(EnumMap moves) { for (Entry entry : moves.entrySet()) { Ghost ghost = ghosts.get(entry.getKey()); if (ghost.lairTime == 0) { if (ghost.edibleTime == 0 || ghost.edibleTime % GHOST_SPEED_REDUCTION != 0) { ghost.lastMoveMade = _checkGhostDir(ghost, entry.getValue()); moves.put(entry.getKey(), ghost.lastMoveMade); ghost.currentNodeIndex = currentMaze.graph[ghost.currentNodeIndex].neighbourhood.get(ghost.lastMoveMade); } } } } private EnumMap _completeGhostMoves(EnumMap moves) { if (moves == null) { moves = new EnumMap(GHOST.class); for (Map.Entry entry : ghosts.entrySet()) { moves.put(entry.getKey(), entry.getValue().lastMoveMade); } } if (moves.size() < NUM_GHOSTS) { for (GHOST ghostType : ghosts.keySet()) { if (!moves.containsKey(ghostType)) { moves.put(ghostType, MOVE.NEUTRAL); } } } return moves; } /** * _check ghost dir. * * @param ghost the ghost * @param direction the direction * @return the mOVE */ private MOVE _checkGhostDir(Ghost ghost, MOVE direction) { //Gets the neighbours of the node with the node that would correspond to reverse removed Node node = currentMaze.graph[ghost.currentNodeIndex]; //The direction is possible and not opposite to the previous direction of that ghost if (node.neighbourhood.containsKey(direction) && direction != ghost.lastMoveMade.opposite()) { return direction; } else { if (node.neighbourhood.containsKey(ghost.lastMoveMade)) { return ghost.lastMoveMade; } else { MOVE[] moves = node.allPossibleMoves.get(ghost.lastMoveMade); return moves[rnd.nextInt(moves.length)]; } } } /** * _eat pill. */ private void _eatPill() { pillWasEaten = false; int pillIndex = currentMaze.graph[pacman.currentNodeIndex].pillIndex; if (pillIndex >= 0 && pills.get(pillIndex)) { score += PILL; pills.clear(pillIndex); pillWasEaten = true; } } /** * _eat power pill. */ private void _eatPowerPill() { powerPillWasEaten = false; int powerPillIndex = currentMaze.graph[pacman.currentNodeIndex].powerPillIndex; if (powerPillIndex >= 0 && powerPills.get(powerPillIndex)) { score += POWER_PILL; ghostEatMultiplier = 1; powerPills.clear(powerPillIndex); int newEdibleTime = (int) (EDIBLE_TIME * (Math.pow(EDIBLE_TIME_REDUCTION, levelCount % LEVEL_RESET_REDUCTION))); for (Ghost ghost : ghosts.values()) { if (ghost.lairTime == 0) { ghost.edibleTime = newEdibleTime; } else { ghost.edibleTime = 0; } } powerPillWasEaten = true; } } private boolean _reverseGhosts(EnumMap moves, boolean force) { boolean reversed = false; boolean globalReverse = false; if (rnd.nextDouble() < GHOST_REVERSAL) { globalReverse = true; } for (Entry entry : moves.entrySet()) { Ghost ghost = ghosts.get(entry.getKey()); if (currentLevelTime > 1 && ghost.lairTime == 0 && ghost.lastMoveMade != MOVE.NEUTRAL) { if (force || (powerPillWasEaten || globalReverse)) { ghost.lastMoveMade = ghost.lastMoveMade.opposite(); ghost.currentNodeIndex = currentMaze.graph[ghost.currentNodeIndex].neighbourhood.get(ghost.lastMoveMade); reversed = true; timeOfLastGlobalReversal = totalTime; } } } return reversed; } /** * _feast. */ private void _feast() { pacmanWasEaten = false; for (GHOST ghost : ghosts.keySet()) { ghostsEaten.put(ghost, false); } for (Ghost ghost : ghosts.values()) { int distance = getShortestPathDistance(pacman.currentNodeIndex, ghost.currentNodeIndex); if (distance <= EAT_DISTANCE && distance != -1) { if (ghost.edibleTime > 0) //pac-man eats ghost { score += GHOST_EAT_SCORE * ghostEatMultiplier; ghostEatMultiplier *= 2; ghost.edibleTime = 0; ghost.lairTime = (int) (COMMON_LAIR_TIME * (Math.pow(LAIR_REDUCTION, levelCount % LEVEL_RESET_REDUCTION))); ghost.currentNodeIndex = currentMaze.lairNodeIndex; ghost.lastMoveMade = MOVE.NEUTRAL; ghostsEaten.put(ghost.type, true); } else //ghost eats pac-man { pacman.numberOfLivesRemaining--; pacmanWasEaten = true; if (pacman.numberOfLivesRemaining <= 0) { gameOver = true; } else { _levelReset(); } return; } } } for (Ghost ghost : ghosts.values()) { if (ghost.edibleTime > 0) { ghost.edibleTime--; } } } /** * _check level state. */ private void _checkLevelState() { //put a cap on the total time a game can be played for if (totalTime + 1 > MAX_TIME) { gameOver = true; score += pacman.numberOfLivesRemaining * AWARD_LIFE_LEFT; } //if all pills have been eaten or the time is up... else if ((pills.isEmpty() && powerPills.isEmpty()) || currentLevelTime >= LEVEL_LIMIT) { _newLevelReset(); } } ///////////////////////////////////////////////////////////////////////////// /////////////////// Query Methods (return only) /////////////////////////// ///////////////////////////////////////////////////////////////////////////// /** * Returns whether pacman was eaten in the last time step * * @return whether Ms Pac-Man was eaten. */ public boolean wasPacManEaten() { return pacmanWasEaten; } /** * Returns whether a ghost was eaten in the last time step * * @param ghost the ghost to check * @return whether a ghost was eaten. */ public boolean wasGhostEaten(GHOST ghost) { return ghostsEaten.get(ghost); } public int getNumGhostsEaten() { int count = 0; for (GHOST ghost : ghosts.keySet()) { if (ghostsEaten.get(ghost)) { count++; } } return count; } /** * Returns whether a pill was eaten in the last time step * * @return whether a pill was eaten. */ public boolean wasPillEaten() { return pillWasEaten; } /** * Returns whether a power pill was eaten in the last time step * * @return whether a power pill was eaten. */ public boolean wasPowerPillEaten() { return powerPillWasEaten; } /** * Returns the time when the last global reversal event took place. * * @return time the last global reversal event took place (not including power pill reversals) */ public int getTimeOfLastGlobalReversal() { return timeOfLastGlobalReversal; } /** * Checks whether the game is over or not: all lives are lost or 16 levels have been * played. The variable is set by the methods _feast() and _checkLevelState(). * * @return true, if successful */ public boolean gameOver() { return gameOver; } /** * Returns the current maze of the game. * * @return The current maze. */ public Maze getCurrentMaze() { return currentMaze; } /** * Returns the x coordinate of the specified node. * * @param nodeIndex the node index * @return the node x cood */ public int getNodeXCood(int nodeIndex) { return currentMaze.graph[nodeIndex].x; } /** * Returns the y coordinate of the specified node. * * @param nodeIndex The node index * @return The node's y coordinate */ public int getNodeYCood(int nodeIndex) { return currentMaze.graph[nodeIndex].y; } /** * Gets the index of the current maze. * * @return The maze index */ public int getMazeIndex() { return mazeIndex; } /** * Returns the current level. * * @return The current level */ public int getCurrentLevel() { return levelCount; } /** * Returns the number of nodes in the current maze. * * @return number of nodes in the current maze. */ public int getNumberOfNodes() { return currentMaze.graph.length; } /** * Returns the current value awarded for eating a ghost. * * @return the current value awarded for eating a ghost. */ public int getGhostCurrentEdibleScore() { return GHOST_EAT_SCORE * ghostEatMultiplier; } /** * Returns the node index where ghosts start in the maze once leaving * the lair. * * @return the node index where ghosts start after leaving the lair. */ public int getGhostInitialNodeIndex() { return currentMaze.initialGhostNodeIndex; } /** * Returns the node index where Ms. Pac-Man starts in the maze * * @return the node index where Ms. Pac-Man starts in the maze */ public int getPacManInitialNodeIndex() { return currentMaze.initialPacManNodeIndex; } /** * Whether the pill specified is still there or has been eaten. * * @param pillIndex The pill index * @return true, if is pill still available */ public Boolean isPillStillAvailable(int pillIndex) { if (po) { int pillLocation = currentMaze.pillIndices[pillIndex]; if (!isNodeObservable(pillLocation)) { return null; } } return pills.get(pillIndex); } /** * Whether the power pill specified is still there or has been eaten. * * @param powerPillIndex The power pill index * @return true, if is power pill still available */ public Boolean isPowerPillStillAvailable(int powerPillIndex) { if (po) { int pillLocation = currentMaze.powerPillIndices[powerPillIndex]; if (!isNodeObservable(pillLocation)) { return null; } } return powerPills.get(powerPillIndex); } /** * Returns the pill index of the node specified. This can be -1 if there * is no pill at the specified node. * * @param nodeIndex The Index of the node. * @return a number corresponding to the pill index (or -1 if node has no pill) */ public int getPillIndex(int nodeIndex) { return currentMaze.graph[nodeIndex].pillIndex; } /** * Returns the power pill index of the node specified. This can be -1 if there * is no power pill at the specified node. * * @param nodeIndex The Index of the node. * @return a number corresponding to the power pill index (or -1 if node has no pill) */ public int getPowerPillIndex(int nodeIndex) { return currentMaze.graph[nodeIndex].powerPillIndex; } /** * Returns the array of node indices that are junctions (3 or more neighbours). * * @return the junction indices */ public int[] getJunctionIndices() { return currentMaze.junctionIndices; } /** * Returns the indices to all the nodes that have pills. * * @return the pill indices */ public int[] getPillIndices() { return currentMaze.pillIndices; } /** * Returns the indices to all the nodes that have power pills. * * @return the power pill indices */ public int[] getPowerPillIndices() { return currentMaze.powerPillIndices; } /** * Current node index of Ms Pac-Man. * * @return the pacman current node index */ public int getPacmanCurrentNodeIndex() { if (po && !isNodeObservable(pacman.currentNodeIndex)) { return -1; } return pacman.currentNodeIndex; } /** * Current node index of Ms Pac-Man. * * @return the pacman last move made */ public MOVE getPacmanLastMoveMade() { if (po && !isNodeObservable(pacman.currentNodeIndex)) { return null; } return pacman.lastMoveMade; } /** * Lives that remain for Ms Pac-Man. * * @return the number of lives remaining */ public int getPacmanNumberOfLivesRemaining() { return pacman.numberOfLivesRemaining; } /** * Current node at which the specified ghost resides. * * @param ghostType the ghost type * @return the ghost current node index */ public int getGhostCurrentNodeIndex(GHOST ghostType) { if (po) { int index = ghosts.get(ghostType).currentNodeIndex; return isNodeObservable(index) ? index : -1; } return ghosts.get(ghostType).currentNodeIndex; } /** * Current direction of the specified ghost. * * @param ghostType the ghost type * @return the ghost last move made */ public MOVE getGhostLastMoveMade(GHOST ghostType) { if (po) { Ghost ghost = ghosts.get(ghostType); return isNodeObservable(ghost.currentNodeIndex) ? ghost.lastMoveMade : null; } return ghosts.get(ghostType).lastMoveMade; } /** * Returns the edible time for the specified ghost. * * @param ghostType the ghost type * @return the ghost edible time */ public int getGhostEdibleTime(GHOST ghostType) { if (po) { Ghost ghost = ghosts.get(ghostType); return isNodeObservable(ghost.currentNodeIndex) ? ghost.edibleTime : -1; } return ghosts.get(ghostType).edibleTime; } /** * Simpler check to see if a ghost is edible. * * @param ghostType the ghost type * @return true, if is ghost edible */ public Boolean isGhostEdible(GHOST ghostType) { if (po) { Ghost ghost = ghosts.get(ghostType); return (isNodeObservable(ghost.currentNodeIndex)) ? ghost.edibleTime > 0 : null; } return ghosts.get(ghostType).edibleTime > 0; } /** * Returns the score of the game. * * @return the score */ public int getScore() { return score; } /** * Returns the time of the current level (important with respect to LEVEL_LIMIT). * * @return the current level time */ public int getCurrentLevelTime() { return currentLevelTime; } /** * Total time the game has been played for (at most LEVEL_LIMIT*MAX_LEVELS). * * @return the total time */ public int getTotalTime() { return totalTime; } /** * Total number of pills in the mazes[gs.curMaze] * * @return the number of pills */ public int getNumberOfPills() { return currentMaze.pillIndices.length; } /** * Total number of power pills in the mazes[gs.curMaze] * * @return the number of power pills */ public int getNumberOfPowerPills() { return currentMaze.powerPillIndices.length; } /** * Total number of pills in the mazes[gs.curMaze] * * @return the number of active pills */ public int getNumberOfActivePills() { return pills.cardinality(); } /** * Total number of power pills in the mazes[gs.curMaze] * * @return the number of active power pills */ public int getNumberOfActivePowerPills() { return powerPills.cardinality(); } /** * Time left that the specified ghost will spend in the lair. * * @param ghostType the ghost type * @return the ghost lair time */ public int getGhostLairTime(GHOST ghostType) { if (po) { Ghost ghost = ghosts.get(ghostType); return isNodeObservable(ghost.currentNodeIndex) ? ghost.lairTime : -1; } return ghosts.get(ghostType).lairTime; } /** * Returns the indices of all active pills in the mazes[gs.curMaze] * * @return the active pills indices */ public int[] getActivePillsIndices() { int[] indices = new int[pills.cardinality()]; int index = 0; for (int i = 0; i < currentMaze.pillIndices.length; i++) { if (!po || isNodeObservable(currentMaze.pillIndices[i])) { if (pills.get(i)) { indices[index++] = currentMaze.pillIndices[i]; } } } if (index != indices.length) { int[] results = new int[index]; System.arraycopy(indices, 0, results, 0, index); return results; } return indices; } /** * Returns the indices of all active power pills in the mazes[gs.curMaze] * * @return the active power pills indices */ public int[] getActivePowerPillsIndices() { int[] indices = new int[powerPills.cardinality()]; int index = 0; for (int i = 0; i < currentMaze.powerPillIndices.length; i++) { if (!po || isNodeObservable(currentMaze.powerPillIndices[i])) { if (powerPills.get(i)) { indices[index++] = currentMaze.powerPillIndices[i]; } } } if (index != indices.length) { int[] results = new int[index]; System.arraycopy(indices, 0, results, 0, index); return results; } return indices; } /** * s * If in lair (getLairTime(-) > 0) or if not at junction. * * @param ghostType the ghost type * @return true, if successful */ public Boolean doesGhostRequireAction(GHOST ghostType) { //inlcude neutral here for the unique case where the ghost just left the lair if (!po || isNodeObservable(ghosts.get(ghostType).currentNodeIndex)) { return ((isJunction(ghosts.get(ghostType).currentNodeIndex) || (ghosts.get(ghostType).lastMoveMade == MOVE.NEUTRAL) && ghosts.get(ghostType).currentNodeIndex == currentMaze.initialGhostNodeIndex) && (ghosts.get(ghostType).edibleTime == 0 || ghosts.get(ghostType).edibleTime % GHOST_SPEED_REDUCTION != 0)); } else { return null; } } /** * Checks if the node specified by the nodeIndex is a junction. * * @param nodeIndex the node index * @return true, if is junction */ public boolean isJunction(int nodeIndex) { return currentMaze.graph[nodeIndex].numNeighbouringNodes > 2; } /** * Gets the possible moves from the node index specified. * * @param nodeIndex The current node index * @return The set of possible moves */ public MOVE[] getPossibleMoves(int nodeIndex) { return currentMaze.graph[nodeIndex].allPossibleMoves.get(MOVE.NEUTRAL); } /** * Gets the possible moves except the one that corresponds to the reverse of the move supplied. * * @param nodeIndex The current node index * @param lastModeMade The last mode made (possible moves will exclude the reverse) * @return The set of possible moves */ public MOVE[] getPossibleMoves(int nodeIndex, MOVE lastModeMade) { return currentMaze.graph[nodeIndex].allPossibleMoves.get(lastModeMade); } /** * Gets the neighbouring nodes from the current node index. * * @param nodeIndex The current node index * @return The set of neighbouring nodes */ public int[] getNeighbouringNodes(int nodeIndex) { return currentMaze.graph[nodeIndex].allNeighbouringNodes.get(MOVE.NEUTRAL); } /** * Gets the neighbouring nodes from the current node index excluding the node * that corresponds to the opposite of the last move made which is given as an argument. * * @param nodeIndex The current node index * @param lastModeMade The last mode made * @return The set of neighbouring nodes except the one that is opposite of the last move made */ public int[] getNeighbouringNodes(int nodeIndex, MOVE lastModeMade) { return currentMaze.graph[nodeIndex].allNeighbouringNodes.get(lastModeMade); } /** * Given a node index and a move to be made, it returns the node index the move takes one to. * If there is no neighbour in that direction, the method returns -1. * * @param nodeIndex The current node index * @param moveToBeMade The move to be made * @return The node index of the node the move takes one to */ public int getNeighbour(int nodeIndex, MOVE moveToBeMade) { Integer neighbour = currentMaze.graph[nodeIndex].neighbourhood.get(moveToBeMade); return neighbour == null ? -1 : neighbour; } /** * Method that returns the direction to take given a node index and an index of a neighbouring * node. Returns null if the neighbour is invalid. * * @param currentNodeIndex The current node index. * @param neighbourNodeIndex The direct neighbour (node index) of the current node. * @return the move to make to reach direct neighbour */ public MOVE getMoveToMakeToReachDirectNeighbour(int currentNodeIndex, int neighbourNodeIndex) { for (MOVE move : MOVE.values()) { if (currentMaze.graph[currentNodeIndex].neighbourhood.containsKey(move) && currentMaze.graph[currentNodeIndex].neighbourhood.get(move) == neighbourNodeIndex) { return move; } } return null; } ///////////////////////////////////////////////////////////////////////////// /////////////////// Helper Methods (computational) //////////////////////// ///////////////////////////////////////////////////////////////////////////// /** * Returns the PATH distance from any node to any other node. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @return the shortest path distance */ public int getShortestPathDistance(int fromNodeIndex, int toNodeIndex) { if (fromNodeIndex == toNodeIndex) { return 0; } else if (fromNodeIndex < toNodeIndex) { return currentMaze.shortestPathDistances[((toNodeIndex * (toNodeIndex + 1)) / 2) + fromNodeIndex]; } else { return currentMaze.shortestPathDistances[((fromNodeIndex * (fromNodeIndex + 1)) / 2) + toNodeIndex]; } } /** * Returns the EUCLEDIAN distance between two nodes in the current mazes[gs.curMaze]. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @return the euclidean distance */ public double getEuclideanDistance(int fromNodeIndex, int toNodeIndex) { return Math.sqrt(Math.pow(currentMaze.graph[fromNodeIndex].x - currentMaze.graph[toNodeIndex].x, 2) + Math.pow(currentMaze.graph[fromNodeIndex].y - currentMaze.graph[toNodeIndex].y, 2)); } /** * Returns the MANHATTAN distance between two nodes in the current mazes[gs.curMaze]. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @return the manhattan distance */ public int getManhattanDistance(int fromNodeIndex, int toNodeIndex) { return (Math.abs(currentMaze.graph[fromNodeIndex].x - currentMaze.graph[toNodeIndex].x) + Math.abs(currentMaze.graph[fromNodeIndex].y - currentMaze.graph[toNodeIndex].y)); } /** * Gets the distance. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @param distanceMeasure the distance measure * @return the distance */ public double getDistance(int fromNodeIndex, int toNodeIndex, DM distanceMeasure) { switch (distanceMeasure) { case PATH: return getShortestPathDistance(fromNodeIndex, toNodeIndex); case EUCLID: return getEuclideanDistance(fromNodeIndex, toNodeIndex); case MANHATTAN: return getManhattanDistance(fromNodeIndex, toNodeIndex); } return -1; } /** * Returns the distance between two nodes taking reversals into account. * * @param fromNodeIndex the index of the originating node * @param toNodeIndex the index of the target node * @param lastMoveMade the last move made * @param distanceMeasure the distance measure to be used * @return the distance between two nodes. */ public double getDistance(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade, DM distanceMeasure) { switch (distanceMeasure) { case PATH: return getApproximateShortestPathDistance(fromNodeIndex, toNodeIndex, lastMoveMade); case EUCLID: return getEuclideanDistance(fromNodeIndex, toNodeIndex); case MANHATTAN: return getManhattanDistance(fromNodeIndex, toNodeIndex); } return -1; } /** * Gets the closest node index from node index. * * @param fromNodeIndex the from node index * @param targetNodeIndices the target node indices * @param distanceMeasure the distance measure * @return the closest node index from node index */ public int getClosestNodeIndexFromNodeIndex(int fromNodeIndex, int[] targetNodeIndices, DM distanceMeasure) { double minDistance = Integer.MAX_VALUE; int target = -1; for (int i = 0; i < targetNodeIndices.length; i++) { double distance = 0; distance = getDistance(targetNodeIndices[i], fromNodeIndex, distanceMeasure); if (distance < minDistance) { minDistance = distance; target = targetNodeIndices[i]; } } return target; } /** * Gets the farthest node index from node index. * * @param fromNodeIndex the from node index * @param targetNodeIndices the target node indices * @param distanceMeasure the distance measure * @return the farthest node index from node index */ public int getFarthestNodeIndexFromNodeIndex(int fromNodeIndex, int[] targetNodeIndices, DM distanceMeasure) { double maxDistance = Integer.MIN_VALUE; int target = -1; for (int i = 0; i < targetNodeIndices.length; i++) { double distance = 0; distance = getDistance(targetNodeIndices[i], fromNodeIndex, distanceMeasure); if (distance > maxDistance) { maxDistance = distance; target = targetNodeIndices[i]; } } return target; } /** * Gets the next move towards target. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @param distanceMeasure the distance measure * @return the next move towards target */ public MOVE getNextMoveTowardsTarget(int fromNodeIndex, int toNodeIndex, DM distanceMeasure) { MOVE move = null; double minDistance = Integer.MAX_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].neighbourhood.entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, distanceMeasure); if (distance < minDistance) { minDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the next move away from target. * * @param fromNodeIndex the from node index * @param toNodeIndex the to node index * @param distanceMeasure the distance measure * @return the next move away from target */ public MOVE getNextMoveAwayFromTarget(int fromNodeIndex, int toNodeIndex, DM distanceMeasure) { MOVE move = null; double maxDistance = Integer.MIN_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].neighbourhood.entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, distanceMeasure); if (distance > maxDistance) { maxDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the approximate next move towards target not considering directions opposing the last move made. * * @param fromNodeIndex The node index from which to move (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @param distanceMeasure The distance measure required (Manhattan, Euclidean or Straight line) * @return The approximate next move towards target (chosen greedily) */ public MOVE getApproximateNextMoveTowardsTarget(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade, DM distanceMeasure) { MOVE move = null; double minDistance = Integer.MAX_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].allNeighbourhoods.get(lastMoveMade).entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, distanceMeasure); if (distance < minDistance) { minDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the approximate next move away from a target not considering directions opposing the last move made. * * @param fromNodeIndex The node index from which to move (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @param distanceMeasure The distance measure required (Manhattan, Euclidean or Straight line) * @return The approximate next move towards target (chosen greedily) */ public MOVE getApproximateNextMoveAwayFromTarget(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade, DM distanceMeasure) { MOVE move = null; double maxDistance = Integer.MIN_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].allNeighbourhoods.get(lastMoveMade).entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, distanceMeasure); if (distance > maxDistance) { maxDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the exact next move towards target taking into account reversals. This uses the pre-computed paths. * * @param fromNodeIndex The node index from which to move (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @param distanceMeasure the distance measure to be used * @return the next move towards target */ public MOVE getNextMoveTowardsTarget(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade, DM distanceMeasure) { MOVE move = null; double minDistance = Integer.MAX_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].allNeighbourhoods.get(lastMoveMade).entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, lastMoveMade, distanceMeasure); if (distance < minDistance) { minDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the exact next move away from target taking into account reversals. This uses the pre-computed paths. * * @param fromNodeIndex The node index from which to move (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @param distanceMeasure the distance measure to be used * @return the next move away from target */ public MOVE getNextMoveAwayFromTarget(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade, DM distanceMeasure) { MOVE move = null; double maxDistance = Integer.MIN_VALUE; for (Entry entry : currentMaze.graph[fromNodeIndex].allNeighbourhoods.get(lastMoveMade).entrySet()) { double distance = getDistance(entry.getValue(), toNodeIndex, lastMoveMade, distanceMeasure); if (distance > maxDistance) { maxDistance = distance; move = entry.getKey(); } } return move; } /** * Gets the A* path considering previous moves made (i.e., opposing actions are ignored) * * @param fromNodeIndex The node index from which to move (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @return The A* path * @deprecated use getShortestPath() instead. */ @Deprecated public int[] getAStarPath(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade) { return getShortestPath(fromNodeIndex, toNodeIndex, lastMoveMade); } /** * Gets the shortest path from node A to node B as specified by their indices. * * @param fromNodeIndex The node index from where to start (i.e., current position) * @param toNodeIndex The target node index * @return the shortest path from start to target */ public int[] getShortestPath(int fromNodeIndex, int toNodeIndex) { return caches[mazeIndex].getPathFromA2B(fromNodeIndex, toNodeIndex); } /** * Gets the approximate shortest path taking into account the last move made (i.e., no reversals). * This is approximate only as the path is computed greedily. A more accurate path can be obtained * using A* which is slightly more costly. * * @param fromNodeIndex The node index from where to start (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @return the shortest path from start to target * @deprecated use getShortestPath() instead. */ @Deprecated public int[] getApproximateShortestPath(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade) { return getShortestPath(fromNodeIndex, toNodeIndex, lastMoveMade); } /** * Gets the shortest path taking into account the last move made (i.e., no reversals). * This is approximate only as the path is computed greedily. A more accurate path can be obtained * using A* which is slightly more costly. * * @param fromNodeIndex The node index from where to start (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @return the shortest path from start to target */ public int[] getShortestPath(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade) { if (currentMaze.graph[fromNodeIndex].neighbourhood.size() == 0)//lair { return new int[0]; } return caches[mazeIndex].getPathFromA2B(fromNodeIndex, toNodeIndex, lastMoveMade); } /** * Similar to getApproximateShortestPath but returns the distance of the path only. It is slightly * more efficient. * * @param fromNodeIndex The node index from where to start (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @return the exact distance of the path * @deprecated use getShortestPathDistance() instead. */ @Deprecated public int getApproximateShortestPathDistance(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade) { return getShortestPathDistance(fromNodeIndex, toNodeIndex, lastMoveMade); } /** * Similar to getShortestPath but returns the distance of the path only. It is slightly * more efficient. * * @param fromNodeIndex The node index from where to start (i.e., current position) * @param toNodeIndex The target node index * @param lastMoveMade The last move made * @return the exact distance of the path */ public int getShortestPathDistance(int fromNodeIndex, int toNodeIndex, MOVE lastMoveMade) { if (currentMaze.graph[fromNodeIndex].neighbourhood.size() == 0)//lair { return 0; } return caches[mazeIndex].getPathDistanceFromA2B(fromNodeIndex, toNodeIndex, lastMoveMade); } /** * Can be used to query if the game contains Messaging *

* Will be true if messaging is active and the copy of the game is owned by a Ghost *

* Pacman can't access the Messenger * * @return True if messaging is available, false otherwise */ public boolean hasMessaging() { return messenger != null && agent < GHOST.values().length; } /** * Gets the messenger or null if it either doesn't exist or you don't have access to it * * @return The messenger */ public Messenger getMessenger() { return (hasMessaging() ? messenger : null); } /** * Gets a data structure that can be modified and then used to construct a forward model * * @return The GameInfo object for use in making the Game */ public GameInfo getBlankGameInfo() { return new GameInfo(pills.length()); } /** * Gets a GameInfo object that is populated with present data that you can see * * @return */ public GameInfo getPopulatedGameInfo() { GameInfo info = getBlankGameInfo(); if (getPacmanCurrentNodeIndex() != -1) { info.setPacman(new PacMan( getPacmanCurrentNodeIndex(), getPacmanLastMoveMade(), getPacmanNumberOfLivesRemaining(), false )); } EnumMap ghosts = info.getGhosts(); for (Constants.GHOST ghost : GHOST.values()) { if (getGhostCurrentNodeIndex(ghost) != -1) { ghosts.put(ghost, new Ghost( ghost, getGhostCurrentNodeIndex(ghost), getGhostEdibleTime(ghost), getGhostLairTime(ghost), getGhostLastMoveMade(ghost) ) ); } } for (int index : getActivePillsIndices()) { info.setPillAtIndex(getPillIndex(index), true); } for(int index : getActivePowerPillsIndices()){ info.setPowerPillAtIndex(getPowerPillIndex(index), true); } return info; } /** * Gets a copy of the game that is populated with the data contained within info * * @param info The data you wish the game to be supplied with * @return The resultant game */ public Game getGameFromInfo(GameInfo info) { Game game = copy(false); // Destroy the messenger reference - can't allow communication in the playouts // Can we? start a fresh one? game.messenger = null; game.pills = info.getPills(); game.powerPills = info.getPowerPills(); // Etc game.pacman = info.getPacman(); game.ghosts = info.getGhosts(); game.beenBlanked = true; game.po = false; return game; } /** * Is this game Partially Observable? * * @return The boolean answer to the question */ public boolean isGamePo() { return po; } public void setGhostsPresent(boolean ghostsPresent) { this.ghostsPresent = ghostsPresent; } public void setPillsPresent(boolean pillsPresent) { this.pillsPresent = pillsPresent; } public void setPowerPillsPresent(boolean powerPillsPresent) { this.powerPillsPresent = powerPillsPresent; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy