package net.sf.colossus.ai; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import net.sf.colossus.ai.helper.LegionMove; import net.sf.colossus.ai.helper.OnTheFlyLegionMove; import net.sf.colossus.ai.objectives.IObjectiveHelper; import net.sf.colossus.ai.objectives.SecondObjectiveHelper; import net.sf.colossus.ai.objectives.TacticalObjective; import net.sf.colossus.client.Client; import net.sf.colossus.client.CritterMove; import net.sf.colossus.client.LegionClientSide; import net.sf.colossus.common.Constants; import net.sf.colossus.game.Battle; import net.sf.colossus.game.BattleCritter; import net.sf.colossus.game.Legion; import net.sf.colossus.server.VariantSupport; import net.sf.colossus.util.ValueRecorder; import net.sf.colossus.variant.BattleHex; import net.sf.colossus.variant.MasterBoardTerrain; /** * Yet Another AI, to test some stuff. * * @author Romain Dolbeau */ public class ExperimentalAI extends SimpleAI // NO_UCD { private static final Logger LOGGER = Logger.getLogger(ExperimentalAI.class .getName()); private final static long MAX_EXHAUSTIVE_SEARCH_MOVES = 15000; public ExperimentalAI(Client client) { super(client); /* ExperimentalAI doesn't like to loose critter. */ bec.OFFBOARD_DEATH_SCALE_FACTOR = -2000; /* ExperimentalAI doesn't like to get out unprotected */ bec.DEFENDER_BY_EDGE_OR_BLOCKINGHAZARD_BONUS = 40; /* And it's a sadist, too. */ bec.DEFENDER_BY_DAMAGINGHAZARD_BONUS = 60; variant = VariantSupport.getCurrentVariant(); } @Override Collection<LegionMove> findLegionMoves( final List<List<CritterMove>> allCritterMoves) { long realcount = 1; for (List<CritterMove> lcm : allCritterMoves) { realcount *= lcm.size(); } if (realcount < MAX_EXHAUSTIVE_SEARCH_MOVES) { LOGGER.finer("Less than " + MAX_EXHAUSTIVE_SEARCH_MOVES + ", using exhaustive search (" + realcount + ")"); return generateLegionMoves(allCritterMoves, true); } LOGGER.finer("More than " + MAX_EXHAUSTIVE_SEARCH_MOVES + ", using on-the-fly search (" + realcount + ")"); return new OnTheFlyLegionMove(allCritterMoves); } @Override public List<CritterMove> battleMove() { List<CritterMove> r = super.battleMove(); /* force the GC, so we have a chance to call the finalize() from * the OnTheFlyLegionMove::Iterator used in findBattleMoves. */ Runtime.getRuntime().gc(); return r; } /** this computes the special case of the Titan critter */ @Override protected void evaluateCritterMove_Titan(final BattleCritter critter, ValueRecorder value, final MasterBoardTerrain terrain, final BattleHex hex, final Legion legion, final int turn) { if (hex.isEntrance()) { return; } // Reward titans sticking to the edges of the back row // surrounded by allies. We need to relax this in the // last few turns of the battle, so that attacking titans // don't just sit back and wait for a time loss. if (!critter.isTitan()) { LOGGER .warning("evaluateCritterMove_Titan called on non-Titan critter"); return; } if (terrain.isTower() && legion.equals(client.getDefender())) { // Stick to the center of the tower. value.add(bec.TITAN_TOWER_HEIGHT_BONUS * hex.getElevation(), "TitanTowerHeightBonus"); } else { if (legion.equals(client.getDefender())) { // defending titan is a coward. value.add(bec.TITAN_FORWARD_EARLY_PENALTY * 6 - rangeToClosestOpponent(hex), "Defending TitanForwardEarlyPenalty"); for (int i = 0; i < 6; i++) { BattleHex neighbor = hex.getNeighbor(i); if (neighbor == null /* Edge of the map */ || neighbor.getTerrain().blocksGround() || (neighbor.getTerrain().isGroundNativeOnly() && !hasOpponentNativeCreature(neighbor .getTerrain()))) { value.add(bec.TITAN_BY_EDGE_OR_BLOCKINGHAZARD_BONUS, "Defending TitanByEdgeOrBlockingHazard (" + i + ")"); } } } else { // attacking titan should progressively involve itself value.add( Math.round((((float)4. - turn) / (float)3.) * bec.TITAN_FORWARD_EARLY_PENALTY * ((float)6. - rangeToClosestOpponent(hex))), "Progressive TitanForwardEarlyPenalty"); for (int i = 0; i < 6; i++) { BattleHex neighbor = hex.getNeighbor(i); if (neighbor == null /* Edge of the map */ || neighbor.getTerrain().blocksGround() || (neighbor.getTerrain().isGroundNativeOnly() && !hasOpponentNativeCreature(neighbor .getTerrain()))) { // being close to the edge is not a disavantage, even late // in the battle, so min is 0 point. value .add( Math.round((Math.max(((float)4. - turn) / (float)3., (float)0.) * bec.TITAN_BY_EDGE_OR_BLOCKINGHAZARD_BONUS)), "Progressive TitanByEdgeOrBlockingHazard (" + i + ")"); } } } } } /** this compute for non-titan defending critter */ @Override protected void evaluateCritterMove_Defender(final BattleCritter critter, ValueRecorder value, final MasterBoardTerrain terrain, final BattleHex hex, final LegionClientSide legion, final int turn) { if (hex.isEntrance()) { return; } // Encourage defending critters to hang back. BattleHex entrance = terrain.getEntrance(legion.getEntrySide()); if (terrain.isTower()) { // Stick to the center of the tower. value.add(bec.DEFENDER_TOWER_HEIGHT_BONUS * hex.getElevation(), "DefenderTowerHeightBonus"); } else { int range = Battle.getRange(hex, entrance, true); // To ensure that defending legions completely enter // the board, prefer the second row to the first. The // exception is small legions early in the battle, // when trying to survive long enough to recruit. int preferredRange = 3; if (legion.getHeight() <= 3 && turn < 4) { preferredRange = 2; } if (range != preferredRange) { value.add( bec.DEFENDER_FORWARD_EARLY_PENALTY * Math.abs(range - preferredRange), "DefenderForwardEarlyPenalty"); } for (int i = 0; i < 6; i++) { BattleHex neighbor = hex.getNeighbor(i); if (neighbor == null /* Edge of the map */ || neighbor.getTerrain().blocksGround() || (neighbor.getTerrain().isGroundNativeOnly() && !hasOpponentNativeCreature(neighbor .getTerrain()))) { value.add(bec.DEFENDER_BY_EDGE_OR_BLOCKINGHAZARD_BONUS, "DefenderByEdgeOrBlockingHazard (" + i + ")"); } else if (neighbor.getTerrain().isDamagingToNonNative() && !hasOpponentNativeCreature(neighbor.getTerrain())) { value.add(bec.DEFENDER_BY_DAMAGINGHAZARD_BONUS, "DefenderByDamagingHazard (" + i + ")"); } } } } /** * "Does nothing" override of evaluateCritterMove_Strike in @SimpleAI. * The job of that one is handled (supposedly better... I wish) by * the objectives code. */ @Override protected void evaluateCritterMove_Strike(final BattleCritter critter, final Map<BattleHex, Integer> strikeMap, ValueRecorder value, final MasterBoardTerrain terrain, final BattleHex hex, final int power, final int skill, final LegionClientSide legion, final int turn, final Set<BattleHex> targetHexes) { return; } /** * "Does nothing" override of evaluateCritterMove_Rangestrike in @SimpleAI. * The job of that one is handled (supposedly better... I wish) by * the objectives code. */ @Override protected void evaluateCritterMove_Rangestrike( final BattleCritter critter, final Map<BattleHex, Integer> strikeMap, ValueRecorder value, final MasterBoardTerrain terrain, final BattleHex hex, final int power, final int skill, final LegionClientSide legion, final int turn, final Set<BattleHex> targetHexes) { return; } @Override protected int evaluateLegionBattleMoveAsAWhole(LegionMove lm, Map<BattleHex, Integer> strikeMap, ValueRecorder value) { final Legion legion = client.getMyEngagedLegion(); if (legion.equals(client.getAttacker())) { // TODO, something } else { boolean nobodyGetsHurt = true; int numCanBeReached = 0; int maxThatCanReach = 0; for (BattleCritter critter : client.getActiveBattleUnits()) { int canReachMe = 0; BattleHex myHex = critter.getCurrentHex(); for (BattleCritter foe : client.getInactiveBattleUnits()) { BattleHex foeHex = foe.getCurrentHex(); int range = Battle.getRange(foeHex, myHex, true); if ((range != Constants.OUT_OF_RANGE) && ((range - 2) <= foe.getSkill())) { canReachMe++; } } if (canReachMe > 0) { nobodyGetsHurt = false; numCanBeReached++; if (maxThatCanReach < canReachMe) { maxThatCanReach = canReachMe; } } } if (numCanBeReached == 1) // TODO: Rangestriker { value.add(bec.DEF__AT_MOST_ONE_IS_REACHABLE, "Def_AtMostOneIsReachable"); } if (maxThatCanReach == 1) // TODO: Rangestriker { value.add(bec.DEF__NOONE_IS_GANGBANGED, "Def_NoOneIsGangbanged"); } if (nobodyGetsHurt) // TODO: Rangestriker { value.add(bec.DEF__NOBODY_GETS_HURT, "Def_NobodyGetsHurt"); } } if (listObjectives != null) { for (TacticalObjective to : listObjectives) { ValueRecorder temp = to.situationContributeToTheObjective(); temp.setScale(to.getPriority()); value.add(temp); } } return value.getValue(); } private List<TacticalObjective> listObjectives = null; @Override public void initBattle() { super.initBattle(); if (client.getMyEngagedLegion() != null) { /* TODO: Objectives are never uupdated during Battle, which is * inherently broken. For instance, both defender recruit at turn 4 * and attacker's summoned angel won't have specific objective, * when it's likely they should attack. */ IObjectiveHelper helper = new SecondObjectiveHelper(client, this, variant); if (client.getMyEngagedLegion().equals(client.getDefender())) { listObjectives = helper.defenderObjective(); } else { listObjectives = helper.attackerObjective(); } } } @Override public void cleanupBattle() { super.cleanupBattle(); if (listObjectives != null) { for (TacticalObjective to : listObjectives) { LOGGER.info("Objective:" + to.getDescription() + " -> " + to.objectiveAttained()); } listObjectives = null; } } }