Browse Source

First draft of 2nd version

tags/v2.0b1
parent
commit
d581bd6c02
6 changed files with 310 additions and 31 deletions
  1. BIN
      ds_project_2020-2021_PartB_v3.pdf
  2. +52
    -0
      src/host/labyrinth/Board.java
  3. +44
    -17
      src/host/labyrinth/Common.java
  4. +2
    -2
      src/host/labyrinth/Game.java
  5. +191
    -0
      src/host/labyrinth/HeuristicPlayer.java
  6. +21
    -12
      src/host/labyrinth/Player.java

BIN
ds_project_2020-2021_PartB_v3.pdf View File


+ 52
- 0
src/host/labyrinth/Board.java View File

@@ -13,6 +13,7 @@
package host.labyrinth;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.IntFunction;

/**
@@ -36,6 +37,7 @@ class Board {
tiles = null;
supplies =null;
walls = new ArrayList<Edge>();
moves = new ArrayList<Integer[]>();
}

/**
@@ -52,6 +54,7 @@ class Board {
tiles = new Tile[N*N];
supplies = new Supply[S];
walls = new ArrayList<Edge>();
moves = new ArrayList<Integer[]>();
}

/**
@@ -77,6 +80,9 @@ class Board {
this.supplies = b.supplies.clone();
for (Edge it: b.walls)
this.walls.add(new Edge(it));
for (Integer[] m : b.moves) {
this.moves.add(m);
}
}
/** @} */

@@ -174,6 +180,15 @@ class Board {
&& !(Position.toID(row, col) == 0 && direction == Direction.DOWN);
}

/**
* Utility function to check if there is a supply on the tile or not
* @param tileId The tile to check
* @return Yes/no
*/
boolean hasSupply (int tileId) {
return (Const.noSupply != tiles[tileId].hasSupply(supplies)) ? true : false;
}

/**
* Try to pick supply from a tile. If succeed it also erases the
* supply from the board.
@@ -191,6 +206,7 @@ class Board {
return supplyId;
}

/**
* A plain fair dice functionality provided by the board.
* @return A random direction;
@@ -202,6 +218,27 @@ class Board {

/** @return the size of each site of the board. */
int size () { return N; }
int[][] getOpponentMoves (int playerId) {
int[][] ret = new int[moves.size()-1][Const.moveItems];
int ii= 0, ri =0;
for (Integer[] m : moves) {
if (ii != playerId)
ret[ri++] = Arrays.stream(m).mapToInt(i->i).toArray();
++ii;
}
return ret;
}
int generatePlayerId () {
moves.add(null);
return moves.size() -1;
}

void updateMove(int[] m, int playerId) {
moves.set(playerId, Arrays.stream(m).boxed().toArray(Integer[]::new));
}

/** @} */

/**
@@ -233,6 +270,13 @@ class Board {
*/
ArrayList<Edge> getWalls() { return walls; }

/**
* @note Use it with care. Any use of this function results to what Sean Parent calls "incidental data-structure".
* <a href="https://github.com/sean-parent/sean-parent.github.io/blob/master/better-code/03-data-structures.md"> see also here</a>
* @return Reference to inner walls array.
*/
ArrayList<Integer[]> getMoves() { return moves; }

void setN(int N) { this.N = N; }
void setS(int S) { this.S = S; }
void setW(int W) { this.W = W; }
@@ -257,6 +301,13 @@ class Board {
*/
void setWalls (ArrayList<Edge> walls) { this.walls= walls; }

/**
* @param moves Reference to moves that we want to act as replacement for the inner moves vector.
* @note Use with care.
* Any call to this function will probably add memory for the garbage collector.
*/
void setMoves(ArrayList<Integer[]> moves) { this.moves =moves; }

/** @} */


@@ -562,5 +613,6 @@ class Board {
* Array to hold all the walls using the edge representation
* required by the closed room preventing algorithm.
*/
private ArrayList<Integer[]> moves;
/** @} */
}

+ 44
- 17
src/host/labyrinth/Common.java View File

@@ -13,6 +13,7 @@ package host.labyrinth;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.IntFunction;

/**
* Class to hold constant values for entire application
@@ -20,8 +21,14 @@ import java.util.Collections;
class Const {
static final int maxTileWalls = 2; /**< Number of maximum walls for each tile on the board */
static final int noSupply =-1; /**< Number to indicate the absent of supply */
static final int noOpponent =-1; /**< Number to indicate the absent of supply */
static final int noTileId =-1; /**< Number to indicate wrong tileId */
static final int EOR =-1; /**< Number to indicate the End Of Range */
static final int moveItems =4; /**< The number of items return by move() */
static final int viewDistance =3; /**< The max distance of the Heuristic player's ability to see */

static final double opponentFactor =1.0;
static final double supplyFactor =0.65;
}
/**
* Application wide object to hold settings like values for the session.
@@ -34,23 +41,6 @@ class Session {
static boolean interactive = false; /**< When true each round of the game requires user input */
}

