package net.sf.colossus.game; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import net.sf.colossus.common.Constants; import net.sf.colossus.server.PlayerServerSide; import net.sf.colossus.server.VariantSupport; import net.sf.colossus.variant.MasterHex; /** * A player in a game. * * This class holds all information describing a player in a game, such * as the current legions and the score. Instances of this class are always bound to * an instance of {@link Game}. * * TODO there is an excessive amount of methods around the markersAvailable list. */ public class Player { /** * The game this information belongs to. */ private final Game game; /** * A name for this player for UI purposes and as identifier. */ private String name; /** * The current legions owned by this player. */ private final List<Legion> legions = new ArrayList<Legion>(); /** * The number of the player in the game. * * TODO clarify if this is just an arbitrary number (in which case we might want * to get rid of it) or the actual turn sequence */ private final int number; /** * Set to true if the player is dead. * * TODO check if that isn't equivalent to not having legions anymore */ private boolean dead; /** * Only needed during loading of a game. Pulled up to game anyway, * getNumLivingLegions needs it during loading. */ private boolean deadBeforeSave = false; /** * The starting tower of the player. * * TODO this should be kind-of final: once a tower has been assigned, it shouldn't * change anymore -- but assigning the towers has probably to happen a while * after all players are created. We could at least at an assertion into the * setter that it is allowed to change the value only if it was not set before. */ private MasterHex startingTower; /** * The label of the color we use. * * TODO this should really be an object representing a markerset * TODO similar to {@link #startingTower} this should be set only once but probably * can't be set in the constructor. */ private PlayerColor color; /** * The type of player: local human, AI or network. * * TODO make typesafe version * TODO shouldn't this be final? It should be possible to set that in the constructor. * Unless we have to allow changes e.g. for humans dropping out of the game (in * which case the todo should be read as "add some documentation regarding that ;-) ). */ private String type; /** * A string representing all players eliminated by this player. * * The format is just a sequence of the short, two-character versions * of the colors, e.g. "BkRd". * * TODO this should really be a List<Player> */ private String playersEliminated = ""; private int mulligansLeft; private int score; /** * Sorted set of available legion markers for this player. */ private final SortedSet<String> markersAvailable = new TreeSet<String>( new MarkerComparator(getShortColor())); public Player(Game game, String playerName, int number) { assert game != null : "No game without Game"; assert playerName != null : "Player needs a name"; assert number >= 0 : "Player number must not be negative"; // TODO check for max on number once game has the players stored in it this.game = game; this.name = playerName; this.number = number; this.dead = false; this.mulligansLeft = 1; } public Game getGame() { return game; } /** * TODO should be List<Legion>, but currently subclasses still use more specific types * TODO should be unmodifiable, but at least {@link PlayerServerSide#die(Player)} still * removes items */ public List<? extends Legion> getLegions() { return this.legions; } public int getNumber() { return number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isDead() { return dead; } // TODO it would probably be safer not to have this method and instead set the // state only in the constructor or during die() public void setDead(boolean dead) { this.dead = dead; } /** During loading of a game, this player was already dead in the game * before saving. No client needs to be created for this player, and * all legion activities/reveals are skipped. * @return True if player was dead */ public boolean getDeadBeforeSave() { return this.deadBeforeSave; } public void setDeadBeforeSave(boolean val) { this.deadBeforeSave = val; } public void setType(String type) { this.type = type; } public String getType() { return type; } public boolean isHuman() { return isLocalHuman() || isNetwork(); } public boolean isLocalHuman() { return getType().endsWith(Constants.human); } public boolean isNetwork() { return getType().endsWith(Constants.network); } public boolean isNone() { return getType().endsWith(Constants.none); } public boolean isAI() { return type.endsWith(Constants.ai); } public void setStartingTower(MasterHex startingTower) { this.startingTower = startingTower; } public MasterHex getStartingTower() { return startingTower; } public void setColor(PlayerColor color) { this.color = color; } public PlayerColor getColor() { return color; } public String getShortColor() { if (color == null) { return null; } else { return color.getShortName(); } } public String getPlayersElim() { return playersEliminated; } public void setPlayersElim(String playersEliminated) { this.playersEliminated = playersEliminated; } public void addPlayerElim(Player player) { playersEliminated = playersEliminated + player.getShortColor(); } public Legion getLegionByMarkerId(String markerId) { for (Legion legion : getLegions()) { if (legion.getMarkerId().equals(markerId)) { return legion; } } return null; } public boolean hasLegion(String markerId) { for (Legion legion : getLegions()) { if (legion.getMarkerId().equals(markerId)) { return true; } } return false; } public Legion getTitanLegion() { for (Legion legion : getLegions()) { if (legion.hasTitan()) { return legion; } } return null; } public void addLegion(Legion legion) { legions.add(legion); } public void removeLegion(Legion legion) { legions.remove(legion); } public void removeAllLegions() { legions.clear(); } public void addMarkerAvailable(String markerId) { markersAvailable.add(markerId); } public void removeMarkerAvailable(String markerId) { markersAvailable.remove(markerId); } public void clearMarkersAvailable() { markersAvailable.clear(); } public Set<String> getMarkersUsed() { SortedSet<String> used = new TreeSet<String>(); for (Legion l : legions) { used.add(l.getMarkerId()); } return Collections.unmodifiableSortedSet(used); } public Set<String> getMarkersAvailable() { return Collections.unmodifiableSortedSet(markersAvailable); } public int getNumMarkersAvailable() { return markersAvailable.size(); } public String getFirstAvailableMarker() { synchronized (markersAvailable) { if (markersAvailable.isEmpty()) { return null; } return markersAvailable.first(); } } public boolean isMarkerAvailable(String markerId) { return markersAvailable.contains(markerId); } /** Removes the selected marker from the list of those available. * Returns the markerId if it was present, or null if it was not. */ public String selectMarkerId(String markerId) { if (markersAvailable.remove(markerId)) { return markerId; } else { return null; } } public int getNumCreatures() { int count = 0; for (Legion legion : getLegions()) { count += legion.getHeight(); } return count; } public int getCreaturePoints() { int count = 0; for (Legion legion : getLegions()) { count += legion.getHeight(); } return count; } /** * Overridden for debug/logging purposes. */ @Override public String toString() { return getName(); } public void setMulligansLeft(int mulligansLeft) { this.mulligansLeft = mulligansLeft; } public int getMulligansLeft() { return mulligansLeft; } public void setScore(int score) { this.score = score; } public int getScore() { return score; } public int getTitanPower() { return 6 + getScore() / getGame().getVariant().getTitanImprovementValue(); } public boolean canTitanTeleport() { return getScore() >= getGame().getVariant().getTitanTeleportValue(); } /** * Return the total value of all of this player's creatures. */ public int getTotalPointValue() { int total = 0; for (Legion legion : getLegions()) { total += legion.getPointValue(); } return total; } public boolean hasTeleported() { for (Legion info : getLegions()) { if (info.hasTeleported()) { return true; } } return false; } /** * Check if the player has already moved. * * @return true iff at least one legion of the player has been moved */ public boolean hasMoved() { for (Legion legion : getLegions()) { if (legion.hasMoved()) { return true; } } return false; } public int getNumLegions() { return getLegions().size(); } /** * Return the full basename for the titan of this player. */ public String getTitanBasename() { try { return "Titan-" + getTitanPower() + "-" + getColor().getName(); } catch (Exception ex) { return Constants.titan; } } /** * Return the full basename for an angel of this player. */ public String getAngelBasename() { try { int power = VariantSupport.getCurrentVariant() .getCreatureByName("Angel").getPower(); return "Angel-" + power + "-" + getColor().getName(); } catch (Exception ex) { return Constants.angel; } } /** * wasted luck per strike Number (sn), for probability based Battle Rolls. * Like, Centaur for sn 4 should make 1.5 hits, first time makes 1 hit, * saves 0.5 to next time. * Stored per player per strike-number base. */ private final double[] accumulatedWastedLuck = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; public boolean applyAccumulatedWastedLuck(int sn, double wastedLuck, StringBuffer eawlString) { accumulatedWastedLuck[sn] += wastedLuck; eawlString.append(String.format("%5.2f", Double.valueOf(accumulatedWastedLuck[sn]))); if (accumulatedWastedLuck[sn] >= (1 - 0.0000000000000001)) { accumulatedWastedLuck[sn] -= 1; return true; } return false; } // PlayerServerSide overrides this to use a better random source public int makeBattleRoll() { return Dice.rollDie(); } // PlayerServerSide overrides this to use a better random source public int makeMovementRoll() { return Dice.rollDie(); } public HashSet<Legion> getPendingSplitLegions() { HashSet<Legion> legions = new HashSet<Legion>(); for (Legion l : getLegions()) { if (l.getSplitRequestSent()) { legions.add(l); } } return legions; } public int countPendingSplits() { return getPendingSplitLegions().size(); } public HashSet<Legion> getPendingUndoSplitLegions() { HashSet<Legion> legions = new HashSet<Legion>(); for (Legion l : getLegions()) { if (l.getUndoSplitRequestSent()) { legions.add(l); } } return legions; } public int countPendingUndoSplits() { return getPendingUndoSplitLegions().size(); } }