package games.strategy.triplea.delegate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.CompositeChange;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.ResourceCollection;
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.delegate.AutoSave;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.engine.random.IRandomStats.DiceType;
import games.strategy.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.Properties;
import games.strategy.triplea.attachments.AbstractConditionsAttachment;
import games.strategy.triplea.attachments.AbstractTriggerAttachment;
import games.strategy.triplea.attachments.ICondition;
import games.strategy.triplea.attachments.RulesAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.attachments.TriggerAttachment;
import games.strategy.triplea.attachments.UnitAttachment;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.CompositeMatchOr;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
import games.strategy.util.ThreadUtil;
/**
* At the end of the turn collect income.
*/
@MapSupport
@AutoSave(afterStepEnd = true)
public class EndTurnDelegate extends AbstractEndTurnDelegate {
@Override
protected String doNationalObjectivesAndOtherEndTurnEffects(final IDelegateBridge bridge) {
final StringBuilder endTurnReport = new StringBuilder();
// do national objectives
if (isNationalObjectives()) {
final String nationalObjectivesText = determineNationalObjectives(bridge);
if (nationalObjectivesText.trim().length() > 0) {
endTurnReport.append(nationalObjectivesText).append("<br />");
}
}
// create resources if any owned units have the ability
final String createsResourcesPositiveText = createResources(bridge, false);
if (createsResourcesPositiveText.trim().length() > 0) {
endTurnReport.append(createsResourcesPositiveText).append("<br />");
}
final String createsResourcesNegativeText = createResources(bridge, true);
if (createsResourcesNegativeText.trim().length() > 0) {
endTurnReport.append(createsResourcesNegativeText).append("<br />");
}
// create units if any owned units have the ability
final String createsUnitsText = createUnits(bridge);
if (createsUnitsText.trim().length() > 0) {
endTurnReport.append(createsUnitsText).append("<br />");
}
return endTurnReport.toString();
}
private String createUnits(final IDelegateBridge bridge) {
final StringBuilder endTurnReport = new StringBuilder();
final GameData data = getData();
final PlayerID player = data.getSequence().getStep().getPlayerID();
final Match<Unit> myCreatorsMatch =
new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player), Matches.UnitCreatesUnits);
final CompositeChange change = new CompositeChange();
for (final Territory t : data.getMap().getTerritories()) {
final Collection<Unit> myCreators = Match.getMatches(t.getUnits().getUnits(), myCreatorsMatch);
if (myCreators != null && !myCreators.isEmpty()) {
final Collection<Unit> toAdd = new ArrayList<>();
final Collection<Unit> toAddSea = new ArrayList<>();
final Collection<Unit> toAddLand = new ArrayList<>();
for (final Unit u : myCreators) {
final UnitAttachment ua = UnitAttachment.get(u.getType());
final IntegerMap<UnitType> createsUnitsMap = ua.getCreatesUnitsList();
final Collection<UnitType> willBeCreated = createsUnitsMap.keySet();
for (final UnitType ut : willBeCreated) {
if (UnitAttachment.get(ut).getIsSea() && Matches.TerritoryIsLand.match(t)) {
toAddSea.addAll(ut.create(createsUnitsMap.getInt(ut), player));
} else if (!UnitAttachment.get(ut).getIsSea() && !UnitAttachment.get(ut).getIsAir()
&& Matches.TerritoryIsWater.match(t)) {
toAddLand.addAll(ut.create(createsUnitsMap.getInt(ut), player));
} else {
toAdd.addAll(ut.create(createsUnitsMap.getInt(ut), player));
}
}
}
if (!toAdd.isEmpty()) {
final String transcriptText =
player.getName() + " creates " + MyFormatter.unitsToTextNoOwner(toAdd) + " in " + t.getName();
bridge.getHistoryWriter().startEvent(transcriptText, toAdd);
endTurnReport.append(transcriptText).append("<br />");
final Change place = ChangeFactory.addUnits(t, toAdd);
change.add(place);
}
if (!toAddSea.isEmpty()) {
final Match<Territory> myTerrs = new CompositeMatchAnd<>(Matches.TerritoryIsWater);
final Collection<Territory> waterNeighbors = data.getMap().getNeighbors(t, myTerrs);
if (waterNeighbors != null && !waterNeighbors.isEmpty()) {
final Territory tw = getRandomTerritory(waterNeighbors, bridge);
final String transcriptText =
player.getName() + " creates " + MyFormatter.unitsToTextNoOwner(toAddSea) + " in " + tw.getName();
bridge.getHistoryWriter().startEvent(transcriptText, toAddSea);
endTurnReport.append(transcriptText).append("<br />");
final Change place = ChangeFactory.addUnits(tw, toAddSea);
change.add(place);
}
}
if (!toAddLand.isEmpty()) {
final Match<Territory> myTerrs =
new CompositeMatchAnd<>(Matches.isTerritoryOwnedBy(player), Matches.TerritoryIsLand);
final Collection<Territory> landNeighbors = data.getMap().getNeighbors(t, myTerrs);
if (landNeighbors != null && !landNeighbors.isEmpty()) {
final Territory tl = getRandomTerritory(landNeighbors, bridge);
final String transcriptText =
player.getName() + " creates " + MyFormatter.unitsToTextNoOwner(toAddLand) + " in " + tl.getName();
bridge.getHistoryWriter().startEvent(transcriptText, toAddLand);
endTurnReport.append(transcriptText).append("<br />");
final Change place = ChangeFactory.addUnits(tl, toAddLand);
change.add(place);
}
}
}
}
if (!change.isEmpty()) {
bridge.addChange(change);
}
return endTurnReport.toString();
}
private static Territory getRandomTerritory(final Collection<Territory> territories, final IDelegateBridge bridge) {
if (territories == null || territories.isEmpty()) {
return null;
}
if (territories.size() == 1) {
return territories.iterator().next();
}
// there is an issue with maps that have lots of rolls without any pause between them: they are causing the cypted
// random source (ie:
// live and pbem games) to lock up or error out
// so we need to slow them down a bit, until we come up with a better solution (like aggregating all the chances
// together, then getting
// a ton of random numbers at once instead of one at a time)
ThreadUtil.sleep(100);
final List<Territory> list = new ArrayList<>(territories);
final int random =
// ZERO BASED
bridge.getRandom(list.size(), null, DiceType.ENGINE, "Random territory selection for creating units");
return list.get(random);
}
private String createResources(final IDelegateBridge bridge, final boolean negativeResources) {
final StringBuilder endTurnReport = new StringBuilder();
final GameData data = getData();
final PlayerID player = data.getSequence().getStep().getPlayerID();
final Match<Unit> myCreatorsMatch = new CompositeMatchAnd<>(Matches.unitIsOwnedBy(player),
negativeResources ? Matches.UnitCreatesResourcesNegative : Matches.UnitCreatesResourcesPositive);
for (final Territory t : data.getMap().getTerritories()) {
final Collection<Unit> myCreators = Match.getMatches(t.getUnits().getUnits(), myCreatorsMatch);
if (myCreators != null && !myCreators.isEmpty()) {
for (final Unit u : myCreators) {
final CompositeChange change = new CompositeChange();
final UnitAttachment ua = UnitAttachment.get(u.getType());
final IntegerMap<Resource> createsResourcesMap = ua.getCreatesResourcesList();
final Collection<Resource> willBeCreated = createsResourcesMap.keySet();
for (final Resource r : willBeCreated) {
int toAdd = createsResourcesMap.getInt(r);
if (r.getName().equals(Constants.PUS)) {
toAdd *= Properties.getPU_Multiplier(data);
}
int total = player.getResources().getQuantity(r) + toAdd;
if (total < 0) {
toAdd -= total;
total = 0;
}
final String transcriptText = u.getUnitType().getName() + " in " + t.getName() + " creates " + toAdd + " "
+ r.getName() + "; " + player.getName() + " end with " + total + " " + r.getName();
bridge.getHistoryWriter().startEvent(transcriptText);
endTurnReport.append(transcriptText).append("<br />");
final Change resources = ChangeFactory.changeResourcesChange(player, r, toAdd);
change.add(resources);
}
if (!change.isEmpty()) {
bridge.addChange(change);
}
}
}
}
return endTurnReport.toString();
}
/**
* Determine if National Objectives have been met, and then do them.
*/
private String determineNationalObjectives(final IDelegateBridge bridge) {
final GameData data = getData();
final PlayerID player = data.getSequence().getStep().getPlayerID();
// First figure out all the conditions that will be tested, so we can test them all at the same time.
final HashSet<TriggerAttachment> toFirePossible = new HashSet<>();
final HashSet<ICondition> allConditionsNeeded = new HashSet<>();
final boolean useTriggers = games.strategy.triplea.Properties.getTriggers(data);
if (useTriggers) {
// add conditions required for triggers
final Match<TriggerAttachment> endTurnDelegateTriggerMatch = new CompositeMatchAnd<>(
AbstractTriggerAttachment.availableUses, AbstractTriggerAttachment.whenOrDefaultMatch(null, null),
new CompositeMatchOr<TriggerAttachment>(TriggerAttachment.resourceMatch()));
toFirePossible.addAll(TriggerAttachment.collectForAllTriggersMatching(
new HashSet<>(Collections.singleton(player)), endTurnDelegateTriggerMatch, bridge));
allConditionsNeeded.addAll(
AbstractConditionsAttachment.getAllConditionsRecursive(new HashSet<>(toFirePossible), null));
}
// add conditions required for national objectives (nat objs that have uses left)
final List<RulesAttachment> natObjs =
Match.getMatches(RulesAttachment.getNationalObjectives(player), availableUses);
allConditionsNeeded
.addAll(AbstractConditionsAttachment.getAllConditionsRecursive(new HashSet<>(natObjs), null));
if (allConditionsNeeded.isEmpty()) {
return "";
}
final StringBuilder endTurnReport = new StringBuilder();
// now test all the conditions
final HashMap<ICondition, Boolean> testedConditions =
AbstractConditionsAttachment.testAllConditionsRecursive(allConditionsNeeded, null, bridge);
// now that we have all testedConditions, may as well do triggers first.
if (useTriggers) {
if (!toFirePossible.isEmpty()) {
// get all triggers that are satisfied based on the tested conditions.
final Set<TriggerAttachment> toFireTestedAndSatisfied = new HashSet<>(
Match.getMatches(toFirePossible, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions)));
// now list out individual types to fire, once for each of the matches above.
endTurnReport.append(TriggerAttachment.triggerResourceChange(toFireTestedAndSatisfied, bridge, null, null, true,
true, true, true)).append("<br />");
}
}
// now do all the national objectives
for (final RulesAttachment rule : natObjs) {
int uses = rule.getUses();
if (uses == 0 || !rule.isSatisfied(testedConditions)) {
continue;
}
int toAdd = rule.getObjectiveValue();
toAdd *= Properties.getPU_Multiplier(data);
toAdd *= rule.getEachMultiple();
int total = player.getResources().getQuantity(Constants.PUS) + toAdd;
if (total < 0) {
toAdd -= total;
total = 0;
}
final Change change =
ChangeFactory.changeResourcesChange(player, data.getResourceList().getResource(Constants.PUS), toAdd);
bridge.addChange(change);
if (uses > 0) {
uses--;
final Change use = ChangeFactory.attachmentPropertyChange(rule, Integer.toString(uses), "uses");
bridge.addChange(use);
}
final String PUMessage = MyFormatter.attachmentNameToText(rule.getName()) + ": " + player.getName()
+ " met a national objective for an additional " + toAdd + MyFormatter.pluralize(" PU", toAdd) + "; end with "
+ total + MyFormatter.pluralize(" PU", total);
bridge.getHistoryWriter().startEvent(PUMessage);
endTurnReport.append(PUMessage).append("<br />");
}
return endTurnReport.toString();
}
private boolean isNationalObjectives() {
return games.strategy.triplea.Properties.getNationalObjectives(getData());
}
private static Match<RulesAttachment> availableUses = new Match<RulesAttachment>() {
@Override
public boolean match(final RulesAttachment ra) {
return ra.getUses() != 0;
}
};
@Override
protected String addOtherResources(final IDelegateBridge aBridge) {
final StringBuilder endTurnReport = new StringBuilder();
final GameData data = aBridge.getData();
final CompositeChange change = new CompositeChange();
final Collection<Territory> territories = data.getMap().getTerritoriesOwnedBy(m_player);
final ResourceCollection productionCollection = getResourceProduction(territories, data);
final IntegerMap<Resource> production = productionCollection.getResourcesCopy();
for (final Entry<Resource, Integer> resource : production.entrySet()) {
final Resource r = resource.getKey();
int toAdd = resource.getValue();
int total = m_player.getResources().getQuantity(r) + toAdd;
if (total < 0) {
toAdd -= total;
total = 0;
}
final String resourceText =
m_player.getName() + " collects " + toAdd + " " + MyFormatter.pluralize(r.getName(), toAdd) + "; ends with "
+ total + " " + MyFormatter.pluralize(r.getName(), total) + " total";
aBridge.getHistoryWriter().startEvent(resourceText);
endTurnReport.append(resourceText).append("<br />");
change.add(ChangeFactory.changeResourcesChange(m_player, r, toAdd));
}
if (!change.isEmpty()) {
aBridge.addChange(change);
}
return endTurnReport.toString();
}
/**
* Since territory resource may contain any resource except PUs (PUs use "getProduction" instead),
* we will now figure out the total production of non-PUs resources.
*/
private static ResourceCollection getResourceProduction(final Collection<Territory> territories,
final GameData data) {
final ResourceCollection rVal = new ResourceCollection(data);
for (final Territory current : territories) {
final TerritoryAttachment attachment = TerritoryAttachment.get(current);
if (attachment == null) {
throw new IllegalStateException("No attachment for owned territory:" + current.getName());
}
final ResourceCollection toAdd = attachment.getResources();
if (toAdd == null) {
continue;
}
// Match will Check if territory is originally owned convoy center, or if contested
if (Matches.territoryCanCollectIncomeFrom(current.getOwner(), data).match(current)) {
rVal.add(toAdd);
}
}
return rVal;
}
}