/**
* Helper C++-like enumerator class to hold direction
*/
class Direction {
static final int UP =1; /**< North direction */
static final int RIGHT =3; /**< East direction */
static final int DOWN =5; /**< South direction */
static final int LEFT =7; /**< West direction */

/**
* Utility to get the opposite direction.
* @param direction Input direction
* @return The opposite direction
*/
static int opposite (int direction) { return (direction+4)%DirRange.End; }
}

/**
* Helper C++ like enumerator class for direction ranged loops.
*
@@ -69,6 +59,36 @@ class DirRange {
static final int Step =2; /**< Step for iterator style direction */
}

/**
* Helper C++-like enumerator class to hold direction
*/
class Direction {
static final int UP =1; /**< North direction */
static final int RIGHT =3; /**< East direction */
static final int DOWN =5; /**< South direction */
static final int LEFT =7; /**< West direction */

/**
* Utility to get the opposite direction.
* @param direction Input direction
* @return The opposite direction
*/
static int opposite (int direction) { return (direction+4)%DirRange.End; }

static int get (int fromId, int toId) {
if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)-1) == toId)
return Direction.LEFT;
else if (Position.toID(Position.toRow(fromId), Position.toCol(fromId)+1) == toId)
return Direction.RIGHT;
else if (Position.toID(Position.toRow(fromId)+1, Position.toCol(fromId) ) == toId)
return Direction.UP;
else if (Position.toID(Position.toRow(fromId)-1, Position.toCol(fromId) ) == toId)
return Direction.DOWN;
else
return DirRange.End;
}
}

/**
* @brief
* An Application wide board position implementation holding just the id coordinate.
@@ -200,6 +220,13 @@ class Range {
return Const.EOR;
}

/**
* @return The size of the underline structure
*/
int size () {
return numbers.size();
}

/** @name protected data types */
/** @{ */
protected ArrayList<Integer> numbers; /**< handle to range */


+ 2
- 2
src/host/labyrinth/Game.java View File

