A java PacMan game application for A.U.TH (data structures class)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

619 lines
23 KiB

  1. /**
  2. * @file Node89978445.java
  3. * @brief
  4. * File containing the Node class witch represents
  5. * the moves of Pacman
  6. *
  7. * @author Christos Choutouridis 8997 cchoutou@ece.auth.gr
  8. * @author Konstantina Tsechelidou 8445 konstsec@ece.auth.gr
  9. */
  10. package gr.auth.ee.dsproject.node;
  11. import java.util.ArrayList;
  12. import gr.auth.ee.dsproject.pacman.PacmanUtilities;
  13. import gr.auth.ee.dsproject.pacman.Room;
  14. /**
  15. * @class Node89978445
  16. * @brief
  17. * This class holds each move of the current round and it's evaluation
  18. *
  19. */
  20. public class Node89978445
  21. {
  22. double nodeEvaluation; /**
  23. * Pacman's current move evaluation as return value of evaluation ()
  24. * This is used also as the "return status" of the object
  25. * @note
  26. * The variable is initialized to Globals.NO_EVAL and calculated only
  27. * after the getEvaluation () call
  28. */
  29. int[] nodeXY; // Pacman's current x,y coordinate
  30. int[][] curGhostPos; // Array holding the Ghost (x,y) pairs
  31. int[][] flagPos; // Array holding the flag (x,y) pairs
  32. boolean[] curFlagStatus; // Array holding the flag capture status
  33. Room[][] Maze; // copy of the current Maze
  34. /*
  35. * Tree navigation references. These variables are handled by
  36. * PacTree
  37. */
  38. Node89978445 parent; // reference to parent node in search tree
  39. ArrayList<Node89978445> children; // references to children nodes in search tree
  40. int depth; // the depth of the node in search tree
  41. /*
  42. * ============ Constructors ==============
  43. */
  44. /**
  45. * @brief
  46. * The simple constructor. Just initialize the data
  47. * @note
  48. * Using this constructor means that the user MUST call setMaze()
  49. * and setPosition() manually after the creation of the Node89978445 object
  50. */
  51. public Node89978445 ()
  52. {
  53. this.Maze = null; // Fill members
  54. nodeXY = Globals.POSITION_FALSE;
  55. nodeEvaluation = Globals.NO_EVAL;
  56. parent = null;
  57. // allocate objects
  58. curGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  59. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  60. curFlagStatus = new boolean[PacmanUtilities.numberOfFlags];
  61. children = new ArrayList<Node89978445> ();
  62. }
  63. /**
  64. * @brief
  65. * Constructor for the Node89978445
  66. * @param Maze The current maze object
  67. * @param curXY The current pacman's (x, y) position
  68. */
  69. public Node89978445 (Room[][] Maze, int[] curXY)
  70. {
  71. this.Maze = Maze; // Fill members
  72. nodeXY = curXY;
  73. nodeEvaluation = Globals.NO_EVAL;
  74. parent = null;
  75. // allocate objects
  76. curGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  77. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  78. curFlagStatus = new boolean[PacmanUtilities.numberOfFlags];
  79. children = new ArrayList<Node89978445> ();
  80. }
  81. /*
  82. * ============== Setters ===============
  83. */
  84. /**
  85. * @brief
  86. * SetMaze (Room) to Node object
  87. * @param maze The room to set
  88. */
  89. public void setMaze (Room[][] maze) {
  90. this.Maze = maze;
  91. }
  92. /**
  93. * @brief
  94. * Set pacman's position
  95. * @param curXY Pacman's current X, Y position
  96. */
  97. public void setPosition (int[] curXY) {
  98. nodeXY = curXY;
  99. }
  100. /*
  101. * ============== getters =================
  102. */
  103. /**
  104. * @brief
  105. * If not done calls the evaluation algorithm and returns the result
  106. * @return The evaluation result (also stored inside object)
  107. * @note
  108. * We assume the current position was a valid position.
  109. * This routine is not called upon creation of the class
  110. */
  111. public double getEvaluation ()
  112. {
  113. // If already evaluated do not re-evaluate the move
  114. if (nodeEvaluation == Globals.NO_EVAL) {
  115. // Safety filters
  116. if (Maze == null)
  117. return Globals.NO_EVAL;
  118. if (nodeXY == Globals.POSITION_FALSE)
  119. return Globals.NO_EVAL;
  120. // calculate helper arrays
  121. curGhostPos = findGhosts ();
  122. flagPos = findFlags ();
  123. curFlagStatus = checkFlags ();
  124. return nodeEvaluation = evaluate ();
  125. }
  126. else
  127. return nodeEvaluation;
  128. }
  129. /**
  130. * @return Nodes position in maze
  131. */
  132. public int[] getCurrentPacmanPos () {
  133. return nodeXY;
  134. }
  135. /**
  136. * @return Current ghost position ina maze
  137. */
  138. public int[][] getCurrentGhostPos () {
  139. return curGhostPos;
  140. }
  141. /**
  142. * @return The depth of the node tin the min-max tree
  143. */
  144. public int getDepth () {
  145. return depth;
  146. }
  147. /*
  148. * ============= Helper API ============
  149. */
  150. /**
  151. * @brief
  152. * Convert back a maze position to Room's direction format
  153. * @param nextPos The intention (x, y) coordinates
  154. * @param curPos The current coordinates
  155. * @return The direction
  156. * @arg Room.NORTH (x decreases)
  157. * @arg Room.SOUTH (x increases)
  158. * @arg Room.WEST (y decreases)
  159. * @arg Room.EAST (y increases)
  160. */
  161. public static int moveConv (int[] nextPos, int[] curPos)
  162. {
  163. int dx = nextPos[0] - curPos[0];
  164. int dy = nextPos[1] - curPos[1];
  165. if (dx == -1 || dx == PacmanUtilities.numberOfRows-1)
  166. return Room.NORTH;
  167. else if (dx == 1 || dx == -PacmanUtilities.numberOfRows+1)
  168. return Room.SOUTH;
  169. else if (dy == -1 || dy == PacmanUtilities.numberOfColumns-1)
  170. return Room.WEST;
  171. else if (dy == 1 || dy == -PacmanUtilities.numberOfColumns+1)
  172. return Room.EAST;
  173. else
  174. return Globals.INVALID_MOVE;
  175. }
  176. /**
  177. * @param creature creature's (x,y)
  178. * @return
  179. * Return true if the creature is inside the maze boxes
  180. */
  181. public static boolean isInsideBox (int[] creature)
  182. {
  183. boolean ret = false; //have faith
  184. for (int i=0 ; i<4 ; ++i) {
  185. if ((creature[0]>=Globals.BOXES[i][0][0] && creature[0]<=Globals.BOXES[i][1][0]) &&
  186. (creature[1]>=Globals.BOXES[i][0][1] && creature[1]<=Globals.BOXES[i][1][1]))
  187. ret = true;
  188. }
  189. return ret;
  190. }
  191. /**
  192. * @brief
  193. * Torus borders are the squares in the outer limits of the maze where
  194. * there are no walls. Pacman's gravity can curve the maze plane to
  195. * a torus through these squares. This function check if a position is
  196. * a torus border position
  197. * @param creature creature's (x,y)
  198. * @return
  199. * Return true if the creature is in a border position.
  200. */
  201. public static boolean isTorusSquare (int[] creature)
  202. {
  203. for (int i=0 ; i<Globals.TORUS_BORDERS.length ; ++i) {
  204. if (creature[0] == Globals.TORUS_BORDERS[i][0] &&
  205. creature[1] == Globals.TORUS_BORDERS[i][1]) {
  206. return true;
  207. }
  208. }
  209. return false;
  210. }
  211. /**
  212. * @brief
  213. * Static version of move validation to use outside of the class
  214. * Check if the requested move for a node is valid
  215. * @param node The node of interest
  216. * @param move The move to check
  217. * @return The hypothetical new position of pacman
  218. * @arg The resulting position in maze if the move is valid.
  219. * @arg If it is not valid return Globals.POSITION_FALSE
  220. */
  221. public static int[] pacmanValidMove (Node89978445 node, int move) {
  222. return node.pacmanValidMove (node.nodeXY, move);
  223. }
  224. /*
  225. * ============= Node's private methods =============
  226. */
  227. /**
  228. * @brief
  229. * Loop entire maze and return an object that holds Ghost positions
  230. * @param none
  231. * @return Object with Ghost position
  232. * The first dimension holds the number of the ghost
  233. * The 2nd dimension holds the (x, y) coordinates of the ghost
  234. */
  235. private int[][] findGhosts ()
  236. {
  237. int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return
  238. int g = 0; // Ghost index
  239. boolean keepGoing = true; // Boundary check helper variable
  240. // Loop entire Maze (i, j)
  241. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  242. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  243. if (Maze[x][y].isGhost ()) {
  244. // In case of a Ghost save its position to return object
  245. ret[g][0] = x;
  246. ret[g][1] = y;
  247. // boundary check
  248. if (++g >= PacmanUtilities.numberOfGhosts)
  249. keepGoing = false;
  250. }
  251. }
  252. }
  253. return ret;
  254. }
  255. /**
  256. * @brief
  257. * Loop entire maze and return an object that holds flags positions
  258. * @param none
  259. * @return Object with flag positions
  260. * The first dimension holds the number of the flag
  261. * The 2nd dimension holds the (x, y) coordinates of the flag
  262. */
  263. private int[][] findFlags ()
  264. {
  265. int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return
  266. int f = 0; // Flag index
  267. boolean keepGoing = true; // Boundary check helper variable
  268. // Loop entire Maze (i, j)
  269. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  270. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  271. if (Maze[x][y].isFlag()) {
  272. // In case of a Ghost save its position to return object
  273. ret[f][0] = x;
  274. ret[f][1] = y;
  275. // boundary check
  276. if (++f >= PacmanUtilities.numberOfFlags)
  277. keepGoing = false;
  278. }
  279. }
  280. }
  281. return ret;
  282. }
  283. /**
  284. * @brief
  285. * Loop through flags and check their status
  286. * @return Object with flag status
  287. */
  288. private boolean[] checkFlags ()
  289. {
  290. boolean[] ret = new boolean [PacmanUtilities.numberOfFlags];
  291. int x, y;
  292. for (int i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  293. x = flagPos[i][0]; // Get row,col coordinates
  294. y = flagPos[i][1];
  295. ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
  296. }
  297. return ret;
  298. }
  299. /*
  300. * ============ private evaluation helper methods ==============
  301. */
  302. /**
  303. * Helper enumerator to make a function pointer like trick
  304. * inside @ref moveDist()
  305. */
  306. private enum Creature {
  307. GHOST, PACMAN
  308. }
  309. /**
  310. * @brief
  311. * Check if the requested ghost move is valid
  312. * @param ghost ghost coordinates to check the move validation
  313. * @param move the move to check
  314. * @return
  315. * @arg The resulting position in maze if the move is valid.
  316. * @arg If it is not valid return Globals.POSITION_FALSE
  317. */
  318. private int[] ghostValidMove (int[] ghost, int move)
  319. {
  320. int[] newPos = new int[2];
  321. // find hypothetical new position
  322. newPos[0] = ghost[0];
  323. newPos[1] = ghost[1];
  324. newPos[0] += (move == Room.SOUTH) ? 1:0;
  325. newPos[0] -= (move == Room.NORTH) ? 1:0;
  326. newPos[1] += (move == Room.EAST) ? 1:0;
  327. newPos[1] -= (move == Room.WEST) ? 1:0;
  328. // Valid filters
  329. if (!((newPos[0] >= 0 && newPos[0] < PacmanUtilities.numberOfRows) &&
  330. (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns)))
  331. return Globals.POSITION_FALSE;
  332. /*
  333. * else if (Maze[newPos[0]][newPos[1]].isFlag ())
  334. * return Globals.POSITION_FALSE;
  335. * @note
  336. * We comment out the flag part...
  337. * This incorrect behavior help evaluation routine to stop treat
  338. * flag position as asylum. In other words we remove the discontinuity
  339. * of shortestMoveDist() for ghost function.
  340. */
  341. else if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
  342. return Globals.POSITION_FALSE;
  343. else
  344. return newPos;
  345. }
  346. /**
  347. * @brief
  348. * Check if the requested pacman move is valid
  349. * @param pacman pacman coordinates to check the move validation
  350. * @param move the move to check
  351. * @return
  352. * @arg The resulting position in maze if the move is valid.
  353. * @arg If it is not valid return Globals.POSITION_FALSE
  354. */
  355. private int[] pacmanValidMove (int[] pacman, int move)
  356. {
  357. int[] newPos = new int[2];
  358. // find hypothetical new position
  359. newPos[0] = pacman[0];
  360. newPos[1] = pacman[1];
  361. newPos[0] += (move == Room.SOUTH) ? 1:0;
  362. newPos[0] -= (move == Room.NORTH) ? 1:0;
  363. newPos[1] += (move == Room.EAST) ? 1:0;
  364. newPos[1] -= (move == Room.WEST) ? 1:0;
  365. // Pacman curves Maze plane to a Torus
  366. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows-1;
  367. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  368. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns-1;
  369. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  370. // Valid filters
  371. if (Maze[pacman[0]][pacman[1]].walls[move] == 0)
  372. return Globals.POSITION_FALSE;
  373. else if (Maze[newPos[0]][newPos[1]].isGhost ())
  374. return Globals.POSITION_FALSE;
  375. else
  376. return newPos;
  377. }
  378. /**
  379. * @brief
  380. * A Breadth-first search to find the shortest path distance
  381. * from an origin point to another point in the maze as if
  382. * a creature could walk through origin to xy
  383. * @param origin origin's x, y (starting point)
  384. * @param xy destination's x, y
  385. * @param creature The type of creature for whom the path is for.
  386. * This helper variable let us decide which validation
  387. * function should call.
  388. * @return The number of steps from origin to xy
  389. * @note
  390. * As long as Java have not function pointers. I prefer this
  391. * as a work around over interfaces and lambdas.
  392. */
  393. private int shortestMoveDist (int[] origin, int[] xy, Creature creature)
  394. {
  395. int move; // move direction
  396. int steps, qStepItems; // distance and group counters
  397. boolean done = false; // algo ending flag
  398. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  399. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  400. // Queue to feed with possible position
  401. Queue2D q = new Queue2D (Globals.DISTANCE_MAX - 1);
  402. // 2D array holding all the distances from the origin to each square of the maze
  403. int[][] dist = new int [PacmanUtilities.numberOfRows][PacmanUtilities.numberOfColumns];
  404. /*
  405. * If target square is inside a box abort with max DISTANCE_FALSE
  406. * This is for speeding thing up, as the algorithm would return
  407. * the same thing anyway.
  408. */
  409. if (isInsideBox(xy))
  410. return Globals.DISTANCE_FALSE;
  411. /*
  412. * initialize data for algorithm
  413. */
  414. for (int i=0 ; i<PacmanUtilities.numberOfRows ; ++i)
  415. for (int j=0 ; j<PacmanUtilities.numberOfColumns ; ++j)
  416. dist[i][j] = Globals.DISTANCE_FALSE; // dist array starts with -1
  417. dist [origin[0]][origin[1]] = 0; //starting point is the origin position
  418. q.insert (origin); // feed first loop data
  419. qStepItems = 1; // init counters
  420. steps = 1;
  421. /*
  422. * Main loop of the algorithm.
  423. * For every position pulled from queue with the same "steps" value:
  424. * - check all the valid moves around current's position
  425. * - mark them with the current "steps" value in dist[][] array
  426. * - push them to queue
  427. * - count the pushed position with the same "steps" value in queue
  428. * If no other queued position with the current "steps" value remained
  429. * increase "steps" value and continue.
  430. */
  431. while (done == false) {
  432. if (qStepItems>0) { // Have we any item on the current group?
  433. xyItem = q.remove (); //load an item of the current group
  434. --qStepItems; // mark the removal of the item
  435. // loop every valid next position for the current position
  436. for (move=0 ; move<4 ; ++move) {
  437. switch (creature) {
  438. case GHOST: xyNext = ghostValidMove (xyItem, move); break;
  439. case PACMAN: xyNext = pacmanValidMove (xyItem, move); break;
  440. default: xyNext = Globals.POSITION_FALSE;
  441. /*
  442. * Function pointer - like behavior
  443. */
  444. }
  445. if (xyNext != Globals.POSITION_FALSE) {
  446. if (dist[xyNext[0]][xyNext[1]] == Globals.DISTANCE_FALSE) {
  447. // If we haven't been there
  448. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  449. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  450. /*
  451. * first match: The first time we reach destination we
  452. * have measured the distance. No need any other try
  453. */
  454. done = true;
  455. break;
  456. }
  457. else {
  458. // If we are not there yet, feed queue with another position
  459. q.insert (xyNext);
  460. }
  461. }
  462. }
  463. }
  464. }
  465. else {
  466. // We are done with group, mark how many are for the next one
  467. if ((qStepItems = q.size()) <= 0)
  468. done = true; // There is no path to destination, abort
  469. ++steps; // Update distance counter
  470. }
  471. }
  472. return dist[xy[0]][xy[1]];
  473. }
  474. /**
  475. * @brief
  476. * Normalize a distance to our evaluation interval.
  477. * @param v The value to normalize
  478. * @param max The maximum input limit value
  479. * @param out_min The minimum output limit value
  480. * @param out_max The maximum output limit value
  481. */
  482. private double normalize (double v, double max, double out_min, double out_max) {
  483. return (v / max) * (out_max - out_min) + out_min;
  484. }
  485. /**
  486. * @brief
  487. * Evaluate the current move.
  488. * The position evaluation is based on shortestMoveDist() results.
  489. * We measure the move distance of:
  490. * 1) Ghosts to current position, as a ghost path, which represent the minimum steps
  491. * needed by a ghost in order to reach the current position avoiding all obstacles
  492. * 2) Pacman to all the flags, which represent the minimum steps needed by pacman
  493. * in order to reach the flags avoiding all obstacles including the curving
  494. * of the plane in the borders
  495. * 3) Pacman to all the torus border squares, which represent the minimum steps needed
  496. * by pacman in order to reach the torus border squares. @ref Globals.TORUS_BORDERS
  497. *
  498. * We mix these values in order to produce the final evaluation value
  499. */
  500. private double evaluate ()
  501. {
  502. double eval = 0; // mixed evaluation value
  503. double ghostEval = 0, gd; // ghost evaluation value
  504. double flagEval = 0; // flag evaluation value
  505. double torusEval = 0; // torus evaluation value
  506. int[] ghostDist = new int[PacmanUtilities.numberOfGhosts]; // hold the ghost distances
  507. int[] flagDist = new int[PacmanUtilities.numberOfFlags]; // hold the flag distances
  508. int[] torusDist = new int[Globals.TORUS_BORDERS.length]; // hold the torus square distances
  509. int minGhostDist = Globals.DISTANCE_MAX+1; // hold the minimum ghost distance
  510. int averGhostDist = Globals.DISTANCE_MAX; // hold the average ghost distance
  511. int minFlagDist = Globals.DISTANCE_MAX+1; // hold the minimum flag distance
  512. int minTorusDist = Globals.DISTANCE_MAX+1; // hold the minimum torus square distance
  513. int i; // loop counter
  514. /*
  515. * === Ghost distances part ===
  516. */
  517. for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
  518. if ((ghostDist [i] = shortestMoveDist (curGhostPos[i], nodeXY, Creature.GHOST)) >= 1) {
  519. averGhostDist += ghostDist [i];
  520. if (minGhostDist > ghostDist[i])
  521. minGhostDist = ghostDist[i];
  522. }
  523. }
  524. averGhostDist /= PacmanUtilities.numberOfGhosts;
  525. // extract the ghostEval as a combination of the minimum distance and the average
  526. gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_AVER_FACTOR * averGhostDist;
  527. // normalize the ghostEval to our interval
  528. if (minGhostDist <= Globals.EVAL_GHOST_BOOST)
  529. ghostEval = normalize (gd, Globals.DISTANCE_MAX ,Globals.EVAL_MIN+Globals.EVAL_GHOSTBOOST_OFFSET, Globals.EVAL_MAX);
  530. else if (minGhostDist <= Globals.EVAL_GHOST_TERRITORY)
  531. ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX - Globals.EVAL_GHOSTTERRITORY_OFFSET);
  532. else
  533. ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  534. /*
  535. * === Flag distances part ===
  536. */
  537. for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  538. if (curFlagStatus[i] == false) {
  539. if ((flagDist[i] = shortestMoveDist (nodeXY, flagPos[i], Creature.PACMAN)) >= 0) {
  540. if (minFlagDist > flagDist[i])
  541. minFlagDist = flagDist[i];
  542. }
  543. }
  544. }
  545. // normalize the flagEval to our interval
  546. if (minFlagDist <= Globals.EVAL_FLAG_BOOST)
  547. flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX+Globals.EVAL_FLAGBOOST_OFFSET);
  548. else
  549. flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  550. /*
  551. * === Torus borders distances part ===
  552. */
  553. for (i=0 ; i<Globals.TORUS_BORDERS.length ; ++i) {
  554. if ((torusDist[i] = shortestMoveDist (nodeXY, Globals.TORUS_BORDERS[i], Creature.PACMAN)) >= 0) {
  555. if (minTorusDist > torusDist[i])
  556. minTorusDist = torusDist[i];
  557. }
  558. }
  559. // normalize the flagEval to our interval
  560. torusEval = normalize (Globals.BORDERDIST_MAX-minTorusDist, Globals.BORDERDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  561. /*
  562. * === Mix up the values and return the normalized value back to caller ===
  563. */
  564. eval = ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
  565. + flagEval * Globals.EVAL_FLAGDIST_FACTOR
  566. + torusEval * Globals.EVAL_TORUSDIST_FACTOR;
  567. return normalize (eval, Globals.EVAL_FINAL_MAX, Globals.EVAL_FINAL_MIN, Globals.EVAL_FINAL_MAX);
  568. }
  569. }