Files
forge/src/main/java/forge/game/player/PlayerControllerHuman.java
Maxmtg 5b44514fd4 handleLeylinesAndChancellors - scripted all actions performed from opening hand
now you may execute actions from opening hand in any order (rule 103.5)
2013-06-02 22:21:46 +00:00

566 lines
22 KiB
Java

package forge.game.player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.google.common.collect.Lists;
import forge.Card;
import forge.GameEntity;
import forge.Singletons;
import forge.card.cost.Cost;
import forge.card.mana.Mana;
import forge.card.replacement.ReplacementEffect;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.spellability.TargetSelection;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameType;
import forge.game.phase.PhaseType;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
import forge.gui.GuiUtils;
import forge.gui.input.InputAttack;
import forge.gui.input.InputBlock;
import forge.gui.input.InputConfirmMulligan;
import forge.gui.input.InputPassPriority;
import forge.gui.input.InputPlayOrDraw;
import forge.gui.input.InputSelectCards;
import forge.gui.input.InputSelectCardsFromList;
import forge.gui.input.InputSynchronized;
import forge.gui.match.CMatchUI;
import forge.item.CardPrinted;
import forge.util.TextUtil;
/**
* A prototype for player controller class
*
* Handles phase skips for now.
*/
public class PlayerControllerHuman extends PlayerController {
public PlayerControllerHuman(Game game0, Player p, LobbyPlayer lp) {
super(game0, p, lp);
}
public boolean isUiSetToSkipPhase(final Player turn, final PhaseType phase) {
return !CMatchUI.SINGLETON_INSTANCE.stopAtPhase(turn, phase);
}
/**
* Uses GUI to learn which spell the player (human in our case) would like to play
*/
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities) {
if (abilities.isEmpty()) {
return null;
} else if (abilities.size() == 1) {
return abilities.get(0);
} else {
return GuiChoose.oneOrNone("Choose ability to play", abilities);
}
}
/**
* TODO: Write javadoc for this method.
* @param c
*/
/**public void playFromSuspend(Card c) {
c.setSuspendCast(true);
HumanPlay.playCardWithoutPayingManaCost(player, c);
}**/
/* (non-Javadoc)
* @see forge.game.player.PlayerController#playCascade(java.util.List, forge.Card)
*/
@Override
public boolean playCascade(Card cascadedCard, Card sourceCard) {
final StringBuilder title = new StringBuilder();
title.append(sourceCard.getName()).append(" - Cascade Ability");
final StringBuilder question = new StringBuilder();
question.append("Cast ").append(cascadedCard.getName());
question.append(" without paying its mana cost?");
boolean result = GuiDialog.confirm(cascadedCard, question.toString());
if ( result )
HumanPlay.playCardWithoutPayingManaCost(player, cascadedCard);
return result;
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility)
*/
@Override
public void playSpellAbilityForFree(SpellAbility copySA) {
HumanPlay.playSaWithoutPayingManaCost(player.getGame(), copySA);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#sideboard(forge.deck.Deck)
*/
@Override
public Deck sideboard(Deck deck, GameType gameType) {
CardPool sideboard = deck.get(DeckSection.Sideboard);
CardPool main = deck.get(DeckSection.Main);
int deckMinSize = Math.min(main.countAll(), gameType.getDecksFormat().getMainRange().getMinimumInteger());
CardPool newSb = new CardPool();
List<CardPrinted> newMain = null;
while (newMain == null || newMain.size() < deckMinSize) {
if (newMain != null) {
String errMsg = String.format("Too few cards in your main deck (minimum %d), please make modifications to your deck again.", deckMinSize);
JOptionPane.showMessageDialog(null, errMsg, "Invalid deck", JOptionPane.ERROR_MESSAGE);
}
boolean isLimited = (gameType == GameType.Draft || gameType == GameType.Sealed);
newMain = GuiChoose.sideboard(sideboard.toFlatList(), main.toFlatList(), isLimited);
}
newSb.clear();
newSb.addAll(main);
newSb.addAll(sideboard);
for(CardPrinted c : newMain) {
newSb.remove(c);
}
Deck res = (Deck)deck.copyTo(deck.getName());
res.getMain().clear();
res.getMain().add(newMain);
CardPool resSb = res.getOrCreate(DeckSection.Sideboard);
resSb.clear();
resSb.addAll(newSb);
return res;
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#assignCombatDamage()
*/
@Override
public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender) {
// Attacker is a poor name here, since the creature assigning damage
// could just as easily be the blocker.
Map<Card, Integer> map;
if (defender != null && assignDamageAsIfNotBlocked(attacker)) {
map = new HashMap<Card, Integer>();
map.put(null, damageDealt);
} else {
if ((attacker.hasKeyword("Trample") && defender != null) || (blockers.size() > 1)) {
map = CMatchUI.SINGLETON_INSTANCE.getDamageToAssign(attacker, blockers, damageDealt, defender);
} else {
map = new HashMap<Card, Integer>();
map.put(blockers.get(0), damageDealt);
}
}
return map;
}
private final boolean assignDamageAsIfNotBlocked(Card attacker) {
return attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")
|| (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
&& GuiDialog.confirm(attacker, "Do you want to assign its combat damage as though it weren't blocked?"));
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#announceRequirements(java.lang.String)
*/
@Override
public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) {
List<Object> options = new ArrayList<Object>();
for(int i = canChooseZero ? 0 : 1; i < 10; i++)
options.add(Integer.valueOf(i));
options.add("Other amount");
Object chosen = GuiChoose.oneOrNone("Choose " + announce + " for " + ability.getSourceCard().getName(), options);
if (chosen instanceof Integer || chosen == null)
return (Integer)chosen;
String message = String.format("How much will you announce for %s?%s", announce, canChooseZero ? "" : " (X cannot be 0)");
while(true){
String str = JOptionPane.showInputDialog(null, message, ability.getSourceCard().getName(), JOptionPane.QUESTION_MESSAGE);
if (null == str) return null; // that is 'cancel'
if(StringUtils.isNumeric(str)) {
Integer val = Integer.valueOf(str);
if (val == 0 && canChooseZero || val > 0)
return val;
}
GuiDialog.message("You have to enter a valid number", "Announce value");
}
}
@Override
public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> valid, String message) {
String outerMessage = "Select %d " + message + "(s) to sacrifice";
return choosePermanentsTo(min, max, valid, outerMessage);
}
@Override
public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> valid, String message) {
String outerMessage = "Select %d " + message + "(s) to be destroyed";
return choosePermanentsTo(min, max, valid, outerMessage);
}
private List<Card> choosePermanentsTo(int min, int max, List<Card> valid, String outerMessage) {
max = Math.min(max, valid.size());
if (max <= 0)
return new ArrayList<Card>();
InputSelectCards inp = new InputSelectCardsFromList(min == 0 ? 1 : min, max, valid);
inp.setMessage(outerMessage);
inp.setCancelAllowed(min == 0);
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.hasCancelled() ? Lists.<Card>newArrayList() : inp.getSelected();
}
@Override
public Card chooseSingleCardForEffect(List<Card> options, SpellAbility sa, String title, boolean isOptional) {
// Human is supposed to read the message and understand from it what to choose
if ( isOptional )
return GuiChoose.oneOrNone(title, options);
else
return GuiChoose.one(title, options);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#confirmAction(forge.card.spellability.SpellAbility, java.lang.String, java.lang.String)
*/
@Override
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return GuiDialog.confirm(sa.getSourceCard(), message);
}
@Override
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
return GuiDialog.confirm(hostCard, message);
}
@Override
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
InputPlayOrDraw inp = new InputPlayOrDraw(player, isFirstGame);
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.isPlayingFirst();
}
@Override
public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
GuiUtils.setPanelSelection(attacker);
return GuiChoose.order("Choose Damage Order for " + attacker, "Damaged First", 0, blockers, null, attacker);
}
@Override
public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
GuiUtils.setPanelSelection(blocker);
return GuiChoose.order("Choose Damage Order for " + blocker, "Damaged First", 0, attackers, null, blocker);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List, forge.game.zone.ZoneType, forge.game.player.Player)
*/
@Override
public void reveal(String string, Collection<Card> cards, ZoneType zone, Player owner) {
String message = string;
if ( StringUtils.isBlank(message) )
message = String.format("Looking at %s's %s", owner, zone);
GuiChoose.oneOrNone(message, cards);
}
@Override
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
List<Card> toBottom = null;
List<Card> toTop = null;
if (topN.size() == 1) {
if (willPutCardOnTop(topN.get(0)))
toTop = topN;
else
toBottom = topN;
} else {
toBottom = GuiChoose.order("Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null, null);
topN.removeAll(toBottom);
if ( topN.isEmpty() )
toTop = null;
else if ( topN.size() == 1 )
toTop = topN;
else
toTop = GuiChoose.order("Arrange cards to be put on top of your library", "Cards arranged", 0, topN, null, null);
}
return ImmutablePair.of(toTop, toBottom);
}
@Override
public boolean willPutCardOnTop(Card c) {
return GuiDialog.confirm(c, "Where will you put " + c.getName() + " in your library", new String[]{"Top", "Bottom"} );
}
@Override
public List<Card> orderMoveToZoneList(List<Card> cards, ZoneType destinationZone) {
if (destinationZone == ZoneType.Library) {
return GuiChoose.order("Choose order of cards to put into the library", "Closest to top", 0, cards, null, null);
} else if (destinationZone == ZoneType.Battlefield) {
return GuiChoose.order("Choose order of cards to put onto the battlefield", "Put first", 0, cards, null, null);
} else if (destinationZone == ZoneType.Graveyard) {
return GuiChoose.order("Choose order of cards to put into the graveyard", "Closest to top", 0, cards, null, null);
}
return cards;
}
@Override
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> valid, int min, int max) {
if ( p != player ) {
int cntToKeepInHand = min == 0 ? -1 : valid.size() - min;
return GuiChoose.order("Choose cards to Discard", "Discarded", cntToKeepInHand, valid, null, null);
}
InputSelectCards inp = new InputSelectCardsFromList(min, max, valid);
inp.setMessage("Discard %d cards");
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.getSelected();
}
@Override
public Card chooseCardToDredge(List<Card> dredgers) {
if (GuiDialog.confirm(null, "Do you want to dredge?", false)) {
return GuiChoose.oneOrNone("Select card to dredge", dredgers);
}
return null;
}
@Override
public void playMiracle(SpellAbility miracle, Card card) {
if (GuiDialog.confirm(card, card + " - Drawn. Play for Miracle Cost?")) {
HumanPlay.playSpellAbility(player, miracle);
}
}
@Override
public void playMadness(SpellAbility madness) {
if (GuiDialog.confirm(madness.getSourceCard(), madness.getSourceCard() + " - Discarded. Pay Madness Cost?")) {
HumanPlay.playSpellAbility(player, madness);
}
}
@Override
public List<Card> chooseCardsToDelve(int colorLessAmount, List<Card> grave) {
List<Card> toExile = new ArrayList<Card>();
int cardsInGrave = grave.size();
final Integer[] cntChoice = new Integer[cardsInGrave + 1];
for (int i = 0; i <= cardsInGrave; i++) {
cntChoice[i] = Integer.valueOf(i);
}
final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice);
System.out.println("Delve for " + chosenAmount);
for (int i = 0; i < chosenAmount; i++) {
final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", grave);
if (nowChosen == null) {
// User canceled,abort delving.
toExile.clear();
break;
}
grave.remove(nowChosen);
toExile.add(nowChosen);
}
return toExile;
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
*/
@Override
public Target chooseTargets(SpellAbility ability) {
if (ability.getTarget() == null) {
return null;
}
Target oldTarget = new Target(ability.getTarget());
TargetSelection select = new TargetSelection(ability);
ability.getTarget().resetTargets();
if (select.chooseTargets()) {
return ability.getTarget();
} else {
// Return old target, since we had to reset them above
return oldTarget;
}
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.lang.String, forge.card.spellability.SpellAbility)
*/
@Override
public List<Card> chooseCardsToDiscardUnlessType(int num, List<Card> hand, final String uType, SpellAbility sa) {
final InputSelectCards target = new InputSelectCardsFromList(num, num, hand) {
private static final long serialVersionUID = -5774108410928795591L;
@Override
protected boolean hasAllTargets() {
for(Card c : selected) {
if (c.isType(uType))
return true;
}
return super.hasAllTargets();
}
};
target.setMessage("Select %d cards to discard, unless you discard a " + uType + ".");
Singletons.getControl().getInputQueue().setInputAndWait(target);
return target.getSelected();
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseManaFromPool(java.util.List)
*/
@Override
public Mana chooseManaFromPool(List<Mana> manaChoices) {
List<String> options = new ArrayList<String>();
for(int i = 0; i < manaChoices.size(); i++) {
Mana m = manaChoices.get(i);
options.add(String.format("%d. %s mana from %s", 1+i, m.getColor(), m.getSourceCard() ));
}
String chosen = GuiChoose.one("Pay Mana from Mana Pool", options);
String idx = TextUtil.split(chosen, '.')[0];
return manaChoices.get(Integer.parseInt(idx)-1);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseSomeType(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.String)
*/
@Override
public String chooseSomeType(String kindOfType, String aiLogic, List<String> validTypes, List<String> invalidTypes) {
return GuiChoose.one("Choose a " + kindOfType.toLowerCase() + " type", validTypes);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String)
*/
@Override
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
return GuiDialog.confirm(replacementEffect.getHostCard(), question);
}
@Override
public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
final InputConfirmMulligan inp = new InputConfirmMulligan(player, firstPlayer, isCommander);
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand);
}
@Override
public void declareAttackers(Player attacker) {
game.getCombat().initiatePossibleDefenders(attacker.getOpponents());
// This input should not modify combat object itself, but should return user choice
InputSynchronized inpAttack = new InputAttack(attacker, player, game.getCombat());
Singletons.getControl().getInputQueue().setInputAndWait(inpAttack);
}
@Override
public void declareBlockers(Player defender) {
// This input should not modify combat object itself, but should return user choice
InputSynchronized inpBlock = new InputBlock(player, defender, game.getCombat());
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
}
@Override
public void takePriority() {
PhaseType phase = game.getPhaseHandler().getPhase();
boolean maySkipPriority = mayAutoPass(phase) || isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), phase);
if (game.getStack().isEmpty() && maySkipPriority) {
return;
} else
autoPassCancel(); // probably cancel, since something has happened
SpellAbility chosenSa = null;
do {
if (chosenSa != null) {
HumanPlay.playSpellAbility(player, chosenSa);
}
InputPassPriority defaultInput = new InputPassPriority(player);
Singletons.getControl().getInputQueue().setInputAndWait(defaultInput);
chosenSa = defaultInput.getChosenSa();
} while( chosenSa != null );
}
@Override
public List<Card> chooseCardsToDiscardToMaximumHandSize(int nDiscard) {
final int n = player.getZone(ZoneType.Hand).size();
final int max = player.getMaxHandSize();
InputSelectCardsFromList inp = new InputSelectCardsFromList(nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards());
String msgFmt = "Cleanup Phase: You can only have a maximum of %d cards, you currently have %d cards in your hand - select %d card(s) to discard";
String message = String.format(msgFmt, max, n, nDiscard);
inp.setMessage(message);
inp.setCancelAllowed(false);
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.getSelected();
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseCardsToRevealFromHand(int, int, java.util.List)
*/
@Override
public List<Card> chooseCardsToRevealFromHand(int min, int max, List<Card> valid) {
InputSelectCardsFromList inp = new InputSelectCardsFromList(min, max, valid);
inp.setMessage("Choose Which Cards to Reveal");
Singletons.getControl().getInputQueue().setInputAndWait(inp);
return inp.getSelected();
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#payManaOptional(forge.Card, forge.card.cost.Cost)
*/
@Override
public boolean payManaOptional(Card c, Cost attackCost, String prompt, ManaPaymentPurpose purpose) {
return HumanPlay.payCostDuringAbilityResolve(player, c, attackCost, null);
}
/* (non-Javadoc)
* @see forge.game.player.PlayerController#chooseSaToActivateFromOpeningHand(java.util.List)
*/
@Override
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
List<Card> srcCards = new ArrayList<Card>();
for(SpellAbility sa : usableFromOpeningHand) {
srcCards.add(sa.getSourceCard());
}
List<Card> chosen = GuiChoose.order("Choose cards to activate from opening hand", "Activate first", -1, srcCards, null, null);
List<SpellAbility> result = new ArrayList<SpellAbility>();
for(Card c : chosen) {
for(SpellAbility sa : usableFromOpeningHand) {
if ( sa.getSourceCard() == c ) {
result.add(sa);
break;
}
}
}
return result;
}
}