package games.strategy.triplea.delegate;
import java.io.Serializable;
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.Map.Entry;
import games.strategy.engine.data.Change;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.TechnologyFrontier;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.engine.random.IRandomStats.DiceType;
import games.strategy.sound.SoundPath;
import games.strategy.triplea.Constants;
import games.strategy.triplea.MapSupport;
import games.strategy.triplea.attachments.AbstractTriggerAttachment;
import games.strategy.triplea.attachments.ICondition;
import games.strategy.triplea.attachments.PlayerAttachment;
import games.strategy.triplea.attachments.TerritoryAttachment;
import games.strategy.triplea.attachments.TriggerAttachment;
import games.strategy.triplea.delegate.dataObjects.TechResults;
import games.strategy.triplea.delegate.remote.ITechDelegate;
import games.strategy.triplea.formatter.MyFormatter;
import games.strategy.triplea.player.ITripleAPlayer;
import games.strategy.util.CompositeMatchAnd;
import games.strategy.util.CompositeMatchOr;
import games.strategy.util.IntegerMap;
import games.strategy.util.Match;
import games.strategy.util.Util;
/**
* Logic for dealing with player tech rolls. This class requires the
* TechActivationDelegate which actually activates the tech.
*/
@MapSupport
public class TechnologyDelegate extends BaseTripleADelegate implements ITechDelegate {
private int m_techCost;
private HashMap<PlayerID, Collection<TechAdvance>> m_techs;
private TechnologyFrontier m_techCategory;
private boolean m_needToInitialize = true;
/** Creates new TechnolgoyDelegate. */
public TechnologyDelegate() {}
@Override
public void initialize(final String name, final String displayName) {
super.initialize(name, displayName);
m_techs = new HashMap<>();
m_techCost = -1;
}
/**
* Called before the delegate will run, AND before "start" is called.
*/
@Override
public void setDelegateBridgeAndPlayer(final IDelegateBridge iDelegateBridge) {
super.setDelegateBridgeAndPlayer(new GameDelegateBridge(iDelegateBridge));
}
/**
* Called before the delegate will run.
*/
@Override
public void start() {
super.start();
if (!m_needToInitialize) {
return;
}
if (games.strategy.triplea.Properties.getTriggers(getData())) {
// First set up a match for what we want to have fire as a default in this delegate. List out as a composite match
// OR.
// use 'null, null' because this is the Default firing location for any trigger that does NOT have 'when' set.
final Match<TriggerAttachment> technologyDelegateTriggerMatch = new CompositeMatchAnd<>(
AbstractTriggerAttachment.availableUses, AbstractTriggerAttachment.whenOrDefaultMatch(null, null),
new CompositeMatchOr<TriggerAttachment>(TriggerAttachment.techAvailableMatch()));
// get all possible triggers based on this match.
final HashSet<TriggerAttachment> toFirePossible = TriggerAttachment.collectForAllTriggersMatching(
new HashSet<>(Collections.singleton(m_player)), technologyDelegateTriggerMatch, m_bridge);
if (!toFirePossible.isEmpty()) {
// get all conditions possibly needed by these triggers, and then test them.
final HashMap<ICondition, Boolean> testedConditions =
TriggerAttachment.collectTestsForAllTriggers(toFirePossible, m_bridge);
// get all triggers that are satisfied based on the tested conditions.
final List<TriggerAttachment> toFireTestedAndSatisfied =
Match.getMatches(toFirePossible, AbstractTriggerAttachment.isSatisfiedMatch(testedConditions));
// now list out individual types to fire, once for each of the matches above.
TriggerAttachment.triggerAvailableTechChange(new HashSet<>(toFireTestedAndSatisfied), m_bridge,
null, null, true, true, true, true);
}
}
m_needToInitialize = false;
}
@Override
public void end() {
super.end();
m_needToInitialize = true;
}
@Override
public Serializable saveState() {
final TechnologyExtendedDelegateState state = new TechnologyExtendedDelegateState();
state.superState = super.saveState();
state.m_needToInitialize = m_needToInitialize;
state.m_techs = m_techs;
return state;
}
@Override
public void loadState(final Serializable state) {
final TechnologyExtendedDelegateState s = (TechnologyExtendedDelegateState) state;
super.loadState(s.superState);
m_needToInitialize = s.m_needToInitialize;
m_techs = s.m_techs;
}
@Override
public boolean delegateCurrentlyRequiresUserInput() {
if (!games.strategy.triplea.Properties.getTechDevelopment(getData())) {
return false;
}
if (!TerritoryAttachment.doWeHaveEnoughCapitalsToProduce(m_player, getData())) {
return false;
}
if (games.strategy.triplea.Properties.getWW2V3TechModel(getData())) {
final Resource techtokens = getData().getResourceList().getResource(Constants.TECH_TOKENS);
if (techtokens != null) {
final int techTokens = m_player.getResources().getQuantity(techtokens);
if (techTokens > 0) {
return true;
}
}
}
final int techCost = TechTracker.getTechCost(m_player);
int money = m_player.getResources().getQuantity(Constants.PUS);
if (money < techCost) {
final PlayerAttachment pa = PlayerAttachment.get(m_player);
if (pa == null) {
return false;
}
final Collection<PlayerID> helpPay = pa.getHelpPayTechCost();
if (helpPay == null || helpPay.isEmpty()) {
return false;
}
for (final PlayerID p : helpPay) {
money += p.getResources().getQuantity(Constants.PUS);
}
if (money < techCost) {
return false;
}
}
return true;
}
public Map<PlayerID, Collection<TechAdvance>> getAdvances() {
return m_techs;
}
private boolean isWW2V2() {
return games.strategy.triplea.Properties.getWW2V2(getData());
}
private boolean isWW2V3TechModel() {
return games.strategy.triplea.Properties.getWW2V3TechModel(getData());
}
private boolean isSelectableTechRoll() {
return games.strategy.triplea.Properties.getSelectableTechRoll(getData());
}
private boolean isLL_TECH_ONLY() {
return games.strategy.triplea.Properties.getLL_TECH_ONLY(getData());
}
@Override
public TechResults rollTech(final int techRolls, final TechnologyFrontier techToRollFor, final int newTokens,
final IntegerMap<PlayerID> whoPaysHowMuch) {
int rollCount = techRolls;
if (isWW2V3TechModel()) {
rollCount = newTokens;
}
final boolean canPay = checkEnoughMoney(rollCount, whoPaysHowMuch);
if (!canPay) {
return new TechResults("Not enough money to pay for that many tech rolls.");
}
chargeForTechRolls(rollCount, whoPaysHowMuch);
int m_currTokens = 0;
if (isWW2V3TechModel()) {
m_currTokens = m_player.getResources().getQuantity(Constants.TECH_TOKENS);
}
final GameData data = getData();
if (getAvailableTechs(m_player, data).isEmpty()) {
if (isWW2V3TechModel()) {
final Resource techTokens = data.getResourceList().getResource(Constants.TECH_TOKENS);
final String transcriptText = m_player.getName() + " No more available tech advances.";
m_bridge.getHistoryWriter().startEvent(transcriptText);
final Change removeTokens =
ChangeFactory.changeResourcesChange(m_bridge.getPlayerID(), techTokens, -m_currTokens);
m_bridge.addChange(removeTokens);
}
return new TechResults("No more available tech advances.");
}
final String annotation = m_player.getName() + " rolling for tech.";
int[] random;
int techHits = 0;
int remainder = 0;
final int diceSides = data.getDiceSides();
if (BaseEditDelegate.getEditMode(data)) {
final ITripleAPlayer tripleaPlayer = getRemotePlayer();
random = tripleaPlayer.selectFixedDice(techRolls, diceSides, true, annotation, diceSides);
techHits = getTechHits(random);
} else if (isLL_TECH_ONLY()) {
techHits = techRolls / diceSides;
remainder = techRolls % diceSides;
if (remainder > 0) {
random = m_bridge.getRandom(diceSides, 1, m_player, DiceType.TECH, annotation);
if (random[0] + 1 <= remainder) {
techHits++;
}
} else {
random = m_bridge.getRandom(diceSides, 1, m_player, DiceType.TECH, annotation);
remainder = diceSides;
}
} else {
random = m_bridge.getRandom(diceSides, techRolls, m_player, DiceType.TECH, annotation);
techHits = getTechHits(random);
}
final boolean isRevisedModel = isWW2V2() || (isSelectableTechRoll() && !isWW2V3TechModel());
final String directedTechInfo = isRevisedModel ? " for " + techToRollFor.getTechs().get(0) : "";
final DiceRoll renderDice = (isLL_TECH_ONLY() ? new DiceRoll(random, techHits, remainder, false)
: new DiceRoll(random, techHits, diceSides - 1, true));
m_bridge.getHistoryWriter()
.startEvent(
m_player.getName() + (random.length > 1 ? " roll " : " rolls : ") + MyFormatter.asDice(random)
+ directedTechInfo + " and gets " + techHits + " " + MyFormatter.pluralize("hit", techHits),
renderDice);
if (isWW2V3TechModel()
&& (techHits > 0 || games.strategy.triplea.Properties.getRemoveAllTechTokensAtEndOfTurn(data))) {
m_techCategory = techToRollFor;
// remove all the tokens
final Resource techTokens = data.getResourceList().getResource(Constants.TECH_TOKENS);
final String transcriptText = m_player.getName() + " removing all Technology Tokens after "
+ (techHits > 0 ? "successful" : "unsuccessful") + " research.";
m_bridge.getHistoryWriter().startEvent(transcriptText);
final Change removeTokens =
ChangeFactory.changeResourcesChange(m_bridge.getPlayerID(), techTokens, -m_currTokens);
m_bridge.addChange(removeTokens);
}
Collection<TechAdvance> advances;
if (isRevisedModel) {
if (techHits > 0) {
advances = Collections.singletonList(techToRollFor.getTechs().get(0));
} else {
advances = Collections.emptyList();
}
} else {
advances = getTechAdvances(techHits);
}
// Put in techs so they can be activated later.
m_techs.put(m_player, advances);
final List<String> advancesAsString = new ArrayList<>();
final Iterator<TechAdvance> iter = advances.iterator();
int count = advances.size();
final StringBuilder text = new StringBuilder();
while (iter.hasNext()) {
final TechAdvance advance = iter.next();
text.append(advance.getName());
count--;
advancesAsString.add(advance.getName());
if (count > 1) {
text.append(", ");
}
if (count == 1) {
text.append(" and ");
}
}
final String transcriptText = m_player.getName() + " discover " + text.toString();
if (advances.size() > 0) {
m_bridge.getHistoryWriter().startEvent(transcriptText);
// play a sound
getSoundChannel().playSoundForAll(SoundPath.CLIP_TECHNOLOGY_SUCCESSFUL, m_player);
} else {
getSoundChannel().playSoundForAll(SoundPath.CLIP_TECHNOLOGY_FAILURE, m_player);
}
return new TechResults(random, remainder, techHits, advancesAsString, m_player);
}
boolean checkEnoughMoney(final int rolls, final IntegerMap<PlayerID> whoPaysHowMuch) {
final Resource PUs = getData().getResourceList().getResource(Constants.PUS);
final int cost = rolls * getTechCost();
if (whoPaysHowMuch == null || whoPaysHowMuch.isEmpty()) {
final int has = m_bridge.getPlayerID().getResources().getQuantity(PUs);
return has >= cost;
} else {
int runningTotal = 0;
for (final Entry<PlayerID, Integer> entry : whoPaysHowMuch.entrySet()) {
final int has = entry.getKey().getResources().getQuantity(PUs);
final int paying = entry.getValue();
if (paying > has) {
return false;
}
runningTotal += paying;
}
return runningTotal >= cost;
}
}
private void chargeForTechRolls(final int rolls, final IntegerMap<PlayerID> whoPaysHowMuch) {
final Resource PUs = getData().getResourceList().getResource(Constants.PUS);
int cost = rolls * getTechCost();
if (whoPaysHowMuch == null || whoPaysHowMuch.isEmpty()) {
final String transcriptText = m_bridge.getPlayerID().getName() + " spend " + cost + " on tech rolls";
m_bridge.getHistoryWriter().startEvent(transcriptText);
final Change charge = ChangeFactory.changeResourcesChange(m_bridge.getPlayerID(), PUs, -cost);
m_bridge.addChange(charge);
} else {
for (final Entry<PlayerID, Integer> entry : whoPaysHowMuch.entrySet()) {
final PlayerID p = entry.getKey();
final int pays = Math.min(cost, entry.getValue());
if (pays <= 0) {
continue;
}
cost -= pays;
final String transcriptText = p.getName() + " spend " + pays + " on tech rolls";
m_bridge.getHistoryWriter().startEvent(transcriptText);
final Change charge = ChangeFactory.changeResourcesChange(p, PUs, -pays);
m_bridge.addChange(charge);
}
}
if (isWW2V3TechModel()) {
final Resource tokens = getData().getResourceList().getResource(Constants.TECH_TOKENS);
final Change newTokens = ChangeFactory.changeResourcesChange(m_bridge.getPlayerID(), tokens, rolls);
m_bridge.addChange(newTokens);
}
}
private int getTechHits(final int[] random) {
int count = 0;
for (final int element : random) {
if (element == getData().getDiceSides() - 1) {
count++;
}
}
return count;
}
private Collection<TechAdvance> getTechAdvances(int hits) {
List<TechAdvance> available = new ArrayList<>();
if (hits > 0 && isWW2V3TechModel()) {
available = getAvailableAdvancesForCategory(m_techCategory);
hits = 1;
} else {
available = getAvailableAdvances();
}
if (available.isEmpty()) {
return Collections.emptyList();
}
if (hits >= available.size()) {
return available;
}
if (hits == 0) {
return Collections.emptyList();
}
final Collection<TechAdvance> newAdvances = new ArrayList<>(hits);
final String annotation = m_player.getName() + " rolling to see what tech advances are aquired";
int[] random;
if (isSelectableTechRoll() || BaseEditDelegate.getEditMode(getData())) {
final ITripleAPlayer tripleaPlayer = getRemotePlayer();
random = tripleaPlayer.selectFixedDice(hits, 0, true, annotation, available.size());
} else {
random = new int[hits];
final List<Integer> rolled = new ArrayList<>();
// generating discrete rolls. messy, can't think of a more elegant way
// hits guaranteed to be less than available at this point.
for (int i = 0; i < hits; i++) {
int roll = m_bridge.getRandom(available.size() - i, null, DiceType.ENGINE, annotation);
for (final int r : rolled) {
if (roll >= r) {
roll++;
}
}
random[i] = roll;
rolled.add(roll);
}
}
final List<Integer> rolled = new ArrayList<>();
for (final int element : random) {
final int index = element;
// check in case of dice chooser.
if (!rolled.contains(index) && index < available.size()) {
newAdvances.add(available.get(index));
rolled.add(index);
}
}
m_bridge.getHistoryWriter().startEvent("Rolls to resolve tech hits:" + MyFormatter.asDice(random));
return newAdvances;
}
private List<TechAdvance> getAvailableAdvances() {
return getAvailableTechs(m_bridge.getPlayerID(), getData());
}
public static List<TechAdvance> getAvailableTechs(final PlayerID player, final GameData data) {
final Collection<TechAdvance> currentAdvances = TechTracker.getCurrentTechAdvances(player, data);
final Collection<TechAdvance> allAdvances = TechAdvance.getTechAdvances(data, player);
return Util.difference(allAdvances, currentAdvances);
}
private List<TechAdvance> getAvailableAdvancesForCategory(final TechnologyFrontier techCategory) {
// Collection<TechAdvance> allAdvances = TechAdvance.getTechAdvances(m_data, techCategory);
final Collection<TechAdvance> playersAdvances =
TechTracker.getCurrentTechAdvances(m_bridge.getPlayerID(), getData());
final List<TechAdvance> available = Util.difference(techCategory.getTechs(), playersAdvances);
return available;
}
public int getTechCost() {
m_techCost = TechTracker.getTechCost(m_player);
return m_techCost;
}
@Override
public Class<ITechDelegate> getRemoteType() {
return ITechDelegate.class;
}
}
class TechnologyExtendedDelegateState implements Serializable {
private static final long serialVersionUID = -1375328472343199099L;
Serializable superState;
// add other variables here:
public boolean m_needToInitialize;
public HashMap<PlayerID, Collection<TechAdvance>> m_techs;
}