- Started working on the full split card face creation in CardFactory (mostly works, but some parts need modification to work correctly).

- Merge: merge the latest trunk into Splitcards.
This commit is contained in:
Agetian
2013-03-03 06:08:40 +00:00
28 changed files with 359 additions and 336 deletions

2
.gitattributes vendored
View File

@@ -14220,9 +14220,9 @@ src/main/java/forge/gui/GuiChoose.java -text
src/main/java/forge/gui/GuiDialog.java -text
src/main/java/forge/gui/GuiDisplayUtil.java svneol=native#text/plain
src/main/java/forge/gui/GuiImportPicture.java svneol=native#text/plain
src/main/java/forge/gui/GuiInput.java svneol=native#text/plain
src/main/java/forge/gui/GuiProgressBarWindow.java svneol=native#text/plain
src/main/java/forge/gui/GuiUtils.java svneol=native#text/plain
src/main/java/forge/gui/InputProxy.java svneol=native#text/plain
src/main/java/forge/gui/ListChooser.java svneol=native#text/plain
src/main/java/forge/gui/MultiLineLabel.java svneol=native#text/plain
src/main/java/forge/gui/MultiLineLabelUI.java svneol=native#text/plain

View File

@@ -1,217 +1,36 @@
Forge Beta: 03-01-2013 ver 1.3.9
Forge Beta: 03-##-2013 ver 1.3.10
12193 cards in total.
12### cards in total.
Release Notes:
You can now cast cards for it's miracle cost without getting a crash. Blocking with a creature was not resetting after combat and this would prevent this creature from blocking in subsequent turns, now fixed.
Work continues on the quest worlds format and a new world based on Ravnica is coming along nicely.
Added a 'copy to clipboard' button on WinLose screen so players can easily copy the game log.
Find-as-you-type is now implemented for Deck Editor tables. Just start typing while the table has focus and the next card with a matching string in its name will be highlighted. If more than one card matches, hit Enter to select the next matching card. A popup panel will appear with the search string so you know what you are searching for. If no cards match the string, the string will be highlighted in red. Normally, if you hit the spacebar while a card is selected in one table, the card will be moved to the other table (catalog/deck). When the popup is displayed, space characters are interpreted as part of the search string. Find-as-you-type mode is automatically exited after 5 seconds of inactivity, or hit Escape to exit find-as-you-type mode immediately.
The Deck Editor has also gained some hotkey and context menu abilities. R-click on a card (or a group of selected cards) for a list of actions and keyboard shortcuts. In particular, you can now transfer cards 4 at a time using the keyboard and interact with the sideboard from anywhere. Also remember that you can jump to the other table with the arrow keys and jump to the text filter with ctrl/cmd+f. From the text filter, you can jump down to the tables by pressing enter.
In recent weeks people had noticed that the computer was picking the weakest cards in Draft mode rather than the strongest cards. This left the AI with a draft mode deck that was suboptimal. The computer should now pick the strongest cards rather than the weakest cards.
Work was also done on making the UI more keyboard-friendly. For example, the OK button should now stay focused during matches, so you can advance through the stages by hitting Enter without having to go over and click the button all the time. If you find the button is losing focus, please report it as a bug.
Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards).
The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator.
All Traditional sets are now up to 85% complete. Standard Format is supported at 99.19%. We are now at under 800 unsupported cards that are missing from Forge.
A person reported "Love the game but I seem to be having a problem using a draft pool to start a quest. It works for sealed for me but when I select the draft deck option it's always blank even if I have one or several drafts completed." This should now be fixed and draft decks should now show up in quest start combobox.
Several of the exiting sound files were changed and a handful of new sounds were added to the /res/sound/ folder.
Our snapshot and beta releases should now display the correct SVN revision number in the title bar. This should allow people to file a bug report with the correct SVN revision number.
New Cards:
Archery Training
Aurelia's Fury
Aven Shrine
Barrin's Spite
Battletide Alchemist
Bioshift
Blind Seer
Blinding Powder
Bloom Tender
Bomb Squad
Bounty of the Hunt
Builder's Bane
Cabal Shrine
Cannibalize
Cephalid Shrine
Chant of Vitu-Ghazi
Chaoslace
Charm Peddler
Circle of Despair
Cleansing Meditation
Common Cause
Conflagrate
Conjurer's Ban
Cornered Market
Covenant of Minds
Crashing Boars
Crush Underfoot
Cryptic Gateway
Deathlace
Deepwood Elder
Desecrator Hag
Disruption Aura
Duplicity
Dwarven Shrine
Eight-and-a-Half-Tails
Embolden
Endemic Plague
Epochrasite
Ersatz Gnomes
Eye for an Eye
Eye of Singularity
Eye of Yawgmoth
Feint
Fiery Bombardment
Fiery Justice
Fire and Brimstone
Fire Covenant
Flash
Flickerform
Forbidden Crypt
Forked Lightning
Frostwielder
Game Preserve
Gargantuan Gorilla
Ghosts of the Innocent
Glamer Spinners
Guard Dogs
Hail of Arrows
Heartseeker
Heroic Defiance
Hint of Insanity
Holistic Wisdom
Infectious Rage
Infernal Harvest
Invoke Prejudice
Jaded Response
Jaws of Stone
Killing Glare
Knollspine Invocation
Knowledge Exploitation
Kumano's Blessing
Kumano's Pupils
Kumano, Master Yamabushi
Leonin Bola
Library of Leng
Lifelace
Light from Within
Lightning Dart
Living Inferno
Magmatic Core
Mana Vapors
Marble Priest
Mark of Eviction
Martyr's Cause
Memory Crystal
Meteor Shower
Mirror Golem
Mist of Stagnation
Mist of Stagnation
Moonlace
Moonring Mirror
Nantuko Shrine
Not of This World
Pendrell Flux
Phosphorescent Feast
Phyrexian Purge
Pious Kitsune
Plague Boiler
Planeswalker's Mischief
Pledge of Loyalty
Pollen Remedy
Protean Hulk
Psychic Allergy
Purelace
Purgatory
Quenchable Fire
Rally the Horde
Razia's Purification
Razor Boomerang
Reincarnation
Reins of the Vinesteed
Remedy
Retribution
Reverent Mantra
Reviving Vapors
Reweave
Rite of Ruin
Rock Slide
Rolling Thunder
Sabertooth Cobra
Samite Elder
Sapphire Drake
Searing Rays
Serra's Hymn
Shambling Swarm
Shared Animosity
Shuriken
Simic Guildmage
Sphinx Ambassador
Spike Cannibal
Spoils of War
Struggle for Sanity
Sunforger
Surestrike Trident
Tainted Pact
Takeno, Samurai General
Talara's Bane
Talruum Piper
Temporal Extortion
Thelon of Havenwood
Thought Gorger
Thoughtlace
Thran Tome
Unforge
Vodalian Mystic
Volcanic Wind
Warren Weirding
Winnow
Worldpurge
Dimensional Breach
Lim-Dul's Vault
Eureka
Hypergenesis
New Phenomenons:
Chaotic AEther
Planewide Disaster
New Planes:
Aretopolis
Undercity Reaches
Feeding Grounds
Horizon Boughs
New Vanguard Avatars:
Arcanis the Omnipotent Avatar
Bosh, Iron Golem Avatar
Figure of Destiny Avatar
Haakon Stromgald Scourge Avatar
Jaya Ballard Avatar
Maro Avatar
Master of the Wild Hunt Avatar
Necropotence Avatar
Sisters of Stone Death Avatar
Stuffy Doll Avatar
Two Headed Giant of Foriys Avatar
Vampire Nocturnus Avatar
Viridian Zealot Avatar
Known Issues:
@@ -222,8 +41,6 @@ Several people have noticed that the cards displayed on the battlefield will fai
Some time was spent turning the static ETB triggers into the proper ETB replacement effects they should be, mainly to interact correctly with each other. This work is not yet finished. As a result there is currently some inconsistencies with "Enters the battlefield with counters" (Not incredibly noticeable).
It seems like the front face of double faced cards aren't triggering properly, but the back face and single faced cards are.
A recent contribution to the code base should fix some of the bugs that people noticed with cloning type abilities. At this time there are two remaining issues that we hope will be addressed in the near future.
1. Leave play triggers don't work correct for clones.
@@ -240,21 +57,7 @@ Some people use the Windows application 7zip. This utility can be found at http:
Contributors to This Release:
Agetian
Asepetci
Foreroes
Gos
Hellfish
Marc
Max
Myk
Rooger
RumbleBBU
Serrasmurf
Sloth
Sol
Swordshine
Chris H
(Quest icons used created by Teekatas, from his Legendora set http://raindropmemory.deviantart.com)

View File

@@ -496,6 +496,27 @@ The "Full catalog view" button appears to the left of the "Buy Card" button. Tog
Multibuy: By selecting any number of items and hitting space (or selecting the "Buy Card" or "Sell Card" buttons), a player can buy one of everything selected.
Find-as-you-type is now implemented for Deck Editor tables. Just start typing while the table has focus and the next card with a matching string in its name will be highlighted. If more than one card matches, hit Enter to select the next matching card. A popup panel will appear with the search string so you know what you are searching for. If no cards match the string, the string will be highlighted in red. Normally, if you hit the spacebar while a card is selected in one table, the card will be moved to the other table (catalog/deck). When the popup is displayed, space characters are interpreted as part of the search string. Find-as-you-type mode is automatically exited after 5 seconds of inactivity, or hit Escape to exit find-as-you-type mode immediately.
The Deck Editor has also gained some hotkey and context menu abilities. R-click on a card (or a group of selected cards) for a list of actions and keyboard shortcuts. In particular, you can now transfer cards 4 at a time using the keyboard and interact with the sideboard from anywhere. Also remember that you can jump to the other table with the arrow keys and jump to the text filter with ctrl/cmd+f. From the text filter, you can jump down to the tables by pressing enter.
The Game Log:
Added a 'copy to clipboard' button on WinLose screen so players can easily copy the game log.
The UI more keyboard-friendly:
Work was also done on making the UI more keyboard-friendly. For example, the OK button should now stay focused during matches, so you can advance through the stages by hitting Enter without having to go over and click the button all the time. If you find the button is losing focus, please report it as a bug.
Gatecrash Guild Sealed game mode:
Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards).
The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator.
Our Lawyers Made Us Do This:

