/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package net.sf.colossus.ai.objectives; import java.util.Map; import java.util.Set; import net.sf.colossus.ai.AbstractAI; import net.sf.colossus.ai.helper.BattleEvalConstants; import net.sf.colossus.client.Client; import net.sf.colossus.game.BattleCritter; import net.sf.colossus.game.Creature; import net.sf.colossus.game.Legion; import net.sf.colossus.util.Probs; import net.sf.colossus.util.ValueRecorder; import net.sf.colossus.variant.BattleHex; import net.sf.colossus.variant.MasterBoardTerrain; /** The objective of sending all of a CreatureType into battle, presumably * because we don't really need them for anything else. * * @author Romain Dolbeau */ class CreatureAttackTacticalObjective extends AbstractTacticalObjective { private final Creature creature; private final Legion liveLegion; private final Client client; private final AbstractAI ai; private final BattleEvalConstants bec; CreatureAttackTacticalObjective(float priority, Client client, Legion liveLegion, Creature creature, AbstractAI ai, BattleEvalConstants bec) { super(priority); this.creature = creature; this.liveLegion = liveLegion; this.client = client; this.ai = ai; this.bec = bec; } public boolean objectiveAttained() { return getCount() == 0; } public int getCount() { return liveLegion.numCreature(creature.getType()); } /** This is mostly a copy/paste from the EvaluateCritterMove_Strike * and EvaluateCritterMove_Rangestrike functions in SimpleAI. This is * known. The goal is indeed to replace the big hardwired functions in * SimpleAI by a bunch of objectives, so we can tweak what critter does * what in an easier way. */ public ValueRecorder situationContributeToTheObjective() { ValueRecorder value = new ValueRecorder(getDescription()); final MasterBoardTerrain terrain = client.getBattleSite().getTerrain(); final int turn = client.getBattleTurnNumber(); int which = 0; Map<BattleHex, Integer> strikeMap = ai.findStrikeMap(); for (BattleCritter critter : client.getActiveBattleUnits()) { if (critter.getCurrentHex().isEntrance()) { continue; } if (critter.getType().equals(creature.getType())) { final int skill = critter.getSkill(); final int power = critter.getPower(); Set<BattleHex> targetHexes = client.findStrikes(critter .getTag()); String desc = creature.getName() + " #" + which; which++; int numTargets = targetHexes.size(); if (targetHexes.size() == 0) { continue; } if (client.isInContact(critter, true)) { value.add(bec.ATTACKER_ADJACENT_TO_ENEMY, desc + ": AttackerAdjacentToEnemy"); int killValue = 0; int numKillableTargets = 0; int hitsExpected = 0; for (BattleHex targetHex : targetHexes) { BattleCritter target = client.getBattleCS() .getBattleUnit(targetHex); // Reward being next to enemy titans. (Banzai!) if (target.isTitan()) { value.add(bec.ADJACENT_TO_ENEMY_TITAN, desc + ": AdjacentToEnemyTitan"); } // Reward being next to a rangestriker, so it can't hang // back and plink us. if (target.isRangestriker() && !critter.isRangestriker()) { value.add(bec.ADJACENT_TO_RANGESTRIKER, desc + ": AdjacenttoRangestriker"); } // Attack Warlocks so they don't get Titan if (target.getType().useMagicMissile()) { value.add(bec.ADJACENT_TO_BUDDY_TITAN, desc + ": AdjacentToBuddyTitan"); } // Reward being next to an enemy that we can probably // kill this turn. int dice = ai.getBattleStrike().getDice(critter, target); int strikeNum = ai.getBattleStrike().getStrikeNumber( critter, target); double meanHits = Probs.meanHits(dice, strikeNum); if (meanHits + target.getHits() >= target.getPower()) { numKillableTargets++; int targetValue = ai.getKillValue(target, terrain); killValue = Math.max(targetValue, killValue); } else { // reward doing damage to target - esp. titan. int targetValue = ai.getKillValue(target, terrain); killValue = (int)(0.5 * (meanHits / target .getPower()) * Math .max(targetValue, killValue)); } // Reward ganging up on enemies. if (strikeMap != null) { int numAttackingThisTarget = strikeMap.get( targetHex).intValue(); if (numAttackingThisTarget > 1) { value.add(bec.GANG_UP_ON_CREATURE, desc + ": GangUpOnCreature Strike"); } } // Penalize damage that we can take this turn, { dice = ai.getBattleStrike().getDice(target, critter); strikeNum = ai.getBattleStrike().getStrikeNumber( target, critter); hitsExpected += Probs.meanHits(dice, strikeNum); } } if (liveLegion.equals(client.getAttacker())) { value.add(bec.ATTACKER_KILL_SCALE_FACTOR * killValue, desc + ": AttackerKillValueScaled"); value.add(bec.KILLABLE_TARGETS_SCALE_FACTOR * numKillableTargets, desc + ": AttackerNumKillable"); } else { value.add(bec.DEFENDER_KILL_SCALE_FACTOR * killValue, desc + ": DefenderKillValueScaled"); value.add(bec.KILLABLE_TARGETS_SCALE_FACTOR * numKillableTargets, desc + ": DefenderNumKillable"); } int hits = critter.getHits(); // XXX Attacking legions late in battle ignore damage. // the isTitan() here should be moved to _Titan function above ? if (liveLegion.equals(client.getDefender()) || critter.isTitan() || turn <= 4) { if (hitsExpected + hits >= power) { if (liveLegion.equals(client.getAttacker())) { value.add(bec.ATTACKER_GET_KILLED_SCALE_FACTOR * ai.getKillValue(critter, terrain), desc + ": AttackerGetKilled"); } else { value.add(bec.DEFENDER_GET_KILLED_SCALE_FACTOR * ai.getKillValue(critter, terrain), desc + ": DefenderGetKilled"); } } else { if (liveLegion.equals(client.getAttacker())) { value.add(bec.ATTACKER_GET_HIT_SCALE_FACTOR * ai.getKillValue(critter, terrain), desc + ": AttackerGetHit"); } else { value.add(bec.DEFENDER_GET_HIT_SCALE_FACTOR * ai.getKillValue(critter, terrain), desc + ": DefendergetHit"); } } } } else { // Rangestrikes. value.add(bec.FIRST_RANGESTRIKE_TARGET, desc + ": FirstRangestrikeTarget"); // Having multiple targets is good, in case someone else // kills one. if (numTargets >= 2) { value.add(bec.EXTRA_RANGESTRIKE_TARGET, desc + ": ExtraRangestrikeTarget"); } // Non-warlock skill 4 rangestrikers should slightly prefer // range 3 to range 4. Non-brush rangestrikers should // prefer strikes not through bramble. Warlocks should // try to rangestrike titans. boolean penalty = true; for (BattleHex targetHex : targetHexes) { BattleCritter target = client.getBattleCS() .getBattleUnit(targetHex); if (target.isTitan()) { value.add(bec.RANGESTRIKE_TITAN, desc + ": RangestrikeTitan"); } int strikeNum = ai.getBattleStrike().getStrikeNumber( critter, target); if (strikeNum <= 4 - skill + target.getSkill()) { penalty = false; } // Reward ganging up on enemies. if (strikeMap != null) { int numAttackingThisTarget = strikeMap.get( targetHex).intValue(); if (numAttackingThisTarget > 1) { value.add(bec.GANG_UP_ON_CREATURE, desc + ": GangUpOnCreature RangeStrike"); } } } if (!penalty) { value.add(bec.RANGESTRIKE_WITHOUT_PENALTY, desc + ": RangestrikeWithoutPenalty"); } } } } return value; } public String getDescription() { return "Using " + creature.getName() + " to attack (" + getPriority() + ")"; } }