Labyrinth/src/host/labyrinth/HeuristicPlayer.java

271 lines
10 KiB
Java

/**
* @file HeuristicPlayer.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/
package host.labyrinth;
/**
* @brief
* This class represents the game's player who cheats.
*/
class HeuristicPlayer extends Player {
/** @name Constructors */
/** @{ */
/**
* Create a new player and put him at the row-column coordinates
* @param name The name of the player
* @param champion Flag to indicate if a player is a `champion`
* @param board Reference to the board of the game
* @param row The row coordinate of initial player position
* @param column The column coordinate of initial player's position
*/
public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) throws Exception {
super(name, champion, board, row, column);
}
/**
* Create a new player and put him at the row-column coordinates
* @param name The name of the player
* @param champion Flag to indicate if a player is a `champion`
* @param board Reference to the board of the game
* @param tileId The tileId coordinate of player's initial position
*/
public HeuristicPlayer(String name, boolean champion, Board board, int tileId) throws Exception {
super(name, champion, board, tileId);
}
/** @} */
/** @name Board's main application interface */
/** @{ */
/**
* Utility to get the distance of a possible supply in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @return The distance or Const.noSupply
*/
int supplyInDirection(int currentPos, int direction) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
pos = new Position(pos.getRow(), pos.getCol(), direction);
if (board.hasSupply(pos.getId()))
return i+1;
}
return Const.noSupply;
}
/**
* Utility to get the distance of a possible opponent in some direction
* @param currentPos The current position of the player
* @param direction The direction to check
* @return The distance or Const.noOpponent
*/
int opponetInDirection(int currentPos, int direction) {
Position pos = new Position(Position.toRow(currentPos), Position.toCol(currentPos));
int [] opp = board.getOpponentMove(playerId);
for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
pos = new Position(pos.getRow(), pos.getCol(), direction);
if (opp[MOVE_TILE_ID] == pos.getId())
return i+1;
}
return Const.noOpponent;
}
/**
* This is the main move evaluation function.
* @param currentPos The current position of the player (before the move to evaluate)
* @param direction The direction (a.k.a. the move) to evaluate
* @return A signed real number. The higher the output, the higher the evaluation.
*/
double evaluate (int currentPos, int direction) {
int opDist = opponetInDirection (currentPos, direction);
int supDist = supplyInDirection(currentPos, direction);
// saturate
opDist = (opDist == Const.noOpponent) ? 0: opDist;
supDist = (supDist == Const.noSupply) ? 0 : supDist;
return ((supDist != 0)? (1.0/supDist * Const.supplyFactor) : 0)
- ((opDist != 0) ? (1.0/opDist * Const.opponentFactor) : 0);
}
/**
* Selects the best possible move to return
* @param currentPos Player's current position to the board
* @return The move array
*
* @note
* This function always return a new move.
*/
int[] getNextMove(int currentPos) {
Range dirs = new Range(DirRange.Begin, DirRange.End, DirRange.Step);
int N = dirs.size();
double[] eval = new double[N];
int [] eval_dir = new int[N];
for (int i =0, dir = dirs.get() ; dir != Const.EOR ; dir = dirs.get(), ++i) {
if (board.isWalkable(currentPos, dir))
eval[i] = evaluate(currentPos, dir);
else
eval[i] = Double.NEGATIVE_INFINITY;
eval_dir[i] = dir;
}
int dir;
if (isUnevaluated(eval, N)) {
ShuffledRange r = new ShuffledRange(DirRange.Begin, DirRange.End, DirRange.Step);
do
dir = r.get();
while (!board.isWalkable(currentPos, dir));
}
else {
dir = directionOfMax (eval, eval_dir, N);
}
Position new_pos = new Position( Position.toRow(currentPos), Position.toCol(currentPos), dir );
int [] ret = {new_pos.getId(), new_pos.getRow(), new_pos.getCol(), dir};
return ret;
}
/**
* HeuristicPlayer's move.
*
* A player of this kind cheats. He does not throw a dice to get a direction. In contrary he
* calculates his next move very carefully.
* If the player is a champion then he also picks up a possible supply from the tile.
*
* @param id The id of the starting tile.
* @return An array containing player's final position and possible supply of that position.
* The array format is:
* <ul>
* <li> int[0]: The tileId of the final player's position.
* <li> int[1]: The row of the final player's position.
* <li> int[2]: The column of the final player's position.
* <li> int[3]: The dice/direction of the move.
* </ul>
*/
@Override
int[] move(int id) {
// Initialize return array with the current data
int[] ret = getNextMove(id);
y = Position.toRow(ret[MOVE_TILE_ID]);
x = Position.toCol(ret[MOVE_TILE_ID]);
int supplyFlag =0, moveFlag =1;
// In case of a champion player, try also to pick a supply
if (champion && (board.tryPickSupply(ret[MOVE_TILE_ID]) != Const.noSupply)) {
++score; // keep score
++supplyFlag;
}
++dirCounter[ret[MOVE_DICE]]; // update direction counters
board.updateMove(ret, playerId);
// Update supply and opponent distance
int smin =DirRange.End, omin =DirRange.End;
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
int s = supplyInDirection (ret[MOVE_TILE_ID], d);
int o = opponetInDirection(ret[MOVE_TILE_ID], d);
if (s >= 0 && s < smin) smin = s;
if (o >= 0 && o < omin) omin = o;
}
// update path
Integer[] p = {
ret[MOVE_TILE_ID], ret[MOVE_DICE], moveFlag, supplyFlag,
dirCounter[Direction.UP], dirCounter[Direction.RIGHT], dirCounter[Direction.DOWN], dirCounter[Direction.LEFT],
(smin != DirRange.End)? smin:Const.noSupply, (omin != DirRange.End)? omin:Const.noOpponent
};
path.add(p);
return ret;
}
/**
* Prints round information for the player
*/
void statistics() {
if (!path.isEmpty()) {
Integer[] last = path.get(path.size()-1);
String who = String.format("%12s", name);
System.out.print(who + ": score[" + score + "]" + ", dice =" + last[1] + ", tileId =" + last[0] + " (" + Position.toRow(last[0]) + ", " + Position.toCol(last[0]) + ")");
if (last[2] == 0)
System.out.println(" *Can not move.");
else if (last[3] != 0)
System.out.println(" *Found a supply.");
else
System.out.println("");
// extra prints for heuristic
if (last[8] != Const.noSupply) System.out.println(" supply =" + last[8]);
else System.out.println(" supply = blind");
if (last[9] != Const.noOpponent) System.out.println(" opponent =" + last[9]);
else System.out.println(" opponent = blind");
}
}
/**
* Prints final statistics for the player
*/
void final_statistics () {
String who = String.format("%12s", name);
System.out.println();
System.out.println(who + ": score[" + score + "]");
System.out.println(" Moves up: " + dirCounter[Direction.UP]);
System.out.println(" Moves right: " + dirCounter[Direction.RIGHT]);
System.out.println(" Moves down: " + dirCounter[Direction.DOWN]);
System.out.println(" Moves left: " + dirCounter[Direction.LEFT]);
}
/** @} */
/**
* A small utility to extract the direction of maximum evaluation result.
*
* We search into the \c eval results and find the index of the maximum evaluation.
* Then we return the direction of \c eval_dir matrix at the same index we found.
*
* @param eval Array with evaluation results for each direction
* @param eval_dir Array with the matching direction to \c eval array
* @param N The size of both arrays
* @return The direction of maximum evaluation. If \c eval is empty returns the first item \c eval[0]
* @note
* This function should not be called if there is at least one evaluation result in \c eval
*/
private int directionOfMax (double[] eval, int[] eval_dir, int N) {
double M = Double.NEGATIVE_INFINITY;
int M_idx = 0;
for (int i =0; i < N ; ++i) {
if (eval[i] > M) {
M = eval[i];
M_idx = i;
}
}
return eval_dir[M_idx];
}
/**
* A small utility to check if there is at least one evaluation result in the \c eval array
* @param eval The array to check
* @param N The size of the array
* @return True if there is none, false otherwise
*/
private boolean isUnevaluated (double[] eval, int N) {
for (int i =0 ; i<N ; ++i)
if (eval[i] != 0 && eval[i] != Double.NEGATIVE_INFINITY)
return false;
return true;
}
/** @name Class data */
/** @{ */
/** @} */
}