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.

562 lines
21 KiB

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