public List<Combat> addAttackers(Game game) { Map<Integer, Combat> engagements = new HashMap<Integer, Combat>(); // useful only for two player games - will only attack first opponent UUID defenderId = game.getOpponents(playerId).iterator().next(); List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); // use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); StringBuilder binary = new StringBuilder(); for (int i = powerElements - 1; i >= 0; i--) { Game sim = game.copy(); binary.setLength(0); binary.append(Integer.toBinaryString(i)); while (binary.length() < attackersList.size()) { binary.insert(0, "0"); } for (int j = 0; j < attackersList.size(); j++) { if (binary.charAt(j) == '1') { setStoredBookmark( sim .bookmarkState()); // makes it possible to UNDO a declared attacker with costs // from e.g. Propaganda if (!sim.getCombat() .declareAttacker(attackersList.get(j).getId(), defenderId, playerId, sim)) { sim.undo(playerId); } } } if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) { logger.debug("simulating -- found redundant attack combination"); } else if (logger.isDebugEnabled()) { logger.debug("simulating -- attack:" + sim.getCombat().getGroups().size()); } } return new ArrayList<Combat>(engagements.values()); }
@Override public boolean checkTrigger(GameEvent event, Game game) { if (game.getActivePlayerId().equals(this.controllerId)) { if (game.getCombat().attacksAlone()) { this.getEffects() .get(0) .setTargetPointer(new FixedTarget(game.getCombat().getAttackers().get(0))); return true; } } return false; }
@Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType() == EventType.DECLARED_ATTACKERS && game.getActivePlayerId().equals(this.controllerId)) { if (game.getCombat().attacksAlone()) { for (Effect effect : this.getEffects()) { effect.setTargetPointer(new FixedTarget(game.getCombat().getAttackers().get(0))); } return true; } } return false; }
public List<Combat> addBlockers(Game game) { Map<Integer, Combat> engagements = new HashMap<Integer, Combat>(); int numGroups = game.getCombat().getGroups().size(); if (numGroups == 0) return new ArrayList<Combat>(); // add a node with no blockers Game sim = game.copy(); engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId)); List<Permanent> blockers = getAvailableBlockers(game); addBlocker(game, blockers, engagements); return new ArrayList<Combat>(engagements.values()); }
/** * There are effects that let creatures assigns combat damage equal to its toughness rather than * its power. So this method takes this into account to get the value of damage a creature will * assign * * @param permanent * @param game * @return */ private int getDamageValueFromPermanent(Permanent permanent, Game game) { if (game.getCombat().useToughnessForDamage()) { return permanent.getToughness().getValue(); } else { return permanent.getPower().getValue(); } }
@Override public void selectBlockers(Game game, UUID defendingPlayerId) { int numGroups = game.getCombat().getGroups().size(); if (numGroups == 0) return; List<Permanent> blockers = getAvailableBlockers(game); for (Permanent blocker : blockers) { int check = rnd.nextInt(numGroups + 1); if (check < numGroups) { CombatGroup group = game.getCombat().getGroups().get(check); if (group.getAttackers().size() > 0) this.declareBlocker(blocker.getId(), group.getAttackers().get(0), game); } } actionCount++; }
@Override public void selectAttackers(Game game, UUID attackingPlayerId) { // useful only for two player games - will only attack first opponent // logger.info("select attackers"); UUID defenderId = game.getOpponents(playerId).iterator().next(); List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); // use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); int value = rnd.nextInt(powerElements); StringBuilder binary = new StringBuilder(); binary.append(Integer.toBinaryString(value)); while (binary.length() < attackersList.size()) { binary.insert(0, "0"); // pad with zeros } for (int i = 0; i < attackersList.size(); i++) { if (binary.charAt(i) == '1') { setStoredBookmark( game .bookmarkState()); // makes it possible to UNDO a declared attacker with costs from // e.g. Propaganda if (!game.getCombat() .declareAttacker(attackersList.get(i).getId(), defenderId, playerId, game)) { game.undo(playerId); } } } actionCount++; }
@Override public void adjustTargets(Ability ability, Game game) { if (this.getAbilities().contains(ability) && ability.getRule().startsWith("&bull Dragons")) { FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that player controls"); filter.add(new ControllerIdPredicate(game.getCombat().getAttackerId())); ability.getTargets().clear(); ability.addTarget(new TargetCreaturePermanent(filter)); } }
protected void addBlocker(Game game, List<Permanent> blockers, Map<Integer, Combat> engagements) { if (blockers.isEmpty()) return; int numGroups = game.getCombat().getGroups().size(); // try to block each attacker with each potential blocker Permanent blocker = blockers.get(0); if (logger.isDebugEnabled()) logger.debug("simulating -- block:" + blocker); List<Permanent> remaining = remove(blockers, blocker); for (int i = 0; i < numGroups; i++) { 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().hashCode(), sim.getCombat()) != null) logger.debug("simulating -- found redundant block combination"); addBlocker(sim, remaining, engagements); // and recurse minus the used blocker } } addBlocker(game, remaining, engagements); }
@Override public boolean apply(Permanent input, Game game) { for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (combatGroup.getBlockers().contains(input.getId()) && combatGroup.getAttackers().contains(attackerId)) { return true; } } return false; }
public static CombatSimulator load(Game game) { CombatSimulator simCombat = new CombatSimulator(); for (CombatGroup group : game.getCombat().getGroups()) { simCombat.groups.add( new CombatGroupSimulator( group.getDefenderId(), group.getAttackers(), group.getBlockers(), game)); } for (UUID defenderId : game.getCombat().getDefenders()) { simCombat.defenders.add(defenderId); Player player = game.getPlayer(defenderId); if (player != null) { simCombat.playersLife.put(defenderId, player.getLife()); } else { Permanent permanent = game.getPermanent(defenderId); simCombat.planeswalkerLoyalty.put( defenderId, permanent.getCounters(game).getCount(CounterType.LOYALTY)); } } return simCombat; }
@Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.DECLARED_BLOCKERS) { for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (!combatGroup.getBlockers().isEmpty()) { return true; } } } return false; }
@Override public void adjustTargets(Ability ability, Game game) { if (ability.getAbilityType().equals(AbilityType.TRIGGERED)) { ability.getTargets().clear(); FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls"); UUID defenderId = game.getCombat().getDefenderId(ability.getSourceId()); filter.add(new ControllerIdPredicate(defenderId)); TargetCreaturePermanent target = new TargetCreaturePermanent(filter); ability.addTarget(target); } }
@Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Set<UUID> toSacrifice = new HashSet<UUID>(); if (controller.flipCoin(game)) { // each blocking creature is sacrificed by its controller for (CombatGroup combatGroup : game.getCombat().getGroups()) { for (UUID blockerId : combatGroup.getBlockers()) { toSacrifice.add(blockerId); } } } else { // each blocked creature is sacrificed by its controller for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (!combatGroup.getBlockers().isEmpty()) { for (UUID attackerId : combatGroup.getAttackers()) { toSacrifice.add(attackerId); } } } } for (UUID creatureId : toSacrifice) { Permanent creature = game.getPermanent(creatureId); if (creature != null) { creature.sacrifice(source.getSourceId(), game); Player player = game.getPlayer(creature.getControllerId()); if (player != null) { game.informPlayers( new StringBuilder(player.getName()) .append(" sacrifices ") .append(creature.getName()) .toString()); } } } return true; } return false; }
@Override public boolean apply(Game game, Ability source) { for (UUID attackingCreatureId : game.getCombat().getAttackers()) { Permanent attackingCreature = game.getPermanent(attackingCreatureId); if (attackingCreature != null) { if (attackingCreature.getColor(game).isBlack() && attackingCreature.hasAbility(FlyingAbility.getInstance().getId(), game)) { return true; } } } return false; }
@Override public boolean activate(Game game, boolean noMana) { UUID defenderId = game.getCombat().getDefenderId(sourceId); if (defenderId != null) { FilterCreaturePermanent filter = filterTemplate.copy(); filter.add(new ControllerIdPredicate(defenderId)); this.getTargets().clear(); TargetCreaturePermanent target = new TargetCreaturePermanent(filter); target.setRequired(true); this.addTarget(target); return super.activate(game, noMana); } return false; }
@Override public boolean apply(Game game, Ability source) { List<UUID> attackers = game.getCombat().getAttackers(); int damage = attackers.size(); if (!attackers.isEmpty()) { for (UUID attacker : attackers) { Permanent creature = game.getPermanent(attacker); if (creature != null) { creature.damage(damage, source.getSourceId(), game, false, true); } } return true; } return false; }
@Override public void selectAttackers(Game game, UUID attackingPlayerId) { // useful only for two player games - will only attack first opponent UUID defenderId = game.getOpponents(playerId).iterator().next(); List<Permanent> attackersList = super.getAvailableAttackers(game); // use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); int value = rnd.nextInt(powerElements); StringBuilder binary = new StringBuilder(); binary.append(Integer.toBinaryString(value)); while (binary.length() < attackersList.size()) { binary.insert(0, "0"); // pad with zeros } for (int i = 0; i < attackersList.size(); i++) { if (binary.charAt(i) == '1') game.getCombat().declareAttacker(attackersList.get(i).getId(), defenderId, game); } actionCount++; }
@Override public boolean canBeBlockedCheckAfter(Permanent attacker, Ability source, Game game) { for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (combatGroup.getAttackers().contains(source.getSourceId())) { for (UUID blockerId : combatGroup.getBlockers()) { Permanent blockingCreature = game.getPermanent(blockerId); if (blockingCreature != null) { for (Permanent permanent : game.getBattlefield() .getAllActivePermanents( new FilterCreaturePermanent(), blockingCreature.getControllerId(), game)) { if (!combatGroup.getBlockers().contains(permanent.getId())) { // not all creatures block Tromokratis return false; } } } } } } return true; }
public boolean putOntoBattlefield( int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer) { Player controller = game.getPlayer(controllerId); if (controller == null) { return false; } lastAddedTokenIds.clear(); // moved here from CreateTokenEffect because not all cards that create tokens use // CreateTokenEffect // they use putOntoBattlefield directly // TODO: Check this setCode handling because it makes no sense if token put into play with e.g. // "Feldon of the third Path" String setCode = null; if (this.getOriginalExpansionSetCode() != null && !this.getOriginalExpansionSetCode().isEmpty()) { setCode = this.getOriginalExpansionSetCode(); } else { Card source = game.getCard(sourceId); if (source != null) { setCode = source.getExpansionSetCode(); } else { MageObject object = game.getObject(sourceId); if (object instanceof PermanentToken) { setCode = ((PermanentToken) object).getExpansionSetCode(); } } } if (!expansionSetCodeChecked) { expansionSetCodeChecked = this.updateExpansionSetCode(setCode); } GameEvent event = new GameEvent( EventType.CREATE_TOKEN, null, sourceId, controllerId, amount, this.getCardType().contains(CardType.CREATURE)); if (!game.replaceEvent(event)) { amount = event.getAmount(); List<Permanent> permanents = new ArrayList<>(); List<Permanent> permanentsEntered = new ArrayList<>(); for (int i = 0; i < amount; i++) { PermanentToken newToken = new PermanentToken( this, event.getPlayerId(), setCode, game); // use event.getPlayerId() because it can be replaced by replacement effect game.getState().addCard(newToken); permanents.add(newToken); game.getPermanentsEntering().put(newToken.getId(), newToken); newToken.setTapped(tapped); } game.setScopeRelevant(true); for (Permanent permanent : permanents) { if (permanent.entersBattlefield(sourceId, game, Zone.OUTSIDE, true)) { permanentsEntered.add(permanent); } else { game.getPermanentsEntering().remove(permanent.getId()); } } game.setScopeRelevant(false); for (Permanent permanent : permanentsEntered) { game.addPermanent(permanent); permanent.setZone(Zone.BATTLEFIELD, game); game.getPermanentsEntering().remove(permanent.getId()); this.lastAddedTokenIds.add(permanent.getId()); this.lastAddedTokenId = permanent.getId(); game.addSimultaneousEvent( new ZoneChangeEvent( permanent, permanent.getControllerId(), Zone.OUTSIDE, Zone.BATTLEFIELD)); if (attacking && game.getCombat() != null) { game.getCombat().addAttackingCreature(permanent.getId(), game, attackedPlayer); } if (!game.isSimulation()) { game.informPlayers( controller.getLogName() + " puts a " + permanent.getLogName() + " token onto the battlefield"); } } return true; } return false; }
protected int simulateBlockers( Game game, SimulationNode2 node, UUID defenderId, int depth, int alpha, int beta, boolean counter) { if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } Integer val = null; SimulationNode2 bestNode = null; // check if defender is being attacked if (game.getCombat().isAttacked(defenderId, game)) { SimulatedPlayer2 defender = (SimulatedPlayer2) game.getPlayer(defenderId); if (logger.isDebugEnabled()) { logger.debug( defender.getName() + "'s possible blockers: " + defender.getAvailableBlockers(game)); } List<Combat> combats = defender.addBlockers(game); for (Combat engagement : combats) { if (alpha >= beta) { logger.debug("Sim blockers -- pruning blockers"); break; } Game sim = game.copy(); for (CombatGroup group : engagement.getGroups()) { if (group.getAttackers().size() > 0) { UUID attackerId = group.getAttackers().get(0); for (UUID blockerId : group.getBlockers()) { sim.getPlayer(defenderId).declareBlocker(defenderId, blockerId, attackerId, sim); } } } sim.fireEvent( GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, defenderId); if (logger.isDebugEnabled()) { logger.debug("Sim block for player:" + game.getPlayer(defenderId).getName()); } sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); logger.debug("Sim blockers: resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent( GameEvent.getEvent( GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); finishCombat(sim); if (sim.gameOver(null)) { val = GameStateEvaluator2.evaluate(playerId, sim); } else if (!counter) { val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta); } else { val = GameStateEvaluator2.evaluate(playerId, sim); } if (!defenderId.equals(playerId)) { if (val < beta) { beta = val; bestNode = newNode; bestNode.setScore(val); bestNode.setCombat(simCombat); } } else { if (val > alpha) { alpha = val; bestNode = newNode; bestNode.setScore(val); bestNode.setCombat(simCombat); } } } } if (val == null) { val = GameStateEvaluator2.evaluate(playerId, game); } if (bestNode != null) { node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); } if (logger.isDebugEnabled()) { logger.debug( "Sim blockers: returning score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } return val; }
protected int simulateAttackers( Game game, SimulationNode2 node, UUID attackerId, int depth, int alpha, int beta, boolean counter) { if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } Integer val = null; SimulationNode2 bestNode = null; SimulatedPlayer2 attacker = (SimulatedPlayer2) game.getPlayer(attackerId); UUID defenderId = game.getOpponents(attackerId).iterator().next(); if (logger.isDebugEnabled()) { logger.debug( attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(defenderId, game)); } for (Combat engagement : attacker.addAttackers(game)) { if (logger.isDebugEnabled()) { logger.debug( "Sim Attackers: " + engagement.getAttackers() + ", blockers: " + engagement.getBlockers()); } if (alpha >= beta) { logger.debug("Sim Attackers -- pruning attackers"); break; } Game sim = game.copy(); for (CombatGroup group : engagement.getGroups()) { for (UUID attackId : group.getAttackers()) { sim.getPlayer(attackerId).declareAttacker(attackId, defenderId, sim, false); } } sim.fireEvent( GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, attackerId); if (logger.isDebugEnabled()) { logger.debug("Sim attack for player:" + game.getPlayer(attackerId).getName()); } sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); logger.debug("Sim attack: resolving triggered abilities"); sim.applyEffects(); } sim.fireEvent( GameEvent.getEvent( GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); sim.getPhase().setStep(new DeclareBlockersStep()); val = simulateCombat(sim, newNode, depth - 1, alpha, beta, counter); if (!attackerId.equals(playerId)) { if (val < beta) { beta = val; bestNode = newNode; bestNode.setScore(val); if (newNode.getChildren().size() > 0) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } } } else { if (val > alpha) { alpha = val; bestNode = newNode; bestNode.setScore(val); if (newNode.getChildren().size() > 0) { bestNode.setCombat(newNode.getChildren().get(0).getCombat()); } } } } if (val == null) { val = GameStateEvaluator2.evaluate(playerId, game); } if (bestNode != null) { node.children.clear(); node.children.add(bestNode); node.setScore(bestNode.getScore()); } if (logger.isDebugEnabled()) { logger.debug( "Sim attackers: returning score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } return val; }
protected int simulateCombat( Game game, SimulationNode2 node, int depth, int alpha, int beta, boolean counter) { Integer val = null; if (ALLOW_INTERRUPT && Thread.interrupted()) { Thread.currentThread().interrupt(); logger.debug("interrupted"); return GameStateEvaluator2.evaluate(playerId, game); } if (game.getTurn().getStepType() != PhaseStep.DECLARE_BLOCKERS) { game.getTurn().setPhase(new CombatPhase()); if (game.getPhase().beginPhase(game, game.getActivePlayerId())) { simulateStep(game, new BeginCombatStep()); game.getPhase().setStep(new DeclareAttackersStep()); if (!game.getStep().skipStep(game, game.getActivePlayerId())) { game.fireEvent( new GameEvent( GameEvent.EventType.DECLARE_ATTACKERS_STEP_PRE, null, null, game.getActivePlayerId())); if (!game.replaceEvent( GameEvent.getEvent( GameEvent.EventType.DECLARING_ATTACKERS, game.getActivePlayerId(), game.getActivePlayerId()))) { val = simulateAttackers( game, node, game.getActivePlayerId(), depth, alpha, beta, counter); } } else if (!counter) { simulateToEnd(game); val = simulatePostCombatMain(game, node, depth, alpha, beta); } } } else { if (!game.getStep().skipStep(game, game.getActivePlayerId())) { game.fireEvent( new GameEvent( GameEvent.EventType.DECLARE_BLOCKERS_STEP_PRE, null, null, game.getActivePlayerId())); if (!game.replaceEvent( GameEvent.getEvent( GameEvent.EventType.DECLARING_BLOCKERS, game.getActivePlayerId(), game.getActivePlayerId()))) { // only suitable for two player games - only simulates blocks for 1st defender val = simulateBlockers( game, node, game.getCombat().getDefenders().iterator().next(), depth, alpha, beta, counter); } } else if (!counter) { finishCombat(game); /// val = simulateCounterAttack(game, node, depth, alpha, beta); } } if (val == null) { val = GameStateEvaluator2.evaluate(playerId, game); } if (logger.isDebugEnabled()) { logger.debug( "returning -- combat score: " + val + " depth:" + depth + " for player:" + game.getPlayer(node.getPlayerId()).getName()); } return val; }
public boolean checkBlockRestrictions(Game game, int blockersCount) { boolean blockWasLegal = true; if (attackers.isEmpty()) { return blockWasLegal; } if (blockersCount == 1) { List<UUID> toBeRemoved = new ArrayList<>(); for (UUID blockerId : getBlockers()) { Permanent blocker = game.getPermanent(blockerId); if (blocker != null && blocker.getAbilities().containsKey(CantBlockAloneAbility.getInstance().getId())) { blockWasLegal = false; game.informPlayers(blocker.getLogName() + " can't block alone. Removing it from combat."); toBeRemoved.add(blockerId); } } for (UUID blockerId : toBeRemoved) { game.getCombat().removeBlocker(blockerId, game); } if (blockers.isEmpty()) { this.blocked = false; } } for (UUID uuid : attackers) { Permanent attacker = game.getPermanent(uuid); // Check if there are enough blockers to have a legal block if (attacker != null && this.blocked && attacker.getMinBlockedBy() > 1 && blockers.size() > 0 && blockers.size() < attacker.getMinBlockedBy()) { for (UUID blockerId : blockers) { Permanent blocker = game.getPermanent(blockerId); if (blocker != null) { blocker.setBlocking(blocker.getBlocking() - 1); } } blockers.clear(); blockerOrder.clear(); this.blocked = false; game.informPlayers( attacker.getLogName() + " can't be blocked except by " + attacker.getMinBlockedBy() + " or more creatures. Blockers discarded."); blockWasLegal = false; } // Check if there are to many blockers (maxBlockedBy = 0 means no restrictions) if (attacker != null && this.blocked && attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) { for (UUID blockerId : blockers) { Permanent blocker = game.getPermanent(blockerId); if (blocker != null) { blocker.setBlocking(blocker.getBlocking() - 1); } } blockers.clear(); blockerOrder.clear(); this.blocked = false; game.informPlayers( new StringBuilder(attacker.getLogName()) .append(" can't be blocked by more than ") .append(attacker.getMaxBlockedBy()) .append(attacker.getMaxBlockedBy() == 1 ? " creature." : " creatures.") .append(" Blockers discarded.") .toString()); blockWasLegal = false; } } return blockWasLegal; }
@Override public boolean checkTrigger(GameEvent event, Game game) { return !game.getCombat().getAttackers().isEmpty(); }