Compare commits

..

1 Commits

Author SHA1 Message Date
Christos Houtouridis
43c591bf4a A dynamic programming test 2018-12-02 19:44:46 +02:00
9 changed files with 136 additions and 481 deletions

2
.gitignore vendored
View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

View File

@ -1,7 +1,6 @@
package net.hoo2.auth.dsproject.snake;
import java.lang.Math;
import java.util.*;
/**
* @class Board
@ -34,7 +33,6 @@ public class Board {
snakes = null;
ladders = null;
apples = null;
players = null;
}
/**
@ -66,7 +64,6 @@ 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
}
@ -84,7 +81,6 @@ 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());
@ -143,13 +139,6 @@ 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
@ -563,7 +552,6 @@ 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 3 --
* @title Snake game project. -- Part 2 --
*
* This is the code documentation page of the Snake game project.
* Listed are:
@ -173,35 +173,20 @@ public class Game {
boolean registerPlayer (int playerId, String name) {
if (players.size() >= MAX_PLAYERS)
return false;
players.add(new Player(playerId, name));
players.add(new Player(playerId, name, board));
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) {
boolean registerHeuristicPlayer (int playerId, String name) {
if (players.size() >= MAX_PLAYERS)
return false;
//players.add(new HeuristicPlayer(playerId, name, board));
players.add(new MinMaxPlayer(playerId, name));
players.add(new HeuristicPlayer(playerId, name, board));
return true;
}
@ -261,7 +246,7 @@ public class Game {
// using a dice throw
for (Integer pid : playingOrder.keySet()) {
Player p = _getPlayer(pid);
tile = p.getNextMove (board, p.getTile())[0];
tile = p.getNextMove (p.getTile());
p.statistics(verbose, false);
if (tile>= board.getN()*board.getM())
// The first one here is the winner
@ -284,11 +269,11 @@ public class Game {
*/
public static void main(String[] args) {
// Current project requirements
int lines = 20;
int columns = 10;
int numOfSnakes = 3;
int numOfLadders = 3;
int numOfApples = 6;
int lines = 6; //20;
int columns = 6; //10;
int numOfSnakes = 1; //3;
int numOfLadders = 1; //3;
int numOfApples = 2; //6;
int numOfPlayers = 2;
boolean verbose = true;
@ -305,14 +290,14 @@ public class Game {
// 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));
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
game.board.setPlayers(game.getPlayers()); // inform the board with the players
game.setTurns(game.getPlayers()); // Choose play order
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);

View File

@ -15,13 +15,23 @@ import java.util.*;
*/
public class HeuristicPlayer
extends Player {
// evaluate configuration
static final double FACTOR_TILE = 0.15;
static final double FACTOR_FWD_STEP = 0.55;
static final double FACTOR_BACK_STEP = -1.0;
static final double FACTOR_UP_POINTS = 0.3;
static final double FACTOR_DWN_POINTS= -1.0;
static final double FACTOR_INIT_EVAL = 1.0;
static final double FACTOR_ROUND = -1.0;
static final double FACTOR_GAME = 10.0;
/** @name Constructors */
/** @{ */
/** Default doing nothing constructor */
public HeuristicPlayer() {
super ();
path = new ArrayList<Integer[]>();
path = new ArrayList<Integer[]>();
evalData = new EvalData[board.getN() * board.getM() +1];
}
/**
* @brief The main constructor
@ -31,9 +41,10 @@ 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) {
super (playerId, name);
path = new ArrayList<Integer[]>();
HeuristicPlayer (int playerId, String name, Board board) {
super (playerId, name, board);
path = new ArrayList<Integer[]>();
evalData = new EvalData[board.getN() * board.getM() +1];
}
/* @} */
@ -62,19 +73,10 @@ public class HeuristicPlayer
*/
@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;
}
}
roll = evalProcess(tile);
// Do the move and get the move data
Integer[] move_data = Arrays.stream(move (tile, roll, true))
.boxed()
@ -119,6 +121,51 @@ public class HeuristicPlayer
super.statistics(verbose, sum);
}
int evalProcess (int tile) {
int[] check = new int[MOVE_DATA_SIZE];
int begin;
int end = board.getN() * board.getM();
int roll;
EvalData e = new EvalData();
for (int i =0 ; i<=end ; ++i) {
evalData[i] = new EvalData();
evalData[i].evaluation = Double.NEGATIVE_INFINITY;
}
for (begin =tile ; begin < end ; ++begin) {
if ((board.checkLadder(begin, false) != begin) ||
(board.checkSnake(begin) != begin))
continue;
int initRound = evalData[begin].round;
double initEval = evalData[begin].evaluation;
for (roll = 1 ; roll <= 6 ; ++roll) {
check = move (begin, roll, false);
e.game = _saturate (check);
e.round = initRound +1;
e.from = begin;
e.roll = roll;
e.steps = check[MOVE_STEPS_IDX];
e.points = check[MOVE_POINTS_IDX];
e.evaluation = _evalFormula (check[MOVE_TILE_IDX], e.steps, e.points, e.round, initEval, e.game);
if (e.evaluation > evalData[check[MOVE_TILE_IDX]].evaluation) {
evalData[check[MOVE_TILE_IDX]].copy(e);
}
}
}
// find route back of the optimal path
e = evalData[board.getN() * board.getM()];
EvalData last = e;
while (e.from != tile) {
last = e;
e = evalData[e.from];
}
return last.roll; // return the first choice
}
double evaluate (int tile, int roll) {
return 0;
}
/**
* The main evaluation function
@ -126,15 +173,53 @@ public class HeuristicPlayer
* @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);
private double _evalFormula (int tile, int steps, int points, int round, double initEval, boolean game) {
initEval = (initEval == Double.NEGATIVE_INFINITY) ? 0 : initEval;
return tile * FACTOR_TILE +
steps * ((steps>0) ? FACTOR_FWD_STEP : FACTOR_BACK_STEP) +
points * ((points>0) ? FACTOR_UP_POINTS : FACTOR_DWN_POINTS) +
initEval * FACTOR_INIT_EVAL +
round * FACTOR_ROUND +
((game) ? 1:0) * FACTOR_GAME;
}
return 0.65*check[MOVE_STEPS_IDX] + 0.35*check[MOVE_POINTS_IDX];
private boolean _saturate (int[] check) {
// make adjustments if game is finished
int tiles = board.getM() * board.getN();
boolean game = (check[MOVE_TILE_IDX] >= tiles) ? true : false;
if (check[MOVE_TILE_IDX]> tiles) {
check[MOVE_STEPS_IDX] -= check[MOVE_TILE_IDX] - tiles;
check[MOVE_TILE_IDX] = tiles;
}
return game;
}
/** @name Data members package access only */
/** @{ */
private ArrayList<Integer[]> path; /**< Players history as required */
private EvalData[] evalData;
/** @} */
}
class EvalData {
int round;
int from;
int roll;
int steps;
int points;
boolean game;
double evaluation;
EvalData () { }
void copy (EvalData e) {
round = e.round;
from = e.from;
roll = e.roll;
steps = e.steps;
points = e.points;
game = e.game;
evaluation = e.evaluation;
}
}

