From 3b3eda40908331fdee4cea3faee5283934f36702 Mon Sep 17 00:00:00 2001 From: Christos Houtouridis Date: Sat, 22 Dec 2018 15:55:48 +0200 Subject: [PATCH] Part 3 code --- .gitignore | 1 + src/net/hoo2/auth/dsproject/snake/Board.java | 12 + src/net/hoo2/auth/dsproject/snake/Game.java | 35 ++- .../auth/dsproject/snake/HeuristicPlayer.java | 4 +- .../auth/dsproject/snake/MinMaxPlayer.java | 290 ++++++++++++++++++ src/net/hoo2/auth/dsproject/snake/Node.java | 102 ++++++ src/net/hoo2/auth/dsproject/snake/Player.java | 35 ++- 7 files changed, 451 insertions(+), 28 deletions(-) create mode 100644 src/net/hoo2/auth/dsproject/snake/MinMaxPlayer.java create mode 100644 src/net/hoo2/auth/dsproject/snake/Node.java diff --git a/.gitignore b/.gitignore index dd96669..fc359fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.classpath *.pdf +/bin/ diff --git a/src/net/hoo2/auth/dsproject/snake/Board.java b/src/net/hoo2/auth/dsproject/snake/Board.java index b4c3318..8ac870d 100644 --- a/src/net/hoo2/auth/dsproject/snake/Board.java +++ b/src/net/hoo2/auth/dsproject/snake/Board.java @@ -1,6 +1,7 @@ package net.hoo2.auth.dsproject.snake; import java.lang.Math; +import java.util.*; /** * @class Board @@ -33,6 +34,7 @@ public class Board { snakes = null; ladders = null; apples = null; + players = null; } /** @@ -64,6 +66,7 @@ public class Board { snakes = new Snake[numOfSnakes]; ladders = new Ladder[numOfLadders]; apples = new Apple[numOfApples]; + players = null; createBoard (); // Complete board preparation and make all the element memory allocations } @@ -81,6 +84,7 @@ public class Board { snakes = new Snake[B.getSnakes().length]; ladders = new Ladder[B.getLadders().length]; apples = new Apple[B.getApples().length]; + players = B.getPlayers(); // reference only (don't need to clone) // Copy B's guts into new memory copyTiles(B.getTiles()); copySnakes(B.getSnakes()); @@ -139,6 +143,13 @@ public class Board { */ void setApples(Apple[] apples) { this.apples = apples; } + /** get reference to players */ + ArrayList getPlayers() { return players; } + /** set reference to players */ + void setPlayers (ArrayList players) { + this.players = players; + } + /** * Copy tiles * @param tiles Source of tiles to use @@ -552,6 +563,7 @@ public class Board { private Snake[] snakes; /**< Board's snakes */ private Ladder[] ladders; /**< Board's ladders */ private Apple[] apples; /**< Board's apples */ + private ArrayList players; /**< oard's copy of players reference vector */ /** @} */ } diff --git a/src/net/hoo2/auth/dsproject/snake/Game.java b/src/net/hoo2/auth/dsproject/snake/Game.java index 1f5f9a1..7d3ac59 100644 --- a/src/net/hoo2/auth/dsproject/snake/Game.java +++ b/src/net/hoo2/auth/dsproject/snake/Game.java @@ -2,7 +2,7 @@ package net.hoo2.auth.dsproject.snake; /** * @mainpage - * @title Snake game project. -- Part 2 -- + * @title Snake game project. -- Part 3 -- * * This is the code documentation page of the Snake game project. * Listed are: @@ -173,20 +173,35 @@ public class Game { boolean registerPlayer (int playerId, String name) { if (players.size() >= MAX_PLAYERS) return false; - players.add(new Player(playerId, name, board)); + players.add(new Player(playerId, name)); return true; } +// /** +// * Register a heuristic player to the game +// * @param playerId The player ID to use +// * @param name The player name to use +// * @return The status of the operation +// */ +// boolean registerHeuristicPlayer (int playerId, String name) { +// if (players.size() >= MAX_PLAYERS) +// return false; +// //players.add(new HeuristicPlayer(playerId, name, board)); +// players.add(new HeuristicPlayer(playerId, name)); +// return true; +// } + /** * Register a heuristic player to the game * @param playerId The player ID to use * @param name The player name to use * @return The status of the operation */ - boolean registerHeuristicPlayer (int playerId, String name) { + boolean registerMinMaxPlayer (int playerId, String name) { if (players.size() >= MAX_PLAYERS) return false; - players.add(new HeuristicPlayer(playerId, name, board)); + //players.add(new HeuristicPlayer(playerId, name, board)); + players.add(new MinMaxPlayer(playerId, name)); return true; } @@ -246,7 +261,7 @@ public class Game { // using a dice throw for (Integer pid : playingOrder.keySet()) { Player p = _getPlayer(pid); - tile = p.getNextMove (p.getTile()); + tile = p.getNextMove (board, p.getTile())[0]; p.statistics(verbose, false); if (tile>= board.getN()*board.getM()) // The first one here is the winner @@ -275,7 +290,7 @@ public class Game { int numOfLadders = 3; int numOfApples = 6; int numOfPlayers = 2; - boolean verbose = false; + boolean verbose = true; // Print caption System.out.println("================== Snake Game =================="); @@ -290,14 +305,14 @@ public class Game { // Player registration, the one is cheater for (int i=0 ; i(); } /* @} */ diff --git a/src/net/hoo2/auth/dsproject/snake/MinMaxPlayer.java b/src/net/hoo2/auth/dsproject/snake/MinMaxPlayer.java new file mode 100644 index 0000000..8712a40 --- /dev/null +++ b/src/net/hoo2/auth/dsproject/snake/MinMaxPlayer.java @@ -0,0 +1,290 @@ +package net.hoo2.auth.dsproject.snake; + +import java.util.*; + +/** + * @class MinMaxPlayer + * @brief Represent a MinMax Player in the Game + * + * The players are playing in a round-robin sequence and we keep track + * for each one of them their playing order, score and place on the board. + * This kind of player, is a cheater. He can control the dice. Not fair dude. + * + * @author Christos Choutouridis AEM:8997 + * @email cchoutou@ece.auth.gr + */ +public class MinMaxPlayer + extends Player { + static final int MINIMAX_TREE_DEPTH = 2; /**< The maximum depth of the minimax tree */ + /** @name Constructors */ + /** @{ */ + /** Default doing nothing constructor */ + public MinMaxPlayer() { + super (); + path = new ArrayList(); + } + /** + * @brief The main constructor + * + * This creates a player for the game + * @param playerId The player's to create + * @param name The name of the player + * @param board Reference to the board the player will play on. + */ + MinMaxPlayer (int playerId, String name) { + super (playerId, name); + path = new ArrayList(); + } + /* @} */ + + /** @name Get/Set interface */ + /** @{ */ + ArrayList getPath() { return path; } + void setPath (ArrayList path) { + this.path = path; + } + /** @} */ + + /** + * Override dice functionality for the player + * @return As this is called from the game only to select playing order + * we cheat and return 1 + */ + @Override + int dice () { + return 1; + } + + /** + * Override get the next move after the user's move + * @param board The board in which we play on + * @param tile The initial tile + * @return The the move as an array + * See @ref Node.nodeMove + */ + @Override + int[] getNextMove (Board board, int tile) { + int [] ret = new int[4]; + Node root = new Node (board); + + createMySubtree(root, 1, tile, selectOpponent(board).getTile()); + // Evaluate each possible dice result and find the better one + Node r = MaxValue (root, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); + // Do the move and get the move data + Integer[] move_data = Arrays.stream (move (board, tile, r.getNodeMove()[3], true)) + .boxed() + .toArray(Integer[]::new); + // Store the move data + path.add(move_data); + ret[0] = move_data[MOVE_TILE_IDX]; + ret[1] = move_data[MOVE_INITTILE_IDX]; + ret[2] = move_data[MOVE_POINTS_IDX]; + ret[3] = move_data[MOVE_ROLL_IDX]; + return ret; // return the new tile position + } + + /** + * The MinMax statistics version + * @param verbose Flag to select the verbosity + * @param sum Flag to select if we need to print a summarize of the user history + */ + @Override + void statistics (boolean verbose, boolean sum) { + if (sum) { + // If we run the summarize + int nSnakes =0; + int nLadders =0; + int nRedApples =0; + int nBlackApples =0; + + // Calculate frequencies + for (int i=0 ; i max_tile) { // max filtering + opp = p; + max_tile = p.getTile(); + } + } + return opp; + } + + /** + * 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 tile the tile of MinMax player + * @param oppTile the tile of the best opponent + */ + private void createMySubtree (Node parent, int depth, int tile, int oppTile) { + int [] moveData; + int [] nodeMove; + for (int roll = 1 ; roll <= 6 ; ++roll) { + Board nodeBoard = new Board (parent.getNodeBoard()); // clone board + moveData = move (nodeBoard, tile, roll, false); // simulate move + nodeMove = new int[4]; // create nodeMove + nodeMove[0] = moveData[MOVE_TILE_IDX]; + nodeMove[1] = moveData[MOVE_INITTILE_IDX]; + nodeMove[2] = moveData[MOVE_POINTS_IDX]; + nodeMove[3] = moveData[MOVE_ROLL_IDX]; + // make child Node + Node child = new Node (parent, depth, nodeMove, nodeBoard); + parent.addChild(child); // add child to tree + createOppSubtree (child, depth+1, nodeMove[0], oppTile); + } + } + + /** + * 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 tile the tile of MinMax player + * @param oppTile the tile of the best opponent + */ + private void createOppSubtree (Node parent, int depth, int tile, int oppTile) { + int [] moveData; + int [] nodeMove; + for (int roll =1 ; roll <= 6 ; ++roll) { + Board nodeBoard = new Board(parent.getNodeBoard()); // clone board + moveData = move (nodeBoard, oppTile, roll, false); // simulate move + nodeMove = new int[4]; + nodeMove[0] = moveData[MOVE_TILE_IDX]; + nodeMove[1] = moveData[MOVE_INITTILE_IDX]; + nodeMove[2] = moveData[MOVE_POINTS_IDX]; + nodeMove[3] = moveData[MOVE_ROLL_IDX]; + Node child = new Node (parent, depth, nodeMove, nodeBoard); // make child Node + parent.addChild(child); // add child to tree + if (depth >= MINIMAX_TREE_DEPTH) { + child.setNodeEvaluation( + evaluate(parent.getNodeMove()[0], // our tile + parent.getNodeMove()[0] - parent.getNodeMove()[1], // our steps + parent.getNodeMove()[2], // our points + nodeMove[0]) // opponent tile + ); + } + else { + createMySubtree (child, depth+1, tile, nodeMove[0]); + } + } + } + /** + * The main evaluation function + * @param tile The current tile of the player + * @param steps The total steps of the move + * @param points The total points of the move + * @param oppTile The tile of the best opponent + * @return The evaluation of the roll + */ + private double evaluate (int tile, int steps, int points, int oppTile) { + if (tile > oppTile) + return 0.65*steps + 0.35*points; + else + return 0.8*steps + 0.2*points - (oppTile - tile)*0.5; + } + + /** + * The Minimax recursive function for the maximizing part + * @param s The current Node + * @param a The alpha for pruning + * @param b The beta for pruning + * @return The selected Node + */ + private Node MaxValue (Node s, double a, double b) { + if (s.getChildren() == null) + return s; + else { + Node r = null; + double vv, v = Double.NEGATIVE_INFINITY; + for (Node c : s.getChildren()) { + if (r == null) r = c; + vv = MinValue (c, a, b).getNodeEvaluation(); + //v = max (v, vv); + if (vv > v) { + v = vv; + r = c; + } + if (vv >= b) + return c; // prune + a = max (a, vv); + } + return r; + } + } + + /** + * The Minimax recursive function for the minimizing part + * @param s The current Node + * @param a The alpha for pruning + * @param b The beta for pruning + * @return The selected Node + */ + private Node MinValue (Node s, double a, double b) { + if (s.getChildren() == null) + return s; + else { + Node r = null; + double vv, v = Double.POSITIVE_INFINITY; + for (Node c : s.getChildren()) { + if (r == null) r = c; + vv = MaxValue (c, a, b).getNodeEvaluation(); + //v = min (v, vv); + if (vv < v) { + v = vv; + r = c; + } + if (vv <= a) + return c; // prune + b = min (b, vv); + } + return r; + } + } + + /** @return the minimum of x and y */ + private static double min (double x, double y) { + return (x < y) ? x : y; + } + /** return the maximum of x and y */ + private static double max (double x, double y) { + return (x > y) ? x : y; + } + /** @} */ + + /** @name Data members package access only */ + /** @{ */ + private ArrayList path; /**< Players history as required */ + /** @} */ +} diff --git a/src/net/hoo2/auth/dsproject/snake/Node.java b/src/net/hoo2/auth/dsproject/snake/Node.java new file mode 100644 index 0000000..d6784fd --- /dev/null +++ b/src/net/hoo2/auth/dsproject/snake/Node.java @@ -0,0 +1,102 @@ +package net.hoo2.auth.dsproject.snake; + +import java.util.*; + +/** + * @class Node + * @brief Represent a node in the Minimax tre + * + * @author Christos Choutouridis AEM:8997 + * @email cchoutou@ece.auth.gr + */ +class Node { + + + /** @name Constructors */ + /** @{ */ + /** Null initialize constructor */ + Node () { } + /** The main constructor for the Node */ + Node (Node parent, int nodeDepth, int [] nodeMove, Board nodeBoard) { + this.parent = parent; + this.children = null; + this.nodeDepth = nodeDepth; + this.nodeMove = nodeMove; + this.nodeBoard = nodeBoard; + this.nodeEvaluation = 0; + } + /** 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; + } + /**@} */ + + /** @name Get/Set interface */ + /** @{ */ + /** Get parent */ + Node getParent() { return parent; } + /** get children */ + ArrayList + 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; } + + /** set parent */ + void setParent(Node parent) { this.parent = parent; } + /** set children */ + void setChildren(ArrayList 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; + } + /**@}*/ + + /** @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 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 */ + /**@}*/ +} diff --git a/src/net/hoo2/auth/dsproject/snake/Player.java b/src/net/hoo2/auth/dsproject/snake/Player.java index fb7745a..c1179e7 100644 --- a/src/net/hoo2/auth/dsproject/snake/Player.java +++ b/src/net/hoo2/auth/dsproject/snake/Player.java @@ -32,7 +32,6 @@ public class Player { Player () { playerId = score = tile = 0; name = ""; - board = null; lastMove = new int[MOVE_DATA_SIZE]; dryMove = new int[MOVE_DATA_SIZE]; } @@ -44,10 +43,9 @@ public class Player { * @param name The name of the player * @param board Reference to the board the player will play on. */ - Player (int playerId, String name, Board board) { + Player (int playerId, String name) { this.playerId = playerId; this.name = name; - this.board = board; score = 0; tile = 0; lastMove = new int[MOVE_DATA_SIZE]; @@ -69,12 +67,7 @@ public class Player { void setScore (int score) { this.score = score; } - /** Get reference to Board */ - Board getBoard () { return board; } - /** Set Board reference */ - void setBoard (Board board) { - this.board = board; - } + /** Get tile */ int getTile () { return tile; } /** Set tile */ @@ -109,16 +102,25 @@ public class Player { /** * Get the next tile after the user's move + * @param board The board in which we play on * @param tile The initial tile - * @return The tile after the move + * @return The the move as an array + * [0]: Tile after move + * [1]: The roll of the dice * @note * We add this move() 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 */ - int getNextMove (int tile) { - return move (tile, dice(), true)[MOVE_TILE_IDX]; + int[] getNextMove (Board board, int tile) { + int [] ret = new int[4]; // allocate move memory + move (board, tile, dice(), true); // make the move + ret[0] = dryMove[MOVE_TILE_IDX]; + ret[1] = dryMove[MOVE_INITTILE_IDX]; + ret[2] = dryMove[MOVE_POINTS_IDX]; + ret[3] = dryMove[MOVE_ROLL_IDX]; + return ret; } /** @@ -149,7 +151,7 @@ public class Player { * We could also had members like:
 MoveData previous; 
