latest monte carlo ai - has a memory leak

This commit is contained in:
BetaSteward 2012-01-10 21:29:53 -05:00
parent a06f27ec89
commit dfffdfcf8c
38 changed files with 677 additions and 286 deletions

View file

@ -116,14 +116,14 @@ public class ComputerPlayer6 extends ComputerPlayer<ComputerPlayer6> implements
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
logState(game);
game.firePriorityEvent(playerId);
switch (game.getTurn().getStepType()) {
case UPKEEP:
case DRAW:
pass();
break;
return false;
case PRECOMBAT_MAIN:
case DECLARE_BLOCKERS:
case POSTCOMBAT_MAIN:
@ -134,16 +134,17 @@ public class ComputerPlayer6 extends ComputerPlayer<ComputerPlayer6> implements
calculateActions(game);
}
act(game);
return true;
} else {
pass();
}
break;
return false;
case BEGIN_COMBAT:
case FIRST_COMBAT_DAMAGE:
case COMBAT_DAMAGE:
case END_COMBAT:
pass();
break;
return false;
case DECLARE_ATTACKERS:
if (!game.getActivePlayerId().equals(playerId)) {
printOutState(game, playerId);
@ -152,18 +153,20 @@ public class ComputerPlayer6 extends ComputerPlayer<ComputerPlayer6> implements
calculateActions(game);
}
act(game);
return true;
//printOutState(game, playerId);
} else {
pass();
}
break;
return false;
case END_TURN:
pass();
break;
return false;
case CLEANUP:
pass();
break;
return false;
}
return false;
}
protected void printOutState(Game game, UUID playerId) {
@ -264,11 +267,11 @@ public class ComputerPlayer6 extends ComputerPlayer<ComputerPlayer6> implements
test = root;
root = root.children.get(0);
}
logger.info("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue);
logger.info("simlating -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue);
if (!suggested.isEmpty()) {
return false;
}
if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue().hashCode() == test.gameValue) {
if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue(true).hashCode() == test.gameValue) {
/*
// Try to fix horizon effect
@ -477,7 +480,7 @@ public class ComputerPlayer6 extends ComputerPlayer<ComputerPlayer6> implements
logger.info("interrupted");
return GameStateEvaluator2.evaluate(playerId, game);
}
node.setGameValue(game.getState().getValue().hashCode());
node.setGameValue(game.getState().getValue(true).hashCode());
SimulatedPlayer2 currentPlayer = (SimulatedPlayer2) game.getPlayer(game.getPlayerList().get());
//logger.info("simulating -- player " + currentPlayer.getName());
SimulationNode2 bestNode = null;

View file

@ -83,7 +83,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
logState(game);
if (logger.isDebugEnabled())
logger.debug("Game State: Turn-" + game.getTurnNum() + " Step-" + game.getTurn().getStepType() + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name);
@ -92,7 +92,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player {
case UPKEEP:
case DRAW:
pass();
break;
return false;
case PRECOMBAT_MAIN:
if (game.getActivePlayerId().equals(playerId)) {
System.out.println("Computer7:");
@ -102,13 +102,14 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player {
calculatePreCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case BEGIN_COMBAT:
pass();
break;
return false;
case DECLARE_ATTACKERS:
if (!game.getActivePlayerId().equals(playerId)) {
printOutState(game, playerId);
@ -117,16 +118,17 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player {
calculatePreCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case DECLARE_BLOCKERS:
case FIRST_COMBAT_DAMAGE:
case COMBAT_DAMAGE:
case END_COMBAT:
pass();
break;
return false;
case POSTCOMBAT_MAIN:
if (game.getActivePlayerId().equals(playerId)) {
printOutState(game, playerId);
@ -135,15 +137,17 @@ public class ComputerPlayer7 extends ComputerPlayer6 implements Player {
calculatePostCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case END_TURN:
case CLEANUP:
pass();
break;
return false;
}
return false;
}
protected void calculatePreCombatActions(Game game) {

View file

@ -265,7 +265,7 @@ public class SimulatedPlayer2 extends ComputerPlayer<SimulatedPlayer2> {
if (binary.charAt(j) == '1')
sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, sim);
}
if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) {
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) {
logger.debug("simulating -- found redundant attack combination");
}
else {
@ -289,7 +289,7 @@ public class SimulatedPlayer2 extends ComputerPlayer<SimulatedPlayer2> {
//add a node with no blockers
Game sim = game.copy();
engagements.put(sim.getCombat().getValue(sim), sim.getCombat());
engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat());
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId));
List<Permanent> blockers = getAvailableBlockers(game);
@ -310,7 +310,7 @@ public class SimulatedPlayer2 extends ComputerPlayer<SimulatedPlayer2> {
if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) {
Game sim = game.copy();
sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim);
if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null)
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null)
logger.debug("simulating -- found redundant block combination");
addBlocker(sim, remaining, engagements); // and recurse minus the used blocker
}
@ -360,7 +360,8 @@ public class SimulatedPlayer2 extends ComputerPlayer<SimulatedPlayer2> {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
//should never get here
return false;
}
}

View file

@ -79,6 +79,8 @@ import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
/**
*
@ -428,7 +430,7 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
log.debug("priority");
UUID opponentId = game.getOpponents(playerId).iterator().next();
if (game.getActivePlayerId().equals(playerId)) {
@ -449,12 +451,12 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
if (ability.canActivate(playerId, game)) {
if (ability.getEffects().hasOutcome(Outcome.PutLandInPlay)) {
if (this.activateAbility(ability, game))
return;
return true;
}
if (ability.getEffects().hasOutcome(Outcome.PutCreatureInPlay)) {
if (getOpponentBlockers(opponentId, game).size() <= 1)
if (this.activateAbility(ability, game))
return;
return true;
}
}
}
@ -476,7 +478,7 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
for (Card card: playableNonInstant) {
if (card.getSpellAbility().canActivate(playerId, game)) {
if (this.activateAbility(card.getSpellAbility(), game))
return;
return true;
}
}
}
@ -485,7 +487,7 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
if (ability.canActivate(playerId, game)) {
if (!(ability.getEffects().get(0) instanceof BecomesCreatureSourceEffect)) {
if (this.activateAbility(ability, game))
return;
return true;
}
}
}
@ -512,8 +514,20 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
}
}
pass();
return true;
}
@Override
public boolean activateAbility(ActivatedAbility ability, Game game) {
for (Target target: ability.getModes().getMode().getTargets()) {
for (UUID targetId: target.getTargets()) {
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, targetId, ability.getId(), ability.getControllerId()));
}
}
return super.activateAbility(ability, game);
}
protected void playLand(Game game) {
log.debug("playLand");
Set<Card> lands = hand.getCards(new FilterLandCard(), game);
@ -699,6 +713,11 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
}
}
}
// pay phyrexian life costs
if (cost instanceof PhyrexianManaCost) {
if (cost.pay(null, game, null, playerId, false))
return true;
}
return false;
}
@ -1402,7 +1421,7 @@ public class ComputerPlayer<T extends ComputerPlayer<T>> extends PlayerImpl<T> i
for (MageObject object: list) {
sb.append(object.getName()).append(",");
}
log.debug(sb.toString());
log.info(sb.toString());
}
protected void logAbilityList(String message, List<Ability> list) {

View file

@ -30,14 +30,15 @@ package mage.player.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import mage.Constants.PhaseStep;
import mage.Constants.RangeOfInfluence;
import mage.Constants.Zone;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.common.PassAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.combat.Combat;
import mage.game.combat.CombatGroup;
@ -51,16 +52,18 @@ import org.apache.log4j.Logger;
*/
public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> implements Player {
private static final int thinkTimeRatioThreshold = 20;
protected transient MCTSNode root;
protected int thinkTime;
protected int maxThinkTime;
private final static transient Logger logger = Logger.getLogger(ComputerPlayerMCTS.class);
private ExecutorService pool;
private transient ExecutorService pool;
private int cores;
public ComputerPlayerMCTS(String name, RangeOfInfluence range, int skill) {
super(name, range);
human = false;
thinkTime = skill;
maxThinkTime = (int) (skill * 1.5);
cores = Runtime.getRuntime().availableProcessors();
pool = Executors.newFixedThreadPool(cores);
}
@ -79,12 +82,18 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
if (game.getStep().getType() == PhaseStep.DRAW)
logList("computer player " + name + " hand: ", new ArrayList(hand.getCards(game)));
game.firePriorityEvent(playerId);
getNextAction(game, NextAction.PRIORITY);
Ability ability = root.getAction();
if (ability == null)
logger.fatal("null ability");
activateAbility((ActivatedAbility)ability, game);
if (ability instanceof PassAbility)
return false;
return true;
}
protected void calculateActions(Game game, NextAction action) {
@ -101,9 +110,13 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
protected void getNextAction(Game game, NextAction nextAction) {
if (root != null) {
root = root.getMatchingState(game.getState().getValue().hashCode(), nextAction);
if (root != null)
root.emancipate();
MCTSNode newRoot = null;
newRoot = root.getMatchingState(game.getState().getValue(false, game));
if (newRoot != null)
newRoot.emancipate();
else
logger.info("unable to find matching state");
root = newRoot;
}
calculateActions(game, nextAction);
}
@ -180,13 +193,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
@Override
public void selectAttackers(Game game) {
Game sim = createMCTSGame(game);
getNextAction(sim, NextAction.SELECT_ATTACKERS);
// MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId);
// player.setNextAction(MCTSPlayer.NextAction.SELECT_ATTACKERS);
// root = new MCTSNode(sim);
// applyMCTS();
Combat combat = root.bestChild().getCombat();
getNextAction(game, NextAction.SELECT_ATTACKERS);
Combat combat = root.getCombat();
UUID opponentId = game.getCombat().getDefenders().iterator().next();
for (UUID attackerId: combat.getAttackers()) {
this.declareAttacker(attackerId, opponentId, game);
@ -195,13 +203,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
@Override
public void selectBlockers(Game game) {
Game sim = createMCTSGame(game);
getNextAction(sim, NextAction.SELECT_BLOCKERS);
// MCTSPlayer player = (MCTSPlayer) sim.getPlayer(playerId);
// player.setNextAction(MCTSPlayer.NextAction.SELECT_BLOCKERS);
// root = new MCTSNode(sim);
// applyMCTS();
Combat combat = root.bestChild().getCombat();
getNextAction(game, NextAction.SELECT_BLOCKERS);
Combat combat = root.getCombat();
List<CombatGroup> groups = game.getCombat().getGroups();
for (int i = 0; i < groups.size(); i++) {
if (i < combat.getGroups().size()) {
@ -248,36 +251,83 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
// }
protected void applyMCTS(final Game game, final NextAction action) {
int thinkTime = calculateThinkTime(game, action);
long startTime = System.nanoTime();
long endTime = startTime + (thinkTime * 1000000000l);
logger.info("applyMCTS - Thinking for " + (endTime - startTime)/1000000000.0 + "s");
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);
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();
logger.info("Created " + root.getNodeCount() + " nodes - size: " + root.size());
displayMemory();
}
try {
pool.invokeAll(tasks);
} catch (InterruptedException ex) {
logger.warn("applyMCTS interrupted");
}
for (MCTSExecutor task: tasks) {
root.merge(task.getRoot());
}
logger.info("Created " + root.getNodeCount() + " nodes");
// root.print(1);
return;
}
//try to ensure that there are at least 20 simulations per node at all times
private int calculateThinkTime(Game game, NextAction action) {
int thinkTime = 0;
int nodeSizeRatio = 0;
if (root.getNumChildren() > 0)
nodeSizeRatio = root.size() / root.getNumChildren();
logger.info("Ratio: " + nodeSizeRatio);
PhaseStep curStep = game.getStep().getType();
if (action == NextAction.SELECT_ATTACKERS || action == NextAction.SELECT_BLOCKERS) {
if (nodeSizeRatio < thinkTimeRatioThreshold) {
thinkTime = maxThinkTime;
}
else {
thinkTime = maxThinkTime / 2;
}
}
else if (game.getActivePlayerId().equals(playerId) && (curStep == PhaseStep.PRECOMBAT_MAIN || curStep == PhaseStep.POSTCOMBAT_MAIN)) {
if (nodeSizeRatio < thinkTimeRatioThreshold) {
thinkTime = maxThinkTime;
}
else {
thinkTime = maxThinkTime / 2;
}
}
else {
if (nodeSizeRatio < thinkTimeRatioThreshold) {
thinkTime = maxThinkTime / 2;
}
else {
thinkTime = 0;
}
}
return thinkTime;
}
/**
* Copies game and replaces all players in copy with mcts players
* Shuffles each players library so that there is no knowledge of its order
* Swaps all other players hands with random cards from the library so that
* there is no knowledge of what cards are in opponents hands
* The most knowledge that is known is what cards are in an opponents deck
*
* @param game
* @return a new game object with simulated players
@ -289,11 +339,35 @@ public class ComputerPlayerMCTS extends ComputerPlayer<ComputerPlayerMCTS> imple
Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId());
MCTSPlayer newPlayer = new MCTSPlayer(copyPlayer.getId());
newPlayer.restore(origPlayer);
newPlayer.getLibrary().shuffle();
if (!newPlayer.getId().equals(playerId)) {
int handSize = newPlayer.getHand().size();
newPlayer.getLibrary().addAll(newPlayer.getHand().getCards(mcts), mcts);
newPlayer.getHand().clear();
newPlayer.getLibrary().shuffle();
for (int i = 0; i < handSize; i++) {
Card card = newPlayer.getLibrary().removeFromTop(mcts);
mcts.setZone(card.getId(), Zone.HAND);
newPlayer.getHand().add(card);
}
}
else {
newPlayer.getLibrary().shuffle();
}
mcts.getState().getPlayers().put(copyPlayer.getId(), newPlayer);
}
mcts.setSimulation(true);
mcts.resume();
return mcts;
}
protected void displayMemory() {
long heapSize = Runtime.getRuntime().totalMemory();
long heapMaxSize = Runtime.getRuntime().maxMemory();
long heapFreeSize = Runtime.getRuntime().freeMemory();
long heapUsedSize = heapSize - heapFreeSize;
long mb = 1024 * 1024;
logger.info("Max heap size: " + heapMaxSize/mb + " Heap size: " + heapSize/mb + " Used: " + heapUsedSize/mb);
}
}

View file

@ -57,21 +57,22 @@ public class MCTSExecutor implements Callable<Boolean> {
long endTime = startTime + (thinkTime * 1000000000l);
MCTSNode current;
if (root.getNumChildren() == 1)
//there is only one possible action
return true;
// 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;
// Selection
while (!current.isLeaf()) {
current = current.select();
current = current.select(this.playerId);
}
int result;
@ -79,17 +80,17 @@ public class MCTSExecutor implements Callable<Boolean> {
// Expansion
current.expand();
if (current == root && current.getNumChildren() == 1)
//there is only one possible action
return true;
// 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();
current = current.select(this.playerId);
result = current.simulate(this.playerId);
simCount++;
}
else {
result = current.isWinner(this.playerId)?1:0;
result = current.isWinner(this.playerId)?1:-1;
}
// Backpropagation
current.backpropagate(result);
@ -103,4 +104,8 @@ public class MCTSExecutor implements Callable<Boolean> {
return root;
}
public void clear() {
root = null;
}
}

View file

@ -27,14 +27,18 @@
*/
package mage.player.ai;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.Constants.PhaseStep;
import mage.Constants.Zone;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.PlayLandAbility;
import mage.abilities.common.PassAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.GameState;
import mage.game.combat.Combat;
import mage.game.combat.CombatGroup;
import mage.game.turn.Step.StepPart;
@ -48,7 +52,8 @@ import org.apache.log4j.Logger;
*/
public class MCTSNode {
private static final double selectionCoefficient = 1;
private static final double selectionCoefficient = 1.0;
private static final double passRatioTolerance = 0.01;
private final static transient Logger logger = Logger.getLogger(MCTSNode.class);
private int visits = 0;
@ -56,68 +61,76 @@ public class MCTSNode {
private MCTSNode parent;
private List<MCTSNode> children = new ArrayList<MCTSNode>();
private Ability action;
private Combat combat;
// private Combat combat;
private Game game;
private int stateValue;
private String stateValue;
private UUID playerId;
private static int nodeCount;
public MCTSNode(Game game) {
this.game = game;
this.stateValue = game.getState().getValue().hashCode();
this.stateValue = game.getState().getValue(false, game);
setPlayer();
nodeCount = 1;
}
protected MCTSNode(MCTSNode parent, Game game, int state, Ability action) {
protected MCTSNode(MCTSNode parent, Game game, Ability action) {
this.game = game;
this.stateValue = state;
this.stateValue = game.getState().getValue(false, game);
this.parent = parent;
this.action = action;
setPlayer();
nodeCount++;
}
protected MCTSNode(MCTSNode parent, Game game, int state, Combat combat) {
protected MCTSNode(MCTSNode parent, Game game) {
this.game = game;
this.stateValue = state;
this.stateValue = game.getState().getValue(false, game);
this.parent = parent;
this.combat = combat;
// this.combat = game.getCombat();
setPlayer();
nodeCount++;
}
public MCTSNode select() {
private void setPlayer() {
if (game.getStep().getStepPart() == StepPart.PRIORITY)
playerId = game.getPriorityPlayerId();
else {
if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS)
playerId = game.getCombat().getDefenders().iterator().next();
else
playerId = game.getActivePlayerId();
}
}
public MCTSNode select(UUID targetPlayerId) {
double bestValue = Double.NEGATIVE_INFINITY;
boolean isTarget = playerId.equals(targetPlayerId);
MCTSNode bestChild = null;
// logger.info("start select");
if (children.size() == 1) {
return children.get(0);
}
for (MCTSNode node: children) {
double uct;
if (node.visits > 0)
uct = (node.wins / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0)));
if (isTarget)
uct = (node.wins / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0)));
else
uct = ((node.visits - node.wins) / (node.visits + 1.0)) + (selectionCoefficient * Math.sqrt(Math.log(visits + 1.0) / (node.visits + 1.0)));
else
// ensure that a random unvisited node is played first
uct = 10000 + 1000 * Math.random();
// logger.info("uct: " + uct);
if (uct > bestValue) {
bestChild = node;
bestValue = uct;
}
}
// logger.info("stop select");
return bestChild;
}
public void expand() {
MCTSPlayer player;
if (game.getStep().getStepPart() == StepPart.PRIORITY)
player = (MCTSPlayer) game.getPlayer(game.getPriorityPlayerId());
else {
if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS)
player = (MCTSPlayer) game.getPlayer(game.getCombat().getDefenders().iterator().next());
else
player = (MCTSPlayer) game.getPlayer(game.getActivePlayerId());
}
MCTSPlayer player = (MCTSPlayer) game.getPlayer(playerId);
if (player.getNextAction() == null) {
logger.fatal("next action is null");
}
@ -127,12 +140,12 @@ public class MCTSNode {
List<Ability> abilities = player.getPlayableOptions(game);
for (Ability ability: abilities) {
Game sim = game.copy();
int simState = sim.getState().getValue().hashCode();
// String simState = sim.getState().getValue(false, sim);
// logger.info("expand " + ability.toString());
MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId());
simPlayer.activateAbility((ActivatedAbility)ability, sim);
sim.resume();
children.add(new MCTSNode(this, sim, simState, ability));
children.add(new MCTSNode(this, sim, ability));
}
break;
case SELECT_ATTACKERS:
@ -141,13 +154,13 @@ public class MCTSNode {
UUID defenderId = game.getOpponents(player.getId()).iterator().next();
for (List<UUID> attack: attacks) {
Game sim = game.copy();
int simState = sim.getState().getValue().hashCode();
// String simState = sim.getState().getValue(false, sim);
MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId());
for (UUID attackerId: attack) {
simPlayer.declareAttacker(attackerId, defenderId, sim);
}
sim.resume();
children.add(new MCTSNode(this, sim, simState, sim.getCombat()));
children.add(new MCTSNode(this, sim));
}
break;
case SELECT_BLOCKERS:
@ -155,7 +168,7 @@ public class MCTSNode {
List<List<List<UUID>>> blocks = player.getBlocks(game);
for (List<List<UUID>> block: blocks) {
Game sim = game.copy();
int simState = sim.getState().getValue().hashCode();
// String simState = sim.getState().getValue(false, sim);
MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId());
List<CombatGroup> groups = sim.getCombat().getGroups();
for (int i = 0; i < groups.size(); i++) {
@ -166,18 +179,18 @@ public class MCTSNode {
}
}
sim.resume();
children.add(new MCTSNode(this, sim, simState, sim.getCombat()));
children.add(new MCTSNode(this, sim));
}
break;
}
}
public int simulate(UUID playerId) {
long startTime = System.nanoTime();
Game sim = createSimulation(game);
// long startTime = System.nanoTime();
Game sim = createSimulation(game, playerId);
sim.resume();
long duration = System.nanoTime() - startTime;
int retVal = 0;
// long duration = System.nanoTime() - startTime;
int retVal = 0; //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()) {
@ -201,12 +214,34 @@ public class MCTSNode {
}
public MCTSNode bestChild() {
if (children.size() == 1)
return children.get(0);
double bestCount = -1;
double bestRatio = 0;
boolean bestIsPass = false;
MCTSNode bestChild = null;
for (MCTSNode node: children) {
//favour passing vs any other action except for playing land if ratio is close
if (node.visits > bestCount) {
if (bestIsPass) {
double ratio = node.wins/(node.visits * 1.0);
if (ratio < bestRatio + passRatioTolerance)
continue;
}
bestChild = node;
bestCount = node.visits;
bestRatio = node.wins/(node.visits * 1.0);
bestIsPass = false;
}
else if (node.action instanceof PassAbility && node.visits > 10 && !(bestChild.action instanceof PlayLandAbility)) {
//favour passing vs any other action if ratio is close
double ratio = node.wins/(node.visits * 1.0);
if (ratio > bestRatio - passRatioTolerance) {
bestChild = node;
bestCount = node.visits;
bestRatio = ratio;
bestIsPass = true;
}
}
}
return bestChild;
@ -229,13 +264,17 @@ public class MCTSNode {
}
public Combat getCombat() {
return combat;
return game.getCombat();
}
public int getNodeCount() {
return nodeCount;
}
public String getStateValue() {
return stateValue;
}
/**
* Copies game and replaces all players in copy with simulated players
* Shuffles each players library so that there is no knowledge of its order
@ -243,14 +282,27 @@ public class MCTSNode {
* @param game
* @return a new game object with simulated players
*/
protected Game createSimulation(Game game) {
protected Game createSimulation(Game game, UUID playerId) {
Game sim = game.copy();
for (Player copyPlayer: sim.getState().getPlayers().values()) {
Player origPlayer = game.getState().getPlayers().get(copyPlayer.getId()).copy();
SimulatedPlayerMCTS newPlayer = new SimulatedPlayerMCTS(copyPlayer.getId(), true);
newPlayer.restore(origPlayer);
newPlayer.shuffleLibrary(sim);
if (!newPlayer.getId().equals(playerId)) {
int handSize = newPlayer.getHand().size();
newPlayer.getLibrary().addAll(newPlayer.getHand().getCards(sim), sim);
newPlayer.getHand().clear();
newPlayer.getLibrary().shuffle();
for (int i = 0; i < handSize; i++) {
Card card = newPlayer.getLibrary().removeFromTop(sim);
sim.setZone(card.getId(), Zone.HAND);
newPlayer.getHand().add(card);
}
}
else {
newPlayer.getLibrary().shuffle();
}
sim.getState().getPlayers().put(copyPlayer.getId(), newPlayer);
}
sim.setSimulation(true);
@ -268,29 +320,35 @@ public class MCTSNode {
return false;
}
public MCTSNode getMatchingState(int state, NextAction nextAction) {
for (MCTSNode node: children) {
if (node.stateValue == state && node.action != null) {
MCTSPlayer player;
if (game.getStep().getStepPart() == StepPart.PRIORITY)
player = (MCTSPlayer) game.getPlayer(game.getPriorityPlayerId());
else {
if (game.getStep().getType() == PhaseStep.DECLARE_BLOCKERS)
player = (MCTSPlayer) game.getPlayer(game.getCombat().getDefenders().iterator().next());
else
player = (MCTSPlayer) game.getPlayer(game.getActivePlayerId());
}
if (player.getNextAction() == nextAction)
return node;
/**
*
* performs a breadth first search for a matching game state
*
* @param state - the game state that we are looking for
* @param nextAction - the next action that will be performed
* @return the matching state or null if no match is found
*/
public MCTSNode getMatchingState(String state) {
ArrayDeque<MCTSNode> queue = new ArrayDeque<MCTSNode>();
queue.add(this);
while (!queue.isEmpty()) {
MCTSNode current = queue.remove();
if (current.stateValue.equals(state))
return current;
for (MCTSNode child: current.children) {
queue.add(child);
}
MCTSNode match = node.getMatchingState(state, nextAction);
if (match != null)
return node;
}
return null;
}
public void merge(MCTSNode merge) {
if (!stateValue.equals(merge.stateValue)) {
logger.info("mismatched merge states");
return;
}
this.visits += merge.visits;
this.wins += merge.wins;
@ -301,16 +359,61 @@ public class MCTSNode {
for (MCTSNode child: children) {
for (MCTSNode mergeChild: mergeChildren) {
if (mergeChild.stateValue == child.stateValue) {
child.merge(mergeChild);
mergeChildren.remove(mergeChild);
break;
if (mergeChild.action != null && child.action != null) {
if (mergeChild.action.toString().equals(child.action.toString())) {
if (!mergeChild.stateValue.equals(child.stateValue)) {
logger.info("mismatched merge states");
mergeChildren.remove(mergeChild);
}
else {
child.merge(mergeChild);
mergeChildren.remove(mergeChild);
}
break;
}
}
else {
if (mergeChild.game.getCombat().getValue().equals(child.game.getCombat().getValue())) {
if (!mergeChild.stateValue.equals(child.stateValue)) {
logger.info("mismatched merge states");
mergeChildren.remove(mergeChild);
}
else {
child.merge(mergeChild);
mergeChildren.remove(mergeChild);
}
break;
}
}
}
}
if (!mergeChildren.isEmpty()) {
children.addAll(mergeChildren);
for (MCTSNode child: mergeChildren) {
child.parent = this;
children.add(child);
}
}
}
public void print(int depth) {
String indent = String.format("%1$-" + depth + "s", "");
StringBuilder sb = new StringBuilder();
MCTSPlayer player = (MCTSPlayer) game.getPlayer(playerId);
sb.append(indent).append(player.getName()).append(" ").append(visits).append(":").append(wins).append(" - ");
if (action != null)
sb.append(action.toString());
System.out.println(sb.toString());
for (MCTSNode child: children) {
child.print(depth + 1);
}
}
public int size() {
int num = 1;
for (MCTSNode child: children) {
num += child.size();
}
return num;
}
}

View file

@ -201,10 +201,11 @@ public class MCTSPlayer extends ComputerPlayer<MCTSPlayer> {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
// logger.info("Paused for Priority for player:" + getName());
game.pause();
nextAction = NextAction.PRIORITY;
return false;
}
// @Override

View file

@ -42,6 +42,7 @@ import mage.abilities.Mode;
import mage.abilities.Modes;
import mage.abilities.TriggeredAbilities;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
@ -56,6 +57,7 @@ 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;
@ -99,11 +101,24 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
// logger.info("priority");
boolean didSomething = false;
Ability ability = getAction(game);
// logger.info("simulate " + ability.toString());
if (!(ability instanceof PassAbility))
didSomething = true;
activateAbility((ActivatedAbility) ability, game);
actionCount++;
return didSomething;
}
private Ability getAction(Game game) {
List<Ability> playables = getPlayableAbilities(game);
Ability ability;
while (true) {
List<Ability> playables = getPlayableAbilities(game);
Ability ability;
if (playables.size() == 1)
ability = playables.get(0);
else
@ -120,13 +135,23 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
if (amount > 0)
ability.addManaCost(new GenericManaCost(rnd.nextInt(amount)));
}
// logger.info("simulate " + ability.toString());
activateAbility((ActivatedAbility) ability, game);
actionCount++;
if (ability.isUsesStack())
// check if ability kills player, if not then it's ok to play
// if (ability.isUsesStack()) {
// Game testSim = game.copy();
// activateAbility((ActivatedAbility) ability, testSim);
// StackObject testAbility = testSim.getStack().pop();
// testAbility.resolve(testSim);
// testSim.applyEffects();
// testSim.checkStateAndTriggered();
// if (!testSim.getPlayer(playerId).hasLost()) {
// break;
// }
// }
// else {
break;
// }
}
return ability;
}
@Override

View file

@ -131,14 +131,14 @@ public class ComputerPlayer2 extends ComputerPlayer<ComputerPlayer2> implements
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
logState(game);
game.firePriorityEvent(playerId);
switch (game.getTurn().getStepType()) {
case UPKEEP:
case DRAW:
pass();
break;
return false;
case PRECOMBAT_MAIN:
case BEGIN_COMBAT:
case DECLARE_ATTACKERS:
@ -151,12 +151,13 @@ public class ComputerPlayer2 extends ComputerPlayer<ComputerPlayer2> implements
calculateActions(game);
}
act(game);
break;
return true;
case END_TURN:
case CLEANUP:
pass();
break;
return false;
}
return false;
}
protected void act(Game game) {
@ -210,8 +211,8 @@ public class ComputerPlayer2 extends ComputerPlayer<ComputerPlayer2> implements
test = root;
root = root.children.get(0);
}
logger.debug("simlating -- game value:" + game.getState().getValue() + " test value:" + test.gameValue);
if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue().hashCode() == test.gameValue) {
logger.debug("simlating -- game value:" + game.getState().getValue(true) + " test value:" + test.gameValue);
if (root.playerId.equals(playerId) && root.abilities != null && game.getState().getValue(true).hashCode() == test.gameValue) {
logger.debug("simulating -- continuing previous action chain");
actions = new LinkedList<Ability>(root.abilities);
combat = root.combat;
@ -412,7 +413,7 @@ public class ComputerPlayer2 extends ComputerPlayer<ComputerPlayer2> implements
logger.debug(indent(node.depth) + "interrupted");
return GameStateEvaluator.evaluate(playerId, game);
}
node.setGameValue(game.getState().getValue().hashCode());
node.setGameValue(game.getState().getValue(true).hashCode());
SimulatedPlayer currentPlayer = (SimulatedPlayer) game.getPlayer(game.getPlayerList().get());
boolean isSimulatedPlayer = currentPlayer.getId().equals(playerId);
logger.debug(indent(node.depth) + "simulating priority -- player " + currentPlayer.getName());

View file

@ -96,7 +96,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
logState(game);
if (logger.isDebugEnabled())
logger.debug("Game State: Turn-" + game.getTurnNum() + " Step-" + game.getTurn().getStepType() + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name);
@ -105,51 +105,55 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
case UPKEEP:
case DRAW:
pass();
break;
return false;
case PRECOMBAT_MAIN:
if (game.getActivePlayerId().equals(playerId)) {
if (actions.size() == 0) {
calculatePreCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case BEGIN_COMBAT:
pass();
break;
return false;
case DECLARE_ATTACKERS:
if (!game.getActivePlayerId().equals(playerId)) {
if (actions.size() == 0) {
calculatePreCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case DECLARE_BLOCKERS:
case FIRST_COMBAT_DAMAGE:
case COMBAT_DAMAGE:
case END_COMBAT:
pass();
break;
return false;
case POSTCOMBAT_MAIN:
if (game.getActivePlayerId().equals(playerId)) {
if (actions.size() == 0) {
calculatePostCombatActions(game);
}
act(game);
return true;
}
else
pass();
break;
return false;
case END_TURN:
case CLEANUP:
pass();
break;
return false;
}
return false;
}
protected void calculatePreCombatActions(Game game) {

View file

@ -188,7 +188,7 @@ public class SimulatedPlayer extends ComputerPlayer<SimulatedPlayer> {
if (binary.charAt(j) == '1')
sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, sim);
}
if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null) {
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) {
logger.debug("simulating -- found redundant attack combination");
}
else if (logger.isDebugEnabled()) {
@ -205,7 +205,7 @@ public class SimulatedPlayer extends ComputerPlayer<SimulatedPlayer> {
//add a node with no blockers
Game sim = game.copy();
engagements.put(sim.getCombat().getValue(sim), sim.getCombat());
engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat());
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId));
List<Permanent> blockers = getAvailableBlockers(game);
@ -227,7 +227,7 @@ public class SimulatedPlayer extends ComputerPlayer<SimulatedPlayer> {
if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) {
Game sim = game.copy();
sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim);
if (engagements.put(sim.getCombat().getValue(sim), sim.getCombat()) != null)
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null)
logger.debug("simulating -- found redundant block combination");
addBlocker(sim, remaining, engagements); // and recurse minus the used blocker
}
@ -277,8 +277,9 @@ public class SimulatedPlayer extends ComputerPlayer<SimulatedPlayer> {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
//should never get here
return false;
}
protected String indent(int num) {

View file

@ -374,20 +374,22 @@ public class HumanPlayer extends PlayerImpl<HumanPlayer> {
}
@Override
public void priority(Game game) {
public boolean priority(Game game) {
passed = false;
if (!abort) {
if (passedTurn && game.getStack().isEmpty()) {
pass();
return;
return false;
}
game.firePriorityEvent(playerId);
waitForResponse();
if (response.getBoolean() != null) {
pass();
return false;
} else if (response.getInteger() != null) {
pass();
passedTurn = true;
return false;
} else if (response.getString() != null && response.getString().equals("special")) {
specialAction(game);
} else if (response.getUUID() != null) {
@ -403,7 +405,9 @@ public class HumanPlayer extends PlayerImpl<HumanPlayer> {
}
}
}
return true;
}
return false;
}
@Override

View file

@ -96,36 +96,37 @@ class PhyrexianMetamorphEffect extends ContinuousEffectImpl<PhyrexianMetamorphEf
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getFirstTarget());
Permanent permanent = game.getPermanent(source.getSourceId());
permanent.setName(card.getName());
permanent.getColor().setColor(card.getColor());
permanent.getManaCost().clear();
permanent.getManaCost().add(card.getManaCost());
permanent.getCardType().clear();
for (CardType type: card.getCardType()) {
permanent.getCardType().add(type);
}
if (!card.getCardType().contains(CardType.ARTIFACT)) {
card.getCardType().add(CardType.ARTIFACT);
if (card != null && permanent != null) {
permanent.setName(card.getName());
permanent.getColor().setColor(card.getColor());
permanent.getManaCost().clear();
permanent.getManaCost().add(card.getManaCost());
permanent.getCardType().clear();
for (CardType type: card.getCardType()) {
permanent.getCardType().add(type);
}
if (!card.getCardType().contains(CardType.ARTIFACT)) {
card.getCardType().add(CardType.ARTIFACT);
}
permanent.getSubtype().clear();
for (String type: card.getSubtype()) {
permanent.getSubtype().add(type);
}
permanent.getSupertype().clear();
for (String type: card.getSupertype()) {
permanent.getSupertype().add(type);
}
permanent.setExpansionSetCode(card.getExpansionSetCode());
permanent.getAbilities().clear();
for (Ability ability0: card.getAbilities()) {
// Ability ability = ability0.copy();
// ability.newId();
// ability.setSourceId(card.getId());
permanent.addAbility(ability0);
}
permanent.getPower().setValue(card.getPower().getValue());
permanent.getToughness().setValue(card.getToughness().getValue());
}
permanent.getSubtype().clear();
for (String type: card.getSubtype()) {
permanent.getSubtype().add(type);
}
permanent.getSupertype().clear();
for (String type: card.getSupertype()) {
permanent.getSupertype().add(type);
}
permanent.setExpansionSetCode(card.getExpansionSetCode());
permanent.getAbilities().clear();
for (Ability ability0: card.getAbilities()) {
// Ability ability = ability0.copy();
// ability.newId();
// ability.setSourceId(card.getId());
permanent.addAbility(ability0);
}
permanent.getPower().setValue(card.getPower().getValue());
permanent.getToughness().setValue(card.getToughness().getValue());
return true;
}

View file

@ -40,7 +40,7 @@ import mage.game.events.GameEvent;
*/
public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility<DiesTriggeredAbility> {
boolean used = false;
// boolean used = false;
public DiesTriggeredAbility(Effect effect, boolean optional) {
super(Zone.BATTLEFIELD, Zone.GRAVEYARD, effect, "When {this} dies, ", optional);
@ -59,18 +59,18 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility<DiesTrigger
return new DiesTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return super.checkTrigger(event, game) && !used;
}
// @Override
// public boolean checkTrigger(GameEvent event, Game game) {
// return super.checkTrigger(event, game) && !used;
// }
@Override
public void trigger(Game game, UUID controllerId) {
if ( !used ) {
super.trigger(game, controllerId);
used = true;
}
}
// @Override
// public void trigger(Game game, UUID controllerId) {
// if ( !used ) {
// super.trigger(game, controllerId);
// used = true;
// }
// }
}

View file

@ -42,6 +42,7 @@ public class PassAbility extends ActivatedAbilityImpl<PassAbility> {
public PassAbility() {
super(Zone.ALL, new PassEffect());
this.usesStack = false;
}
public PassAbility(final PassAbility ability) {

View file

@ -139,10 +139,10 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
this.attackOption = game.attackOption;
this.state = game.state.copy();
// Issue 350
//this.gameCards = game.gameCards;
for (Map.Entry<UUID, Card> entry: game.gameCards.entrySet()) {
this.gameCards.put(entry.getKey(), entry.getValue().copy());
}
this.gameCards = game.gameCards;
// for (Map.Entry<UUID, Card> entry: game.gameCards.entrySet()) {
// this.gameCards.put(entry.getKey(), entry.getValue().copy());
// }
this.simulation = game.simulation;
this.gameOptions = game.gameOptions;
this.lki.putAll(game.lki);
@ -386,13 +386,14 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
public void resume() {
PlayerList players = state.getPlayerList(state.getActivePlayerId());
Player player = getPlayer(players.get());
boolean wasPaused = state.isPaused();
state.resume();
if (!isGameOver()) {
// if (simulation)
// logger.info("Turn " + Integer.toString(state.getTurnNum()));
fireInformEvent("Turn " + Integer.toString(state.getTurnNum()));
if (checkStopOnTurnOption()) return;
state.getTurn().resumePlay(this);
state.getTurn().resumePlay(this, wasPaused);
if (!isPaused() && !isGameOver()) {
endOfTurn();
player = players.getNext(this);
@ -623,12 +624,13 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
checkStateAndTriggered();
if (isPaused() || isGameOver()) return;
// resetPassed should be called if player performs any action
player.priority(this);
if (player.priority(this))
applyEffects();
if (isPaused()) return;
}
resuming = false;
applyEffects();
}
resuming = false;
if (isPaused() || isGameOver()) return;
if (allPassed()) {
if (!state.getStack().isEmpty()) {
@ -781,7 +783,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
for (Permanent perm: getBattlefield().getAllActivePermanents()) {
if (perm.getCardType().contains(CardType.CREATURE)) {
//20091005 - 704.5f
if (perm.getToughness().getValue() == 0) {
if (perm.getToughness().getValue() <= 0) {
if (perm.moveToZone(Zone.GRAVEYARD, null, this, false)) {
somethingHappened = true;
continue;

View file

@ -32,7 +32,10 @@ import mage.abilities.TriggeredAbility;
import mage.game.events.GameEvent;
import mage.game.stack.SpellStack;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import mage.Constants.Zone;
@ -42,10 +45,13 @@ import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.DelayedTriggeredAbilities;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.Mode;
import mage.abilities.SpecialActions;
import mage.abilities.TriggeredAbilities;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffects;
import mage.cards.Card;
import mage.choices.Choice;
import mage.game.combat.Combat;
import mage.game.combat.CombatGroup;
import mage.game.command.Command;
@ -60,6 +66,7 @@ import mage.game.turn.TurnMods;
import mage.players.Player;
import mage.players.PlayerList;
import mage.players.Players;
import mage.target.Target;
import mage.util.Copyable;
import mage.watchers.Watchers;
@ -146,6 +153,7 @@ public class GameState implements Serializable, Copyable<GameState> {
for (Map.Entry<UUID, Abilities<ActivatedAbility>> entry: state.otherAbilities.entrySet()) {
otherAbilities.put(entry.getKey(), entry.getValue().copy());
}
this.paused = state.paused;
}
@Override
@ -158,34 +166,100 @@ public class GameState implements Serializable, Copyable<GameState> {
playerList.add(player.getId());
}
public String getValue() {
public String getValue(boolean useHidden) {
StringBuilder sb = new StringBuilder(1024);
sb.append(turnNum).append(turn.getPhaseType()).append(turn.getStepType()).append(activePlayerId).append(priorityPlayerId);
for (Player player: players.values()) {
sb.append("player").append(player.getLife()).append("hand").append(player.getHand()).append("library").append(player.getLibrary().size()).append("graveyard").append(player.getGraveyard());
sb.append("player").append(player.getLife()).append("hand");
if (useHidden)
sb.append(player.getHand());
else
sb.append(player.getHand().size());
sb.append("library").append(player.getLibrary().size()).append("graveyard").append(player.getGraveyard());
}
for (UUID permanentId: battlefield.getAllPermanentIds()) {
sb.append("permanent").append(permanentId);
sb.append("permanents");
for (Permanent permanent: battlefield.getAllPermanents()) {
sb.append(permanent.getValue());
}
sb.append("spells");
for (StackObject spell: stack) {
sb.append("spell").append(spell.getId());
sb.append(spell.getControllerId()).append(spell.getName());
}
for (ExileZone zone: exile.getExileZones()) {
sb.append("exile").append(zone.getName()).append(zone);
}
sb.append("combat");
for (CombatGroup group: combat.getGroups()) {
sb.append("combat").append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers());
sb.append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers());
}
return sb.toString();
}
public String getValue(boolean useHidden, Game game) {
StringBuilder sb = new StringBuilder(1024);
sb.append(turnNum).append(turn.getPhaseType()).append(turn.getStepType()).append(activePlayerId).append(priorityPlayerId);
for (Player player: players.values()) {
sb.append("player").append(player.isPassed()).append(player.getLife()).append("hand");
if (useHidden)
sb.append(player.getHand());
else
sb.append(player.getHand().size());
sb.append("library").append(player.getLibrary().size());
sb.append("graveyard");
for (Card card: player.getGraveyard().getCards(game)) {
sb.append(card.getName());
}
}
sb.append("permanents");
List<String> perms = new ArrayList<String>();
for (Permanent permanent: battlefield.getAllPermanents()) {
perms.add(permanent.getValue());
}
Collections.sort(perms);
sb.append(perms);
sb.append("spells");
for (StackObject spell: stack) {
sb.append(spell.getControllerId()).append(spell.getName());
sb.append(spell.getStackAbility().toString());
for (Mode mode: spell.getStackAbility().getModes().values()) {
if (!mode.getTargets().isEmpty()) {
sb.append("targets");
for (Target target: mode.getTargets()) {
sb.append(target.getTargets());
}
}
if (!mode.getChoices().isEmpty()) {
sb.append("choices");
for (Choice choice: mode.getChoices()) {
sb.append(choice.getChoice());
}
}
}
}
for (ExileZone zone: exile.getExileZones()) {
sb.append("exile").append(zone.getName()).append(zone);
}
sb.append("combat");
for (CombatGroup group: combat.getGroups()) {
sb.append(group.getDefenderId()).append(group.getAttackers()).append(group.getBlockers());
}
return sb.toString();
}
public Players getPlayers() {
return players;
}

View file

@ -107,13 +107,13 @@ public class Combat implements Serializable, Copyable<Combat> {
attackerId = null;
}
public int getValue(Game game) {
public String getValue() {
StringBuilder sb = new StringBuilder();
sb.append(attackerId).append(defenders);
for (CombatGroup group : groups) {
sb.append(group.getValue(game));
sb.append(group.defenderId).append(group.attackers).append(group.attackerOrder).append(group.blockers).append(group.blockerOrder);
}
return sb.toString().hashCode();
return sb.toString();
}
public void setAttacker(UUID playerId) {
@ -128,16 +128,21 @@ public class Combat implements Serializable, Copyable<Combat> {
player.selectAttackers(game);
if (game.isPaused() || game.isGameOver())
return;
for (CombatGroup group: groups) {
for (UUID attacker: group.getAttackers()) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId));
}
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId));
game.fireInformEvent(player.getName() + " attacks with " + groups.size() + " creatures");
resumeSelectAttackers(game);
}
}
public void resumeSelectAttackers(Game game) {
Player player = game.getPlayer(attackerId);
for (CombatGroup group: groups) {
for (UUID attacker: group.getAttackers()) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId));
}
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId));
game.fireInformEvent(player.getName() + " attacks with " + groups.size() + " creatures");
}
protected void checkAttackRequirements(Player player, Game game) {
//20101001 - 508.1d
for (Permanent creature : player.getAvailableAttackers(game)) {
@ -176,6 +181,13 @@ public class Combat implements Serializable, Copyable<Combat> {
}
}
public void resumeSelectBlockers(Game game) {
//TODO: this isn't quite right - but will work fine for two-player games
for (UUID defenderId : getPlayerDefenders(game)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
}
}
protected void checkBlockRequirements(Player player, Game game) {
//20101001 - 509.1c
//TODO: handle case where more than one attacker must be blocked

View file

@ -74,21 +74,21 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
this.players.putAll(group.players);
}
protected String getValue(Game game) {
StringBuilder sb = new StringBuilder(1024);
for (UUID attackerId: attackers) {
getPermanentValue(attackerId, sb, game);
}
for (UUID blockerId: blockers) {
getPermanentValue(blockerId, sb, game);
}
return sb.toString();
}
private void getPermanentValue(UUID permId, StringBuilder sb, Game game) {
Permanent perm = game.getPermanent(permId);
sb.append(perm.getValue());
}
// protected String getValue(Game game) {
// StringBuilder sb = new StringBuilder(1024);
// for (UUID attackerId: attackers) {
// getPermanentValue(attackerId, sb, game);
// }
// for (UUID blockerId: blockers) {
// getPermanentValue(blockerId, sb, game);
// }
// return sb.toString();
// }
//
// private void getPermanentValue(UUID permId, StringBuilder sb, Game game) {
// Permanent perm = game.getPermanent(permId);
// sb.append(perm.getValue());
// }
public boolean hasFirstOrDoubleStrike(Game game) {
for (UUID permId: attackers) {

View file

@ -79,7 +79,7 @@ public class PermanentCard extends PermanentImpl<PermanentCard> {
public PermanentCard(final PermanentCard permanent) {
super(permanent);
this.card = permanent.card;
this.card = permanent.card.copy();
this.maxLevelCounters = permanent.maxLevelCounters;
}

View file

@ -152,6 +152,9 @@ public abstract class PermanentImpl<T extends PermanentImpl<T>> extends CardImpl
sb.append(controllerId).append(name).append(tapped).append(damage);
sb.append(subtype).append(supertype).append(power.getValue()).append(toughness.getValue());
sb.append(abilities);
for (Counter counter: counters.values()) {
sb.append(counter.getName()).append(counter.getCount());
}
return sb.toString();
}

View file

@ -182,21 +182,21 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
String name = null;
if (object == null) {
Player targetPlayer = game.getPlayer(targetId);
if (player != null) name = targetPlayer.getName();
if (targetPlayer != null) name = targetPlayer.getName();
} else {
name = object.getName();
}
if (name != null && player.chooseUse(ability.getEffects().get(0).getOutcome(), "Change target from " + name + "?", game)) {
if (!player.chooseTarget(ability.getEffects().get(0).getOutcome(), newTarget, ability, game))
newTarget.addTarget(targetId, ability, game);
newTarget.addTarget(targetId, ability, game, false);
}
else {
newTarget.addTarget(targetId, ability, game);
newTarget.addTarget(targetId, ability, game, false);
}
}
target.clearChosen();
for (UUID newTargetId: newTarget.getTargets()) {
target.addTarget(newTargetId, ability, game);
target.addTarget(newTargetId, ability, game, false);
}
}
return true;

View file

@ -63,6 +63,12 @@ public class DeclareAttackersStep extends Step<DeclareAttackersStep> {
game.getCombat().selectAttackers(game);
}
@Override
public void resumeBeginStep(Game game, UUID activePlayerId) {
super.resumeBeginStep(game, activePlayerId);
game.getCombat().resumeSelectAttackers(game);
}
@Override
public DeclareAttackersStep copy() {
return new DeclareAttackersStep(this);

View file

@ -61,10 +61,20 @@ public class DeclareBlockersStep extends Step<DeclareBlockersStep> {
public void beginStep(Game game, UUID activePlayerId) {
super.beginStep(game, activePlayerId);
game.getCombat().selectBlockers(game);
game.getCombat().checkBlockRestrictions(game);
game.getCombat().damageAssignmentOrder(game);
if (!game.isPaused()) {
game.getCombat().checkBlockRestrictions(game);
game.getCombat().damageAssignmentOrder(game);
}
}
@Override
public void resumeBeginStep(Game game, UUID activePlayerId) {
super.resumeBeginStep(game, activePlayerId);
game.getCombat().resumeSelectBlockers(game);
game.getCombat().checkBlockRestrictions(game);
game.getCombat().damageAssignmentOrder(game);
}
@Override
public DeclareBlockersStep copy() {
return new DeclareBlockersStep(this);

View file

@ -117,7 +117,7 @@ public abstract class Phase<T extends Phase<T>> implements Serializable {
return false;
}
public boolean resumePlay(Game game, PhaseStep stepType) {
public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) {
if (game.isPaused() || game.isGameOver())
return false;
@ -128,7 +128,7 @@ public abstract class Phase<T extends Phase<T>> implements Serializable {
step = it.next();
currentStep = step;
} while (step.getType() != stepType);
resumeStep(game);
resumeStep(game, wasPaused);
while (it.hasNext()) {
step = it.next();
if (game.isPaused() || game.isGameOver())
@ -179,13 +179,20 @@ public abstract class Phase<T extends Phase<T>> implements Serializable {
}
}
protected void resumeStep(Game game) {
protected void resumeStep(Game game, boolean wasPaused) {
boolean resuming = true;
switch (currentStep.getStepPart()) {
case PRE:
prePriority(game, activePlayerId);
if (wasPaused) {
currentStep.resumeBeginStep(game, activePlayerId);
resuming = false;
}
else {
prePriority(game, activePlayerId);
}
case PRIORITY:
if (!game.isPaused() && !game.isGameOver())
currentStep.priority(game, activePlayerId, true);
currentStep.priority(game, activePlayerId, resuming);
case POST:
if (!game.isPaused() && !game.isGameOver())
postPriority(game, activePlayerId);

View file

@ -76,6 +76,10 @@ public abstract class Step<T extends Step<T>> implements Serializable {
stepPart = StepPart.PRE;
game.fireEvent(new GameEvent(preStepEvent, null, null, activePlayerId));
}
public void resumeBeginStep(Game game, UUID activePlayerId) {
stepPart = StepPart.PRE;
}
public void priority(Game game, UUID activePlayerId, boolean resuming) {
if (hasPriority) {

View file

@ -133,7 +133,7 @@ public class Turn implements Serializable {
playExtraTurns(game);
}
public void resumePlay(Game game) {
public void resumePlay(Game game, boolean wasPaused) {
activePlayerId = game.getActivePlayerId();
UUID priorityPlayerId = game.getPriorityPlayerId();
TurnPhase phaseType = game.getPhase().getType();
@ -145,7 +145,7 @@ public class Turn implements Serializable {
phase = it.next();
currentPhase = phase;
} while (phase.type != phaseType);
if (phase.resumePlay(game, stepType)) {
if (phase.resumePlay(game, stepType, wasPaused)) {
//20091005 - 500.4/703.4n
game.emptyManaPools();
game.saveState();

View file

@ -210,7 +210,7 @@ public interface Player extends MageItem, Copyable<Player> {
public void setResponseBoolean(Boolean responseBoolean);
public void setResponseInteger(Integer data);
public abstract void priority(Game game);
public abstract boolean priority(Game game);
public abstract boolean choose(Outcome outcome, Target target, UUID sourceId, Game game);
public abstract boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map<String, Serializable> options);
public abstract boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game);

View file

@ -137,7 +137,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
this.name = player.name;
this.human = player.human;
this.life = player.life;
this.wins = player.loses;
this.wins = player.wins;
this.loses = player.loses;
this.library = player.library.copy();
this.hand = player.hand.copy();
@ -355,6 +355,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
public boolean putInHand(Card card, Game game) {
if (card.getOwnerId().equals(playerId)) {
this.hand.add(card);
game.setZone(card.getId(), Zone.HAND);
} else {
return game.getPlayer(card.getOwnerId()).putInHand(card, game);
}
@ -557,10 +558,9 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
int bookmark = game.bookmarkState();
ability.newId();
game.getStack().push(new StackAbility(ability, playerId));
String message = ability.getActivatedMessage(game);
if (ability.activate(game, false)) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, ability.getId(), ability.getSourceId(), playerId));
game.fireInformEvent(name + message);
game.fireInformEvent(name + ability.getActivatedMessage(game));
game.removeBookmark(bookmark);
return true;
}
@ -946,19 +946,31 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
@Override
public void restore(Player player) {
this.library = player.getLibrary();
this.hand = player.getHand();
this.graveyard = player.getGraveyard();
this.abilities = player.getAbilities();
this.manaPool = player.getManaPool();
this.library = player.getLibrary().copy();
this.hand = player.getHand().copy();
this.graveyard = player.getGraveyard().copy();
this.abilities = player.getAbilities().copy();
this.manaPool = player.getManaPool().copy();
this.life = player.getLife();
this.counters = player.getCounters();
this.inRange = player.getInRange();
this.counters = player.getCounters().copy();
this.inRange.clear();
this.inRange.addAll(player.getInRange());
this.landsPlayed = player.getLandsPlayed();
this.name = player.getName();
this.range = player.getRange();
this.passed = player.isPassed();
}
this.human = player.isHuman();
this.wins = player.hasWon();
this.loses = player.hasLost();
this.landsPerTurn = player.getLandsPerTurn();
this.maxHandSize = player.getMaxHandSize();
this.left = player.hasLeft();
this.canGainLife = player.isCanGainLife();
this.canLoseLife = player.isCanLoseLife();
this.attachments.clear();
this.attachments.addAll(player.getAttachments());
this.userData = player.getUserData();
}
@Override
public boolean isPassed() {
@ -1255,7 +1267,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
private void addTargetOptions(List<Ability> options, Ability option, int targetNum, Game game) {
for (UUID targetId: option.getTargets().getUnchosen().get(targetNum).possibleTargets(option.getSourceId(), playerId, game)) {
Ability newOption = option.copy();
newOption.getTargets().get(targetNum).addTarget(targetId, option, game);
newOption.getTargets().get(targetNum).addTarget(targetId, option, game, true);
if (targetNum < option.getTargets().size() - 2) {
//addTargetOptions(options, newOption, targetNum + 1, game);
// ayrat: bug fix
@ -1291,7 +1303,7 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
private void addCostTargetOptions(List<Ability> options, Ability option, int targetNum, Game game) {
for (UUID targetId: option.getCosts().getTargets().get(targetNum).possibleTargets(option.getSourceId(), playerId, game)) {
Ability newOption = option.copy();
newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game);
newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game, true);
if (targetNum < option.getCosts().getTargets().size() - 1) {
addCostTargetOptions(options, newOption, targetNum + 1, game);
}

View file

@ -56,6 +56,8 @@ public interface Target extends Serializable {
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game);
public void addTarget(UUID id, Ability source, Game game);
public void addTarget(UUID id, int amount, Ability source, Game game);
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent);
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent);
public boolean canTarget(UUID id, Game game);
public boolean canTarget(UUID id, Ability source, Game game);
public boolean isLegal(Ability source, Game game);

View file

@ -76,9 +76,9 @@ public abstract class TargetAmount<T extends TargetAmount<T>> extends TargetImpl
}
@Override
public void addTarget(UUID id, int amount, Ability source, Game game) {
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
if (amount <= remainingAmount) {
super.addTarget(id, amount, source, game);
super.addTarget(id, amount, source, game, skipEvent);
remainingAmount -= amount;
}
}

View file

@ -176,6 +176,11 @@ public abstract class TargetImpl<T extends TargetImpl<T>> implements Target {
@Override
public void addTarget(UUID id, Ability source, Game game) {
addTarget(id, source, game, false);
}
@Override
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) {
//20100423 - 113.3
if (maxNumberOfTargets == 0 || targets.size() < maxNumberOfTargets) {
if (!targets.containsKey(id)) {
@ -183,7 +188,8 @@ public abstract class TargetImpl<T extends TargetImpl<T>> implements Target {
if (!game.replaceEvent(GameEvent.getEvent(EventType.TARGET, id, source.getId(), source.getControllerId()))) {
targets.put(id, 0);
chosen = targets.size() >= minNumberOfTargets;
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId()));
if (!skipEvent)
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId()));
}
}
else {
@ -193,8 +199,13 @@ public abstract class TargetImpl<T extends TargetImpl<T>> implements Target {
}
}
@Override
@Override
public void addTarget(UUID id, int amount, Ability source, Game game) {
addTarget(id, amount, source, game, false);
}
@Override
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
if (targets.containsKey(id)) {
amount += targets.get(id);
}
@ -202,14 +213,15 @@ public abstract class TargetImpl<T extends TargetImpl<T>> implements Target {
if (!game.replaceEvent(GameEvent.getEvent(EventType.TARGET, id, source.getId(), source.getControllerId()))) {
targets.put(id, amount);
chosen = targets.size() >= minNumberOfTargets;
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId()));
if (!skipEvent)
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, id, source.getId(), source.getControllerId()));
}
}
else {
targets.put(id, amount);
}
}
@Override
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Game game) {
Player player = game.getPlayer(playerId);