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