mirror of
https://github.com/correl/mage.git
synced 2025-01-11 11:05:23 +00:00
fixes + optimizations + updates to monte carlo ai
This commit is contained in:
parent
23616432e4
commit
7fce6c552d
16 changed files with 312 additions and 228 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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/
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue