package net.sf.colossus.game; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import net.sf.colossus.variant.CreatureType; /** * The caretaker tracks the number of creatures still available and those dead. * * For each creature type the number of creatures still available for mustering and * the number of creatures already dead is stored. The latter function means this * version of a caretaker integrates what is called a 'graveyard' in a normal Titan * game. */ public class Caretaker { private static final Logger LOGGER = Logger.getLogger(Caretaker.class .getName()); /** * Callback interface for listening to changes to the numbers. */ public static interface ChangeListener { /** * Called whenever a change to the availability of a single creature type occurs. * * This is not called by the {@link Caretaker} on a full update but only on smaller * changes. * * @param type The creature type for which the count is changed. * @param availableCount The new number of available creatures of this type. */ public void creatureTypeAvailabilityUpdated(CreatureType type, int availableCount); /** * Called whenever a change to the number of dead creatures of a single type occurs. * * This is not called by the {@link Caretaker} on a full update but only on smaller * changes. * * @param type The creature type for which the count is changed. * @param deadCount The new number of dead creatures of this type. */ public void creatureTypeDeadCountUpdated(CreatureType type, int deadCount); /** Called when a change was done on either avail or dead, or both. * * @param type The creature type for which the count is changed. */ public void creatureTypeCountsUpdated(CreatureType type); /** * Called after large changes when listeners should perform an update of all * inferred information and/or displays. */ public void fullUpdate(); } /** * Map of creature types to the number of available creatures. */ private final Map<CreatureType, Integer> creatureAvailableCounts = new HashMap<CreatureType, Integer>(); /** * Map of creature types to the number of dead creatures. */ private final Map<CreatureType, Integer> creatureDeadCounts = new HashMap<CreatureType, Integer>(); /** * The game of which we manage the creatures. */ private final Game game; /** * All parties interested in changes to our numbers. */ private final List<ChangeListener> listeners = new ArrayList<ChangeListener>(); public Caretaker(Game game) { this.game = game; resetAllCounts(); } public void resetAllCounts() { if (game.getVariant() == null) { // TODO for some reason this can happen during stresstesting -> fix game initialization return; } for (CreatureType type : game.getVariant().getCreatureTypes()) { // We don't need synchronized access here, because this happens // only at game start, not during game creatureAvailableCounts.put(type, Integer.valueOf(type.getMaxCount())); creatureDeadCounts.put(type, Integer.valueOf(0)); } triggerFullUpdate(); } public void setAvailableCount(CreatureType type, int availableCount) { assert type != null : "Can not update counts unless creature type given"; // TODO get rid of synchronized access // Synchronized access is needed only because of Balrog. // If Balrog count update would be done together with score change, // instead of only "primarily during turn change", // (so it's needed additionally after engagement as part of lookup // for special possible recruits, "because in engagement phase one // or Balrogs might have been created") // and/or every if client has it's own caretaker, recruit graph, // and custom recruit base, // then this whole synchronized access could be dropped. synchronized (creatureAvailableCounts) { creatureAvailableCounts.put(type, Integer.valueOf(availableCount)); } triggerOneAvailabilityCount(type, availableCount); } public void setDeadCount(CreatureType type, int deadCount) { assert type != null : "Can not update counts unless creature type given"; creatureDeadCounts.put(type, Integer.valueOf(deadCount)); triggerOneDeadCount(type, deadCount); } public int getAvailableCount(CreatureType type) { synchronized (creatureAvailableCounts) { return creatureAvailableCounts.get(type).intValue(); } } public void adjustAvailableCount(CreatureType type) { int total = type.getMaxCount(); int dead = getDeadCount(type); int alive = game.getNumLivingCreatures(type); int avail = total - (dead + alive); if (avail < 0) { LOGGER.warning("available for " + type.getName() + " can't be " + avail + "! Setting it to 0."); avail = 0; } // We don't need synchronized access here, because get and set // do that if (getAvailableCount(type) != avail) { LOGGER.info("Caretaker counts for " + type.getName() + ": adjusting avail to " + avail + " dead=" + dead + " inGame=" + alive + " total=" + total); setAvailableCount(type, avail); } } public int getDeadCount(CreatureType type) { return creatureDeadCounts.get(type).intValue(); } protected Game getGame() { return game; } public void addListener(ChangeListener listener) { this.listeners.add(listener); } public void removeListener(ChangeListener listener) { this.listeners.remove(listener); } private void triggerOneAvailabilityCount(CreatureType type, int count) { for (ChangeListener listener : listeners) { listener.creatureTypeAvailabilityUpdated(type, count); } } private void triggerOneDeadCount(CreatureType type, int count) { for (ChangeListener listener : listeners) { listener.creatureTypeDeadCountUpdated(type, count); } } private void triggerOneCountUpdate(CreatureType type) { for (ChangeListener listener : listeners) { listener.creatureTypeCountsUpdated(type); } } private void triggerFullUpdate() { for (ChangeListener listener : listeners) { listener.fullUpdate(); } } public void takeOne(CreatureType type) { assert type != null : "Can not take null creature type"; int count = getAvailableCount(type); assert count > 0 : "Trying to take creature that doesn't exist anymore"; setAvailableCount(type, count - 1); } public void putOneBack(CreatureType type) { assert type != null : "Can not put null creature type back"; setAvailableCount(type, getAvailableCount(type) + 1); } public void putDeadOne(CreatureType type) { assert type != null : "Can not put null creature type into graveyard"; setDeadCount(type, getDeadCount(type) + 1); } /** * Move dead non-Titan immortals back to stacks. */ public void resurrectImmortals() { for (CreatureType type : getGame().getVariant().getCreatureTypes()) { if (type.isImmortal()) { int dead = getDeadCount(type); if (dead > 0) { int live = getAvailableCount(type); // Don't use setCount() / setDeadCount(), because we // want to update displays only at the end. // We don't need synchronized access here, because Balrog // is not immortal creatureAvailableCounts.put(type, Integer.valueOf(live + dead)); creatureDeadCounts.put(type, Integer.valueOf(0)); triggerOneCountUpdate(type); } } } // triggerFullUpdate(); } }