View File

@@ -4,7 +4,7 @@ Types:Instant
A:SP$ Charm | Cost$ R G | Choices$ CantBlockEffect,DBGainCtrl,DmgAll | CharmNum$ 1 | SpellDescription$ Choose one - Creatures without flying can't block this turn; or gain control of all permanents you own; or Gruul Charm deals 3 damage to each creature with flying.
SVar:CantBlockEffect:DB$ Effect | Name$ Gruul Charm Effect | StaticAbilities$ KWPump | AILogic$ Evasion | SpellDescription$ Creatures without flying can't block this turn.
SVar:KWPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.withoutFlying | AddHiddenKeyword$ CARDNAME can't block. | Description$ Creatures without flying can't block this turn.
SVar:DBGainCtrl:DB$ GainControl | Cost$ R | AllValid$ Permanent.YouOwn | SpellDescription$ Gain control of all permanents you own.
SVar:DBGainCtrl:DB$ GainControl | AllValid$ Permanent.YouOwn | SpellDescription$ Gain control of all permanents you own.
SVar:DmgAll:DB$ DamageAll | NumDmg$ 3 | ValidCards$ Creature.withFlying | ValidDescription$ each creature with flying. | SpellDescription$ CARDNAME deals 3 damage to each creature with flying.
SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/gruul_charm.jpg

