@@ -13,6 +13,7 @@ | |||
package host.labyrinth; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.function.IntFunction; | |||
/** | |||
@@ -36,6 +37,7 @@ class Board { | |||
tiles = null; | |||
supplies =null; | |||
walls = new ArrayList<Edge>(); | |||
moves = new ArrayList<Integer[]>(); | |||
} | |||
/** | |||
@@ -52,6 +54,7 @@ class Board { | |||
tiles = new Tile[N*N]; | |||
supplies = new Supply[S]; | |||
walls = new ArrayList<Edge>(); | |||
moves = new ArrayList<Integer[]>(); | |||
} | |||
/** | |||
@@ -77,6 +80,9 @@ class Board { | |||
this.supplies = b.supplies.clone(); | |||
for (Edge it: b.walls) | |||
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); | |||
} | |||
/** | |||
* 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 | |||
* supply from the board. | |||
@@ -191,6 +206,7 @@ class Board { | |||
return supplyId; | |||
} | |||
/** | |||
* A plain fair dice functionality provided by the board. | |||
* @return A random direction; | |||
@@ -202,6 +218,27 @@ class Board { | |||
/** @return the size of each site of the board. */ | |||
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; } | |||
/** | |||
* @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 setS(int S) { this.S = S; } | |||
void setW(int W) { this.W = W; } | |||
@@ -257,6 +301,13 @@ class Board { | |||
*/ | |||
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 | |||
* 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.Collections; | |||
import java.util.function.IntFunction; | |||
/** | |||
* Class to hold constant values for entire application | |||
@@ -20,8 +21,14 @@ import java.util.Collections; | |||
class Const { | |||
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 noOpponent =-1; /**< Number to indicate the absent of supply */ | |||
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 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. | |||
@@ -34,23 +41,6 @@ class Session { | |||
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. | |||
* | |||
@@ -69,6 +59,36 @@ class DirRange { | |||
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 | |||
* An Application wide board position implementation holding just the id coordinate. | |||
@@ -200,6 +220,13 @@ class Range { | |||
return Const.EOR; | |||
} | |||
/** | |||
* @return The size of the underline structure | |||
*/ | |||
int size () { | |||
return numbers.size(); | |||
} | |||
/** @name protected data types */ | |||
/** @{ */ | |||
protected ArrayList<Integer> numbers; /**< handle to range */ | |||
@@ -155,8 +155,8 @@ public class Game { | |||
// Create a game, a board and 2 players. | |||
Game game = new Game(); | |||
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 | |||
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 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.board = board; | |||
this.score = 0; | |||
this.x = column; | |||
this.y = row; | |||
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 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.board = board; | |||
this.score = 0; | |||
this.x = Position.toCol(tileId); | |||
this.y = Position.toRow(tileId); | |||
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) { | |||
// 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 | |||
if (board.isWalkable(id, diceDirection)) { // The result is walkable | |||
@@ -94,6 +102,7 @@ class Player { | |||
++score; // keep score | |||
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | |||
} | |||
board.updateMove(ret, playerId); | |||
} | |||
else | |||
System.out.println(name + ":\t*Can not move."); | |||
@@ -143,12 +152,12 @@ class Player { | |||
/** @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 */ | |||
/** @} */ | |||
} |