@@ -13,7 +13,6 @@ | |||
package host.labyrinth; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.function.IntFunction; | |||
/** | |||
@@ -35,9 +34,10 @@ class Board { | |||
this.S = 0; | |||
this.W = 0; | |||
tiles = null; | |||
supplies =null; | |||
supplies = null; | |||
walls = new ArrayList<Edge>(); | |||
moves = new ArrayList<Integer[]>(); | |||
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE]; | |||
playerCount =0; | |||
} | |||
/** | |||
@@ -54,7 +54,8 @@ class Board { | |||
tiles = new Tile[N*N]; | |||
supplies = new Supply[S]; | |||
walls = new ArrayList<Edge>(); | |||
moves = new ArrayList<Integer[]>(); | |||
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE]; | |||
playerCount =0; | |||
} | |||
/** | |||
@@ -72,17 +73,28 @@ class Board { | |||
*/ | |||
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.N = b.N; | |||
this.S = b.S; | |||
this.W = b.W; | |||
tiles = new Tile[b.tiles.length]; | |||
supplies = new Supply[b.supplies.length]; | |||
walls = new ArrayList<Edge>(); | |||
moves = new int[Const.numOfPlayers][Player.MOVE_DATA_SIZE]; | |||
playerCount =b.playerCount; | |||
// clone moves array of array of primitives | |||
for (int i=0 ; i<b.moves.length ; ++i) | |||
this.moves[i] = b.moves[i].clone(); | |||
// Clone arrays of objects | |||
for (int i=0 ; i<b.tiles.length ; ++i) | |||
this.tiles[i] = new Tile(b.tiles[i]); | |||
for (int i=0 ; i<b.supplies.length ; ++i) | |||
this.supplies[i] = new Supply(b.supplies[i]); | |||
// clone vectors | |||
for (Edge it: b.walls) | |||
this.walls.add(new Edge(it)); | |||
for (Integer[] m : b.moves) { | |||
this.moves.add(m); | |||
} | |||
this.walls.add(new Edge(it)); | |||
} | |||
/** @} */ | |||
@@ -220,29 +232,34 @@ class Board { | |||
int size () { return N; } | |||
/** | |||
* Boards utility to give access to other player moves. | |||
* Utility function to create player IDs | |||
* @return The generated player id. | |||
*/ | |||
int generatePlayerId () throws Exception { | |||
if (playerCount < Const.numOfPlayers) | |||
return playerCount++; | |||
else | |||
throw new Exception("Maximum number of players exceeded"); | |||
} | |||
/** | |||
* Boards utility to give access to other player Id. | |||
* | |||
* @param playerId The id of player who asks | |||
* @return The moves data of all other players | |||
*/ | |||
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; | |||
* @return The other player's Id. | |||
*/ | |||
int getOpponentId(int playerId) { | |||
return Const.numOfPlayers - (playerId +1); | |||
} | |||
/** | |||
* Utility function to create player IDs | |||
* @return The generated player id. | |||
* Boards utility to give access to other player moves. | |||
* | |||
* @param playerId The id of player who asks | |||
* @return The moves data of other player | |||
*/ | |||
int generatePlayerId () { | |||
moves.add(null); | |||
return moves.size() -1; | |||
int[] getOpponentMove (int playerId) { | |||
return moves[getOpponentId(playerId)]; | |||
} | |||
/** | |||
@@ -256,7 +273,8 @@ class Board { | |||
* @param playerId The id of the player who update his/her data. | |||
*/ | |||
void updateMove(int[] m, int playerId) { | |||
moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new)); | |||
//moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new)); | |||
moves[playerId] = m; | |||
} | |||
/** @} */ | |||
@@ -295,7 +313,7 @@ class Board { | |||
* <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; } | |||
int[][] getMoves() { return moves; } | |||
void setN(int N) { this.N = N; } | |||
void setS(int S) { this.S = S; } | |||
@@ -326,7 +344,7 @@ class Board { | |||
* @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; } | |||
void setMoves(int[][] moves) { this.moves =moves; } | |||
/** @} */ | |||
@@ -633,6 +651,7 @@ class Board { | |||
* Array to hold all the walls using the edge representation | |||
* required by the closed room preventing algorithm. | |||
*/ | |||
private ArrayList<Integer[]> moves; | |||
private int[][] moves; | |||
private int playerCount; | |||
/** @} */ | |||
} |
@@ -19,16 +19,23 @@ import java.util.Collections; | |||
* Class to hold constant values for entire application | |||
*/ | |||
class Const { | |||
static final int numOfPlayers = 2; | |||
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 int noView = viewDistance+1; | |||
static final double opponentFactor =1.0; | |||
static final double supplyFactor =0.65; | |||
/** Parameters to control move evaluation */ | |||
/** @{ */ | |||
static final double opponentFactor = 1.0; /**< opponent distance factor */ | |||
static final double supplyFactor = 0.65; /**< supply distance factor */ | |||
static final double preMoveFactor = 0.65; /**< pre move distances factor */ | |||
static final double postMoveFactor = 0.35; /**< post move distances factor */ | |||
static final int minimaxTreeDepth = 4; /**< The maximum depth of the minimax tree */ | |||
/** @} */ | |||
} | |||
/** | |||
* Application wide object to hold settings like values for the session. | |||
@@ -68,13 +75,16 @@ class Direction { | |||
static final int RIGHT =3; /**< East direction */ | |||
static final int DOWN =5; /**< South direction */ | |||
static final int LEFT =7; /**< West direction */ | |||
static final int NONE =8; /**< No 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 opposite (int direction) { | |||
return (direction != NONE) ? (direction+4)%DirRange.End : NONE; | |||
} | |||
static int get (int fromId, int toId) { | |||
if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)-1) == toId) | |||
@@ -133,6 +143,7 @@ class Position { | |||
case Direction.DOWN: this.id = toID(row-1, col); break; | |||
case Direction.LEFT: this.id = toID(row, col-1); break; | |||
case Direction.RIGHT:this.id = toID(row, col+1); break; | |||
case Direction.NONE: this.id = toID(row, col); break; | |||
} | |||
} | |||
@@ -18,13 +18,14 @@ | |||
* all the supplies of the board before Minotaur catches him and before the | |||
* game ends. | |||
* | |||
* In this 2nd assignment we deal with the creation of a new heuristic player | |||
* In this 3rd assignment we deal with the creation of a new minimax player | |||
* who can cheat and manipulate the dice. Documented classes: | |||
* - Tile | |||
* - Supply | |||
* - Board | |||
* - Player | |||
* - HeuristicPlayer | |||
* - MinMaxPlayer | |||
* - Game | |||
* | |||
* Which are the requested classes. We also provide some extra functionalities in: | |||
@@ -156,9 +157,9 @@ 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 HeuristicPlayer("Theseus", true, board, 0); | |||
Player T = new MinMaxPlayer("Theseus", true, board, 0); | |||
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2)); | |||
Player players [] = {T, M}; | |||
Player players [] = {M, T}; | |||
// Populate data to the board | |||
board.createBoard(T.playerTileId(), M.playerTileId()); | |||
@@ -1,5 +1,5 @@ | |||
/** | |||
* @file Player.java | |||
* @file HeuristicPlayer.java | |||
* | |||
* @author | |||
* Anastasia Foti AEM:8959 | |||
@@ -29,7 +29,7 @@ class HeuristicPlayer extends Player { | |||
* @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) throws Exception { | |||
super(name, champion, board, row, column); | |||
} | |||
@@ -40,7 +40,7 @@ class HeuristicPlayer extends Player { | |||
* @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) throws Exception { | |||
super(name, champion, board, tileId); | |||
} | |||
/** @} */ | |||
@@ -58,7 +58,7 @@ class HeuristicPlayer extends Player { | |||
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); | |||
pos = new Position(pos.getRow(), pos.getCol(), direction); | |||
if (board.hasSupply(pos.getId())) | |||
return i+1; | |||
} | |||
@@ -73,14 +73,12 @@ class HeuristicPlayer extends Player { | |||
*/ | |||
int opponetInDirection(int currentPos, int direction) { | |||
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos)); | |||
int [][] opps = board.getOpponentMoves(playerId); | |||
int [] opp = board.getOpponentMove(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; | |||
} | |||
pos = new Position(pos.getRow(), pos.getCol(), direction); | |||
if (opp[MOVE_TILE_ID] == pos.getId()) | |||
return i+1; | |||
} | |||
return Const.noOpponent; | |||
} | |||
@@ -102,8 +100,15 @@ class HeuristicPlayer extends Player { | |||
- ((opDist != 0) ? (1.0/opDist * Const.opponentFactor) : 0); | |||
} | |||
// Must return a new move always | |||
int getNextMove(int currentPos) { | |||
/** | |||
* Selects the best possible move to return | |||
* @param currentPos Player's current position to the board | |||
* @return The move array | |||
* | |||
* @note | |||
* This function always return a new move. | |||
*/ | |||
int[] getNextMove(int currentPos) { | |||
Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step); | |||
int N = dirs.size(); | |||
double[] eval = new double[N]; | |||
@@ -127,7 +132,8 @@ class HeuristicPlayer extends Player { | |||
dir = directionOfMax (eval, eval_dir, N); | |||
} | |||
Position new_pos = new Position( Position.toRow(currentPos), Position.toCol(currentPos), dir ); | |||
return new_pos.getId(); | |||
int [] ret = {new_pos.getId(), new_pos.getRow(), new_pos.getCol(), dir}; | |||
return ret; | |||
} | |||
/** | |||
@@ -144,37 +150,36 @@ class HeuristicPlayer extends Player { | |||
* <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). | |||
* <li> int[3]: The dice/direction of the move. | |||
* </ul> | |||
*/ | |||
@Override | |||
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[] ret = getNextMove(id); | |||
y = Position.toRow(ret[MOVE_TILE_ID]); | |||
x = Position.toCol(ret[MOVE_TILE_ID]); | |||
int supplyFlag =0, moveFlag =1; | |||
// In case of a champion player, try also to pick a supply | |||
if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) { | |||
if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) { | |||
++score; // keep score | |||
++supplyFlag; | |||
} | |||
int dir = Direction.get(id, ret[0]); // update direction counters | |||
++dirCounter[dir]; | |||
++dirCounter[ret[MOVE_DICE]]; // update direction counters | |||
board.updateMove(ret, playerId); | |||
// Update supply and opponent distance | |||
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); | |||
int s = supplyInDirection (ret[MOVE_TILE_ID], d); | |||
int o = opponetInDirection(ret[MOVE_TILE_ID], d); | |||
if (s >= 0 && s < smin) smin = s; | |||
if (o >= 0 && o < omin) omin = o; | |||
} | |||
// update path | |||
Integer[] p = { | |||
ret[0], dir, moveFlag, supplyFlag, | |||
ret[MOVE_TILE_ID], ret[MOVE_DICE], 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 | |||
}; | |||
@@ -197,10 +202,10 @@ class HeuristicPlayer extends Player { | |||
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"); | |||
if (last[8] != Const.noSupply) System.out.println(" supply =" + last[8]); | |||
else System.out.println(" supply = blind"); | |||
if (last[9] != Const.noOpponent) System.out.println(" opponent =" + last[9]); | |||
else System.out.println(" opponent = blind"); | |||
} | |||
} | |||
@@ -0,0 +1,388 @@ | |||
/** | |||
* @file MinMaxPlayer.java | |||
* | |||
* @author | |||
* Anastasia Foti AEM:8959 | |||
* <anastaskf@ece.auth.gr> | |||
* | |||
* @author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
package host.labyrinth; | |||
/** | |||
* @brief | |||
* This class represents the game's minimax player. | |||
*/ | |||
class MinMaxPlayer extends Player { | |||
/** @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 MinMaxPlayer(String name, boolean champion, Board board, int row, int column) throws Exception { | |||
super(name, champion, board, row, column); | |||
} | |||
/** | |||
* 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 MinMaxPlayer(String name, boolean champion, Board board, int tileId) throws Exception { | |||
super(name, champion, board, tileId); | |||
} | |||
/** @} */ | |||
/** @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 | |||
* @param board Reference to the Board object to use | |||
* | |||
* @return The distance or Const.noView | |||
*/ | |||
int supplyInDirection(int currentPos, int direction, Board board) { | |||
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos)); | |||
for (int i=0 ; i<=Const.viewDistance ; ++i) { | |||
if (board.hasSupply(pos.getId())) | |||
return i; | |||
if (board.isWalkable(pos.getId(), direction)) | |||
pos = new Position(pos.getRow(), pos.getCol(), direction); | |||
else | |||
break; | |||
} | |||
return Const.noView; | |||
} | |||
/** | |||
* 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 | |||
* @param board Reference to the Board object to use | |||
* | |||
* @return The distance or Const.noView | |||
*/ | |||
int opponetInDirection(int currentPos, int direction, Board board) { | |||
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos)); | |||
int[] opp = board.getOpponentMove(playerId); | |||
for (int i=0 ; i<=Const.viewDistance ; ++i) { | |||
if (opp[MOVE_TILE_ID] == pos.getId()) | |||
return i; | |||
if (board.isWalkable(pos.getId(), direction)) | |||
pos = new Position(pos.getRow(), pos.getCol(), direction); | |||
else | |||
break; | |||
} | |||
return Const.noView; | |||
} | |||
/** | |||
* 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 | |||
* @param board Reference to the Board object to use | |||
* @return A signed real number. The higher the output, the higher the evaluation. | |||
*/ | |||
double evaluate (int currentPos, int direction, Board board) { | |||
Position next = new Position (Position.toRow(currentPos), Position.toCol(currentPos), direction); | |||
int preOpDist = opponetInDirection (currentPos, direction, board); | |||
int preSupDist = supplyInDirection(currentPos, direction, board); | |||
int postOpDist = opponetInDirection (next.getId(), direction, board); | |||
int postSupDist = supplyInDirection(next.getId(), direction, board); | |||
return ((preSupDist != Const.noView) ? Const.preMoveFactor *(1.0/(preSupDist+1) * Const.supplyFactor) : 0) | |||
- ((preOpDist != Const.noView) ? Const.preMoveFactor *(1.0/(preOpDist+1) * Const.opponentFactor) : 0) | |||
+ ((postSupDist != Const.noView)? Const.postMoveFactor*(1.0/(preSupDist+1) * Const.supplyFactor) : 0) | |||
- ((postOpDist != Const.noView) ? Const.postMoveFactor*(1.0/(preOpDist+1) * Const.opponentFactor) : 0); | |||
} | |||
/** | |||
* Executes the minimax algorithm and return a reference to selected move | |||
* @param node The root node to start | |||
* @return Reference to the selected move | |||
*/ | |||
Node chooseMinMaxMove(Node node) { | |||
node.setNodeEvaluation(maxValue(node)); | |||
return node.getPath(); | |||
} | |||
/** | |||
* Selects the best possible move to return | |||
* @param currentPos Player's current position to the board | |||
* @return The move array | |||
* | |||
* @note | |||
* This function always return a new move. | |||
*/ | |||
int[] getNextMove(int currentPos) { | |||
Node root = new Node (board); | |||
int [] opp = board.getOpponentMove(playerId); | |||
createMySubtree(currentPos, opp[MOVE_TILE_ID], root, root.getNodeDepth()+1); | |||
return chooseMinMaxMove(root).getNodeMove(); | |||
} | |||
/** | |||
* MinMaxPlayer'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 dice/direction of the move. | |||
* </ul> | |||
*/ | |||
@Override | |||
int[] move(int id) { | |||
// Initialize return array with the current data | |||
int[] ret = getNextMove(id); | |||
y = Position.toRow(ret[MOVE_TILE_ID]); | |||
x = Position.toCol(ret[MOVE_TILE_ID]); | |||
int supplyFlag =0, moveFlag =1; | |||
// In case of a champion player, try also to pick a supply | |||
if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) { | |||
++score; // keep score | |||
++supplyFlag; | |||
} | |||
++dirCounter[ret[MOVE_DICE]]; // update direction counters | |||
board.updateMove(ret, playerId); | |||
// Update supply and opponent distance | |||
int smin =Const.noView, omin =Const.noView; | |||
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) { | |||
int s = supplyInDirection (ret[MOVE_TILE_ID], d, board); | |||
int o = opponetInDirection(ret[MOVE_TILE_ID], d, board); | |||
if (s < smin) smin = s; | |||
if (o < omin) omin = o; | |||
} | |||
// update path | |||
Integer[] p = { | |||
ret[MOVE_TILE_ID], ret[MOVE_DICE], moveFlag, supplyFlag, | |||
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT], | |||
smin, omin | |||
}; | |||
path.add(p); | |||
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 minimax player | |||
if (last[8] != Const.noView) System.out.println(" supply =" + last[8]); | |||
else System.out.println(" supply = blind"); | |||
if (last[9] != Const.noView) System.out.println(" opponent =" + last[9]); | |||
else System.out.println(" opponent = 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]); | |||
} | |||
/** @} */ | |||
/** @name Minimax algorithm related part */ | |||
/** @{ */ | |||
/** | |||
* Get the previous direction of the player | |||
* @param parent Reference to previous nNode | |||
* @return The previous direction if exist, else return Direction.NONE | |||
*/ | |||
private int prevDirection(Node parent) { | |||
if (parent != null && parent.getParent() != null) | |||
return parent.getParent().getNodeMove()[MOVE_DICE]; | |||
return Direction.NONE; | |||
} | |||
/** | |||
* A simulated move in a copy of the bard. | |||
* | |||
* @param board The board on witch we simulate the move | |||
* @param currentPos The current position of the player to the @c board | |||
* @param dir The direction of the move | |||
* @param champion Flag to indicate if the player is champion or not | |||
* @return The move array | |||
*/ | |||
private int[] dryMove (Board board, int currentPos, int dir, boolean champion) { | |||
int[] ret = new int[MOVE_DATA_SIZE]; | |||
Position p = new Position(Position.toRow(currentPos), Position.toCol(currentPos), dir); | |||
ret[MOVE_TILE_ID] = p.getId(); | |||
ret[MOVE_ROW] = p.getRow(); | |||
ret[MOVE_COLUMN] = p.getCol(); | |||
ret[MOVE_DICE] = dir; | |||
board.updateMove(ret, (champion) ? playerId : board.getOpponentId(playerId)); | |||
return ret; | |||
} | |||
/** | |||
* One of the 2 recursive functions for creating the minimax tree. This one | |||
* creates children for the MinMax player. | |||
* | |||
* @param parent The parent Node | |||
* @param depth The current depth for the children | |||
* @param currentPos The tile of MinMax player | |||
* @param oppCurrentPos The tile of the opponent | |||
* | |||
* @note | |||
* Even though unnecessary we calculate the evaluation for every node and not only for the leafs. | |||
* This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()" | |||
* and use AB pruning but the depth of the tree is not worth the try. | |||
*/ | |||
private void createMySubtree (int currentPos, int oppCurrentPos, Node parent, int depth) { | |||
ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step); | |||
int [] nodeMove; | |||
for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) { | |||
if ((dir != Direction.opposite(prevDirection(parent))) | |||
&& parent.getNodeBoard().isWalkable(currentPos, dir)) { | |||
Board nodeBoard = new Board (parent.getNodeBoard()); // clone board | |||
double eval = evaluate (currentPos, dir, nodeBoard); // evaluate the move | |||
nodeMove = dryMove (nodeBoard, currentPos, dir, true); // simulate the move | |||
// make child Node | |||
Node child = new Node (parent, depth, nodeMove, nodeBoard, eval); | |||
parent.addChild(child); // add child to tree | |||
createOppSubtree (nodeMove[MOVE_TILE_ID], oppCurrentPos, child, depth+1); | |||
} | |||
} | |||
} | |||
/** | |||
* One of the 2 recursive functions for creating the minimax tree. This one | |||
* creates children for the opponent player. | |||
* | |||
* @param parent The parent Node | |||
* @param depth The current depth for the children | |||
* @param currentPos The tile of MinMax player | |||
* @param oppCurrentPos The tile of the opponent | |||
* | |||
* @note | |||
* Even though unnecessary we calculate the evaluation for every node and not only for the leafs. | |||
* This follows the exercise instructions. We could also rely on lazy evaluation of "evaluation()" | |||
* and use AB pruning but the depth of the tree is not worth the try. | |||
*/ | |||
private void createOppSubtree (int currentPos, int oppCurrentPos, Node parent, int depth) { | |||
ShuffledRange dirs = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step); | |||
int [] nodeMove; | |||
for (int dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get()) { | |||
if ((dir != Direction.opposite(prevDirection(parent))) | |||
&& parent.getNodeBoard().isWalkable(oppCurrentPos, dir)) { | |||
Board nodeBoard = new Board(parent.getNodeBoard()); // clone board | |||
nodeMove = dryMove (nodeBoard, oppCurrentPos, dir, false); // simulate move | |||
Position init = new Position( // evaluate from "My" perspective the move | |||
parent.getNodeMove()[MOVE_ROW], | |||
parent.getNodeMove()[MOVE_COLUMN], | |||
Direction.opposite(parent.getNodeMove()[MOVE_DICE]) | |||
); | |||
double eval = evaluate(init.getId(), parent.getNodeMove()[MOVE_DICE], nodeBoard); | |||
// make child Node | |||
Node child = new Node (parent, depth, nodeMove, nodeBoard, eval); | |||
parent.addChild(child); // add child to tree | |||
if (depth < Const.minimaxTreeDepth) { | |||
createMySubtree (currentPos, nodeMove[MOVE_TILE_ID], child, depth+1); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* The Minimax recursive function for the maximizing part. | |||
* | |||
* @param node The current Node | |||
* @return The selected Node | |||
*/ | |||
private double maxValue (Node node) { | |||
if (node.getChildren() == null) { | |||
node.setPath(node); | |||
return node.getNodeEvaluation(); | |||
} | |||
else { | |||
double M = Double.NEGATIVE_INFINITY; | |||
for (Node n : node.getChildren()) { | |||
n.setNodeEvaluation(minValue(n)); // evaluation propagation | |||
if (M < n.getNodeEvaluation()) { | |||
M = n.getNodeEvaluation(); | |||
node.setPath(n); // path propagation | |||
} | |||
} | |||
return M; | |||
} | |||
} | |||
/** | |||
* The Minimax recursive function for the minimizing part. | |||
* | |||
* @param node The current Node | |||
* @return The selected Node | |||
*/ | |||
private double minValue (Node node) { | |||
if (node.getChildren() == null) { | |||
node.setPath(node); | |||
return node.getNodeEvaluation(); | |||
} | |||
else { | |||
double m = Double.POSITIVE_INFINITY; | |||
for (Node n : node.getChildren()) { | |||
n.setNodeEvaluation(maxValue(n)); // evaluation propagation | |||
if (m > n.getNodeEvaluation()) { | |||
m = n.getNodeEvaluation(); | |||
node.setPath(n); // path propagation | |||
} | |||
} | |||
return m; | |||
} | |||
} | |||
/** @} */ | |||
} |
@@ -0,0 +1,119 @@ | |||
/** | |||
* @file Node.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; | |||
/** | |||
* Node object for minimax tree. | |||
*/ | |||
class Node { | |||
/** @name Constructors */ | |||
/** @{ */ | |||
/** Null initialize constructor */ | |||
Node () { } | |||
/** The main constructor for the Node */ | |||
Node (Node parent, int nodeDepth, int [] nodeMove, Board nodeBoard, double nodeEvaluation) { | |||
this.parent = parent; | |||
this.children = null; | |||
this.nodeDepth = nodeDepth; | |||
this.nodeMove = nodeMove; | |||
this.nodeBoard = nodeBoard; | |||
this.nodeEvaluation = nodeEvaluation; | |||
this.path = null; | |||
} | |||
/** A special constructor for creating a root Node */ | |||
Node (Board nodeBoard) { | |||
this.parent = null; | |||
this.children = null; | |||
this.nodeDepth = 0; | |||
this.nodeMove = new int [4]; | |||
this.nodeBoard = nodeBoard; | |||
this.nodeEvaluation = 0; | |||
this.path = null; | |||
} | |||
/**@} */ | |||
/** @name Get/Set interface */ | |||
/** @{ */ | |||
/** Get parent */ | |||
Node getParent() { return parent; } | |||
/** get children */ | |||
ArrayList<Node> | |||
getChildren() { return children; } | |||
/** get nodeDepth */ | |||
int getNodeDepth() { return nodeDepth; } | |||
/** get nodeMove */ | |||
int[] getNodeMove() { return nodeMove; } | |||
/** get nodeBoard */ | |||
Board getNodeBoard() { return nodeBoard; } | |||
/** get nodeEvluation */ | |||
double getNodeEvaluation (){ return nodeEvaluation; } | |||
/** get path */ | |||
Node getPath() { return path; } | |||
/** set parent */ | |||
void setParent(Node parent) { this.parent = parent; } | |||
/** set children */ | |||
void setChildren(ArrayList<Node> children) { | |||
this.children = children; | |||
} | |||
/** set nodeDepth */ | |||
void setNodeDepth(int nodeDepth) { | |||
this.nodeDepth = nodeDepth; | |||
} | |||
/** set nodeMove */ | |||
void setNodeMove(int[] nodeMove) { | |||
this.nodeMove = nodeMove; | |||
} | |||
/** set nodeBoard */ | |||
void setNodeBoard(Board nodeBoard) { | |||
this.nodeBoard = nodeBoard; | |||
} | |||
/** set nodeEvaluation */ | |||
void setNodeEvaluation(double nodeEvaluation) { | |||
this.nodeEvaluation = nodeEvaluation; | |||
} | |||
/** set path */ | |||
void setPath (Node path) { | |||
this.path = path; | |||
} | |||
/**@}*/ | |||
/** @name Public API */ | |||
/** @{ */ | |||
/** | |||
* Add a child to the tree | |||
* @param child The child to add | |||
* @return the status of the operation | |||
*/ | |||
boolean addChild (Node child) { | |||
if (children == null) | |||
children = new ArrayList<>(); | |||
return children.add(child); | |||
} | |||
/**@}*/ | |||
/** @name Data members */ | |||
/** @{ */ | |||
private Node parent; /**< Back reference to parent Node */ | |||
private ArrayList<Node> children; /**< Fwd reference to leaf Nodes */ | |||
private int nodeDepth; /**< The Node's depth */ | |||
private int[] nodeMove; /**< The Node's move data [tile, initTile, points, roll]*/ | |||
private Board nodeBoard; /**< Reference to Board's copy of the current node*/ | |||
private double nodeEvaluation; /**< The Node's evaluation result */ | |||
private Node path; /**< The minimax evaluation path */ | |||
/**@}*/ | |||
} |
@@ -19,6 +19,13 @@ import java.util.ArrayList; | |||
* This class represents the game's player | |||
*/ | |||
class Player { | |||
/** Helper variables to keep track of the move() return values @see move() */ | |||
static final int MOVE_DATA_SIZE = 4; /**< The move return data array size */ | |||
static final int MOVE_TILE_ID = 0; /**< Index of the tileId information of the move */ | |||
static final int MOVE_ROW = 1; /**< The index of row information */ | |||
static final int MOVE_COLUMN = 2; /**< The index of column information */ | |||
static final int MOVE_DICE = 3; /**< The index of dice information */ | |||
/** @name Constructors */ | |||
/** @{ */ | |||
@@ -30,7 +37,7 @@ class Player { | |||
* @param row The row coordinate of initial player position | |||
* @param column The column coordinate of initial player's position | |||
*/ | |||
Player(String name, boolean champion, Board board, int row, int column) { | |||
Player(String name, boolean champion, Board board, int row, int column) throws Exception { | |||
this.playerId = board.generatePlayerId(); | |||
this.name = name; | |||
this.board = board; | |||
@@ -38,7 +45,7 @@ class Player { | |||
this.x = column; | |||
this.y = row; | |||
this.champion = champion; | |||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is worst. | |||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is the worst. | |||
this.path = new ArrayList<Integer[]>(); | |||
int[] m = { | |||
Position.toID(row, column), row, column, Const.noSupply | |||
@@ -53,7 +60,7 @@ class Player { | |||
* @param board Reference to the board of the game | |||
* @param tileId The tileId coordinate of player's initial position | |||
*/ | |||
Player(String name, boolean champion, Board board, int tileId) { | |||
Player(String name, boolean champion, Board board, int tileId) throws Exception { | |||
this.playerId = board.generatePlayerId(); | |||
this.name = name; | |||
this.board = board; | |||
@@ -61,7 +68,7 @@ class Player { | |||
this.x = Position.toCol(tileId); | |||
this.y = Position.toRow(tileId); | |||
this.champion = champion; | |||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is worst. | |||
this.dirCounter= new int[DirRange.End]; // yes we spoil some memory. Java is the worst. | |||
this.path = new ArrayList<Integer[]>(); | |||
int[] m = { | |||
tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply | |||
@@ -87,37 +94,39 @@ class Player { | |||
* <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[1]: The supplyId in case player picked one (Const.noSupply otherwise). | |||
* <li> int[3]: The dice/direction of the move. | |||
* </ul> | |||
*/ | |||
int[] move(int id) { | |||
// Initialize return array with the current data | |||
int[] ret = new int[Const.moveItems]; | |||
ret[0] = id; | |||
ret[1] = Position.toRow(id); | |||
ret[2] = Position.toCol(id); | |||
ret[3] = Const.noSupply; | |||
int[] ret = new int[MOVE_DATA_SIZE]; | |||
ret[MOVE_TILE_ID] = id; | |||
ret[MOVE_ROW] = Position.toRow(id); | |||
ret[MOVE_COLUMN] = Position.toCol(id); | |||
ret[MOVE_DICE] = Direction.NONE; | |||
int supplyFlag =0, moveFlag =0; | |||
int diceDirection = board.dice(); // throw the dice | |||
if (board.isWalkable(id, diceDirection)) { // The result is walkable | |||
moveFlag =1; // mark the successful move | |||
// Get next tile | |||
Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection); | |||
ret[0] = next.getId(); // Update player's and return data | |||
ret[1] = y = next.getRow(); | |||
ret[2] = x = next.getCol(); | |||
// In case of a champion player, try also to pick a supply | |||
if (champion && (ret[3] = board.tryPickSupply(next.getId())) != Const.noSupply) { | |||
supplyFlag =1; // mark the successful supply pickup | |||
++score; // keep score | |||
} | |||
++dirCounter[diceDirection]; // update direction counters | |||
board.updateMove(ret, playerId); | |||
int diceDirection; | |||
do | |||
diceDirection = board.dice(); // throw the dice | |||
while (!board.isWalkable(id, diceDirection)); | |||
moveFlag =1; // mark the successful move | |||
// Get next tile | |||
Position next = new Position(Position.toRow(id), Position.toCol(id), diceDirection); | |||
ret[MOVE_TILE_ID] = next.getId(); // Update move's return data | |||
ret[MOVE_ROW] = y = next.getRow(); | |||
ret[MOVE_COLUMN] = x = next.getCol(); | |||
ret[MOVE_DICE] = diceDirection; | |||
// In case of a champion player, try also to pick a supply | |||
if (champion && (board.tryPickSupply(next.getId()) != Const.noSupply)) { | |||
supplyFlag =1; // mark the successful supply pickup | |||
++score; // keep score | |||
} | |||
++dirCounter[diceDirection]; // update direction counters | |||
board.updateMove(ret, playerId); | |||
// update path | |||
Integer[] p = { | |||
ret[0], diceDirection, moveFlag, supplyFlag, | |||
ret[MOVE_TILE_ID], diceDirection, moveFlag, supplyFlag, | |||
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT], | |||
Const.noSupply, Const.noOpponent | |||
}; | |||