A java PacMan game application for A.U.TH (data structures class)
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

500 Zeilen
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. }