View File

@@ -2,7 +2,6 @@ Name:Joven's Tools
ManaCost:6
Types:Artifact
A:AB$ Pump | Cost$ 4 T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't be blocked except by Walls. | SpellDescription$ Target creature can't be blocked this turn except by Walls.
SVar:RemRandomDeck:True
SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/jovens_tools.jpg
SetInfo:HML|Uncommon|http://magiccards.info/scans/en/hl/133.jpg

View File

@@ -2,7 +2,7 @@ Name:Varchild's Crusader
ManaCost:3 R
Types:Creature Human Knight
PT:3/2
A:AB$ Pump | Cost$ 0 | KW$ CARDNAME can't be blocked except by Walls. & HIDDEN At the beginning of the end step, sacrifice CARDNAME. | SpellDescription$ CARDNAME can't be blocked this turn except by Walls. Sacrifice CARDNAME at the beginning of the next end step.
A:AB$ Pump | Cost$ 0 | KW$ HIDDEN CARDNAME can't be blocked except by Walls. & HIDDEN At the beginning of the end step, sacrifice CARDNAME. | SpellDescription$ CARDNAME can't be blocked this turn except by Walls. Sacrifice CARDNAME at the beginning of the next end step.
SVar:RemAIDeck:True
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/varchilds_crusader.jpg

View File

