|
- /**
- * @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<Node89978445> 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<Node89978445> ();
- }
-
- /**
- * @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<Node89978445> ();
-
- // 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.numberOfRows ; ++x) {
- for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
- if (Maze[x][y].isGhost ()) {
- // In case of a Ghost save its position to return object
- ret[g][0] = x;
- ret[g][1] = y;
- // boundary check
- if (++g > 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.numberOfRows ; ++x) {
- for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
- if (Maze[x][y].isFlag()) {
- // In case of a Ghost save its position to return object
- ret[g][0] = x;
- ret[g][1] = y;
- // boundary check
- if (++g > 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<PacmanUtilities.numberOfFlags ; ++i) {
- x = flagPos[i][0]; // Get row,col coordinates
- y = flagPos[i][1];
- ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
- }
- return ret;
- }
-
- /*
- * ============ private evaluation helper methods ==============
- */
-
- private enum Creature {
- GHOST, PACMAN
- }
-
- /**
- * @brief
- * Check if the requested ghost move is valid
- */
- private int[] ghostValidMove (int[] ghost, int move)
- {
- int[] newPos = new int[2];
-
- // find hypothetical new position
- newPos[0] = ghost[0];
- newPos[1] = ghost[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;
-
- // Valid filters
- if (!((newPos[0] >= 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 ; i<r ; ++i) {
- for (int j=0 ; j<c ; ++j) {
- dist[i][j] = -1;
- // dist array starts with -1
- }
- }
- // loop data
- dist [origin[0]][origin[1]] = 0; //starting point is the origin position
- q.insert (origin); // feed first loop data
- qStepItems = 1; // init counters
- steps = 1;
- /*
- * Main loop of the algorithm
- *
- * For every position we check the valid moves, we apply to them
- * the step number and we push them to queue. We group the items in
- * queue with their step number by measuring how many we push them for
- * the current steps variable value.
- */
- while (done == false) {
- if (qStepItems>0) { // 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<PacmanUtilities.numberOfGhosts ; ++i) {
- ghostDist [i] = moveDist (curGhostPos[i], nodeXY, Creature.GHOST);
- averGhostDist += ghostDist [i];
- if (minGhostDist > 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<PacmanUtilities.numberOfFlags ; ++i) {
- if (curFlagStatus[i] == false) {
- flagDist[i] = moveDist (nodeXY, flagPos[i], Creature.PACMAN);
- if (minFlagDist > 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;
-
- }
-
- }
|