package games.strategy.triplea.ai.weakAI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.NamedAttachable;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.ProductionRule;
import games.strategy.engine.data.RepairRule;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.Route;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.triplea.Constants;
import games.strategy.triplea.TripleAUnit;
import games.strategy.triplea.ai.AIUtils;
import games.strategy.triplea.ai.AbstractAI;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.delegate.BattleDelegate;
import games.strategy.triplea.delegate.DelegateFinder;
import games.strategy.triplea.delegate.Matches;
import games.strategy.triplea.delegate.TransportTracker;
import games.strategy.triplea.delegate.dataObjects.PlaceableUnits;
import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate;
import games.strategy.triplea.delegate.remote.IMoveDelegate;
import games.strategy.triplea.delegate.remote.IPurchaseDelegate;
import games.strategy.triplea.delegate.remote.ITechDelegate;
import games.strategy.util.CompositeMatch;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.CompositeMatchOr;
import games.strategy.util.IntegerMap;
import games.strategy.util.InverseMatch;
import games.strategy.util.Match;
import games.strategy.util.Util;
/*
* A very weak ai, based on some simple rules.<p>
*/
public class WeakAI extends AbstractAI {
private static final Logger s_logger = Logger.getLogger(WeakAI.class.getName());
/** Creates new WeakAI. */
public WeakAI(final String name, final String type) {
super(name, type);
}
@Override
protected void tech(final ITechDelegate techDelegate, final GameData data, final PlayerID player) {}
private Route getAmphibRoute(final PlayerID player, final GameData data) {
if (!isAmphibAttack(player, data)) {
return null;
}
final Territory ourCapitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
final Match<Territory> endMatch = new Match<Territory>() {
@Override
public boolean match(final Territory o) {
final boolean impassable = TerritoryAttachment.get(o) != null && TerritoryAttachment.get(o).getIsImpassable();
return !impassable && !o.isWater() && Utils.hasLandRouteToEnemyOwnedCapitol(o, player, data);
}
};
final Match<Territory> routeCond =
new CompositeMatchAnd<>(Matches.TerritoryIsWater, Matches.territoryHasNoEnemyUnits(player, data));
final Route withNoEnemy = Utils.findNearest(ourCapitol, endMatch, routeCond, data);
if (withNoEnemy != null && withNoEnemy.numberOfSteps() > 0) {
return withNoEnemy;
}
// this will fail if our capitol is not next to water, c'est la vie.
final Route route = Utils.findNearest(ourCapitol, endMatch, Matches.TerritoryIsWater, data);
if (route != null && route.numberOfSteps() == 0) {
return null;
}
return route;
}
private boolean isAmphibAttack(final PlayerID player, final GameData data) {
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
// we dont own our own capitol
if (capitol == null || !capitol.getOwner().equals(player)) {
return false;
}
// find a land route to an enemy territory from our capitol
final Route invasionRoute =
Utils.findNearest(capitol, Matches.isTerritoryEnemyAndNotUnownedWaterOrImpassableOrRestricted(player, data),
new CompositeMatchAnd<>(Matches.TerritoryIsLand, new InverseMatch<>(Matches.TerritoryIsNeutralButNotWater)),
data);
return invasionRoute == null;
}
@Override
protected void move(final boolean nonCombat, final IMoveDelegate moveDel, final GameData data,
final PlayerID player) {
if (nonCombat) {
doNonCombatMove(moveDel, player, data);
} else {
doCombatMove(moveDel, player, data);
}
pause();
}
private void doNonCombatMove(final IMoveDelegate moveDel, final PlayerID player, final GameData data) {
final List<Collection<Unit>> moveUnits = new ArrayList<>();
final List<Route> moveRoutes = new ArrayList<>();
final List<Collection<Unit>> transportsToLoad = new ArrayList<>();
// load the transports first
// they may be able to move farther
populateTransportLoad(data, moveUnits, moveRoutes, transportsToLoad, player);
doMove(moveUnits, moveRoutes, transportsToLoad, moveDel);
moveRoutes.clear();
moveUnits.clear();
transportsToLoad.clear();
// do the rest of the moves
populateNonCombat(data, moveUnits, moveRoutes, player);
populateNonCombatSea(true, data, moveUnits, moveRoutes, player);
doMove(moveUnits, moveRoutes, null, moveDel);
moveUnits.clear();
moveRoutes.clear();
transportsToLoad.clear();
// load the transports again if we can
// they may be able to move farther
populateTransportLoad(data, moveUnits, moveRoutes, transportsToLoad, player);
doMove(moveUnits, moveRoutes, transportsToLoad, moveDel);
moveRoutes.clear();
moveUnits.clear();
transportsToLoad.clear();
// unload the transports that can be unloaded
populateTransportUnloadNonCom(data, moveUnits, moveRoutes, player);
doMove(moveUnits, moveRoutes, null, moveDel);
}
private void doCombatMove(final IMoveDelegate moveDel, final PlayerID player, final GameData data) {
final List<Collection<Unit>> moveUnits = new ArrayList<>();
final List<Route> moveRoutes = new ArrayList<>();
final List<Collection<Unit>> transportsToLoad = new ArrayList<>();
// load the transports first
// they may be able to take part in a battle
populateTransportLoad(data, moveUnits, moveRoutes, transportsToLoad, player);
doMove(moveUnits, moveRoutes, transportsToLoad, moveDel);
moveRoutes.clear();
moveUnits.clear();
// we want to move loaded transports before we try to fight our battles
populateNonCombatSea(false, data, moveUnits, moveRoutes, player);
// find second amphib target
final Route altRoute = getAlternativeAmphibRoute(player, data);
if (altRoute != null) {
moveCombatSea(data, moveUnits, moveRoutes, player, altRoute, 1);
}
doMove(moveUnits, moveRoutes, null, moveDel);
moveUnits.clear();
moveRoutes.clear();
transportsToLoad.clear();
// fight
populateCombatMove(data, moveUnits, moveRoutes, player);
populateCombatMoveSea(data, moveUnits, moveRoutes, player);
doMove(moveUnits, moveRoutes, null, moveDel);
}
private void populateTransportLoad(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final List<Collection<Unit>> transportsToLoad, final PlayerID player) {
if (!isAmphibAttack(player, data)) {
return;
}
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
if (capitol == null || !capitol.getOwner().equals(player)) {
return;
}
List<Unit> unitsToLoad = capitol.getUnits().getMatches(Matches.UnitIsInfrastructure.invert());
unitsToLoad = Match.getMatches(unitsToLoad, Matches.unitIsOwnedBy(getPlayerID()));
for (final Territory neighbor : data.getMap().getNeighbors(capitol)) {
if (!neighbor.isWater()) {
continue;
}
final List<Unit> units = new ArrayList<>();
for (final Unit transport : neighbor.getUnits().getMatches(Matches.unitIsOwnedBy(player))) {
int free = TransportTracker.getAvailableCapacity(transport);
if (free <= 0) {
continue;
}
final Iterator<Unit> iter = unitsToLoad.iterator();
while (iter.hasNext() && free > 0) {
final Unit current = iter.next();
final UnitAttachment ua = UnitAttachment.get(current.getType());
if (ua.getIsAir()) {
continue;
}
if (ua.getTransportCost() <= free) {
iter.remove();
free -= ua.getTransportCost();
units.add(current);
}
}
}
if (units.size() > 0) {
final Route route = new Route();
route.setStart(capitol);
route.add(neighbor);
moveUnits.add(units);
moveRoutes.add(route);
transportsToLoad.add(neighbor.getUnits().getMatches(Matches.UnitIsTransport));
}
}
}
private void populateTransportUnloadNonCom(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final PlayerID player) {
final Route amphibRoute = getAmphibRoute(player, data);
if (amphibRoute == null) {
return;
}
final Territory lastSeaZoneOnAmphib = amphibRoute.getAllTerritories().get(amphibRoute.numberOfSteps() - 1);
final Territory landOn = amphibRoute.getEnd();
final CompositeMatch<Unit> landAndOwned =
new CompositeMatchAnd<>(Matches.UnitIsLand, Matches.unitIsOwnedBy(player));
final List<Unit> units = lastSeaZoneOnAmphib.getUnits().getMatches(landAndOwned);
if (units.size() > 0) {
// just try to make the move, the engine will stop us if it doesnt work
final Route route = new Route();
route.setStart(lastSeaZoneOnAmphib);
route.add(landOn);
moveUnits.add(units);
moveRoutes.add(route);
}
}
private List<Unit> load2Transports(final List<Unit> transportsToLoad) {
final List<Unit> units = new ArrayList<>();
for (final Unit transport : transportsToLoad) {
final Collection<Unit> landunits = TransportTracker.transporting(transport);
for (final Unit u : landunits) {
units.add(u);
}
}
return units;
}
private void doMove(final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes,
final List<Collection<Unit>> transportsToLoad, final IMoveDelegate moveDel) {
for (int i = 0; i < moveRoutes.size(); i++) {
pause();
if (moveRoutes.get(i) == null || moveRoutes.get(i).getEnd() == null || moveRoutes.get(i).getStart() == null
|| moveRoutes.get(i).hasNoSteps()) {
s_logger.fine("Route not valid" + moveRoutes.get(i) + " units:" + moveUnits.get(i));
continue;
}
String result;
if (transportsToLoad == null) {
result = moveDel.move(moveUnits.get(i), moveRoutes.get(i));
} else {
result = moveDel.move(moveUnits.get(i), moveRoutes.get(i), transportsToLoad.get(i));
}
if (result != null) {
s_logger.fine("could not move " + moveUnits.get(i) + " over " + moveRoutes.get(i) + " because : " + result);
}
}
}
private void moveCombatSea(final GameData data, final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes,
final PlayerID player, final Route amphibRoute, final int maxTrans) {
// TODO workaround - should check if amphibRoute is in moveRoutes
if (moveRoutes.size() == 2) {
moveRoutes.remove(1);
moveUnits.remove(1);
}
Territory firstSeaZoneOnAmphib = null;
Territory lastSeaZoneOnAmphib = null;
if (amphibRoute == null) {
return;
}
firstSeaZoneOnAmphib = amphibRoute.getAllTerritories().get(0);
lastSeaZoneOnAmphib = amphibRoute.getAllTerritories().get(amphibRoute.numberOfSteps() - 1);
final Match<Unit> ownedAndNotMoved =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.unitHasNotMoved, Transporting);
final List<Unit> unitsToMove = new ArrayList<>();
final List<Unit> transports = firstSeaZoneOnAmphib.getUnits().getMatches(ownedAndNotMoved);
if (transports.size() <= maxTrans) {
unitsToMove.addAll(transports);
} else {
unitsToMove.addAll(transports.subList(0, maxTrans));
}
final List<Unit> landUnits = load2Transports(unitsToMove);
final Route r = getMaxSeaRoute(data, firstSeaZoneOnAmphib, lastSeaZoneOnAmphib, player);
moveRoutes.add(r);
unitsToMove.addAll(landUnits);
moveUnits.add(unitsToMove);
}
/**
* prepares moves for transports.
*
* @param maxTrans
* -
* if -1 unlimited
*/
private void populateNonCombatSea(final boolean nonCombat, final GameData data,
final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes, final PlayerID player) {
final Route amphibRoute = getAmphibRoute(player, data);
Territory firstSeaZoneOnAmphib = null;
Territory lastSeaZoneOnAmphib = null;
if (amphibRoute != null) {
firstSeaZoneOnAmphib = amphibRoute.getAllTerritories().get(1);
lastSeaZoneOnAmphib = amphibRoute.getAllTerritories().get(amphibRoute.numberOfSteps() - 1);
}
final Match<Unit> ownedAndNotMoved =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.unitHasNotMoved);
for (final Territory t : data.getMap()) {
// move sea units to the capitol, unless they are loaded transports
if (t.isWater()) {
// land units, move all towards the end point
if (t.getUnits().someMatch(Matches.UnitIsLand)) {
// move along amphi route
if (lastSeaZoneOnAmphib != null) {
// two move route to end
final Route r = getMaxSeaRoute(data, t, lastSeaZoneOnAmphib, player);
if (r != null && r.numberOfSteps() > 0) {
moveRoutes.add(r);
final List<Unit> unitsToMove = t.getUnits().getMatches(Matches.unitIsOwnedBy(player));
moveUnits.add(unitsToMove);
}
}
}
if (nonCombat && t.getUnits().someMatch(ownedAndNotMoved)) {
// move toward the start of the amphib route
if (firstSeaZoneOnAmphib != null) {
final Route r = getMaxSeaRoute(data, t, firstSeaZoneOnAmphib, player);
moveRoutes.add(r);
moveUnits.add(t.getUnits().getMatches(ownedAndNotMoved));
}
}
}
}
}
private Route getMaxSeaRoute(final GameData data, final Territory start, final Territory destination,
final PlayerID player) {
final Match<Territory> routeCond =
new CompositeMatchAnd<>(Matches.TerritoryIsWater, Matches.territoryHasEnemyUnits(player, data).invert(),
Matches.territoryHasNonAllowedCanal(player, null, data).invert());
Route r = data.getMap().getRoute(start, destination, routeCond);
if (r == null) {
return null;
}
if (r.numberOfSteps() > 2) {
final Route newRoute = new Route();
newRoute.setStart(start);
newRoute.add(r.getAllTerritories().get(1));
newRoute.add(r.getAllTerritories().get(2));
r = newRoute;
}
return r;
}
private void populateCombatMoveSea(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final PlayerID player) {
final Collection<Unit> unitsAlreadyMoved = new HashSet<>();
for (final Territory t : data.getMap()) {
if (!t.isWater()) {
continue;
}
if (!t.getUnits().someMatch(Matches.enemyUnit(player, data))) {
continue;
}
final Territory enemy = t;
final float enemyStrength = AIUtils.strength(enemy.getUnits().getUnits(), false, true);
if (enemyStrength > 0) {
final CompositeMatch<Unit> attackable =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), new Match<Unit>() {
@Override
public boolean match(final Unit o) {
return !unitsAlreadyMoved.contains(o);
}
});
final Set<Territory> dontMoveFrom = new HashSet<>();
// find our strength that we can attack with
int ourStrength = 0;
final Collection<Territory> attackFrom = data.getMap().getNeighbors(enemy, Matches.TerritoryIsWater);
for (final Territory owned : attackFrom) {
// dont risk units we are carrying
if (owned.getUnits().someMatch(Matches.UnitIsLand)) {
dontMoveFrom.add(owned);
continue;
}
ourStrength += AIUtils.strength(owned.getUnits().getMatches(attackable), true, true);
}
if (ourStrength > 1.32 * enemyStrength) {
s_logger.fine("Attacking : " + enemy + " our strength:" + ourStrength + " enemy strength" + enemyStrength);
for (final Territory owned : attackFrom) {
if (dontMoveFrom.contains(owned)) {
continue;
}
final List<Unit> units = owned.getUnits().getMatches(attackable);
unitsAlreadyMoved.addAll(units);
moveUnits.add(units);
moveRoutes.add(data.getMap().getRoute(owned, enemy));
}
}
}
}
}
// searches for amphibious attack on empty territory
private Route getAlternativeAmphibRoute(final PlayerID player, final GameData data) {
if (!isAmphibAttack(player, data)) {
return null;
}
final Match<Territory> routeCondition =
new CompositeMatchAnd<>(Matches.TerritoryIsWater, Matches.territoryHasNoEnemyUnits(player, data));
// should select all territories with loaded transports
final Match<Territory> transportOnSea =
new CompositeMatchAnd<>(Matches.TerritoryIsWater, Matches.territoryHasLandUnitsOwnedBy(player));
Route altRoute = null;
final int length = Integer.MAX_VALUE;
for (final Territory t : data.getMap()) {
if (!transportOnSea.match(t)) {
continue;
}
final CompositeMatchAnd<Unit> ownedTransports =
new CompositeMatchAnd<>(Matches.UnitCanTransport, Matches.unitIsOwnedBy(player), Matches.unitHasNotMoved);
final CompositeMatchAnd<Territory> enemyTerritory =
new CompositeMatchAnd<>(Matches.isTerritoryEnemy(player, data), Matches.TerritoryIsLand,
new InverseMatch<>(Matches.TerritoryIsNeutralButNotWater), Matches.TerritoryIsEmpty);
final int trans = t.getUnits().countMatches(ownedTransports);
if (trans > 0) {
final Route newRoute = Utils.findNearest(t, enemyTerritory, routeCondition, data);
if (newRoute != null && length > newRoute.numberOfSteps()) {
altRoute = newRoute;
}
}
}
return altRoute;
}
private void populateNonCombat(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final PlayerID player) {
final Collection<Territory> territories = data.getMap().getTerritories();
movePlanesHomeNonCom(moveUnits, moveRoutes, player, data);
// move our units toward the nearest enemy capitol
for (final Territory t : territories) {
if (t.isWater()) {
continue;
}
if (TerritoryAttachment.get(t) != null && TerritoryAttachment.get(t).isCapital()) {
// if they are a threat to take our capitol, dont move
// compare the strength of units we can place
final float ourStrength = AIUtils.strength(player.getUnits().getUnits(), false, false);
final float attackerStrength = Utils.getStrengthOfPotentialAttackers(t, data);
if (attackerStrength > ourStrength) {
continue;
}
}
// these are the units we can move
final CompositeMatch<Unit> moveOfType = new CompositeMatchAnd<>();
moveOfType.add(Matches.unitIsOwnedBy(player));
moveOfType.add(Matches.UnitIsNotAA);
// we can never move factories
moveOfType.add(Matches.UnitCanMove);
moveOfType.add(Matches.UnitIsNotInfrastructure);
moveOfType.add(Matches.UnitIsLand);
final CompositeMatchAnd<Territory> moveThrough =
new CompositeMatchAnd<>(new InverseMatch<>(Matches.TerritoryIsImpassable),
new InverseMatch<>(Matches.TerritoryIsNeutralButNotWater), Matches.TerritoryIsLand);
final List<Unit> units = t.getUnits().getMatches(moveOfType);
if (units.size() == 0) {
continue;
}
int minDistance = Integer.MAX_VALUE;
Territory to = null;
// find the nearest enemy owned capital
for (final PlayerID otherPlayer : data.getPlayerList().getPlayers()) {
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(otherPlayer, data);
if (capitol != null && !data.getRelationshipTracker().isAllied(player, capitol.getOwner())) {
final Route route = data.getMap().getRoute(t, capitol, moveThrough);
if (route != null) {
final int distance = route.numberOfSteps();
if (distance != 0 && distance < minDistance) {
minDistance = distance;
to = capitol;
}
}
}
}
if (to != null) {
if (units.size() > 0) {
moveUnits.add(units);
final Route routeToCapitol = data.getMap().getRoute(t, to, moveThrough);
final Territory firstStep = routeToCapitol.getAllTerritories().get(1);
final Route route = new Route();
route.setStart(t);
route.add(firstStep);
moveRoutes.add(route);
}
} else { // if we cant move to a capitol, move towards the enemy
final CompositeMatchAnd<Territory> routeCondition =
new CompositeMatchAnd<>(Matches.TerritoryIsLand, Matches.TerritoryIsImpassable.invert());
Route newRoute = Utils.findNearest(t, Matches.territoryHasEnemyLandUnits(player, data), routeCondition, data);
// move to any enemy territory
if (newRoute == null) {
newRoute = Utils.findNearest(t, Matches.isTerritoryEnemy(player, data), routeCondition, data);
}
if (newRoute != null && newRoute.numberOfSteps() != 0) {
moveUnits.add(units);
final Territory firstStep = newRoute.getAllTerritories().get(1);
final Route route = new Route();
route.setStart(t);
route.add(firstStep);
moveRoutes.add(route);
}
}
}
}
private void movePlanesHomeNonCom(final List<Collection<Unit>> moveUnits, final List<Route> moveRoutes,
final PlayerID player, final GameData data) {
// the preferred way to get the delegate
final IMoveDelegate delegateRemote = (IMoveDelegate) getPlayerBridge().getRemoteDelegate();
// this works because we are on the server
final BattleDelegate delegate = DelegateFinder.battleDelegate(data);
final Match<Territory> canLand =
new CompositeMatchAnd<>(Matches.isTerritoryAllied(player, data), new Match<Territory>() {
@Override
public boolean match(final Territory o) {
return !delegate.getBattleTracker().wasConquered(o);
}
});
final Match<Territory> routeCondition = new CompositeMatchAnd<>(
Matches.territoryHasEnemyAAforCombatOnly(player, data).invert(), Matches.TerritoryIsImpassable.invert());
for (final Territory t : delegateRemote.getTerritoriesWhereAirCantLand()) {
final Route noAARoute = Utils.findNearest(t, canLand, routeCondition, data);
final Route aaRoute = Utils.findNearest(t, canLand, Matches.TerritoryIsImpassable.invert(), data);
final Collection<Unit> airToLand =
t.getUnits().getMatches(new CompositeMatchAnd<>(Matches.UnitIsAir, Matches.unitIsOwnedBy(player)));
// dont bother to see if all the air units have enough movement points
// to move without aa guns firing
// simply move first over no aa, then with aa
// one (but hopefully not both) will be rejected
moveUnits.add(airToLand);
moveRoutes.add(noAARoute);
moveUnits.add(airToLand);
moveRoutes.add(aaRoute);
}
}
private void populateCombatMove(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final PlayerID player) {
populateBomberCombat(data, moveUnits, moveRoutes, player);
final Collection<Unit> unitsAlreadyMoved = new HashSet<>();
// find the territories we can just walk into
final CompositeMatchOr<Territory> walkInto =
new CompositeMatchOr<>(Matches.isTerritoryEnemyAndNotUnownedWaterOrImpassableOrRestricted(player, data),
Matches.isTerritoryFreeNeutral(data));
final List<Territory> enemyOwned = Match.getMatches(data.getMap().getTerritories(), walkInto);
Collections.shuffle(enemyOwned);
Collections.sort(enemyOwned, (o1, o2) -> {
// -1 means o1 goes first. 1 means o2 goes first. zero means they are equal.
if (o1 == o2 || (o1 == null && o2 == null)) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
if (o1.equals(o2)) {
return 0;
}
final TerritoryAttachment ta1 = TerritoryAttachment.get(o1);
final TerritoryAttachment ta2 = TerritoryAttachment.get(o2);
if (ta1 == null && ta2 == null) {
return 0;
}
if (ta1 == null) {
return 1;
}
if (ta2 == null) {
return -1;
}
// take capitols first if we can
if (ta1.isCapital() && !ta2.isCapital()) {
return -1;
}
if (!ta1.isCapital() && ta2.isCapital()) {
return 1;
}
final boolean factoryInT1 = o1.getUnits().someMatch(Matches.UnitCanProduceUnits);
final boolean factoryInT2 = o2.getUnits().someMatch(Matches.UnitCanProduceUnits);
// next take territories which can produce
if (factoryInT1 && !factoryInT2) {
return -1;
}
if (!factoryInT1 && factoryInT2) {
return 1;
}
final boolean infrastructureInT1 = o1.getUnits().someMatch(Matches.UnitIsInfrastructure);
final boolean infrastructureInT2 = o2.getUnits().someMatch(Matches.UnitIsInfrastructure);
// next take territories with infrastructure
if (infrastructureInT1 && !infrastructureInT2) {
return -1;
}
if (!infrastructureInT1 && infrastructureInT2) {
return 1;
}
// next take territories with largest PU value
return ta2.getProduction() - ta1.getProduction();
});
final List<Territory> isWaterTerr = Utils.onlyWaterTerr(data, enemyOwned);
enemyOwned.removeAll(isWaterTerr);
// first find the territories we can just walk into
for (final Territory enemy : enemyOwned) {
if (AIUtils.strength(enemy.getUnits().getUnits(), false, false) == 0) {
// only take it with 1 unit
boolean taken = false;
for (final Territory attackFrom : data.getMap().getNeighbors(enemy,
Matches.territoryHasLandUnitsOwnedBy(player))) {
if (taken) {
break;
}
// get the cheapest unit to move in
final List<Unit> unitsSortedByCost = new ArrayList<>(attackFrom.getUnits().getUnits());
Collections.sort(unitsSortedByCost, AIUtils.getCostComparator());
for (final Unit unit : unitsSortedByCost) {
final Match<Unit> match = new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.UnitIsLand,
Matches.UnitIsNotInfrastructure, Matches.UnitCanMove, Matches.UnitIsNotAA,
Matches.UnitCanNotMoveDuringCombatMove.invert());
if (!unitsAlreadyMoved.contains(unit) && match.match(unit)) {
moveRoutes.add(data.getMap().getRoute(attackFrom, enemy));
// if unloading units, unload all of them,
// otherwise we wont be able to unload them
// in non com, for land moves we want to move the minimal
// number of units, to leave units free to move elsewhere
if (attackFrom.isWater()) {
final List<Unit> units = attackFrom.getUnits().getMatches(Matches.unitIsLandAndOwnedBy(player));
moveUnits.add(Util.difference(units, unitsAlreadyMoved));
unitsAlreadyMoved.addAll(units);
} else {
moveUnits.add(Collections.singleton(unit));
}
unitsAlreadyMoved.add(unit);
taken = true;
break;
}
}
}
}
}
// find the territories we can reasonably expect to take
for (final Territory enemy : enemyOwned) {
final float enemyStrength = AIUtils.strength(enemy.getUnits().getUnits(), false, false);
if (enemyStrength > 0) {
final CompositeMatch<Unit> attackable = new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player),
Matches.UnitIsStrategicBomber.invert(), new Match<Unit>() {
@Override
public boolean match(final Unit o) {
return !unitsAlreadyMoved.contains(o);
}
});
attackable.add(Matches.UnitIsNotAA);
attackable.add(Matches.UnitCanMove);
attackable.add(Matches.UnitIsNotInfrastructure);
attackable.add(Matches.UnitCanNotMoveDuringCombatMove.invert());
attackable.add(Matches.UnitIsNotSea);
final Set<Territory> dontMoveFrom = new HashSet<>();
// find our strength that we can attack with
int ourStrength = 0;
final Collection<Territory> attackFrom =
data.getMap().getNeighbors(enemy, Matches.territoryHasLandUnitsOwnedBy(player));
for (final Territory owned : attackFrom) {
if (TerritoryAttachment.get(owned) != null && TerritoryAttachment.get(owned).isCapital()
&& (Utils.getStrengthOfPotentialAttackers(owned, data) > AIUtils.strength(owned.getUnits().getUnits(),
false, false))) {
dontMoveFrom.add(owned);
continue;
}
ourStrength += AIUtils.strength(owned.getUnits().getMatches(attackable), true, false);
}
// prevents 2 infantry from attacking 1 infantry
if (ourStrength > 1.37 * enemyStrength) {
// this is all we need to take it, dont go overboard, since we may be able to use the units to attack
// somewhere else
double remainingStrengthNeeded = (2.5 * enemyStrength) + 4;
for (final Territory owned : attackFrom) {
if (dontMoveFrom.contains(owned)) {
continue;
}
List<Unit> units = owned.getUnits().getMatches(attackable);
// only take the units we need if
// 1) we are not an amphibious attack
// 2) we can potentially attack another territory
if (!owned.isWater()
&& data.getMap().getNeighbors(owned, Matches.territoryHasEnemyLandUnits(player, data)).size() > 1) {
units = Utils.getUnitsUpToStrength(remainingStrengthNeeded, units, false);
}
remainingStrengthNeeded -= AIUtils.strength(units, true, false);
if (units.size() > 0) {
unitsAlreadyMoved.addAll(units);
moveUnits.add(units);
moveRoutes.add(data.getMap().getRoute(owned, enemy));
}
}
s_logger.fine("Attacking : " + enemy + " our strength:" + ourStrength + " enemy strength" + enemyStrength
+ " remaining strength needed " + remainingStrengthNeeded);
}
}
}
}
private void populateBomberCombat(final GameData data, final List<Collection<Unit>> moveUnits,
final List<Route> moveRoutes, final PlayerID player) {
final Match<Territory> enemyFactory = Matches.territoryIsEnemyNonNeutralAndHasEnemyUnitMatching(data, player,
Matches.UnitCanProduceUnitsAndCanBeDamaged);
final Match<Unit> ownBomber = new CompositeMatchAnd<>(Matches.UnitIsStrategicBomber, Matches.unitIsOwnedBy(player));
for (final Territory t : data.getMap().getTerritories()) {
final Collection<Unit> bombers = t.getUnits().getMatches(ownBomber);
if (bombers.isEmpty()) {
continue;
}
final Match<Territory> routeCond = new InverseMatch<>(Matches.territoryHasEnemyAAforCombatOnly(player, data));
final Route bombRoute = Utils.findNearest(t, enemyFactory, routeCond, data);
moveUnits.add(bombers);
moveRoutes.add(bombRoute);
}
}
private int countTransports(final GameData data, final PlayerID player) {
final CompositeMatchAnd<Unit> ownedTransport =
new CompositeMatchAnd<>(Matches.UnitIsTransport, Matches.unitIsOwnedBy(player));
int sum = 0;
for (final Territory t : data.getMap()) {
sum += t.getUnits().countMatches(ownedTransport);
}
return sum;
}
private int countLandUnits(final GameData data, final PlayerID player) {
final CompositeMatchAnd<Unit> ownedLandUnit =
new CompositeMatchAnd<>(Matches.UnitIsLand, Matches.unitIsOwnedBy(player));
int sum = 0;
for (final Territory t : data.getMap()) {
sum += t.getUnits().countMatches(ownedLandUnit);
}
return sum;
}
@Override
public void purchase(final boolean purchaseForBid, final int PUsToSpend, final IPurchaseDelegate purchaseDelegate,
final GameData data, final PlayerID player) {
if (purchaseForBid) {
// bid will only buy land units, due to weak ai placement for bid not being able to handle sea units
final Resource PUs = data.getResourceList().getResource(Constants.PUS);
int leftToSpend = PUsToSpend;
final List<ProductionRule> rules = player.getProductionFrontier().getRules();
final IntegerMap<ProductionRule> purchase = new IntegerMap<>();
int minCost = Integer.MAX_VALUE;
int i = 0;
while ((minCost == Integer.MAX_VALUE || leftToSpend >= minCost) && i < 100000) {
i++;
for (final ProductionRule rule : rules) {
final NamedAttachable resourceOrUnit = rule.getResults().keySet().iterator().next();
if (!(resourceOrUnit instanceof UnitType)) {
continue;
}
final UnitType results = (UnitType) resourceOrUnit;
if (Matches.UnitTypeIsSea.match(results) || Matches.UnitTypeIsAir.match(results)
|| Matches.UnitTypeIsInfrastructure.match(results) || Matches.UnitTypeIsAAforAnything.match(results)
|| Matches.UnitTypeHasMaxBuildRestrictions.match(results)
|| Matches.UnitTypeConsumesUnitsOnCreation.match(results)
|| Matches.UnitTypeIsStatic(player).match(results)) {
continue;
}
final int cost = rule.getCosts().getInt(PUs);
if (cost < 1) {
continue;
}
if (minCost == Integer.MAX_VALUE) {
minCost = cost;
}
if (minCost > cost) {
minCost = cost;
}
// give a preference to cheap units
if (Math.random() * cost < 2) {
if (cost <= leftToSpend) {
leftToSpend -= cost;
purchase.add(rule, 1);
}
}
}
}
purchaseDelegate.purchase(purchase);
pause();
return;
}
final boolean isAmphib = isAmphibAttack(player, data);
final Route amphibRoute = getAmphibRoute(player, data);
final int transportCount = countTransports(data, player);
final int landUnitCount = countLandUnits(data, player);
int defUnitsAtAmpibRoute = 0;
if (isAmphib && amphibRoute != null) {
defUnitsAtAmpibRoute = amphibRoute.getEnd().getUnits().getUnitCount();
}
final Resource PUs = data.getResourceList().getResource(Constants.PUS);
final int totPU = player.getResources().getQuantity(PUs);
int leftToSpend = totPU;
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
final List<ProductionRule> rules = player.getProductionFrontier().getRules();
final IntegerMap<ProductionRule> purchase = new IntegerMap<>();
List<RepairRule> rrules = Collections.emptyList();
final CompositeMatch<Unit> ourFactories =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.UnitCanProduceUnits);
final List<Territory> rfactories =
Match.getMatches(Utils.findUnitTerr(data, player, ourFactories), Matches.isTerritoryOwnedBy(player));
// figure out if anything needs to be repaired
if (player.getRepairFrontier() != null
&& games.strategy.triplea.Properties.getDamageFromBombingDoneToUnitsInsteadOfTerritories(data)) {
rrules = player.getRepairFrontier().getRules();
final IntegerMap<RepairRule> repairMap = new IntegerMap<>();
final HashMap<Unit, IntegerMap<RepairRule>> repair = new HashMap<>();
final Map<Unit, Territory> unitsThatCanProduceNeedingRepair = new HashMap<>();
final int minimumUnitPrice = 3;
int diff = 0;
int capProduction = 0;
Unit capUnit = null;
Territory capUnitTerritory = null;
int currentProduction = 0;
// we should sort this
Collections.shuffle(rfactories);
for (final Territory fixTerr : rfactories) {
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.UnitCanProduceUnitsAndCanBeDamaged)
.match(fixTerr)) {
continue;
}
final Unit possibleFactoryNeedingRepair = TripleAUnit.getBiggestProducer(
Match.getMatches(fixTerr.getUnits().getUnits(), ourFactories), fixTerr, player, data, false);
if (Matches.UnitHasTakenSomeBombingUnitDamage.match(possibleFactoryNeedingRepair)) {
unitsThatCanProduceNeedingRepair.put(possibleFactoryNeedingRepair, fixTerr);
}
final TripleAUnit taUnit = (TripleAUnit) possibleFactoryNeedingRepair;
diff = taUnit.getUnitDamage();
if (fixTerr == capitol) {
capProduction =
TripleAUnit.getHowMuchCanUnitProduce(possibleFactoryNeedingRepair, fixTerr, player, data, true, true);
capUnit = possibleFactoryNeedingRepair;
capUnitTerritory = fixTerr;
}
currentProduction +=
TripleAUnit.getHowMuchCanUnitProduce(possibleFactoryNeedingRepair, fixTerr, player, data, true, true);
}
rfactories.remove(capitol);
unitsThatCanProduceNeedingRepair.remove(capUnit);
// assume minimum unit price is 3, and that we are buying only that... if we over repair, oh well, that is better
// than under-repairing
// goal is to be able to produce all our units, and at least half of that production in the capitol
//
// if capitol is super safe, we don't have to do this. and if capitol is under siege, we should repair enough to
// place all our units here
int maxUnits = (totPU - 1) / minimumUnitPrice;
if ((capProduction <= maxUnits / 2 || rfactories.isEmpty()) && capUnit != null) {
for (final RepairRule rrule : rrules) {
if (!capUnit.getUnitType().equals(rrule.getResults().keySet().iterator().next())) {
continue;
}
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.UnitCanProduceUnitsAndCanBeDamaged)
.match(capitol)) {
continue;
}
final TripleAUnit taUnit = (TripleAUnit) capUnit;
diff = taUnit.getUnitDamage();
final int unitProductionAllowNegative =
TripleAUnit.getHowMuchCanUnitProduce(capUnit, capUnitTerritory, player, data, false, true) - diff;
if (!rfactories.isEmpty()) {
diff = Math.min(diff, (maxUnits / 2 - unitProductionAllowNegative) + 1);
} else {
diff = Math.min(diff, (maxUnits - unitProductionAllowNegative));
}
diff = Math.min(diff, leftToSpend - minimumUnitPrice);
if (diff > 0) {
if (unitProductionAllowNegative >= 0) {
currentProduction += diff;
} else {
currentProduction += diff + unitProductionAllowNegative;
}
repairMap.add(rrule, diff);
repair.put(capUnit, repairMap);
leftToSpend -= diff;
purchaseDelegate.purchaseRepair(repair);
repair.clear();
repairMap.clear();
// ideally we would adjust this after each single PU spent, then re-evaluate
// everything.
maxUnits = (leftToSpend - 1) / minimumUnitPrice;
}
}
}
int i = 0;
while (currentProduction < maxUnits && i < 2) {
for (final RepairRule rrule : rrules) {
for (final Unit fixUnit : unitsThatCanProduceNeedingRepair.keySet()) {
if (fixUnit == null || !fixUnit.getType().equals(rrule.getResults().keySet().iterator().next())) {
continue;
}
if (!Matches.territoryIsOwnedAndHasOwnedUnitMatching(player, Matches.UnitCanProduceUnitsAndCanBeDamaged)
.match(unitsThatCanProduceNeedingRepair.get(fixUnit))) {
continue;
}
// we will repair the first territories in the list as much as we can, until we fulfill the condition, then
// skip all other
// territories
if (currentProduction >= maxUnits) {
continue;
}
final TripleAUnit taUnit = (TripleAUnit) fixUnit;
diff = taUnit.getUnitDamage();
final int unitProductionAllowNegative = TripleAUnit.getHowMuchCanUnitProduce(fixUnit,
unitsThatCanProduceNeedingRepair.get(fixUnit), player, data, false, true) - diff;
if (i == 0) {
if (unitProductionAllowNegative < 0) {
diff = Math.min(diff, (maxUnits - currentProduction) - unitProductionAllowNegative);
} else {
diff = Math.min(diff, (maxUnits - currentProduction));
}
}
diff = Math.min(diff, leftToSpend - minimumUnitPrice);
if (diff > 0) {
if (unitProductionAllowNegative >= 0) {
currentProduction += diff;
} else {
currentProduction += diff + unitProductionAllowNegative;
}
repairMap.add(rrule, diff);
repair.put(fixUnit, repairMap);
leftToSpend -= diff;
purchaseDelegate.purchaseRepair(repair);
repair.clear();
repairMap.clear();
// ideally we would adjust this after each single PU spent, then re-evaluate
// everything.
maxUnits = (leftToSpend - 1) / minimumUnitPrice;
}
}
}
rfactories.add(capitol);
if (capUnit != null) {
unitsThatCanProduceNeedingRepair.put(capUnit, capUnitTerritory);
}
i++;
}
}
int minCost = Integer.MAX_VALUE;
int i = 0;
while ((minCost == Integer.MAX_VALUE || leftToSpend >= minCost) && i < 100000) {
i++;
for (final ProductionRule rule : rules) {
final NamedAttachable resourceOrUnit = rule.getResults().keySet().iterator().next();
if (!(resourceOrUnit instanceof UnitType)) {
continue;
}
final UnitType results = (UnitType) resourceOrUnit;
if (Matches.UnitTypeIsAir.match(results) || Matches.UnitTypeIsInfrastructure.match(results)
|| Matches.UnitTypeIsAAforAnything.match(results) || Matches.UnitTypeHasMaxBuildRestrictions.match(results)
|| Matches.UnitTypeConsumesUnitsOnCreation.match(results)
|| Matches.UnitTypeIsStatic(player).match(results)) {
continue;
}
final int transportCapacity = UnitAttachment.get(results).getTransportCapacity();
// buy transports if we can be amphibious
if (Matches.UnitTypeIsSea.match(results)) {
if (!isAmphib || transportCapacity <= 0) {
continue;
}
}
final int cost = rule.getCosts().getInt(PUs);
if (cost < 1) {
continue;
}
if (minCost == Integer.MAX_VALUE) {
minCost = cost;
}
if (minCost > cost) {
minCost = cost;
}
// give a preferene to cheap units, and to transports
// but dont go overboard with buying transports
int goodNumberOfTransports = 0;
final boolean isTransport = transportCapacity > 0;
if (amphibRoute != null) {
// 25% transports - can be more if frontier is far away
goodNumberOfTransports = (landUnitCount / 4);
// boost for transport production
if (isTransport && defUnitsAtAmpibRoute > goodNumberOfTransports && landUnitCount > defUnitsAtAmpibRoute
&& defUnitsAtAmpibRoute > transportCount) {
final int transports = (leftToSpend / cost);
leftToSpend -= cost * transports;
purchase.add(rule, transports);
continue;
}
// goodNumberOfTransports = ((int) (amphibRoute.getTerritories().size() * 2.6)) + 1;
}
final boolean buyBecauseTransport =
(Math.random() < 0.7 && transportCount < goodNumberOfTransports) || Math.random() < 0.10;
final boolean dontBuyBecauseTooManyTransports = transportCount > 2 * goodNumberOfTransports;
if ((!isTransport && Math.random() * cost < 2)
|| (isTransport && buyBecauseTransport && !dontBuyBecauseTooManyTransports)) {
if (cost <= leftToSpend) {
leftToSpend -= cost;
purchase.add(rule, 1);
}
}
}
}
purchaseDelegate.purchase(purchase);
pause();
}
@Override
public void place(final boolean bid, final IAbstractPlaceDelegate placeDelegate, final GameData data,
final PlayerID player) {
if (player.getUnits().size() == 0) {
return;
}
final Territory capitol = TerritoryAttachment.getFirstOwnedCapitalOrFirstUnownedCapital(player, data);
// place in capitol first
placeAllWeCanOn(data, capitol, placeDelegate, player);
final List<Territory> randomTerritories = new ArrayList<>(data.getMap().getTerritories());
Collections.shuffle(randomTerritories);
for (final Territory t : randomTerritories) {
if (t != capitol && t.getOwner().equals(player) && t.getUnits().someMatch(Matches.UnitCanProduceUnits)) {
placeAllWeCanOn(data, t, placeDelegate, player);
}
}
}
private void placeAllWeCanOn(final GameData data, final Territory placeAt, final IAbstractPlaceDelegate placeDelegate,
final PlayerID player) {
final PlaceableUnits pu = placeDelegate.getPlaceableUnits(player.getUnits().getUnits(), placeAt);
if (pu.getErrorMessage() != null) {
return;
}
int placementLeft = pu.getMaxUnits();
if (placementLeft == -1) {
placementLeft = Integer.MAX_VALUE;
}
final List<Unit> seaUnits = new ArrayList<>(player.getUnits().getMatches(Matches.UnitIsSea));
if (seaUnits.size() > 0) {
final Route amphibRoute = getAmphibRoute(player, data);
Territory seaPlaceAt = null;
if (amphibRoute != null) {
seaPlaceAt = amphibRoute.getAllTerritories().get(1);
} else {
final Set<Territory> seaNeighbors = data.getMap().getNeighbors(placeAt, Matches.TerritoryIsWater);
if (!seaNeighbors.isEmpty()) {
seaPlaceAt = seaNeighbors.iterator().next();
}
}
if (seaPlaceAt != null) {
final int seaPlacement = Math.min(placementLeft, seaUnits.size());
placementLeft -= seaPlacement;
final Collection<Unit> toPlace = seaUnits.subList(0, seaPlacement);
doPlace(seaPlaceAt, toPlace, placeDelegate);
}
}
final List<Unit> landUnits = new ArrayList<>(player.getUnits().getMatches(Matches.UnitIsLand));
if (!landUnits.isEmpty()) {
final int landPlaceCount = Math.min(placementLeft, landUnits.size());
placementLeft -= landPlaceCount;
final Collection<Unit> toPlace = landUnits.subList(0, landPlaceCount);
doPlace(placeAt, toPlace, placeDelegate);
}
}
private void doPlace(final Territory where, final Collection<Unit> toPlace, final IAbstractPlaceDelegate del) {
final String message = del.placeUnits(new ArrayList<>(toPlace), where, IAbstractPlaceDelegate.BidMode.NOT_BID);
if (message != null) {
s_logger.fine(message);
s_logger.fine("Attempt was at:" + where + " with:" + toPlace);
}
pause();
}
@Override
public boolean shouldBomberBomb(final Territory territory) {
return true;
}
public static final Match<Unit> Transporting = new Match<Unit>() {
@Override
public boolean match(final Unit o) {
return (TripleAUnit.get(o).getTransporting().size() > 0);
}
};
}