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.

500 lines
17 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[][] curGhostPos; // Array holding the Ghost (x,y) pairs
  29. int[][] flagPos; // Array holding the flag (x,y) pairs
  30. boolean[] curFlagStatus; // 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. curGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  58. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  59. curFlagStatus = 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. curGhostPos = new int [PacmanUtilities.numberOfGhosts][2];
  76. flagPos = new int [PacmanUtilities.numberOfFlags][2];
  77. curFlagStatus = new boolean[PacmanUtilities.numberOfFlags];
  78. children = new ArrayList<Node89978445> ();
  79. // calculate helper arrays
  80. curGhostPos = findGhosts ();
  81. flagPos = findFlags ();
  82. curFlagStatus = checkFlags ();
  83. //Evaluate the position
  84. nodeEvaluation = evaluate ();
  85. }
  86. /*
  87. * ============== Setters ===============
  88. */
  89. /**
  90. * @brief SetMaze (Room) to Node object
  91. * @param maze The room to set
  92. */
  93. public void setMaze (Room[][] maze) {
  94. this.Maze = maze;
  95. }
  96. /**
  97. * @brief Set pacman's position
  98. * @param curXY Pacman's current X, Y position
  99. */
  100. public void setPosition (int[] curXY) {
  101. nodeXY = curXY;
  102. }
  103. /*
  104. * ============== getters =================
  105. */
  106. /**
  107. * @brief If not done runs the evaluation algorithm and returns the result
  108. * @note We assume the current position was a valid position
  109. * @return The evaluation result
  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.FALSE_POS)
  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. public int[] getCurrentPacmanPos () {
  130. return nodeXY;
  131. }
  132. public int[][] getCurrentGhostPos () {
  133. return curGhostPos;
  134. }
  135. public int getDepth () {
  136. return depth;
  137. }
  138. /*
  139. * ============= Helper API ============
  140. */
  141. public static int moveConv (int[] nextPos, int[] curPos)
  142. {
  143. int dx = nextPos[0] - curPos[0];
  144. int dy = nextPos[1] - curPos[1];
  145. if (dx < 0) return Room.NORTH;
  146. else if (dx > 0) return Room.SOUTH;
  147. else if (dy < 0) return Room.WEST;
  148. else return Room.EAST;
  149. }
  150. /**
  151. * @param creature creature's (x,y)
  152. * @return
  153. * Return true if the creature is inside the maze boxes
  154. */
  155. public static boolean isInsideBox (int[] creature)
  156. {
  157. boolean ret = false; //have faith
  158. for (int i=0 ; i<4 ; ++i) {
  159. if ((creature[0]>=Globals.BOXES[i][0][0] && creature[0]<=Globals.BOXES[i][1][0]) &&
  160. (creature[1]>=Globals.BOXES[i][0][1] && creature[1]<=Globals.BOXES[i][1][1]))
  161. ret = true;
  162. }
  163. return ret;
  164. }
  165. /**
  166. * @brief
  167. * Static version of move validation
  168. * Check if the requested move for a node is valid
  169. */
  170. public static int[] pacmanValidMove (Node89978445 node, int move)
  171. {
  172. int[] newPos = new int[2];
  173. // find hypothetical new position
  174. newPos[0] = node.nodeXY[0];
  175. newPos[1] = node.nodeXY[1];
  176. newPos[0] += (move == Room.SOUTH) ? 1:0;
  177. newPos[0] -= (move == Room.NORTH) ? 1:0;
  178. newPos[1] += (move == Room.EAST) ? 1:0;
  179. newPos[1] -= (move == Room.WEST) ? 1:0;
  180. // Pacman curves Maze plane to a Torus
  181. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  182. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  183. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  184. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  185. // Valid filters
  186. if (!isInsideBox(node.nodeXY) &&
  187. (node.Maze[node.nodeXY[0]][node.nodeXY[1]].walls[move] == 0))
  188. return Globals.FALSE_POS;
  189. return newPos;
  190. }
  191. /*
  192. * ============= Node's private methods =============
  193. */
  194. /**
  195. * @brief
  196. * Loop entire maze and return an object that holds Ghost positions
  197. * @param none
  198. * @return Object with Ghost position
  199. * The first dimension holds the number of the ghost
  200. * The 2nd dimension holds the (x, y) coordinates of the ghost
  201. */
  202. private int[][] findGhosts ()
  203. {
  204. int [][] ret = new int [PacmanUtilities.numberOfGhosts][2]; // Make an object to return
  205. int g = 0; // Ghost index
  206. boolean keepGoing = true; // Boundary check helper variable
  207. // Loop entire Maze (i, j)
  208. for (int x=0 ; keepGoing && x<PacmanUtilities.numberOfRows ; ++x) {
  209. for (int y=0 ; keepGoing && y<PacmanUtilities.numberOfColumns ; ++y) {
  210. if (Maze[x][y].isGhost ()) {
  211. // In case of a Ghost save its position to return object
  212. ret[g][0] = x;
  213. ret[g][1] = y;
  214. // boundary check
  215. if (++g > PacmanUtilities.numberOfGhosts)
  216. keepGoing = false;
  217. }
  218. }
  219. }
  220. return ret;
  221. }
  222. /**
  223. * @brief
  224. * Loop entire maze and return an object that holds flags positions
  225. * @param none
  226. * @return Object with flag positions
  227. * The first dimension holds the number of the flag
  228. * The 2nd dimension holds the (x, y) coordinates of the flag
  229. */
  230. private int[][] findFlags ()
  231. {
  232. int [][] ret = new int [PacmanUtilities.numberOfFlags][2]; // Make an object to return
  233. int g = 0; // Flag 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].isFlag()) {
  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.numberOfFlags)
  244. keepGoing = false;
  245. }
  246. }
  247. }
  248. return ret;
  249. }
  250. /**
  251. * @brief
  252. * Loop through flags and check their status
  253. * @param none
  254. * @return Object with flag status
  255. */
  256. private boolean[] checkFlags ()
  257. {
  258. boolean[] ret = new boolean [PacmanUtilities.numberOfFlags];
  259. int x, y;
  260. for (int i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  261. x = flagPos[i][0]; // Get row,col coordinates
  262. y = flagPos[i][1];
  263. ret[i] = (Maze[x][y].isCapturedFlag()) ? true : false;
  264. }
  265. return ret;
  266. }
  267. /*
  268. * ============ private evaluation helper methods ==============
  269. */
  270. private enum Creature {
  271. GHOST, PACMAN
  272. }
  273. /**
  274. * @brief
  275. * Check if the requested ghost move is valid
  276. */
  277. private int[] ghostValidMove (int[] ghost, int move)
  278. {
  279. int[] newPos = new int[2];
  280. // find hypothetical new position
  281. newPos[0] = ghost[0];
  282. newPos[1] = ghost[1];
  283. newPos[0] += (move == Room.SOUTH) ? 1:0;
  284. newPos[0] -= (move == Room.NORTH) ? 1:0;
  285. newPos[1] += (move == Room.EAST) ? 1:0;
  286. newPos[1] -= (move == Room.WEST) ? 1:0;
  287. // Valid filters
  288. if (!((newPos[0] >= 0 && newPos[0] < PacmanUtilities.numberOfRows) &&
  289. (newPos[1] >= 0 && newPos[1] < PacmanUtilities.numberOfColumns)))
  290. return Globals.FALSE_POS;
  291. if (!isInsideBox(ghost) && (Maze[ghost[0]][ghost[1]].walls[move] == 0))
  292. return Globals.FALSE_POS;
  293. return newPos;
  294. }
  295. /**
  296. * @brief
  297. * Check if the requested pacman move is valid
  298. */
  299. private int[] pacmanValidMove (int[] pacman, int move)
  300. {
  301. int[] newPos = new int[2];
  302. // find hypothetical new position
  303. newPos[0] = pacman[0];
  304. newPos[1] = pacman[1];
  305. newPos[0] += (move == Room.SOUTH) ? 1:0;
  306. newPos[0] -= (move == Room.NORTH) ? 1:0;
  307. newPos[1] += (move == Room.EAST) ? 1:0;
  308. newPos[1] -= (move == Room.WEST) ? 1:0;
  309. // Pacman curves Maze plane to a Torus
  310. if (newPos[0] < 0) newPos[0] = PacmanUtilities.numberOfRows;
  311. if (newPos[0] >= PacmanUtilities.numberOfRows ) newPos[0] = 0;
  312. if (newPos[1] < 0) newPos[1] = PacmanUtilities.numberOfColumns;
  313. if (newPos[1] >= PacmanUtilities.numberOfColumns ) newPos[1] = 0;
  314. // Valid filters
  315. if (!isInsideBox(pacman) && (Maze[pacman[0]][pacman[1]].walls[move] == 0))
  316. return Globals.FALSE_POS;
  317. return newPos;
  318. }
  319. /**
  320. * @brief
  321. * A Breadth-first search to find the shortest path distance
  322. * from an origin point to another point in the maze as if a
  323. * a creature could walk through
  324. * @param origin origin (x, y)
  325. * @param xy The x, y coordinate in Maze
  326. * @param creature The type of creature for whom the path is for
  327. * @return The number of steps from origin to xy
  328. */
  329. private int moveDist (int[] origin, int[] xy, Creature creature)
  330. {
  331. int move;
  332. int steps, qStepItems; // distance and group counters
  333. boolean done = false; // algo ending flag
  334. int r = PacmanUtilities.numberOfRows; // helper for shorting names
  335. int c = PacmanUtilities.numberOfColumns; // helper for shorting names
  336. int[] xyItem = new int [2]; // Coordinates of the current position of the algo
  337. int[] xyNext = new int [2]; // Coordinates of the next valid position of the algo
  338. Queue2D q = new Queue2D (Globals.MAX_DISTANCE - 1); // Queue to feed with possible position
  339. int [][] dist = new int [r][c];
  340. /*<
  341. * 2D array holding all the distances from the origin to each square of the maze
  342. */
  343. // If target square is inside a box abort with max distance
  344. if (isInsideBox(xy))
  345. return Globals.MAX_DISTANCE;
  346. /*
  347. * init data for algorithm
  348. */
  349. for (int i=0 ; i<r ; ++i) {
  350. for (int j=0 ; j<c ; ++j) {
  351. dist[i][j] = -1;
  352. // dist array starts with -1
  353. }
  354. }
  355. // loop data
  356. dist [origin[0]][origin[1]] = 0; //starting point is the origin position
  357. q.insert (origin); // feed first loop data
  358. qStepItems = 1; // init counters
  359. steps = 1;
  360. /*
  361. * Main loop of the algorithm
  362. *
  363. * For every position we check the valid moves, we apply to them
  364. * the step number and we push them to queue. We group the items in
  365. * queue with their step number by measuring how many we push them for
  366. * the current steps variable value.
  367. */
  368. while (done == false) {
  369. if (qStepItems>0) { // Have we any item on the current group?
  370. xyItem = q.remove (); //load an item of the current group
  371. --qStepItems; // mark the removal of the item
  372. for (move=0 ; move<4 ; ++move) {
  373. // loop every valid next position for the current position
  374. switch (creature) {
  375. case GHOST: xyNext = ghostValidMove (xyItem, move); break;
  376. case PACMAN: xyNext = pacmanValidMove (xyItem, move); break;
  377. default: xyNext = Globals.FALSE_POS;
  378. }
  379. if (xyNext != Globals.FALSE_POS) {
  380. if (dist[xyNext[0]][xyNext[1]] == -1) {
  381. // If we haven't been there
  382. dist[xyNext[0]][xyNext[1]] = steps; // mark the distance
  383. if ((xyNext[0] == xy[0]) && (xyNext[1] == xy[1])) {
  384. /*
  385. * first match:
  386. * The first time we reach destination we have count the
  387. * distance. No need any other try
  388. */
  389. done = true;
  390. break;
  391. }
  392. else {
  393. // If we are not there yet, feed queue with another position
  394. q.insert (xyNext);
  395. }
  396. }
  397. }
  398. }
  399. }
  400. else {
  401. // We are done with group, mark how many are for the next one
  402. if ((qStepItems = q.size()) <= 0)
  403. return dist[xy[0]][xy[1]]; // fail safe return
  404. ++steps; // Update distance counter
  405. }
  406. }
  407. return dist[xy[0]][xy[1]];
  408. }
  409. /**
  410. * @brief
  411. * Evaluate the current move
  412. */
  413. private double evaluate ()
  414. {
  415. double ghostEval=0, gd=0;
  416. double flagEval=0;
  417. int i;
  418. int[] flagDist = new int[PacmanUtilities.numberOfFlags];
  419. int[] ghostDist = new int[PacmanUtilities.numberOfGhosts];
  420. int minGhostDist=Globals.MAX_DISTANCE+1;
  421. int averGhostDist; // cast to integer
  422. int minFlagDist=Globals.MAX_DISTANCE+1;
  423. // Find ghost distances, min and average
  424. for (i=0, averGhostDist=0 ; i<PacmanUtilities.numberOfGhosts ; ++i) {
  425. ghostDist [i] = moveDist (curGhostPos[i], nodeXY, Creature.GHOST);
  426. averGhostDist += ghostDist [i];
  427. if (minGhostDist > ghostDist[i])
  428. minGhostDist = ghostDist[i];
  429. }
  430. averGhostDist /= PacmanUtilities.numberOfGhosts;
  431. // extract the ghostEval as a combination of the minimum distance and the average
  432. gd = Globals.EVAL_GHOSTDIST_MIN_FACTOR * minGhostDist + Globals.EVAL_GHOSTDIST_MIN_FACTOR * averGhostDist;
  433. // normalize the ghostEval to our interval
  434. ghostEval = (-1.0 / (1+gd)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) + (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  435. // Find flag distances and min
  436. for (i=0 ; i<PacmanUtilities.numberOfFlags ; ++i) {
  437. if (curFlagStatus[i] == false) {
  438. flagDist[i] = moveDist (nodeXY, flagPos[i], Creature.PACMAN);
  439. if (minFlagDist > flagDist[i])
  440. minFlagDist = flagDist[i];
  441. }
  442. }
  443. // normalize the flagEval to our interval
  444. flagEval = (1.0 / (1+minFlagDist)) * (Globals.EVAL_MAX - Globals.EVAL_MIN) - (Globals.EVAL_MAX - Globals.EVAL_MIN)/2;
  445. // mix the life and goal to output
  446. return ghostEval * Globals.EVAL_GHOSTDIST_FACTOR
  447. + flagEval * Globals.EVAL_FLAGDIST_FACTOR;
  448. }
  449. }