Attackers sorting, Blocking groups - rule 509.3. Fixed Issue 195.

This commit is contained in:
magenoxx 2011-08-02 21:32:05 +04:00
parent 4627c92d41
commit d33bf20bf0
11 changed files with 209 additions and 43 deletions

View file

@ -892,6 +892,12 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
return min;
}
@Override
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
//TODO: improve this
return attackers.iterator().next().getId();
}
@Override
public UUID chooseBlockerOrder(List<Permanent> blockers, Game game) {
//TODO: improve this

View file

@ -469,6 +469,22 @@ public class HumanPlayer extends PlayerImpl<HumanPlayer> {
}
}
@Override
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
while (!abort) {
game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true);
waitForResponse();
if (response.getUUID() != null) {
for (Permanent perm: attackers) {
if (perm.getId().equals(response.getUUID()))
return perm.getId();
}
}
}
return null;
}
@Override
public UUID chooseBlockerOrder(List<Permanent> blockers, Game game) {
while (!abort) {

View file

@ -28,12 +28,6 @@
package mage.game.combat;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.Constants.Outcome;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.keyword.VigilanceAbility;
@ -48,9 +42,11 @@ import mage.players.PlayerList;
import mage.target.common.TargetDefender;
import mage.util.Copyable;
import java.io.Serializable;
import java.util.*;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class Combat implements Serializable, Copyable<Combat> {
@ -60,32 +56,41 @@ public class Combat implements Serializable, Copyable<Combat> {
private static FilterCreatureForCombat filterBlockers = new FilterCreatureForCombat();
protected List<CombatGroup> groups = new ArrayList<CombatGroup>();
protected Map<UUID, CombatGroup> blockingGroups = new HashMap<UUID, CombatGroup>();
protected Set<UUID> defenders = new HashSet<UUID>();
protected UUID attackerId; //the player that is attacking
public Combat() {}
public Combat() {
}
public Combat(final Combat combat) {
this.attackerId = combat.attackerId;
for (CombatGroup group: combat.groups) {
for (CombatGroup group : combat.groups) {
groups.add(group.copy());
}
for (UUID defenderId: combat.defenders) {
for (UUID defenderId : combat.defenders) {
defenders.add(defenderId);
}
for (Map.Entry<UUID, CombatGroup> group : combat.blockingGroups.entrySet()) {
blockingGroups.put(group.getKey(), group.getValue());
}
}
public List<CombatGroup> getGroups() {
return groups;
}
public Collection<CombatGroup> getBlockingGroups() {
return blockingGroups.values();
}
public Set<UUID> getDefenders() {
return defenders;
}
public List<UUID> getAttackers() {
List<UUID> attackers = new ArrayList<UUID>();
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
attackers.addAll(group.attackers);
}
return attackers;
@ -93,7 +98,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public List<UUID> getBlockers() {
List<UUID> blockers = new ArrayList<UUID>();
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
blockers.addAll(group.blockers);
}
return blockers;
@ -101,6 +106,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public void clear() {
groups.clear();
blockingGroups.clear();
defenders.clear();
attackerId = null;
}
@ -108,7 +114,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public int getValue(Game game) {
StringBuilder sb = new StringBuilder();
sb.append(attackerId).append(defenders);
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
sb.append(group.getValue(game));
}
return sb.toString().hashCode();
@ -131,23 +137,21 @@ public class Combat implements Serializable, Copyable<Combat> {
protected void checkAttackRequirements(Player player, Game game) {
//20101001 - 508.1d
for (Permanent creature: game.getBattlefield().getAllActivePermanents(filterAttackers, player.getId())) {
for (RequirementEffect effect: game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
for (Permanent creature : game.getBattlefield().getAllActivePermanents(filterAttackers, player.getId())) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
if (effect.mustAttack(game)) {
UUID defenderId = effect.mustAttackDefender(game.getContinuousEffects().getAbility(effect.getId()), game);
if (defenderId == null) {
if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game);
}
else {
} else {
TargetDefender target = new TargetDefender(defenders, creature.getId());
target.setRequired(true);
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game);
}
}
}
else {
} else {
player.declareAttacker(creature.getId(), defenderId, game);
}
}
@ -160,7 +164,7 @@ public class Combat implements Serializable, Copyable<Combat> {
Player player = game.getPlayer(attackerId);
//20101001 - 509.1c
checkBlockRequirements(player, game);
for (UUID defenderId: getPlayerDefenders(game)) {
for (UUID defenderId : getPlayerDefenders(game)) {
game.getPlayer(defenderId).selectBlockers(game);
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
}
@ -170,9 +174,9 @@ public class Combat implements Serializable, Copyable<Combat> {
protected void checkBlockRequirements(Player player, Game game) {
//20101001 - 509.1c
//TODO: handle case where more than one attacker must be blocked
for (Permanent creature: game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) {
for (Permanent creature : game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) {
if (game.getOpponents(attackerId).contains(creature.getControllerId())) {
for (RequirementEffect effect: game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) {
if (effect.mustBlock(game)) {
UUID attackId = effect.mustBlockAttacker(game.getContinuousEffects().getAbility(effect.getId()), game);
Player defender = game.getPlayer(creature.getControllerId());
@ -210,7 +214,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
break;
case MULITPLE:
for (UUID opponentId: game.getOpponents(attackerId)) {
for (UUID opponentId : game.getOpponents(attackerId)) {
addDefender(opponentId, game);
}
break;
@ -219,7 +223,7 @@ public class Combat implements Serializable, Copyable<Combat> {
private void addDefender(UUID defenderId, Game game) {
defenders.add(defenderId);
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId)) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId)) {
defenders.add(permanent.getId());
}
}
@ -238,12 +242,36 @@ public class Combat implements Serializable, Copyable<Combat> {
groups.add(newGroup);
}
// add blocking group for creatures that block more than one creature
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game) {
Permanent blocker = game.getPermanent(blockerId);
if (blockerId != null && blocker != null && blocker.getBlocking() > 1) {
if (!blockingGroups.containsKey(blockerId)) {
CombatGroup newGroup = new CombatGroup(playerId, playerId != null);
newGroup.blockers.add(blockerId);
// add all blocked attackers
for (CombatGroup group : groups) {
if (group.getBlockers().contains(blockerId)) {
// take into account banding
for (UUID attacker : group.attackers) {
newGroup.attackers.add(attacker);
}
}
}
blockingGroups.put(blockerId, newGroup);
} else {
//TODO: handle banding
blockingGroups.get(blockerId).attackers.add(attackerId);
}
}
}
public void removeFromCombat(UUID creatureId, Game game) {
Permanent creature = game.getPermanent(creatureId);
if (creature != null) {
creature.setAttacking(false);
creature.setBlocking(0);
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
group.remove(creatureId);
}
}
@ -251,15 +279,15 @@ public class Combat implements Serializable, Copyable<Combat> {
public void endCombat(Game game) {
Permanent creature;
for (CombatGroup group: groups) {
for (UUID attacker: group.attackers) {
for (CombatGroup group : groups) {
for (UUID attacker : group.attackers) {
creature = game.getPermanent(attacker);
if (creature != null) {
creature.setAttacking(false);
creature.setBlocking(0);
}
}
for (UUID blocker: group.blockers) {
for (UUID blocker : group.blockers) {
creature = game.getPermanent(blocker);
if (creature != null) {
creature.setAttacking(false);
@ -271,7 +299,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
public boolean hasFirstOrDoubleStrike(Game game) {
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.hasFirstOrDoubleStrike(game))
return true;
}
@ -279,7 +307,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
public CombatGroup findGroup(UUID attackerId) {
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.getAttackers().contains(attackerId))
return group;
}
@ -288,7 +316,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public int totalUnblockedDamage(Game game) {
int total = 0;
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.getBlockers().isEmpty()) {
total += group.totalAttackerDamage(game);
}
@ -307,7 +335,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
public boolean isAttacked(UUID defenderId, Game game) {
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.getDefenderId().equals(defenderId))
return true;
if (group.defenderIsPlaneswalker) {
@ -321,7 +349,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public UUID getDefendingPlayer(UUID attackerId) {
UUID defenderId = null;
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.getAttackers().contains(attackerId)) {
defenderId = group.getDefenderId();
break;
@ -332,13 +360,12 @@ public class Combat implements Serializable, Copyable<Combat> {
private Set<UUID> getPlayerDefenders(Game game) {
Set<UUID> playerDefenders = new HashSet<UUID>();
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
if (group.defenderIsPlaneswalker) {
Permanent permanent = game.getPermanent(group.getDefenderId());
if (permanent != null)
playerDefenders.add(permanent.getControllerId());
}
else {
} else {
playerDefenders.add(group.getDefenderId());
}
}
@ -346,9 +373,15 @@ public class Combat implements Serializable, Copyable<Combat> {
}
public void damageAssignmentOrder(Game game) {
for (CombatGroup group: groups) {
for (CombatGroup group : groups) {
group.pickBlockerOrder(attackerId, game);
}
for (Map.Entry<UUID, CombatGroup> blockingGroup : blockingGroups.entrySet()) {
Permanent blocker = game.getPermanent(blockingGroup.getKey());
if (blocker != null) {
blockingGroup.getValue().pickAttackerOrder(blocker.getControllerId(), game);
}
}
}
@Override

View file

@ -52,6 +52,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
protected List<UUID> attackers = new ArrayList<UUID>();
protected List<UUID> blockers = new ArrayList<UUID>();
protected List<UUID> blockerOrder = new ArrayList<UUID>();
protected List<UUID> attackerOrder = new ArrayList<UUID>();
protected boolean blocked;
protected UUID defenderId;
protected boolean defenderIsPlaneswalker;
@ -74,6 +75,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
for (UUID orderId: group.blockerOrder) {
this.blockerOrder.add(orderId);
}
for (UUID orderId: group.attackerOrder) {
this.attackerOrder.add(orderId);
}
}
protected String getValue(Game game) {
@ -148,6 +152,17 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
public void assignDamageToAttackers(boolean first, Game game) {
if (blockers.size() > 0 && (!first || hasFirstOrDoubleStrike(game))) {
if (attackers.size() == 1) {
singleAttackerDamage(first, game);
}
else {
multiAttackerDamage(first, game);
}
}
}
private boolean canDamage(Permanent perm, boolean first) {
return (first && hasFirstOrDoubleStrike(perm)) || (!first && !hasFirstStrike(perm));
}
@ -168,7 +183,6 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
Permanent blocker = game.getPermanent(blockers.get(0));
Permanent attacker = game.getPermanent(attackers.get(0));
if (blocker != null && attacker != null) {
int blockerDamage = blocker.getPower().getValue();
if (canDamage(attacker, first)) {
int damage = attacker.getPower().getValue();
if (hasTrample(attacker)) {
@ -190,7 +204,10 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
if (canDamage(blocker, first)) {
attacker.damage(blockerDamage, blocker.getId(), game, true, true);
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
int blockerDamage = blocker.getPower().getValue();
attacker.damage(blockerDamage, blocker.getId(), game, true, true);
}
}
}
}
@ -198,10 +215,11 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
private void multiBlockerDamage(boolean first, Game game) {
//TODO: handle banding
Permanent attacker = game.getPermanent(attackers.get(0));
if (attacker == null) {
return;
}
Player player = game.getPlayer(attacker.getControllerId());
int damage = attacker.getPower().getValue();
if (attacker == null)
return;
if (canDamage(attacker, first)) {
Map<UUID, Integer> assigned = new HashMap<UUID, Integer>();
for (UUID blockerId: blockerOrder) {
@ -230,7 +248,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
for (UUID blockerId: blockerOrder) {
Permanent blocker = game.getPermanent(blockerId);
if (canDamage(blocker, first)) {
attacker.damage(blocker.getPower().getValue(), blocker.getId(), game, true, true);
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
attacker.damage(blocker.getPower().getValue(), blocker.getId(), game, true, true);
}
}
}
// Issue#73
@ -249,6 +269,69 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
/**
* Damages attacking creatures by a creature that blocked several ones
* Damages only attackers as blocker was damage in {@link #singleBlockerDamage}.
*
* Handles abilities like "{this} an block any number of creatures.".
*
* @param first
* @param game
*/
private void singleAttackerDamage(boolean first, Game game) {
Permanent blocker = game.getPermanent(blockers.get(0));
Permanent attacker = game.getPermanent(attackers.get(0));
if (blocker != null && attacker != null) {
if (canDamage(blocker, first)) {
int damage = blocker.getPower().getValue();
attacker.damage(damage, blocker.getId(), game, true, true);
}
}
}
/**
* Damages attacking creatures by a creature that blocked several ones
* Damages only attackers as blocker was damage in either {@link #singleBlockerDamage} or {@link #multiBlockerDamage}.
*
* Handles abilities like "{this} an block any number of creatures.".
*
* @param first
* @param game
*/
private void multiAttackerDamage(boolean first, Game game) {
Permanent blocker = game.getPermanent(blockers.get(0));
Player player = game.getPlayer(blocker.getControllerId());
if (blocker == null) {
return;
}
int damage = blocker.getPower().getValue();
if (canDamage(blocker, first)) {
Map<UUID, Integer> assigned = new HashMap<UUID, Integer>();
for (UUID attackerId: attackerOrder) {
Permanent attacker = game.getPermanent(attackerId);
int lethalDamage;
if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId()))
lethalDamage = 1;
else
lethalDamage = attacker.getToughness().getValue() - attacker.getDamage();
if (lethalDamage >= damage) {
assigned.put(attackerId, damage);
damage = 0;
break;
}
int damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game);
assigned.put(attackerId, damageAssigned);
damage -= damageAssigned;
}
for (Map.Entry<UUID, Integer> entry : assigned.entrySet()) {
Permanent attacker = game.getPermanent(entry.getKey());
attacker.damage(entry.getValue(), blocker.getId(), game, true, true);
}
}
}
private void defenderDamage(Permanent attacker, int amount, Game game) {
if (this.defenderIsPlaneswalker) {
Permanent defender = game.getPermanent(defenderId);
@ -277,7 +360,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
Permanent blocker = game.getPermanent(blockerId);
if (blockerId != null) {
if (blockerId != null && blocker != null) {
blocker.setBlocking(blocker.getBlocking() + 1);
blockers.add(blockerId);
blockerOrder.add(blockerId);
@ -310,6 +393,29 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
}
}
public void pickAttackerOrder(UUID playerId, Game game) {
if (attackers.isEmpty())
return;
Player player = game.getPlayer(playerId);
List<UUID> attackerList = new ArrayList<UUID>(attackers);
attackerOrder.clear();
while (true) {
if (attackerList.size() == 1) {
attackerOrder.add(attackerList.get(0));
break;
}
else {
List<Permanent> attackerPerms = new ArrayList<Permanent>();
for (UUID attackerId: attackerList) {
attackerPerms.add(game.getPermanent(attackerId));
}
UUID attackerId = player.chooseAttackerOrder(attackerPerms, game);
attackerOrder.add(attackerId);
attackerList.remove(attackerId);
}
}
}
public int totalAttackerDamage(Game game) {
int total = 0;

View file

@ -70,6 +70,9 @@ public class CombatDamageStep extends Step<CombatDamageStep> {
for (CombatGroup group: game.getCombat().getGroups()) {
group.assignDamage(first, game);
}
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
group.assignDamageToAttackers(first, game);
}
}
public boolean getFirst() {

View file

@ -165,6 +165,7 @@ public interface Player extends MageItem, Copyable<Player> {
public abstract Mode chooseMode(Modes modes, Ability source, Game game);
public abstract void selectAttackers(Game game);
public abstract void selectBlockers(Game game);
public abstract UUID chooseAttackerOrder(List<Permanent> attacker, Game game);
public abstract UUID chooseBlockerOrder(List<Permanent> blockers, Game game);
public abstract void assignDamage(int damage, List<UUID> targets, String singleTargetName, UUID sourceId, Game game);
public abstract int getAmount(int min, int max, String message, Game game);

View file

@ -849,6 +849,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
CombatGroup group = game.getCombat().findGroup(attackerId);
if (blocker != null && group != null && group.canBlock(blocker, game)) {
group.addBlocker(blockerId, playerId, game);
game.getCombat().addBlockingGroup(blockerId, attackerId, playerId, game);
}
}