@@ -13,6 +13,7 @@ | |||||
package host.labyrinth; | package host.labyrinth; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | |||||
import java.util.function.IntFunction; | import java.util.function.IntFunction; | ||||
/** | /** | ||||
@@ -36,6 +37,7 @@ class Board { | |||||
tiles = null; | tiles = null; | ||||
supplies =null; | supplies =null; | ||||
walls = new ArrayList<Edge>(); | walls = new ArrayList<Edge>(); | ||||
moves = new ArrayList<Integer[]>(); | |||||
} | } | ||||
/** | /** | ||||
@@ -52,6 +54,7 @@ class Board { | |||||
tiles = new Tile[N*N]; | tiles = new Tile[N*N]; | ||||
supplies = new Supply[S]; | supplies = new Supply[S]; | ||||
walls = new ArrayList<Edge>(); | walls = new ArrayList<Edge>(); | ||||
moves = new ArrayList<Integer[]>(); | |||||
} | } | ||||
/** | /** | ||||
@@ -77,6 +80,9 @@ class Board { | |||||
this.supplies = b.supplies.clone(); | this.supplies = b.supplies.clone(); | ||||
for (Edge it: b.walls) | for (Edge it: b.walls) | ||||
this.walls.add(new Edge(it)); | this.walls.add(new Edge(it)); | ||||
for (Integer[] m : b.moves) { | |||||
this.moves.add(m); | |||||
} | |||||
} | } | ||||
/** @} */ | /** @} */ | ||||
@@ -174,6 +180,15 @@ class Board { | |||||
&& !(Position.toID(row, col) == 0 && direction == Direction.DOWN); | && !(Position.toID(row, col) == 0 && direction == Direction.DOWN); | ||||
} | } | ||||
/** | |||||
* Utility function to check if there is a supply on the tile or not | |||||
* @param tileId The tile to check | |||||
* @return Yes/no | |||||
*/ | |||||
boolean hasSupply (int tileId) { | |||||
return (Const.noSupply != tiles[tileId].hasSupply(supplies)) ? true : false; | |||||
} | |||||
/** | /** | ||||
* Try to pick supply from a tile. If succeed it also erases the | * Try to pick supply from a tile. If succeed it also erases the | ||||
* supply from the board. | * supply from the board. | ||||
@@ -191,6 +206,7 @@ class Board { | |||||
return supplyId; | return supplyId; | ||||
} | } | ||||
/** | /** | ||||
* A plain fair dice functionality provided by the board. | * A plain fair dice functionality provided by the board. | ||||
* @return A random direction; | * @return A random direction; | ||||
@@ -202,6 +218,27 @@ class Board { | |||||
/** @return the size of each site of the board. */ | /** @return the size of each site of the board. */ | ||||
int size () { return N; } | int size () { return N; } | ||||
int[][] getOpponentMoves (int playerId) { | |||||
int[][] ret = new int[moves.size()-1][Const.moveItems]; | |||||
int ii= 0, ri =0; | |||||
for (Integer[] m : moves) { | |||||
if (ii != playerId) | |||||
ret[ri++] = Arrays.stream(m).mapToInt(i->i).toArray(); | |||||
++ii; | |||||
} | |||||
return ret; | |||||
} | |||||
int generatePlayerId () { | |||||
moves.add(null); | |||||
return moves.size() -1; | |||||
} | |||||
void updateMove(int[] m, int playerId) { | |||||
moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new)); | |||||
} | |||||
/** @} */ | /** @} */ | ||||
/** | /** | ||||
@@ -233,6 +270,13 @@ class Board { | |||||
*/ | */ | ||||
ArrayList<Edge> getWalls() { return walls; } | ArrayList<Edge> getWalls() { return walls; } | ||||
/** | |||||
* @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<Integer[]> getMoves() { return moves; } | |||||
void setN(int N) { this.N = N; } | void setN(int N) { this.N = N; } | ||||
void setS(int S) { this.S = S; } | void setS(int S) { this.S = S; } | ||||
void setW(int W) { this.W = W; } | void setW(int W) { this.W = W; } | ||||
@@ -257,6 +301,13 @@ class Board { | |||||
*/ | */ | ||||
void setWalls (ArrayList<Edge> walls) { this.walls= walls; } | void setWalls (ArrayList<Edge> walls) { this.walls= walls; } | ||||
/** | |||||
* @param moves Reference to moves that we want to act as replacement for the inner moves vector. | |||||
* @note Use with care. | |||||
* Any call to this function will probably add memory for the garbage collector. | |||||
*/ | |||||
void setMoves(ArrayList<Integer[]> moves) { this.moves =moves; } | |||||
/** @} */ | /** @} */ | ||||
@@ -562,5 +613,6 @@ class Board { | |||||
* Array to hold all the walls using the edge representation | * Array to hold all the walls using the edge representation | ||||
* required by the closed room preventing algorithm. | * required by the closed room preventing algorithm. | ||||
*/ | */ | ||||
private ArrayList<Integer[]> moves; | |||||
/** @} */ | /** @} */ | ||||
} | } |
@@ -13,6 +13,7 @@ package host.labyrinth; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.function.IntFunction; | |||||
/** | /** | ||||
* Class to hold constant values for entire application | * Class to hold constant values for entire application | ||||
@@ -20,8 +21,14 @@ import java.util.Collections; | |||||
class Const { | class Const { | ||||
static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */ | static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */ | ||||
static final int noSupply =-1; /**< Number to indicate the absent of supply */ | static final int noSupply =-1; /**< Number to indicate the absent of supply */ | ||||
static final int noOpponent =-1; /**< Number to indicate the absent of supply */ | |||||
static final int noTileId =-1; /**< Number to indicate wrong tileId */ | static final int noTileId =-1; /**< Number to indicate wrong tileId */ | ||||
static final int EOR =-1; /**< Number to indicate the End Of Range */ | static final int EOR =-1; /**< Number to indicate the End Of Range */ | ||||
static final int moveItems =4; /**< The number of items return by move() */ | |||||
static final int viewDistance =3; /**< The max distance of the Heuristic player's ability to see */ | |||||
static final double opponentFactor =1.0; | |||||
static final double supplyFactor =0.65; | |||||
} | } | ||||
/** | /** | ||||
* Application wide object to hold settings like values for the session. | * Application wide object to hold settings like values for the session. | ||||
@@ -34,23 +41,6 @@ class Session { | |||||
static boolean interactive = false; /**< When true each round of the game requires user input */ | static boolean interactive = false; /**< When true each round of the game requires user input */ | ||||
} | } | ||||
/** | |||||
* Helper C++-like enumerator class to hold direction | |||||
*/ | |||||
class Direction { | |||||
static final int UP =1; /**< North direction */ | |||||
static final int RIGHT =3; /**< East direction */ | |||||
static final int DOWN =5; /**< South direction */ | |||||
static final int LEFT =7; /**< West direction */ | |||||
/** | |||||
* Utility to get the opposite direction. | |||||
* @param direction Input direction | |||||
* @return The opposite direction | |||||
*/ | |||||
static int opposite (int direction) { return (direction+4)%DirRange.End; } | |||||
} | |||||
/** | /** | ||||
* Helper C++ like enumerator class for direction ranged loops. | * Helper C++ like enumerator class for direction ranged loops. | ||||
* | * | ||||
@@ -69,6 +59,36 @@ class DirRange { | |||||
static final int Step =2; /**< Step for iterator style direction */ | static final int Step =2; /**< Step for iterator style direction */ | ||||
} | } | ||||
/** | |||||
* Helper C++-like enumerator class to hold direction | |||||
*/ | |||||
class Direction { | |||||
static final int UP =1; /**< North direction */ | |||||
static final int RIGHT =3; /**< East direction */ | |||||
static final int DOWN =5; /**< South direction */ | |||||
static final int LEFT =7; /**< West direction */ | |||||
/** | |||||
* Utility to get the opposite direction. | |||||
* @param direction Input direction | |||||
* @return The opposite direction | |||||
*/ | |||||
static int opposite (int direction) { return (direction+4)%DirRange.End; } | |||||
static int get (int fromId, int toId) { | |||||
if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)-1) == toId) | |||||
return Direction.LEFT; | |||||
else if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)+1) == toId) | |||||
return Direction.RIGHT; | |||||
else if (Position.toID(Position.toRow(fromId)+1, Position.toCol(fromId) ) == toId) | |||||
return Direction.UP; | |||||
else if (Position.toID(Position.toRow(fromId)-1, Position.toCol(fromId) ) == toId) | |||||
return Direction.DOWN; | |||||
else | |||||
return DirRange.End; | |||||
} | |||||
} | |||||
/** | /** | ||||
* @brief | * @brief | ||||
* An Application wide board position implementation holding just the id coordinate. | * An Application wide board position implementation holding just the id coordinate. | ||||
@@ -200,6 +220,13 @@ class Range { | |||||
return Const.EOR; | return Const.EOR; | ||||
} | } | ||||
/** | |||||
* @return The size of the underline structure | |||||
*/ | |||||
int size () { | |||||
return numbers.size(); | |||||
} | |||||
/** @name protected data types */ | /** @name protected data types */ | ||||
/** @{ */ | /** @{ */ | ||||
protected ArrayList<Integer> numbers; /**< handle to range */ | protected ArrayList<Integer> numbers; /**< handle to range */ | ||||
@@ -155,8 +155,8 @@ public class Game { | |||||
// Create a game, a board and 2 players. | // Create a game, a board and 2 players. | ||||
Game game = new Game(); | Game game = new Game(); | ||||
Board board = new Board(Session.boardSize, Session.supplySize); | 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)); | |||||
Player T = new HeuristicPlayer("Theseus", true, board, 0); | |||||
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||||
// Populate data to the board | // Populate data to the board | ||||
board.createBoard(T.playerTileId(), M.playerTileId()); | board.createBoard(T.playerTileId(), M.playerTileId()); | ||||
@@ -0,0 +1,191 @@ | |||||
/** | |||||
* @file Player.java | |||||
* | |||||
* @author | |||||
* Anastasia Foti AEM:8959 | |||||
* <anastaskf@ece.auth.gr> | |||||
* | |||||
* @author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
package host.labyrinth; | |||||
import java.util.ArrayList; | |||||
//import java.util.Arrays; | |||||
/** | |||||
* @brief | |||||
* This class represents the game's player who cheats. | |||||
*/ | |||||
class HeuristicPlayer extends Player { | |||||
/** @name Constructors */ | |||||
/** @{ */ | |||||
public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) { | |||||
super(name, champion, board, row, column); | |||||
path = new ArrayList<Integer[]>(); | |||||
} | |||||
public HeuristicPlayer(String name, boolean champion, Board board, int tileId) { | |||||
super(name, champion, board, tileId); | |||||
path = new ArrayList<Integer[]>(); | |||||
} | |||||
/** @} */ | |||||
/** @name Board's main application interface */ | |||||
/** @{ */ | |||||
/** | |||||
* Utility to get the distance of a possible supply in some direction | |||||
* @param currentPos The current position of the player | |||||
* @param direction The direction to check | |||||
* @return The distance or Const.noSupply | |||||
*/ | |||||
int supplyInDirection(int currentPos, int direction) { | |||||
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos)); | |||||
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) { | |||||
pos = new Position(Position.toRow(pos.getId()), Position.toCol(pos.getId()), direction); | |||||
if (board.hasSupply(pos.getId())) | |||||
return i+1; | |||||
} | |||||
return Const.noSupply; | |||||
} | |||||
/** | |||||
* Utility to get the distance of a possible opponent in some direction | |||||
* @param currentPos The current position of the player | |||||
* @param direction The direction to check | |||||
* @return The distance or Const.noOpponent | |||||
*/ | |||||
int opponetInDirection(int currentPos, int direction) { | |||||
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos)); | |||||
int [][] opps = board.getOpponentMoves(playerId); | |||||
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) { | |||||
pos = new Position(Position.toRow(pos.getId()), Position.toCol(pos.getId()), direction); | |||||
for (int o =0 ; o<opps.length; ++o) { | |||||
if (opps[o][0] == pos.getId()) | |||||
return i+1; | |||||
} | |||||
} | |||||
return Const.noOpponent; | |||||
} | |||||
double evaluate (int currentPos, int dice) { | |||||
int opDist = opponetInDirection (currentPos, dice); | |||||
int supDist = supplyInDirection(currentPos, dice); | |||||
// saturate | |||||
opDist = (opDist == Const.noOpponent) ? Integer.MAX_VALUE : opDist; | |||||
supDist = (supDist == Const.noSupply) ? Integer.MAX_VALUE : supDist; | |||||
return 1.0/supDist * Const.supplyFactor | |||||
- 1.0/opDist * Const.opponentFactor; | |||||
} | |||||
int directionOfMax (double[] eval, int[] eval_dir, int N) { | |||||
double M = Double.NEGATIVE_INFINITY; | |||||
int M_idx = -1; | |||||
for (int i =0; i < N ; ++i) { | |||||
if (eval[i] > M) { | |||||
M = eval[i]; | |||||
M_idx = i; | |||||
} | |||||
} | |||||
return eval_dir[M_idx]; | |||||
} | |||||
boolean isUnevaluated (double[] eval, int N) { | |||||
for (int i =0 ; i<N ; ++i) | |||||
if (eval[i] != 0 && eval[i] != Double.NEGATIVE_INFINITY) | |||||
return false; | |||||
return true; | |||||
} | |||||
// Must return a new move always | |||||
int getNextMove(int currentPos) { | |||||
Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step); | |||||
int N = dirs.size(); | |||||
double[] eval = new double[N]; | |||||
int [] eval_dir = new int[N]; | |||||
for (int i =0, dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get(), ++i) { | |||||
if (board.isWalkable(currentPos, dir)) | |||||
eval[i] = evaluate(currentPos, dir); | |||||
else | |||||
eval[i] = Double.NEGATIVE_INFINITY; | |||||
eval_dir[i] = dir; | |||||
} | |||||
int dir; | |||||
if (isUnevaluated(eval, N)) { | |||||
ShuffledRange r = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step); | |||||
do | |||||
dir = r.get(); | |||||
while (!board.isWalkable(currentPos, dir)); | |||||
} | |||||
else { | |||||
dir = directionOfMax (eval, eval_dir, N); | |||||
} | |||||
Position new_pos = new Position( Position.toRow(currentPos), Position.toCol(currentPos), dir ); | |||||
return new_pos.getId(); | |||||
} | |||||
/** | |||||
* HeuristicPlayer's move. | |||||
* | |||||
* A player of this kind cheats. He does not throw a dice to get a direction. In contrary he | |||||
* calculates his next move very carefully. | |||||
* If the player is a champion then he also picks up a possible supply from the tile. | |||||
* | |||||
* @param id The id of the starting tile. | |||||
* @return An array containing player's final position and possible supply of that position. | |||||
* The array format is: | |||||
* <ul> | |||||
* <li> int[0]: The tileId of the final player's position. | |||||
* <li> int[1]: The row of the final player's position. | |||||
* <li> int[2]: The column of the final player's position. | |||||
* <li> int[3]: The supplyId in case player picked one (Const.noSupply otherwise). | |||||
* </ul> | |||||
*/ | |||||
int[] move(int id) { | |||||
// Initialize return array with the current data | |||||
int[] ret = new int[Const.moveItems]; | |||||
ret[0] = getNextMove(id); | |||||
ret[1] = y = Position.toRow(ret[0]); | |||||
ret[2] = x = Position.toCol(ret[0]); | |||||
int supplyFlag = 0; | |||||
// In case of a champion player, try also to pick a supply | |||||
if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) { | |||||
++score; // keep score | |||||
++supplyFlag; | |||||
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | |||||
} | |||||
board.updateMove(ret, playerId); | |||||
// Update supply and opponent distance | |||||
int dir = Direction.get(id, ret[0]); | |||||
int smin =DirRange.End, omin =DirRange.End; | |||||
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) { | |||||
int s = supplyInDirection (ret[0], d); | |||||
int o = opponetInDirection(ret[0], d); | |||||
if (s < smin) smin = s; | |||||
if (o < omin) omin = o; | |||||
} | |||||
Integer[] p = {dir, supplyFlag, smin, omin }; | |||||
path.add(p); | |||||
return ret; | |||||
} | |||||
/** @} */ | |||||
/** @name Class data */ | |||||
/** @{ */ | |||||
private ArrayList<Integer[]> path; /**< our history | |||||
* The integer[] format is: | |||||
* { dice, tookSupply, SupplyDistance, OpponentDistance} | |||||
*/ | |||||
/** @} */ | |||||
} |
@@ -29,14 +29,16 @@ class Player { | |||||
* @param row The row coordinate of initial player position | * @param row The row coordinate of initial player position | ||||
* @param column The column coordinate of initial player's position | * @param column The column coordinate of initial player's position | ||||
*/ | */ | ||||
Player(int id, String name, boolean champion, Board board, int row, int column) { | |||||
this.playerId = id; | |||||
Player(String name, boolean champion, Board board, int row, int column) { | |||||
this.playerId = board.generatePlayerId(); | |||||
this.name = name; | this.name = name; | ||||
this.board = board; | this.board = board; | ||||
this.score = 0; | this.score = 0; | ||||
this.x = column; | this.x = column; | ||||
this.y = row; | this.y = row; | ||||
this.champion = champion; | this.champion = champion; | ||||
int[] m = {Position.toID(row, column), row, column, Const.noSupply}; | |||||
board.updateMove(m, playerId); | |||||
} | } | ||||
/** | /** | ||||
@@ -47,14 +49,16 @@ class Player { | |||||
* @param board Reference to the board of the game | * @param board Reference to the board of the game | ||||
* @param tileId The tileId coordinate of player's initial position | * @param tileId The tileId coordinate of player's initial position | ||||
*/ | */ | ||||
Player(int id, String name, boolean champion, Board board, int tileId) { | |||||
this.playerId = id; | |||||
Player(String name, boolean champion, Board board, int tileId) { | |||||
this.playerId = board.generatePlayerId(); | |||||
this.name = name; | this.name = name; | ||||
this.board = board; | this.board = board; | ||||
this.score = 0; | this.score = 0; | ||||
this.x = Position.toCol(tileId); | this.x = Position.toCol(tileId); | ||||
this.y = Position.toRow(tileId); | this.y = Position.toRow(tileId); | ||||
this.champion = champion; | this.champion = champion; | ||||
int[] m = {tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply}; | |||||
board.updateMove(m, playerId); | |||||
} | } | ||||
/** @} */ | /** @} */ | ||||
@@ -80,7 +84,11 @@ class Player { | |||||
*/ | */ | ||||
int[] move(int id) { | int[] move(int id) { | ||||
// Initialize return array with the current data | // Initialize return array with the current data | ||||
int[] ret = {id, Position.toRow(id), Position.toCol(id), Const.noSupply}; | |||||
int[] ret = new int[Const.moveItems]; | |||||
ret[0] = id; | |||||
ret[1] = Position.toRow(id); | |||||
ret[2] = Position.toCol(id); | |||||
ret[3] = Const.noSupply; | |||||
int diceDirection = board.dice(); // throw the dice | int diceDirection = board.dice(); // throw the dice | ||||
if (board.isWalkable(id, diceDirection)) { // The result is walkable | if (board.isWalkable(id, diceDirection)) { // The result is walkable | ||||
@@ -94,6 +102,7 @@ class Player { | |||||
++score; // keep score | ++score; // keep score | ||||
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | ||||
} | } | ||||
board.updateMove(ret, playerId); | |||||
} | } | ||||
else | else | ||||
System.out.println(name + ":\t*Can not move."); | System.out.println(name + ":\t*Can not move."); | ||||
@@ -143,12 +152,12 @@ class Player { | |||||
/** @name Class data */ | /** @name Class data */ | ||||
/** @{ */ | /** @{ */ | ||||
private int playerId; /**< The unique identifier of the player */ | |||||
private String name; /**< The name of the player */ | |||||
private Board board; /**< Reference to the session's boards */ | |||||
private int score; /**< The current score of the player */ | |||||
private int x; /**< The column coordinate of the player on the board */ | |||||
private int y; /**< The row coordinate of the player on the board */ | |||||
private boolean champion; /**< Champion indicate a player who plays against the Minotaur */ | |||||
protected int playerId; /**< The unique identifier of the player */ | |||||
protected String name; /**< The name of the player */ | |||||
protected Board board; /**< Reference to the session's boards */ | |||||
protected int score; /**< The current score of the player */ | |||||
protected int x; /**< The column coordinate of the player on the board */ | |||||
protected int y; /**< The row coordinate of the player on the board */ | |||||
protected boolean champion; /**< Champion indicate a player who plays against the Minotaur */ | |||||
/** @} */ | /** @} */ | ||||
} | } |