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.

639 lines
24 KiB

  1. /**
  2. * @file Board.java
  3. *
  4. * @author
  5. * Anastasia Foti AEM:8959
  6. * <anastaskf@ece.auth.gr>
  7. *
  8. * @author
  9. * Christos Choutouridis AEM:8997
  10. * <cchoutou@ece.auth.gr>
  11. */
  12. package host.labyrinth;
  13. import java.util.ArrayList;
  14. import java.util.Arrays;
  15. import java.util.function.IntFunction;
  16. /**
  17. * @brief
  18. * This class is the representation of the games's board
  19. *
  20. * The board is the square arrangement of the tiles. This class is also
  21. * the owner of the tile and supply objects.
  22. */
  23. class Board {
  24. /** @name Constructors */
  25. /** @{ */
  26. /**
  27. * The empty constructor for default initialization
  28. */
  29. Board() {
  30. this.N = 0;
  31. this.S = 0;
  32. this.W = 0;
  33. tiles = null;
  34. supplies =null;
  35. walls = new ArrayList<Edge>();
  36. moves = new ArrayList<Integer[]>();
  37. }
  38. /**
  39. * The main constructor for the application
  40. * @param N The size of each edge of the board
  41. * @param S The number of supplies on the board
  42. */
  43. Board(int N, int S) {
  44. assert (N%2 != 0) : "Board's size has to be an odd number.";
  45. assert (S <= (N*N-2)) : "At least 2 tiles has to be without supplies.";
  46. this.N = Session.boardSize = N;
  47. this.S = S;
  48. this.W = 0;
  49. tiles = new Tile[N*N];
  50. supplies = new Supply[S];
  51. walls = new ArrayList<Edge>();
  52. moves = new ArrayList<Integer[]>();
  53. }
  54. /**
  55. * Deep copy constructor
  56. * @param b The board to copy
  57. *
  58. * @note
  59. * The lack of value semantics in java is (in author's opinion) one of the greatest
  60. * weakness of the language and one of the reasons why it will never be a language
  61. * to care about. To quote Alexander Stepanof's words in "elements of programming" section 1.5:
  62. * "Assignment is a procedure that takes two objects of the same type and makes the first
  63. * object equal to the second without modifying the second".
  64. * In this class we try to cope with this situation knowing that we can not do anything about
  65. * assignment operator. We just add value semantics to the copy constructor and go on with our lifes...
  66. */
  67. Board(Board b) {
  68. // Copy primitives
  69. this.N = b.N;
  70. this.S = b.S;
  71. this.W = b.W;
  72. // Clone arrays
  73. this.tiles = b.tiles.clone();
  74. this.supplies = b.supplies.clone();
  75. for (Edge it: b.walls)
  76. this.walls.add(new Edge(it));
  77. for (Integer[] m : b.moves) {
  78. this.moves.add(m);
  79. }
  80. }
  81. /** @} */
  82. /** @name Board's main application interface */
  83. /** @{ */
  84. /**
  85. * Creates the board with all the requested walls and supplies.
  86. *
  87. * @param theseusTile
  88. * @param minotaurTile
  89. */
  90. void createBoard(int theseusTile, int minotaurTile) {
  91. createTiles();
  92. createSupplies(theseusTile, minotaurTile);
  93. }
  94. /**
  95. * Returns a 2-D array with the string representation of the board.
  96. *
  97. * The rows of the array represent the Y-coordinate and the columns the X-coordinate.
  98. * The only difference is that between each row there is an extra row with the possible
  99. * walls. This way the number of rows of the returning array are 2N+1 and the number of
  100. * columns N+1.\n
  101. * So each tile of the board is represented by 3 strings. One for the north wall, one for
  102. * the body and one for the south wall.
  103. *
  104. * @param theseusTile The current Theseus tile
  105. * @param minotaurTile The current Minotaur tile
  106. * @return The string representation of the board
  107. */
  108. String[][] getStringRepresentation(int theseusTile, int minotaurTile) {
  109. String[][] frame = new String[2*N+1][N];
  110. for (int row=0 ; row<N ; ++row) {
  111. int col;
  112. for (col =0 ; col<N-1 ; ++col)
  113. renderTile(frame, row, col, theseusTile, minotaurTile);
  114. renderSentinelTile(frame, row, col, theseusTile, minotaurTile);
  115. }
  116. return frame;
  117. }
  118. /**
  119. * Print board utility.
  120. * @param sBoard Reference to string representation of the board to print.
  121. *
  122. * @note
  123. * As the lower row addresses of the string representation of the board contain
  124. * the south rows, in order to view the board correctly we have to print the rows
  125. * in the opposite order.
  126. */
  127. void printBoard (String[][] sBoard) {
  128. for (int i=sBoard.length-1 ; i>=0 ; --i) {
  129. for (String it : sBoard[i])
  130. System.out.print(it);
  131. System.out.println();
  132. }
  133. }
  134. /**
  135. * Predicate to check if a direction is Walkable.
  136. *
  137. * A `walkable` direction is a tile direction where:
  138. * <ul>
  139. * <li>The wall is not the DOWN wall from tile (0, 0).
  140. * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
  141. * </ul>
  142. *
  143. * @param tileId The starting tileId.
  144. * @param direction The desired direction.
  145. * @return True if it is walkable.
  146. */
  147. boolean isWalkable(int tileId, int direction) {
  148. return !tiles[tileId].hasWall(direction)
  149. && !(tileId == 0 && direction == Direction.DOWN);
  150. }
  151. /**
  152. * Predicate to check if a direction is Walkable.
  153. *
  154. * A `walkable` direction is a tile direction where:
  155. * <ul>
  156. * <li>The wall is not the DOWN wall from tile (0, 0).
  157. * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
  158. * </ul>
  159. *
  160. * @param row Row position of the starting tile.
  161. * @param col Column position of the starting tile.
  162. * @param direction The desired direction.
  163. * @return True if it is walkable.
  164. */
  165. boolean isWalkable(int row, int col, int direction) {
  166. return !tiles[Position.toID(row, col)].hasWall(direction)
  167. && !(Position.toID(row, col) == 0 && direction == Direction.DOWN);
  168. }
  169. /**
  170. * Utility function to check if there is a supply on the tile or not
  171. * @param tileId The tile to check
  172. * @return Yes/no
  173. */
  174. boolean hasSupply (int tileId) {
  175. return (Const.noSupply != tiles[tileId].hasSupply(supplies)) ? true : false;
  176. }
  177. /**
  178. * Try to pick supply from a tile. If succeed it also erases the
  179. * supply from the board.
  180. *
  181. * @param tileId The tile to check
  182. * @return The id of supply.
  183. * @arg Const.noSupply if there is none
  184. * @arg The ID of supply if there is one.
  185. */
  186. int tryPickSupply(int tileId) {
  187. int supplyId = tiles[tileId].hasSupply(supplies);
  188. if (supplyId != Const.noSupply) {
  189. tiles[tileId].pickSupply(supplies, supplyId);
  190. }
  191. return supplyId;
  192. }
  193. /**
  194. * A plain fair dice functionality provided by the board.
  195. * @return A random direction;
  196. */
  197. int dice () {
  198. ShuffledRange d = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
  199. return d.get();
  200. }
  201. /** @return the size of each site of the board. */
  202. int size () { return N; }
  203. /**
  204. * Boards utility to give access to other player moves.
  205. *
  206. * @param playerId The id of player who asks
  207. * @return The moves data of all other players
  208. */
  209. int[][] getOpponentMoves (int playerId) {
  210. int[][] ret = new int[moves.size()-1][Const.moveItems];
  211. int ii= 0, ri =0;
  212. for (Integer[] m : moves) {
  213. if (ii != playerId)
  214. ret[ri++] = Arrays.stream(m).mapToInt(i->i).toArray();
  215. ++ii;
  216. }
  217. return ret;
  218. }
  219. /**
  220. * Utility function to create player IDs
  221. * @return The generated player id.
  222. */
  223. int generatePlayerId () {
  224. moves.add(null);
  225. return moves.size() -1;
  226. }
  227. /**
  228. * Utility to update the moves of each player.
  229. *
  230. * This function is used by the players to update their position on the board.
  231. * After that a player can read other player positions using getOpponentMoves()
  232. * @see getOpponentMoves()
  233. *
  234. * @param m Reference to new move data
  235. * @param playerId The id of the player who update his/her data.
  236. */
  237. void updateMove(int[] m, int playerId) {
  238. moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new));
  239. }
  240. /** @} */
  241. /**
  242. * @name Accessor/Mutator interface
  243. * @note
  244. * Please consider not to use mutator interface. Its the abstraction killer :(
  245. */
  246. /** @{ */
  247. int getN() { return N; }
  248. int getS() { return S; }
  249. int getW() { return W; }
  250. /**
  251. * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
  252. * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
  253. * @return Reference to inner tiles array.
  254. */
  255. Tile[] getTiles() { return tiles; }
  256. /**
  257. * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
  258. * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
  259. * @return Reference to inner supplies array.
  260. */
  261. Supply[] getSupplies() { return supplies; }
  262. /**
  263. * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
  264. * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
  265. * @return Reference to inner walls array.
  266. */
  267. ArrayList<Edge> getWalls() { return walls; }
  268. /**
  269. * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
  270. * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
  271. * @return Reference to inner walls array.
  272. */
  273. ArrayList<Integer[]> getMoves() { return moves; }
  274. void setN(int N) { this.N = N; }
  275. void setS(int S) { this.S = S; }
  276. void setW(int W) { this.W = W; }
  277. /**
  278. * @param tiles Reference to tiles that we want to act as replacement for the inner tiles array.
  279. * @note Use with care.
  280. * Any call to this function will probably add memory for the garbage collector.
  281. */
  282. void setTiles(Tile[] tiles) { this.tiles = tiles; }
  283. /**
  284. * @param supplies Reference to supplies that we want to act as replacement for the inner supplies array.
  285. * @note Use with care.
  286. * Any call to this function will probably add memory for the garbage collector.
  287. */
  288. void setSupplies(Supply[] supplies) { this.supplies= supplies; }
  289. /**
  290. * @param walls Reference to walls that we want to act as replacement for the inner walls vector.
  291. * @note Use with care.
  292. * Any call to this function will probably add memory for the garbage collector.
  293. */
  294. void setWalls (ArrayList<Edge> walls) { this.walls= walls; }
  295. /**
  296. * @param moves Reference to moves that we want to act as replacement for the inner moves vector.
  297. * @note Use with care.
  298. * Any call to this function will probably add memory for the garbage collector.
  299. */
  300. void setMoves(ArrayList<Integer[]> moves) { this.moves =moves; }
  301. /** @} */
  302. /** @name Sentinel predicates */
  303. /** @{ */
  304. private boolean isLeftSentinel (int tileId) { return (Position.toCol(tileId) == 0); }
  305. private boolean isRightSentinel (int tileId) { return (Position.toCol(tileId) == N-1); }
  306. private boolean isUpSentinel (int tileId) { return (Position.toRow(tileId) == N-1); }
  307. private boolean isDownSentinel (int tileId) { return (Position.toRow(tileId) == 0); }
  308. /** @} */
  309. /**
  310. * @name private functionality of the object
  311. */
  312. /** @{ */
  313. /**
  314. * This function creates randomly all the tiles of the board
  315. */
  316. private void createTiles() {
  317. int wallCount;
  318. wallCount = createBasicTileWalls (); // First create tiles with outer walls
  319. wallCount += createInnerWalls(); // Greedy create as many inner walls we can
  320. W = wallCount;
  321. }
  322. /**
  323. * This function create randomly the board's supplies.
  324. *
  325. * The supplies has to be in separate tiles and in tiles with no player
  326. *
  327. * @param theseusTile The tile of the Theseus
  328. * @param minotaurTile The tile of the Minotaur
  329. */
  330. private void createSupplies(int theseusTile, int minotaurTile) {
  331. ShuffledRange rand = new ShuffledRange(0, N*N); // Make a shuffled range of all tiles
  332. for (int tileId, i=0 ; i<supplies.length ; ++i) {
  333. // Pick a tile as long as there is no player in it
  334. do
  335. tileId = rand.get();
  336. while (tileId == theseusTile || tileId == minotaurTile);
  337. supplies[i] = new Supply(i, tileId);
  338. }
  339. }
  340. /**
  341. * Predicate to check if a wall creates a closed room.
  342. *
  343. * This algorithm has a complexity of @f$ O(N^2logN) @f$ where N represents the total
  344. * number of tiles.
  345. * It should be used with care.
  346. *
  347. * @param tileId The tileId of the wall.
  348. * @param direction The wall's relative direction.
  349. * @return True if the wall creates a closed room, false otherwise.
  350. */
  351. private boolean isRoomCreator (int tileId, int direction) {
  352. // Clone the list of all the walls locally.
  353. ArrayList<Edge> w = new ArrayList<Edge>();
  354. for (Edge it : walls)
  355. w.add(new Edge(it));
  356. // Create the largest possible coherent graph from the list of walls(edges)
  357. Graph g = new Graph(new Edge(tileId, direction));
  358. int size;
  359. do {
  360. size = w.size(); // mark the size (before the pass)
  361. for (int i =0, S=w.size() ; i<S ; ++i) // for each edge(wall) on the local wall list
  362. if (g.attach(w.get(i))) { // can we attach the edge(wall) to the graph ?
  363. w.remove(i); // if yes remove it from the local wall list
  364. --i; --S; // decrease iterator and size to match ArrayList's new values
  365. }
  366. } while (size != w.size()); // If the size hasn't change(no new graph leafs) exit
  367. // Search if a vertex is attached to the graph more than once.
  368. // This means that there is at least 2 links to the same node
  369. // so the graph has a closed loop
  370. for (Edge it : walls) {
  371. if (g.count(it.getV1()) > 1) return true;
  372. if (g.count(it.getV2()) > 1) return true;
  373. }
  374. return false;
  375. }
  376. /**
  377. * Predicate to check if a tile direction is `Wallable`.
  378. *
  379. * A `wallable` direction is a tile direction where:
  380. * <ul>
  381. * <li>The wall is not the DOWN wall from tile (0, 0).
  382. * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
  383. * <li>The neighbor in this direction has at most `Const.maxTileWalls -1` walls.
  384. * <li>The wall does not create a closed room (Optional requirement).
  385. * </ul>
  386. *
  387. * @note
  388. * A wallable direction automatically implies that the direction in not an outer wall.
  389. *
  390. * @param tileId The tile to check.
  391. * @param direction The direction to check
  392. * @return True if the direction is wallable.
  393. */
  394. private boolean isWallableDir (int tileId, int direction) {
  395. // Check list
  396. if (!isWalkable(tileId, direction))
  397. return false;
  398. switch (direction) {
  399. case Direction.UP:
  400. if (tiles[upTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
  401. break;
  402. case Direction.DOWN:
  403. if (tiles[downTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
  404. break;
  405. case Direction.LEFT:
  406. if (tiles[leftTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
  407. break;
  408. case Direction.RIGHT:
  409. if (tiles[rightTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
  410. break;
  411. }
  412. if (Session.loopGuard && isRoomCreator(tileId, direction))
  413. return false;
  414. return true;
  415. }
  416. /**
  417. * Predicate to check if a tile is `Wallable`.
  418. *
  419. * A `wallable` tile is a tile where:
  420. * <ul>
  421. * <li>The tile has at most `Const.maxTileWalls -1` walls.
  422. * <li>There is at least one wallable direction on the tile.
  423. * </ul>
  424. * @param tileId The tile to check
  425. * @return True if the tile is wallable.
  426. */
  427. private boolean isWallable (int tileId) {
  428. // Check list
  429. if (tileId == Const.noTileId)
  430. return false;
  431. if (tiles[tileId].hasWalls() >= Const.maxTileWalls)
  432. return false;
  433. Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
  434. for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get())
  435. if (isWallableDir(tileId, dir))
  436. return true;
  437. return false;
  438. }
  439. /**
  440. * This utility function create/allocate the tiles of the board and create
  441. * the outer walls at the same time.
  442. *
  443. * @return The number of walls created from the utility.
  444. */
  445. private int createBasicTileWalls () {
  446. int wallCount =0;
  447. for (int i =0 ; i< tiles.length ; ++i) {
  448. boolean up = isUpSentinel(i);
  449. boolean down = isDownSentinel(i) && (i != 0);
  450. boolean left = isLeftSentinel(i);
  451. boolean right = isRightSentinel(i);
  452. wallCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0));
  453. tiles[i] = new Tile (i, up, down, left, right);
  454. // If we have loopGuard enable we populate walls also.
  455. if (Session.loopGuard) {
  456. if (up) walls.add(new Edge(i, Direction.UP));
  457. if (down) walls.add(new Edge(i, Direction.DOWN));
  458. if (left) walls.add(new Edge(i, Direction.LEFT));
  459. if (right) walls.add(new Edge(i, Direction.RIGHT));
  460. }
  461. }
  462. return wallCount;
  463. }
  464. /**
  465. * Create randomly a wall in the wallable selected tile.
  466. * @param tileId The wallable tile to create the wall
  467. */
  468. private void createInnerWall(int tileId) {
  469. // Randomly pick a wallable direction in that tile.
  470. ShuffledRange randDirections = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
  471. int dir;
  472. do
  473. dir = randDirections.get();
  474. while (!isWallableDir(tileId, dir));
  475. // Add wall to tileId and the adjacent tileId
  476. Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir);
  477. tiles[tileId].setWall(dir);
  478. tiles[neighbor.getId()].setWall(Direction.opposite(dir));
  479. // If we have loopGuard enable we populate walls also.
  480. if (Session.loopGuard)
  481. walls.add(new Edge(tileId, dir));
  482. }
  483. /**
  484. * This utility creates the inner walls of the board.
  485. *
  486. * @return The number of walls failed to create.
  487. */
  488. private int createInnerWalls () {
  489. ShuffledRange randTiles = new ShuffledRange(0, N*N);
  490. for (int tileId, walls =0, shuffleMark =0 ; true ; ) {
  491. // randomly pick a wallable tile.
  492. do {
  493. if ((tileId = randTiles.get())== Const.EOR) {
  494. if (walls == shuffleMark) // Wallable tiles exhausted.
  495. return walls;
  496. else { // Re-shuffle and continue.
  497. randTiles = new ShuffledRange(0, N*N);
  498. shuffleMark =walls;
  499. }
  500. }
  501. } while (!isWallable(tileId));
  502. ++walls;
  503. createInnerWall(tileId);
  504. }
  505. }
  506. /**
  507. * Utility to get the body (center line) of the string representation of the tile.
  508. *
  509. * @param row What board's row to get.
  510. * @param col What board's column to get.
  511. * @param theseusTile The current tile of the Theseus.
  512. * @param minotaurTile The current tile of the Minotaur.
  513. * @return The body string
  514. */
  515. private String getTileBody (int row, int col, int theseusTile, int minotaurTile) {
  516. int tileId = Position.toID(row, col);
  517. boolean T = (tileId == theseusTile) ? true : false;
  518. boolean M = (tileId == minotaurTile) ? true : false;
  519. int S = tiles[tileId].hasSupply(supplies);
  520. if (T && !M) return " T ";
  521. else if (T && M) return "T+M";
  522. else if (M) {
  523. if (S == Const.noSupply) return " M ";
  524. else return "M+s";
  525. }
  526. else if (S != Const.noSupply)
  527. return String.format("s%02d", S+1);
  528. else return " ";
  529. }
  530. /**
  531. * Utility to render the 3 strings of the tile in the representation frame.
  532. *
  533. * @param frame Reference to the frame to print into.
  534. * @param row The board's row to print.
  535. * @param col The board's column to print.
  536. * @param theseusTile The current tile of the Theseus.
  537. * @param minotaurTile The current tile of the Minotaur.
  538. */
  539. private void renderTile(String[][] frame, int row, int col, int theseusTile, int minotaurTile) {
  540. IntFunction<Integer> toframe = (r)->{ return 2*r+1; };
  541. int tileId = Position.toID(row, col);
  542. frame[toframe.apply(row)+1][col] = tiles[tileId].hasWall(Direction.UP) ? "+---" : "+ ";
  543. frame[toframe.apply(row) ][col] = (tiles[tileId].hasWall(Direction.LEFT)? "|" : " ")
  544. + getTileBody(row, col, theseusTile, minotaurTile);
  545. frame[toframe.apply(row)-1][col] = tiles[tileId].hasWall(Direction.DOWN) ? "+---" : "+ ";
  546. }
  547. /**
  548. * Utility to render the 3 strings of the tile in the representation frame in
  549. * the case the tile lies in the east wall. We call these tiles `sentinel tiles`
  550. *
  551. * @param frame Reference to the frame to print into.
  552. * @param row The board's row to print.
  553. * @param col The board's column to print.
  554. * @param theseusTile The current tile of the Theseus.
  555. * @param minotaurTile The current tile of the Minotaur.
  556. */
  557. private void renderSentinelTile(String[][] frame, int row, int col, int theseusTile, int minotaurTile ) {
  558. IntFunction<Integer> toframe = (r)->{ return 2*r+1; };
  559. int tileId = Position.toID(row, col);
  560. frame[toframe.apply(row)+1][col] = tiles[tileId].hasWall(Direction.UP) ? "+---+" : "+ +";
  561. frame[toframe.apply(row) ][col] = (tiles[tileId].hasWall(Direction.LEFT)? "|" : " ")
  562. + getTileBody(row, col, theseusTile, minotaurTile)
  563. + (tiles[tileId].hasWall(Direction.RIGHT)? "|" : " ");
  564. frame[toframe.apply(row)-1][col] = tiles[tileId].hasWall(Direction.DOWN) ? "+---+" : "+ +";
  565. }
  566. /** @} */
  567. /** @name Neighbor access lambdas */
  568. /** @{ */
  569. private IntFunction<Integer> leftTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)-1); };
  570. private IntFunction<Integer> rightTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)+1); };
  571. private IntFunction<Integer> upTileId = (id) -> { return Position.toID(Position.toRow(id)+1, Position.toCol(id) ); };
  572. private IntFunction<Integer> downTileId = (id) -> { return Position.toID(Position.toRow(id)-1, Position.toCol(id) ); };
  573. /** @} */
  574. /** @name Class data */
  575. /** @{ */
  576. private int N; /**< The size of each edge of the board */
  577. private int S; /**< The number of the supplies on the board */
  578. private int W; /**< The number of walls on the board */
  579. private Tile[] tiles; /**< Array to hold all the tiles for the board */
  580. private Supply[] supplies; /**< Array to hold all the supplies on the board */
  581. private ArrayList<Edge> walls; /**<
  582. * Array to hold all the walls using the edge representation
  583. * required by the closed room preventing algorithm.
  584. */
  585. private ArrayList<Integer[]> moves;
  586. /** @} */
  587. }