diff --git a/src/host/labyrinth/Common.java b/src/host/labyrinth/Common.java index 762e925..750aef2 100644 --- a/src/host/labyrinth/Common.java +++ b/src/host/labyrinth/Common.java @@ -57,6 +57,7 @@ class DirRange { 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 Step =2; /**< Step for iterator style direction */ + static final int numOfDirections =4; } /** diff --git a/src/host/labyrinth/Game.java b/src/host/labyrinth/Game.java index f9083a9..fee064a 100644 --- a/src/host/labyrinth/Game.java +++ b/src/host/labyrinth/Game.java @@ -18,14 +18,15 @@ * all the supplies of the board before Minotaur catches him and before the * 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 * - Board * - Player + * - HeuristicPlayer * - Game - * + * * Which are the requested classes. We also provide some extra functionalities in: * - Const * - Session @@ -153,10 +154,11 @@ public class Game { if (!Game.getArguments(args)) throw new Exception(""); // 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 board.createBoard(T.playerTileId(), M.playerTileId()); @@ -169,34 +171,36 @@ public class Game { game.waitUser (); // Main game loop while (true) { - int[] m; 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.getStringRepresentation(T.playerTileId(), M.playerTileId()) ); // Loop termination cases 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()) { - 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)) { - System.out.println("New day has come... Tie!!!"); - System.exit(0); + System.out.println("**** New day has come... Tie! ****"); + break; } game.waitUser (); } + for (Player p : players) + p.final_statistics(); + System.exit(0); } catch (Exception e) { // We don't handle exceptions. Print error and exit with error status. diff --git a/src/host/labyrinth/HeuristicPlayer.java b/src/host/labyrinth/HeuristicPlayer.java index 48c5faa..d7538dd 100644 --- a/src/host/labyrinth/HeuristicPlayer.java +++ b/src/host/labyrinth/HeuristicPlayer.java @@ -12,9 +12,6 @@ package host.labyrinth; -import java.util.ArrayList; -//import java.util.Arrays; - /** * @brief * This class represents the game's player who cheats. @@ -23,14 +20,28 @@ class HeuristicPlayer 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 HeuristicPlayer(String name, boolean champion, Board board, int row, int column) { super(name, champion, board, row, column); - path = new ArrayList(); } + /** + * 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) { super(name, champion, board, tileId); - path = new ArrayList(); } /** @} */ @@ -74,34 +85,21 @@ class HeuristicPlayer extends Player { 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 - 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 int[3]: The supplyId in case player picked one (Const.noSupply otherwise). * */ + @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 supplyFlag = 0; + 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) { ++score; // keep score ++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); // Update supply and opponent distance - int dir = Direction.get(id, ret[0]); int smin =DirRange.End, omin =DirRange.End; for (int d = DirRange.Begin ; d= 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); 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 path; /**< our history - * The integer[] format is: - * { dice, tookSupply, SupplyDistance, OpponentDistance} - */ + /** @} */ } diff --git a/src/host/labyrinth/Player.java b/src/host/labyrinth/Player.java index 0311610..dd4bb74 100644 --- a/src/host/labyrinth/Player.java +++ b/src/host/labyrinth/Player.java @@ -12,6 +12,8 @@ package host.labyrinth; +import java.util.ArrayList; + /** * @brief * 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 - * @param id The id of the player * @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 @@ -37,13 +38,16 @@ class Player { this.x = column; this.y = row; 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(); + int[] m = { + Position.toID(row, column), row, column, Const.noSupply + }; board.updateMove(m, playerId); } /** * 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 champion Flag to indicate if a player is a `champion` * @param board Reference to the board of the game @@ -57,7 +61,11 @@ class Player { this.x = Position.toCol(tileId); this.y = Position.toRow(tileId); 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(); + int[] m = { + tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply + }; board.updateMove(m, playerId); } /** @} */ @@ -89,9 +97,11 @@ class Player { ret[1] = Position.toRow(id); ret[2] = Position.toCol(id); ret[3] = Const.noSupply; + 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 @@ -99,16 +109,49 @@ class Player { 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 - System.out.println(name + ":\t*Found a supply. [score: " + score + "]"); } + ++dirCounter[diceDirection]; // update direction counters 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; } + /** + * 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 */ int playerTileId() { return Position.toID(y, x); } /** Utility to access player's row position (row coordinate) */ @@ -147,17 +190,34 @@ class Player { void setChampion (boolean champion) { this.champion = champion; } - /** @} */ - + /** @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 path; + /**< + * our history. The integer[] format is: + *
    + *
  • Integer[0]: tileId - The tile id we choose for the move + *
  • Integer[1]: dice - The dice (a.k.a direction) of move + *
  • Integer[2]: moveStatus - True if it was successful (we can move in that direction) + *
  • Integer[3]: tookSupply - True if we took supply + *
  • Integer[4]: upCounter - Accumulator to count all the up moves + *
  • Integer[5]: righrCounter - Accumulator to count all the right moves + *
  • Integer[6]: downCounter - Accumulator to count all the down moves + *
  • Integer[7]: leftCounter - Accumulator to count all the left moves + *
  • Integer[8]: SupDistance - The distance of the nearest supply (only for heuristic players) + *
  • Integer[9]: OppDistance - The distance of the nearest opponent (only for heuristic players) + *
+ * } + */ /** @} */ }