/** * @file Node89978445.java * @brief * File containing the Node class witch represents * the moves of Pacman * * @author Christos Choutouridis 8997 cchoutou@ece.auth.gr * @author Konstantina Tsechelidou 8445 konstsec@ece.auth.gr */ package gr.auth.ee.dsproject.node; import java.util.ArrayList; //import java.awt.image.PackedColorModel; import gr.auth.ee.dsproject.pacman.PacmanUtilities; import gr.auth.ee.dsproject.pacman.Room; /** * @class Node89978445 * @brief * This class holds each move of the current round and it's evaluation * */ public class Node89978445 { double nodeEvaluation; /*< * Pacman's current move evaluation * This is used also as the "return status" of the object */ int[] nodeXY; // Pacman's current x,y coordinate int[][] curGhostPos; // Array holding the Ghost (x,y) pairs int[][] flagPos; // Array holding the flag (x,y) pairs boolean[] curFlagStatus; // Array holding the flag capture status Room[][] Maze; // copy of the current Maze /* * Tree navigation references. These variables are handled by * PacTree */ Node89978445 parent; ArrayList children; int depth; /* * ============ Constructors ============== */ /** * @brief * The simple constructor. Just initialize the data * @note * Using this constructor means that the user MUST call setMaze(), setPosition() * and setMove manually after the creation of the Node89978445 object */ public Node89978445 () { // Fill members this.Maze = null; nodeXY = Globals.FALSE_POS; nodeEvaluation = Globals.NO_EVAL; parent = null; // allocate objects curGhostPos = new int [PacmanUtilities.numberOfGhosts][2]; flagPos = new int [PacmanUtilities.numberOfFlags][2]; curFlagStatus = new boolean[PacmanUtilities.numberOfFlags]; children = new ArrayList (); } /** * @brief * Constructor for the Node89978445 * @param Maze The current maze object * @param curXY The current pacman's (x, y) position */ public Node89978445 (Room[][] Maze, int[] curXY) { this.Maze = Maze; // Fill members nodeXY = curXY; nodeEvaluation = Globals.NO_EVAL; parent = null; // allocate objects curGhostPos = new int [PacmanUtilities.numberOfGhosts][2]; flagPos = new int [PacmanUtilities.numberOfFlags][2]; curFlagStatus = new boolean[PacmanUtilities.numberOfFlags]; children = new ArrayList (); // calculate helper arrays curGhostPos = findGhosts (); flagPos = findFlags (); curFlagStatus = checkFlags (); //Evaluate the position nodeEvaluation = evaluate (); } /* * ============== Setters =============== */ /** * @brief SetMaze (Room) to Node object * @param maze The room to set */ public void setMaze (Room[][] maze) { this.Maze = maze; } /** * @brief Set pacman's position * @param curXY Pacman's current X, Y position */ public void setPosition (int[] curXY) { nodeXY = curXY; } /* * ============== getters ================= */ /** * @brief If not done runs the evaluation algorithm and returns the result * @note We assume the current position was a valid position * @return The evaluation result */ public double getEvaluation () { // If already evaluated do not re-evaluate the move if (nodeEvaluation == Globals.NO_EVAL) { // Safety filters if (Maze == null) return Globals.NO_EVAL; if (nodeXY == Globals.FALSE_POS) return Globals.NO_EVAL; // calculate helper arrays curGhostPos = findGhosts (); flagPos = findFlags (); curFlagStatus = checkFlags (); return nodeEvaluation = evaluate (); } else return nodeEvaluation; } public int[] getCurrentPacmanPos () { return nodeXY; } public int[][] getCurrentGhostPos () { return curGhostPos; } public int getDepth () { return depth; } /* * ============= Helper API ============ */ public static int moveConv (int[] nextPos, int[] curPos) { int dx = nextPos[0] - curPos[0]; int dy = nextPos[1] - curPos[1]; if (dx < 0) return Room.NORTH; else if (dx > 0) return Room.SOUTH; else if (dy < 0) return Room.WEST; else return Room.EAST; } /** * @param creature creature's (x,y) * @return * Return true if the creature is inside the maze boxes */ public static boolean isInsideBox (int[] creature) { boolean ret = false; //have faith for (int i=0 ; i<4 ; ++i) { if ((creature[0]>=Globals.BOXES[i][0][0] && creature[0]<=Globals.BOXES[i][1][0]) && (creature[1]>=Globals.BOXES[i][0][1] && creature[1]<=Globals.BOXES[i][1][1])) ret = true; } return ret; } /** * @brief * Static version of move validation * Check if the requested move for a node is valid */ public static int[] pacmanValidMove (Node89978445 node, int move) { int[] newPos = new int[2]; // find hypothetical new position newPos[0] = node.nodeXY[0]; newPos[1] = node.nodeXY[1]; newPos[0] += (move == Room.SOUTH) ? 1:0; newPos[0] -= (move == Room.NORTH) ? 1:0; newPos[1] += (move == Room.EAST) ? 1:0; newPos[1] -= (move == Room.WEST) ? 1:0; // Pacman curves Maze plane to a Torus if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows; if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0; if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns; if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0; // Valid filters if (!isInsideBox(node.nodeXY) && (node.Maze[node.nodeXY[0]][node.nodeXY[1]].walls[move] == 0)) return Globals.FALSE_POS; return newPos; } /* * ============= Node's private methods ============= */ /** * @brief * Loop entire maze and return an object that holds Ghost positions * @param none * @return Object with Ghost position * The first dimension holds the number of the ghost * The 2nd dimension holds the (x, y) coordinates of the ghost */ private int[][] findGhosts () { int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return int g = 0; // Ghost index boolean keepGoing = true; // Boundary check helper variable // Loop entire Maze (i, j) for (int x=0 ; keepGoing && x PacmanUtilities.numberOfGhosts) keepGoing = false; } } } return ret; } /** * @brief * Loop entire maze and return an object that holds flags positions * @param none * @return Object with flag positions * The first dimension holds the number of the flag * The 2nd dimension holds the (x, y) coordinates of the flag */ private int[][] findFlags () { int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return int g = 0; // Flag index boolean keepGoing = true; // Boundary check helper variable // Loop entire Maze (i, j) for (int x=0 ; keepGoing && x PacmanUtilities.numberOfFlags) keepGoing = false; } } } return ret; } /** * @brief * Loop through flags and check their status * @param none * @return Object with flag status */ private boolean[] checkFlags () { boolean[] ret = new boolean [PacmanUtilities.numberOfFlags]; int x, y; for (int i=0 ; i= 0 && newPos[0] < PacmanUtilities.numberOfRows) && (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns))) return Globals.FALSE_POS; if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0)) return Globals.FALSE_POS; return newPos; } /** * @brief * Check if the requested pacman move is valid */ private int[] pacmanValidMove (int[] pacman, int move) { int[] newPos = new int[2]; // find hypothetical new position newPos[0] = pacman[0]; newPos[1] = pacman[1]; newPos[0] += (move == Room.SOUTH) ? 1:0; newPos[0] -= (move == Room.NORTH) ? 1:0; newPos[1] += (move == Room.EAST) ? 1:0; newPos[1] -= (move == Room.WEST) ? 1:0; // Pacman curves Maze plane to a Torus if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows; if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0; if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns; if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0; // Valid filters if (!isInsideBox(pacman) && (Maze[pacman[0]][pacman[1]].walls[move] == 0)) return Globals.FALSE_POS; return newPos; } /** * @brief * A Breadth-first search to find the shortest path distance * from an origin point to another point in the maze as if a * a creature could walk through * @param origin origin (x, y) * @param xy The x, y coordinate in Maze * @param creature The type of creature for whom the path is for * @return The number of steps from origin to xy */ private int moveDist (int[] origin, int[] xy, Creature creature) { int move; int steps, qStepItems; // distance and group counters boolean done = false; // algo ending flag int r = PacmanUtilities.numberOfRows; // helper for shorting names int c = PacmanUtilities.numberOfColumns; // helper for shorting names int[] xyItem = new int [2]; // Coordinates of the current position of the algo int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo Queue2D q = new Queue2D (Globals.MAX_DISTANCE - 1); // Queue to feed with possible position int [][] dist = new int [r][c]; /*< * 2D array holding all the distances from the origin to each square of the maze */ // If target square is inside a box abort with max distance if (isInsideBox(xy)) return Globals.MAX_DISTANCE; /* * init data for algorithm */ for (int i=0 ; i0) { // Have we any item on the current group? xyItem = q.remove (); //load an item of the current group --qStepItems; // mark the removal of the item for (move=0 ; move<4 ; ++move) { // loop every valid next position for the current position switch (creature) { case GHOST: xyNext = ghostValidMove (xyItem, move); break; case PACMAN: xyNext = pacmanValidMove (xyItem, move); break; default: xyNext = Globals.FALSE_POS; } if (xyNext != Globals.FALSE_POS) { if (dist[xyNext[0]][xyNext[1]] == -1) { // If we haven't been there dist[xyNext[0]][xyNext[1]] = steps; // mark the distance if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) { /* * first match: * The first time we reach destination we have count the * distance. No need any other try */ done = true; break; } else { // If we are not there yet, feed queue with another position q.insert (xyNext); } } } } } else { // We are done with group, mark how many are for the next one if ((qStepItems = q.size()) <= 0) return dist[xy[0]][xy[1]]; // fail safe return ++steps; // Update distance counter } } return dist[xy[0]][xy[1]]; } /** * @brief * Evaluate the current move */ private double evaluate () { double ghostEval=0, gd=0; double flagEval=0; int i; int[] flagDist = new int[PacmanUtilities.numberOfFlags]; int[] ghostDist = new int[PacmanUtilities.numberOfGhosts]; int minGhostDist=Globals.MAX_DISTANCE+1; int averGhostDist; // cast to integer int minFlagDist=Globals.MAX_DISTANCE+1; // Find ghost distances, min and average for (i=0, averGhostDist=0 ; i ghostDist[i]) minGhostDist = ghostDist[i]; } averGhostDist /= PacmanUtilities.numberOfGhosts; // extract the ghostEval as a combination of the minimum distance and the average gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_MIN_FACTOR * averGhostDist; // normalize the ghostEval to our interval ghostEval = (-1.0 / (1+gd)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) + (Globals.EVAL_MAX - Globals.EVAL_MIN)/2; // Find flag distances and min for (i=0 ; i flagDist[i]) minFlagDist = flagDist[i]; } } // normalize the flagEval to our interval flagEval = (1.0 / (1+minFlagDist)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) - (Globals.EVAL_MAX - Globals.EVAL_MIN)/2; // mix the life and goal to output return ghostEval * Globals.EVAL_GHOSTDIST_FACTOR + flagEval * Globals.EVAL_FLAGDIST_FACTOR; } }