@@ -1110,6 +1110,8 @@ public class AbilityUtils {
if (paid) {
unpaidCommand = paidCommand;
}
ability.setActivatingPlayer(payer);
ability.setTarget(sa.getTarget());
GameActionUtil.payCostDuringAbilityResolve(payer, ability, cost, paidCommand, unpaidCommand, sa, game);
waitForInput = true; // wait for the human input
break; // multiple human players are not supported

View File

@@ -25,6 +25,7 @@ import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.staticability.StaticAbility;
import forge.game.ai.ComputerUtilCard;
import forge.game.ai.ComputerUtilCost;
import forge.game.ai.ComputerUtilMana;
import forge.game.phase.CombatUtil;
import forge.game.phase.PhaseHandler;
@@ -46,7 +47,13 @@ public class AttachAi extends SpellAbilityAi {
final Card source = sa.getSourceCard();
if (abCost != null) {
// No Aura spells have Additional Costs
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
return false;
}
}
// prevent run-away activations - first time will always return true
@@ -991,10 +998,12 @@ public class AttachAi extends SpellAbilityAi {
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
}
final boolean evasive = (keyword.endsWith("Unblockable") || keyword.equals("Fear")
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|| keyword.endsWith("walk"));
|| keyword.endsWith("walk") || keyword.equals("CARDNAME can't be blocked except by Walls.")
|| keyword.equals("All creatures able to block CARDNAME do so.")
|| keyword.equals("CARDNAME can't be blocked by more than one creature."));
// give evasive keywords to creatures that can attack and deal damage
if (evasive) {
if (card.getNetCombatDamage() <= 0

View File

@@ -77,6 +77,13 @@ public class ControlGainAi extends SpellAbilityAi {
// if Defined, then don't worry about targeting
if (tgt == null) {
if (sa.hasParam("AllValid")) {
List<Card> tgtCards = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
tgt.resetTargets();

View File

@@ -91,7 +91,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
int minGain = 200; // The minimum gain in destroyed creatures
if (sa.getPayCosts().isReusuableResource()) {
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
minGain = 100;
}

View File

@@ -166,7 +166,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final boolean evasive = (keyword.endsWith("Unblockable") || keyword.endsWith("Fear")
|| keyword.endsWith("Intimidate") || keyword.endsWith("Shadow")
|| keyword.contains("CantBeBlockedBy"));
|| keyword.contains("CantBeBlockedBy") || keyword.endsWith("CARDNAME can't be blocked except by Walls."));
final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido"));
// give evasive keywords to creatures that can or do attack
if (evasive) {

View File

@@ -48,7 +48,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
playerLibraries.put(p, newLibrary);
}
GameNew.restartGame(game, sa.getActivatingPlayer(), playerLibraries);
GameNew.restartGame(Singletons.getModel().getMatch(), game, sa.getActivatingPlayer(), playerLibraries);
}
/* (non-Javadoc)

View File

@@ -32,6 +32,7 @@ import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.card.ICardFace;
import forge.card.cost.Cost;
import forge.card.mana.ManaCost;
import forge.card.replacement.ReplacementHandler;
import forge.card.spellability.AbilityActivated;
import forge.card.spellability.SpellAbility;
@@ -359,7 +360,31 @@ public class CardFactory {
}
if ( st == CardSplitType.Split ) {
card.setName(rules.getName());
// BUILD COMBINED 'Original' SIDE HERE
// Combined mana cost
ManaCost combinedManaCost = ManaCost.combine(rules.getMainPart().getManaCost(), rules.getOtherPart().getManaCost());
card.setManaCost(combinedManaCost);
// Combined card color
CardColor combinedCardColor = new CardColor(card);
combinedCardColor.addToCardColor(Color.fromColorSet(rules.getMainPart().getColor()));
combinedCardColor.addToCardColor(Color.fromColorSet(rules.getOtherPart().getColor()));
ArrayList<CardColor> combinedCardColorArr = new ArrayList<CardColor>();
combinedCardColorArr.add(combinedCardColor);
card.setColor(combinedCardColorArr);
// Combined abilities -- DOESN'T WORK AS DESIRED (?)
for (String a : rules.getMainPart().getAbilities()) {
card.addIntrinsicAbility(a);
}
for (String a : rules.getOtherPart().getAbilities()) {
card.addIntrinsicAbility(a);
}
// Combined text -- CURRENTLY TAKES ORACLE TEXT BECAUSE THE ABILITY TEXT DOESN'T WORK (?)
String combinedText = String.format("%s: %s\n%s: %s", rules.getMainPart().getName(), rules.getMainPart().getOracleText(), rules.getOtherPart().getName(), rules.getOtherPart().getOracleText());
card.setText(combinedText);
}
return card;

View File

@@ -3,10 +3,15 @@ package forge.card.cardfactory;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import forge.Card;
import forge.Command;
import forge.Singletons;
import forge.card.cost.Cost;
import forge.card.mana.ManaCost;
import forge.card.spellability.Ability;
import forge.card.spellability.AbilityActivated;
import forge.card.spellability.Target;
import forge.control.input.Input;
@@ -193,5 +198,126 @@ class CardFactoryArtifacts {
ability.setStackDescription(sbStack.toString());
card.addSpellAbility(ability);
} // *************** END ************ END **************************
// *************** START *********** START **************************
else if (cardName.equals("Temporal Aperture")) {
/*
* 5, Tap: Shuffle your library, then reveal the top card. Until end
* of turn, for as long as that card remains on top of your library,
* play with the top card of your library revealed and you may play
* that card without paying its mana cost. (If it has X in its mana
* cost, X is 0.)
*/
final Card[] topCard = new Card[1];
final Ability freeCast = new Ability(card, ManaCost.ZERO) {
@Override
public boolean canPlay() {
final PlayerZone lib = card.getController().getZone(ZoneType.Library);
return super.canPlay() && ((lib.size() > 0) && lib.get(0).equals(topCard[0]));
}
@Override
public void resolve() {
final Card freeCard = topCard[0];
final Player player = card.getController();
if (freeCard != null) {
if (freeCard.isLand()) {
if (player.canPlayLand(freeCard)) {
player.playLand(freeCard);
} else {
JOptionPane.showMessageDialog(null, "You can't play any more lands this turn.", "",
JOptionPane.INFORMATION_MESSAGE);
}
} else {
Singletons.getModel().getGame().getActionPlay().playCardWithoutManaCost(freeCard, player);
}
} else {
final StringBuilder sb = new StringBuilder();
sb.append("Error in ").append(cardName).append(". freeCard is null");
JOptionPane.showMessageDialog(null, sb.toString(), "", JOptionPane.INFORMATION_MESSAGE);
}
}
@Override
public boolean canPlayAI() {
return false;
}
};
freeCast.setDescription("Play the previously revealed top card of your library for free.");
final StringBuilder sb = new StringBuilder();
sb.append(cardName).append(" - play card without paying its mana cost.");
freeCast.setStackDescription(sb.toString());
class AbilityTemporalAperture extends AbilityActivated {
public AbilityTemporalAperture(final Card ca, final Cost co, final Target t) {
super(ca, co, t);
}
@Override
public AbilityActivated getCopy() {
AbilityActivated res = new AbilityTemporalAperture(getSourceCard(),
getPayCosts(), getTarget() == null ? null : new Target(getTarget()));
CardFactoryUtil.copySpellAbility(this, res);
return res;
}
private static final long serialVersionUID = -7328518969488588777L;
@Override
public void resolve() {
final PlayerZone lib = card.getController().getZone(ZoneType.Library);
if (lib.size() > 0) {
// shuffle your library
card.getController().shuffle();
// reveal the top card
topCard[0] = lib.get(0);
final StringBuilder sb = new StringBuilder();
sb.append("Revealed card:\n").append(topCard[0].getName());
JOptionPane.showMessageDialog(null, sb.toString(), card.getName(), JOptionPane.PLAIN_MESSAGE);
card.addSpellAbility(freeCast);
card.addExtrinsicKeyword("Play with the top card of your library revealed.");
Singletons.getModel().getGame().getEndOfTurn().addUntil(new Command() {
private static final long serialVersionUID = -2860753262177388046L;
@Override
public void execute() {
card.removeSpellAbility(freeCast);
card.removeExtrinsicKeyword("Play with the top card of your library revealed.");
}
});
}
} // resolve
@Override
public boolean canPlayAI() {
return false;
}
}
final Cost abCost = new Cost(card, "5 T", true);
final AbilityActivated ability = new AbilityTemporalAperture(card, abCost, null);
final StringBuilder sbStack = new StringBuilder();
sbStack.append(card).append(" - Shuffle your library, then reveal the top card.");
ability.setStackDescription(sbStack.toString());
final StringBuilder sbDesc = new StringBuilder();
sbDesc.append(abCost).append("Shuffle your library, then reveal the top card. ");
sbDesc.append("Until end of turn, for as long as that card remains on top of your ");
sbDesc.append("library, play with the top card of your library revealed ");
sbDesc.append("and you may play that card without paying its mana cost. ");
sbDesc.append("(If it has X in its mana cost, X is 0.)");
ability.setDescription(sbDesc.toString());
card.addSpellAbility(ability);
} // *************** END ************ END **************************
}
}

View File

