@@ -1,5 +1,6 @@ | |||
bin/ | |||
doc/ | |||
out/ | |||
deliverable/ | |||
.classpath | |||
.project |
@@ -7,6 +7,7 @@ | |||
package net.hoo2.auth.labyrinth; | |||
import java.util.ArrayList; | |||
import java.util.function.IntFunction; | |||
/** | |||
@@ -24,26 +25,27 @@ class Board { | |||
* The empty constructor for default initialization | |||
*/ | |||
Board() { | |||
this.N = this.S = this.W = 0; | |||
this.tiles = null; | |||
this.supplies =null; | |||
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 | |||
* @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]"; | |||
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 = W; | |||
this.W = 0; | |||
tiles = new Tile[N*N]; | |||
supplies = new Supply[S]; | |||
walls = new ArrayList<Edge>(); | |||
} | |||
/** | |||
@@ -67,6 +69,7 @@ class Board { | |||
// Clone arrays | |||
this.tiles = b.tiles.clone(); | |||
this.supplies = b.supplies.clone(); | |||
this.walls = b.walls; | |||
} | |||
/** @} */ | |||
@@ -79,7 +82,7 @@ class Board { | |||
* @param theseusTile | |||
* @param minotaurTile | |||
*/ | |||
void createBoard(int theseusTile, int minotaurTile) throws Exception { | |||
void createBoard(int theseusTile, int minotaurTile) { | |||
createTiles(); | |||
createSupplies(theseusTile, minotaurTile); | |||
} | |||
@@ -220,6 +223,15 @@ class Board { | |||
void setSupplies(Supply[] supplies) { this.supplies= supplies; } | |||
/** @} */ | |||
/** @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 | |||
*/ | |||
@@ -228,11 +240,11 @@ class Board { | |||
/** | |||
* 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"); | |||
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; | |||
} | |||
/** | |||
@@ -254,13 +266,43 @@ class Board { | |||
} | |||
} | |||
/** @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); } | |||
/** @} */ | |||
/** | |||
* Predicate to check if a wall creates a closed room. | |||
* | |||
* This algorithm has a complexity of O(N^2) where N represents the total | |||
* number of tiles it should be used with care. | |||
* | |||
* @param tileId The tileId of the wall where the wall is. | |||
* @param direction The wall's relative direction from the tile. | |||
* @return True if the wall creates a closed room, false otherwise. | |||
*/ | |||
private boolean createsClosedRoom (int tileId, int direction) { | |||
// Get a snapshot of the current walls | |||
ArrayList<Edge> w = new ArrayList<Edge>(); | |||
for (Edge it : walls) | |||
w.add(new Edge(it)); | |||
// Create a graph from the current wall(edge) | |||
// and populate the graph with all the edges we can attach. | |||
Graph g = new Graph(new Edge(tileId, direction)); | |||
int size; | |||
do { | |||
size = w.size(); | |||
for (int i =0, S=w.size() ; i<S ; ++i) | |||
if (g.attach(w.get(i))) { | |||
w.remove(i); | |||
--i; --S; | |||
} | |||
} while (size != w.size()); | |||
// Search if a vertex is attached 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`. | |||
@@ -270,6 +312,7 @@ class Board { | |||
* <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 | |||
@@ -286,12 +329,22 @@ class Board { | |||
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); | |||
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; | |||
} | |||
return false; | |||
if (Session.loopGuard && createsClosedRoom(tileId, direction)) | |||
return false; | |||
return true; | |||
} | |||
/** | |||
@@ -326,16 +379,22 @@ class Board { | |||
* @return The number of walls created from the utility. | |||
*/ | |||
private int createBasicTileWalls () { | |||
int tileCount =0; | |||
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); | |||
tileCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0)); | |||
wallCount += ((up?1:0) + (down?1:0) + (left?1:0) + (right?1:0)); | |||
tiles[i] = new Tile (i, up, down, left, right); | |||
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 tileCount; | |||
return wallCount; | |||
} | |||
/** | |||
@@ -352,6 +411,8 @@ class Board { | |||
Position neighbor = new Position(Position.toRow(tileId), Position.toCol(tileId), dir); | |||
tiles[tileId].setWall(dir); | |||
tiles[neighbor.getId()].setWall(Direction.opposite(dir)); | |||
if (Session.loopGuard) | |||
walls.add(new Edge(tileId, dir)); | |||
} | |||
/** | |||
@@ -360,23 +421,23 @@ class Board { | |||
* @param walls The number of walls to create | |||
* @return The number of walls failed to create. | |||
*/ | |||
private int createInnerWalls (int walls) { | |||
private int createInnerWalls () { | |||
ShuffledRange randTiles = new ShuffledRange(0, N*N); | |||
for (int tileId, i =0, shuffleMark =0 ; i<walls ; ++i) { | |||
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 - i; | |||
return walls; | |||
else { // Re-shuffle and continue. | |||
randTiles = new ShuffledRange(0, N*N); | |||
shuffleMark =i; | |||
} | |||
} | |||
} while (!isWallable(tileId)); | |||
++walls; | |||
createInnerWall(tileId); | |||
} | |||
return 0; | |||
} | |||
/** | |||
@@ -456,10 +517,14 @@ class Board { | |||
/** @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 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. | |||
*/ | |||
/** @} */ | |||
} |
@@ -21,9 +21,11 @@ class Const { | |||
* Application wide object to hold settings like values for the session. | |||
*/ | |||
class Session { | |||
static int boardSize = 15; /**< Default board's size (if no one set it via command line) */ | |||
static int supplySize = 4; /**< Default board's supply size (if no one set it via command line) */ | |||
static int wallSize = 4*15-1; /**< Default board's wall size (if no one set it via command line) */ | |||
static int boardSize = 15; /**< Default board's size (if no one set it via command line) */ | |||
static int supplySize = 4; /**< Default board's supply size (if no one set it via command line) */ | |||
static int maxRounds = 100; /**< Default number of rounds per game (if no one set it via command line) */ | |||
static boolean loopGuard = false; /**< When true a wall creation guard is added to prevent closed rooms inside the board */ | |||
static boolean interactive = false; /**< When true each round of the game requires user input */ | |||
} | |||
/** | |||
@@ -209,3 +211,179 @@ class ShuffledRange extends Range { | |||
Collections.shuffle(numbers); | |||
} | |||
} | |||
/** | |||
* @brief | |||
* A utility class used for room prevent algorithm. | |||
* | |||
* This class is the wall representation we use in the room preventing algorithm. | |||
* In this algorithm we represent the crosses between tiles as nodes (V) of a graph and the | |||
* walls as edges. So for example: | |||
* | |||
* _ V = 15 | |||
* / | |||
* +---+---+---+ We have a 4x4=16 vertices board(nodes) and 14 edges(walls). | |||
* | | To represent the vertices on the board we use the | |||
* + +---+ + same trick as the tileId. | |||
* | | | The edges are represented as vertices pairs. | |||
* + + + + <. | |||
* | | | \_ V = 7 | |||
* + +---+---+ | |||
* ^ ^ | |||
* V = 0 V = 3 | |||
* | |||
* @note | |||
* Beside the fact that we prefer this kind of representation of the walls in | |||
* the application we use the one that is suggested from the assignment. This is | |||
* used only in room preventing algorithm. | |||
* @note | |||
* Using this kind of representation we don't have any more the "problem" | |||
* of setting the wall in both neighbor tiles. | |||
*/ | |||
class Edge { | |||
/** | |||
* This constructor as as the interface between the application's wall | |||
* representation and the one based on graph. | |||
* @param tileId The tile id of the wall. | |||
* @param direction The direction of the tile where the wall should be. | |||
*/ | |||
Edge(int tileId, int direction) { | |||
int N = Session.boardSize +1; | |||
switch (direction) { | |||
case Direction.UP: | |||
v1= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId); | |||
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId) + 1; | |||
break; | |||
case Direction.DOWN: | |||
v1= (Position.toRow(tileId))*N + Position.toCol(tileId); | |||
v2= (Position.toRow(tileId))*N + Position.toCol(tileId) + 1; | |||
break; | |||
case Direction.LEFT: | |||
v1= (Position.toRow(tileId))*N + Position.toCol(tileId); | |||
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId); | |||
break; | |||
case Direction.RIGHT: | |||
v1= (Position.toRow(tileId))*N + Position.toCol(tileId) + 1; | |||
v2= (Position.toRow(tileId) + 1)*N + Position.toCol(tileId) +1; | |||
break; | |||
} | |||
} | |||
/** A deep copy contructor */ | |||
Edge(Edge e) { | |||
v1 = e.getV1(); | |||
v2 = e.getV2(); | |||
} | |||
/** Access of the first node of the edge */ | |||
int getV1() { return v1; } | |||
/** Access of the second node of the edge */ | |||
int getV2() { return v2; } | |||
private int v1; /**< First vertex of the edge */ | |||
private int v2; /**< Second vertex of the edge */ | |||
} | |||
/** | |||
* @brief | |||
* Provides a graph functionality for the room preventing algorithm. | |||
* We use a graph to represent the wall structure of the walls. This way | |||
* is easy to find any closed loops. Using graph we transform the problem | |||
* of the closed room in the problem of finding a non simple graph. | |||
* | |||
* If the board has non connected wall structure then we need more than | |||
* one graph to represent it. | |||
* | |||
* An example graph from a board, starting from V=1 is: | |||
* <pre> | |||
* 6---7 8 (1) | |||
* | | / \ | |||
* 3 4 5 (4) (2) | |||
* | | | \ | |||
* 0 1---2 (5)--(8) | |||
* </pre> | |||
*/ | |||
class Graph { | |||
/** | |||
* Constructs a node of the graph using the value of a vertex(node). | |||
* @param v The verteg to attach. | |||
*/ | |||
Graph (int v) { | |||
V = v; | |||
E = new ArrayList<Graph>(); | |||
} | |||
/** | |||
* Constructor that transform an edge into graph. | |||
* @param e The edge to transform. | |||
*/ | |||
Graph (Edge e) { | |||
V = e.getV1(); | |||
E = new ArrayList<Graph>(); | |||
E.add(new Graph(e.getV2())); | |||
} | |||
/** Access to the current vertex */ | |||
int getV() { return V; } | |||
/** Access to the links of the current vertex */ | |||
ArrayList<Graph> getE() { return E; } | |||
/** | |||
* Attach an edge into a graph IFF the graph already has a vertex | |||
* with the same value of one of the vertices of the edge. | |||
* @param e The edge to attach. | |||
* @return The status of the operation. | |||
* @arg True on success | |||
* @arg False on failure | |||
*/ | |||
boolean attach (Edge e) { | |||
return tryAttach(e, 0) > 0; | |||
} | |||
/** | |||
* Counts the number of vertices on the graph with the value of `v` | |||
* @param v The vertex to count | |||
* @return The number of vertices with value `v` | |||
*/ | |||
int count (int v) { | |||
return tryCount (v, 0); | |||
} | |||
/** | |||
* Recursive algorithm that tries to attach an edge into a graph | |||
* IFF the graph already has a vertex. | |||
* with the same value of one of the vertices of the edge. | |||
* @param e The edge to attach. | |||
* @param count An initial count value to feed to the algorithm. | |||
* @return The status of the operation. | |||
* @arg True on success | |||
* @arg False on failure | |||
*/ | |||
private int tryAttach (Edge e, int count) { | |||
for (Graph n: E) | |||
count += n.tryAttach (e, count); | |||
if (V == e.getV1()) { | |||
E.add(new Graph(e.getV2())); | |||
++count; | |||
} | |||
if (V == e.getV2()) { | |||
E.add(new Graph(e.getV1())); | |||
++count; | |||
} | |||
return count; | |||
} | |||
/** | |||
* Recursive algorithm that tries to count the number of vertices | |||
* on the graph with the value of `v` | |||
* @param v The vertex to count | |||
* @param count An initial count value to feed to the algorithm. | |||
* @return The number of vertices with value `v` | |||
*/ | |||
private int tryCount (int v, int count) { | |||
for (Graph n: E) | |||
count = n.tryCount (v, count); | |||
if (V == v) | |||
return ++count; | |||
return count; | |||
} | |||
private int V; /**< The value of the current vertex/node */ | |||
private ArrayList<Graph> E; /**< A list of all the child nodes */ | |||
} |
@@ -68,33 +68,44 @@ public class Game { | |||
Session.boardSize = Integer.parseInt(args[++i]); | |||
break; | |||
case "-w": | |||
case "--walls": | |||
if (i+1 < args.length) | |||
Session.wallSize = Integer.parseInt(args[++i]); | |||
break; | |||
case "-s": | |||
case "--suplies": | |||
if (i+1 < args.length) | |||
Session.supplySize = Integer.parseInt(args[++i]); | |||
break; | |||
case "-r": | |||
case "--rounds": | |||
if (i+1 < args.length) | |||
Session.maxRounds = Integer.parseInt(args[++i]); | |||
break; | |||
case "--norooms": | |||
Session.loopGuard = true; | |||
break; | |||
case "-i": | |||
case "--interactive": | |||
Session.interactive = true; | |||
break; | |||
default: | |||
case "-h": | |||
case "--help": | |||
System.out.println("Labyrinth Game"); | |||
System.out.println(""); | |||
System.out.println("Usage:"); | |||
System.out.println("labyrinth [-b|--board <Num>] [-w|--walls <Num>] [-s|--supplies <Num>]"); | |||
System.out.println("labyrinth [-b|--board <Num>] [-s|--supplies <Num>] [-r|--rounds <Num>] [--norooms] [-i|--interactive]"); | |||
System.out.println("or"); | |||
System.out.println("labyrinth -h|--help"); | |||
System.out.println(""); | |||
System.out.println("\t-b | --board: Sets the size of board's edge."); | |||
System.out.println("\t-w | --walls: Sets the number of walls on the board."); | |||
System.out.println("\t-s | --supplies: Sets the number of supplies on the board."); | |||
System.out.println("\t-h | --help: Print this and exit"); | |||
break; | |||
System.out.println("\nOptions\n"); | |||
System.out.println("-b | --board:\n Sets the size of board's edge.\n"); | |||
System.out.println("-s | --supplies:\n Sets the number of supplies on the board.\n"); | |||
System.out.println("-r | --rounds:\n Sets the maximum number of rounds of the game.\n"); | |||
System.out.println("--norooms:\n Prevents the creation of closed rooms inside the board.\n"); | |||
System.out.println("-i | --interactive:\n Each round requires user input in order to continue.\n"); | |||
System.out.println("-h | --help:\n Print this and exits."); | |||
return false; | |||
} | |||
} | |||
return true; | |||
@@ -111,9 +122,9 @@ public class Game { | |||
// Create a game, a board and 2 players. | |||
Game game = new Game(); | |||
Board board = new Board(Session.boardSize, Session.supplySize, Session.wallSize); | |||
Player T = new Player(1, "Theseus", board, 0); | |||
Player M = new Player(2, "Minotaur", board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||
Board board = new Board(Session.boardSize, Session.supplySize); | |||
Player T = new Player(1, "Theseus", true, board, 0); | |||
Player M = new Player(2, "Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||
// Populate data to the board | |||
board.createBoard(T.playerTileId(), M.playerTileId()); | |||