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.

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