package scotlandyard.engine.impl;
import java.util.ArrayList;
import java.util.List;
import scotlandyard.engine.spec.IBMap;
import scotlandyard.engine.spec.IGame;
import scotlandyard.engine.spec.IPlayer;
import scotlandyard.engine.spec.IUser;
/**
* game object implements IGame
*
* @author simon
* @version 3.0
*/
public class Game implements IGame {
private String id;
private BMap map;
private String mapPath;
private List<IPlayer> players = new ArrayList<IPlayer>();
private int round = 0;
private char status = NEW;
private String whoWon = "false";
private volatile int turn = -1;
/**
* returns a string representing the winner
* @return string for who won the game
*/
public String getWhoWon()
{
return whoWon;
}
/**
* set who won the game
*/
public void setWhoWon(String winner)
{
whoWon = winner;
}
/**
* constructs an empty game object
*/
public Game() {
}
/**
* constructs a game object with an id
*/
public Game(String id) {
this.id = id;
}
/**
* construct a game object with an id and a xml mapfile
* @param id
* @param mapFile
* @throws Exception
*/
public Game(String id, String mapFile) throws Exception {
this(id);
this.map = new BMap(mapFile);
}
/**
* looks at an array to see if it contains an item
* @param array array to test
* @param item item to look for
* @return true if the item is contained in the array
*/
public static boolean contains(Integer[] array, Integer item) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(item)) {
return true;
}
}
return false;
}
/**
* gets the game status of the current game
* @param _status
* @return string representation of the constant
*/
public static String getStatusDefinition(final char _status) {
final String result;
switch (_status) {
case NEW:
result = "NEW";
break;
case STARTED:
result = "STARTED";
break;
case FINISHED:
result = "FINISHED";
break;
default:
result = "UNKNOWN";
break;
}
return result;
}
/**
* adds a player to the game
* @return boolean true if it worked false other wise
*/
@Override
public boolean addPlayer(final IUser user, final boolean mrx)
throws Exception {
if (mrx && getMrX() != null) {
throw new Exception("This game already has Mr.X");
}
if (this.status != NEW) {
throw new Exception(
"This game has already been started, create another game");
}
if (getPlayerIndex(user.getEmail()) > -1) {
throw new Exception("There is already a player with the email ["
+ user.getEmail() + "] in this game");
}
final Player p = new Player(user, mrx);
p.icon = "d.png";
// initializeTickets(p);
// p.position = map.getEmptyPosition();
// map.positions[p.position]=p.email;
if (mrx) {
int c = this.players.size();
p.icon="x.png";
this.players.add(0, p);
return this.players.size() == c + 1;
}
return this.players.add(p);
}
/**
* test if player can move or not
* @param player
* @return true if player can move, else false
*/
public boolean canPlayerMove(IPlayer player) {
if (contains(this.getLegalMoves(player.getEmail()), 1)) {
return true;
}
if (contains(this.getLegalMoves(player.getEmail()), 2)) {
return true;
}
if (contains(this.getLegalMoves(player.getEmail()), 4)) {
return true;
}
if (contains(this.getLegalMoves(player.getEmail()), 8)) {
return true;
}
if (contains(this.getLegalMoves(player.getEmail()), 16)) {
return true;
}
return false;
}
/**
* converts a integer into a binary number
* @return int[] the number as a binary
*/
@Override
public int[] decomposeBinary(int binary) {
int[] result = new int[5];
result[4] = (binary >= 16) ? 16 : 0;
result[3] = (binary >= 8) ? 8 : 0;
result[2] = (binary >= 4) ? 4 : 0;
result[1] = (binary >= 2) ? 2 : 0;
result[0] = (binary >= 1) ? 1 : 0;
return result;
}
/**
* get whos turn it is where the int represents
* an index in player array
* @return int
*/
@Override
public synchronized int getCurrentTurn() {
return turn;
}
/**
* get game id
* @return String id of game
*/
@Override
public String getId() {
return id;
}
/**
* gets all the legal moves a player can make based on there tickets, position of all players
* @return Integer[] representing legal moves with numbers representing different transport types and combinations of transports
*/
@Override
public Integer[] getLegalMoves(String email) {
if (!getWhosTurn().equals(email) || this.getStatus() == FINISHED) {
return new Integer[] {};
}
int p = getPlayerIndex(email);
final IPlayer player = this.players.get(p);
int[] result = map.getPossibleMoves(player.getPosition());
final Integer[] copy = new Integer[result.length];
// used for bitwise comparison, as tickets are encoded in binary
// BUS : 0001
// player has : 0110
// result : 0000
final boolean no_bus = player.getTickets(IBMap.BUS) < 1;
final boolean no_taxi = player.getTickets(IBMap.TAXI) < 1;
final boolean no_water = player.getTickets(IBMap.WATER) < 1;
final boolean no_ug = player.getTickets(IBMap.UG) < 1;
final boolean no_double = player.getTickets(IBMap.DOUBLE) < 1;
// first get all possible moves
// then filter them according to the number of tickets remaining
for (int i = 0; i < result.length; i++) {
// if the position is empty, or occupied by Mr X, then its OK
copy[i] = 0;
if (map.positions[i] == null
|| this.getMrX().getEmail().equals(map.positions[i])) {
copy[i] = result[i];
int temp = copy[i]; // need temp variable to stop transport
// types matching incorrectly
// double
if (temp >= 16) { // this condition is not required
temp -= 16;
if (no_double) {
copy[i] -= 16;// but maybe used later, so will leave it
// now
}
}
// UG
if (temp >= 8) {
temp -= 8;
if (no_ug) {
copy[i] -= 8;
}
}
// water
if (temp >= 4) {
temp -= 4;
if (no_water) {
copy[i] -= 4;
}
}
// taxi
if (temp >= 2) {
temp -= 2;
if (no_taxi) {
copy[i] -= 2;
}
}
// bus
if (temp >= 1) {
temp -= 1;
if (no_bus) {
copy[i] -= 1;
}
}
}
}
if (player.isMrx() && !no_double) {
for (int i = 0; i < result.length; i++) {
if (result[i] != 0) // meaning that there is a connection
{
int[] resultTwo = map.getPossibleMoves(i);
for (int j = 0; j < resultTwo.length; j++) {
if (resultTwo[j] != 0 && map.positions[j] == null) {
if (copy[j] < 16) {
copy[j] += 16;
}
}
}
}
}
}
return copy;
}
/**
* gets the map being used for this game
* @return map
*/
@Override
public IBMap getMap() {
return map;
}
/**
* gets the path to the map being used for this game
* @return String
*/
public String getMapPath() {
return mapPath;
}
/**
* gets the player object representing mrx for this game
* @return IPlayer
*/
@Override
public IPlayer getMrX() {
for (final IPlayer p : players) {
if (p.isMrx()) {
return p;
}
}
return null;
}
/**
* get the player with the provided hash
* @return IPlayer the requested player
*/
@Override
public IPlayer getPlayer(final String hash) {
for (IPlayer player : players) {
if (player.getHash().equals(hash)) {
return player;
}
}
return null;
}
/**
* get the player index for the player with this email
* @return int the players index
*/
@Override
public int getPlayerIndex(String email) {
for (int i = 0; i < players.size(); i++) {
if (email.equals(this.players.get(i).getEmail())) {
return i;
}
}
return -1;
}
/**
* get a list of all players in this game
* @return List<IPlayer> all players
*/
@Override
public List<IPlayer> getPlayers() {
return this.players;
}
/**
* get the round number
* @return int
*/
@Override
public int getRound() {
return round;
}
/**
* get the game status
* @return char
*/
@Override
public char getStatus() {
return status;
}
/**
* get the email of the player who is currently having his turn
* @return String
*/
@Override
public String getWhosTurn() {
return this.players.get(turn).getEmail();
}
/**
* tells if this method has the provided user or not
* @return boolean true if player is in game false other wise
*/
@Override
public boolean hasPlayer(final IUser user) {
for (IPlayer p : this.players) {
if (p.getHash().equals(user.getHash())) {
return true;
}
}
return false;
}
/**
* initialise tickets for game use
* sets tickets with the game rule
* @param p
*/
private void initializeTickets(final IPlayer p) {
final boolean mrx = p.isMrx();
p.setTickets(IBMap.TAXI, mrx ? MRX_TAXI_TICKETS
: DETECTIVE_TAXI_TICKETS);
p.setTickets(IBMap.BUS, mrx ? MRX_BUS_TICKETS : DETECTIVE_BUS_TICKETS);
p.setTickets(IBMap.UG, mrx ? MRX_UG_TICKETS : DETECTIVE_UG_TICKETS);
p.setTickets(IBMap.WATER, mrx ? MRX_WATER_TICKETS
: DETECTIVE_WATER_TICKETS);
p.setTickets(IBMap.DOUBLE, mrx ? MRX_DOUBLE_TICKETS : 0);
}
/**
* rounds 3,8,13,18 are those where mrx will expose himself to others
* @param round : the round number
* @return true if the round shall expose mrx
*/
@Override
public boolean isRoundExposingMrX(int round) {
return round == 3 || round == 8 || round == 13 || round == 18
|| this.getStatus() == FINISHED;
}
/**
* move player from the current position to another with specify transport type
* @param email - player identifier
* @param newPosition - position node that this move will take the player to
* @param transport - transport type the player use for this move
* @throws Exception
*/
@Override
public void movePlayer(String email, int newPosition, int transport)
throws Exception {
final String curTurn = getWhosTurn();
if (!curTurn.equals(email)) {
throw new Exception("It is " + curTurn + " turn now");
}
final int p = getPlayerIndex(email);
final IPlayer player = this.players.get(p);
final int current = player.getPosition();
if (!this.getMrX().getEmail().equals(email) && this.getMrX().getEmail().equals(this.map.positions[newPosition])) {
this.setWhoWon(player.getName() + " Has Caught Mrx ");
this.status = FINISHED;
}else{
if (this.map.positions[newPosition] != null) {
throw new Exception("This position is already occupied by ["
+ map.positions[newPosition] + "]");
}
if (this.getLegalMoves(email)[newPosition] == 0) {
throw new Exception("node " + current + " and " + newPosition
+ " are not connected");
}
if (player.getTickets(transport) < 1) {
throw new Exception("not enough tickets to move from " + current
+ " to " + newPosition);
}
}
// update player last activity
player.updateLastActivity();
// decrement player's tickets
player.consumeTicket(transport);
// make player place empty
map.positions[player.getPosition()] = null;
// move to new position
player.setPosition(newPosition);
// make player new place on the map
map.positions[player.getPosition()] = player.getEmail();
// if the player is not mrx then increment mrx's tokens
if (!player.isMrx()) {
this.getMrX().setTickets(transport,
(this.getMrX().getTickets(transport) + 1));
}
// increment the round when MrX plays
if (turn == 0) {
round++;
this.mrxMadeAMove();
}
// increment turn
turn = (turn + 1) % this.players.size();
if(round > 22)
{
this.setWhoWon("Mr X has won the Game");
this.status = FINISHED;
}
}
/**
* this method should be declared as abstract, bit we don't want the class
* to be declared as abstract because of one method. this method is
* triggered when Mr X makes a move
*/
@Override
public void mrxMadeAMove() {
}
/**
* removes player who leaves the game while the game is running
* @param email
*/
@Override
public void removePlayer(String email) {
final int p = getPlayerIndex(email);
if (p > -1) {
this.players.remove(p);
}
}
/**
* sets the game identifier
* @param id - this will be the name that the user use for the game when creating the game
*/
@Override
public void setId(String id) {
this.id = id;
}
/**
* sets the map that user selected when creating the game
* @param map - "palmerston north" or "auckland"
*/
@Override
public void setMap(final IBMap map) {
this.map = (BMap) map;
}
/**
* sets map path with xml document
* @param mapPath
*/
@Override
public void setMapPath(String mapPath) {
this.mapPath = mapPath;
}
/**
* start the game, change game status to running, initialise players and tokens
* @param player
* @throws Exception
*/
@Override
public void start(IPlayer player) throws Exception {
if (!this.players.contains(player)) {
throw new Exception(
"You can not start this game unless you join it");
}
if (this.players.size() < 2 || this.getMrX() == null) {
throw new Exception(
"This game can be started given that it has at least two players, and one of them must be MrX");
}
if (this.status != NEW) {
throw new Exception("This game has already been started");
}
this.turn = 0;
player.updateLastActivity();
for (IPlayer p : this.players) {
initializeTickets(p);
p.setPosition(map.getEmptyPosition());
map.positions[p.getPosition()] = p.getEmail();
}
this.status = STARTED;
}
}