package forge;
import java.util.*;
import java.util.Map.Entry;
/**
* <p>Combat class.</p>
*
* @author Forge
* @version $Id: $
*/
public class Combat {
// key is attacker Card
// value is CardList of blockers
private Map<Card, CardList> map = new TreeMap<Card, CardList>();
private Set<Card> blocked = new HashSet<Card>();
private HashMap<Card, CardList> unblockedMap = new HashMap<Card, CardList>();
private HashMap<Card, Integer> defendingDamageMap = new HashMap<Card, Integer>();
// Defenders are the Defending Player + Each Planeswalker that player controls
private ArrayList<Object> defenders = new ArrayList<Object>();
private int currentDefender = 0;
private int nextDefender = 0;
// This Hash keeps track of
private HashMap<Card, Object> attackerToDefender = new HashMap<Card, Object>();
private int attackingDamage;
private Player attackingPlayer = null;
private Player defendingPlayer = null;
private CardList attackersWithLure = new CardList();
private CardList canBlockAttackerWithLure = new CardList();
/**
* <p>Constructor for Combat.</p>
*/
public Combat() {
// Let the Begin Turn/Untap Phase Reset Combat properly
}
/**
* <p>reset.</p>
*/
public void reset() {
resetAttackers();
blocked.clear();
unblockedMap.clear();
attackingDamage = 0;
defendingDamageMap.clear();
attackingPlayer = null;
defendingPlayer = null;
attackersWithLure.clear();
canBlockAttackerWithLure.clear();
defenders.clear();
currentDefender = 0;
nextDefender = 0;
initiatePossibleDefenders(AllZone.getPhase().getPlayerTurn().getOpponent());
}
/**
* <p>initiatePossibleDefenders.</p>
*
* @param defender a {@link forge.Player} object.
*/
public void initiatePossibleDefenders(Player defender) {
defenders.add(defender);
CardList planeswalkers = AllZoneUtil.getPlayerCardsInPlay(defender);
planeswalkers = planeswalkers.getType("Planeswalker");
for (Card pw : planeswalkers)
defenders.add(pw);
}
/**
* <p>nextDefender.</p>
*
* @return a {@link java.lang.Object} object.
*/
public Object nextDefender() {
if (nextDefender >= defenders.size())
return null;
currentDefender = nextDefender;
nextDefender++;
return defenders.get(currentDefender);
}
/**
* <p>Setter for the field <code>currentDefender</code>.</p>
*
* @param def a int.
*/
public void setCurrentDefender(int def) {
currentDefender = def;
}
/**
* <p>getRemainingDefenders.</p>
*
* @return a int.
*/
public int getRemainingDefenders() {
return defenders.size() - nextDefender;
}
/**
* <p>Getter for the field <code>defenders</code>.</p>
*
* @return a {@link java.util.ArrayList} object.
*/
public ArrayList<Object> getDefenders() {
return defenders;
}
/**
* <p>Setter for the field <code>defenders</code>.</p>
*
* @param newDef a {@link java.util.ArrayList} object.
*/
public void setDefenders(ArrayList<Object> newDef) {
defenders = newDef;
}
/**
* <p>getDefendingPlaneswalkers.</p>
*
* @return an array of {@link forge.Card} objects.
*/
public Card[] getDefendingPlaneswalkers() {
Card[] pwDefending = new Card[defenders.size() - 1];
int i = 0;
for (Object o : defenders) {
if (o instanceof Card) {
pwDefending[i] = (Card) o;
i++;
}
}
return pwDefending;
}
/**
* <p>getDeclaredAttackers.</p>
*
* @return a int.
*/
public int getDeclaredAttackers() {
return attackerToDefender.size();
}
/**
* <p>Setter for the field <code>attackingPlayer</code>.</p>
*
* @param player a {@link forge.Player} object.
*/
public void setAttackingPlayer(Player player) {
attackingPlayer = player;
}
/**
* <p>Setter for the field <code>defendingPlayer</code>.</p>
*
* @param player a {@link forge.Player} object.
*/
public void setDefendingPlayer(Player player) {
defendingPlayer = player;
}
/**
* <p>Getter for the field <code>attackingPlayer</code>.</p>
*
* @return a {@link forge.Player} object.
*/
public Player getAttackingPlayer() {
return attackingPlayer;
}
/**
* <p>Getter for the field <code>defendingPlayer</code>.</p>
*
* @return a {@link forge.Player} object.
*/
public Player getDefendingPlayer() {
return defendingPlayer;
}
/**
* <p>Getter for the field <code>defendingDamageMap</code>.</p>
*
* @return a {@link java.util.HashMap} object.
*/
public HashMap<Card, Integer> getDefendingDamageMap() {
return defendingDamageMap;
}
/**
* <p>getTotalDefendingDamage.</p>
*
* @return a int.
*/
public int getTotalDefendingDamage() {
int total = 0;
Collection<Integer> c = defendingDamageMap.values();
Iterator<Integer> itr = c.iterator();
while (itr.hasNext())
total += itr.next();
return total;
}
/**
* <p>setDefendingDamage.</p>
*/
public void setDefendingDamage() {
defendingDamageMap.clear();
CardList att = new CardList(getAttackers());
// sum unblocked attackers' power
for (int i = 0; i < att.size(); i++) {
if (!isBlocked(att.get(i))
|| (getBlockers(att.get(i)).size() == 0 && att.get(i).hasKeyword("Trample"))) {
int damageDealt = att.get(i).getNetCombatDamage();
if (damageDealt > 0) {
//if the creature has first strike do not do damage in the normal combat phase
if (!att.get(i).hasFirstStrike() || att.get(i).hasDoubleStrike())
addDefendingDamage(damageDealt, att.get(i));
}
} // ! isBlocked...
}// for
}
/**
* <p>setDefendingFirstStrikeDamage.</p>
*
* @return a boolean.
*/
public boolean setDefendingFirstStrikeDamage() {
boolean needsFirstStrike = false;
defendingDamageMap.clear();
CardList att = new CardList(getAttackers());
// sum unblocked attackers' power
for (int i = 0; i < att.size(); i++) {
if (!isBlocked(att.get(i))) {
int damageDealt = att.get(i).getNetCombatDamage();
if (damageDealt > 0) {
// if the creature has first strike or double strike do damage in the first strike combat phase
if (att.get(i).hasFirstStrike() || att.get(i).hasDoubleStrike()) {
addDefendingDamage(damageDealt, att.get(i));
needsFirstStrike = true;
}
}
}
} // for
return needsFirstStrike;
}
/**
* <p>addDefendingDamage.</p>
*
* @param n a int.
* @param source a {@link forge.Card} object.
*/
public void addDefendingDamage(int n, Card source) {
String slot = getDefenderByAttacker(source).toString();
Object o = defenders.get(Integer.parseInt(slot));
if (o instanceof Card) {
Card pw = (Card) o;
pw.addAssignedDamage(n, source);
return;
}
if (!defendingDamageMap.containsKey(source))
defendingDamageMap.put(source, n);
else {
defendingDamageMap.put(source, defendingDamageMap.get(source) + n);
}
}
/**
* <p>addAttackingDamage.</p>
*
* @param n a int.
*/
public void addAttackingDamage(int n) {
attackingDamage += n;
}
/**
* <p>Getter for the field <code>attackingDamage</code>.</p>
*
* @return a int.
*/
public int getAttackingDamage() {
return attackingDamage;
}
/**
* <p>sortAttackerByDefender.</p>
*
* @return an array of {@link forge.CardList} objects.
*/
public CardList[] sortAttackerByDefender() {
CardList attackers[] = new CardList[defenders.size()];
for (int i = 0; i < attackers.length; i++)
attackers[i] = new CardList();
for (Card atk : attackerToDefender.keySet()) {
Object o = attackerToDefender.get(atk);
int i = Integer.parseInt(o.toString());
attackers[i].add(atk);
}
return attackers;
}
/**
* <p>isAttacking.</p>
*
* @param c a {@link forge.Card} object.
* @return a boolean.
*/
public boolean isAttacking(Card c) {
return map.get(c) != null;
}
/**
* <p>addAttacker.</p>
*
* @param c a {@link forge.Card} object.
*/
public void addAttacker(Card c) {
map.put(c, new CardList());
attackerToDefender.put(c, currentDefender);
}
/**
* <p>getDefenderByAttacker.</p>
*
* @param c a {@link forge.Card} object.
* @return a {@link java.lang.Object} object.
*/
public Object getDefenderByAttacker(Card c) {
return attackerToDefender.get(c);
}
/**
* <p>resetAttackers.</p>
*/
public void resetAttackers() {
map.clear();
attackerToDefender.clear();
}
/**
* <p>getAttackers.</p>
*
* @return an array of {@link forge.Card} objects.
*/
public Card[] getAttackers() {
CardList out = new CardList();
Iterator<Card> it = map.keySet().iterator();
while (it.hasNext()) {
out.add((Card) it.next());
}
return out.toArray();
}// getAttackers()
/**
* <p>isBlocked.</p>
*
* @param attacker a {@link forge.Card} object.
* @return a boolean.
*/
public boolean isBlocked(Card attacker) {
return blocked.contains(attacker);
}
/**
* <p>addBlocker.</p>
*
* @param attacker a {@link forge.Card} object.
* @param blocker a {@link forge.Card} object.
*/
public void addBlocker(Card attacker, Card blocker) {
blocked.add(attacker);
getList(attacker).add(blocker);
}
/**
* <p>resetBlockers.</p>
*/
public void resetBlockers() {
reset();
CardList att = new CardList(getAttackers());
for (int i = 0; i < att.size(); i++)
addAttacker(att.get(i));
}
/**
* <p>getAllBlockers.</p>
*
* @return a {@link forge.CardList} object.
*/
public CardList getAllBlockers() {
CardList att = new CardList(getAttackers());
CardList block = new CardList();
for (int i = 0; i < att.size(); i++)
block.addAll(getBlockers(att.get(i)));
return block;
}// getAllBlockers()
/**
* <p>getBlockers.</p>
*
* @param attacker a {@link forge.Card} object.
* @return a {@link forge.CardList} object.
*/
public CardList getBlockers(Card attacker) {
if (getList(attacker) == null)
return new CardList();
else
return new CardList(getList(attacker));
}
/**
* <p>getAttackerBlockedBy.</p>
*
* @param blocker a {@link forge.Card} object.
* @return a {@link forge.Card} object.
*/
public Card getAttackerBlockedBy(Card blocker) {
CardList att = new CardList(getAttackers());
for (int i = 0; i < att.size(); i++) {
if (getBlockers(att.get(i)).contains(blocker)) return att.get(i);
} // for
return null;
}
/**
* <p>getList.</p>
*
* @param attacker a {@link forge.Card} object.
* @return a {@link forge.CardList} object.
*/
private CardList getList(Card attacker) {
return (CardList) map.get(attacker);
}
/**
* <p>removeFromCombat.</p>
*
* @param c a {@link forge.Card} object.
*/
public void removeFromCombat(Card c) {
// is card an attacker?
CardList att = new CardList(getAttackers());
if (att.contains(c)) {
map.remove(c);
attackerToDefender.remove(c);
} else// card is a blocker
{
for (Card a : att)
if (getBlockers(a).contains(c)) {
getList(a).remove(c);
// TODO if Declare Blockers and Declare Blockers (Abilities) merge this logic needs to be tweaked
if (getBlockers(a).size() == 0 && AllZone.getPhase().is(Constant.Phase.Combat_Declare_Blockers))
blocked.remove(a);
}
}
// update combat
CombatUtil.showCombat();
}// removeFromCombat()
/**
* <p>verifyCreaturesInPlay.</p>
*/
public void verifyCreaturesInPlay() {
CardList all = new CardList();
all.addAll(getAttackers());
all.addAll(getAllBlockers());
for (int i = 0; i < all.size(); i++)
if (!AllZoneUtil.isCardInPlay(all.get(i)))
removeFromCombat(all.get(i));
}// verifyCreaturesInPlay()
/**
* <p>setUnblocked.</p>
*/
public void setUnblocked() {
CardList attacking = new CardList(getAttackers());
for (Card attacker : attacking) {
CardList block = getBlockers(attacker);
if (block.size() == 0) {
// this damage is assigned to a player by setPlayerDamage()
addUnblockedAttacker(attacker);
//Run Unblocked Trigger
HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", attacker);
AllZone.getTriggerHandler().runTrigger("AttackerUnblocked", runParams);
}
}
}
// set Card.setAssignedDamage() for all creatures in combat
// also assigns player damage by setPlayerDamage()
/**
* <p>setAssignedFirstStrikeDamage.</p>
*
* @return a boolean.
*/
public boolean setAssignedFirstStrikeDamage() {
boolean needFirstStrike = setDefendingFirstStrikeDamage();
CardList block;
CardList attacking = new CardList(getAttackers());
for (int i = 0; i < attacking.size(); i++) {
Card attacker = attacking.get(i);
block = getBlockers(attacker);
int damageDealt = attacker.getNetCombatDamage();
// attacker always gets all blockers' attack
for (Card b : block) {
if (b.hasFirstStrike() || b.hasDoubleStrike()) {
needFirstStrike = true;
int attack = b.getNetCombatDamage();
attacker.addAssignedDamage(attack, b);
}
}
if (block.size() == 0) {
// this damage is assigned to a player by setDefendingFirstStrikeDamage()
} else if (attacker.hasFirstStrike() || attacker.hasDoubleStrike()) {
needFirstStrike = true;
if (getAttackingPlayer().isHuman()) {// human attacks
if (attacker.hasKeyword("Trample") || block.size() > 1)
AllZone.getDisplay().assignDamage(attacker, block, damageDealt);
else block.get(0).addAssignedDamage(damageDealt, attacking.get(i));
} else {// computer attacks
distributeAIDamage(attacker, block, damageDealt);
}
}// if(hasFirstStrike || doubleStrike)
}// for
return needFirstStrike;
}// setAssignedFirstStrikeDamage()
// set Card.setAssignedDamage() for all creatures in combat
// also assigns player damage by setPlayerDamage()
/**
* <p>setAssignedDamage.</p>
*/
public void setAssignedDamage() {
setDefendingDamage();
CardList block;
CardList attacking = new CardList(getAttackers());
for (int i = 0; i < attacking.size(); i++) {
Card attacker = attacking.get(i);
block = getBlockers(attacker);
int damageDealt = attacker.getNetCombatDamage();
// attacker always gets all blockers' attack
for (Card b : block) {
if (!b.hasFirstStrike() || b.hasDoubleStrike()) {
int attack = b.getNetCombatDamage();
attacker.addAssignedDamage(attack, b);
}
}
if (block.size() == 0) {
// this damage is assigned to a player by setDefendingDamage()
} else if (!attacker.hasFirstStrike() || attacker.hasDoubleStrike()) {
if (getAttackingPlayer().isHuman()) {// human attacks
if (attacker.hasKeyword("Trample") || block.size() > 1)
AllZone.getDisplay().assignDamage(attacker, block, damageDealt);
else block.get(0).addAssignedDamage(damageDealt, attacking.get(i));
} else {// computer attacks
distributeAIDamage(attacker, block, damageDealt);
}
}// if !hasFirstStrike ...
}// for
// should first strike affect the following?
}// assignDamage()
/**
* <p>distributeAIDamage.</p>
*
* @param attacker a {@link forge.Card} object.
* @param block a {@link forge.CardList} object.
* @param damage a int.
*/
private void distributeAIDamage(Card attacker, CardList block, int damage) {
Card c = attacker;
if (block.size() == 1) {
Card blocker = block.get(0);
// trample
if (attacker.hasKeyword("Trample")) {
int damageNeeded = 0;
//TODO: if the human can be killed distribute only the minimum of damage to the blocker
damageNeeded = blocker.getEnoughDamageToKill(damage, attacker, true);
if (damageNeeded > damage)
damageNeeded = Math.min(blocker.getLethalDamage(), damage);
else
damageNeeded = Math.max(blocker.getLethalDamage(), damageNeeded);
int trample = damage - damageNeeded;
if (0 < trample) // If Extra trample damage, assign to defending player/planeswalker
this.addDefendingDamage(trample, attacker);
blocker.addAssignedDamage(damageNeeded, attacker);
} else blocker.addAssignedDamage(damage, attacker);
}// 1 blocker
else {
boolean killsAllBlockers = true;//Does the attacker deal lethal damage to all blockers
for (Card b : block) {
int enoughDamageToKill = b.getEnoughDamageToKill(damage, attacker, true);
if (enoughDamageToKill <= damage) {
damage -= enoughDamageToKill;
CardList cl = new CardList();
cl.add(attacker);
b.addAssignedDamage(enoughDamageToKill, c);
} else killsAllBlockers = false;
}// for
// if attacker has no trample, and there's damage left, assign the rest
// to a random blocker
if (damage > 0
&& !(c.hasKeyword("Trample")
&& killsAllBlockers == true)) {
int index = CardUtil.getRandomIndex(block);
block.get(index).addAssignedDamage(damage, c);
damage = 0;
} else if (c.hasKeyword("Trample")
&& killsAllBlockers == true) {
this.addDefendingDamage(damage, c);
}
}
}// setAssignedDamage()
/**
* <p>dealAssignedDamage.</p>
*/
public static void dealAssignedDamage() {
// This function handles both Regular and First Strike combat assignment
Player player = AllZone.getCombat().getDefendingPlayer();
boolean bFirstStrike = AllZone.getPhase().is(Constant.Phase.Combat_FirstStrikeDamage);
HashMap<Card, Integer> defMap = AllZone.getCombat().getDefendingDamageMap();
for (Entry<Card, Integer> entry : defMap.entrySet()) {
player.addCombatDamage(entry.getValue(), entry.getKey());
}
CardList unblocked = new CardList(bFirstStrike ? AllZone.getCombat().getUnblockedAttackers() :
AllZone.getCombat().getUnblockedFirstStrikeAttackers());
for (int j = 0; j < unblocked.size(); j++) {
if (bFirstStrike)
CombatUtil.checkUnblockedAttackers(unblocked.get(j));
else {
if (!unblocked.getCard(j).hasFirstStrike() && !unblocked.getCard(j).hasDoubleStrike())
CombatUtil.checkUnblockedAttackers(unblocked.get(j));
}
}
// this can be much better below here...
CardList combatants = new CardList();
combatants.addAll(AllZone.getCombat().getAttackers());
combatants.addAll(AllZone.getCombat().getAllBlockers());
combatants.addAll(AllZone.getCombat().getDefendingPlaneswalkers());
Card c;
for (int i = 0; i < combatants.size(); i++) {
c = combatants.get(i);
// if no assigned damage to resolve, move to next
if (c.getTotalAssignedDamage() == 0)
continue;
Map<Card, Integer> assignedDamageMap = c.getAssignedDamageMap();
HashMap<Card, Integer> damageMap = new HashMap<Card, Integer>();
for (Entry<Card, Integer> entry : assignedDamageMap.entrySet()) {
Card crd = entry.getKey();
damageMap.put(crd, entry.getValue());
}
c.addCombatDamage(damageMap);
damageMap.clear();
c.clearAssignedDamage();
}
//This was deeper before, but that resulted in the stack entry acting like before.
}
/**
* <p>isUnblocked.</p>
*
* @param att a {@link forge.Card} object.
* @return a boolean.
*/
public boolean isUnblocked(Card att) {
return unblockedMap.containsKey(att);
}
/**
* <p>getUnblockedAttackers.</p>
*
* @return an array of {@link forge.Card} objects.
*/
public Card[] getUnblockedAttackers() {
CardList out = new CardList();
Iterator<Card> it = unblockedMap.keySet().iterator();
while (it.hasNext()) { // only add creatures without firstStrike to this
// list.
Card c = (Card) it.next();
if (!c.hasFirstStrike()) {
out.add(c);
}
}
return out.toArray();
}// getUnblockedAttackers()
/**
* <p>getUnblockedFirstStrikeAttackers.</p>
*
* @return an array of {@link forge.Card} objects.
*/
public Card[] getUnblockedFirstStrikeAttackers() {
CardList out = new CardList();
Iterator<Card> it = unblockedMap.keySet().iterator();
while (it.hasNext()) { // only add creatures without firstStrike to this
// list.
Card c = (Card) it.next();
if (c.hasFirstStrike() || c.hasDoubleStrike()) {
out.add(c);
}
}
return out.toArray();
}// getUnblockedAttackers()
/**
* <p>addUnblockedAttacker.</p>
*
* @param c a {@link forge.Card} object.
*/
public void addUnblockedAttacker(Card c) {
unblockedMap.put(c, new CardList());
}
}// Class Combat