mirror of
https://github.com/correl/mage.git
synced 2025-03-07 20:53:18 -10:00
latest monte carlo ai - has a memory leak
This commit is contained in:
parent
a06f27ec89
commit
dfffdfcf8c
38 changed files with 677 additions and 286 deletions
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue