@@ -223,11 +223,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,13 +239,14 @@ 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(); | |||
apples[i].setPoints(0); | |||
// eat it | |||
if (eat) | |||
apples[i].setPoints(0); | |||
} | |||
} | |||
return ds; | |||
@@ -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,26 +29,21 @@ 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 */ | |||
/** @{ */ | |||
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 ArrayList<Player> players; /**< A reference to players */ | |||
private Map<Integer, Integer> playingOrder; | |||
/** @} */ | |||
/** @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 | |||
* with the result of a dice. If there is another one with the same dice result return true. | |||
@@ -59,12 +52,33 @@ public class Game { | |||
* @param players Reference to already register players | |||
* @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 die, ArrayList<Integer[]> pairs) { | |||
for (int i =0; i<pairs.size() ; ++i) | |||
if (pairs.get(i)[1] == die) | |||
return true; | |||
return false; | |||
} | |||
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); | |||
} | |||
} | |||
} | |||
} | |||
private Player _getPlayer(int playerId) { | |||
for (Player p : players) { | |||
if (p.getPlayerId() == playerId) | |||
return p; | |||
} | |||
return null; | |||
} | |||
/** @} */ | |||
@@ -75,6 +89,8 @@ public class Game { | |||
round = 0; | |||
board = new Board(); | |||
players = new ArrayList<>(); | |||
playingOrder | |||
= new HashMap<Integer, Integer>(); | |||
} | |||
/** | |||
@@ -92,6 +108,8 @@ public class Game { | |||
// delegate constructors | |||
board = new Board (N, M, numOfSnakes, numOfLadders, numOfApples); | |||
players = new ArrayList<>(); | |||
playingOrder | |||
= new HashMap<Integer, Integer>(); | |||
} | |||
/** @} */ | |||
@@ -118,6 +136,11 @@ public class Game { | |||
void setPlayers(ArrayList<Player> players) { | |||
this.players = players; | |||
} | |||
Map<Integer, Integer> getPlayingOrder () { return playingOrder; } | |||
void setPlayingOrder (Map<Integer, Integer> playingOrder) { | |||
this.playingOrder = playingOrder; | |||
} | |||
/** @} */ | |||
/** @name Public functionality */ | |||
@@ -134,26 +157,49 @@ public class Game { | |||
players.add(new Player(playerId, name, board)); | |||
return true; | |||
} | |||
/** | |||
* Register a 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)); | |||
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) { | |||
Map<Integer, Integer> setTurns (ArrayList<Player> players) { | |||
int[] pairData = new int[2]; | |||
Integer[] PairData; | |||
ArrayList<Integer[]> | |||
pairList = new ArrayList<>(); | |||
for (int d, 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); | |||
d = players.get(i).dice (); | |||
while (_search (d, pairList)); | |||
pairData[0] = players.get(i).getPlayerId(); | |||
pairData[1] = d; | |||
PairData = Arrays.stream(pairData).boxed().toArray( Integer[]::new ); | |||
pairList.add(PairData); | |||
} | |||
// Sort players vector | |||
players.sort((p1, p2) -> | |||
Integer.compare (p1.getTurn(), p2.getTurn()) | |||
); | |||
// 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 | |||
*/ | |||
@@ -168,17 +214,19 @@ public class Game { | |||
* | |||
* @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 (p.getTile()); | |||
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 +251,7 @@ public class Game { | |||
int numOfLadders = 3; | |||
int numOfApples = 6; | |||
int numOfPlayers = 2; | |||
boolean verbose = false; | |||
// Print caption | |||
System.out.println("================== Snake Game =================="); | |||
@@ -214,14 +263,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) | |||
game.registerPlayer(i+1, String.format("Player %d", i+1)); | |||
game.playOrder(); // Choose play order | |||
// 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)); | |||
else | |||
game.registerPlayer(i+1, String.format("Player %d", i+1)); | |||
} | |||
game.setTurns(game.getPlayers()); // Choose play order | |||
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 +284,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 +300,11 @@ public class Game { | |||
else | |||
System.out.println(" " +p.getName() + ": " + p.getScore() +" points"); | |||
} | |||
for (Player p : game.getPlayers()) { | |||
if (p.getClass().getSimpleName().equals("HeuristicPlayer")) | |||
p.statistics(verbose, true); | |||
} | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
package net.hoo2.auth.dsproject.snake; | |||
import java.util.*; | |||
public class HeuristicPlayer | |||
extends Player { | |||
private ArrayList<Integer[]> path; | |||
public HeuristicPlayer() { | |||
super (); | |||
path = new ArrayList<Integer[]>(); | |||
} | |||
HeuristicPlayer (int playerId, String name, Board board) { | |||
super (playerId, name, board); | |||
path = new ArrayList<Integer[]>(); | |||
} | |||
@Override | |||
int dice () { | |||
return 1; | |||
} | |||
@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; | |||
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; | |||
} | |||
} | |||
Integer[] move_data = Arrays.stream(move (tile, roll, true)) | |||
.boxed() | |||
.toArray(Integer[]::new); | |||
path.add(move_data); | |||
return tile + roll; | |||
} | |||
@Override | |||
void statistics (boolean verbose, boolean sum) { | |||
if (sum) { | |||
int nSnakes =0; | |||
int nLadders =0; | |||
int nRedApples =0; | |||
int nBlackApples =0; | |||
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]; | |||
} | |||
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 | |||
super.statistics(verbose, sum); | |||
} | |||
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]; | |||
} | |||
} |
@@ -1,5 +1,7 @@ | |||
package net.hoo2.auth.dsproject.snake; | |||
import java.util.Arrays; | |||
/** | |||
* @class Player | |||
* @brief Represent a Player in the Game | |||
@@ -11,13 +13,26 @@ package net.hoo2.auth.dsproject.snake; | |||
* @email cchoutou@ece.auth.gr | |||
*/ | |||
public class Player { | |||
static final int MOVE_DATA_SIZE = 9; | |||
static final int MOVE_TILE_IDX = 0; | |||
static final int MOVE_INITTILE_IDX = 1; | |||
static final int MOVE_STEPS_IDX = 2; | |||
static final int MOVE_ROLL_IDX = 3; | |||
static final int MOVE_POINTS_IDX = 4; | |||
static final int MOVE_SNAKES_IDX = 5; // Always <= 1 | |||
static final int MOVE_LADDERS_IDX = 6; // Always <= 1 | |||
static final int MOVE_RED_APPLES_IDX = 7; // Always <= 1 | |||
static final int MOVE_BLACK_APPLES_IDX = 8; // 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 | |||
@@ -33,7 +48,8 @@ public class Player { | |||
this.board = board; | |||
score = 0; | |||
tile = 0; | |||
turn = 0; | |||
lastMove = new int[MOVE_DATA_SIZE]; | |||
dryMove = new int[MOVE_DATA_SIZE]; | |||
} | |||
/** @} */ | |||
@@ -63,17 +79,29 @@ public class Player { | |||
void setTile (int tile) { | |||
this.tile = tile; | |||
} | |||
/** Get turn */ | |||
int getTurn () { return turn; } | |||
/** Set turn */ | |||
void setTurn (int turn) { | |||
this.turn = turn; | |||
int[] getLastMove () { return lastMove; } | |||
void setLastMove (int[] lastMove) { | |||
this.lastMove = lastMove; | |||
} | |||
/** @} */ | |||
/** @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); | |||
} | |||
// Liskov substitution principle | |||
int getNextMove (int tile) { | |||
return move (tile, dice(), true)[MOVE_TILE_IDX]; | |||
} | |||
/** | |||
* @brief Move functionality | |||
* This function prints to stdout various logs about user interaction with elements | |||
@@ -81,57 +109,87 @@ public class Player { | |||
* @param tile The initial tile of the player | |||
* @param die The die to play | |||
* @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 | |||
*/ | |||
int [] move (int tile, int die) { | |||
int [] ret = new int[4]; | |||
int [] move (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) { | |||
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) { | |||
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]; | |||
if (run) { | |||
lastMove = dryMove.clone(); | |||
this.tile = lastMove[MOVE_TILE_IDX]; | |||
score += lastMove[MOVE_POINTS_IDX]; | |||
} | |||
return dryMove; | |||
} | |||
/**@} */ | |||
/** @name Data members (private) */ | |||
//Liskov substitution principle | |||
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 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 */ | |||
Board board; /**< Reference to current board */ | |||
int tile; /**< Player's tile location */ | |||
int[] lastMove; | |||
private int [] dryMove; | |||
/** @} */ | |||
} | |||