/* * Created on 07-01-2005 * */ package net.sf.colossus.ai; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.client.Client; import net.sf.colossus.game.Creature; import net.sf.colossus.game.Legion; import net.sf.colossus.util.Combos; import net.sf.colossus.variant.CreatureType; import net.sf.colossus.variant.MasterBoardTerrain; import net.sf.colossus.xmlparser.TerrainRecruitLoader; /** * @author kmilvangjens */ public class MilvangAI extends RationalAI // NO_UCD { private static final Logger LOGGER = Logger.getLogger(MilvangAI.class .getName()); private static final double PRIMARY_RECRUIT_FACTOR = 1.0; private static final double SECONDARY_RECRUIT_FACTOR = 0.1; public MilvangAI(Client client) { super(client); // TODO Auto-generated constructor stub } double findRecruitPotential(Map<CreatureType, Integer> critters, MasterBoardTerrain terrain) { int recruitNow = 0; int recruitLater = 0; // TODO why do we pass null here? RD/ it's (not quite) a bug, the Hex // is required only for special recruiting. This will only miss // Balrogs in variant Balrogs. List<CreatureType> tempRecruits = TerrainRecruitLoader .getPossibleRecruits(terrain, null); List<CreatureType> recruiters = TerrainRecruitLoader .getPossibleRecruiters(terrain, null); recruiters.retainAll(critters.keySet()); Iterator<CreatureType> lit = tempRecruits.iterator(); while (lit.hasNext()) { CreatureType creature = lit.next(); Iterator<CreatureType> liter = recruiters.iterator(); while (liter.hasNext()) { CreatureType lesser = liter.next(); // TODO another null for the TerranRecruitLoader -> why? Same // reason: it's required for custom recruiting. int numNeeded = TerrainRecruitLoader.numberOfRecruiterNeeded( lesser, creature, terrain, null); // TODO Not sure whether it's totally clean to directly use the // NonTitan form. But Titan cannot be a recruit anyway, and // before I changed it it used the method from CreatureType // directly anyway. int hintValue = getHintedRecruitmentValueNonTitan(creature); if (hintValue > recruitNow && numNeeded <= critters.get(lesser).intValue()) { recruitNow = hintValue; } if (hintValue > recruitLater) { recruitLater = hintValue; } } } return recruitNow * recruitNow + 0.1 * recruitLater * recruitLater; } @Override MusteredCreatures chooseCreaturesToSplitOut(Legion legion, boolean at_risk) { // // split a 5 to 8 high legion // if (legion.getHeight() == 8) { List<CreatureType> creatures = doInitialGameSplit(legion .getCurrentHex()); return new MusteredCreatures(true, creatures); } LOGGER.log(Level.FINEST, "sortCreaturesByValueName() in chooseCreaturesToSplitOut"); boolean hasTitan = legion.hasTitan(); List<CreatureType> critters = new ArrayList<CreatureType>(); for (Creature creature : legion.getCreatures()) { critters.add(creature.getType()); } double bestValue = 0; // make sure the list is never null even if we don't find anything List<CreatureType> bestKeep = new ArrayList<CreatureType>(); Combos<CreatureType> combos = new Combos<CreatureType>(critters, critters.size() - 2); for (Iterator<List<CreatureType>> it = combos.iterator(); it.hasNext();) { List<CreatureType> keepers = it.next(); double critterValue = 0; boolean keepTitan = false; Map<CreatureType, Integer> critterMap = new HashMap<CreatureType, Integer>(); for (CreatureType creatureType : keepers) { keepTitan |= creatureType.getName().equals("Titan"); // TODO: should this use the Titan-aware form of the method? int tmp = getHintedRecruitmentValueNonTitan(creatureType); critterValue += tmp * tmp; Integer numCritters = critterMap.get(creatureType); if (numCritters == null) { critterMap.put(creatureType, Integer.valueOf(1)); } else { critterMap.put(creatureType, Integer.valueOf(numCritters.intValue() + 1)); } } if (hasTitan && !keepTitan) { continue; // do no consider splitting Titan off } double totalRecruitValue = 0; double bestRecruitValue = 0; for (MasterBoardTerrain terrain : variant.getTerrains()) { double currRecruitValue = findRecruitPotential(critterMap, terrain); // TODO shouldn't that rather be terrain.isTower()? if (currRecruitValue > bestRecruitValue && !terrain.getId().equals("Tower")) { totalRecruitValue += SECONDARY_RECRUIT_FACTOR * bestRecruitValue; bestRecruitValue = currRecruitValue; } else { totalRecruitValue += SECONDARY_RECRUIT_FACTOR * currRecruitValue; } } totalRecruitValue += PRIMARY_RECRUIT_FACTOR * bestRecruitValue; if (critterValue + totalRecruitValue > bestValue) { bestValue = critterValue + totalRecruitValue; bestKeep = keepers; } } // remove the keep from critters to obtain the split for (CreatureType creature : bestKeep) { critters.remove(creature); } LOGGER.log(Level.FINEST, "Splitting: " + bestKeep + "/" + critters); return new MusteredCreatures(false, critters); } }