Compare commits

...

8 Commits
v1.0 ... master

Author SHA1 Message Date
Christos Houtouridis
b58da8a26e Typo 2018-12-22 18:42:40 +02:00
Christos Houtouridis
7f5f3f34e8 Part C report 2018-12-22 18:39:02 +02:00
Christos Houtouridis
3b3eda4090 Part 3 code 2018-12-22 15:55:48 +02:00
Christos Houtouridis
b583d63095 Report: A first deliverable edition 2018-12-02 22:09:58 +02:00
Christos Houtouridis
da8ad7259a A get/set pair was missing. 2018-12-02 22:03:09 +02:00
Christos Houtouridis
ee3dfb7cea Add report files to repo so we can proceed with part 2 2018-12-02 19:56:34 +02:00
Christos Houtouridis
984f537cb8 Some comments added 2018-12-01 23:48:32 +02:00
Christos Houtouridis
4b3a936391 A first version of the new structure 2018-12-01 22:42:49 +02:00
10 changed files with 854 additions and 108 deletions

2
.gitignore vendored
View File

@ -1,8 +1,8 @@
*bin/*
*doc/*
*report/*
*deliverable/*
*.project
*.classpath
*.pdf
/bin/

BIN
report/Snake.odt Normal file

Binary file not shown.

BIN
report/resources/poster.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -1,7 +1,5 @@
package net.hoo2.auth.dsproject.snake;
import java.lang.Math;
/**
* @class Apple
* @brief Represent an Apple in the Board.

View File

@ -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
@ -223,11 +234,11 @@ public class Board {
* @param tile The tile to check
* @return The result tile
*/
int checkLadder (int tile) {
int checkLadder (int tile, boolean climb) {
for (int i =0 ; i<ladders.length ; ++i) {
if (ladders[i].getDownStepId() == tile &&
ladders[i].getBroken() == false) {
ladders[i].setBroken(true);
ladders[i].setBroken(climb);
return ladders[i].getUpStepId();
}
}
@ -239,12 +250,13 @@ public class Board {
* @param tile The tile to check
* @return The score difference
*/
int checkApple (int tile) {
int checkApple (int tile, boolean eat) {
int ds =0; // delta-score
for (int i =0 ; i<apples.length ; ++i) {
if (apples[i].getAppleTileId() == tile) {
// eat it
ds = apples[i].getPoints();
// eat it
if (eat)
apples[i].setPoints(0);
}
}
@ -551,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; /**< board's copy of players reference vector */
/** @} */
}

View File

@ -2,7 +2,7 @@ package net.hoo2.auth.dsproject.snake;
/**
* @mainpage
* @title Snake game project. -- Part 1 --
* @title Snake game project. -- Part 3 --
*
* This is the code documentation page of the Snake game project.
* Listed are:
@ -13,9 +13,7 @@ package net.hoo2.auth.dsproject.snake;
* @author Christos Choutouridis AEM:8997
* @email cchoutou@ece.auth.gr
*/
import java.lang.Math;
import java.util.ArrayList;
import java.util.*;
/**
* @class Game
@ -31,8 +29,8 @@ import java.util.ArrayList;
public class Game {
/** @name Constants */
/**@{ */
static final int MAX_PLAYERS = 4; /**< The maximum number of allowed players in the game */
static final int MAX_GAME_ROUNDS = 10000; /**< the maximum allowed round of the game */
static final int MAX_PLAYERS = 6; /**< The maximum number of allowed players in the game */
static final int MAX_GAME_ROUNDS = 1000; /**< the maximum allowed round of the game */
/**@} */
/** @name Private data members */
@ -40,31 +38,65 @@ public class Game {
private int round; /**< The current round of the game */
private Board board; /**< A reference to board */
private ArrayList<Player> players; /**< A reference to players */
private Map<Integer, Integer> playingOrder; /**< A Map with {PlayerID, Dice-roll} pairs
@note
This way of storing the playing order is unnecessary.
The old style was far more better. We had a turn variable
in each Player and we used to sort the players vector based on
that value. This made the rest of the program simpler, faster, easer.
We have adopt the above approach just because it is one of the
homework requirements.
*/
/** @} */
/** @name private api */
/** @{ */
/**
* Dice functionality
* @return An integer in the range [1 .. 6]
*/
private int _dice () {
return (int)(1 + Math.random()*5);
}
/**
* Search the players already in the players vector and compare their turn to play
* Search the players already in the pairs vector and compare their turn to play
* with the result of a dice. If there is another one with the same dice result return true.
*
* @param turn The dice result to check in order to find player's turn to play
* @param players Reference to already register players
* @param roll The dice result to check in order to find player's turn to play
* @param pairs Reference to already register players and their dice result
* @return True if there is another player with the same dice result
*/
private boolean _search (int die, ArrayList<Player> players) {
for (int i =0; i<players.size() ; ++i)
if (players.get(i).getTurn() == die)
private boolean _search (int roll, ArrayList<Integer[]> pairs) {
for (int i =0; i<pairs.size() ; ++i)
if (pairs.get(i)[1] == roll)
return true;
return false;
}
/**
* Sort the pairs vector using second parameter witch represent the
* dice roll. We use bubblesort for the implementation.
* @param pairs A vector with {PlayerId, dice-roll} pairs to sort
*/
private void _sort (ArrayList<Integer[]> pairs) {
Integer[] temp;
for (int i=pairs.size()-1 ; i>0 ; --i) {
for (int j =0 ; j<i ; ++j) {
if (pairs.get(j)[1] > pairs.get(i)[1]) {
temp = pairs.get(i);
pairs.set(i, pairs.get(j));
pairs.set(j, temp);
}
}
}
}
/**
* Helper function to get a player from players vector using it's ID
* @param playerId The player's ID to seek
* @return Reference to Player or null
*/
private Player _getPlayer(int playerId) {
for (Player p : players) {
if (p.getPlayerId() == playerId)
return p;
}
return null;
}
/** @} */
@ -75,6 +107,8 @@ public class Game {
round = 0;
board = new Board();
players = new ArrayList<>();
playingOrder
= new HashMap<Integer, Integer>();
}
/**
@ -92,6 +126,8 @@ public class Game {
// delegate constructors
board = new Board (N, M, numOfSnakes, numOfLadders, numOfApples);
players = new ArrayList<>();
playingOrder
= new HashMap<Integer, Integer>();
}
/** @} */
@ -118,6 +154,12 @@ public class Game {
void setPlayers(ArrayList<Player> players) {
this.players = players;
}
/** Get reference to playingOrder Map */
Map<Integer, Integer> getPlayingOrder () { return playingOrder; }
/** Set the playingOrder Map */
void setPlayingOrder (Map<Integer, Integer> playingOrder) {
this.playingOrder = playingOrder;
}
/** @} */
/** @name Public functionality */
@ -131,29 +173,71 @@ 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 registerMinMaxPlayer (int playerId, String name) {
if (players.size() >= MAX_PLAYERS)
return false;
//players.add(new HeuristicPlayer(playerId, name, board));
players.add(new MinMaxPlayer(playerId, name));
return true;
}
/**
* @brief Set the playing order of players
* This function emulates the classic roll of dice to decide which player
* plays first which second and so on.
*/
void playOrder () {
int d;
for (int i =0 ; i<players.size() ; ++i) {
do
// Keep rolling the dice as the die belongs to another user
d = _dice();
while (_search (d, players));
players.get(i).setTurn(d);
}
// Sort players vector
players.sort((p1, p2) ->
Integer.compare (p1.getTurn(), p2.getTurn())
);
}
Map<Integer, Integer> setTurns (ArrayList<Player> players) {
int[] pairData = new int[2]; // We use plain int[] to create an Integer[]
Integer[] PairData; // The Integer[] to push to ArrayList<>
ArrayList<Integer[]>
pairList = new ArrayList<>(); // Use use ArrayList before Map in order to sort
for (int d, i =0 ; i<players.size() ; ++i) {
do
// Keep rolling the dice until we get a unique value
d = players.get(i).dice ();
while (_search (d, pairList));
// Make Integer[] pair
pairData[0] = players.get(i).getPlayerId();
pairData[1] = d;
PairData = Arrays.stream(pairData)
.boxed()
.toArray(Integer[]::new);
pairList.add(PairData); // Add to vector
}
// Sort playeingOrger
_sort (pairList);
// Make and return the Map
for (int i =0 ; i<pairList.size() ; ++i) {
playingOrder.put(pairList.get(i)[0], pairList.get(i)[1]);
}
return playingOrder;
}
/**
* Sort the players according to their score
*/
@ -166,19 +250,22 @@ public class Game {
/**
* A game round. In each round every player plays when is his turn
*
* @param verbose Flag to indicate how much we print to console
* @return The winner if we have one, or null
*/
Player round () {
int [] mret;
Player round (boolean verbose) {
int tile;
++round; // keep track of round
// traverse the players vector and move each player on the board
// using a dice throw
for (int i =0 ; i<players.size() ; ++i) {
mret = players.get(i).move (players.get(i).getTile(), _dice());
if (mret[0]>= board.getN()*board.getM())
for (Integer pid : playingOrder.keySet()) {
Player p = _getPlayer(pid);
tile = p.getNextMove (board, p.getTile())[0];
p.statistics(verbose, false);
if (tile>= board.getN()*board.getM())
// The first one here is the winner
return players.get(i);
return p;
}
return null; // No one finished yet
}
@ -203,6 +290,7 @@ public class Game {
int numOfLadders = 3;
int numOfApples = 6;
int numOfPlayers = 2;
boolean verbose = true;
// Print caption
System.out.println("================== Snake Game ==================");
@ -214,14 +302,18 @@ public class Game {
Game game = new Game (lines, columns, numOfSnakes, numOfLadders, numOfApples);
// game.getBoard().createElementBoard(); // Not explicitly required
// Player registration
for (int i=0 ; i<numOfPlayers && i<MAX_PLAYERS; ++i)
// Player registration, the one is cheater
for (int i=0 ; i<numOfPlayers && i<MAX_PLAYERS; ++i) {
if (i == 0)
game.registerMinMaxPlayer(i+1, String.format("Player %d", i+1));
else
game.registerPlayer(i+1, String.format("Player %d", i+1));
game.playOrder(); // 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
winner = game.round ();
winner = game.round (verbose);
while (winner == null
&& game.getRound() < MAX_GAME_ROUNDS);
if (game.getRound() == MAX_GAME_ROUNDS) {
@ -231,8 +323,10 @@ public class Game {
}
// Print the results
System.out.println("***** Game finished *****");
System.out.println("*** Game finished ***");
System.out.println("");
System.out.println("");
System.out.println("*** Game Results ***");
System.out.println("Rounds: " + game.getRound());
System.out.println("Winner: " + winner.getName() + " [" + winner.getScore() +" points]");
System.out.println("Score: ");
@ -245,5 +339,13 @@ public class Game {
else
System.out.println(" " +p.getName() + ": " + p.getScore() +" points");
}
// Print the extra statistics for the heuristic player only
// We use a little reflection for that
for (Player p : game.getPlayers()) {
if (p.getClass().getSimpleName().equals("HeuristicPlayer"))
p.statistics(verbose, true);
}
}
}

View File

@ -0,0 +1,140 @@
package net.hoo2.auth.dsproject.snake;
import java.util.*;
/**
* @class HeuristicPlayer
* @brief Represent a Heuristic 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 HeuristicPlayer
extends Player {
/** @name Constructors */
/** @{ */
/** Default doing nothing constructor */
public HeuristicPlayer() {
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.
*/
HeuristicPlayer (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 tile after the user's move
* @param tile The initial tile
* @return The tile after the move
*/
@Override
int getNextMove (int tile) {
Map<Integer, Double> moves = new HashMap<Integer, Double>();
double max = Double.NEGATIVE_INFINITY;
double ev = Double.NEGATIVE_INFINITY;
int roll = 0;
// Evaluate each possible dice result and find the better one
for (int r=1 ; r<=6 ; ++r) {
moves.put (new Integer(r), evaluate (tile, r));
if ((ev = moves.get(r)) > max) {
max = ev;
roll = r;
}
}
// Do the move and get the move data
Integer[] move_data = Arrays.stream(move (tile, roll, true))
.boxed()
.toArray(Integer[]::new);
// Store the move data
path.add(move_data);
return tile + roll; // return the new tile position
}
/**
* The Heuristic statistics version
* @param verbose Flag to select the verbosity
* @param sum Flag to select if we need to print a summarize of the user hystory
*/
@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);
}
/**
* The main evaluation function
* @param tile The current tile of the player
* @param roll the roll to check
* @return The evaluation of the roll
*/
private double evaluate (int tile, int roll) {
int[] check = new int[MOVE_DATA_SIZE];
check = move(tile, roll, false);
return 0.65*check[MOVE_STEPS_IDX] + 0.35*check[MOVE_POINTS_IDX];
}
/** @name Data members package access only */
/** @{ */
private ArrayList<Integer[]> path; /**< Players history as required */
/** @} */
}

View File

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

View File

@ -0,0 +1,102 @@
package net.hoo2.auth.dsproject.snake;
import java.util.*;
/**
* @class Node
* @brief Represent a node in the Minimax tree
*
* @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 */
/**@}*/
}

View File

@ -1,5 +1,8 @@
package net.hoo2.auth.dsproject.snake;
import java.util.Arrays;
import java.lang.Math;
/**
* @class Player
* @brief Represent a Player in the Game
@ -11,13 +14,26 @@ package net.hoo2.auth.dsproject.snake;
* @email cchoutou@ece.auth.gr
*/
public class Player {
/** Helper variables to keep track of the move() return values @see move() */
static final int MOVE_DATA_SIZE = 9; /**< The move return data array size */
static final int MOVE_TILE_IDX = 0; /**< The index of tile */
static final int MOVE_INITTILE_IDX = 1; /**< The index of init tile */
static final int MOVE_STEPS_IDX = 2; /**< The index of steps */
static final int MOVE_ROLL_IDX = 3; /**< The index of roll */
static final int MOVE_POINTS_IDX = 4; /**< The index of points */
static final int MOVE_SNAKES_IDX = 5; /**< The index for number of snakes (Always <= 1) */
static final int MOVE_LADDERS_IDX = 6; /**< The index for number of ladders (Always <= 1) */
static final int MOVE_RED_APPLES_IDX = 7; /**< The index for number of red apples (Always <= 1) */
static final int MOVE_BLACK_APPLES_IDX = 8; /**< The index for number of black apples (Always <= 1) */
/** @name Constructors */
/** @{ */
/** Default doing nothing constructor */
Player () {
playerId = score = tile = turn = 0;
playerId = score = tile = 0;
name = "";
board = null;
lastMove = new int[MOVE_DATA_SIZE];
dryMove = new int[MOVE_DATA_SIZE];
}
/**
* @brief The main constructor
@ -27,13 +43,13 @@ 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;
turn = 0;
lastMove = new int[MOVE_DATA_SIZE];
dryMove = new int[MOVE_DATA_SIZE];
}
/** @} */
@ -51,87 +67,172 @@ 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 */
void setTile (int tile) {
this.tile = tile;
}
/** Get turn */
int getTurn () { return turn; }
/** Set turn */
void setTurn (int turn) {
this.turn = turn;
/** Get lastMove */
int[] getLastMove () { return lastMove; }
/** Set lastMove */
void setLastMove (int[] lastMove) {
this.lastMove = lastMove;
}
/** Get dryMove */
int[] getDryMove () { return dryMove; }
/** Set dryMove */
void setDryMove (int[] dryMove) {
this.dryMove = dryMove;
}
/** @} */
/** @name Exposed API members */
/** @{ */
/**
* Dice functionality for the players
* @return An integer in the range [1 .. 6]
*/
int dice () {
return (int)(1 + Math.random()*5);
}
/**
* 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 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 (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;
}
/**
* @brief Move functionality
* This function prints to stdout various logs about user interaction with elements
*
* @param tile The initial tile of the player
* @param die The die to play
* @param roll The dice roll to play
* @param run Flag to indicate if we fake the move(dry run) or we do make the move.
* @return
* int[0] tile after move
* int[1] number of snake bites
* int[2] number of ladder used
* int[3] number of apples eaten
* int[MOVE_TILE_IDX (0)] tile after move
* int[MOVE_INITTILE_IDX (1)] tile before move
* int[MOVE_STEPS_IDX (2)] number of total steps for the move
* int[MOVE_ROLL_IDX (3)] the roll of the dice
* int[MOVE_POINTS_IDX (4)] the points of the move
* int[MOVE_SNAKES_IDX (5)] number of snake bites
* int[MOVE_LADDERS_IDX (6)] number of ladders used
* int[MOVE_RED_APPLES_IDX (7)] number of red apples eaten
* int[MOVE_BLACK_APPLES_IDX (8)] number of black apples eaten
* @note
* Probably a mutable class like: <pre>
* class MoveData {
* int tile;
* int initTile;
* ...
* }
* </pre>
* for returning value would be better, less error prone, cleaner, etc...
* 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 die) {
int [] ret = new int[4];
int [] move (Board board, int tile, int roll, boolean run) {
int t;
tile += die; // Initial move
//System.out.println(name + " +" + die + "->tile: " + tile); //XXX: Debug only
Arrays.fill(dryMove, 0);
dryMove[MOVE_INITTILE_IDX] = tile;
dryMove[MOVE_ROLL_IDX] = roll;
tile += roll; // Initial move
boolean keepGoing;
do {
keepGoing = false;
// Check apples
t = board.checkApple(tile);
if (t != 0) {
score += t;
++ret[3];
System.out.println(name + " Apple @" + tile + " " + t + " points");
//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];
else
++dryMove[MOVE_BLACK_APPLES_IDX];
}
// Check ladder
t = board.checkLadder(tile);
if (t != tile) {
System.out.println(name + " Ladder @" + tile + " new position " + t);
//if ((t = board.checkLadder(tile, run)) != tile) {
if ((t = board.checkLadder(tile, true)) != tile) {
tile = t;
++ret[2];
++dryMove[MOVE_LADDERS_IDX];
keepGoing = true;
}
// Check snakes
t = board.checkSnake(tile);
if (t != tile) {
System.out.println(name + " Ouch!! Snake @" + tile + " new position " + t);
if ((t = board.checkSnake(tile)) != tile) {
tile = t;
++ret[1];
++dryMove[MOVE_SNAKES_IDX];
keepGoing = true;
}
} while (keepGoing);
ret[0] = this.tile = tile;
return ret;
dryMove[MOVE_TILE_IDX] = tile;
dryMove[MOVE_STEPS_IDX]= tile - dryMove[MOVE_INITTILE_IDX];
// Check if we do run the move
if (run) {
lastMove = dryMove.clone();
this.tile = lastMove[MOVE_TILE_IDX];
score += lastMove[MOVE_POINTS_IDX];
}
return dryMove;
}
/**
* The base statistics version
* @param verbose Flag to select the verbosity
* @param sum Flag to select if we need to print a summarize (not used here)
* @note
* We added this function because:
* 1) we need to keep the "Is a" relationship (Liskov substitution principle)
* 2) It help us get rid of the move() console output code
* 3) makes the code smaller
*/
void statistics (boolean verbose, boolean sum) {
int begin = lastMove[MOVE_INITTILE_IDX];
int roll = lastMove[MOVE_ROLL_IDX];
int last = lastMove[MOVE_TILE_IDX];
if (verbose)
System.out.println(name + " +" + roll + "->tile: " + (begin + roll));
if (lastMove[MOVE_RED_APPLES_IDX] > 0)
System.out.println(name + " Apple " + lastMove[MOVE_POINTS_IDX] + " points");
if (lastMove[MOVE_BLACK_APPLES_IDX] > 0)
System.out.println(name + " Apple " + lastMove[MOVE_POINTS_IDX] + " points");
if (lastMove[MOVE_LADDERS_IDX] > 0)
System.out.println(name + " Ladder @" + (begin + roll) + " new position " + last);
if (lastMove[MOVE_SNAKES_IDX] > 0)
System.out.println(name + " Ouch!! Snake @" + (begin + roll) + " new position " + last);
// No use of sum here
}
/**@} */
/** @name Data members (private) */
/** @name Data members package access only */
/** @{ */
private int playerId; /**< Player's ID */
private String name; /**< Player's name */
private int score; /**< Player's score */
private Board board; /**< Reference to current board */
private int tile; /**< Player's tile location */
private int turn; /**< Player's turn of playing */
int playerId; /**< Player's ID */
String name; /**< Player's name */
int score; /**< Player's score */
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 */
/** @} */
}