package net.sf.colossus.game;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import net.sf.colossus.util.CollectionHelper;
import net.sf.colossus.util.Predicate;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.MasterBoardTerrain;
import net.sf.colossus.variant.MasterHex;
import net.sf.colossus.variant.Variant;
/**
* An ongoing game in Colossus.
*
* As opposed to {@link Variant} this class holds information about an ongoing game
* and its status.
*/
public class Game
{
private static final Logger LOGGER = Logger
.getLogger(Game.class.getName());
/**
* The variant played in this game.
*/
private final Variant variant;
/**
* The state of the different players in the game.
*/
protected final List<Player> players = new ArrayList<Player>();
/**
* The caretaker takes care of managing the available and dead creatures.
*/
private final Caretaker caretaker;
/**
* The current turn number. Advance when every player has done his move
*/
protected int turnNumber = -1;
/**
* The current game phase (Split, Move, Fight, Muster)
*/
protected Phase phase;
/**
* Last movement roll for any player.
*/
private int movementRoll = -1;
/**
* Status for Game is over and message for it
* On client side this also implies:
* If the game is over, then quitting does not require confirmation.
*/
private boolean gameOver = false;
private String gameOverMessage = null;
private boolean suspended;
private Engagement engagement;
protected Battle battle = null;
private final BattleStrike battleStrike;
/**
* Create a Game object.
*
* @param variant The variant object, not null
* @param playerNames Names of the players, not used yet
*/
public Game(Variant variant, String[] playerNames)
{
assert variant != null : "Can't create game with null variant!";
this.variant = variant;
this.caretaker = new Caretaker(this);
this.phase = Phase.INIT;
this.battleStrike = new BattleStrike(this);
}
public Variant getVariant()
{
return variant;
}
public void addPlayer(Player p)
{
players.add(p);
}
public Collection<Player> getPlayers()
{
assert players.size() != 0 : "getPlayers called before player info set (size==0)!";
return Collections.unmodifiableCollection(players);
}
/**
* Get a list of preliminary player names, during game startup / clients
* connecting. Preliminary, because some of them might change their name
* later (e.g. the "byColor" ones).
* @return List of player names
*/
public Collection<String> getPreliminaryPlayerNames()
{
Collection<String> prePlayerNames = new ArrayList<String>();
assert players.size() != 0 : "getPreliminaryPlayerNames called before player info set (size==0)!";
for (Player p : Collections.unmodifiableCollection(players))
{
prePlayerNames.add(p.getName());
}
return prePlayerNames;
}
public int getNumPlayers()
{
assert players.size() != 0 : "getNumPlayers called before player info set (size==0)!";
return players.size();
}
public int getNumLivingPlayers()
{
int alive = 0;
for (Player info : players)
{
if (!info.isDead() && !info.getDeadBeforeSave())
{
alive++;
}
}
return alive;
}
/**
*
* @return Returns true if all still alive players are AIs
*/
public boolean onlyAIsRemain()
{
for (Player p : players)
{
if (!p.isAI() && !p.isDead())
{
return false;
}
}
return true;
}
/**
* Returns the number of real players (Human or Network)
* which are still alive.
*
* TODO partly same idea as "onlyAIsRemain()"
*/
protected int getNumHumansRemaining()
{
int remaining = 0;
for (Player player : getPlayers())
{
if (player.isHuman() && !player.isDead())
{
remaining++;
}
}
return remaining;
}
// Server uses this to decide whether it needs to start a file server
public int getNumRemoteRemaining()
{
int remaining = 0;
for (Player player : getPlayers())
{
if (player.isNetwork() && !player.isDead())
{
remaining++;
}
}
return remaining;
}
public Caretaker getCaretaker()
{
return caretaker;
}
public int getMovementRoll()
{
return movementRoll;
}
public void setMovementRoll(int roll)
{
movementRoll = roll;
}
public boolean isGameOver()
{
return this.gameOver;
}
public void setSuspended(boolean value)
{
this.suspended = value;
}
public boolean isSuspended()
{
return this.suspended;
}
public String getGameOverMessage()
{
return this.gameOverMessage;
}
public void setGameOver(boolean gameOver, String message)
{
this.gameOver = gameOver;
this.gameOverMessage = message;
}
public void createEngagement(MasterHex hex, Legion attacker,
Legion defender)
{
this.engagement = new Engagement(hex, attacker, defender);
}
public void clearEngagementData()
{
this.engagement = null;
}
public boolean isEngagementInProgress()
{
return this.engagement != null;
}
public Engagement getEngagement()
{
return this.engagement;
}
public Battle getBattle()
{
return this.battle;
}
public Legion getBattleActiveLegion()
{
return battle.getBattleActiveLegion();
}
public MasterHex getBattleSite()
{
return engagement == null ? null : engagement.getLocation();
}
public Legion getDefender()
{
if (engagement != null)
{
return engagement.getDefendingLegion();
}
else
{
LOGGER.warning("asking for defender but engagement is null?");
return null;
}
}
public Legion getAttacker()
{
if (engagement != null)
{
return engagement.getAttackingLegion();
}
else
{
LOGGER.warning("asking for attacker but engagement is null?");
return null;
}
}
/**
* Return a list of angel types that can be acquired based
* on the hex in which legion is, when reaching given score threshold,
* and if they are still available from caretaker
* @param terrain The terrain in which this legion wants to acquire
* @param score A acquring threshold, e.g. in Default 100, ..., 400, 500
* @return list of acquirables
*/
List<CreatureType> findAvailableEligibleAngels(MasterBoardTerrain terrain,
int score)
{
List<CreatureType> recruits = new ArrayList<CreatureType>();
List<String> allRecruits = getVariant().getRecruitableAcquirableList(
terrain, score);
Iterator<String> it = allRecruits.iterator();
while (it.hasNext())
{
String name = it.next();
CreatureType creature = getVariant().getCreatureByName(name);
if (getCaretaker().getAvailableCount(creature) >= 1
&& !recruits.contains(creature))
{
recruits.add(creature);
}
}
return recruits;
}
/** Return a list of all legions of all players. */
public List<Legion> getAllLegions()
{
List<Legion> list = new ArrayList<Legion>();
for (Player player : players)
{
List<? extends Legion> legions = player.getLegions();
list.addAll(legions);
}
return list;
}
public int getNumLivingCreatures(CreatureType type)
{
int livingCount = 0;
for (Player player : players)
{
List<? extends Legion> legions = player.getLegions();
for (Legion legion : legions)
{
livingCount += legion.numCreature(type);
}
}
return livingCount;
}
public List<Legion> getLegionsByHex(MasterHex masterHex)
{
assert masterHex != null : "No hex given to find legions on.";
List<Legion> result = new ArrayList<Legion>();
for (Legion legion : getAllLegions())
{
if (masterHex.equals(legion.getCurrentHex()))
{
result.add(legion);
}
}
return result;
}
public int getNumEnemyLegions(MasterHex masterHex, Player player)
{
int count = 0;
for (Legion legion : getEnemyLegions(player))
{
if (masterHex.equals(legion.getCurrentHex()))
{
count++;
}
}
return count;
}
public int getNumLegions(MasterHex masterHex)
{
int count = 0;
for (Legion legion : getAllLegions())
{
if (masterHex.equals(legion.getCurrentHex()))
{
count++;
}
}
return count;
}
public List<Legion> getFriendlyLegions(final MasterHex hex,
final Player player)
{
return CollectionHelper.selectAsList(player.getLegions(),
new Predicate<Legion>()
{
public boolean matches(Legion legion)
{
return legion.getCurrentHex().equals(hex);
}
});
}
/** Return a list of all legions not belonging to player. */
public List<Legion> getEnemyLegions(final Player player)
{
List<Legion> result = new ArrayList<Legion>();
for (Player otherPlayer : players)
{
if (!otherPlayer.equals(player))
{
result.addAll(otherPlayer.getLegions());
}
}
return result;
}
public List<Legion> getEnemyLegions(final MasterHex hex,
final Player player)
{
List<Legion> result = new ArrayList<Legion>();
for (Player otherPlayer : players)
{
if (!otherPlayer.equals(player))
{
for (Legion legion : otherPlayer.getLegions())
{
if (legion.getCurrentHex().equals(hex))
{
result.add(legion);
}
}
}
}
return result;
}
// TODO decide which one of getFirstFriendlyLegion() to use;
// the one is from server side, the other from client side.
// Is the CollectionHelper style more efficient?
// The simple loop is easier to understand IMHO...
/*
public Legion getFirstFriendlyLegion(final MasterHex hex, Player player)
{
return CollectionHelper.selectFirst(player.getLegions(),
new Predicate<Legion>()
{
public boolean matches(Legion legion)
{
return legion.getCurrentHex().equals(hex);
}
});
}
*/
public Legion getFirstFriendlyLegion(MasterHex masterHex, Player player)
{
for (Legion legion : player.getLegions())
{
if (masterHex.equals(legion.getCurrentHex()))
{
return legion;
}
}
// only info. I *think* in recombining illegal split case
// it might not find anything and that would be totally OK ...
LOGGER.info("Could not find any friendly legion for player "
+ player.getName() + " in hex " + masterHex);
return null;
}
public boolean isOccupied(MasterHex masterHex)
{
for (Legion legion : getAllLegions())
{
if (masterHex.equals(legion.getCurrentHex()))
{
return true;
}
}
return false;
}
public Legion getFirstLegion(MasterHex masterHex)
{
for (Legion legion : getAllLegions())
{
if (masterHex.equals(legion.getCurrentHex()))
{
return legion;
}
}
return null;
}
public int getNumFriendlyLegions(MasterHex masterHex, Player player)
{
int count = 0;
List<? extends Legion> legions = player.getLegions();
for (Legion legion : legions)
{
if (masterHex.equals(legion.getCurrentHex()))
{
count++;
}
}
return count;
}
/**
* Finds the first legion in a hex not belonging to a certain player.
*
* Note that there is no assumption that the player has a legion in that
* location itself. This method is e.g. used to evaluate moves in the AI.
*
* @param masterHex the hex where to look for enemy regions. Not null.
* @param player the player whose enemies we are looking for. Not null.
*
* @return the first legion that is in the specified hex and does not
* belong to the given player, null if no such legion exists
*/
public Legion getFirstEnemyLegion(MasterHex masterHex, Player player)
{
assert masterHex != null : "Hex needs to be specified";
assert player != null : "Player needs to be specified";
for (Legion legion : getEnemyLegions(player))
{
if (masterHex.equals(legion.getCurrentHex()))
{
return legion;
}
}
return null;
}
/**
* Return a set of all hexes with engagements.
*
* TODO if we can be sure that the activePlayer is set properly, we could
* just create a set of all hexes he is on and then check if someone
* else occupies any of the same
*/
// TODO This is the client side version
public Set<MasterHex> findEngagements()
{
Set<MasterHex> result = new HashSet<MasterHex>();
Map<MasterHex, Player> playersOnHex = new HashMap<MasterHex, Player>();
for (Player player : players)
{
for (Legion legion : player.getLegions())
{
MasterHex hex = legion.getCurrentHex();
if (playersOnHex.get(hex) == null)
{
// no player on that hex found yet, set this one
playersOnHex.put(hex, player);
}
else
{
if (!playersOnHex.get(hex).equals(player))
{
// someone else already on the hex -> engagement
result.add(hex);
}
}
}
}
return result;
}
/** Return set of hexLabels for engagements found. */
// The GameServerSide version works based on the activePlayer idea...
// TODO which one to keep?
/*
public Set<MasterHex> findEngagements()
{
Set<MasterHex> result = new HashSet<MasterHex>();
Player player = getActivePlayer();
for (Legion legion : player.getLegions())
{
MasterHex hex = legion.getCurrentHex();
if (getNumEnemyLegions(hex, player) > 0)
{
result.add(hex);
}
}
return result;
}
*/
public boolean containsOpposingLegions(MasterHex hex)
{
Player player = null;
for (Legion legion : getLegionsByHex(hex))
{
if (player == null)
{
player = legion.getPlayer();
}
else if (legion.getPlayer() != player)
{
return true;
}
}
return false;
}
/**
* Return a set of all other unengaged legions of the legion's player
* that have summonables (not sorted in any particular order).
*/
public List<Legion> findLegionsWithSummonables(Legion summoner)
{
List<Legion> result = new ArrayList<Legion>();
Player player = summoner.getPlayer();
for (Legion legion : player.getLegions())
{
if (!legion.equals(summoner) && legion.hasSummonable()
&& !containsOpposingLegions(legion.getCurrentHex()))
{
result.add(legion);
}
}
return result;
}
// For making Proposals needed both client and server side
public Legion getLegionByMarkerId(String markerId)
{
LOGGER.severe("getLegionByMarkerId called for markerId " + markerId
+ "in the non-overriden method of game.Game class!!");
Thread.dumpStack();
return null;
}
/**
* Set the current turn number. Used only on client side;
* server side increments directly.
*
* @param turn Set this number as current turn number
*/
public void setTurnNumber(int turn)
{
this.turnNumber = turn;
}
/**
* Returns the current turn in the game
* @return returns the current turn number
*/
public int getTurnNumber()
{
return turnNumber;
}
public boolean isPhase(Phase phase)
{
return this.phase == phase;
}
public void setPhase(Phase phase)
{
this.phase = phase;
}
public Phase getPhase()
{
return phase;
}
public boolean isEngagementOngoing()
{
if (isPhase(Phase.FIGHT) && engagement != null)
{
return true;
}
else
{
return false;
}
}
public int getBattleTurnNumber()
{
return battle.getBattleTurnNumber();
}
public BattleStrike getBattleStrike()
{
return battleStrike;
}
}