|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- /**
- * @file Board.java
- *
- * @author
- * Christos Choutouridis
- * <cchoutou@ece.auth.gr>
- * AEM:8997
- */
-
- package host.labyrinth;
-
- import java.util.ArrayList;
- 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 = 0;
- this.S = 0;
- this.W = 0;
- tiles = null;
- supplies =null;
- walls = new ArrayList<Edge>();
- }
-
- /**
- * 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
- */
- Board(int N, int S) {
- assert (N%2 != 0) : "Board's size has to be an odd number.";
- this.N = Session.boardSize = N;
- this.S = S;
- this.W = 0;
- tiles = new Tile[N*N];
- supplies = new Supply[S];
- walls = new ArrayList<Edge>();
- }
-
- /**
- * 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();
- this.walls = b.walls;
- }
- /** @} */
-
- /** @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) {
- 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<N ; ++row) {
- int col;
- for (col =0 ; col<N-1 ; ++col)
- renderTile(frame, row, col, theseusTile, minotaurTile);
- renderSentinelTile(frame, row, col, theseusTile, minotaurTile);
- }
- return frame;
- }
-
- /**
- * Print board utility.
- * @param sBoard Reference to string representation of the board to print.
- *
- * @note
- * As the lower row addresses of the string representation of the board contain
- * the south rows, in order to view the board correctly we have to print the rows
- * in the opposite order.
- */
- void printBoard (String[][] sBoard) {
- for (int i=sBoard.length-1 ; i>=0 ; --i) {
- for (String it : sBoard[i])
- System.out.print(it);
- System.out.println();
- }
- }
-
- /**
- * Predicate to check if a direction is Walkable.
- *
- * A `walkable` direction is a tile direction where:
- * <ul>
- * <li>The wall is not the DOWN wall from tile (0, 0).
- * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
- * </ul>
- *
- * @param tileId The starting tileId.
- * @param direction The desired direction.
- * @return True if it is walkable.
- */
- boolean isWalkable(int tileId, int direction) {
- return !tiles[tileId].hasWall(direction)
- && !(tileId == 0 && direction == Direction.DOWN);
- }
-
- /**
- * Predicate to check if a direction is Walkable.
- *
- * A `walkable` direction is a tile direction where:
- * <ul>
- * <li>The wall is not the DOWN wall from tile (0, 0).
- * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
- * </ul>
- *
- * @param row Row position of the starting tile.
- * @param col Column position of the starting tile.
- * @param direction The desired direction.
- * @return True if it is walkable.
- */
- boolean isWalkable(int row, int col, int direction) {
- return !tiles[Position.toID(row, col)].hasWall(direction)
- && !(Position.toID(row, col) == 0 && direction == Direction.DOWN);
- }
-
- /**
- * Try to pick supply from a tile. If succeed it also erases the
- * supply from the board.
- *
- * @param tileId The tile to check
- * @return The id of supply.
- * @arg Const.noSupply if there is none
- * @arg The ID of supply if there is one.
- */
- int tryPickSupply(int tileId) {
- int supplyId = tiles[tileId].hasSupply(supplies);
- if (supplyId != Const.noSupply) {
- tiles[tileId].pickSupply(supplies, supplyId);
- }
- return supplyId;
- }
-
- /**
- * A plain fair dice functionality provided by the board.
- * @return A random direction;
- */
- int dice () {
- ShuffledRange d = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
- return d.get();
- }
-
- /** @return the size of each site of the board. */
- int size () { return N; }
- /** @} */
-
- /**
- * @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".
- * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
- * @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".
- * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
- * @return Reference to inner supplies array.
- */
- Supply[] getSupplies() { return supplies; }
-
- /**
- * @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
- * <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
- * @return Reference to inner walls array.
- */
- ArrayList<Edge> getWalls() { return walls; }
-
- 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; }
-
- /**
- * @param walls Reference to walls that we want to act as replacement for the inner walls vector.
- * @note Use with care.
- * Any call to this function will probably add memory for the garbage collector.
- */
- void setWalls (ArrayList<Edge> walls) { this.walls= walls; }
-
- /** @} */
-
-
- /** @name Sentinel predicates */
- /** @{ */
- private boolean isLeftSentinel (int tileId) { return (Position.toCol(tileId) == 0); }
- private boolean isRightSentinel (int tileId) { return (Position.toCol(tileId) == N-1); }
- private boolean isUpSentinel (int tileId) { return (Position.toRow(tileId) == N-1); }
- private boolean isDownSentinel (int tileId) { return (Position.toRow(tileId) == 0); }
- /** @} */
-
- /**
- * @name private functionality of the object
- */
- /** @{ */
-
- /**
- * This function creates randomly all the tiles of the board
- */
- private void createTiles() {
- int wallCount;
- wallCount = createBasicTileWalls (); // First create tiles with outer walls
- wallCount += createInnerWalls(); // Greedy create as many inner walls we can
- W = wallCount;
- }
-
- /**
- * 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<supplies.length ; ++i) {
- // Pick a tile as long as there is no player in it
- do
- tileId = rand.get();
- while (tileId == theseusTile || tileId == minotaurTile);
- supplies[i] = new Supply(i, tileId);
- }
- }
-
- /**
- * Predicate to check if a wall creates a closed room.
- *
- * This algorithm has a complexity of @f$ O(N^2logN) @f$ where N represents the total
- * number of tiles.
- * It should be used with care.
- *
- * @param tileId The tileId of the wall.
- * @param direction The wall's relative direction.
- * @return True if the wall creates a closed room, false otherwise.
- */
- private boolean isRoomCreator (int tileId, int direction) {
- // Clone the list of all the walls locally.
- ArrayList<Edge> w = new ArrayList<Edge>();
- for (Edge it : walls)
- w.add(new Edge(it));
- // Create the largest possible coherent graph from the list of walls(edges)
- Graph g = new Graph(new Edge(tileId, direction));
- int size;
- do {
- size = w.size(); // mark the size (before the pass)
- for (int i =0, S=w.size() ; i<S ; ++i) // for each edge(wall) on the local wall list
- if (g.attach(w.get(i))) { // can we attach the edge(wall) to the graph ?
- w.remove(i); // if yes remove it from the local wall list
- --i; --S; // decrease iterator and size to match ArrayList's new values
- }
- } while (size != w.size()); // If the size hasn't change(no new graph leafs) exit
-
- // Search if a vertex is attached to the graph more than once.
- // This means that there is at least 2 links to the same node
- // so the graph has a closed loop
- for (Edge it : walls) {
- if (g.count(it.getV1()) > 1) return true;
- if (g.count(it.getV2()) > 1) return true;
- }
- return false;
- }
-
- /**
- * Predicate to check if a tile direction is `Wallable`.
- *
- * A `wallable` direction is a tile direction where:
- * <ul>
- * <li>The wall is not the DOWN wall from tile (0, 0).
- * <li>There is not already a wall in the desired direction. (Implies no sentinel tile).
- * <li>The neighbor in this direction has at most `Const.maxTileWalls -1` walls.
- * <li>The wall does not create a closed room (Optional requirement).
- * </ul>
- *
- * @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 (!isWalkable(tileId, direction))
- return false;
- switch (direction) {
- case Direction.UP:
- if (tiles[upTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
- break;
- case Direction.DOWN:
- if (tiles[downTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
- break;
- case Direction.LEFT:
- if (tiles[leftTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
- break;
- case Direction.RIGHT:
- if (tiles[rightTileId.apply(tileId)].hasWalls() >= Const.maxTileWalls) return false;
- break;
- }
- if (Session.loopGuard && isRoomCreator(tileId, direction))
- return false;
- return true;
- }
-
- /**
- * Predicate to check if a tile is `Wallable`.
- *
- * A `wallable` tile is a tile where:
- * <ul>
- * <li>The tile has at most `Const.maxTileWalls -1` walls.
- * <li>There is at least one wallable direction on the tile.
- * </ul>
- * @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(DirRange.Begin, DirRange.End, DirRange.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 wallCount =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);
- wallCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0));
- tiles[i] = new Tile (i, up, down, left, right);
- // If we have loopGuard enable we populate walls also.
- if (Session.loopGuard) {
- if (up) walls.add(new Edge(i, Direction.UP));
- if (down) walls.add(new Edge(i, Direction.DOWN));
- if (left) walls.add(new Edge(i, Direction.LEFT));
- if (right) walls.add(new Edge(i, Direction.RIGHT));
- }
- }
- return wallCount;
- }
-
- /**
- * 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(DirRange.Begin, DirRange.End, DirRange.Step);
- int dir;
- do
- dir = randDirections.get();
- while (!isWallableDir(tileId, dir));
- // Add wall to tileId and the adjacent tileId
- Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir);
- tiles[tileId].setWall(dir);
- tiles[neighbor.getId()].setWall(Direction.opposite(dir));
- // If we have loopGuard enable we populate walls also.
- if (Session.loopGuard)
- walls.add(new Edge(tileId, dir));
- }
-
- /**
- * This utility creates the inner walls of the board.
- *
- * @return The number of walls failed to create.
- */
- private int createInnerWalls () {
- ShuffledRange randTiles = new ShuffledRange(0, N*N);
- for (int tileId, i =0, walls =0, shuffleMark =0 ; true ; ) {
- // randomly pick a wallable tile.
- do {
- if ((tileId = randTiles.get())== Const.noTileId) {
- if (i == shuffleMark) // Wallable tiles exhausted.
- return walls;
- else { // Re-shuffle and continue.
- randTiles = new ShuffledRange(0, N*N);
- shuffleMark =i;
- }
- }
- } while (!isWallable(tileId));
- ++walls;
- createInnerWall(tileId);
- }
- }
-
- /**
- * Utility to get the body (center line) of the string representation of the tile.
- *
- * @param row What board's row to get.
- * @param col What board's column to get.
- * @param theseusTile The current tile of the Theseus.
- * @param minotaurTile The current tile of the Minotaur.
- * @return The body string
- */
- private String getTileBody (int row, int col, int theseusTile, int minotaurTile) {
- int tileId = Position.toID(row, col);
- boolean T = (tileId == theseusTile) ? true : false;
- boolean M = (tileId == minotaurTile) ? true : false;
- int S = tiles[tileId].hasSupply(supplies);
-
- if (T && !M) return " T ";
- else if (T && M) return "T+M";
- else if (M) {
- if (S == Const.noSupply) return " M ";
- else return "M+s";
- }
- else if (S != Const.noSupply)
- return String.format("s%02d", S+1);
- else return " ";
- }
-
- /**
- * Utility to render the 3 strings of the tile in the representation frame.
- *
- * @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 renderTile(String[][] frame, int row, int col, int theseusTile, int minotaurTile) {
- IntFunction<Integer> 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<Integer> 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<Integer> leftTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)-1); };
- private IntFunction<Integer> rightTileId = (id) -> { return Position.toID(Position.toRow(id), Position.toCol(id)+1); };
- private IntFunction<Integer> upTileId = (id) -> { return Position.toID(Position.toRow(id)+1, Position.toCol(id) ); };
- private IntFunction<Integer> 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 */
- private ArrayList<Edge> walls; /**<
- * Array to hold all the walls using the edge representation
- * required by the closed room preventing algorithm.
- */
- /** @} */
- }
|