package net.sf.colossus.client; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import net.sf.colossus.common.Constants; import net.sf.colossus.game.Battle; import net.sf.colossus.game.BattleCritter; import net.sf.colossus.game.BattlePhase; import net.sf.colossus.game.BattleUnit; import net.sf.colossus.game.Game; import net.sf.colossus.game.Legion; import net.sf.colossus.game.Phase; import net.sf.colossus.game.Player; import net.sf.colossus.util.CollectionHelper; import net.sf.colossus.util.Predicate; import net.sf.colossus.variant.BattleHex; import net.sf.colossus.variant.CreatureType; import net.sf.colossus.variant.MasterHex; /** * Contains a lot of Battle related data * * Currently contains also many methods that were earlier in "Strike.java" * (client package). * First moved to here to make it easier to unify them with the server side * version or possibly even with Battle from game package. * * TODO One handicap right now is isInContact(...) * * This method is used by getDice, getAttackerSkill and getStrikeNumber; * they ask this from Client (and thus need client as argument). * On server side, those methods are in CreatureServerSide * (do they belong there?? IMHO not, because those calls are valid to * to only during a battle, which might not always be the case and nothing * prevents calling it then) and CreatureServerSide is able to resolve that * question by itself. * */ public class BattleClientSide extends Battle { private static final Logger LOGGER = Logger .getLogger(BattleClientSide.class.getName()); private BattlePhase battlePhase; private Player battleActivePlayer; private final List<BattleUnit> battleUnits = new ArrayList<BattleUnit>(); public BattleClientSide(Game game, Legion attacker, Legion defender, MasterHex location) { super(game, attacker, defender, location); LOGGER.info("Battle client side instantiated for " + attacker.getMarkerId() + " attacking " + defender.getMarkerId() + " in land " + location.getTerrain().getDisplayName()); } public void init(int battleTurnNumber, Player battleActivePlayer, BattlePhase battlePhase) { this.battleTurnNumber = battleTurnNumber; this.battleActivePlayer = battleActivePlayer; this.battlePhase = battlePhase; this.getDefendingLegion().setEntrySide( this.getAttackingLegion().getEntrySide().getOpposingSide()); } // Helper method public GameClientSide getGameClientSide() { return (GameClientSide)game; } public Player getBattleActivePlayer() { return battleActivePlayer; } public void cleanupBattle() { battleUnits.clear(); setBattlePhase(null); battleTurnNumber = -1; battleActivePlayer = ((GameClientSide)game).getNoonePlayer(); } @Override public Legion getBattleActiveLegion() { if (battleActivePlayer.equals(getDefendingLegion().getPlayer())) { return getDefendingLegion(); } else { return getAttackingLegion(); } } public BattlePhase getBattlePhase() { return battlePhase; } public void setBattlePhase(BattlePhase battlePhase) { this.battlePhase = battlePhase; } public boolean isBattlePhase(BattlePhase phase) { return this.battlePhase == phase; } public void setupPhase(BattlePhase phase, Player battleActivePlayer, int battleTurnNumber) { setBattlePhase(phase); setBattleActivePlayer(battleActivePlayer); setBattleTurnNumber(battleTurnNumber); } // public for IOracle public String getBattlePhaseName() { if (game.isPhase(Phase.FIGHT)) { if (battlePhase != null) { return battlePhase.toString(); } } return ""; } public void setBattleActivePlayer(Player battleActivePlayer) { this.battleActivePlayer = battleActivePlayer; } public void setupBattleFight(BattlePhase battlePhase, Player battleActivePlayer) { setBattlePhase(battlePhase); setBattleActivePlayer(battleActivePlayer); if (isBattlePhase(BattlePhase.FIGHT)) { markOffboardCreaturesDead(); } } public BattleUnit createBattleUnit(String imageName, boolean isDefender, int tag, BattleHex hex, CreatureType type, Legion legion) { BattleUnit battleUnit = new BattleUnit(imageName, isDefender, tag, hex, type, legion); battleUnits.add(battleUnit); return battleUnit; } public boolean anyOffboardCreatures() { for (BattleCritter critter : getActiveBattleUnits()) { if (isCritterOffboard(critter)) { return true; } } return false; } public boolean isCritterOffboard(BattleCritter critter) { return critter.getCurrentHex().getLabel().startsWith("X"); } public boolean isTitanOffboard(Player player) { for (BattleCritter critter : getBattleUnits()) { if (critter.isTitan() && isCritterOffboard(critter) && player.equals(getGameClientSide().getPlayerByTag( critter.getTag()))) { return true; } } return false; } public List<BattleUnit> getActiveBattleUnits() { return CollectionHelper.selectAsList(battleUnits, new Predicate<BattleUnit>() { public boolean matches(BattleUnit battleUnit) { return getBattleActivePlayer().equals( getGameClientSide() .getPlayerByTag(battleUnit.getTag())); } }); } public List<BattleUnit> getInactiveBattleUnits() { return CollectionHelper.selectAsList(battleUnits, new Predicate<BattleUnit>() { public boolean matches(BattleUnit battleUnit) { return !getBattleActivePlayer().equals( getGameClientSide() .getPlayerByTag(battleUnit.getTag())); } }); } @Override public List<BattleCritter> getAllCritters() { List<BattleCritter> critters = new ArrayList<BattleCritter>(); for (BattleCritter critter : getBattleUnits()) { critters.add(critter); } return critters; } public List<BattleUnit> getBattleUnits() { return Collections.unmodifiableList(battleUnits); } public List<BattleUnit> getBattleUnits(final BattleHex hex) { return CollectionHelper.selectAsList(battleUnits, new Predicate<BattleUnit>() { public boolean matches(BattleUnit battleUnit) { return hex.equals(battleUnit.getCurrentHex()); } }); } public BattleUnit getBattleUnitCS(BattleHex hex) { List<BattleUnit> lBattleUnits = getBattleUnits(hex); if (lBattleUnits.isEmpty()) { return null; } return lBattleUnits.get(0); } public BattleUnit getBattleUnit(BattleHex hex) { BattleUnit unit = getBattleUnitCS(hex); BattleCritter critter = getCritter(hex); if (unit == null && critter == null) { // ok. } else if ( // unit == null && critter != null // || unit != null && critter == null unit != critter) { LOGGER .warning("getBattleUnit(hex) returns different result than getCritter(hex)!"); } return unit; } /** Get the BattleUnit with this tag. */ BattleUnit getBattleUnit(int tag) { for (BattleUnit battleUnit : battleUnits) { if (battleUnit.getTag() == tag) { return battleUnit; } } return null; } public void resetAllBattleMoves() { for (BattleCritter battleUnit : battleUnits) { battleUnit.setMoved(false); battleUnit.setStruck(false); } } public void markOffboardCreaturesDead() { for (BattleUnit battleUnit : getActiveBattleUnits()) { if (battleUnit.getCurrentHex().getLabel().startsWith("X")) { battleUnit.setDead(true); } } } public void removeDeadBattleChits() { Iterator<BattleUnit> it = battleUnits.iterator(); while (it.hasNext()) { BattleUnit battleUnit = it.next(); if (battleUnit.isDead()) { it.remove(); } } } /** Return the set of hexes with critters that have * valid strike targets. * @param client The client. */ Set<BattleHex> findCrittersWithTargets(Client client) { Set<BattleHex> set = new HashSet<BattleHex>(); for (BattleCritter battleUnit : getActiveBattleUnits()) { if (findTargets(battleUnit, true).size() > 0) { set.add(battleUnit.getCurrentHex()); } } return set; } /** * Tell whether a given creature can strike (rangestrike included) * the given potential target * TODO duplicated in CreatureServerSide * * @param striker The creature striking * @param target The potential target * @return whether striking target is a valid strike */ public boolean canStrike(BattleCritter striker, BattleCritter target) { BattleHex targetHex = target.getCurrentHex(); return findTargets(striker, true).contains(targetHex); } // Not a candidate to pull up: tag-to-BattleUnit resolving is // purely a client side issue. Even resolve-data-from-network issue? public Set<BattleHex> findTargets(int tag) { BattleCritter battleUnit = getBattleUnit(tag); return findTargets(battleUnit, true); } /** * Return a set of hexes containing targets that the critter may strike * TODO duplicated in BattleServerSide * * @param battleUnit the striking creature * @param rangestrike Whether to include rangestrike targets * @return a set of hexes containing targets */ public Set<BattleHex> findTargets(BattleCritter battleUnit, boolean rangestrike) { Set<BattleHex> set = new HashSet<BattleHex>(); // Each creature may strike only once per turn. if (battleUnit.hasStruck()) { return set; } // Offboard creatures can't strike. if (battleUnit.getCurrentHex().getLabel().startsWith("X")) { return set; } boolean isDefender = battleUnit.isDefender(); BattleHex currentHex = battleUnit.getCurrentHex(); boolean adjacentEnemy = false; // First mark and count normal strikes. for (int i = 0; i < 6; i++) { // Adjacent creatures separated by a cliff are not engaged. if (!currentHex.isCliff(i)) { BattleHex targetHex = currentHex.getNeighbor(i); if (targetHex != null && isOccupied(targetHex) && !targetHex.isEntrance()) { BattleCritter target = getBattleUnit(targetHex); if (target.isDefender() != isDefender) { adjacentEnemy = true; if (!target.isDead()) { set.add(targetHex); } } } } } CreatureType creature = battleUnit.getType(); // Then do rangestrikes if applicable. Rangestrikes are not allowed // if the creature can strike normally, so only look for them if // no targets have yet been found. if (rangestrike && !adjacentEnemy && creature.isRangestriker() && getBattlePhase() != BattlePhase.STRIKEBACK) { for (BattleCritter target : getInactiveBattleUnits()) { if (!target.isDead()) { BattleHex targetHex = target.getCurrentHex(); if (isRangestrikePossible(battleUnit, target)) { set.add(targetHex); } } } } return set; } /** Return true if the rangestrike is possible. * /* * WARNING: this is a duplication from code in Battle ; caller should use * a Battle instance instead. * @deprecated Should use an extension of Battle instead of Strike, with * extension of Creature instead of BattleCritter and extra BattleHex */ @Deprecated private boolean isRangestrikePossible(BattleCritter striker, BattleCritter target) { CreatureType creature = striker.getType(); CreatureType targetCreature = target.getType(); BattleHex currentHex = striker.getCurrentHex(); BattleHex targetHex = target.getCurrentHex(); if (currentHex.isEntrance() || targetHex.isEntrance()) { return false; } int range = Battle.getRange(currentHex, targetHex, false); int skill = creature.getSkill(); if (range > skill) { return false; } // Only magicMissile can rangestrike at range 2, rangestrike Lords, // or rangestrike without LOS. else if (!creature.useMagicMissile() && (range < 3 || targetCreature.isLord() || isLOSBlocked( currentHex, targetHex))) { return false; } return true; } /** Return the titan range (inclusive at both ends) from the critter to the * closest enemy critter. Return OUT_OF_RANGE if there are none. * * // BEGIN OLD COMMENT (when it was in Strike.java): * WARNING: this is a duplication from code in Battle ; caller should use * a Battle instance instead. * @deprecated Should use an extension of Battle instead of Strike * // END OLD COMMENT * * Now this is moved from Strike to BattleClientSide. * IMHO this is not a total duplicate of a method in Battle: Battle * does not have a minRangeToEnemy, just minRange between concrete hexes, * which IS actually called here. * TODO can they be unified? Or move to e.g. some class in ai.helper package? */ @Deprecated public int minRangeToEnemy(BattleCritter critter) { BattleHex hex = critter.getCurrentHex(); int min = Constants.OUT_OF_RANGE; for (BattleCritter target : getBattleUnits()) { if (critter.isDefender() != target.isDefender()) { BattleHex targetHex = target.getCurrentHex(); int range = Battle.getRange(hex, targetHex, false); // Exit early if adjacent. if (range == 2) { return range; } else if (range < min) { min = range; } } } return min; } // TODO pull up to game.Battle /** Return true if there are any enemies adjacent to this battleChit. * Dead critters count as being in contact only if countDead is true. */ @Override public boolean isInContact(BattleCritter striker, boolean countDead) { BattleHex hex = striker.getCurrentHex(); // Offboard creatures are not in contact. if (hex.isEntrance()) { return false; } for (int i = 0; i < 6; i++) { // Adjacent creatures separated by a cliff are not in contact. if (!hex.isCliff(i)) { BattleHex neighbor = hex.getNeighbor(i); if (neighbor != null) { BattleCritter other = getBattleUnit(neighbor); if (other != null && (other.isDefender() != striker.isDefender()) && (countDead || !other.isDead())) { return true; } } } } return false; } }