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.

540 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. ArrayList<Node89978445> 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. ArrayList<Node89978445> 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. /*
  119. * ============= Helper API ============
  120. */
  121. /**
  122. * @param creature creature's (x,y)
  123. * @return
  124. * Return true if the creature is inside the maze boxes
  125. */
  126. public static boolean isInsideBox (int[] creature)
  127. {
  128. boolean ret = false; //have faith
  129. for (int i=0 ; i<4 ; ++i) {
  130. if ((creature[0]>=Globals.BOXES[i][0][0] && creature[0]<=Globals.BOXES[i][1][0]) &&
  131. (creature[1]>=Globals.BOXES[i][0][1] && creature[1]<=Globals.BOXES[i][1][1]))
  132. ret = true;
  133. }
  134. return ret;
  135. }
  136. /**
  137. * @brief
  138. * Static version of move validation
  139. * Check if the requested move for a node is valid
  140. */
  141. public static int[] pacmanValidMove (Node89978445 node, int move)
  142. {
  143. int[] newPos = new int[2];
  144. // find hypothetical new position
  145. newPos[0] = node.nodeXY[0];
  146. newPos[1] = node.nodeXY[1];
  147. newPos[0] += (move == Room.SOUTH) ? 1:0;
  148. newPos[0] -= (move == Room.NORTH) ? 1:0;
  149. newPos[1] += (move == Room.EAST) ? 1:0;
  150. newPos[1] -= (move == Room.WEST) ? 1:0;
  151. // Pacman curves Maze plane to a Torus
  152. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  153. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  154. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  155. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  156. // Valid filters
  157. if (!isInsideBox(node.nodeXY) &&
  158. (node.Maze[node.nodeXY[0]][node.nodeXY[1]].walls[move] == 0))
  159. return Globals.FALSE_POS;
  160. return newPos;
  161. }
  162. /*
  163. * ============= Node's private methods =============
  164. */
  165. /**
  166. * @brief
  167. * Loop entire maze and return an object that holds Ghost positions
  168. * @param none
  169. * @return Object with Ghost position
  170. * The first dimension holds the number of the ghost
  171. * The 2nd dimension holds the (x, y) coordinates of the ghost
  172. */
  173. private int[][] findGhosts ()
  174. {
  175. int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return
  176. int g = 0; // Ghost index
  177. boolean keepGoing = true; // Boundary check helper variable
  178. // Loop entire Maze (i, j)
  179. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  180. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  181. if (Maze[x][y].isGhost ()) {
  182. // In case of a Ghost save its position to return object
  183. ret[g][0] = x;
  184. ret[g][1] = y;
  185. // boundary check
  186. if (++g > PacmanUtilities.numberOfGhosts)
  187. keepGoing = false;
  188. }
  189. }
  190. }
  191. return ret;
  192. }
  193. /**
  194. * @brief
  195. * Loop entire maze and return an object that holds flags positions
  196. * @param none
  197. * @return Object with flag positions
  198. * The first dimension holds the number of the flag
  199. * The 2nd dimension holds the (x, y) coordinates of the flag
  200. */
  201. private int[][] findFlags ()
  202. {
  203. int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return
  204. int g = 0; // Flag index
  205. boolean keepGoing = true; // Boundary check helper variable
  206. // Loop entire Maze (i, j)
  207. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  208. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  209. if (Maze[x][y].isFlag()) {
  210. // In case of a Ghost save its position to return object
  211. ret[g][0] = x;
  212. ret[g][1] = y;
  213. // boundary check
  214. if (++g > PacmanUtilities.numberOfFlags)
  215. keepGoing = false;
  216. }
  217. }
  218. }
  219. return ret;
  220. }
  221. /**
  222. * @brief
  223. * Loop through flags and check their status
  224. * @param none
  225. * @return Object with flag status
  226. */
  227. private boolean[] checkFlags ()
  228. {
  229. boolean[] ret = new boolean [PacmanUtilities.numberOfFlags];
  230. int x, y;
  231. for (int i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  232. x = flagPos[i][0]; // Get row,col coordinates
  233. y = flagPos[i][1];
  234. ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
  235. }
  236. return ret;
  237. }
  238. /*
  239. * ============ evaluation helper methods ==============
  240. */
  241. private int[] ghostValidMove (int[] ghost, int move)
  242. {
  243. int[] newPos = new int[2];
  244. // find hypothetical new position
  245. newPos[0] = ghost[0];
  246. newPos[1] = ghost[1];
  247. newPos[0] += (move == Room.SOUTH) ? 1:0;
  248. newPos[0] -= (move == Room.NORTH) ? 1:0;
  249. newPos[1] += (move == Room.EAST) ? 1:0;
  250. newPos[1] -= (move == Room.WEST) ? 1:0;
  251. // Valid filters
  252. if (!((newPos[0] >= 0 && newPos[0] < PacmanUtilities.numberOfRows) &&
  253. (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns)))
  254. return Globals.FALSE_POS;
  255. if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
  256. return Globals.FALSE_POS;
  257. return newPos;
  258. }
  259. /**
  260. * @brief
  261. * Check if the requested move is valid
  262. */
  263. private int[] pacmanValidMove (int[] pacman, int move)
  264. {
  265. int[] newPos = new int[2];
  266. // find hypothetical new position
  267. newPos[0] = pacman[0];
  268. newPos[1] = pacman[1];
  269. newPos[0] += (move == Room.SOUTH) ? 1:0;
  270. newPos[0] -= (move == Room.NORTH) ? 1:0;
  271. newPos[1] += (move == Room.EAST) ? 1:0;
  272. newPos[1] -= (move == Room.WEST) ? 1:0;
  273. // Pacman curves Maze plane to a Torus
  274. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  275. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  276. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  277. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  278. // Valid filters
  279. if (!isInsideBox(pacman) && (Maze[pacman[0]][pacman[1]].walls[move] == 0))
  280. return Globals.FALSE_POS;
  281. return newPos;
  282. }
  283. /**
  284. * @brief
  285. * A Breadth-first search to find the shortest path distance
  286. * from a ghost to a point in the maze
  287. * @param ghost Ghost (x, y)
  288. * @param xy The x, y coordinate in Maze
  289. * @return
  290. */
  291. private int ghostMoveDist (int[] ghost, int[] xy)
  292. {
  293. int move;
  294. int steps, qStepItems; // distance and group counters
  295. boolean done = false; // algo ending flag
  296. int r = PacmanUtilities.numberOfRows; // helper for shorting names
  297. int c = PacmanUtilities.numberOfColumns; // helper for shorting names
  298. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  299. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  300. Queue2D q = new Queue2D (r+c - 1); // Queue to feed with possible position
  301. int [][] dist = new int [r][c];
  302. /*<
  303. * 2D array holding all the distances from the ghost to each square of the maze
  304. */
  305. // If target square is inside a box abort with max distance
  306. if (isInsideBox(xy))
  307. return Globals.MAX_DISTANCE;
  308. /*
  309. * init data for algorithm
  310. */
  311. for (int i=0 ; i<r ; ++i) {
  312. for (int j=0 ; j<c ; ++j) {
  313. dist[i][j] = -1;
  314. // dist array starts with -1
  315. }
  316. }
  317. // loop data
  318. dist [ghost[0]][ghost[1]] = 0; //starting point is the ghost position
  319. q.insert (ghost); // feed first loop data
  320. qStepItems = 1; // init counters
  321. steps = 1;
  322. /*
  323. * Main loop of the algorithm
  324. *
  325. * For every position we check the valid moves, we apply to them
  326. * the step number and we push them to queue. We group the items in
  327. * queue with their step number by measuring how many we push them for
  328. * the current steps variable value.
  329. */
  330. while (done == false) {
  331. if (qStepItems>0) { // Have we any item on the current group?
  332. xyItem = q.remove (); //load an item of the current group
  333. --qStepItems; // mark the removal of the item
  334. for (move=0 ; move<4 ; ++move) {
  335. // loop every valid next position for the current position
  336. if ((xyNext = ghostValidMove (xyItem, move)) != Globals.FALSE_POS) {
  337. if (dist[xyNext[0]][xyNext[1]] == -1) {
  338. // If we haven't been there
  339. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  340. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  341. /*
  342. * first match:
  343. * The first time we reach destination we have count the
  344. * distance. No need any other try
  345. */
  346. done = true;
  347. break;
  348. }
  349. else {
  350. // If we are not there yet, feed queue with another position
  351. q.insert (xyNext);
  352. }
  353. }
  354. }
  355. }
  356. }
  357. else {
  358. // We are done with group, mark how many are for the next one
  359. if ((qStepItems = q.size()) <= 0)
  360. return dist[xy[0]][xy[1]]; // fail safe return
  361. ++steps; // Update distance counter
  362. }
  363. }
  364. return dist[xy[0]][xy[1]];
  365. }
  366. /**
  367. * @brief
  368. * A Breadth-first search to find the shortest path distance
  369. * from the pacman to a point in the maze. The point will normaly
  370. * represent a flag
  371. * @param pacman Pacman's (x, y)
  372. * @param xy The x, y coordinate in Maze
  373. * @return
  374. */
  375. private int pacmanMoveDist (int[] pacman, int[] xy)
  376. {
  377. int move;
  378. int steps, qStepItems; // distance and group counters
  379. boolean done = false; // algo ending flag
  380. int r = PacmanUtilities.numberOfRows; // helper for shorting names
  381. int c = PacmanUtilities.numberOfColumns; // helper for shorting names
  382. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  383. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  384. Queue2D q = new Queue2D (r+c - 1); // Queue to feed with possible position
  385. int [][] dist = new int [r][c];
  386. /*
  387. * 2D array holding all the distances from pacman to each square of the maze
  388. */
  389. // If target square is inside a box abort with max distance
  390. if (isInsideBox (xy))
  391. return Globals.MAX_DISTANCE;
  392. /*
  393. * init data for algorithm
  394. */
  395. for (int i=0 ; i<r ; ++i) {
  396. for (int j=0 ; j<c ; ++j) {
  397. dist[i][j] = -1;
  398. // dist array starts with -1
  399. }
  400. }
  401. // loop data
  402. dist [pacman[0]][pacman[1]] = 0; //starting point is the pacman position
  403. q.insert (pacman); // feed first loop data
  404. qStepItems = 1; // init counters
  405. steps = 1;
  406. /*
  407. * Main loop of the algorithm
  408. *
  409. * For every position we check the valid moves, we apply to them
  410. * the step number and we push them to queue. We group the items in
  411. * queue with their step number by measuring how many we push them for
  412. * the current steps variable value.
  413. */
  414. while (done == false) {
  415. if (qStepItems>0) { // Have we any item on the current group?
  416. xyItem = q.remove (); //load an item of the current group
  417. --qStepItems; // mark the removal of the item
  418. for (move=0 ; move<4 ; ++move) {
  419. // loop every valid next position for the current position
  420. if ((xyNext = pacmanValidMove (xyItem, move)) != Globals.FALSE_POS) {
  421. if (dist[xyNext[0]][xyNext[1]] == -1) {
  422. // If we haven't been there
  423. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  424. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  425. /*
  426. * first match:
  427. * The first time we reach destination we have count the
  428. * distance. No need any other try
  429. */
  430. done = true;
  431. break;
  432. }
  433. else {
  434. // If we are not there yet, feed queue with another position
  435. q.insert (xyNext);
  436. }
  437. }
  438. }
  439. }
  440. }
  441. else {
  442. // We are done with group, mark how many are for the next one
  443. if ((qStepItems = q.size()) <= 0)
  444. return dist[xy[0]][xy[1]]; // fail safe return
  445. ++steps; // Update distance counter
  446. }
  447. }
  448. return dist[xy[0]][xy[1]];
  449. }
  450. /**
  451. * @brief
  452. * Evaluate the current move
  453. */
  454. private double evaluate ()
  455. {
  456. double ghostEval=0, gd=0;
  457. double flagEval=0;
  458. int i;
  459. int[] flagDist = new int[PacmanUtilities.numberOfFlags];
  460. int[] ghostDist = new int[PacmanUtilities.numberOfGhosts];
  461. int minGhostDist=Globals.MAX_DISTANCE+1;
  462. int averGhostDist; // cast to integer
  463. int minFlagDist=Globals.MAX_DISTANCE+1;
  464. // Find ghost distances, min and average
  465. for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
  466. ghostDist [i] = ghostMoveDist (currentGhostPos[i], nodeXY);
  467. averGhostDist += ghostDist [i];
  468. if (minGhostDist > ghostDist[i])
  469. minGhostDist = ghostDist[i];
  470. }
  471. averGhostDist /= PacmanUtilities.numberOfGhosts;
  472. // extract the ghostEval as a combination of the minimum distance and the average
  473. gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_MIN_FACTOR * averGhostDist;
  474. // normalize the ghostEval to our interval
  475. ghostEval = (-1.0 / (1+gd)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) + (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  476. // Find flag distances and min
  477. for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  478. if (currentFlagStatus[i] == false) {
  479. flagDist[i] = pacmanMoveDist (nodeXY, flagPos[i]);
  480. if (minFlagDist > flagDist[i])
  481. minFlagDist = flagDist[i];
  482. }
  483. }
  484. // normalize the flagEval to our interval
  485. flagEval = (1.0 / (1+minFlagDist)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) - (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  486. // mix the life and goal to output
  487. return ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
  488. + flagEval * Globals.EVAL_FLAGDIST_FACTOR;
  489. }
  490. }