/** * @file Board.java * * @author Christos Choutouridis AEM:8997 * @email cchoutou@ece.auth.gr */ package net.hoo2.auth.labyrinth; import java.util.function.IntFunction; /** * @brief * This class is the representation of the games's board * * The board is the square arrangement of the tiles. This class is also * the owner of the tile and supply objects. */ class Board { /** @name Constructors */ /** @{ */ /** * The empty constructor for default initialization */ Board() { this.N = this.S = this.W = 0; this.tiles = null; this.supplies =null; } /** * The main constructor for the application * @param N The size of each edge of the board * @param S The number of supplies on the board * @param W The number of walls on the board */ Board(int N, int S, int W) { assert (W>= 4*N-1 && W<=(3*N*N+1)/2 ) : "Boards walls has to be in the range [4N-1, (3N^2+1)/2]"; this.N = Session.boardSize = N; this.S = S; this.W = W; tiles = new Tile[N*N]; supplies = new Supply[S]; } /** * Deep copy constructor * @param b The board to copy * * @note * The lack of value semantics in java is (in author's opinion) one of the greatest * weakness of the language and one of the reasons why it will never be a language * to care about. To quote Alexander Stepanof's words in "elements of programming" section 1.5: * "Assignment is a procedure that takes two objects of the same type and makes the first * object equal to the second without modifying the second". * In this class we try to cope with this situation knowing that we can not do anything about * assignment operator. We just add value semantics to the copy constructor and go on with our lifes... */ Board(Board b) { // Copy primitives this.N = b.N; this.S = b.S; this.W = b.W; // Clone arrays this.tiles = b.tiles.clone(); this.supplies = b.supplies.clone(); } /** @} */ /** @name Board's main application interface */ /** @{ */ /** * Creates the board with all the requested walls and supplies. * * @param theseusTile * @param minotaurTile */ void createBoard(int theseusTile, int minotaurTile) throws Exception { createTiles(); createSupplies(theseusTile, minotaurTile); } /** * Returns a 2-D array with the string representation of the board. * * The rows of the array represent the Y-coordinate and the columns the X-coordinate. * The only difference is that between each row there is an extra row with the possible * walls. This way the number of rows of the returning array are 2N+1 and the number of * columns N+1.\n * So each tile of the board is represented by 3 strings. One for the north wall, one for * the body and one for the south wall. * * @param theseusTile The current Theseus tile * @param minotaurTile The current Minotaur tile * @return The string representation of the board */ String[][] getStringRepresentation(int theseusTile, int minotaurTile) { String[][] frame = new String[2*N+1][N]; for (int row=0 ; row=0 ; --i) { for (String it : sBoard[i]) System.out.print(it); System.out.println(); } } /** @} */ /** * @name Accessor/Mutator interface * @note * Please consider not to use mutator interface. Its the abstraction killer :( */ /** @{ */ int getN() { return N; } int getS() { return S; } int getW() { return W; } /** * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure". * see also here * @return Reference to inner tiles array. */ Tile[] getTiles() { return tiles; } /** * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure". * see also here * @return Reference to inner supplies array. */ Supply[] getSupplies() { return supplies; } void setN(int N) { this.N = N; } void setS(int S) { this.S = S; } void setW(int W) { this.W = W; } /** * @param tiles Reference to tiles that we want to act as replacement for the inner tiles array. * @note Use with care. * Any call to this function will probably add memory for the garbage collector. */ void setTiles(Tile[] tiles) { this.tiles = tiles; } /** * @param supplies Reference to supplies that we want to act as replacement for the inner supplies array. * @note Use with care. * Any call to this function will probably add memory for the garbage collector. */ void setSupplies(Supply[] supplies) { this.supplies= supplies; } /** @} */ /** * @name private functionality of the object */ /** @{ */ /** * This function creates randomly all the tiles of the board */ private void createTiles() throws Exception { int wallPool = W; wallPool -= createBasicTileWalls (); // First create tiles with outer walls if (createInnerWalls(wallPool) > 0) // Create inner walls with the rest of the requested walls throw new Exception("Can not create the requested number of walls"); } /** * This function create randomly the board's supplies. * * The supplies has to be in separate tiles and in tiles with no player * * @param theseusTile The tile of the Theseus * @param minotaurTile The tile of the Minotaur */ private void createSupplies(int theseusTile, int minotaurTile) { ShuffledRange rand = new ShuffledRange(0, N*N); // Make a shuffled range of all tiles for (int tileId, i=0 ; i *
  • The wall is not the DOWN wall from tile (0, 0). *
  • There is not already a wall in the desired direction. (Implies no sentinel tile). *
  • The neighbor in this direction has at most `Const.maxTileWalls -1` walls. * * * @note * A wallable direction automatically implies that the direction in not an outer wall. * * @param tileId The tile to check. * @param direction The direction to check * @return True if the direction is wallable. */ private boolean isWallableDir (int tileId, int direction) { // Check list if (tileId == 0 && direction == Direction.DOWN) return false; if (tiles[tileId].hasWall(direction)) return false; switch (direction) { case Direction.UP: return (tiles[upTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); case Direction.DOWN: return (tiles[downTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); case Direction.LEFT: return (tiles[leftTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); case Direction.RIGHT:return (tiles[rightTileId.apply(tileId)].hasWalls() < Const.maxTileWalls); } return false; } /** * Predicate to check if a tile is `Wallable`. * * A `wallable` tile is a tile where: *
      *
    • The tile has at most `Const.maxTileWalls -1` walls. *
    • There is at least one wallable direction on the tile. *
    * @param tileId The tile to check * @return True if the tile is wallable. */ private boolean isWallable (int tileId) { // Check list if (tileId == Const.noTileId) return false; if (tiles[tileId].hasWalls() >= Const.maxTileWalls) return false; Range dirs = new Range(Direction.Begin, Direction.End, Direction.Step); for (int dir ; (dir = dirs.get()) != Const.noTileId ; ) if (isWallableDir(tileId, dir)) return true; return false; } /** * This utility function create/allocate the tiles of the board and create * the outer walls at the same time. * * @return The number of walls created from the utility. */ private int createBasicTileWalls () { int tileCount =0; for (int i =0 ; i< tiles.length ; ++i) { boolean up = isUpSentinel(i); boolean down = isDownSentinel(i) && (i != 0); boolean left = isLeftSentinel(i); boolean right = isRightSentinel(i); tileCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0)); tiles[i] = new Tile (i, up, down, left, right); } return tileCount; } /** * Create randomly a wall in the wallable selected tile. * @param tileId The wallable tile to create the wall */ private void createInnerWall(int tileId) { // Randomly pick a wallable direction in that tile. ShuffledRange randDirections = new ShuffledRange(Direction.Begin, Direction.End, Direction.Step); int dir; do dir = randDirections.get(); while (!isWallableDir(tileId, dir)); Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir); tiles[tileId].setWall(dir); tiles[neighbor.getId()].setWall(Direction.opposite(dir)); } /** * This utility creates the inner walls of the board. * * @param walls The number of walls to create * @return The number of walls failed to create. */ private int createInnerWalls (int walls) { ShuffledRange randTiles = new ShuffledRange(0, N*N); for (int tileId, i =0, shuffleMark =0 ; i toframe = (r)->{ return 2*r+1; }; int tileId = Position.toID(row, col); frame[toframe.apply(row)+1][col] = tiles[tileId].hasWall(Direction.UP) ? "+---" : "+ "; frame[toframe.apply(row) ][col] = (tiles[tileId].hasWall(Direction.LEFT)? "|" : " ") + getTileBody(row, col, theseusTile, minotaurTile); frame[toframe.apply(row)-1][col] = tiles[tileId].hasWall(Direction.DOWN) ? "+---" : "+ "; } /** * Utility to render the 3 strings of the tile in the representation frame in * the case the tile lies in the east wall. We call these tiles `sentinel tiles` * * @param frame Reference to the frame to print into. * @param row The board's row to print. * @param col The board's column to print. * @param theseusTile The current tile of the Theseus. * @param minotaurTile The current tile of the Minotaur. */ private void renderSentinelTile(String[][] frame, int row, int col, int theseusTile, int minotaurTile ) { IntFunction toframe = (r)->{ return 2*r+1; }; int tileId = Position.toID(row, col); frame[toframe.apply(row)+1][col] = tiles[tileId].hasWall(Direction.UP) ? "+---+" : "+ +"; frame[toframe.apply(row) ][col] = (tiles[tileId].hasWall(Direction.LEFT)? "|" : " ") + getTileBody(row, col, theseusTile, minotaurTile) + (tiles[tileId].hasWall(Direction.RIGHT)? "|" : " "); frame[toframe.apply(row)-1][col] = tiles[tileId].hasWall(Direction.DOWN) ? "+---+" : "+ +"; } /** @} */ /** @name Neighbor access lambdas */ /** @{ */ private IntFunction leftTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)-1); }; private IntFunction rightTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)+1); }; private IntFunction upTileId = (id) -> { return Position.toID(Position.toRow(id)+1, Position.toCol(id) ); }; private IntFunction downTileId = (id) -> { return Position.toID(Position.toRow(id)-1, Position.toCol(id) ); }; /** @} */ /** @name Class data */ /** @{ */ private int N; /**< The size of each edge of the board */ private int S; /**< The number of the supplies on the board */ private int W; /**< The number of walls on the board */ private Tile[] tiles; /**< Array to hold all the tiles for the board */ private Supply[] supplies; /**< Array to hold all the supplies on the board */ /** @} */ }