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.

463 lines
17 KiB

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