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.

570 lines
20 KiB

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