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.

547 lines
19 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 java.awt.image.PackedColorModel;
  13. import gr.auth.ee.dsproject.pacman.PacmanUtilities;
  14. import gr.auth.ee.dsproject.pacman.Room;
  15. /**
  16. * @class Node89978445
  17. * @brief
  18. * This class holds each move of the current round and it's evaluation
  19. *
  20. */
  21. public class Node89978445
  22. {
  23. double nodeEvaluation; /**<
  24. * Pacman's current move evaluation
  25. * This is used also as the "return status" of the object
  26. */
  27. int[] nodeXY; // Pacman's current x,y coordinate
  28. int[][] currentGhostPos; // Array holding the Ghost (x,y) pairs
  29. int[][] flagPos; // Array holding the flag (x,y) pairs
  30. boolean[] currentFlagStatus; // Array holding the flag capture status
  31. Room[][] Maze; // copy of the current Maze
  32. /*
  33. * Tree navigation references. These variables are handled by
  34. * PacTree
  35. */
  36. Node89978445 parent;
  37. ArrayList<Node89978445> children;
  38. int depth;
  39. /*
  40. * ============ Constructors ==============
  41. */
  42. /**
  43. * @brief
  44. * The simple constructor. Just initialize the data
  45. * @note
  46. * Using this constructor means that the user MUST call setMaze(), setPosition()
  47. * and setMove manually after the creation of the Node89978445 object
  48. */
  49. public Node89978445 ()
  50. {
  51. // Fill members
  52. this.Maze = null;
  53. nodeXY = Globals.FALSE_POS;
  54. nodeEvaluation = Globals.NO_EVAL;
  55. parent = null;
  56. // allocate objects
  57. currentGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  58. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  59. currentFlagStatus = new boolean[PacmanUtilities.numberOfFlags];
  60. children = new ArrayList<Node89978445> ();
  61. }
  62. /**
  63. * @brief
  64. * Constructor for the Node89978445
  65. * @param Maze The current maze object
  66. * @param curXY The current pacman's (x, y) position
  67. */
  68. public Node89978445 (Room[][] Maze, int[] curXY)
  69. {
  70. this.Maze = Maze; // Fill members
  71. nodeXY = curXY;
  72. nodeEvaluation = Globals.NO_EVAL;
  73. parent = null;
  74. // allocate objects
  75. currentGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  76. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  77. currentFlagStatus = new boolean[PacmanUtilities.numberOfFlags];
  78. children = new ArrayList<Node89978445> ();
  79. }
  80. /*
  81. * ============== Setters ===============
  82. */
  83. /**
  84. * @brief SetMaze (Room) to Node object
  85. * @param maze The room to set
  86. */
  87. public void setMaze (Room[][] maze) {
  88. this.Maze = maze;
  89. }
  90. /**
  91. * @brief Set pacman's position
  92. * @param curXY Pacman's current X, Y position
  93. */
  94. public void setPosition (int[] curXY) {
  95. nodeXY = curXY;
  96. }
  97. /*
  98. * ============== getters =================
  99. */
  100. /**
  101. * @brief If not done runs the evaluation algorithm and returns the result
  102. * @note We assume the current position was a valid position
  103. * @return The evaluation result
  104. */
  105. public double getEvaluation ()
  106. {
  107. // If already evaluated do not re-evaluate the move
  108. if (nodeEvaluation == Globals.NO_EVAL) {
  109. // calculate helper arrays
  110. currentGhostPos = findGhosts ();
  111. flagPos = findFlags ();
  112. currentFlagStatus = checkFlags ();
  113. return nodeEvaluation = evaluate ();
  114. }
  115. else
  116. return nodeEvaluation;
  117. }
  118. public int[] getCurentPacmanPos () {
  119. return nodeXY;
  120. }
  121. public int[][] getCurrentGhostPos () {
  122. return currentGhostPos;
  123. }
  124. /*
  125. * ============= Helper API ============
  126. */
  127. /**
  128. * @param creature creature's (x,y)
  129. * @return
  130. * Return true if the creature is inside the maze boxes
  131. */
  132. public static boolean isInsideBox (int[] creature)
  133. {
  134. boolean ret = false; //have faith
  135. for (int i=0 ; i<4 ; ++i) {
  136. if ((creature[0]>=Globals.BOXES[i][0][0] && creature[0]<=Globals.BOXES[i][1][0]) &&
  137. (creature[1]>=Globals.BOXES[i][0][1] && creature[1]<=Globals.BOXES[i][1][1]))
  138. ret = true;
  139. }
  140. return ret;
  141. }
  142. /**
  143. * @brief
  144. * Static version of move validation
  145. * Check if the requested move for a node is valid
  146. */
  147. public static int[] pacmanValidMove (Node89978445 node, int move)
  148. {
  149. int[] newPos = new int[2];
  150. // find hypothetical new position
  151. newPos[0] = node.nodeXY[0];
  152. newPos[1] = node.nodeXY[1];
  153. newPos[0] += (move == Room.SOUTH) ? 1:0;
  154. newPos[0] -= (move == Room.NORTH) ? 1:0;
  155. newPos[1] += (move == Room.EAST) ? 1:0;
  156. newPos[1] -= (move == Room.WEST) ? 1:0;
  157. // Pacman curves Maze plane to a Torus
  158. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  159. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  160. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  161. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  162. // Valid filters
  163. if (!isInsideBox(node.nodeXY) &&
  164. (node.Maze[node.nodeXY[0]][node.nodeXY[1]].walls[move] == 0))
  165. return Globals.FALSE_POS;
  166. return newPos;
  167. }
  168. /*
  169. * ============= Node's private methods =============
  170. */
  171. /**
  172. * @brief
  173. * Loop entire maze and return an object that holds Ghost positions
  174. * @param none
  175. * @return Object with Ghost position
  176. * The first dimension holds the number of the ghost
  177. * The 2nd dimension holds the (x, y) coordinates of the ghost
  178. */
  179. private int[][] findGhosts ()
  180. {
  181. int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return
  182. int g = 0; // Ghost index
  183. boolean keepGoing = true; // Boundary check helper variable
  184. // Loop entire Maze (i, j)
  185. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  186. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  187. if (Maze[x][y].isGhost ()) {
  188. // In case of a Ghost save its position to return object
  189. ret[g][0] = x;
  190. ret[g][1] = y;
  191. // boundary check
  192. if (++g > PacmanUtilities.numberOfGhosts)
  193. keepGoing = false;
  194. }
  195. }
  196. }
  197. return ret;
  198. }
  199. /**
  200. * @brief
  201. * Loop entire maze and return an object that holds flags positions
  202. * @param none
  203. * @return Object with flag positions
  204. * The first dimension holds the number of the flag
  205. * The 2nd dimension holds the (x, y) coordinates of the flag
  206. */
  207. private int[][] findFlags ()
  208. {
  209. int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return
  210. int g = 0; // Flag index
  211. boolean keepGoing = true; // Boundary check helper variable
  212. // Loop entire Maze (i, j)
  213. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  214. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  215. if (Maze[x][y].isFlag()) {
  216. // In case of a Ghost save its position to return object
  217. ret[g][0] = x;
  218. ret[g][1] = y;
  219. // boundary check
  220. if (++g > PacmanUtilities.numberOfFlags)
  221. keepGoing = false;
  222. }
  223. }
  224. }
  225. return ret;
  226. }
  227. /**
  228. * @brief
  229. * Loop through flags and check their status
  230. * @param none
  231. * @return Object with flag status
  232. */
  233. private boolean[] checkFlags ()
  234. {
  235. boolean[] ret = new boolean [PacmanUtilities.numberOfFlags];
  236. int x, y;
  237. for (int i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  238. x = flagPos[i][0]; // Get row,col coordinates
  239. y = flagPos[i][1];
  240. ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
  241. }
  242. return ret;
  243. }
  244. /*
  245. * ============ evaluation helper methods ==============
  246. */
  247. private int[] ghostValidMove (int[] ghost, int move)
  248. {
  249. int[] newPos = new int[2];
  250. // find hypothetical new position
  251. newPos[0] = ghost[0];
  252. newPos[1] = ghost[1];
  253. newPos[0] += (move == Room.SOUTH) ? 1:0;
  254. newPos[0] -= (move == Room.NORTH) ? 1:0;
  255. newPos[1] += (move == Room.EAST) ? 1:0;
  256. newPos[1] -= (move == Room.WEST) ? 1:0;
  257. // Valid filters
  258. if (!((newPos[0] >= 0 && newPos[0] < PacmanUtilities.numberOfRows) &&
  259. (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns)))
  260. return Globals.FALSE_POS;
  261. if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
  262. return Globals.FALSE_POS;
  263. return newPos;
  264. }
  265. /**
  266. * @brief
  267. * Check if the requested move is valid
  268. */
  269. private int[] pacmanValidMove (int[] pacman, int move)
  270. {
  271. int[] newPos = new int[2];
  272. // find hypothetical new position
  273. newPos[0] = pacman[0];
  274. newPos[1] = pacman[1];
  275. newPos[0] += (move == Room.SOUTH) ? 1:0;
  276. newPos[0] -= (move == Room.NORTH) ? 1:0;
  277. newPos[1] += (move == Room.EAST) ? 1:0;
  278. newPos[1] -= (move == Room.WEST) ? 1:0;
  279. // Pacman curves Maze plane to a Torus
  280. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  281. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  282. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  283. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  284. // Valid filters
  285. if (!isInsideBox(pacman) && (Maze[pacman[0]][pacman[1]].walls[move] == 0))
  286. return Globals.FALSE_POS;
  287. return newPos;
  288. }
  289. /**
  290. * @brief
  291. * A Breadth-first search to find the shortest path distance
  292. * from a ghost to a point in the maze
  293. * @param ghost Ghost (x, y)
  294. * @param xy The x, y coordinate in Maze
  295. * @return
  296. */
  297. private int ghostMoveDist (int[] ghost, int[] xy)
  298. {
  299. int move;
  300. int steps, qStepItems; // distance and group counters
  301. boolean done = false; // algo ending flag
  302. int r = PacmanUtilities.numberOfRows; // helper for shorting names
  303. int c = PacmanUtilities.numberOfColumns; // helper for shorting names
  304. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  305. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  306. Queue2D q = new Queue2D (r+c - 1); // Queue to feed with possible position
  307. int [][] dist = new int [r][c];
  308. /*<
  309. * 2D array holding all the distances from the ghost to each square of the maze
  310. */
  311. // If target square is inside a box abort with max distance
  312. if (isInsideBox(xy))
  313. return Globals.MAX_DISTANCE;
  314. /*
  315. * init data for algorithm
  316. */
  317. for (int i=0 ; i<r ; ++i) {
  318. for (int j=0 ; j<c ; ++j) {
  319. dist[i][j] = -1;
  320. // dist array starts with -1
  321. }
  322. }
  323. // loop data
  324. dist [ghost[0]][ghost[1]] = 0; //starting point is the ghost position
  325. q.insert (ghost); // feed first loop data
  326. qStepItems = 1; // init counters
  327. steps = 1;
  328. /*
  329. * Main loop of the algorithm
  330. *
  331. * For every position we check the valid moves, we apply to them
  332. * the step number and we push them to queue. We group the items in
  333. * queue with their step number by measuring how many we push them for
  334. * the current steps variable value.
  335. */
  336. while (done == false) {
  337. if (qStepItems>0) { // Have we any item on the current group?
  338. xyItem = q.remove (); //load an item of the current group
  339. --qStepItems; // mark the removal of the item
  340. for (move=0 ; move<4 ; ++move) {
  341. // loop every valid next position for the current position
  342. if ((xyNext = ghostValidMove (xyItem, move)) != Globals.FALSE_POS) {
  343. if (dist[xyNext[0]][xyNext[1]] == -1) {
  344. // If we haven't been there
  345. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  346. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  347. /*
  348. * first match:
  349. * The first time we reach destination we have count the
  350. * distance. No need any other try
  351. */
  352. done = true;
  353. break;
  354. }
  355. else {
  356. // If we are not there yet, feed queue with another position
  357. q.insert (xyNext);
  358. }
  359. }
  360. }
  361. }
  362. }
  363. else {
  364. // We are done with group, mark how many are for the next one
  365. if ((qStepItems = q.size()) <= 0)
  366. return dist[xy[0]][xy[1]]; // fail safe return
  367. ++steps; // Update distance counter
  368. }
  369. }
  370. return dist[xy[0]][xy[1]];
  371. }
  372. /**
  373. * @brief
  374. * A Breadth-first search to find the shortest path distance
  375. * from the pacman to a point in the maze. The point will normaly
  376. * represent a flag
  377. * @param pacman Pacman's (x, y)
  378. * @param xy The x, y coordinate in Maze
  379. * @return
  380. */
  381. private int pacmanMoveDist (int[] pacman, int[] xy)
  382. {
  383. int move;
  384. int steps, qStepItems; // distance and group counters
  385. boolean done = false; // algo ending flag
  386. int r = PacmanUtilities.numberOfRows; // helper for shorting names
  387. int c = PacmanUtilities.numberOfColumns; // helper for shorting names
  388. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  389. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  390. Queue2D q = new Queue2D (r+c - 1); // Queue to feed with possible position
  391. int [][] dist = new int [r][c];
  392. /*
  393. * 2D array holding all the distances from pacman to each square of the maze
  394. */
  395. // If target square is inside a box abort with max distance
  396. if (isInsideBox (xy))
  397. return Globals.MAX_DISTANCE;
  398. /*
  399. * init data for algorithm
  400. */
  401. for (int i=0 ; i<r ; ++i) {
  402. for (int j=0 ; j<c ; ++j) {
  403. dist[i][j] = -1;
  404. // dist array starts with -1
  405. }
  406. }
  407. // loop data
  408. dist [pacman[0]][pacman[1]] = 0; //starting point is the pacman position
  409. q.insert (pacman); // feed first loop data
  410. qStepItems = 1; // init counters
  411. steps = 1;
  412. /*
  413. * Main loop of the algorithm
  414. *
  415. * For every position we check the valid moves, we apply to them
  416. * the step number and we push them to queue. We group the items in
  417. * queue with their step number by measuring how many we push them for
  418. * the current steps variable value.
  419. */
  420. while (done == false) {
  421. if (qStepItems>0) { // Have we any item on the current group?
  422. xyItem = q.remove (); //load an item of the current group
  423. --qStepItems; // mark the removal of the item
  424. for (move=0 ; move<4 ; ++move) {
  425. // loop every valid next position for the current position
  426. if ((xyNext = pacmanValidMove (xyItem, move)) != Globals.FALSE_POS) {
  427. if (dist[xyNext[0]][xyNext[1]] == -1) {
  428. // If we haven't been there
  429. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  430. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  431. /*
  432. * first match:
  433. * The first time we reach destination we have count the
  434. * distance. No need any other try
  435. */
  436. done = true;
  437. break;
  438. }
  439. else {
  440. // If we are not there yet, feed queue with another position
  441. q.insert (xyNext);
  442. }
  443. }
  444. }
  445. }
  446. }
  447. else {
  448. // We are done with group, mark how many are for the next one
  449. if ((qStepItems = q.size()) <= 0)
  450. return dist[xy[0]][xy[1]]; // fail safe return
  451. ++steps; // Update distance counter
  452. }
  453. }
  454. return dist[xy[0]][xy[1]];
  455. }
  456. /**
  457. * @brief
  458. * Evaluate the current move
  459. */
  460. private double evaluate ()
  461. {
  462. double ghostEval=0, gd=0;
  463. double flagEval=0;
  464. int i;
  465. int[] flagDist = new int[PacmanUtilities.numberOfFlags];
  466. int[] ghostDist = new int[PacmanUtilities.numberOfGhosts];
  467. int minGhostDist=Globals.MAX_DISTANCE+1;
  468. int averGhostDist; // cast to integer
  469. int minFlagDist=Globals.MAX_DISTANCE+1;
  470. // Find ghost distances, min and average
  471. for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
  472. ghostDist [i] = ghostMoveDist (currentGhostPos[i], nodeXY);
  473. averGhostDist += ghostDist [i];
  474. if (minGhostDist > ghostDist[i])
  475. minGhostDist = ghostDist[i];
  476. }
  477. averGhostDist /= PacmanUtilities.numberOfGhosts;
  478. // extract the ghostEval as a combination of the minimum distance and the average
  479. gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_MIN_FACTOR * averGhostDist;
  480. // normalize the ghostEval to our interval
  481. ghostEval = (-1.0 / (1+gd)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) + (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  482. // Find flag distances and min
  483. for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  484. if (currentFlagStatus[i] == false) {
  485. flagDist[i] = pacmanMoveDist (nodeXY, flagPos[i]);
  486. if (minFlagDist > flagDist[i])
  487. minFlagDist = flagDist[i];
  488. }
  489. }
  490. // normalize the flagEval to our interval
  491. flagEval = (1.0 / (1+minFlagDist)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) - (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  492. // mix the life and goal to output
  493. return ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
  494. + flagEval * Globals.EVAL_FLAGDIST_FACTOR;
  495. }
  496. }