@@ -57,6 +57,7 @@ class DirRange { | |||||
static final int Begin =1; /**< Iterator style begin of range direction (starting north) */ | static final int Begin =1; /**< Iterator style begin of range direction (starting north) */ | ||||
static final int End =8; /**< Iterator style end of range direction (one place after the last) */ | static final int End =8; /**< Iterator style end of range direction (one place after the last) */ | ||||
static final int Step =2; /**< Step for iterator style direction */ | static final int Step =2; /**< Step for iterator style direction */ | ||||
static final int numOfDirections =4; | |||||
} | } | ||||
/** | /** | ||||
@@ -18,14 +18,15 @@ | |||||
* all the supplies of the board before Minotaur catches him and before the | * all the supplies of the board before Minotaur catches him and before the | ||||
* game ends. | * game ends. | ||||
* | * | ||||
* In this first assignment we deal with the board's creation and a basic | |||||
* player-game logic. The game is build around a number of classes: | |||||
* - \ref Tile | |||||
* In this 2nd assignment we deal with the creation of a new heuristic player | |||||
* who can cheat and manipulate the dice. Documented classes: | |||||
* - Tile | |||||
* - Supply | * - Supply | ||||
* - Board | * - Board | ||||
* - Player | * - Player | ||||
* - HeuristicPlayer | |||||
* - Game | * - Game | ||||
* | |||||
* | |||||
* Which are the requested classes. We also provide some extra functionalities in: | * Which are the requested classes. We also provide some extra functionalities in: | ||||
* - Const | * - Const | ||||
* - Session | * - Session | ||||
@@ -153,10 +154,11 @@ public class Game { | |||||
if (!Game.getArguments(args)) throw new Exception(""); | if (!Game.getArguments(args)) throw new Exception(""); | ||||
// Create a game, a board and 2 players. | // Create a game, a board and 2 players. | ||||
Game game = new Game(); | |||||
Board board = new Board(Session.boardSize, Session.supplySize); | |||||
Player T = new HeuristicPlayer("Theseus", true, board, 0); | |||||
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||||
Game game = new Game(); | |||||
Board board = new Board(Session.boardSize, Session.supplySize); | |||||
Player T = new HeuristicPlayer("Theseus", true, board, 0); | |||||
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||||
Player players [] = {T, M}; | |||||
// Populate data to the board | // Populate data to the board | ||||
board.createBoard(T.playerTileId(), M.playerTileId()); | board.createBoard(T.playerTileId(), M.playerTileId()); | ||||
@@ -169,34 +171,36 @@ public class Game { | |||||
game.waitUser (); | game.waitUser (); | ||||
// Main game loop | // Main game loop | ||||
while (true) { | while (true) { | ||||
int[] m; | |||||
System.out.println(); | System.out.println(); | ||||
System.out.println("State after round: " + (game.round()+1)); | |||||
System.out.println("Round: " + (game.round()+1)); | |||||
// Player moves | |||||
m = T.move(T.playerTileId()); | |||||
System.out.println(T.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")"); | |||||
m = M.move(M.playerTileId()); | |||||
System.out.println(M.getName() + ":\t tileId =" + m[0] + " (" + m[1] + ", " + m[2] + ")"); | |||||
// Players moves | |||||
for (Player p : players) { | |||||
p.move(p.playerTileId()); | |||||
p.statistics(); | |||||
} | |||||
board.printBoard( | board.printBoard( | ||||
board.getStringRepresentation(T.playerTileId(), M.playerTileId()) | board.getStringRepresentation(T.playerTileId(), M.playerTileId()) | ||||
); | ); | ||||
// Loop termination cases | // Loop termination cases | ||||
if (T.getScore() == 4) { | if (T.getScore() == 4) { | ||||
System.out.println(T.getName() + " Wins!!! Score =" + T.getScore()); | |||||
System.exit(0); | |||||
System.out.println("**** " + T.getName() + " Wins!!! Score =" + T.getScore() + " ****"); | |||||
break; | |||||
} | } | ||||
if (M.getScore() == 4 || M.playerTileId() == T.playerTileId()) { | if (M.getScore() == 4 || M.playerTileId() == T.playerTileId()) { | ||||
System.out.println(M.getName() + " Wins!!! Score =" + M.getScore()); | |||||
System.exit(0); | |||||
System.out.println("**** " + M.getName() + " Wins!!! Score =" + M.getScore()); | |||||
break; | |||||
} | } | ||||
if (!(game.nextRound() < Session.maxRounds)) { | if (!(game.nextRound() < Session.maxRounds)) { | ||||
System.out.println("New day has come... Tie!!!"); | |||||
System.exit(0); | |||||
System.out.println("**** New day has come... Tie! ****"); | |||||
break; | |||||
} | } | ||||
game.waitUser (); | game.waitUser (); | ||||
} | } | ||||
for (Player p : players) | |||||
p.final_statistics(); | |||||
System.exit(0); | |||||
} | } | ||||
catch (Exception e) { | catch (Exception e) { | ||||
// We don't handle exceptions. Print error and exit with error status. | // We don't handle exceptions. Print error and exit with error status. | ||||
@@ -12,9 +12,6 @@ | |||||
package host.labyrinth; | package host.labyrinth; | ||||
import java.util.ArrayList; | |||||
//import java.util.Arrays; | |||||
/** | /** | ||||
* @brief | * @brief | ||||
* This class represents the game's player who cheats. | * This class represents the game's player who cheats. | ||||
@@ -23,14 +20,28 @@ class HeuristicPlayer extends Player { | |||||
/** @name Constructors */ | /** @name Constructors */ | ||||
/** @{ */ | /** @{ */ | ||||
/** | |||||
* Create a new player and put him at the row-column coordinates | |||||
* @param name The name of the player | |||||
* @param champion Flag to indicate if a player is a `champion` | |||||
* @param board Reference to the board of the game | |||||
* @param row The row coordinate of initial player position | |||||
* @param column The column coordinate of initial player's position | |||||
*/ | |||||
public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) { | public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) { | ||||
super(name, champion, board, row, column); | super(name, champion, board, row, column); | ||||
path = new ArrayList<Integer[]>(); | |||||
} | } | ||||
/** | |||||
* Create a new player and put him at the row-column coordinates | |||||
* @param name The name of the player | |||||
* @param champion Flag to indicate if a player is a `champion` | |||||
* @param board Reference to the board of the game | |||||
* @param tileId The tileId coordinate of player's initial position | |||||
*/ | |||||
public HeuristicPlayer(String name, boolean champion, Board board, int tileId) { | public HeuristicPlayer(String name, boolean champion, Board board, int tileId) { | ||||
super(name, champion, board, tileId); | super(name, champion, board, tileId); | ||||
path = new ArrayList<Integer[]>(); | |||||
} | } | ||||
/** @} */ | /** @} */ | ||||
@@ -74,34 +85,21 @@ class HeuristicPlayer extends Player { | |||||
return Const.noOpponent; | return Const.noOpponent; | ||||
} | } | ||||
double evaluate (int currentPos, int dice) { | |||||
int opDist = opponetInDirection (currentPos, dice); | |||||
int supDist = supplyInDirection(currentPos, dice); | |||||
/** | |||||
* This is the main move evaluation function. | |||||
* @param currentPos The current position of the player (before the move to evaluate) | |||||
* @param direction The direction (a.k.a. the move) to evaluate | |||||
* @return A signed real number. The higher the output, the higher the evaluation. | |||||
*/ | |||||
double evaluate (int currentPos, int direction) { | |||||
int opDist = opponetInDirection (currentPos, direction); | |||||
int supDist = supplyInDirection(currentPos, direction); | |||||
// saturate | // 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; | |||||
opDist = (opDist == Const.noOpponent) ? 0: opDist; | |||||
supDist = (supDist == Const.noSupply) ? 0 : supDist; | |||||
return ((supDist != 0)? (1.0/supDist * Const.supplyFactor) : 0) | |||||
- ((opDist != 0) ? (1.0/opDist * Const.opponentFactor) : 0); | |||||
} | } | ||||
// Must return a new move always | // Must return a new move always | ||||
@@ -149,43 +147,119 @@ class HeuristicPlayer extends Player { | |||||
* <li> int[3]: The supplyId in case player picked one (Const.noSupply otherwise). | * <li> int[3]: The supplyId in case player picked one (Const.noSupply otherwise). | ||||
* </ul> | * </ul> | ||||
*/ | */ | ||||
@Override | |||||
int[] move(int id) { | int[] move(int id) { | ||||
// Initialize return array with the current data | // Initialize return array with the current data | ||||
int[] ret = new int[Const.moveItems]; | int[] ret = new int[Const.moveItems]; | ||||
ret[0] = getNextMove(id); | ret[0] = getNextMove(id); | ||||
ret[1] = y = Position.toRow(ret[0]); | ret[1] = y = Position.toRow(ret[0]); | ||||
ret[2] = x = Position.toCol(ret[0]); | ret[2] = x = Position.toCol(ret[0]); | ||||
int supplyFlag = 0; | |||||
int supplyFlag =0, moveFlag =1; | |||||
// In case of a champion player, try also to pick a supply | // In case of a champion player, try also to pick a supply | ||||
if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) { | if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) { | ||||
++score; // keep score | ++score; // keep score | ||||
++supplyFlag; | ++supplyFlag; | ||||
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | |||||
} | } | ||||
int dir = Direction.get(id, ret[0]); // update direction counters | |||||
++dirCounter[dir]; | |||||
board.updateMove(ret, playerId); | board.updateMove(ret, playerId); | ||||
// Update supply and opponent distance | // Update supply and opponent distance | ||||
int dir = Direction.get(id, ret[0]); | |||||
int smin =DirRange.End, omin =DirRange.End; | int smin =DirRange.End, omin =DirRange.End; | ||||
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) { | for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) { | ||||
int s = supplyInDirection (ret[0], d); | int s = supplyInDirection (ret[0], d); | ||||
int o = opponetInDirection(ret[0], d); | int o = opponetInDirection(ret[0], d); | ||||
if (s < smin) smin = s; | |||||
if (o < omin) omin = o; | |||||
if (s >= 0 && s < smin) smin = s; | |||||
if (o >= 0 && o < omin) omin = o; | |||||
} | } | ||||
Integer[] p = {dir, supplyFlag, smin, omin }; | |||||
// update path | |||||
Integer[] p = { | |||||
ret[0], dir, moveFlag, supplyFlag, | |||||
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT], | |||||
(smin != DirRange.End)? smin:Const.noSupply, (omin != DirRange.End)? omin:Const.noOpponent | |||||
}; | |||||
path.add(p); | path.add(p); | ||||
return ret; | return ret; | ||||
} | } | ||||
/** | |||||
* Prints round information for the player | |||||
*/ | |||||
void statistics() { | |||||
if (!path.isEmpty()) { | |||||
Integer[] last = path.get(path.size()-1); | |||||
String who = String.format("%12s", name); | |||||
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")"); | |||||
if (last[2] == 0) | |||||
System.out.println(" *Can not move."); | |||||
else if (last[3] != 0) | |||||
System.out.println(" *Found a supply."); | |||||
else | |||||
System.out.println(""); | |||||
// extra prints for heuristic | |||||
if (last[8] != Const.noSupply) System.out.println(" supply distance =" + last[8]); | |||||
else System.out.println(" supply distance = blind"); | |||||
if (last[9] != Const.noOpponent) System.out.println(" opponent distance =" + last[9]); | |||||
else System.out.println(" opponent distance = blind"); | |||||
} | |||||
} | |||||
/** | |||||
* Prints final statistics for the player | |||||
*/ | |||||
void final_statistics () { | |||||
String who = String.format("%12s", name); | |||||
System.out.println(); | |||||
System.out.println(who + ": score[" + score + "]"); | |||||
System.out.println(" Moves up: " + dirCounter[Direction.UP]); | |||||
System.out.println(" Moves right: " + dirCounter[Direction.RIGHT]); | |||||
System.out.println(" Moves down: " + dirCounter[Direction.DOWN]); | |||||
System.out.println(" Moves left: " + dirCounter[Direction.LEFT]); | |||||
} | |||||
/** @} */ | /** @} */ | ||||
/** | |||||
* A small utility to extract the direction of maximum evaluation result. | |||||
* | |||||
* We search into the \c eval results and find the index of the maximum evaluation. | |||||
* Then we return the direction of \c eval_dir matrix at the same index we found. | |||||
* | |||||
* @param eval Array with evaluation results for each direction | |||||
* @param eval_dir Array with the matching direction to \c eval array | |||||
* @param N The size of both arrays | |||||
* @return The direction of maximum evaluation. If \c eval is empty returns the first item \c eval[0] | |||||
* @note | |||||
* This function should not be called if there is at least one evaluation result in \c eval | |||||
*/ | |||||
private int directionOfMax (double[] eval, int[] eval_dir, int N) { | |||||
double M = Double.NEGATIVE_INFINITY; | |||||
int M_idx = 0; | |||||
for (int i =0; i < N ; ++i) { | |||||
if (eval[i] > M) { | |||||
M = eval[i]; | |||||
M_idx = i; | |||||
} | |||||
} | |||||
return eval_dir[M_idx]; | |||||
} | |||||
/** | |||||
* A small utility to check if there is at least one evaluation result in the \c eval array | |||||
* @param eval The array to check | |||||
* @param N The size of the array | |||||
* @return True if there is none, false otherwise | |||||
*/ | |||||
private 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; | |||||
} | |||||
/** @name Class data */ | /** @name Class data */ | ||||
/** @{ */ | /** @{ */ | ||||
private ArrayList<Integer[]> path; /**< our history | |||||
* The integer[] format is: | |||||
* { dice, tookSupply, SupplyDistance, OpponentDistance} | |||||
*/ | |||||
/** @} */ | /** @} */ | ||||
} | } |
@@ -12,6 +12,8 @@ | |||||
package host.labyrinth; | package host.labyrinth; | ||||
import java.util.ArrayList; | |||||
/** | /** | ||||
* @brief | * @brief | ||||
* This class represents the game's player | * This class represents the game's player | ||||
@@ -22,7 +24,6 @@ class Player { | |||||
/** | /** | ||||
* Create a new player and put him at the row-column coordinates | * Create a new player and put him at the row-column coordinates | ||||
* @param id The id of the player | |||||
* @param name The name of the player | * @param name The name of the player | ||||
* @param champion Flag to indicate if a player is a `champion` | * @param champion Flag to indicate if a player is a `champion` | ||||
* @param board Reference to the board of the game | * @param board Reference to the board of the game | ||||
@@ -37,13 +38,16 @@ class Player { | |||||
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}; | |||||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is worst. | |||||
this.path = new ArrayList<Integer[]>(); | |||||
int[] m = { | |||||
Position.toID(row, column), row, column, Const.noSupply | |||||
}; | |||||
board.updateMove(m, playerId); | board.updateMove(m, playerId); | ||||
} | } | ||||
/** | /** | ||||
* Create a new player and put him at the row-column coordinates | * Create a new player and put him at the row-column coordinates | ||||
* @param id The id of the player | |||||
* @param name The name of the player | * @param name The name of the player | ||||
* @param champion Flag to indicate if a player is a `champion` | * @param champion Flag to indicate if a player is a `champion` | ||||
* @param board Reference to the board of the game | * @param board Reference to the board of the game | ||||
@@ -57,7 +61,11 @@ class Player { | |||||
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}; | |||||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is worst. | |||||
this.path = new ArrayList<Integer[]>(); | |||||
int[] m = { | |||||
tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply | |||||
}; | |||||
board.updateMove(m, playerId); | board.updateMove(m, playerId); | ||||
} | } | ||||
/** @} */ | /** @} */ | ||||
@@ -89,9 +97,11 @@ class Player { | |||||
ret[1] = Position.toRow(id); | ret[1] = Position.toRow(id); | ||||
ret[2] = Position.toCol(id); | ret[2] = Position.toCol(id); | ||||
ret[3] = Const.noSupply; | ret[3] = Const.noSupply; | ||||
int supplyFlag =0, moveFlag =0; | |||||
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 | ||||
moveFlag =1; // mark the successful move | |||||
// Get next tile | // Get next tile | ||||
Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection); | Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection); | ||||
ret[0] = next.getId(); // Update player's and return data | ret[0] = next.getId(); // Update player's and return data | ||||
@@ -99,16 +109,49 @@ class Player { | |||||
ret[2] = x = next.getCol(); | ret[2] = x = next.getCol(); | ||||
// In case of a champion player, try also to pick a supply | // In case of a champion player, try also to pick a supply | ||||
if (champion && (ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) { | if (champion && (ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) { | ||||
supplyFlag =1; // mark the successful supply pickup | |||||
++score; // keep score | ++score; // keep score | ||||
System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); | |||||
} | } | ||||
++dirCounter[diceDirection]; // update direction counters | |||||
board.updateMove(ret, playerId); | board.updateMove(ret, playerId); | ||||
} | } | ||||
else | |||||
System.out.println(name + ":\t*Can not move."); | |||||
// update path | |||||
Integer[] p = { | |||||
ret[0], diceDirection, moveFlag, supplyFlag, | |||||
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT], | |||||
Const.noSupply, Const.noOpponent | |||||
}; | |||||
path.add(p); | |||||
return ret; | return ret; | ||||
} | } | ||||
/** | |||||
* Prints round information for the player | |||||
*/ | |||||
void statistics() { | |||||
if (!path.isEmpty()) { | |||||
Integer[] last = path.get(path.size()-1); | |||||
String who = String.format("%12s", name); | |||||
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")"); | |||||
if (last[2] == 0) | |||||
System.out.println(" *Can not move."); | |||||
else if (last[3] != 0) | |||||
System.out.println(" *Found a supply."); | |||||
else | |||||
System.out.println(""); | |||||
} | |||||
} | |||||
/** | |||||
* Prints final statistics for the player | |||||
* @note | |||||
* We add this final_statistics() wrapper in order to provide polymorphism to | |||||
* Player class hierarchy and to be sure that we are not braking the | |||||
* Liskov substitution principle | |||||
* @see https://en.wikipedia.org/wiki/Liskov_substitution_principle | |||||
*/ | |||||
void final_statistics () { } | |||||
/** Utility to access player's tileID */ | /** Utility to access player's tileID */ | ||||
int playerTileId() { return Position.toID(y, x); } | int playerTileId() { return Position.toID(y, x); } | ||||
/** Utility to access player's row position (row coordinate) */ | /** Utility to access player's row position (row coordinate) */ | ||||
@@ -147,17 +190,34 @@ class Player { | |||||
void setChampion (boolean champion) { | void setChampion (boolean champion) { | ||||
this.champion = champion; | this.champion = champion; | ||||
} | } | ||||
/** @} */ | /** @} */ | ||||
/** @name Class data */ | /** @name Class data */ | ||||
/** @{ */ | /** @{ */ | ||||
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 */ | |||||
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 */ | |||||
protected int dirCounter[]; | |||||
protected ArrayList<Integer[]> path; | |||||
/**< | |||||
* our history. The integer[] format is: | |||||
* <ul> | |||||
* <li> Integer[0]: tileId - The tile id we choose for the move | |||||
* <li> Integer[1]: dice - The dice (a.k.a direction) of move | |||||
* <li> Integer[2]: moveStatus - True if it was successful (we can move in that direction) | |||||
* <li> Integer[3]: tookSupply - True if we took supply | |||||
* <li> Integer[4]: upCounter - Accumulator to count all the up moves | |||||
* <li> Integer[5]: righrCounter - Accumulator to count all the right moves | |||||
* <li> Integer[6]: downCounter - Accumulator to count all the down moves | |||||
* <li> Integer[7]: leftCounter - Accumulator to count all the left moves | |||||
* <li> Integer[8]: SupDistance - The distance of the nearest supply (only for heuristic players) | |||||
* <li> Integer[9]: OppDistance - The distance of the nearest opponent (only for heuristic players) | |||||
* </ul> | |||||
* } | |||||
*/ | |||||
/** @} */ | /** @} */ | ||||
} | } |