A java snake game 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.

558 lines
20 KiB

  1. package net.hoo2.auth.dsproject.snake;
  2. import java.lang.Math;
  3. /**
  4. * @class Board
  5. * @brief The game's board representation.
  6. *
  7. * The board is a square collection of tiles numbered in a
  8. * boustrophedon (zig-zag) way. A number of snakes, ladders
  9. * and apples which we called elements are placed on the board
  10. * for each game.
  11. *
  12. * @author Christos Choutouridis AEM:8997
  13. * @email cchoutou@ece.auth.gr
  14. */
  15. public class Board {
  16. /** @name Constants */
  17. /**@{ */
  18. static final int POINTS_MAX = 20; /**< The maximum absolute number of points for each apple */
  19. static final int POINTS_STEP = 5; /**< The difference between different apple points */
  20. /**@} */
  21. /** @name Constructors */
  22. /** @{ */
  23. /** A doing nothing default constructor
  24. * @warining Avoid using this constructor as it requires all setters(or copy)
  25. * and @ref createBoard() to be called after.
  26. */
  27. Board () {
  28. N = M =0;
  29. tiles = null;
  30. snakes = null;
  31. ladders = null;
  32. apples = null;
  33. }
  34. /**
  35. * @brief Creates a board for game
  36. *
  37. * This constructor allocates the memory for the board and elements and
  38. * creates a board by placing all required elements on the board.
  39. *
  40. * @param N The row for the board
  41. * @param M The columns of the board
  42. * @param numOfSnakes Number of snakes to place
  43. * @param numOfLadders Number of ladders to place
  44. * @param numOfApples Number of Apples to place
  45. *
  46. * @warning
  47. * We call @ref createBoard() inside this constructor in order for
  48. * the board to be in "playable condition". This is preferable by the author.
  49. * A constructor should(if possible) to leave the object in a usable condition.
  50. * In order to follow the project requirements we create this functionality in a
  51. * separate function @ref createBoard(). We believe that if a user can make a
  52. * mistake he eventually will do it sometime. Here, if we leave the createBoard()
  53. * call to user we are enabling him to make it.
  54. */
  55. Board (int N, int M, int numOfSnakes, int numOfLadders, int numOfApples) {
  56. // Init the board object
  57. setN (N); // Input checked version (may throw)
  58. setM (M); // Input checked version (may throw)
  59. tiles = new int[N][M];
  60. snakes = new Snake[numOfSnakes];
  61. ladders = new Ladder[numOfLadders];
  62. apples = new Apple[numOfApples];
  63. createBoard (); // Complete board preparation and make all the element memory allocations
  64. }
  65. /**
  66. * @brief Copy constructor.
  67. * We make a deep copy of B and we trust B's data to be valid.
  68. * @param B The board we want to copy
  69. * @note We don't use clone as long as we don't inherit Cloneable iface
  70. * @note This requires Snake, Apple and Ladder copy constructors
  71. */
  72. Board (Board B) {
  73. N = B.getN();
  74. M = B.getM();
  75. tiles = new int[N][M];
  76. snakes = new Snake[B.getSnakes().length];
  77. ladders = new Ladder[B.getLadders().length];
  78. apples = new Apple[B.getApples().length];
  79. // Copy B's guts into new memory
  80. copyTiles(B.getTiles());
  81. copySnakes(B.getSnakes());
  82. copyLadders(B.getLadders());
  83. copyApples(B.getApples());
  84. }
  85. /** @} */
  86. /** @name Get/Set interface */
  87. /** @{ */
  88. /** Get value N */
  89. int getN () { return N; }
  90. /** Set value N */
  91. void setN (int N) { this.N = N; }
  92. /** Get value M */
  93. int getM () { return M; }
  94. /** Set value M */
  95. void setM (int M) { this.M = M; }
  96. /** Get reference to tiles */
  97. int[][] getTiles () { return tiles; }
  98. /**
  99. * Set tiles
  100. * @param tiles Source of tiles to use
  101. * @note This has to be called if the board is default constructed
  102. */
  103. void setTiles (int[][] tiles) { this.tiles = tiles; }
  104. /** Get reference to snakes */
  105. Snake[] getSnakes() { return snakes; }
  106. /**
  107. * Set snakes
  108. * @param snakes Reference to snakes to use
  109. * @note This requires snakes must be allocated elsewhere.
  110. */
  111. void setSnakes(Snake[] snakes) { this.snakes = snakes; }
  112. /** Get reference to ladders */
  113. Ladder[] getLadders() { return ladders; }
  114. /**
  115. * Set ladders
  116. * @param ladders Reference to ladders to use
  117. * @note This requires ladders must be allocated elsewhere.
  118. */
  119. void setLadders(Ladder[] ladders) { this.ladders = ladders; }
  120. /** Get reference to apples */
  121. Apple[] getApples() { return apples; }
  122. /**
  123. * Set apples
  124. * @param apples Reference to apples to use
  125. * @note This requires apples must be allocated elsewhere.
  126. */
  127. void setApples(Apple[] apples) { this.apples = apples; }
  128. /**
  129. * Copy tiles
  130. * @param tiles Source of tiles to use
  131. * @note This has to be called if the board is default constructed
  132. */
  133. void copyTiles (int[][] tiles) {
  134. // Check if we need allocation
  135. if (this.tiles == null)
  136. this.tiles = new int[N][M];
  137. // Copy-assign the values
  138. for (int i =0 ; i<N ; ++i)
  139. for (int j =0 ; j<M ; ++j)
  140. this.tiles[i][j] = tiles[i][j];
  141. }
  142. /**
  143. * Copy snakes (deep copy)
  144. * @param snakes Source of snakes to use
  145. * @note Requires Snake copy constructor
  146. * @note This has to be called if the board is default constructed
  147. */
  148. void copySnakes(Snake[] snakes) {
  149. // Check if we need allocation
  150. if (this.snakes == null)
  151. this.snakes = new Snake[snakes.length];
  152. // Assign values (deep copy)
  153. for (int i =0 ; i<this.snakes.length ; ++i)
  154. this.snakes[i] = new Snake(snakes[i]);
  155. }
  156. /**
  157. * Copy ladders (deep copy)
  158. * @param ladders Source of ladders to use
  159. * @note Requires Ladder copy constructor
  160. * @note This has to be called if the board is default constructed
  161. */
  162. void copyLadders (Ladder[] ladders) {
  163. // Check if we need allocation
  164. if (this.ladders == null)
  165. this.ladders = new Ladder[ladders.length];
  166. // Assign values (deep copy)
  167. for (int i =0 ; i<this.ladders.length ; ++i)
  168. this.ladders[i] = new Ladder(ladders[i]);
  169. }
  170. /**
  171. * Copy apples (deep copy)
  172. * @param apples Source of apples to use
  173. * @note Requires Apple copy constructor
  174. * @note This has to be called if the board is default constructed
  175. */
  176. void copyApples (Apple[] apples) {
  177. // Check if we need allocation
  178. if (this.apples == null)
  179. this.apples = new Apple[apples.length];
  180. // Assign values (deep copy)
  181. for (int i =0 ; i<this.apples.length ; ++i)
  182. this.apples[i] = new Apple(apples[i]);
  183. }
  184. /** @} */
  185. /** @name Exposed API members */
  186. /** @{ */
  187. /**
  188. * Check if the tile is a snake head. If so return the snake's
  189. * tails tile. If not return the same tile
  190. * @param tile The tile to check
  191. * @return The result tile
  192. */
  193. int checkSnake (int tile) {
  194. for (int i =0 ; i<snakes.length ; ++i) {
  195. if (snakes[i].getHeadId() == tile)
  196. return snakes[i].getTailId();
  197. }
  198. return tile;
  199. }
  200. /**
  201. * Check if the tile is a ladder down step. If so return the ladder's
  202. * up step tile. If not return the same tile.
  203. * @note
  204. * This also break the ladder if used
  205. * @param tile The tile to check
  206. * @return The result tile
  207. */
  208. int checkLadder (int tile, boolean climb) {
  209. for (int i =0 ; i<ladders.length ; ++i) {
  210. if (ladders[i].getDownStepId() == tile &&
  211. ladders[i].getBroken() == false) {
  212. ladders[i].setBroken(climb);
  213. return ladders[i].getUpStepId();
  214. }
  215. }
  216. return tile;
  217. }
  218. /**
  219. * Check if the tile is an apple tile. If so eat it and return the score difference
  220. * @param tile The tile to check
  221. * @return The score difference
  222. */
  223. int checkApple (int tile, boolean eat) {
  224. int ds =0; // delta-score
  225. for (int i =0 ; i<apples.length ; ++i) {
  226. if (apples[i].getAppleTileId() == tile) {
  227. ds = apples[i].getPoints();
  228. // eat it
  229. if (eat)
  230. apples[i].setPoints(0);
  231. }
  232. }
  233. return ds;
  234. }
  235. /**
  236. * Create a playable board for the game.
  237. * @warning
  238. * This is not required to be called after construction in order to ensure board's playable
  239. * condition. In fact this function SHOULD NOT CALLED AT ALL.
  240. * The project requirements expect this to be public. The preferable mode would be private.
  241. * @see Board() constructor.
  242. */
  243. void createBoard () {
  244. _tileNumbering (); // Create tile numbering
  245. _placeSnakes (); // Place snakes
  246. _placeApples (); // Place Apples
  247. _placeLadders (); // place ladders
  248. }
  249. /**
  250. * @brief
  251. * make and print element boards
  252. * This is not required in order for the board to be playable
  253. * It just produce a stdout output for convenience.
  254. */
  255. void createElementBoard () {
  256. String[][] elementBoardSnakes = new String[N][M];
  257. String[][] elementBoardLadders = new String[N][M];
  258. String[][] elementBoardApples = new String[N][M];
  259. _makeElementSnakes (elementBoardSnakes);
  260. _makeElementLadders (elementBoardLadders);
  261. _makeElementApples (elementBoardApples);
  262. _printElement (elementBoardSnakes, "elementBoardSnakes");
  263. _printElement (elementBoardLadders, "elementBoardLadders");
  264. _printElement (elementBoardApples, "elementBoardApples");
  265. }
  266. /** @} */
  267. /** @name Private api */
  268. /**@{ */
  269. /**
  270. * @brief
  271. * Create the tile numbering in a boustrophedon (zig-zag) way.
  272. * We use a starting point the tile[0][0] and as finish point
  273. * the tile[N-1][M-1]
  274. */
  275. private void _tileNumbering () {
  276. for (int i=0, tile =1 ; i<N ; ++i) {
  277. if (i%2 == 0) {
  278. // Even row, go right
  279. for (int j=0 ; j<M ; ++j)
  280. tiles[i][j] = tile++;
  281. }
  282. else {
  283. // Odd row, go left
  284. for (int j=M-1 ; j>=0 ; --j)
  285. tiles[i][j] = tile++;
  286. }
  287. }
  288. }
  289. /**
  290. * @brief
  291. * Place the snakes on the board
  292. * The only constrain at this point is that snake tails must be placed
  293. * below heads and heads must be placed in separate tiles
  294. */
  295. private void _placeSnakes () {
  296. int [] head = new int [snakes.length]; // temporary place holder for heads
  297. int [] tail = new int [snakes.length]; // temporary place holder for tails
  298. for (int i =0, tile =0 ; i<snakes.length ; ++i) {
  299. // Keep getting heads until they are different from the previous
  300. do
  301. tile = _pickRandom (M+1, M*N); // Don't use first row for heads
  302. while (_search (head, tile) >= 0);
  303. head[i] = tile;
  304. tail[i] = _pickRandom (1, head[i]-head[i]%M); // Don't use heads row and up for tail
  305. snakes[i] = new Snake(i, head[i], tail[i]); // Allocate snake
  306. }
  307. }
  308. /**
  309. * @brief
  310. * Place apples on the board
  311. * The constrains here are
  312. * that apples have to lie on different tiles and not in some
  313. * snake's head.
  314. * @note We require we have snakes.
  315. */
  316. private void _placeApples () {
  317. int [] apple_tiles = new int [apples.length]; // temporary placeholder for apples
  318. int [] snake_tiles = new int [snakes.length]; // array with snake head tiles
  319. for (int i =0 ; i<snakes.length ; ++i) // Load snake head tiles
  320. snake_tiles[i] = snakes[i].getHeadId();
  321. for (int i =0, tile =0 ; i<apples.length ; ++i) {
  322. // Keep getting tiles until they are different from the previous
  323. // and not in some snake's head
  324. do
  325. tile = _pickRandom (1, M*N);
  326. while ((_search (apple_tiles, tile) >= 0) ||
  327. (_search (snake_tiles, tile) >= 0));
  328. apple_tiles[i] = tile;
  329. // get points
  330. int points = _pickRandom (1, (POINTS_MAX/POINTS_STEP)) * POINTS_STEP;
  331. boolean red = (boolean)(Math.random() >=0.5); // get color
  332. // Allocate apple
  333. if (red)
  334. apples[i] = new Apple(i, tile, "red", points);
  335. else
  336. apples[i] = new Apple(i, tile, "black", -points);
  337. }
  338. }
  339. /**
  340. * @brief
  341. * Place ladders on board
  342. *
  343. * We add constrains so each ladder's up-step tile has to be different from:
  344. * * A snake's head tile. This ensures ladders and snakes are independent
  345. * * A ladders's down-step. This ensure we eliminate ladder chains.
  346. * * One other ladder's up-step. This is not critical but helps the printElement functionality
  347. *
  348. * We add constrains so each ladder's down-step tile has to be different from:
  349. * * A snake's head tile. This ensures ladders and snakes are independent
  350. * * A ladders's down-step. This is not critical but helps the printElement functionality
  351. * * One other ladder's up-step. This ensure we eliminate ladder chains.
  352. * @note We require we have snakes.
  353. */
  354. private void _placeLadders () {
  355. int [] up_step = new int [ladders.length]; // temporary place holder for up-steps
  356. int [] down_step = new int [ladders.length]; // temporary place holder for down-step
  357. int [] snake_tiles= new int [snakes.length]; // array with snake head tiles
  358. for (int i =0 ; i<snakes.length ; ++i) // Load snake head tiles
  359. snake_tiles[i] = snakes[i].getHeadId();
  360. for (int i =0, tile =0 ; i<ladders.length ; ++i) {
  361. // Keep getting up-steps until they are different from the previous ladder tiles
  362. // and not in some snake's head
  363. do
  364. tile = _pickRandom (M+1, M*N); // Don't use first row for up-steps
  365. while ((_search (up_step, tile) >= 0)
  366. || (_search (down_step, tile) >= 0)
  367. || (_search (snake_tiles, tile) >= 0));
  368. up_step[i] = tile;
  369. // Keep getting down-steps until they are different from the previous ladder tiles
  370. // and not in some snake's head
  371. do
  372. // Don't use up-step row and up for down-step
  373. tile = _pickRandom (1, up_step[i]-up_step[i]%M);
  374. while ((_search (up_step, tile) >= 0)
  375. || (_search (down_step, tile) >= 0)
  376. || (_search (snake_tiles, tile) >= 0));
  377. down_step[i] = tile;
  378. ladders[i] = new Ladder (i, up_step[i], down_step[i]); // Allocate ladder
  379. }
  380. }
  381. /**
  382. * Make element array of snakes as required by the project
  383. * @param elemSnakes
  384. */
  385. private void _makeElementSnakes (String[][] elemSnakes) {
  386. int [] head_tiles = new int [snakes.length]; // array with snake head tiles
  387. int [] tail_tiles = new int [snakes.length]; // array with snake head tiles
  388. int sn =-1;
  389. // Load snake head tiles
  390. for (int i =0 ; i<snakes.length ; ++i) {
  391. head_tiles[i] = snakes[i].getHeadId();
  392. tail_tiles[i] = snakes[i].getTailId();
  393. }
  394. // Search all tiles for snake heads and tails
  395. for (int i =0; i<N ; ++i) {
  396. for (int j =0 ; j<M ; ++j) {
  397. if ((sn = _search (head_tiles, tiles[i][j])) >= 0)
  398. elemSnakes[i][j] = "SH" + sn;
  399. else if ((sn = _search (tail_tiles, tiles[i][j])) >= 0)
  400. elemSnakes[i][j] = "ST" + sn;
  401. else
  402. elemSnakes[i][j] = "___";
  403. }
  404. }
  405. }
  406. /**
  407. * Make element array of ladders as required by the project
  408. * @param elemLadders
  409. */
  410. private void _makeElementLadders (String[][] elemLadders) {
  411. int [] up_tiles = new int [ladders.length]; // array with ladder up-step tiles
  412. int [] down_tiles = new int [ladders.length]; // array with ladder down-step tiles
  413. int sn =-1;
  414. // Load ladder tiles
  415. for (int i =0 ; i<ladders.length ; ++i) {
  416. up_tiles[i] = ladders[i].getUpStepId();
  417. down_tiles[i] = ladders[i].getDownStepId();
  418. }
  419. // Search all tiles for snake heads and tails
  420. for (int i =0; i<N ; ++i) {
  421. for (int j =0 ; j<M ; ++j) {
  422. if ((sn = _search (up_tiles, tiles[i][j])) >= 0)
  423. elemLadders[i][j] = "LU" + sn;
  424. else if ((sn = _search (down_tiles, tiles[i][j])) >= 0)
  425. elemLadders[i][j] = "LD" + sn;
  426. else
  427. elemLadders[i][j] = "___";
  428. }
  429. }
  430. }
  431. /**
  432. * Make element array of apples as required by the project
  433. * @param elemApples
  434. */
  435. private void _makeElementApples (String[][] elemApples) {
  436. int [] red_tiles = new int [apples.length]; // array with red apple tiles
  437. int [] black_tiles = new int [apples.length]; // array with black apple tiles
  438. int sn =-1;
  439. // Load apple tiles
  440. for (int i =0 ; i<apples.length ; ++i) {
  441. if (apples[i].getColor() == "red")
  442. red_tiles[i] = apples[i].getAppleTileId();
  443. else
  444. black_tiles[i] = apples[i].getAppleTileId();
  445. }
  446. // Search all tiles for snake heads and tails
  447. for (int i =0; i<N ; ++i) {
  448. for (int j =0 ; j<M ; ++j) {
  449. if ((sn = _search (red_tiles, tiles[i][j])) >= 0)
  450. elemApples[i][j] = "AR" + sn;
  451. else if ((sn = _search (black_tiles, tiles[i][j])) >= 0)
  452. elemApples[i][j] = "AB" + sn;
  453. else
  454. elemApples[i][j] = "___";
  455. }
  456. }
  457. }
  458. /**
  459. * Print element array
  460. * @param elem The element array to print
  461. * @param caption The caption
  462. * @note
  463. * As long as we use tiles[0][0] for first tile, this method
  464. * has to print in reverse Y-axis order. For ex:
  465. * <pre>
  466. * 16 15 14 13
  467. * 09 10 11 12
  468. * 08 07 06 05
  469. * 01 02 03 04
  470. * </pre>
  471. */
  472. private void _printElement (String[][] elem, String caption) {
  473. System.out.print(caption);
  474. for (int i=N-1 ; i>=0 ; --i) {
  475. System.out.println("");
  476. System.out.print(" ");
  477. for (int j =0 ; j<M ; ++j)
  478. System.out.print(elem[i][j] + " ");
  479. }
  480. System.out.println("");
  481. System.out.println("");
  482. }
  483. /**
  484. * Pick a random tile in range [from..to]
  485. * @param from The first tile to consider
  486. * @param to The last tile to consider
  487. * @return The random pick
  488. */
  489. private int _pickRandom (int from, int to) {
  490. return from + (int)(Math.random() * (to - from));
  491. }
  492. /** Search algorithm
  493. * @param array Array to search
  494. * @param elem Element of type T to find inside of array
  495. * @return The status of the operation
  496. * @arg -1 Element not found
  497. * @arg >=0 Element found
  498. */
  499. private int _search (int[] array, int elem) {
  500. for (int i=0 ; i<array.length ; ++i)
  501. if (elem == array[i])
  502. return i;
  503. return -1;
  504. }
  505. /**@} */
  506. /** @name Data members */
  507. /** @{ */
  508. private int N; /**< Board's row count */
  509. private int M; /**< Board's Column count */
  510. private int[][] tiles; /**< Board's tiles */
  511. private Snake[] snakes; /**< Board's snakes */
  512. private Ladder[] ladders; /**< Board's ladders */
  513. private Apple[] apples; /**< Board's apples */
  514. /** @} */
  515. }