fixes + optimizations + updates to monte carlo ai

This commit is contained in:
BetaSteward 2012-01-24 22:51:31 -05:00
parent 23616432e4
commit 7fce6c552d
16 changed files with 312 additions and 228 deletions

2
.gitignore vendored
View file

@ -48,3 +48,5 @@ Mage.Server.Plugins/Mage.Draft.8PlayerBooster/target
\.chg\..*$
\.rej$
\.conflict\~$
/Mage.Server.Plugins/Mage.Player.AIMCTS/target/
/Mage.Server.Console/target/

View file

@ -38,6 +38,10 @@ import mage.Constants.Zone;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.common.PassAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.VariableManaCost;
import mage.cards.Card;
import mage.game.Game;
import mage.game.combat.Combat;
@ -52,8 +56,9 @@ import org.apache.log4j.Logger;
*/
public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> implements Player {
private static final int THINK_MIN_RATIO = 20;
private static final int THINK_MIN_RATIO = 40;
private static final int THINK_MAX_RATIO = 100;
private static final boolean USE_MULTIPLE_THREADS = false;
protected transient MCTSNode root;
protected int maxThinkTime;
@ -84,7 +89,7 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
@Override
public boolean priority(Game game) {
if (game.getStep().getType() == PhaseStep.DRAW)
if (game.getStep().getType() == PhaseStep.PRECOMBAT_MAIN)
logList("computer player " + name + " hand: ", new ArrayList(hand.getCards(game)));
game.firePriorityEvent(playerId);
getNextAction(game, NextAction.PRIORITY);
@ -111,7 +116,7 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
protected void getNextAction(Game game, NextAction nextAction) {
if (root != null) {
MCTSNode newRoot = null;
MCTSNode newRoot;
newRoot = root.getMatchingState(game.getState().getValue(false, game));
if (newRoot != null) {
newRoot.emancipate();
@ -161,7 +166,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
//
// @Override
// public boolean chooseUse(Outcome outcome, String message, Game game) {
// throw new UnsupportedOperationException("Not supported yet.");
// getNextAction(game, NextAction.CHOOSE_USE);
// return root.get
// }
//
// @Override
@ -174,11 +180,20 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
// throw new UnsupportedOperationException("Not supported yet.");
// }
// @Override
// public boolean playXMana(VariableManaCost cost, ManaCosts<ManaCost> costs, Game game) {
// throw new UnsupportedOperationException("Not supported yet.");
// }
//
@Override
public boolean playXMana(VariableManaCost cost, ManaCosts<ManaCost> costs, Game game) {
//MCTSPlayer.simulateVariableCosts method adds a generic mana cost for each option
for (ManaCost manaCost: costs) {
if (manaCost instanceof GenericManaCost) {
cost.setPayment(manaCost.getPayment());
logger.debug("using X = " + cost.getPayment().count());
break;
}
}
cost.setPaid();
return true;
}
// @Override
// public int chooseEffect(List<ReplacementEffect> rEffects, Game game) {
// throw new UnsupportedOperationException("Not supported yet.");
@ -261,68 +276,69 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
logger.info("applyMCTS - Thinking for " + (endTime - startTime)/1000000000.0 + "s");
if (thinkTime > 0) {
// List<MCTSExecutor> tasks = new ArrayList<MCTSExecutor>();
// for (int i = 0; i < cores; i++) {
// Game sim = createMCTSGame(game);
// MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId);
// player.setNextAction(action);
// MCTSExecutor exec = new MCTSExecutor(sim, playerId, thinkTime);
// tasks.add(exec);
// }
//
// try {
// pool.invokeAll(tasks);
// } catch (InterruptedException ex) {
// logger.warn("applyMCTS interrupted");
// }
//
// for (MCTSExecutor task: tasks) {
// root.merge(task.getRoot());
// task.clear();
// }
// tasks.clear();
MCTSNode current;
int simCount = 0;
while (true) {
long currentTime = System.nanoTime();
if (currentTime > endTime)
break;
current = root;
// Selection
while (!current.isLeaf()) {
current = current.select(this.playerId);
if (USE_MULTIPLE_THREADS) {
List<MCTSExecutor> tasks = new ArrayList<MCTSExecutor>();
for (int i = 0; i < cores; i++) {
Game sim = createMCTSGame(game);
MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId);
player.setNextAction(action);
MCTSExecutor exec = new MCTSExecutor(sim, playerId, thinkTime);
tasks.add(exec);
}
int result;
if (!current.isTerminal()) {
// Expansion
current.expand();
try {
pool.invokeAll(tasks);
} catch (InterruptedException ex) {
logger.warn("applyMCTS interrupted");
}
// Simulation
current = current.select(this.playerId);
result = current.simulate(this.playerId);
simCount++;
for (MCTSExecutor task: tasks) {
root.merge(task.getRoot());
task.clear();
}
else {
result = current.isWinner(this.playerId)?1:-1;
}
// Backpropagation
current.backpropagate(result);
tasks.clear();
}
else {
MCTSNode current;
int simCount = 0;
while (true) {
long currentTime = System.nanoTime();
if (currentTime > endTime)
break;
current = root;
logger.info("Simulated " + simCount + " games - nodes in tree: " + root.size());
// Selection
while (!current.isLeaf()) {
current = current.select(this.playerId);
}
int result;
if (!current.isTerminal()) {
// Expansion
current.expand();
// Simulation
current = current.select(this.playerId);
result = current.simulate(this.playerId);
simCount++;
}
else {
result = current.isWinner(this.playerId)?1:-1;
}
// Backpropagation
current.backpropagate(result);
}
logger.info("Simulated " + simCount + " games - nodes in tree: " + root.size());
}
displayMemory();
}
// root.print(1);
return;
}
//try to ensure that there are at least THINK_MIN_RATIO simulations per node at all times
private int calculateThinkTime(Game game, NextAction action) {
int thinkTime = 0;
int thinkTime;
int nodeSizeRatio = 0;
if (root.getNumChildren() > 0)
nodeSizeRatio = root.getVisits() / root.getNumChildren();

View file

@ -57,16 +57,10 @@ public class MCTSExecutor implements Callable<Boolean> {
long endTime = startTime + (thinkTime * 1000000000l);
MCTSNode current;
// if (root.getNumChildren() == 1)
// //there is only one possible action - don't spend a lot of time thinking
// endTime = startTime + 1000000000l;
// logger.info("applyMCTS - Thinking for " + (endTime - startTime)/1000000000.0 + "s");
while (true) {
long currentTime = System.nanoTime();
// logger.info("Remaining time: " + (endTime - currentTime)/1000000000.0 + "s");
if (currentTime > endTime)
// if (root.getNodeCount() > 50)
break;
current = root;
@ -80,14 +74,17 @@ public class MCTSExecutor implements Callable<Boolean> {
// Expansion
current.expand();
// if (current == root && current.getNumChildren() == 1)
// //there is only one possible action - don't spend a lot of time thinking
// endTime = startTime + 1000000000l;
// Simulation
current = current.select(this.playerId);
result = current.simulate(this.playerId);
simCount++;
// only run simulations for nodes that have siblings
if (current.getNumChildren() > 1) {
// Simulation
current = current.select(this.playerId);
result = current.simulate(this.playerId);
simCount++;
}
else {
current = current.select(this.playerId);
result = 0;
}
}
else {
result = current.isWinner(this.playerId)?1:-1;
@ -95,8 +92,7 @@ public class MCTSExecutor implements Callable<Boolean> {
// Backpropagation
current.backpropagate(result);
}
// logger.info("Created " + root.getNodeCount() + " nodes");
logger.info("Simulated " + simCount + " nodes");
logger.info("Simulated " + simCount + " games - nodes in tree: " + root.size());
return true;
}

View file

@ -42,7 +42,6 @@ import mage.game.Game;
import mage.game.combat.Combat;
import mage.game.combat.CombatGroup;
import mage.game.turn.Step.StepPart;
import mage.player.ai.MCTSPlayer.NextAction;
import mage.players.Player;
import org.apache.log4j.Logger;
@ -53,7 +52,7 @@ import org.apache.log4j.Logger;
public class MCTSNode {
private static final double selectionCoefficient = 1.0;
private static final double passRatioTolerance = 0.01;
private static final double passRatioTolerance = 0.0;
private final static transient Logger logger = Logger.getLogger(MCTSNode.class);
private int visits = 0;
@ -64,12 +63,14 @@ public class MCTSNode {
private Game game;
private String stateValue;
private UUID playerId;
private boolean terminal = false;
private static int nodeCount;
public MCTSNode(Game game) {
this.game = game;
this.stateValue = game.getState().getValue(false, game);
this.terminal = game.isGameOver();
setPlayer();
nodeCount = 1;
}
@ -77,6 +78,7 @@ public class MCTSNode {
protected MCTSNode(MCTSNode parent, Game game, Ability action) {
this.game = game;
this.stateValue = game.getState().getValue(false, game);
this.terminal = game.isGameOver();
this.parent = parent;
this.action = action;
setPlayer();
@ -86,6 +88,7 @@ public class MCTSNode {
protected MCTSNode(MCTSNode parent, Game game) {
this.game = game;
this.stateValue = game.getState().getValue(false, game);
this.terminal = game.isGameOver();
this.parent = parent;
setPlayer();
nodeCount++;
@ -144,6 +147,7 @@ public class MCTSNode {
sim.resume();
children.add(new MCTSNode(this, sim, ability));
}
game = null;
break;
case SELECT_ATTACKERS:
// logger.info("Select attackers:" + player.getName());
@ -185,7 +189,7 @@ public class MCTSNode {
Game sim = createSimulation(game, playerId);
sim.resume();
// long duration = System.nanoTime() - startTime;
int retVal = 0; //anything other than a win is a loss
int retVal = -1; //anything other than a win is a loss
for (Player simPlayer: sim.getPlayers().values()) {
// logger.info(simPlayer.getName() + " calculated " + ((SimulatedPlayerMCTS)simPlayer).getActionCount() + " actions in " + duration/1000000000.0 + "s");
if (simPlayer.getId().equals(playerId) && simPlayer.hasWon()) {
@ -197,6 +201,8 @@ public class MCTSNode {
}
public void backpropagate(int result) {
if (result == 0)
return;
if (result == 1)
wins++;
visits++;
@ -232,6 +238,7 @@ public class MCTSNode {
//favour passing vs any other action if ratio is close
double ratio = node.wins/(node.visits * 1.0);
if (ratio > bestRatio - passRatioTolerance) {
logger.info("choosing pass over " + bestChild.getAction());
bestChild = node;
bestCount = node.visits;
bestRatio = ratio;
@ -329,7 +336,7 @@ public class MCTSNode {
}
public boolean isTerminal() {
return game.isGameOver();
return terminal;
}
public boolean isWinner(UUID playerId) {

View file

@ -30,6 +30,7 @@ package mage.player.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.Constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.PassAbility;
@ -115,7 +116,7 @@ public class MCTSPlayer extends ComputerPlayer<MCTSPlayer> {
}
for (int i = start; i < numAvailable; i++) {
Ability newAbility = ability.copy();
newAbility.addManaCost(new GenericManaCost(i));
newAbility.getManaCostsToPay().add(new GenericManaCost(i));
options.add(newAbility);
}
}
@ -242,7 +243,9 @@ public class MCTSPlayer extends ComputerPlayer<MCTSPlayer> {
//
// @Override
// public boolean chooseUse(Outcome outcome, String message, Game game) {
// game.end();
// game.pause();
// nextAction = NextAction.CHOOSE_USE;
// return false;
// }
//
// @Override
@ -267,14 +270,12 @@ public class MCTSPlayer extends ComputerPlayer<MCTSPlayer> {
@Override
public void selectAttackers(Game game) {
// logger.info("Paused for select attackers for player:" + getName());
game.pause();
nextAction = NextAction.SELECT_ATTACKERS;
}
@Override
public void selectBlockers(Game game) {
// logger.info("Paused for select blockers for player:" + getName());
game.pause();
nextAction = NextAction.SELECT_BLOCKERS;
}

View file

@ -51,13 +51,11 @@ import mage.abilities.effects.ReplacementEffect;
import mage.abilities.mana.ManaAbility;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.choices.Choice;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.permanent.Permanent;
import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetAmount;
@ -133,7 +131,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
if (ability.getManaCosts().getVariableCosts().size() > 0) {
int amount = getAvailableManaProducers(game).size() - ability.getManaCosts().convertedManaCost();
if (amount > 0)
ability.addManaCost(new GenericManaCost(rnd.nextInt(amount)));
ability.getManaCostsToPay().add(new GenericManaCost(rnd.nextInt(amount)));
}
// check if ability kills player, if not then it's ok to play
// if (ability.isUsesStack()) {
@ -278,67 +276,82 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) {
return chooseRandom(target, game);
if (this.isHuman())
return chooseRandom(target, game);
return super.choose(outcome, target, sourceId, game);
}
@Override
public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map<String, Serializable> options) {
return chooseRandom(target, game);
if (this.isHuman())
return chooseRandom(target, game);
return super.choose(outcome, target, sourceId, game, options);
}
@Override
public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game) {
if (cards.isEmpty())
return !target.isRequired();
Set<UUID> possibleTargets = target.possibleTargets(playerId, cards, game);
if (possibleTargets.isEmpty())
return !target.isRequired();
Iterator<UUID> it = possibleTargets.iterator();
int targetNum = rnd.nextInt(possibleTargets.size());
UUID targetId = it.next();
for (int i = 0; i < targetNum; i++) {
targetId = it.next();
if (this.isHuman()) {
if (cards.isEmpty())
return !target.isRequired();
Set<UUID> possibleTargets = target.possibleTargets(playerId, cards, game);
if (possibleTargets.isEmpty())
return !target.isRequired();
Iterator<UUID> it = possibleTargets.iterator();
int targetNum = rnd.nextInt(possibleTargets.size());
UUID targetId = it.next();
for (int i = 0; i < targetNum; i++) {
targetId = it.next();
}
target.add(targetId, game);
return true;
}
target.add(targetId, game);
return true;
return super.choose(outcome, cards, target, game);
}
@Override
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
return chooseRandomTarget(target, source, game);
if (this.isHuman())
return chooseRandomTarget(target, source, game);
return super.chooseTarget(outcome, target, source, game);
}
@Override
public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
if (cards.isEmpty())
return !target.isRequired();
Card card = cards.getRandom(game);
target.addTarget(card.getId(), source, game);
return true;
if (this.isHuman()) {
if (cards.isEmpty())
return !target.isRequired();
Card card = cards.getRandom(game);
target.addTarget(card.getId(), source, game);
return true;
}
return super.chooseTarget(outcome, cards, target, source, game);
}
@Override
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
Set<UUID> possibleTargets = target.possibleTargets(source==null?null:source.getSourceId(), playerId, game);
if (possibleTargets.isEmpty())
return !target.isRequired();
if (!target.isRequired()) {
if (rnd.nextInt(possibleTargets.size() + 1) == 0) {
return false;
if (this.isHuman()) {
Set<UUID> possibleTargets = target.possibleTargets(source==null?null:source.getSourceId(), playerId, game);
if (possibleTargets.isEmpty())
return !target.isRequired();
if (!target.isRequired()) {
if (rnd.nextInt(possibleTargets.size() + 1) == 0) {
return false;
}
}
}
if (possibleTargets.size() == 1) {
target.addTarget(possibleTargets.iterator().next(), target.getAmountRemaining(), source, game);
if (possibleTargets.size() == 1) {
target.addTarget(possibleTargets.iterator().next(), target.getAmountRemaining(), source, game);
return true;
}
Iterator<UUID> it = possibleTargets.iterator();
int targetNum = rnd.nextInt(possibleTargets.size());
UUID targetId = it.next();
for (int i = 0; i < targetNum; i++) {
targetId = it.next();
}
target.addTarget(targetId, rnd.nextInt(target.getAmountRemaining()) + 1, source, game);
return true;
}
Iterator<UUID> it = possibleTargets.iterator();
int targetNum = rnd.nextInt(possibleTargets.size());
UUID targetId = it.next();
for (int i = 0; i < targetNum; i++) {
targetId = it.next();
}
target.addTarget(targetId, rnd.nextInt(target.getAmountRemaining()) + 1, source, game);
return true;
return super.chooseTargetAmount(outcome, target, source, game);
}
@Override
@ -348,19 +361,24 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public boolean chooseUse(Outcome outcome, String message, Game game) {
return rnd.nextBoolean();
if (this.isHuman())
return rnd.nextBoolean();
return super.chooseUse(outcome, message, game);
}
@Override
public boolean choose(Outcome outcome, Choice choice, Game game) {
Iterator<String> it = choice.getChoices().iterator();
String sChoice = it.next();
int choiceNum = rnd.nextInt(choice.getChoices().size());
for (int i = 0; i < choiceNum; i++) {
sChoice = it.next();
if (this.isHuman()) {
Iterator<String> it = choice.getChoices().iterator();
String sChoice = it.next();
int choiceNum = rnd.nextInt(choice.getChoices().size());
for (int i = 0; i < choiceNum; i++) {
sChoice = it.next();
}
choice.setChoice(sChoice);
return true;
}
choice.setChoice(sChoice);
return true;
return super.choose(outcome, choice, game);
}
@Override
@ -383,70 +401,87 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public int chooseEffect(List<ReplacementEffect> rEffects, Game game) {
return rnd.nextInt(rEffects.size());
if (this.isHuman())
return rnd.nextInt(rEffects.size());
return super.chooseEffect(rEffects, game);
}
@Override
public TriggeredAbility chooseTriggeredAbility(TriggeredAbilities abilities, Game game) {
return abilities.get(rnd.nextInt(abilities.size()));
if (this.isHuman())
return abilities.get(rnd.nextInt(abilities.size()));
return super.chooseTriggeredAbility(abilities, game);
}
@Override
public Mode chooseMode(Modes modes, Ability source, Game game) {
Iterator<Mode> it = modes.values().iterator();
Mode mode = it.next();
if (modes.size() == 1)
if (this.isHuman()) {
Iterator<Mode> it = modes.values().iterator();
Mode mode = it.next();
if (modes.size() == 1)
return mode;
int modeNum = rnd.nextInt(modes.values().size());
for (int i = 0; i < modeNum; i++) {
mode = it.next();
}
return mode;
int modeNum = rnd.nextInt(modes.values().size());
for (int i = 0; i < modeNum; i++) {
mode = it.next();
}
return mode;
return super.chooseMode(modes, source, game);
}
@Override
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
return attackers.get(rnd.nextInt(attackers.size())).getId();
if (this.isHuman())
return attackers.get(rnd.nextInt(attackers.size())).getId();
return super.chooseAttackerOrder(attackers, game);
}
@Override
public UUID chooseBlockerOrder(List<Permanent> blockers, Game game) {
return blockers.get(rnd.nextInt(blockers.size())).getId();
if (this.isHuman())
return blockers.get(rnd.nextInt(blockers.size())).getId();
return super.chooseBlockerOrder(blockers, game);
}
@Override
public void assignDamage(int damage, List<UUID> targets, String singleTargetName, UUID sourceId, Game game) {
int remainingDamage = damage;
UUID targetId;
int amount;
while (remainingDamage > 0) {
if (targets.size() == 1) {
targetId = targets.get(0);
amount = remainingDamage;
}
else {
targetId = targets.get(rnd.nextInt(targets.size()));
amount = rnd.nextInt(damage + 1);
}
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
permanent.damage(amount, sourceId, game, true, false);
remainingDamage -= amount;
}
else {
Player player = game.getPlayer(targetId);
if (player != null) {
player.damage(amount, sourceId, game, false, true);
if (this.isHuman()) {
int remainingDamage = damage;
UUID targetId;
int amount;
while (remainingDamage > 0) {
if (targets.size() == 1) {
targetId = targets.get(0);
amount = remainingDamage;
}
else {
targetId = targets.get(rnd.nextInt(targets.size()));
amount = rnd.nextInt(damage + 1);
}
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
permanent.damage(amount, sourceId, game, true, false);
remainingDamage -= amount;
}
else {
Player player = game.getPlayer(targetId);
if (player != null) {
player.damage(amount, sourceId, game, false, true);
remainingDamage -= amount;
}
}
targets.remove(targetId);
}
targets.remove(targetId);
}
else
super.assignDamage(damage, targets, singleTargetName, sourceId, game);
}
@Override
public int getAmount(int min, int max, String message, Game game) {
return rnd.nextInt(max - min) + min;
if (this.isHuman())
return rnd.nextInt(max - min) + min;
return super.getAmount(min, max, message, game);
}
}

View file

@ -150,7 +150,7 @@ public class SimulatedPlayer extends ComputerPlayer<SimulatedPlayer> {
}
for (int i = start; i < numAvailable; i++) {
Ability newAbility = ability.copy();
newAbility.addCost(new GenericManaCost(i));
newAbility.getManaCostsToPay().add(new GenericManaCost(i));
allActions.add(newAbility);
}
}

View file

@ -374,9 +374,14 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
@Override
public void addCost(Cost cost) {
if (cost != null) {
this.costs.add(cost);
}
if (cost != null) {
if (cost instanceof ManaCost) {
this.addManaCost((ManaCost)cost);
}
else {
this.costs.add(cost);
}
}
}
@Override

View file

@ -99,8 +99,6 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
if (cost != null) {
if (cost instanceof PhyrexianManaCost) {
this.addManaCost((PhyrexianManaCost)cost);
} else if (cost instanceof ManaCost) {
this.addManaCost((ManaCost) cost);
} else {
this.addCost(cost);
}

View file

@ -45,6 +45,7 @@ import mage.Constants.Layer;
import mage.Constants.SubLayer;
import mage.Constants.Zone;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -245,17 +246,20 @@ public class ContinuousEffects implements Serializable {
public List<RequirementEffect> getApplicableRequirementEffects(Permanent permanent, Game game) {
List<RequirementEffect> effects = new ArrayList<RequirementEffect>();
//get all applicable Requirement effects on the battlefield
for (Permanent perm: game.getBattlefield().getActivePermanents(permanent.getControllerId(), game)) {
for (Entry<Effect, Ability> entry: perm.getAbilities().getEffects(game, Zone.BATTLEFIELD, EffectType.REQUIREMENT).entrySet()) {
if (((RequirementEffect)entry.getKey()).applies(permanent, entry.getValue(), game)) {
effects.add((RequirementEffect)entry.getKey());
abilityMap.put(entry.getKey().getId(), entry.getValue());
}
}
}
// for (Permanent perm: game.getBattlefield().getActivePermanents(permanent.getControllerId(), game)) {
// for (Entry<Effect, Ability> entry: perm.getAbilities().getEffects(game, Zone.BATTLEFIELD, EffectType.REQUIREMENT).entrySet()) {
// if (((RequirementEffect)entry.getKey()).applies(permanent, entry.getValue(), game)) {
// effects.add((RequirementEffect)entry.getKey());
// abilityMap.put(entry.getKey().getId(), entry.getValue());
// }
// }
// }
for (RequirementEffect effect: requirementEffects) {
if (effect.applies(permanent, abilityMap.get(effect.getId()), game))
effects.add(effect);
Ability ability = abilityMap.get(effect.getId());
if (!(ability instanceof StaticAbility) || ability.getZone() == game.getZone(ability.getSourceId())) {
if (effect.applies(permanent, ability, game))
effects.add(effect);
}
}
return effects;
}
@ -263,17 +267,20 @@ public class ContinuousEffects implements Serializable {
public List<RestrictionEffect> getApplicableRestrictionEffects(Permanent permanent, Game game) {
List<RestrictionEffect> effects = new ArrayList<RestrictionEffect>();
//get all applicable Restriction effects on the battlefield
for (Permanent perm: game.getBattlefield().getActivePermanents(permanent.getControllerId(), game)) {
for (Entry<Effect, Ability> entry: perm.getAbilities().getEffects(game, Zone.BATTLEFIELD, EffectType.RESTRICTION).entrySet()) {
if (((RestrictionEffect)entry.getKey()).applies(permanent, entry.getValue(), game)) {
effects.add((RestrictionEffect)entry.getKey());
abilityMap.put(entry.getKey().getId(), entry.getValue());
}
}
}
// for (Permanent perm: game.getBattlefield().getActivePermanents(permanent.getControllerId(), game)) {
// for (Entry<Effect, Ability> entry: perm.getAbilities().getEffects(game, Zone.BATTLEFIELD, EffectType.RESTRICTION).entrySet()) {
// if (((RestrictionEffect)entry.getKey()).applies(permanent, entry.getValue(), game)) {
// effects.add((RestrictionEffect)entry.getKey());
// abilityMap.put(entry.getKey().getId(), entry.getValue());
// }
// }
// }
for (RestrictionEffect effect: restrictionEffects) {
if (effect.applies(permanent, abilityMap.get(effect.getId()), game))
effects.add(effect);
Ability ability = abilityMap.get(effect.getId());
if (!(ability instanceof StaticAbility) || ability.getZone() == game.getZone(ability.getSourceId())) {
if (effect.applies(permanent, ability, game))
effects.add(effect);
}
}
return effects;
}
@ -289,49 +296,55 @@ public class ContinuousEffects implements Serializable {
if (planeswalkerRedirectionEffect.applies(event, null, game))
replaceEffects.add(planeswalkerRedirectionEffect);
//get all applicable Replacement effects in each players hand and graveyard
for (Card card: game.getCards()) {
Zone zone = game.getState().getZone(card.getId());
if (zone == Zone.HAND || zone == Zone.GRAVEYARD) {
for (Entry<ReplacementEffect, Ability> entry: card.getAbilities().getReplacementEffects(zone).entrySet()) {
if (entry.getKey().applies(event, entry.getValue(), game)) {
replaceEffects.add(entry.getKey());
abilityMap.put(entry.getKey().getId(), entry.getValue());
}
}
}
}
// for (Card card: game.getCards()) {
// Zone zone = game.getState().getZone(card.getId());
// if (zone == Zone.HAND || zone == Zone.GRAVEYARD) {
// for (Entry<ReplacementEffect, Ability> entry: card.getAbilities().getReplacementEffects(zone).entrySet()) {
// if (entry.getKey().applies(event, entry.getValue(), game)) {
// replaceEffects.add(entry.getKey());
// abilityMap.put(entry.getKey().getId(), entry.getValue());
// }
// }
// }
// }
//get all applicable Replacement effects on the battlefield
for (Permanent permanent: game.getBattlefield().getAllPermanents()) {
for (Entry<ReplacementEffect, Ability> entry: permanent.getAbilities().getReplacementEffects(Zone.BATTLEFIELD).entrySet()) {
if (entry.getKey().applies(event, entry.getValue(), game)) {
replaceEffects.add(entry.getKey());
abilityMap.put(entry.getKey().getId(), entry.getValue());
}
}
}
// for (Permanent permanent: game.getBattlefield().getAllPermanents()) {
// for (Entry<ReplacementEffect, Ability> entry: permanent.getAbilities().getReplacementEffects(Zone.BATTLEFIELD).entrySet()) {
// if (entry.getKey().applies(event, entry.getValue(), game)) {
// replaceEffects.add(entry.getKey());
// abilityMap.put(entry.getKey().getId(), entry.getValue());
// }
// }
// }
//get all applicable Replacement effects on players
for (Player player: game.getPlayers().values()) {
for (Entry<ReplacementEffect, Ability> entry: player.getAbilities().getReplacementEffects(Zone.BATTLEFIELD).entrySet()) {
if (entry.getKey().applies(event, entry.getValue(), game)) {
replaceEffects.add(entry.getKey());
abilityMap.put(entry.getKey().getId(), entry.getValue());
}
}
}
// for (Player player: game.getPlayers().values()) {
// for (Entry<ReplacementEffect, Ability> entry: player.getAbilities().getReplacementEffects(Zone.BATTLEFIELD).entrySet()) {
// if (entry.getKey().applies(event, entry.getValue(), game)) {
// replaceEffects.add(entry.getKey());
// abilityMap.put(entry.getKey().getId(), entry.getValue());
// }
// }
// }
//get all applicable transient Replacement effects
for (ReplacementEffect effect: replacementEffects) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, abilityMap.get(effect.getId()), game)) {
replaceEffects.add(effect);
}
}
Ability ability = abilityMap.get(effect.getId());
if (!(ability instanceof StaticAbility) || ability.getZone() == game.getZone(ability.getSourceId())) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, ability, game)) {
replaceEffects.add(effect);
}
}
}
}
for (PreventionEffect effect: preventionEffects) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, abilityMap.get(effect.getId()), game)) {
replaceEffects.add(effect);
}
}
Ability ability = abilityMap.get(effect.getId());
if (!(ability instanceof StaticAbility) || ability.getZone() == game.getZone(ability.getSourceId())) {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
if (effect.applies(event, ability, game)) {
replaceEffects.add(effect);
}
}
}
}
return replaceEffects;
}

View file

@ -72,6 +72,7 @@ import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import mage.abilities.effects.Effect;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializable {
@ -192,6 +193,15 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
watcher.setSourceId(card.getId());
state.getWatchers().add(watcher);
}
for (StaticAbility ability: card.getAbilities().getStaticAbilities(Zone.ALL)) {
for (Mode mode: ability.getModes().values()) {
for (Effect effect: mode.getEffects()) {
if (effect instanceof ContinuousEffect) {
state.addEffect((ContinuousEffect)effect, ability);
}
}
}
}
}
}

View file

@ -1228,8 +1228,9 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
Map<String, Ability> playableActivated = new HashMap<String, Ability>();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
for (ActivatedAbility ability: permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
if (canPlay(ability, available, game))
playableActivated.put(ability.toString(), ability);
if (!playableActivated.containsKey(ability.toString()))
if (canPlay(ability, available, game))
playableActivated.put(ability.toString(), ability);
}
}
playable.addAll(playableActivated.values());