@@ -373,7 +373,6 @@ public class Cost {
}
public final CostPartMana getCostMana() {
// TODO: Change where ChangeCost happens
for (final CostPart part : this.costParts) {
if (part instanceof CostPartMana) {
return (CostPartMana) part;

View File

@@ -106,7 +106,6 @@ public class InputBlock extends Input {
currentAttacker = null;
allBlocking.clear();
stop();
FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority();
}
}

View File

@@ -59,7 +59,6 @@ public class InputCleanup extends Input {
// goes to the next phase
if (active.isUnlimitedHandSize() || n <= max || n <= 0 || active != turnOwner) {
active.getController().passPriority();
stop();
return;
}
ButtonUtil.disableAll();

View File

@@ -25,6 +25,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.zone.MagicStack;
import forge.gui.match.controllers.CMessage;
import forge.util.MyObservable;
/**
@@ -65,7 +66,7 @@ public class InputControl extends MyObservable implements java.io.Serializable {
*/
public final void setInput(final Input in) {
boolean isInputEmpty = this.input == null || this.input instanceof InputPassPriority;
System.out.println(in.getClass().getName());
//System.out.println(in.getClass().getName());
if (!this.game.getStack().isResolving() && isInputEmpty) {
this.input = in;
} else {
@@ -134,13 +135,10 @@ public class InputControl extends MyObservable implements java.io.Serializable {
* @param update
* a boolean.
*/
public final void resetInput() { resetInput(true); }
public final void resetInput(final boolean update) {
public final void resetInput() {
this.input = null;
if (update) {
this.updateObservers();
}
}
/**
* <p>
@@ -244,4 +242,19 @@ public class InputControl extends MyObservable implements java.io.Serializable {
return pc.getDefaultInput();
} // getInput()
public final void setNewInput(GameState game) {
PhaseHandler ph = game.getPhaseHandler();
final Input tmp = getActualInput();
//String message = String.format("%s's %s, priority of %s [%sP] input is %s", ph.getPlayerTurn(), ph.getPhase(), ph.getPriorityPlayer(), ph.isPlayerPriorityAllowed() ? "+" : "-", tmp == null ? "null" : tmp.getClass().getSimpleName());
//System.out.println(message);
if (tmp != null) {
//System.out.println(ph.getPlayerTurn() + "'s " + ph.getPhase() + ", priority of " + ph.getPriorityPlayer() + " @ input is " + tmp.getClass().getName() );
CMessage.SINGLETON_INSTANCE.getInputControl().setInput(tmp);
} else if (!ph.isPlayerPriorityAllowed()) {
// System.out.println("cannot have priority, forced to pass");
ph.getPriorityPlayer().getController().passPriority();
}
}
} // InputControl

View File

@@ -179,7 +179,6 @@ public class InputMulligan extends Input {
SDisplayUtil.showTab(nextField);
game.getPhaseHandler().nextPhase();
stop();
}
@Override

View File

@@ -75,7 +75,6 @@ public class InputPassPriority extends Input {
@Override
public final void selectButtonOK() {
FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority();
stop();
}
/** {@inheritDoc} */

View File

@@ -11,8 +11,6 @@ import java.util.Set;
import javax.swing.JOptionPane;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -25,11 +23,13 @@ import forge.CardUtil;
import forge.Singletons;
import forge.card.trigger.TriggerHandler;
import forge.card.trigger.TriggerType;
import forge.control.input.Input;
import forge.control.input.InputControl;
import forge.control.input.InputMulligan;
import forge.deck.Deck;
import forge.deck.CardPool;
import forge.deck.DeckSection;
import forge.error.BugReporter;
import forge.game.event.FlipCoinEvent;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -38,9 +38,11 @@ import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.gui.match.controllers.CMessage;
import forge.gui.match.views.VAntes;
import forge.item.CardPrinted;
import forge.item.IPaperCard;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.util.Aggregates;
import forge.util.MyRandom;
@@ -51,6 +53,45 @@ import forge.util.MyRandom;
*/
public class GameNew {
/**
* TODO: Write javadoc for this type.
*
*/
public static final class GameInputUpdatesThread extends Thread {
private final MatchController match;
private final GameState game;
private boolean wasChangedRecently;
/**
* TODO: Write javadoc for Constructor.
* @param match
* @param game
*/
public GameInputUpdatesThread(MatchController match, GameState game) {
this.match = match;
this.game = game;
}
public void run(){
while(!game.isGameOver()) {
boolean needsNewInput = CMessage.SINGLETON_INSTANCE.getInputControl().isValid() == false;
if ( needsNewInput ) {
match.getInput().setNewInput(game);
wasChangedRecently = true;
}
try {
Thread.sleep(wasChangedRecently ? 2 : 40);
wasChangedRecently = false;
} catch (InterruptedException e) {
BugReporter.reportException(e);
break;
}
}
}
}
public static final ForgePreferences preferences = Singletons.getModel().getPreferences();
private static void preparePlayerLibrary(Player player, final ZoneType zoneType, CardPool secion, boolean canRandomFoil, Random generator) {
PlayerZone library = player.getZone(zoneType);
for (final Entry<CardPrinted, Integer> stackOfCards : secion) {
@@ -60,7 +101,7 @@ public class GameNew {
final Card card = cardPrinted.toForgeCard(player);
// apply random pictures for cards
if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_CARD_ART)) {
if (preferences.getPrefBoolean(FPref.UI_RANDOM_CARD_ART)) {
final int cntVariants = cardPrinted.getRules().getEditionInfo(cardPrinted.getEdition()).getCopiesCount();
if (cntVariants > 1) {
card.setRandomPicture(generator.nextInt(cntVariants - 1) + 1);
@@ -86,8 +127,8 @@ public class GameNew {
* TODO: Accept something like match state as parameter. Match should be aware of players,
* their decks and other special starting conditions.
*/
public static void newGame(final Map<Player, PlayerStartConditions> playersConditions, final GameState game, final boolean canRandomFoil) {
Singletons.getModel().getMatch().getInput().clearInput();
public static void newGame(final MatchController match, final Map<Player, PlayerStartConditions> playersConditions, final GameState game, final boolean canRandomFoil) {
match.getInput().clearInput();
Card.resetUniqueNumber();
// need this code here, otherwise observables fail
@@ -97,7 +138,7 @@ public class GameNew {
trigHandler.clearDelayedTrigger();
// friendliness
boolean useAnte = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ANTE);
boolean useAnte = preferences.getPrefBoolean(FPref.UI_ANTE);
final Set<CardPrinted> rAICards = new HashSet<CardPrinted>();
Map<Player, Set<CardPrinted>> removedAnteCards = new HashMap<Player, Set<CardPrinted>>();
@@ -112,8 +153,8 @@ public class GameNew {
initVariantsZones(player, psc);
GameType gameType = Singletons.getModel().getMatch().getGameType();
boolean isFirstGame = Singletons.getModel().getMatch().getPlayedGames().isEmpty();
GameType gameType = match.getGameType();
boolean isFirstGame = match.getPlayedGames().isEmpty();
boolean hasSideboard = psc.getOriginalDeck().has(DeckSection.Sideboard);
boolean canSideBoard = !isFirstGame && gameType.isSideboardingAllowed() && hasSideboard;
@@ -132,7 +173,7 @@ public class GameNew {
preparePlayerLibrary(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), canRandomFoil, generator);
// Shuffling
if (player instanceof AIPlayer && Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_SMOOTH_LAND)) {
if (player instanceof AIPlayer && preferences.getPrefBoolean(FPref.UI_SMOOTH_LAND)) {
// AI may do this instead of shuffling its deck
final Iterable<Card> c1 = GameNew.smoothComputerManaCurve(player.getCardsIn(ZoneType.Library));
player.getZone(ZoneType.Library).setCards(c1);
@@ -174,7 +215,7 @@ public class GameNew {
JOptionPane.showMessageDialog(null, ante.toString(), "", JOptionPane.INFORMATION_MESSAGE);
}
GameNew.actuateGame(game, false);
GameNew.actuateGame(match, game, false);
}
private static void initVariantsZones(final Player player, final PlayerStartConditions psc) {
@@ -243,8 +284,7 @@ public class GameNew {
}
// ultimate of Karn the Liberated
public static void restartGame(final GameState game, final Player startingTurn, Map<Player, List<Card>> playerLibraries) {
MatchController match = Singletons.getModel().getMatch();
public static void restartGame(final MatchController match, final GameState game, final Player startingTurn, Map<Player, List<Card>> playerLibraries) {
Map<LobbyPlayer, PlayerStartConditions> players = match.getPlayers();
Map<Player, PlayerStartConditions> playersConditions = new HashMap<Player, PlayerStartConditions>();
@@ -291,7 +331,7 @@ public class GameNew {
PhaseHandler phaseHandler = game.getPhaseHandler();
phaseHandler.setPlayerTurn(startingTurn);
GameNew.actuateGame(game, true);
GameNew.actuateGame(match, game, true);
}
/**
@@ -302,10 +342,10 @@ public class GameNew {
* newGame, then when all is ready, call this function.
* @param isRestartedGame Whether the actuated game is the first start or a restart
*/
private static void actuateGame(final GameState game, boolean isRestartedGame) {
private static void actuateGame(final MatchController match, final GameState game, boolean isRestartedGame) {
if (!isRestartedGame) {
// Deciding which cards go to ante
if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_ANTE)) {
if (preferences.getPrefBoolean(FPref.UI_ANTE)) {
final String nl = System.getProperty("line.separator");
final StringBuilder msg = new StringBuilder();
for (final Player p : game.getPlayers()) {
@@ -324,15 +364,24 @@ public class GameNew {
JOptionPane.showMessageDialog(null, msg, "Ante", JOptionPane.INFORMATION_MESSAGE);
}
GameOutcome lastGameOutcome = Singletons.getModel().getMatch().getLastGameOutcome();
GameOutcome lastGameOutcome = match.getLastGameOutcome();
// Only cut/coin toss if it's the first game of the match
if (lastGameOutcome == null) {
GameNew.seeWhoPlaysFirstDice();
Player goesFirst;
Player humanPlayer = Singletons.getControl().getPlayer();
boolean isFirstGame = lastGameOutcome == null;
if (isFirstGame) {
goesFirst = GameNew.seeWhoPlaysFirstDice(game);
} else {
Player human = Singletons.getControl().getPlayer();
Player goesFirst = lastGameOutcome.isWinner(human.getLobbyPlayer()) ? human.getOpponent() : human;
setPlayersFirstTurn(goesFirst, false);
goesFirst = lastGameOutcome.isWinner(humanPlayer.getLobbyPlayer()) ? humanPlayer.getOpponent() : humanPlayer;
}
String message = goesFirst + ( isFirstGame ? " has won the coin toss." : " lost the last game.");
boolean willPlay = goesFirst.getController().getWillPlayOnFirstTurn(message);
if ( goesFirst != humanPlayer ) {
JOptionPane.showMessageDialog(null, message + "\nComputer Going First", "You are drawing", JOptionPane.INFORMATION_MESSAGE);
}
goesFirst = willPlay ? goesFirst : goesFirst.getOpponent();
game.getPhaseHandler().setPlayerTurn(goesFirst);
}
// Draw <handsize> cards
@@ -340,9 +389,20 @@ public class GameNew {
p.drawCards(p.getMaxHandSize());
}
game.getPhaseHandler().setPhaseState(PhaseType.MULLIGAN);
InputControl control = Singletons.getModel().getMatch().getInput();
control.setInput(new InputMulligan());
InputControl control = match.getInput();
Input tmp = new InputMulligan();
control.setInput(tmp);
Thread thGame = new GameInputUpdatesThread(match, game);
match.getInput().getInput().showMessage();
thGame.setName("Game input updater");
thGame.start();
} // newGame()
private static String buildFourColumnList(String firstLine, Iterable<CardPrinted> cAnteRemoved) {
@@ -418,49 +478,14 @@ public class GameNew {
* <p>
* seeWhoPlaysFirstCoinToss.
* </p>
* @return
*/
private static void seeWhoPlaysFirstDice() {
int playerDie = 0;
int computerDie = 0;
while (playerDie == computerDie) {
playerDie = MyRandom.getRandom().nextInt(20);
computerDie = MyRandom.getRandom().nextInt(20);
}
private static Player seeWhoPlaysFirstDice(final GameState game) {
// Play the Flip Coin sound
Singletons.getModel().getGame().getEvents().post(new FlipCoinEvent());
game.getEvents().post(new FlipCoinEvent());
List<Player> allPlayers = Singletons.getModel().getGame().getPlayers();
setPlayersFirstTurn(allPlayers.get(MyRandom.getRandom().nextInt(allPlayers.size())), true);
List<Player> allPlayers = game.getPlayers();
return allPlayers.get(MyRandom.getRandom().nextInt(allPlayers.size()));
}
private static void setPlayersFirstTurn(Player goesFirst, boolean firstGame) {
StringBuilder sb = new StringBuilder(goesFirst.toString());
if (firstGame) {
sb.append(" has won the coin toss.");
}
else {
sb.append(" lost the last game.");
}
if (goesFirst.isHuman()) {
if (!humanPlayOrDraw(sb.toString())) {
goesFirst = goesFirst.getOpponent();
}
} else {
sb.append("\nComputer Going First");
JOptionPane.showMessageDialog(null, sb.toString(), "Play or Draw?", JOptionPane.INFORMATION_MESSAGE);
}
Singletons.getModel().getGame().getPhaseHandler().setPlayerTurn(goesFirst);
} // seeWhoPlaysFirstDice()
private static boolean humanPlayOrDraw(String message) {
final String[] possibleValues = { "Play", "Draw" };
final Object playDraw = JOptionPane.showOptionDialog(null, message + "\n\nWould you like to play or draw?",
"Play or Draw?", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null,
possibleValues, possibleValues[0]);
return !playDraw.equals(1);
}
}

View File

@@ -19,7 +19,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerStatistics;
import forge.game.player.PlayerType;
import forge.game.zone.ZoneType;
import forge.gui.GuiInput;
import forge.gui.InputProxy;
import forge.gui.framework.EDocID;
import forge.gui.framework.SDisplayUtil;
import forge.gui.match.CMatchUI;
@@ -149,8 +149,7 @@ public class MatchController {
Singletons.getControl().changeState(FControl.Screens.MATCH_SCREEN);
SDisplayUtil.showTab(EDocID.REPORT_LOG.getDoc());
// set all observers
GuiInput inputControl = CMessage.SINGLETON_INSTANCE.getInputControl();
InputProxy inputControl = CMessage.SINGLETON_INSTANCE.getInputControl();
input.addObserver(inputControl);
currentGame.getStack().addObserver(inputControl);
currentGame.getPhaseHandler().addObserver(inputControl);
@@ -159,7 +158,7 @@ public class MatchController {
// some observers are set in CMatchUI.initMatch
final boolean canRandomFoil = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL) && gameType == GameType.Constructed;
GameNew.newGame(startConditions, currentGame, canRandomFoil);
GameNew.newGame(this, startConditions, currentGame, canRandomFoil);
// TODO restore this functionality!!!
//VMatchUI.SINGLETON_INSTANCE.getViewDevMode().getDocument().setVisible(Preferences.DEV_MODE);

View File

@@ -108,7 +108,6 @@ public class AiInputCommon extends Input {
}
}
player.getController().passPriority();
stop();
} // getMessage();
/**
@@ -162,7 +161,9 @@ public class AiInputCommon extends Input {
sa = computer.getSpellAbilityToPlay();
if ( sa == null ) break;
//System.out.println("Playing sa: " + sa);
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
if (!ComputerUtil.handlePlayingSpellAbility(player, sa, game)) {
break;
}
} while ( sa != null );
}

View File

@@ -425,14 +425,16 @@ public class ComputerUtil {
int count = 0;
while (count < amount) {
final Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
if (prefCard != null) {
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
if (prefCard == null) {
prefCard = ComputerUtilCard.getWorstAI(typeList);
}
if (prefCard == null) {
return null;
}
sacList.add(prefCard);
typeList.remove(prefCard);
count++;
} else {
return null;
}
}
return sacList;
}

View File

@@ -1648,14 +1648,14 @@ public class ComputerUtilCombat {
public static Map<Card, Integer> distributeAIDamage(final Card attacker, final List<Card> block, int dmgCanDeal, GameEntity defender) {
Map<Card, Integer> damageMap = new HashMap<Card, Integer>();
if (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")) {
boolean isAttacking = defender != null;
if (isAttacking && (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked."))) {
damageMap.put(null, dmgCanDeal);
return damageMap;
}
boolean isAttacking = defender != null;
final boolean hasTrample = attacker.hasKeyword("Trample");
if (block.size() == 1) {

View File

@@ -21,9 +21,7 @@ import java.util.Observable;
import java.util.Observer;
import forge.Card;
import forge.Singletons;
import forge.control.input.Input;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.util.MyObservable;
@@ -36,28 +34,16 @@ import forge.util.MyObservable;
* @author Forge
* @version $Id$
*/
public class GuiInput extends MyObservable implements Observer {
public class InputProxy extends MyObservable implements Observer {
/** The input. */
private Input input;
private volatile boolean valid = false;
/** {@inheritDoc} */
@Override
public final void update(final Observable observable, final Object obj) {
PhaseHandler ph = Singletons.getModel().getGame().getPhaseHandler();
final Input tmp = Singletons.getModel().getMatch().getInput().getActualInput();
// System.out.println(ph.getPlayerTurn() + "'s " + ph.getPhase() + ", priority of " + ph.getPriorityPlayer() + " @ actual input is " + ( tmp == null ? "null" : tmp.getClass().getName()) + "; MHP = " + ph.mayPlayerHavePriority() );
if (tmp != null) {
// System.out.println(ph.getPlayerTurn() + "'s " + ph.getPhase() + ", priority of " + ph.getPriorityPlayer() + " @ input is " + tmp.getClass().getName() );
this.setInput(tmp);
} else if (!ph.isPlayerPriorityAllowed()) {
//System.out.println("cannot have priority, forced to pass");
ph.passPriority();
valid = false;
}
}
/**
* <p>
* Setter for the field <code>input</code>.
@@ -66,9 +52,10 @@ public class GuiInput extends MyObservable implements Observer {
* @param in
* a {@link forge.control.input.Input} object.
*/
private void setInput(final Input in) {
public void setInput(final Input in) {
valid = true;
this.input = in;
this.input.showMessage();
this.input.showMessage(); // this call may invalidate the input by the time it returns
}
/**
@@ -121,10 +108,13 @@ public class GuiInput extends MyObservable implements Observer {
return this.getInput().toString();
}
/** @return {@link forge.gui.GuiInput.Input} */
/** @return {@link forge.gui.InputProxy.Input} */
public Input getInput() {
return this.input;
}
public boolean isValid() {
return valid;
}
}

View File

@@ -28,7 +28,7 @@ import javax.swing.JButton;
import forge.Command;
import forge.game.MatchController;
import forge.gui.GuiInput;
import forge.gui.InputProxy;
import forge.gui.framework.ICDoc;
import forge.gui.framework.SDisplayUtil;
import forge.gui.match.views.VMessage;
@@ -42,7 +42,7 @@ public enum CMessage implements ICDoc {
/** */
SINGLETON_INSTANCE;
private GuiInput inputControl = new GuiInput();
private InputProxy inputControl = new InputProxy();
private Component lastFocusedButton = null;
private final ActionListener actCancel = new ActionListener() {
@@ -87,7 +87,7 @@ public enum CMessage implements ICDoc {
*
* @return GuiInput
*/
public GuiInput getInputControl() {
public InputProxy getInputControl() {
return this.inputControl;
}

View File

@@ -214,6 +214,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
while (deltaCardWidth > 0) {
List<CardStackRow> template = tryArrangePilesOfWidth(lands, tokens, creatures, others);
//System.out.println(template == null ? "won't fit" : "Fits @ " + cardWidth + " !!! " + template.toString());
deltaCardWidth = (getCardWidth() - lastGoodCardWidth) / 2;
if (template != null) {
@@ -258,6 +259,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
int x = 0;
int y = PlayArea.GUTTER_Y;
//System.out.println("-------- " + (mirror ? "^" : "_") + " (Positioning ) Card width = " + cardWidth + ". Playarea = " + playAreaWidth + " x " + playAreaHeight );
for (final CardStackRow row : template) {
int rowBottom = 0;
x = PlayArea.GUTTER_X;
@@ -277,10 +279,11 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
this.setComponentZOrder(panel, panelIndex);
final int panelX = x + (stackPosition * this.stackSpacingX);
final int panelY = y + (stackPosition * this.stackSpacingY);
//System.out.println("... placinng " + panel.getCard() + " @ (" + panelX + ", " + panelY + ")" );
panel.setCardBounds(panelX, panelY, this.getCardWidth(), this.cardHeight);
}
rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth() + cardSpacingX;
x += stack.getWidth();
}
y = rowBottom;
}
@@ -291,6 +294,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
int afterFirstRow;
//System.out.println( "======== " + ( mirror ? "^" : "_" ) + " (try arrange) Card width = " + cardWidth + ". PlayArea = " + playAreaWidth + " x " + playAreaHeight + " ========");
boolean landsFit, tokensFit, creaturesFit;
if (this.mirror) {
// Wrap all creatures and lands.
@@ -354,13 +358,13 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
// card width.
final boolean isMinimalSize = this.getCardWidth() == this.getCardWidthMin();
// System.err.format("[%d] @ %d - Repaint playarea - %s %n", new Date().getTime(), cntRepaints++, mirror ? "MIRROR" : "DIRECT");
CardStackRow currentRow = new CardStackRow();
for (final CardStack stack : sourceRow) {
final int rowWidth = currentRow.getWidth();
final int stackWidth = stack.getWidth();
//System.out.printf("Adding %s (+%dpx), current row is %dpx and has %s \n", stack, stackWidth, rowWidth, currentRow );
// If the row is not empty and this stack doesn't fit, add the row.
if (rowWidth + stack.getWidth() > this.playAreaWidth && !currentRow.isEmpty() ) {
if (rowWidth + stackWidth > this.playAreaWidth && !currentRow.isEmpty() ) {
// Stop processing if the row is too wide or tall.
if (rowWidth > this.playAreaWidth || this.getRowsHeight(template) + sourceRow.getHeight() > this.playAreaHeight) {
@@ -388,6 +392,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
template.add(insertIndex, currentRow);
} else return false;
}
//System.out.println("... row complete! " + currentRow.getWidth() + "px");
return true;
}
@@ -409,6 +414,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
private int planOthersRow(final List<CardStack> sourceRow, final int firstPile, final List<CardStackRow> template, final CardStackRow rowToFill) {
int rowWidth = rowToFill.getWidth();
// System.out.println("This row has:" + rowToFill + "; want to add:" + sourceRow );
for (int i = firstPile; i < sourceRow.size(); i++ ) {
CardStack stack = sourceRow.get(i);