A java PacMan game application for A.U.TH (data structures class)
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

614 linhas
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. */
  216. public static int[] pacmanValidMove (Node89978445 node, int move) {
  217. return node.pacmanValidMove (node.nodeXY, move);
  218. }
  219. /*
  220. * ============= Node's private methods =============
  221. */
  222. /**
  223. * @brief
  224. * Loop entire maze and return an object that holds Ghost positions
  225. * @param none
  226. * @return Object with Ghost position
  227. * The first dimension holds the number of the ghost
  228. * The 2nd dimension holds the (x, y) coordinates of the ghost
  229. */
  230. private int[][] findGhosts ()
  231. {
  232. int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return
  233. int g = 0; // Ghost index
  234. boolean keepGoing = true; // Boundary check helper variable
  235. // Loop entire Maze (i, j)
  236. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  237. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  238. if (Maze[x][y].isGhost ()) {
  239. // In case of a Ghost save its position to return object
  240. ret[g][0] = x;
  241. ret[g][1] = y;
  242. // boundary check
  243. if (++g >= PacmanUtilities.numberOfGhosts)
  244. keepGoing = false;
  245. }
  246. }
  247. }
  248. return ret;
  249. }
  250. /**
  251. * @brief
  252. * Loop entire maze and return an object that holds flags positions
  253. * @param none
  254. * @return Object with flag positions
  255. * The first dimension holds the number of the flag
  256. * The 2nd dimension holds the (x, y) coordinates of the flag
  257. */
  258. private int[][] findFlags ()
  259. {
  260. int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return
  261. int f = 0; // Flag index
  262. boolean keepGoing = true; // Boundary check helper variable
  263. // Loop entire Maze (i, j)
  264. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  265. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  266. if (Maze[x][y].isFlag()) {
  267. // In case of a Ghost save its position to return object
  268. ret[f][0] = x;
  269. ret[f][1] = y;
  270. // boundary check
  271. if (++f >= PacmanUtilities.numberOfFlags)
  272. keepGoing = false;
  273. }
  274. }
  275. }
  276. return ret;
  277. }
  278. /**
  279. * @brief
  280. * Loop through flags and check their status
  281. * @return Object with flag status
  282. */
  283. private boolean[] checkFlags ()
  284. {
  285. boolean[] ret = new boolean [PacmanUtilities.numberOfFlags];
  286. int x, y;
  287. for (int i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  288. x = flagPos[i][0]; // Get row,col coordinates
  289. y = flagPos[i][1];
  290. ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
  291. }
  292. return ret;
  293. }
  294. /*
  295. * ============ private evaluation helper methods ==============
  296. */
  297. /**
  298. * Helper enumerator to make a function pointer like trick
  299. * inside @ref moveDist()
  300. */
  301. private enum Creature {
  302. GHOST, PACMAN
  303. }
  304. /**
  305. * @brief
  306. * Check if the requested ghost move is valid
  307. * @param ghost ghost coordinates to check the move validation
  308. * @param move the move to check
  309. * @return
  310. * @arg The resulting position in maze if the move is valid.
  311. * @arg If it is not valid return Globals.POSITION_FALSE
  312. */
  313. private int[] ghostValidMove (int[] ghost, int move)
  314. {
  315. int[] newPos = new int[2];
  316. // find hypothetical new position
  317. newPos[0] = ghost[0];
  318. newPos[1] = ghost[1];
  319. newPos[0] += (move == Room.SOUTH) ? 1:0;
  320. newPos[0] -= (move == Room.NORTH) ? 1:0;
  321. newPos[1] += (move == Room.EAST) ? 1:0;
  322. newPos[1] -= (move == Room.WEST) ? 1:0;
  323. // Valid filters
  324. if (!((newPos[0] >= 0 && newPos[0] < PacmanUtilities.numberOfRows) &&
  325. (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns)))
  326. return Globals.POSITION_FALSE;
  327. /*
  328. * else if (Maze[newPos[0]][newPos[1]].isFlag ())
  329. * return Globals.POSITION_FALSE;
  330. * @note
  331. * We comment out the flag part...
  332. * This incorrect behavior help evaluation routine to stop treat
  333. * flag position as asylum. In other words we remove the discontinuity
  334. * of shortestMoveDist() for ghost function.
  335. */
  336. else if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
  337. return Globals.POSITION_FALSE;
  338. else
  339. return newPos;
  340. }
  341. /**
  342. * @brief
  343. * Check if the requested pacman move is valid
  344. * @param pacman pacman coordinates to check the move validation
  345. * @param move the move to check
  346. * @return
  347. * @arg The resulting position in maze if the move is valid.
  348. * @arg If it is not valid return Globals.POSITION_FALSE
  349. */
  350. private int[] pacmanValidMove (int[] pacman, int move)
  351. {
  352. int[] newPos = new int[2];
  353. // find hypothetical new position
  354. newPos[0] = pacman[0];
  355. newPos[1] = pacman[1];
  356. newPos[0] += (move == Room.SOUTH) ? 1:0;
  357. newPos[0] -= (move == Room.NORTH) ? 1:0;
  358. newPos[1] += (move == Room.EAST) ? 1:0;
  359. newPos[1] -= (move == Room.WEST) ? 1:0;
  360. // Pacman curves Maze plane to a Torus
  361. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows-1;
  362. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  363. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns-1;
  364. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  365. // Valid filters
  366. if (Maze[pacman[0]][pacman[1]].walls[move] == 0)
  367. return Globals.POSITION_FALSE;
  368. else if (Maze[newPos[0]][newPos[1]].isGhost ())
  369. return Globals.POSITION_FALSE;
  370. else
  371. return newPos;
  372. }
  373. /**
  374. * @brief
  375. * A Breadth-first search to find the shortest path distance
  376. * from an origin point to another point in the maze as if
  377. * a creature could walk through origin to xy
  378. * @param origin origin's x, y (starting point)
  379. * @param xy destination's x, y
  380. * @param creature The type of creature for whom the path is for.
  381. * This helper variable let us decide which validation
  382. * function should call.
  383. * @return The number of steps from origin to xy
  384. * @note
  385. * As long as Java have not function pointers. I prefer this
  386. * as a work around over interfaces and lambdas.
  387. */
  388. private int shortestMoveDist (int[] origin, int[] xy, Creature creature)
  389. {
  390. int move; // move direction
  391. int steps, qStepItems; // distance and group counters
  392. boolean done = false; // algo ending flag
  393. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  394. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  395. // Queue to feed with possible position
  396. Queue2D q = new Queue2D (Globals.DISTANCE_MAX - 1);
  397. // 2D array holding all the distances from the origin to each square of the maze
  398. int[][] dist = new int [PacmanUtilities.numberOfRows][PacmanUtilities.numberOfColumns];
  399. /*
  400. * If target square is inside a box abort with max DISTANCE_FALSE
  401. * This is for speeding thing up, as the algorithm would return
  402. * the same thing anyway.
  403. */
  404. if (isInsideBox(xy))
  405. return Globals.DISTANCE_FALSE;
  406. /*
  407. * initialize data for algorithm
  408. */
  409. for (int i=0 ; i<PacmanUtilities.numberOfRows ; ++i)
  410. for (int j=0 ; j<PacmanUtilities.numberOfColumns ; ++j)
  411. dist[i][j] = Globals.DISTANCE_FALSE; // dist array starts with -1
  412. dist [origin[0]][origin[1]] = 0; //starting point is the origin position
  413. q.insert (origin); // feed first loop data
  414. qStepItems = 1; // init counters
  415. steps = 1;
  416. /*
  417. * Main loop of the algorithm.
  418. * For every position pulled from queue with the same "steps" value:
  419. * - check all the valid moves around current's position
  420. * - mark them with the current "steps" value in dist[][] array
  421. * - push them to queue
  422. * - count the pushed position with the same "steps" value in queue
  423. * If no other queued position with the current "steps" value remained
  424. * increase "steps" value and continue.
  425. */
  426. while (done == false) {
  427. if (qStepItems>0) { // Have we any item on the current group?
  428. xyItem = q.remove (); //load an item of the current group
  429. --qStepItems; // mark the removal of the item
  430. // loop every valid next position for the current position
  431. for (move=0 ; move<4 ; ++move) {
  432. switch (creature) {
  433. case GHOST: xyNext = ghostValidMove (xyItem, move); break;
  434. case PACMAN: xyNext = pacmanValidMove (xyItem, move); break;
  435. default: xyNext = Globals.POSITION_FALSE;
  436. /*
  437. * Function pointer - like behavior
  438. */
  439. }
  440. if (xyNext != Globals.POSITION_FALSE) {
  441. if (dist[xyNext[0]][xyNext[1]] == Globals.DISTANCE_FALSE) {
  442. // If we haven't been there
  443. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  444. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  445. /*
  446. * first match: The first time we reach destination we
  447. * have measured the distance. No need any other try
  448. */
  449. done = true;
  450. break;
  451. }
  452. else {
  453. // If we are not there yet, feed queue with another position
  454. q.insert (xyNext);
  455. }
  456. }
  457. }
  458. }
  459. }
  460. else {
  461. // We are done with group, mark how many are for the next one
  462. if ((qStepItems = q.size()) <= 0)
  463. done = true; // There is no path to destination, abort
  464. ++steps; // Update distance counter
  465. }
  466. }
  467. return dist[xy[0]][xy[1]];
  468. }
  469. /**
  470. * @brief
  471. * Normalize a distance to our evaluation interval.
  472. * @param v The value to normalize
  473. * @param max The maximum input limit value
  474. * @param out_min The minimum output limit value
  475. * @param out_max The maximum output limit value
  476. */
  477. private double normalize (double v, double max, double out_min, double out_max) {
  478. return (v / max) * (out_max - out_min) + out_min;
  479. }
  480. /**
  481. * @brief
  482. * Evaluate the current move.
  483. * The position evaluation is based on shortestMoveDist() results.
  484. * We measure the move distance of:
  485. * 1) Ghosts to current position, as a ghost path, which represent the minimum steps
  486. * needed by a ghost in order to reach the current position avoiding all obstacles
  487. * 2) Pacman to all the flags, which represent the minimum steps needed by pacman
  488. * in order to reach the flags avoiding all obstacles including the curving
  489. * of the plane in the borders
  490. * 3) Pacman to all the torus border squares, which represent the minimum steps needed
  491. * by pacman in order to reach the torus border squares. @ref Globals.TORUS_BORDERS
  492. *
  493. * We mix these values in order to produce the final evaluation value
  494. */
  495. private double evaluate ()
  496. {
  497. double eval = 0; // mixed evaluation value
  498. double ghostEval = 0, gd; // ghost evaluation value
  499. double flagEval = 0; // flag evaluation value
  500. double torusEval = 0; // torus evaluation value
  501. int[] ghostDist = new int[PacmanUtilities.numberOfGhosts]; // hold the ghost distances
  502. int[] flagDist = new int[PacmanUtilities.numberOfFlags]; // hold the flag distances
  503. int[] torusDist = new int[Globals.TORUS_BORDERS.length]; // hold the torus square distances
  504. int minGhostDist = Globals.DISTANCE_MAX+1; // hold the minimum ghost distance
  505. int averGhostDist = Globals.DISTANCE_MAX; // hold the average ghost distance
  506. int minFlagDist = Globals.DISTANCE_MAX+1; // hold the minimum flag distance
  507. int minTorusDist = Globals.DISTANCE_MAX+1; // hold the minimum torus square distance
  508. int i; // loop counter
  509. /*
  510. * === Ghost distances part ===
  511. */
  512. for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
  513. if ((ghostDist [i] = shortestMoveDist (curGhostPos[i], nodeXY, Creature.GHOST)) >= 1) {
  514. averGhostDist += ghostDist [i];
  515. if (minGhostDist > ghostDist[i])
  516. minGhostDist = ghostDist[i];
  517. }
  518. }
  519. averGhostDist /= PacmanUtilities.numberOfGhosts;
  520. // extract the ghostEval as a combination of the minimum distance and the average
  521. gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_AVER_FACTOR * averGhostDist;
  522. // normalize the ghostEval to our interval
  523. if (minGhostDist <= Globals.EVAL_GHOST_BOOST)
  524. ghostEval = normalize (gd, Globals.DISTANCE_MAX ,Globals.EVAL_MIN+Globals.EVAL_GHOSTBOOST_OFFSET, Globals.EVAL_MAX);
  525. else if (minGhostDist <= Globals.EVAL_GHOST_TERRITORY)
  526. ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX - Globals.EVAL_GHOSTTERRITORY_OFFSET);
  527. else
  528. ghostEval = normalize (gd, Globals.DISTANCE_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  529. /*
  530. * === Flag distances part ===
  531. */
  532. for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  533. if (curFlagStatus[i] == false) {
  534. if ((flagDist[i] = shortestMoveDist (nodeXY, flagPos[i], Creature.PACMAN)) >= 0) {
  535. if (minFlagDist > flagDist[i])
  536. minFlagDist = flagDist[i];
  537. }
  538. }
  539. }
  540. // normalize the flagEval to our interval
  541. if (minFlagDist <= Globals.EVAL_FLAG_BOOST)
  542. flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX+Globals.EVAL_FLAGBOOST_OFFSET);
  543. else
  544. flagEval = normalize ((double)Globals.FLAGDIST_MAX - minFlagDist, Globals.FLAGDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  545. /*
  546. * === Torus borders distances part ===
  547. */
  548. for (i=0 ; i<Globals.TORUS_BORDERS.length ; ++i) {
  549. if ((torusDist[i] = shortestMoveDist (nodeXY, Globals.TORUS_BORDERS[i], Creature.PACMAN)) >= 0) {
  550. if (minTorusDist > torusDist[i])
  551. minTorusDist = torusDist[i];
  552. }
  553. }
  554. // normalize the flagEval to our interval
  555. torusEval = normalize (Globals.BORDERDIST_MAX-minTorusDist, Globals.BORDERDIST_MAX, Globals.EVAL_MIN, Globals.EVAL_MAX);
  556. /*
  557. * === Mix up the values and return the normalized value back to caller ===
  558. */
  559. eval = ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
  560. + flagEval * Globals.EVAL_FLAGDIST_FACTOR
  561. + torusEval * Globals.EVAL_TORUSDIST_FACTOR;
  562. return normalize (eval, Globals.EVAL_FINAL_MAX, Globals.EVAL_FINAL_MIN, Globals.EVAL_FINAL_MAX);
  563. }
  564. }