|
- /**
- * @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 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 as return value of evaluation ()
- * This is used also as the "return status" of the object
- * @note
- * The variable is initialized to Globals.NO_EVAL and calculated only
- * after the getEvaluation () call
- */
- 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; // reference to parent node in search tree
- ArrayList<Node89978445> children; // references to children nodes in search tree
- int depth; // the depth of the node in search tree
-
- /*
- * ============ Constructors ==============
- */
- /**
- * @brief
- * The simple constructor. Just initialize the data
- * @note
- * Using this constructor means that the user MUST call setMaze()
- * and setPosition() manually after the creation of the Node89978445 object
- */
- public Node89978445 ()
- {
- this.Maze = null; // Fill members
- nodeXY = Globals.POSITION_FALSE;
- 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> ();
- }
-
- /*
- * ============== 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 calls the evaluation algorithm and returns the result
- * @return The evaluation result (also stored inside object)
- * @note
- * We assume the current position was a valid position.
- * This routine is not called upon creation of the class
- */
- 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.POSITION_FALSE)
- return Globals.NO_EVAL;
- // calculate helper arrays
- curGhostPos = findGhosts ();
- flagPos = findFlags ();
- curFlagStatus = checkFlags ();
-
- return nodeEvaluation = evaluate ();
- }
- else
- return nodeEvaluation;
- }
-
- /**
- * @return Nodes position in maze
- */
- public int[] getCurrentPacmanPos () {
- return nodeXY;
- }
- /**
- * @return Current ghost position ina maze
- */
- public int[][] getCurrentGhostPos () {
- return curGhostPos;
- }
- /**
- * @return The depth of the node tin the min-max tree
- */
- public int getDepth () {
- return depth;
- }
-
- /*
- * ============= Helper API ============
- */
- /**
- * @brief
- * Convert back a maze position to Room's direction format
- * @param nextPos The intention (x, y) coordinates
- * @param curPos The current coordinates
- * @return The direction
- * @arg Room.NORTH (x decreases)
- * @arg Room.SOUTH (x increases)
- * @arg Room.WEST (y decreases)
- * @arg Room.EAST (y increases)
- */
- public static int moveConv (int[] nextPos, int[] curPos)
- {
- int dx = nextPos[0] - curPos[0];
- int dy = nextPos[1] - curPos[1];
-
- if (dx == -1 || dx == PacmanUtilities.numberOfRows-1)
- return Room.NORTH;
- else if (dx == 1 || dx == -PacmanUtilities.numberOfRows+1)
- return Room.SOUTH;
- else if (dy == -1 || dy == PacmanUtilities.numberOfColumns-1)
- return Room.WEST;
- else if (dy == 1 || dy == -PacmanUtilities.numberOfColumns+1)
- return Room.EAST;
- else
- return Globals.INVALID_MOVE;
- }
-
- /**
- * @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
- * Torus borders are the squares in the outer limits of the maze where
- * there are no walls. Pacman's gravity can curve the maze plane to
- * a torus through these squares. This function check if a position is
- * a torus border position
- * @param creature creature's (x,y)
- * @return
- * Return true if the creature is in a border position.
- */
- public static boolean isTorusSquare (int[] creature)
- {
- for (int i=0 ; i<Globals.TORUS_BORDERS.length ; ++i) {
- if (creature[0] == Globals.TORUS_BORDERS[i][0] &&
- creature[1] == Globals.TORUS_BORDERS[i][1]) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @brief
- * Static version of move validation to use outside of the class
- * Check if the requested move for a node is valid
- */
- public static int[] pacmanValidMove (Node89978445 node, int move) {
- return node.pacmanValidMove (node.nodeXY, move);
- }
-
-
- /*
- * ============= 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 f = 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[f][0] = x;
- ret[f][1] = y;
- // boundary check
- if (++f >= PacmanUtilities.numberOfFlags)
- keepGoing = false;
- }
- }
- }
- return ret;
- }
-
- /**
- * @brief
- * Loop through flags and check their status
- * @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 ==============
- */
-
- /**
- * Helper enumerator to make a function pointer like trick
- * inside @ref moveDist()
- */
- private enum Creature {
- GHOST, PACMAN
- }
-
- /**
- * @brief
- * Check if the requested ghost move is valid
- * @param ghost ghost coordinates to check the move validation
- * @param move the move to check
- * @return
- * @arg The resulting position in maze if the move is valid.
- * @arg If it is not valid return Globals.POSITION_FALSE
- */
- 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.POSITION_FALSE;
- /*
- * else if (Maze[newPos[0]][newPos[1]].isFlag ())
- * return Globals.POSITION_FALSE;
- * @note
- * We comment out the flag part...
- * This incorrect behavior help evaluation routine to stop treat
- * flag position as asylum. In other words we remove the discontinuity
- * of shortestMoveDist() for ghost function.
- */
- else if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
- return Globals.POSITION_FALSE;
- else
- return newPos;
- }
-
- /**
- * @brief
- * Check if the requested pacman move is valid
- * @param pacman pacman coordinates to check the move validation
- * @param move the move to check
- * @return
- * @arg The resulting position in maze if the move is valid.
- * @arg If it is not valid return Globals.POSITION_FALSE
- */
- 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-1;
- if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
- if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns-1;
- if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
-
- // Valid filters
- if (Maze[pacman[0]][pacman[1]].walls[move] == 0)
- return Globals.POSITION_FALSE;
- else if (Maze[newPos[0]][newPos[1]].isGhost ())
- return Globals.POSITION_FALSE;
- else
- 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 creature could walk through origin to xy
- * @param origin origin's x, y (starting point)
- * @param xy destination's x, y
- * @param creature The type of creature for whom the path is for.
- * This helper variable let us decide which validation
- * function should call.
- * @return The number of steps from origin to xy
- * @note
- * As long as Java have not function pointers. I prefer this
- * as a work around over interfaces and lambdas.
- */
- private int shortestMoveDist (int[] origin, int[] xy, Creature creature)
- {
- int move; // move direction
- int steps, qStepItems; // distance and group counters
- boolean done = false; // algo ending flag
- 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
- // Queue to feed with possible position
- Queue2D q = new Queue2D (Globals.DISTANCE_MAX - 1);
- // 2D array holding all the distances from the origin to each square of the maze
- int[][] dist = new int [PacmanUtilities.numberOfRows][PacmanUtilities.numberOfColumns];
-
- /*
- * If target square is inside a box abort with max DISTANCE_FALSE
- * This is for speeding thing up, as the algorithm would return
- * the same thing anyway.
- */
- if (isInsideBox(xy))
- return Globals.DISTANCE_FALSE;
-
- /*
- * initialize data for algorithm
- */
- for (int i=0 ; i<PacmanUtilities.numberOfRows ; ++i)
- for (int j=0 ; j<PacmanUtilities.numberOfColumns ; ++j)
- dist[i][j] = Globals.DISTANCE_FALSE; // dist array starts with -1
- 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 pulled from queue with the same "steps" value:
- * - check all the valid moves around current's position
- * - mark them with the current "steps" value in dist[][] array
- * - push them to queue
- * - count the pushed position with the same "steps" value in queue
- * If no other queued position with the current "steps" value remained
- * increase "steps" value and continue.
- */
- 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
- // loop every valid next position for the current position
- for (move=0 ; move<4 ; ++move) {
- switch (creature) {
- case GHOST: xyNext = ghostValidMove (xyItem, move); break;
- case PACMAN: xyNext = pacmanValidMove (xyItem, move); break;
- default: xyNext = Globals.POSITION_FALSE;
- /*
- * Function pointer - like behavior
- */
- }
- if (xyNext != Globals.POSITION_FALSE) {
- if (dist[xyNext[0]][xyNext[1]] == Globals.DISTANCE_FALSE) {
- // 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 measured 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)
- done = true; // There is no path to destination, abort
- ++steps; // Update distance counter
- }
- }
- return dist[xy[0]][xy[1]];
- }
-
- /**
- * @brief
- * Normalize a distance to our evaluation interval.
- * @param v The value to normalize
- * @param max The maximum input limit value
- * @param out_min The minimum output limit value
- * @param out_max The maximum output limit value
- */
- private double normalize (double v, double max, double out_min, double out_max) {
- return (v / max) * (out_max - out_min) + out_min;
- }
-
- /**
- * @brief
- * Evaluate the current move.
- * The position evaluation is based on shortestMoveDist() results.
- * We measure the move distance of:
- * 1) Ghosts to current position, as a ghost path, which represent the minimum steps
- * needed by a ghost in order to reach the current position avoiding all obstacles
- * 2) Pacman to all the flags, which represent the minimum steps needed by pacman
- * in order to reach the flags avoiding all obstacles including the curving
- * of the plane in the borders
- * 3) Pacman to all the torus border squares, which represent the minimum steps needed
- * by pacman in order to reach the torus border squares. @ref Globals.TORUS_BORDERS
- *
- * We mix these values in order to produce the final evaluation value
- */
- private double evaluate ()
- {
- double eval = 0; // mixed evaluation value
- double ghostEval = 0, gd; // ghost evaluation value
- double flagEval = 0; // flag evaluation value
- double torusEval = 0; // torus evaluation value
- int[] ghostDist = new int[PacmanUtilities.numberOfGhosts]; // hold the ghost distances
- int[] flagDist = new int[PacmanUtilities.numberOfFlags]; // hold the flag distances
- int[] torusDist = new int[Globals.TORUS_BORDERS.length]; // hold the torus square distances
- int minGhostDist = Globals.DISTANCE_MAX+1; // hold the minimum ghost distance
- int averGhostDist = Globals.DISTANCE_MAX; // hold the average ghost distance
- int minFlagDist = Globals.DISTANCE_MAX+1; // hold the minimum flag distance
- int minTorusDist = Globals.DISTANCE_MAX+1; // hold the minimum torus square distance
- int i; // loop counter
-
- /*
- * === Ghost distances part ===
- */
- for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
- if ((ghostDist [i] = shortestMoveDist (curGhostPos[i], nodeXY, Creature.GHOST)) >= 1) {
- 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_AVER_FACTOR * averGhostDist;
- // normalize the ghostEval to our interval
- if (minGhostDist <= Globals.EVAL_GHOST_BOOST)
- ghostEval = normalize (gd, Globals.DISTANCE_MAX ,Globals.EVAL_MIN+Globals.EVAL_GHOSTBOOST_OFFSET, Globals.EVAL_MAX);
- else if (minGhostDist <= Globals.EVAL_GHOST_TERRITORY)
- ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX - Globals.EVAL_GHOSTTERRITORY_OFFSET);
- else
- ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
-
- /*
- * === Flag distances part ===
- */
- for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
- if (curFlagStatus[i] == false) {
- if ((flagDist[i] = shortestMoveDist (nodeXY, flagPos[i], Creature.PACMAN)) >= 0) {
- if (minFlagDist > flagDist[i])
- minFlagDist = flagDist[i];
- }
- }
- }
- // normalize the flagEval to our interval
- if (minFlagDist <= Globals.EVAL_FLAG_BOOST)
- flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX+Globals.EVAL_FLAGBOOST_OFFSET);
- else
- flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
-
- /*
- * === Torus borders distances part ===
- */
- for (i=0 ; i<Globals.TORUS_BORDERS.length ; ++i) {
- if ((torusDist[i] = shortestMoveDist (nodeXY, Globals.TORUS_BORDERS[i], Creature.PACMAN)) >= 0) {
- if (minTorusDist > torusDist[i])
- minTorusDist = torusDist[i];
- }
- }
- // normalize the flagEval to our interval
- torusEval = normalize (Globals.BORDERDIST_MAX-minTorusDist, Globals.BORDERDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
-
- /*
- * === Mix up the values and return the normalized value back to caller ===
- */
- eval = ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
- + flagEval * Globals.EVAL_FLAGDIST_FACTOR
- + torusEval * Globals.EVAL_TORUSDIST_FACTOR;
- return normalize (eval, Globals.EVAL_FINAL_MAX, Globals.EVAL_FINAL_MIN, Globals.EVAL_FINAL_MAX);
- }
-
- }
|