package org.scotlandyard.impl.engine;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.scotlandyard.engine.Game;
import org.scotlandyard.engine.GameException;
import org.scotlandyard.engine.Token;
import org.scotlandyard.engine.User;
import org.scotlandyard.engine.boardmap.BoardMap;
import org.scotlandyard.engine.boardmap.Coordinate;
import org.scotlandyard.engine.boardmap.Link;
import org.scotlandyard.engine.constants.GameStatus;
import org.scotlandyard.engine.constants.TransportationMethod;
import org.scotlandyard.engine.player.Player;
import org.scotlandyard.engine.player.PlayerPosition;
import org.scotlandyard.impl.engine.player.Detective;
import org.scotlandyard.impl.engine.player.MrX;
import org.scotlandyard.utils.SetOperations;
/**
* implementation of the game object
*
* @author Hussain Al-Mutawa
* @version 1.0
* @since Sun Sep 18, 2011
*/
public final class GameImpl extends SetOperations implements Game {
public static Game getNewInstance(final String identifier,
final User creator, final BoardMap boardMap) throws GameException {
final Game game = new GameImpl(identifier, creator, boardMap);
if (creator!=null && GameEngine.instance().getUser(creator.getEmail()) == null) {
throw new GameException(
"the user can not create a game before login is made");
}
// add the game to the list of available games
GameEngine.instance().getLobby().addGame(game);
return game;
}
private BoardMap boardMap;
private transient final User creator;
private transient final Map<String, Detective> detectives;
private GameStatus gameStatus;
private transient final String identifier;
private MrX mrX;
private transient final Map<Player, PlayerPosition> playerPositions;
private transient final Map<Player, EnumMap<TransportationMethod, Token>> playerTokens;
private Player turn;
/**
* Construct a new Game object
*
* @param identifier
* @param creator
* @param boardMap
*/
private GameImpl(final String identifier, final User creator,
final BoardMap boardMap) {
super();
this.identifier = identifier;
this.creator = creator;
detectives = new HashMap<String, Detective>();
playerPositions = new HashMap<Player, PlayerPosition>();
this.boardMap = boardMap;
gameStatus = GameStatus.JUST_CREATED;
playerTokens = new HashMap<Player, EnumMap<TransportationMethod, Token>>();
}
@Override
public void addDetective(final Detective detective) throws GameException {
if (detective == null) {
throw new GameException("Can not add a null detective to the list");
}
if (this.getPlayer(detective.getEmail()) != null) {
throw new GameException("Can not join a game twice");
}
detectives.put(detective.getEmail(), detective);
playerTokens.put(detective, new EnumMap<TransportationMethod, Token>(
TransportationMethod.class));
}
@Override
public int compareTo(final Game game) {
return getIdentifier().compareTo(game.getIdentifier());
}
@Override
public boolean equals(final Object other) {
return toString().equals(other.toString());
}
@Override
public Set<Coordinate> getAvailablePositions() {
return SetOperations.difference(boardMap.getCoordinates(),
getOccupiedPositions());
}
@Override
public BoardMap getBoardMap() {
return boardMap;
}
@Override
public User getCreator() {
return creator;
}
@Override
public Set<Detective> getDetectives() {
return new HashSet<Detective>(detectives.values());
}
@Override
public GameStatus getGameStatus() {
return gameStatus;
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public MrX getMrX() {
return mrX;
}
@Override
public Set<Coordinate> getOccupiedPositions() {
final Set<Coordinate> positions = new HashSet<Coordinate>();
for (final PlayerPosition playerPosition : playerPositions.values()) {
positions.add(playerPosition.getPosition());
}
return positions;
}
@Override
public Player getPlayer(final String playerEmail) {
Player player = null;
if (this.getMrX() != null
&& this.getMrX().getEmail().equals(playerEmail)) {
player = this.getMrX();
} else {
for (Detective d : this.getDetectives()) {
if (d.getEmail().equals(playerEmail)) {
player = d;
break;
}
}
}
return player;
}
@Override
public Coordinate getPlayerPosition(final Player player) {
return playerPositions.get(player).getPosition();
}
@Override
public Set<Player> getPlayers() {
final Set<Player> players = new HashSet<Player>();
players.addAll(detectives.values());
if (mrX != null) {
players.add(mrX);
}
return players;
}
@Override
public Set<Coordinate> getPosssibleMoves(final Player player) {
final Coordinate coordinate = getPlayerPosition(player);
final Set<Link> links = boardMap.getLinks(coordinate);
final Set<Coordinate> positions = new HashSet<Coordinate>();
for (final Link link : links) {
positions.add(link.getOtherEnd(coordinate));
}
return null; // TODO add getPossibleMoves
}
@Override
public EnumMap<TransportationMethod, Token> getTokens(final Player player) {
return playerTokens.get(player);
}
@Override
public Player getTurn() {
return turn;
}
public Player getWhoOccupiesPosition(final Coordinate coordinate) throws GameException {
if(coordinate==null){
throw new GameException("coordinate has not been initialized");
}
if(this.getBoardMap()==null){
throw new GameException("the current board map for the game has not been initialized");
}
if(coordinate.getBoardMap()==null){
throw new GameException("the board map of the coordinate has not been initialized yet");
}
if(!coordinate.getBoardMap().equals(this.getBoardMap())){
throw new GameException("the board map of the coordinate ["+coordinate.getBoardMap().toString()+"] is"+
"the same as the board map of the game ["+this.getBoardMap().toString()+"]");
}
Player temp = null;
for(Player player:this.getPlayers()){
if(player.getPosition(this)!=null && player.getPosition(this).equals(coordinate)){
temp=player;
}
}
return temp;
}
@Override
public Player getWhoOccupiesPosition(final String coordinateLabel) throws GameException {
return getWhoOccupiesPosition(this.boardMap.getCoordinate(coordinateLabel));
}
public boolean hasPlayer(final String playerEmail) {
boolean found = false;
for (Player player : getPlayers()) {
if (player.getEmail().equals(playerEmail)) {
found = true;
break;
}
}
return found;
}
@Override
public boolean removePlayer(final Player player) throws GameException{
final MrX temp =null;
if(player==null){
throw new GameException("can not remove null player");
}
if (mrX!=null && mrX.getEmail().equals(player.getEmail())) {
mrX=temp;
} else {
if(detectives.remove(player.getEmail())==null){
throw new GameException("this player does not exist to be removed");
}
}
playerPositions.remove(player);
playerTokens.remove(player);
return playerPositions.get(player)==null
&& playerTokens.get(player)==null
&& !hasPlayer(player.getEmail());
}
@Override
public boolean removePlayer(final String playerEmail) throws GameException{
final Player player = this.getPlayer(playerEmail);
return removePlayer(player);
}
@Override
public void setBoardMap(final BoardMap boardMap) {
this.boardMap=boardMap;
}
@Override
public void setGameStatus(final GameStatus gameStatus) {
this.gameStatus = gameStatus;
}
@Override
public void setMrX(final Player player) throws GameException {
if (player != null && this.getPlayer(player.getEmail()) != null) {
throw new GameException("Can not join a game twice");
}
if (mrX != null) {
throw new GameException(
"Mr X is already set, and shall not be changed");
}
mrX = (MrX) player;
}
@Override
public void setPlayerPosition(final PlayerPosition playerPosition)
throws GameException {
if(playerPosition.getPlayer()==null){
throw new GameException("player must be set");
}
if(playerPosition.getPosition()==null){
throw new GameException("position can not be null");
}
if(playerPosition.getPosition().getBoardMap()==null){
throw new GameException("position map must be set first");
}
if(!playerPosition.getPosition().getBoardMap().equals(this.getBoardMap())){
throw new GameException("the map of the position ["+playerPosition.getPosition().getBoardMap().getName()+"] is different from the map of the game ["+this.getBoardMap().getName()+"]");
}
final Coordinate coordinate = playerPosition.getPosition();
final Player player = this.getWhoOccupiesPosition(coordinate);
if(player!=null){
if(!getMrX().equals(player)){
throw new GameException("this position has been already occupied by other detective");
}else if(getMrX().equals(player) && !getMrX().equals(playerPosition.getPlayer())){
//has Mr X been caught?
this.playerPositions.put(playerPosition.getPlayer(), playerPosition);
this.setGameStatus(GameStatus.FINISHED);
throw new GameException("Game is finished, detective ["+playerPosition.getPlayer().getName()+"] has captured Mr X");
}
}
playerPosition.getPlayer().setPosition(this, playerPosition.getPosition());
this.playerPositions.put(playerPosition.getPlayer(), playerPosition);
}
@Override
public void setPlayerPosition(final String playerEmail, final String coordinateLabel) throws GameException{
if(gameStatus!=GameStatus.JUST_CREATED){
throw new GameException("this method can be used to set players positions before the game has started only");
}
if(getPlayer(playerEmail)==null){
throw new GameException("can not find player ["+playerEmail+"]");
}
if(this.getBoardMap()==null){
throw new GameException("the map has not been set yet");
}
final Coordinate coordinate = this.getBoardMap().getCoordinate(coordinateLabel);
if(coordinate==null){
throw new GameException("can not find coordinate ["+coordinateLabel+"]");
}
final PlayerPosition playerPosition=new PlayerPositionImpl(this, this.getPlayer(playerEmail), this.boardMap.getCoordinate(coordinateLabel));
this.setPlayerPosition(playerPosition);
}
@Override
public void setTurn(final Player player) {
turn = player;
}
@Override
public String toString() {
return "Game : " + getIdentifier();
}
@Override
public boolean isPlayable() {
boolean result=false;
if(this.getGameStatus()==GameStatus.STARTED){
result=true;
}
return result;
}
@Override
public boolean isPositionOccupied(Coordinate coordinate) {
for(PlayerPosition pp:this.playerPositions.values()){
if(coordinate.equals(pp.getPosition())){
return true;
}
}
return false;
}
@Override
public Coordinate getEmptyPosition() {
Coordinate result=null;
if(this.playerPositions.size()==this.getBoardMap().getCoordinates().size()){
return result;
}
Iterator<Coordinate>it = this.getBoardMap().getCoordinates().iterator();
while(result==null){
result=it.next();
if(this.isPositionOccupied(result)){
result=null;
}
}
return result;
}
}