Prevent race condition when selecting multiple abilities using Shift+click

This commit is contained in:
drdev
2014-11-27 02:05:26 +00:00
parent 2cb2c266b3
commit 4c6bd6a715
9 changed files with 77 additions and 149 deletions

View File

@@ -97,7 +97,6 @@ import forge.util.MyRandom;
* @version $Id$ * @version $Id$
*/ */
public class AiController { public class AiController {
private final Player player; private final Player player;
private final Game game; private final Game game;
private final AiCardMemory memory; private final AiCardMemory memory;
@@ -111,13 +110,11 @@ public class AiController {
this.bCheatShuffle = canCheatShuffle; this.bCheatShuffle = canCheatShuffle;
} }
public Game getGame() public Game getGame() {
{
return game; return game;
} }
public Player getPlayer() public Player getPlayer() {
{
return player; return player;
} }
@@ -125,24 +122,12 @@ public class AiController {
return memory; return memory;
} }
/**
* <p>
* Constructor for ComputerAI_General.
* </p>
*/
public AiController(final Player computerPlayer, final Game game0) { public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer; player = computerPlayer;
game = game0; game = game0;
memory = new AiCardMemory(); memory = new AiCardMemory();
} }
/**
* <p>
* getAvailableSpellAbilities.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private CardCollection getAvailableCards() { private CardCollection getAvailableCards() {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
@@ -158,13 +143,6 @@ public class AiController {
return all; return all;
} }
/**
* <p>
* getPossibleETBCounters.
* </p>
*
* @return a {@link java.util.ArrayList} object.
*/
private ArrayList<SpellAbility> getPossibleETBCounters() { private ArrayList<SpellAbility> getPossibleETBCounters() {
final Player opp = player.getOpponent(); final Player opp = player.getOpponent();
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
@@ -348,13 +326,10 @@ public class AiController {
return false; return false;
} }
} }
return true; return true;
} }
private ArrayList<SpellAbility> getOriginalAndAltCostAbilities(final ArrayList<SpellAbility> originList) {
private ArrayList<SpellAbility> getOriginalAndAltCostAbilities(final ArrayList<SpellAbility> originList)
{
final ArrayList<SpellAbility> newAbilities = new ArrayList<SpellAbility>(); final ArrayList<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
for (SpellAbility sa : originList) { for (SpellAbility sa : originList) {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
@@ -371,13 +346,6 @@ public class AiController {
return result; return result;
} }
/**
* Returns the spellAbilities from the card list.
*
* @param l
* a {@link forge.CardList} object.
* @return an array of {@link forge.game.spellability.SpellAbility} objects.
*/
private ArrayList<SpellAbility> getSpellAbilities(final CardCollectionView l) { private ArrayList<SpellAbility> getSpellAbilities(final CardCollectionView l) {
final ArrayList<SpellAbility> spellAbilities = new ArrayList<SpellAbility>(); final ArrayList<SpellAbility> spellAbilities = new ArrayList<SpellAbility>();
for (final Card c : l) { for (final Card c : l) {
@@ -388,15 +356,6 @@ public class AiController {
return spellAbilities; return spellAbilities;
} }
/**
* <p>
* getPlayableCounters.
* </p>
*
* @param l
* a {@link forge.CardList} object.
* @return a {@link java.util.ArrayList} object.
*/
private ArrayList<SpellAbility> getPlayableCounters(final CardCollection l) { private ArrayList<SpellAbility> getPlayableCounters(final CardCollection l) {
final ArrayList<SpellAbility> spellAbility = new ArrayList<SpellAbility>(); final ArrayList<SpellAbility> spellAbility = new ArrayList<SpellAbility>();
for (final Card c : l) { for (final Card c : l) {
@@ -407,20 +366,11 @@ public class AiController {
} }
} }
} }
return spellAbility; return spellAbility;
} }
// plays a land if one is available // plays a land if one is available
/**
* <p>
* chooseLandsToPlay.
* </p>
*
* @return a boolean.
*/
public CardCollection getLandsToPlay() { public CardCollection getLandsToPlay() {
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand)); final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
hand.addAll(player.getCardsIn(ZoneType.Exile)); hand.addAll(player.getCardsIn(ZoneType.Exile));
CardCollection landList = CardLists.filter(hand, Presets.LANDS); CardCollection landList = CardLists.filter(hand, Presets.LANDS);
@@ -506,15 +456,13 @@ public class AiController {
return true; return true;
} }
}); });
return landList; return landList;
} }
public Card chooseBestLandToPlay(CardCollection landList) public Card chooseBestLandToPlay(CardCollection landList) {
{ if (landList.isEmpty()) {
if (landList.isEmpty())
return null; return null;
}
//Skip reflected lands. //Skip reflected lands.
CardCollection unreflectedLands = new CardCollection(landList); CardCollection unreflectedLands = new CardCollection(landList);
for (Card l : landList) { for (Card l : landList) {
@@ -594,23 +542,13 @@ public class AiController {
} }
// if return true, go to next phase // if return true, go to next phase
/**
* <p>
* playCounterSpell.
* </p>
*
* @param possibleCounters
* a {@link java.util.ArrayList} object.
* @return a boolean.
*/
private SpellAbility chooseCounterSpell(final ArrayList<SpellAbility> possibleCounters) { private SpellAbility chooseCounterSpell(final ArrayList<SpellAbility> possibleCounters) {
if (possibleCounters == null || possibleCounters.isEmpty()) if (possibleCounters == null || possibleCounters.isEmpty()) {
return null;; return null;
}
SpellAbility bestSA = null; SpellAbility bestSA = null;
int bestRestriction = Integer.MIN_VALUE; int bestRestriction = Integer.MIN_VALUE;
for (final SpellAbility sa : getOriginalAndAltCostAbilities(possibleCounters)) { for (final SpellAbility sa : getOriginalAndAltCostAbilities(possibleCounters)) {
SpellAbility currentSA = sa; SpellAbility currentSA = sa;
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
@@ -639,7 +577,7 @@ public class AiController {
// TODO - "Look" at Targeted SA and "calculate" the threshold // TODO - "Look" at Targeted SA and "calculate" the threshold
// if (bestRestriction < targetedThreshold) return false; // if (bestRestriction < targetedThreshold) return false;
return bestSA; return bestSA;
} // playCounterSpell() }
public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) { public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
return predictSpellToCastInMain2(exceptSA, true); return predictSpellToCastInMain2(exceptSA, true);
@@ -669,7 +607,6 @@ public class AiController {
} }
} }
} }
return null; return null;
} }
@@ -701,9 +638,11 @@ public class AiController {
} }
if (sa.getApi() != null) { if (sa.getApi() != null) {
boolean canPlay = SpellApiToAi.Converter.get(sa.getApi()).canPlayAIWithSubs(player, sa); boolean canPlay = SpellApiToAi.Converter.get(sa.getApi()).canPlayAIWithSubs(player, sa);
if (!canPlay) if (!canPlay) {
return AiPlayDecision.CantPlayAi; return AiPlayDecision.CantPlayAi;
} else if (sa.getPayCosts() != null){ }
}
else if (sa.getPayCosts() != null){
Cost payCosts = sa.getPayCosts(); Cost payCosts = sa.getPayCosts();
ManaCost mana = payCosts.getTotalMana(); ManaCost mana = payCosts.getTotalMana();
if (mana!= null && mana.countX() > 0) { if (mana!= null && mana.countX() > 0) {
@@ -796,9 +735,9 @@ public class AiController {
} }
} }
} }
return canPlayFromEffectAI((SpellPermanent)sa, false, true); return canPlayFromEffectAI((SpellPermanent)sa, false, true);
} else if (sa instanceof Spell) { }
if (sa instanceof Spell) {
return canPlaySpellBasic(card); return canPlaySpellBasic(card);
} }
return AiPlayDecision.WillPlay; return AiPlayDecision.WillPlay;
@@ -901,22 +840,8 @@ public class AiController {
return p; return p;
} }
}; // Comparator };
/**
* <p>
* AI_discardNumType.
* </p>
*
* @param numDiscard
* a int.
* @param uTypes
* an array of {@link java.lang.String} objects. May be null for
* no restrictions.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a CardCollection of discarded cards.
*/
public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) { public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) {
CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
if ((uTypes != null) && (sa != null)) { if ((uTypes != null) && (sa != null)) {
@@ -981,7 +906,8 @@ public class AiController {
if (canDiscardLands) { if (canDiscardLands) {
discardList.add(landsInHand.get(0)); discardList.add(landsInHand.get(0));
validCards.remove(landsInHand.get(0)); validCards.remove(landsInHand.get(0));
} else { // Discard other stuff }
else { // Discard other stuff
CardLists.sortByCmcDesc(validCards); CardLists.sortByCmcDesc(validCards);
int numLandsAvailable = numLandsInPlay; int numLandsAvailable = numLandsInPlay;
if (numLandsInHand > 0) { if (numLandsInHand > 0) {
@@ -991,7 +917,8 @@ public class AiController {
if (validCards.get(0).getCMC() > numLandsAvailable) { if (validCards.get(0).getCMC() > numLandsAvailable) {
discardList.add(validCards.get(0)); discardList.add(validCards.get(0));
validCards.remove(validCards.get(0)); validCards.remove(validCards.get(0));
} else { //Discard worst card }
else { //Discard worst card
Card worst = ComputerUtilCard.getWorstAI(validCards); Card worst = ComputerUtilCard.getWorstAI(validCards);
discardList.add(worst); discardList.add(worst);
validCards.remove(worst); validCards.remove(worst);
@@ -1006,41 +933,37 @@ public class AiController {
ApiType api = sa.getApi(); ApiType api = sa.getApi();
// Abilities without api may also use this routine, However they should provide a unique mode value // Abilities without api may also use this routine, However they should provide a unique mode value
if (null == api) { if (api == null) {
if (mode != null) switch (mode) { if (mode != null) switch (mode) {
// case BraidOfFire: return true; // case BraidOfFire: return true;
// case Ripple: return true; // case Ripple: return true;
} }
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode); String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode);
throw new IllegalArgumentException(exMsg); throw new IllegalArgumentException(exMsg);
}
} else
return SpellApiToAi.Converter.get(api).confirmAction(player, sa, mode, message); return SpellApiToAi.Converter.get(api).confirmAction(player, sa, mode, message);
} }
public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, public boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, int bid, Player winner) {
int bid, Player winner) {
if (mode != null) switch (mode) { if (mode != null) switch (mode) {
case BidLife: case BidLife:
if (sa.hasParam("AIBidMax")) { if (sa.hasParam("AIBidMax")) {
return !player.equals(winner) && bid < Integer.parseInt(sa.getParam("AIBidMax")) && player.getLife() > bid + 5; return !player.equals(winner) && bid < Integer.parseInt(sa.getParam("AIBidMax")) && player.getLife() > bid + 5;
} else {
return false;
} }
return false;
default: default:
return false; return false;
} }
return false; return false;
} }
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
if (logic.equalsIgnoreCase("ProtectFriendly")) { if (logic.equalsIgnoreCase("ProtectFriendly")) {
final Player controller = hostCard.getController(); final Player controller = hostCard.getController();
if (affected instanceof Player) { if (affected instanceof Player) {
return !((Player) affected).isOpponentOf(controller); return !((Player) affected).isOpponentOf(controller);
} else if (affected instanceof Card) { }
if (affected instanceof Card) {
return !((Card) affected).getController().isOpponentOf(controller); return !((Card) affected).getController().isOpponentOf(controller);
} }
} }
@@ -1084,28 +1007,30 @@ public class AiController {
if (AiPlayDecision.WillPlay != canPlayFromEffectAI((Spell) sa, mandatory, withoutPayingManaCost)) { if (AiPlayDecision.WillPlay != canPlayFromEffectAI((Spell) sa, mandatory, withoutPayingManaCost)) {
continue; continue;
} }
} else { }
else {
if (AiPlayDecision.WillPlay == canPlaySa(sa)) { if (AiPlayDecision.WillPlay == canPlaySa(sa)) {
continue; continue;
} }
} }
if (withoutPayingManaCost) if (withoutPayingManaCost) {
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, sa, game); ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, sa, game);
else if (!ComputerUtilCost.canPayCost(sa, player)) }
else if (!ComputerUtilCost.canPayCost(sa, player)) {
continue; continue;
else }
else {
ComputerUtil.playStack(sa, player, game); ComputerUtil.playStack(sa, player, game);
}
return sa; return sa;
} }
return null; return null;
} }
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) { public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
final Card card = spell.getHostCard(); final Card card = spell.getHostCard();
if (spell instanceof SpellApiBased) if (spell instanceof SpellApiBased) {
{
boolean chance = false; boolean chance = false;
if (withoutPayingManaCost) { if (withoutPayingManaCost) {
chance = SpellApiToAi.Converter.get(spell.getApi()).doTriggerNoCostWithSubs(player, spell, mandatory); chance = SpellApiToAi.Converter.get(spell.getApi()).doTriggerNoCostWithSubs(player, spell, mandatory);
@@ -1184,7 +1109,7 @@ public class AiController {
return canPlaySpellBasic(card); return canPlaySpellBasic(card);
} }
public SpellAbility chooseSpellAbilityToPlay() { public List<SpellAbility> chooseSpellAbilityToPlay() {
final PhaseType phase = game.getPhaseHandler().getPhase(); final PhaseType phase = game.getPhaseHandler().getPhase();
if (game.getStack().isEmpty() && phase.isMain()) { if (game.getStack().isEmpty() && phase.isMain()) {
@@ -1194,14 +1119,21 @@ public class AiController {
Card land = chooseBestLandToPlay(landsWannaPlay); Card land = chooseBestLandToPlay(landsWannaPlay);
if (ComputerUtil.damageFromETB(player, land) < player.getLife() || !player.canLoseLife()) { if (ComputerUtil.damageFromETB(player, land) < player.getLife() || !player.canLoseLife()) {
Ability.PLAY_LAND_SURROGATE.setHostCard(land); Ability.PLAY_LAND_SURROGATE.setHostCard(land);
return Ability.PLAY_LAND_SURROGATE; final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
abilities.add(Ability.PLAY_LAND_SURROGATE);
return abilities;
} }
} }
} }
SpellAbility sa = getSpellAbilityToPlay(); SpellAbility sa = getSpellAbilityToPlay();
if (sa == null) { return null; }
// System.out.println("Chosen to play: " + sa); // System.out.println("Chosen to play: " + sa);
return sa;
final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
abilities.add(sa);
return abilities;
} }
// declares blockers for given defender in a given combat // declares blockers for given defender in a given combat

View File

@@ -381,7 +381,7 @@ public class PlayerControllerAi extends PlayerController {
} }
@Override @Override
public SpellAbility chooseSpellAbilityToPlay() { public List<SpellAbility> chooseSpellAbilityToPlay() {
return brains.chooseSpellAbilityToPlay(); return brains.chooseSpellAbilityToPlay();
} }

View File

@@ -19,6 +19,7 @@ package forge.game.phase;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.*; import forge.game.*;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
@@ -881,7 +882,7 @@ public class PhaseHandler implements java.io.Serializable {
} }
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer())); game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
SpellAbility chosenSa = null; List<SpellAbility> chosenSa = null;
int loopCount = 0; int loopCount = 0;
do { do {
@@ -913,7 +914,9 @@ public class PhaseHandler implements java.io.Serializable {
System.out.print("... " + pPlayerPriority + " plays " + chosenSa); System.out.print("... " + pPlayerPriority + " plays " + chosenSa);
} }
pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve
pPlayerPriority.getController().playChosenSpellAbility(chosenSa); for (SpellAbility sa : chosenSa) {
pPlayerPriority.getController().playChosenSpellAbility(sa);
}
loopCount++; loopCount++;
} while (chosenSa != null && (loopCount < 999 || !pPlayerPriority.getController().isAI())); } while (chosenSa != null && (loopCount < 999 || !pPlayerPriority.getController().isAI()));

