Browse Source

DEV: 3rd assignment draft

tags/v3.0b1
parent
commit
c2c5d5e46f
9 changed files with 648 additions and 96 deletions
  1. BIN
      ds_project_2020-2021_PartC.pdf
  2. BIN
      release/8959_8997_PartB.zip
  3. +54
    -35
      src/host/labyrinth/Board.java
  4. +15
    -4
      src/host/labyrinth/Common.java
  5. +4
    -3
      src/host/labyrinth/Game.java
  6. +33
    -28
      src/host/labyrinth/HeuristicPlayer.java
  7. +388
    -0
      src/host/labyrinth/MinMaxPlayer.java
  8. +119
    -0
      src/host/labyrinth/Node.java
  9. +35
    -26
      src/host/labyrinth/Player.java

BIN
ds_project_2020-2021_PartC.pdf View File


BIN
release/8959_8997_PartB.zip View File


+ 54
- 35
src/host/labyrinth/Board.java View File

@@ -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;
/** @} */
}

+ 15
- 4
src/host/labyrinth/Common.java View File

@@ -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;
}
}



+ 4
- 3
src/host/labyrinth/Game.java View File

@@ -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());


+ 33
- 28
src/host/labyrinth/HeuristicPlayer.java View File

@@ -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");
}
}


+ 388
- 0
src/host/labyrinth/MinMaxPlayer.java View File

@@ -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;
}
}

/** @} */
}

+ 119
- 0
src/host/labyrinth/Node.java View File

@@ -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 */
/**@}*/
}

+ 35
- 26
src/host/labyrinth/Player.java View File

@@ -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
};


Loading…
Cancel
Save