@@ -155,8 +155,8 @@ public class Game {
// Create a game, a board and 2 players.
Game game = new Game();
Board board = new Board(Session.boardSize, Session.supplySize);
Player T = new Player(1, "Theseus", true, board, 0);
Player M = new Player(2, "Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2));
Player T = new HeuristicPlayer("Theseus", true, board, 0);
Player M = new Player("Minotaur", false, board, Position.toID(Session.boardSize/2, Session.boardSize/2));

// Populate data to the board
board.createBoard(T.playerTileId(), M.playerTileId());


+ 191
- 0
src/host/labyrinth/HeuristicPlayer.java View File

@@ -0,0 +1,191 @@
/**
* @file Player.java
*
* @author
* Anastasia Foti AEM:8959
* <anastaskf@ece.auth.gr>
*
* @author
* Christos Choutouridis AEM:8997
* <cchoutou@ece.auth.gr>
*/

package host.labyrinth;

import java.util.ArrayList;
//import java.util.Arrays;

/**
* @brief
* This class represents the game's player who cheats.
*/
class HeuristicPlayer extends Player {

/** @name Constructors */
/** @{ */
public HeuristicPlayer(String name, boolean champion, Board board, int row, int column) {
super(name, champion, board, row, column);
path = new ArrayList<Integer[]>();
}

public HeuristicPlayer(String name, boolean champion, Board board, int tileId) {
super(name, champion, board, tileId);
path = new ArrayList<Integer[]>();
}
/** @} */

/** @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(Position.toRow(pos.getId()), Position.toCol(pos.getId()), 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 [][] opps = board.getOpponentMoves(playerId);

for (int i=0 ; board.isWalkable(pos.getId(), direction) && i<Const.viewDistance ; ++i) {
pos = new Position(Position.toRow(pos.getId()), Position.toCol(pos.getId()), direction);
for (int o =0 ; o<opps.length; ++o) {
if (opps[o][0] == pos.getId())
return i+1;
}
}
return Const.noOpponent;
}

double evaluate (int currentPos, int dice) {
int opDist = opponetInDirection (currentPos, dice);
int supDist = supplyInDirection(currentPos, dice);

// saturate
opDist = (opDist == Const.noOpponent) ? Integer.MAX_VALUE : opDist;
supDist = (supDist == Const.noSupply) ? Integer.MAX_VALUE : supDist;
return 1.0/supDist * Const.supplyFactor
- 1.0/opDist * Const.opponentFactor;
}

int directionOfMax (double[] eval, int[] eval_dir, int N) {
double M = Double.NEGATIVE_INFINITY;
int M_idx = -1;
for (int i =0; i < N ; ++i) {
if (eval[i] > M) {
M = eval[i];
M_idx = i;
}
}
return eval_dir[M_idx];
}

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;
}

// Must return a new move always
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 );
return new_pos.getId();
}

/**
* 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 supplyId in case player picked one (Const.noSupply otherwise).
* </ul>
*/
int[] move(int id) {
// Initialize return array with the current data
int[] ret = new int[Const.moveItems];
ret[0] = getNextMove(id);
ret[1] = y = Position.toRow(ret[0]);
ret[2] = x = Position.toCol(ret[0]);
int supplyFlag = 0;
// In case of a champion player, try also to pick a supply
if (champion && (ret[3] = board.tryPickSupply(ret[0])) != Const.noSupply) {
++score; // keep score
++supplyFlag;
System.out.println(name + ":\t*Found a supply. [score: " + score + "]");
}
board.updateMove(ret, playerId);

// Update supply and opponent distance
int dir = Direction.get(id, ret[0]);
int smin =DirRange.End, omin =DirRange.End;
for (int d = DirRange.Begin ; d<DirRange.End ; d += DirRange.Step) {
int s = supplyInDirection (ret[0], d);
int o = opponetInDirection(ret[0], d);
if (s < smin) smin = s;
if (o < omin) omin = o;
}
Integer[] p = {dir, supplyFlag, smin, omin };

path.add(p);
return ret;
}
/** @} */
/** @name Class data */
/** @{ */
private ArrayList<Integer[]> path; /**< our history
* The integer[] format is:
* { dice, tookSupply, SupplyDistance, OpponentDistance}
*/

/** @} */
}

+ 21
- 12
src/host/labyrinth/Player.java View File

@@ -29,14 +29,16 @@ class Player {
* @param row The row coordinate of initial player position
* @param column The column coordinate of initial player's position
*/
Player(int id, String name, boolean champion, Board board, int row, int column) {
this.playerId = id;
Player(String name, boolean champion, Board board, int row, int column) {
this.playerId = board.generatePlayerId();
this.name = name;
this.board = board;
this.score = 0;
this.x = column;
this.y = row;
this.champion = champion;
int[] m = {Position.toID(row, column), row, column, Const.noSupply};
board.updateMove(m, playerId);
}

/**
@@ -47,14 +49,16 @@ class Player {
* @param board Reference to the board of the game
* @param tileId The tileId coordinate of player's initial position
*/
Player(int id, String name, boolean champion, Board board, int tileId) {
this.playerId = id;
Player(String name, boolean champion, Board board, int tileId) {
this.playerId = board.generatePlayerId();
this.name = name;
this.board = board;
this.score = 0;
this.x = Position.toCol(tileId);
this.y = Position.toRow(tileId);
this.champion = champion;
int[] m = {tileId, Position.toRow(tileId), Position.toCol(tileId), Const.noSupply};
board.updateMove(m, playerId);
}
/** @} */

@@ -80,7 +84,11 @@ class Player {
*/
int[] move(int id) {
// Initialize return array with the current data
int[] ret = {id, Position.toRow(id), Position.toCol(id), Const.noSupply};
int[] ret = new int[Const.moveItems];
ret[0] = id;
ret[1] = Position.toRow(id);
ret[2] = Position.toCol(id);
ret[3] = Const.noSupply;

int diceDirection = board.dice(); // throw the dice
if (board.isWalkable(id, diceDirection)) { // The result is walkable
@@ -94,6 +102,7 @@ class Player {
++score; // keep score
System.out.println(name + ":\t*Found a supply. [score: " + score + "]");
}
board.updateMove(ret, playerId);
}
else
System.out.println(name + ":\t*Can not move.");
@@ -143,12 +152,12 @@ class Player {
/** @name Class data */
/** @{ */
private int playerId; /**< The unique identifier of the player */
private String name; /**< The name of the player */
private Board board; /**< Reference to the session's boards */
private int score; /**< The current score of the player */
private int x; /**< The column coordinate of the player on the board */
private int y; /**< The row coordinate of the player on the board */
private boolean champion; /**< Champion indicate a player who plays against the Minotaur */
protected int playerId; /**< The unique identifier of the player */
protected String name; /**< The name of the player */
protected Board board; /**< Reference to the session's boards */
protected int score; /**< The current score of the player */
protected int x; /**< The column coordinate of the player on the board */
protected int y; /**< The row coordinate of the player on the board */
protected boolean champion; /**< Champion indicate a player who plays against the Minotaur */
/** @} */
}

Loading…
Cancel
Save