View File

@ -1,290 +0,0 @@
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

@ -1,102 +0,0 @@
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

@ -32,6 +32,7 @@ public class Player {
Player () {
playerId = score = tile = 0;
name = "";
board = null;
lastMove = new int[MOVE_DATA_SIZE];
dryMove = new int[MOVE_DATA_SIZE];
}
@ -43,9 +44,10 @@ 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) {
Player (int playerId, String name, Board board) {
this.playerId = playerId;
this.name = name;
this.board = board;
score = 0;
tile = 0;
lastMove = new int[MOVE_DATA_SIZE];
@ -67,7 +69,12 @@ 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 */
@ -75,18 +82,10 @@ public class Player {
this.tile = tile;
}
/** 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 */
@ -102,25 +101,16 @@ 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 the move as an array
* [0]: Tile after move
* [1]: The roll of the dice
* @return The tile after the move
* @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;
int getNextMove (int tile) {
return move (tile, dice(), true)[MOVE_TILE_IDX];
}
/**
@ -151,7 +141,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 (Board board, int tile, int roll, boolean run) {
int [] move (int tile, int roll, boolean run) {
int t;
Arrays.fill(dryMove, 0);
@ -163,8 +153,7 @@ public class Player {
do {
keepGoing = false;
// Check apples
//if ((t = board.checkApple(tile, run)) != 0) {
if ((t = board.checkApple(tile, true)) != 0) {
if ((t = board.checkApple(tile, run)) != 0) {
dryMove[MOVE_POINTS_IDX] += t;
if (t > 0)
++dryMove[MOVE_RED_APPLES_IDX];
@ -172,8 +161,7 @@ public class Player {
++dryMove[MOVE_BLACK_APPLES_IDX];
}
// Check ladder
//if ((t = board.checkLadder(tile, run)) != tile) {
if ((t = board.checkLadder(tile, true)) != tile) {
if ((t = board.checkLadder(tile, run)) != tile) {
tile = t;
++dryMove[MOVE_LADDERS_IDX];
keepGoing = true;
@ -230,6 +218,7 @@ 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 */