View File

@@ -233,7 +233,7 @@ public abstract class PlayerController {
public abstract void declareAttackers(Player attacker, Combat combat); public abstract void declareAttackers(Player attacker, Combat combat);
public abstract void declareBlockers(Player defender, Combat combat); public abstract void declareBlockers(Player defender, Combat combat);
public abstract SpellAbility chooseSpellAbilityToPlay(); public abstract List<SpellAbility> chooseSpellAbilityToPlay();
public abstract void playChosenSpellAbility(SpellAbility sa); public abstract void playChosenSpellAbility(SpellAbility sa);
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard); public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);

View File

@@ -526,7 +526,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
@Override @Override
public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) { public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) {
selectCard(panel, new MouseTriggerEvent(evt), false); selectCard(panel, new MouseTriggerEvent(evt), evt.isShiftDown()); //select entire stack if shift key down
super.mouseRightClicked(panel, evt); super.mouseRightClicked(panel, evt);
} }

View File

@@ -378,7 +378,7 @@ public class PlayerControllerForTests extends PlayerController {
} }
@Override @Override
public SpellAbility chooseSpellAbilityToPlay() { public List<SpellAbility> chooseSpellAbilityToPlay() {
//TODO: This method has to return the spellability chosen by player //TODO: This method has to return the spellability chosen by player
// It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface // It should not play the sa right from here. The code has been left as it is to quickly adapt to changed playercontroller interface
if (playerActions != null) { if (playerActions != null) {

View File

@@ -45,7 +45,7 @@ public class InputPassPriority extends InputSyncronizedBase {
private static final long serialVersionUID = -581477682214137181L; private static final long serialVersionUID = -581477682214137181L;
private final Player player; private final Player player;
private SpellAbility chosenSa; private List<SpellAbility> chosenSa;
public InputPassPriority(final PlayerControllerHuman controller, final Player human) { public InputPassPriority(final PlayerControllerHuman controller, final Player human) {
super(controller); super(controller);
@@ -122,7 +122,7 @@ public class InputPassPriority extends InputSyncronizedBase {
runnable.run(); //just pass priority immediately if no mana floating that would be lost runnable.run(); //just pass priority immediately if no mana floating that would be lost
} }
public SpellAbility getChosenSa() { return chosenSa; } public List<SpellAbility> getChosenSa() { return chosenSa; }
@Override @Override
protected boolean onCardSelected(final Card card, final List<Card> otherCardsToSelect, final ITriggerEvent triggerEvent) { protected boolean onCardSelected(final Card card, final List<Card> otherCardsToSelect, final ITriggerEvent triggerEvent) {
@@ -133,30 +133,22 @@ public class InputPassPriority extends InputSyncronizedBase {
} }
final SpellAbility ability = player.getController().getAbilityToPlay(abilities, triggerEvent); final SpellAbility ability = player.getController().getAbilityToPlay(abilities, triggerEvent);
if (selectAbility(ability)) { if (ability != null) {
chosenSa = new ArrayList<SpellAbility>();
chosenSa.add(ability);
if (otherCardsToSelect != null && ability.isManaAbility()) { if (otherCardsToSelect != null && ability.isManaAbility()) {
//if mana ability activated, activate same ability on other cards to select if possible //if mana ability activated, activate same ability on other cards to select if possible
String abStr = ability.toUnsuppressedString(); String abStr = ability.toUnsuppressedString();
final List<SpellAbility> otherAbilitiesToPlay = new ArrayList<SpellAbility>();
for (Card c : otherCardsToSelect) { for (Card c : otherCardsToSelect) {
for (SpellAbility ab : c.getAllPossibleAbilities(player, true)) { for (SpellAbility ab : c.getAllPossibleAbilities(player, true)) {
if (ab.toUnsuppressedString().equals(abStr)) { if (ab.toUnsuppressedString().equals(abStr)) {
otherAbilitiesToPlay.add(ab); chosenSa.add(ab);
break; break;
} }
} }
} }
if (otherAbilitiesToPlay.size() > 0) {
ThreadUtil.invokeInGameThread(new Runnable() { //must execute other abilities on game thread
@Override
public void run() {
for (SpellAbility ab : otherAbilitiesToPlay) {
player.getController().playChosenSpellAbility(ab);
}
}
});
}
} }
stop();
return true; return true;
} }
return false; return false;
@@ -165,7 +157,8 @@ public class InputPassPriority extends InputSyncronizedBase {
@Override @Override
public boolean selectAbility(final SpellAbility ab) { public boolean selectAbility(final SpellAbility ab) {
if (ab != null) { if (ab != null) {
chosenSa = ab; chosenSa = new ArrayList<SpellAbility>();
chosenSa.add(ab);
stop(); stop();
return true; return true;
} }

View File

@@ -834,7 +834,7 @@ public class PlayerControllerHuman extends PlayerController {
} }
@Override @Override
public SpellAbility chooseSpellAbilityToPlay() { public List<SpellAbility> chooseSpellAbilityToPlay() {
MagicStack stack = game.getStack(); MagicStack stack = game.getStack();
if (mayAutoPass()) { if (mayAutoPass()) {