package mage.game.permanent; import mage.abilities.Ability; import mage.abilities.keyword.PhasingAbility; import mage.constants.CardType; import mage.constants.RangeOfInfluence; import mage.filter.FilterPermanent; import mage.game.Game; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; /** * @author BetaSteward_at_googlemail.com */ public class Battlefield implements Serializable { private final Map field = new LinkedHashMap<>(); public Battlefield() { } public Battlefield(final Battlefield battlefield) { for (Entry entry : battlefield.field.entrySet()) { field.put(entry.getKey(), entry.getValue().copy()); } } public Battlefield copy() { return new Battlefield(this); } public void reset(Game game) { for (Permanent perm : field.values()) { perm.reset(game); } } public void clear() { field.clear(); } /** * Returns a count of all {@link Permanent} that match the filter and are * controlled by controllerId. *

* Some filter predicates do not work here (e.g. AnotherPredicate() because * filter.match() is called without controllerId. To use this predicates you * can use count() instead of countAll() * * @param filter * @param controllerId * @param game * @return count */ public int countAll(FilterPermanent filter, UUID controllerId, Game game) { return (int) field.values() .stream() .filter(permanent -> permanent.isControlledBy(controllerId) && filter.match(permanent, game) && permanent.isPhasedIn()) .count(); } /** * Returns a count of all {@link Permanent} that are within the range of * influence of the specified player id and that match the supplied filter. * * @param filter * @param sourcePlayerId * @param source * @param game * @return count */ public int count(FilterPermanent filter, UUID sourcePlayerId, Ability source, Game game) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return (int) field.values() .stream() .filter(permanent -> filter.match(permanent, sourcePlayerId, source, game) && permanent.isPhasedIn()) .count(); } else { List range = game.getState().getPlayersInRange(sourcePlayerId, game); return (int) field.values() .stream() .filter(permanent -> range.contains(permanent.getControllerId()) && filter.match(permanent, sourcePlayerId, source, game) && permanent.isPhasedIn()).count(); } } public boolean containsControlled(FilterPermanent filter, Ability source, Game game, int num) { return containsControlled(filter, source.getSourceId(), source.getControllerId(), source, game, num); } /** * Returns true if the battlefield contains num or more {@link Permanent} * that matches the filter and is controlled by controllerId. This method * ignores the range of influence. * * @param filter * @param sourceId * @param controllerId controller and source can be different (from different players) * @param source * @param game * @param num * @return boolean */ public boolean containsControlled(FilterPermanent filter, UUID sourceId, UUID controllerId, Ability source, Game game, int num) { return field.values() .stream() .filter(permanent -> permanent.isControlledBy(controllerId) && filter.match(permanent, controllerId, source, game) && permanent.isPhasedIn()) .count() >= num; } public boolean contains(FilterPermanent filter, Ability source, Game game, int num) { return contains(filter, source.getSourceId(), source.getControllerId(), source, game, num); } /** * Returns true if the battlefield contains num or more {@link Permanent} * that is within the range of influence of the specified player id and that * matches the supplied filter. * * @param filter * @param sourceId can be null for default SBA checks like legendary rule * @param sourcePlayerId * @param source * @param game * @param num * @return boolean */ public boolean contains(FilterPermanent filter, UUID sourceId, UUID sourcePlayerId, Ability source, Game game, int num) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return field.values().stream() .filter(permanent -> filter.match(permanent, sourcePlayerId, source, game) && permanent.isPhasedIn()).count() >= num; } else { List range = game.getState().getPlayersInRange(sourcePlayerId, game); return field.values().stream() .filter(permanent -> range.contains(permanent.getControllerId()) && filter.match(permanent, sourcePlayerId, source, game) && permanent.isPhasedIn()) .count() >= num; } } public void addPermanent(Permanent permanent) { field.put(permanent.getId(), permanent); } public Permanent getPermanent(UUID key) { return field.get(key); } public void removePermanent(UUID key) { field.remove(key); } /** * Find permanent on the battlefield by id. If you works with cards and want to check it on battlefield then * use game.getState().getZone() instead. Card's id and permanent's id can be different (example: mdf card * puts half card to battlefield, not the main card). * * @param key * @return */ public boolean containsPermanent(UUID key) { return field.containsKey(key); } public void beginningOfTurn(Game game) { for (Permanent perm : field.values()) { perm.beginningOfTurn(game); } } public void endOfTurn(UUID controllerId, Game game) { for (Permanent perm : field.values()) { perm.endOfTurn(game); } } public Collection getAllPermanents() { return field.values(); } public Set getAllPermanentIds() { return field.keySet(); } public List getAllActivePermanents() { return field.values() .stream() .filter(Permanent::isPhasedIn) .collect(Collectors.toList()); } /** * Returns all {@link Permanent} on the battlefield that are controlled by * the specified player id. The method ignores the range of influence. * * @param controllerId * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(UUID controllerId) { return field.values() .stream() .filter(perm -> perm.isPhasedIn() && perm.isControlledBy(controllerId)) .collect(Collectors.toList()); } /** * Returns all {@link Permanent} on the battlefield that match the specified * {@link CardType}. This method ignores the range of influence. * * @param type * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(CardType type, Game game) { return field.values() .stream() .filter(perm -> perm.isPhasedIn() && perm.getCardType(game).contains(type)) .collect(Collectors.toList()); } /** * Returns all {@link Permanent} on the battlefield that match the supplied * filter. This method ignores the range of influence and ignores * ObjectSourcePlayer predicates in the filter (e.g. controller predicates) * * @param filter * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(FilterPermanent filter, Game game) { return field.values() .stream() .filter(perm -> perm.isPhasedIn() && filter.match(perm, game)) .collect(Collectors.toList()); } /** * Returns all {@link Permanent} that match the filter and are controlled by * controllerId. This method ignores the range of influence and ignores * ObjectSourcePlayer predicates in the filter * * @param filter * @param controllerId * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(FilterPermanent filter, UUID controllerId, Game game) { return field.values() .stream() .filter(perm -> perm.isPhasedIn() && perm.isControlledBy(controllerId) && filter.match(perm, game)) .collect(Collectors.toList()); } /** * Returns all {@link Permanent} that are within the range of influence of * the specified player id and that match the supplied filter. * * @param filter * @param sourcePlayerId * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getActivePermanents(FilterPermanent filter, UUID sourcePlayerId, Game game) { return getActivePermanents(filter, sourcePlayerId, null, game); } /** * Returns all {@link Permanent} that are within the range of influence of * the specified player id and that match the supplied filter. * * @param filter * @param sourcePlayerId * @param source * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getActivePermanents(FilterPermanent filter, UUID sourcePlayerId, Ability source, Game game) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return field.values() .stream() .filter(perm -> perm.isPhasedIn() && filter.match(perm, sourcePlayerId, source, game)) .collect(Collectors.toList()); } else { List range = game.getState().getPlayersInRange(sourcePlayerId, game); return field.values() .stream() .filter(perm -> perm.isPhasedIn() && range.contains(perm.getControllerId()) && filter.match(perm, sourcePlayerId, source, game)).collect(Collectors.toList()); } } /** * Returns all {@link Permanent} that are within the range of influence of * the specified player id. * * @param sourcePlayerId * @param game * @return a list of {@link Permanent} * @see Permanent */ public List getActivePermanents(UUID sourcePlayerId, Game game) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return getAllActivePermanents(); } else { List range = game.getState().getPlayersInRange(sourcePlayerId, game); return field.values() .stream() .filter(perm -> perm.isPhasedIn() && range.contains(perm.getControllerId())) .collect(Collectors.toList()); } } public List getPhasedIn(Game game, UUID controllerId) { return field.values() .stream() .filter(perm -> perm.hasAbility(PhasingAbility.getInstance(), game) && perm.isPhasedIn() && perm.isControlledBy(controllerId)) .collect(Collectors.toList()); } public List getPhasedOut(Game game, UUID controllerId) { return field.values() .stream() .filter(perm -> !perm.isPhasedIn() && perm.isControlledBy(controllerId)) .collect(Collectors.toList()); } public void resetPermanentsControl() { for (Permanent perm : field.values()) { if (perm.isPhasedIn()) { perm.resetControl(); } } } /** * since control could change several times during applyEvents we only want * to fire control changed events after all control change effects have been * applied * * @param game * @return */ public boolean fireControlChangeEvents(Game game) { boolean controlChanged = false; for (Permanent perm : field.values()) { if (perm.isPhasedIn()) { controlChanged |= perm.checkControlChanged(game); } } return controlChanged; } public int countTokens(UUID controllerId) { return field .values() .stream() .filter(Objects::nonNull) .filter(PermanentToken.class::isInstance) .map(permanent -> permanent.isControlledBy(controllerId)) .mapToInt(x -> x ? 1 : 0) .sum(); } @Override public String toString() { return "Permanents: " + field.size(); } }