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.

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