Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 2 additions & 118 deletions forge-game/src/main/java/forge/game/combat/AttackConstraints.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -82,62 +81,6 @@ public Pair<Map<Card, GameEntity>, Integer> getLegalAttackers() {

final Map<Map<Card, GameEntity>, Integer> possible = new LinkedHashMap<>();
final List<Attack> reqs = getSortedFilteredRequirements();
final CardCollection myPossibleAttackers = new CardCollection(possibleAttackers);

// First, remove all requirements of creatures that aren't going attack this combat anyway
final CardCollection attackersToRemove = new CardCollection();
for (final Card attacker : myPossibleAttackers) {
final Set<AttackRestrictionType> types = restrictions.get(attacker).getTypes();
if ((types.contains(AttackRestrictionType.NEED_TWO_OTHERS) && myMax <= 2
) || (
types.contains(AttackRestrictionType.NOT_ALONE) && myMax <= 1
) || (
types.contains(AttackRestrictionType.NEED_BLACK_OR_GREEN) && myMax <= 1
) || (
types.contains(AttackRestrictionType.NEED_GREATER_POWER) && myMax <= 1
)) {
reqs.removeIf(findAll(attacker));
attackersToRemove.add(attacker);
}
}
myPossibleAttackers.removeAll(attackersToRemove);
attackersToRemove.clear();

// Next, remove creatures with constraints that can't be fulfilled.
for (final Card attacker : myPossibleAttackers) {
final Set<AttackRestrictionType> types = restrictions.get(attacker).getTypes();
if (types.contains(AttackRestrictionType.NEED_BLACK_OR_GREEN)) {
if (!myPossibleAttackers.anyMatch(AttackRestrictionType.NEED_BLACK_OR_GREEN.getPredicate(attacker))) {
attackersToRemove.add(attacker);
}
} else if (types.contains(AttackRestrictionType.NEED_GREATER_POWER)) {
if (!myPossibleAttackers.anyMatch(AttackRestrictionType.NEED_GREATER_POWER.getPredicate(attacker))) {
attackersToRemove.add(attacker);
}
}
}
myPossibleAttackers.removeAll(attackersToRemove);
for (final Card toRemove : attackersToRemove) {
reqs.removeIf(findAll(toRemove));
}

// First, successively try each creature that must attack alone.
for (final Card attacker : myPossibleAttackers) {
if (restrictions.get(attacker).getTypes().contains(AttackRestrictionType.ONLY_ALONE)) {
final Attack attack = findFirst(reqs, attacker);
if (attack == null) {
// no requirements, we don't care anymore
continue;
}
final Map<Card, GameEntity> attackMap = ImmutableMap.of(attack.attacker, attack.defender);
final int violations = countViolations(attackMap);
if (violations != -1) {
possible.put(attackMap, violations);
}
// remove them from the requirements, as they'll not be relevant to this calculation any more
reqs.removeIf(findAll(attacker));
}
}

// Now try all others (plus empty attack) and count their violations
final FCollection<Map<Card, GameEntity>> legalAttackers = collectLegalAttackers(reqs, myMax);
Expand Down Expand Up @@ -167,7 +110,7 @@ private List<Map<Card, GameEntity>> collectLegalAttackers(final Map<Card, GameEn
final Map<GameEntity, Integer> toDefender = new LinkedHashMap<>();
int attackersNeeded = 0;

outer: while (!reqs.isEmpty()) {
while (!reqs.isEmpty()) {
final Iterator<Attack> iterator = reqs.iterator();
final Attack req = iterator.next();
final boolean isReserved = reserved.contains(req.attacker);
Expand Down Expand Up @@ -196,19 +139,8 @@ private List<Map<Card, GameEntity>> collectLegalAttackers(final Map<Card, GameEn
continue;
}

boolean haveTriedWithout = false;
final AttackRestriction restriction = restrictions.get(req.attacker);
final AttackRequirement requirement = requirements.get(req.attacker);

// construct the predicate restrictions
final Collection<Predicate<Card>> predicateRestrictions = Lists.newLinkedList();
for (final AttackRestrictionType rType : restriction.getTypes()) {
final Predicate<Card> predicate = rType.getPredicate(req.attacker);
if (predicate != null) {
predicateRestrictions.add(predicate);
}
}

if (!requirement.getCausesToAttack().isEmpty()) {
final List<Attack> clonedReqs = deepClone(reqs);
for (final Entry<Card, Collection<StaticAbility>> causesToAttack : requirement.getCausesToAttack().asMap().entrySet()) {
Expand All @@ -222,34 +154,6 @@ private List<Map<Card, GameEntity>> collectLegalAttackers(final Map<Card, GameEn
clonedReqs.removeIf(findAll(req.attacker));
final CardCollection clonedReserved = new CardCollection(reserved);
result.addAll(collectLegalAttackers(myAttackers, clonedReqs, clonedReserved, localMaximum));
haveTriedWithout = true;
}
}

for (final Predicate<Card> predicateRestriction : predicateRestrictions) {
if (Sets.union(myAttackers.keySet(), reserved.asSet()).stream().anyMatch(predicateRestriction)) {
// predicate fulfilled already, ignore!
continue;
}
// otherwise: reserve first creature to match it!
final Attack match = findFirst(reqs, predicateRestriction);
if (match == null) {
// no match: remove this creature completely
reqs.removeIf(findAll(req.attacker));
continue outer;
}
// found one! add it to reserve and lower local maximum
reserved.add(match.attacker);
localMaximum--;

// if limited, try both with and without this creature
if (!haveTriedWithout && isLimited) {
// try without
final List<Attack> clonedReqs = deepClone(reqs);
clonedReqs.removeIf(findAll(req.attacker));
final CardCollection clonedReserved = new CardCollection(reserved);
result.addAll(collectLegalAttackers(myAttackers, clonedReqs, clonedReserved, localMaximum));
haveTriedWithout = true;
}
}

Expand All @@ -259,15 +163,6 @@ private List<Map<Card, GameEntity>> collectLegalAttackers(final Map<Card, GameEn
reqs.removeIf(findAll(req.attacker));
reserved.remove(req.attacker);
localMaximum--;

// need two other attackers: set that number to the number of attackers we still need (but never < 0)
if (restrictions.get(req.attacker).getTypes().contains(AttackRestrictionType.NEED_TWO_OTHERS)) {
final int previousNeeded = attackersNeeded;
attackersNeeded = Math.max(3 - (myAttackers.size() + reserved.size()), 0);
localMaximum -= Math.max(attackersNeeded - previousNeeded, 0);
} else if (restrictions.get(req.attacker).getTypes().contains(AttackRestrictionType.NOT_ALONE)) {
attackersNeeded = Math.max(2 - (myAttackers.size() + reserved.size()), 0);
}
}

// success if we've added everything we want
Expand Down Expand Up @@ -296,7 +191,7 @@ public int compareTo(final Attack other) {
}
@Override
public String toString() {
return "[" + requirements + "] " + attacker + " to " + defender;
return "[" + requirements + "] " + attacker + " to " + defender;
}
}

Expand Down Expand Up @@ -345,17 +240,6 @@ private static List<Attack> deepClone(final List<Attack> original) {
}
return newList;
}
private static Attack findFirst(final List<Attack> reqs, final Predicate<Card> predicate) {
for (final Attack req : reqs) {
if (predicate.test(req.attacker)) {
return req;
}
}
return null;
}
private static Attack findFirst(final List<Attack> reqs, final Card attacker) {
return findFirst(reqs, attacker::equals);
}
private static Predicate<Attack> findAll(final Card attacker) {
return input -> input.attacker.equals(attacker);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ public int countViolations(final GameEntity defender, final Map<Card, GameEntity
- (isAttacking ? defenderSpecific.getOrDefault(defender, 0) : 0);
if (isAttacking) {
final Combat combat = defender.getGame().getCombat();
final Map<Card, AttackRestriction> constraints = combat.getAttackConstraints().getRestrictions();

// check if a restriction will apply such that the requirement is no longer relevant
if (attackers.size() != 1 || !constraints.get(attackers.entrySet().iterator().next().getKey()).getTypes().contains(AttackRestrictionType.ONLY_ALONE)) {
// TODO REFACTOR?!
if (attackers.size() != 1) {
for (final Map.Entry<Card, Collection<StaticAbility>> mustAttack : causesToAttack.asMap().entrySet()) {
if (constraints.get(mustAttack.getKey()).getTypes().contains(AttackRestrictionType.ONLY_ALONE)) continue;
int max = Objects.requireNonNullElse(GlobalAttackRestrictions.getGlobalRestrictions(mustAttack.getKey().getController(), combat.getDefenders()).getMax(), Integer.MAX_VALUE);

// only count violations if the forced creature can actually attack and has no cost incurred for doing so
Expand Down
69 changes: 9 additions & 60 deletions forge-game/src/main/java/forge/game/combat/AttackRestriction.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityAttackBlockRestrict;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;

public class AttackRestriction {

private final Card attacker;
private final Set<AttackRestrictionType> restrictions = EnumSet.noneOf(AttackRestrictionType.class);
private boolean cantAttack;
private final FCollectionView<GameEntity> cantAttackDefender;

public AttackRestriction(final Card attacker, final FCollectionView<GameEntity> possibleDefenders) {
this.attacker = attacker;
setRestrictions();

final FCollection<GameEntity> cantAttackDefender = new FCollection<>();
for (final GameEntity defender : possibleDefenders) {
Expand All @@ -26,15 +27,7 @@ public AttackRestriction(final Card attacker, final FCollectionView<GameEntity>
}
this.cantAttackDefender = cantAttackDefender;

if ((restrictions.contains(AttackRestrictionType.ONLY_ALONE) && (
restrictions.contains(AttackRestrictionType.NEED_GREATER_POWER) ||
restrictions.contains(AttackRestrictionType.NEED_BLACK_OR_GREEN) ||
restrictions.contains(AttackRestrictionType.NOT_ALONE) ||
restrictions.contains(AttackRestrictionType.NEED_TWO_OTHERS))
) || (
restrictions.contains(AttackRestrictionType.NEVER)
) || (
cantAttackDefender.size() == possibleDefenders.size())) {
if (cantAttackDefender.size() == possibleDefenders.size()) {
cantAttack = true;
}
}
Expand All @@ -43,61 +36,17 @@ public boolean canAttack(final GameEntity defender) {
return !cantAttack && !cantAttackDefender.contains(defender);
}

public Set<AttackRestrictionType> getViolation(final Map<Card, GameEntity> attackers) {
final Set<AttackRestrictionType> violations = EnumSet.noneOf(AttackRestrictionType.class);
final int nAttackers = attackers.size();
if (restrictions.contains(AttackRestrictionType.ONLY_ALONE) && nAttackers > 1) {
violations.add(AttackRestrictionType.ONLY_ALONE);
}
if (restrictions.contains(AttackRestrictionType.NEED_GREATER_POWER)
&& attackers.keySet().stream().noneMatch(AttackRestrictionType.NEED_GREATER_POWER.getPredicate(attacker))) {
violations.add(AttackRestrictionType.NEED_GREATER_POWER);
}
if (restrictions.contains(AttackRestrictionType.NEED_BLACK_OR_GREEN)
&& attackers.keySet().stream().noneMatch(AttackRestrictionType.NEED_BLACK_OR_GREEN.getPredicate(attacker))) {
violations.add(AttackRestrictionType.NEED_BLACK_OR_GREEN);
}
if (restrictions.contains(AttackRestrictionType.NOT_ALONE) && nAttackers <= 1) {
violations.add(AttackRestrictionType.NOT_ALONE);
}
if (restrictions.contains(AttackRestrictionType.NEED_TWO_OTHERS) && nAttackers <= 2) {
violations.add(AttackRestrictionType.NEED_TWO_OTHERS);
}
return violations;
public List<StaticAbility> getStaticViolations(final Map<Card, GameEntity> attackers) {
CardCollection others = new CardCollection(attackers.keySet());
others.remove(attacker);
return StaticAbilityAttackBlockRestrict.attackRestrict(attacker, others);
}

public boolean canAttack(final GameEntity defender, final Map<Card, GameEntity> attackers) {
if (!canAttack(defender)) {
return false;
}

return getViolation(attackers).isEmpty();
return getStaticViolations(attackers).isEmpty();
}

public Set<AttackRestrictionType> getTypes() {
return Collections.unmodifiableSet(restrictions);
}

private void setRestrictions() {
if (attacker.hasKeyword("CARDNAME can only attack alone.")) {
restrictions.add(AttackRestrictionType.ONLY_ALONE);
}

if (attacker.hasKeyword("CARDNAME can't attack unless a creature with greater power also attacks.")) {
restrictions.add(AttackRestrictionType.NEED_GREATER_POWER);
}

if (attacker.hasKeyword("CARDNAME can't attack unless a black or green creature also attacks.")) {
restrictions.add(AttackRestrictionType.NEED_BLACK_OR_GREEN);
}

if (attacker.hasKeyword("CARDNAME can't attack or block alone.") || attacker.hasKeyword("CARDNAME can't attack alone.")) {
restrictions.add(AttackRestrictionType.NOT_ALONE);
}

if (attacker.hasKeyword("CARDNAME can't attack unless at least two other creatures attack.")) {
restrictions.add(AttackRestrictionType.NEED_TWO_OTHERS);
}
}

}

This file was deleted.

Loading
Loading