to help us further. * We kept this representation just because it was a requirement. */ - int [] move (int tile, int roll, boolean run) { + int [] move (Board board, int tile, int roll, boolean run) { int t; Arrays.fill(dryMove, 0); @@ -161,7 +163,8 @@ public class Player { do { keepGoing = false; // Check apples - if ((t = board.checkApple(tile, run)) != 0) { + //if ((t = board.checkApple(tile, run)) != 0) { + if ((t = board.checkApple(tile, true)) != 0) { dryMove[MOVE_POINTS_IDX] += t; if (t > 0) ++dryMove[MOVE_RED_APPLES_IDX]; @@ -169,7 +172,8 @@ public class Player { ++dryMove[MOVE_BLACK_APPLES_IDX]; } // Check ladder - if ((t = board.checkLadder(tile, run)) != tile) { + //if ((t = board.checkLadder(tile, run)) != tile) { + if ((t = board.checkLadder(tile, true)) != tile) { tile = t; ++dryMove[MOVE_LADDERS_IDX]; keepGoing = true; @@ -226,7 +230,6 @@ public class Player { int playerId; /**< Player's ID */ String name; /**< Player's name */ int score; /**< Player's score */ - Board board; /**< Reference to current board */ int tile; /**< Player's tile location */ int[] lastMove; /**< move() return data for statistics. These are only valid after a true move */ private int [] dryMove; /**< Fake (dry run) move return buffer */