@@ -5,3 +5,4 @@ | |||
*.classpath | |||
/bin/ |
@@ -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<Player> getPlayers() { return players; } | |||
/** set reference to players */ | |||
void setPlayers (ArrayList<Player> 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<Player> players; /**< oard's copy of players reference vector */ | |||
/** @} */ | |||
} | |||
@@ -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<numOfPlayers && i<MAX_PLAYERS; ++i) { | |||
if (i == 0) | |||
game.registerHeuristicPlayer(i+1, String.format("Player %d", i+1)); | |||
game.registerMinMaxPlayer(i+1, String.format("Player %d", i+1)); | |||
else | |||
game.registerPlayer(i+1, String.format("Player %d", i+1)); | |||
} | |||
game.setTurns(game.getPlayers()); // Choose play order | |||
game.setTurns(game.getPlayers()); // Choose play order | |||
game.board.setPlayers(game.getPlayers()); // inform the board with the players | |||
Player winner; | |||
do // Keep going until someone finishes | |||
do // Keep going until someone finishes | |||
winner = game.round (verbose); | |||
while (winner == null | |||
&& game.getRound() < MAX_GAME_ROUNDS); | |||
@@ -31,8 +31,8 @@ public class HeuristicPlayer | |||
* @param name The name of the player | |||
* @param board Reference to the board the player will play on. | |||
*/ | |||
HeuristicPlayer (int playerId, String name, Board board) { | |||
super (playerId, name, board); | |||
HeuristicPlayer (int playerId, String name) { | |||
super (playerId, name); | |||
path = new ArrayList<Integer[]>(); | |||
} | |||
/* @} */ | |||
@@ -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<Integer[]>(); | |||
} | |||
/** | |||
* @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<Integer[]>(); | |||
} | |||
/* @} */ | |||
/** @name Get/Set interface */ | |||
/** @{ */ | |||
ArrayList<Integer[]> getPath() { return path; } | |||
void setPath (ArrayList<Integer[]> 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<path.size() ; ++i) { | |||
nSnakes += path.get(i)[MOVE_SNAKES_IDX]; | |||
nLadders+= path.get(i)[MOVE_LADDERS_IDX]; | |||
nRedApples += path.get(i)[MOVE_RED_APPLES_IDX]; | |||
nBlackApples += path.get(i)[MOVE_BLACK_APPLES_IDX]; | |||
} | |||
// Print the results | |||
System.out.println(""); | |||
System.out.println("*** Statistics for " + name + " ***"); | |||
System.out.println(" Number of Snake bites : " + nSnakes); | |||
System.out.println(" Number of Ladders used : " + nLadders); | |||
System.out.println(" Number of Red Apples eaten : " + nRedApples); | |||
System.out.println(" Number of Black Apples eaten: " + nBlackApples); | |||
} | |||
else | |||
// Call the base version | |||
super.statistics(verbose, sum); | |||
} | |||
/** @name Private helper API */ | |||
/** @{ */ | |||
/** | |||
* Select the the best Opponent | |||
* @param board Reference to current board | |||
* @return Reference to the player who is most far in the board | |||
*/ | |||
private Player selectOpponent (Board board) { | |||
Player opp = null; | |||
int max_tile = Integer.MIN_VALUE; | |||
for (Player p : board.getPlayers()) { | |||
if (p == this) // Please do not return me | |||
continue; | |||
if (opp == null) opp = p; // init variable | |||
if (p.getTile() > 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<Integer[]> path; /**< Players history as required */ | |||
/** @} */ | |||
} |
@@ -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<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; } | |||
/** 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; | |||
} | |||
/**@}*/ | |||
/** @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 */ | |||
/**@}*/ | |||
} |
@@ -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: <pre> MoveData previous; </pre> 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 */ | |||