package magic.model.choice; import magic.model.MagicGame; import magic.model.MagicPermanent; import magic.model.MagicPlayer; import magic.model.MagicRandom; import magic.model.score.MagicCombatScore; import magic.model.score.MagicFastCombatScore; import magic.model.score.MagicGameCombatScore; import magic.model.score.MagicMultipleScoreRanking; import magic.model.score.MagicScoreRanking; import magic.model.score.MagicSingleScoreRanking; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class MagicDeclareBlockersResultBuilder { private static final Collection<Object> EMPTY_RESULT = Collections.<Object>singletonList(new MagicDeclareBlockersResult(0,0)); private static final int MAX_RESULTS=12; private static final int MAX_ATTACKERS=3; private static final int MAX_TURN=1; private static final double MIN_WARN = 1e5; private static final double MIN_SWITCH = 1e5; private static final double NUM_SAMPLES = 1e4; private final MagicGame game; private final MagicPlayer attackingPlayer; private final MagicPlayer defendingPlayer; private final boolean fast; private MagicScoreRanking results; private MagicDeclareBlockersResult result; private MagicCombatScore combatScore; private MagicCombatCreature[] attackers; private Set<MagicCombatCreature> blockers; private int position; MagicDeclareBlockersResultBuilder(final MagicGame game,final MagicPlayer defendingPlayer,final boolean fast) { this.game=game; this.defendingPlayer=defendingPlayer; this.attackingPlayer=defendingPlayer.getOpponent(); this.fast=fast; build(); } Collection<Object> getResults() { return results == null ? EMPTY_RESULT : results.getResults(); } private void buildBlockersFast() { System.err.println("Running randomized blocking algorithm"); //sample NUM_SAMPLES random blocks final MagicRandom rng = new MagicRandom(attackers.length + blockers.size()); for (int i = 0; i < NUM_SAMPLES; i++) { final Map<Integer, List<MagicCombatCreature>> block = new HashMap<Integer, List<MagicCombatCreature>>(); for (int j = 0; j < attackers.length; j++) { block.put(j, new ArrayList<MagicCombatCreature>(5)); block.get(j).add(attackers[j]); } for (final MagicCombatCreature blocker : blockers) { //determine attackers it can block final List<Integer> choices = new ArrayList<Integer>(); for (int j = 0; j < attackers.length; j++) { if (Arrays.asList(attackers[j].candidateBlockers).contains(blocker)) { choices.add(j); } } //choose one of the attackers or don't block final int idx = rng.nextInt(choices.size() + 1); if (idx < choices.size()) { block.get(choices.get(idx)).add(blocker); } } //convert block to a MagicDeclareBlockersResult object result.clear(); for (int j = 0; j < attackers.length; j++) { result.add(block.get(j).toArray(new MagicCombatCreature[0])); } //score result final int score=combatScore.getScore(result); if (results.addScore(score)) { results.addScoreResult(new MagicDeclareBlockersResult(result,position++,score)); } } } private void buildBlockersForAttacker(final int index) { // A new result is found. if (index==attackers.length) { final int score=combatScore.getScore(result); if (results.addScore(score)) { results.addScoreResult(new MagicDeclareBlockersResult(result,position++,score)); } return; } // Get the remaining candidate blockers. final MagicCombatCreature attacker=attackers[index]; final MagicCombatCreature[] candidateBlockers=new MagicCombatCreature[attacker.candidateBlockers.length]; int blockersSize=0; for (final MagicCombatCreature blocker : attacker.candidateBlockers) { if (blockers.contains(blocker)) { candidateBlockers[blockersSize++]=blocker; } } // No blockers. result.addLast(new MagicCombatCreature[]{attacker}); buildBlockersForAttacker(index+1); result.removeLast(); if (blockersSize == 0) { return; } // One blocker. if (blockersSize == 1) { final MagicCombatCreature blocker = candidateBlockers[0]; blockers.remove(blocker); result.addLast(new MagicCombatCreature[]{attacker,blocker}); buildBlockersForAttacker(index+1); result.removeLast(); blockers.add(blocker); return; } // Single blocker which does not deal lethal damage to the attacker. // Not sufficient: might want to chump block with multiple blockers to // survive the attack or damage the attackers enough to finish it off // with direct damage int lethalDamage = attacker.lethalDamage; for (int blockerIndex = 0; blockerIndex < blockersSize; blockerIndex++) { final MagicCombatCreature blocker=candidateBlockers[blockerIndex]; if (blocker.power < lethalDamage) { blockers.remove(blocker); result.addLast(new MagicCombatCreature[]{attacker,blocker}); buildBlockersForAttacker(index+1); result.removeLast(); blockers.add(blocker); } } // All combinations of blockers that deal lethal damage to the attacker. final MagicCombatCreature[] creatures = new MagicCombatCreature[blockersSize+1]; creatures[0] = attacker; int size = 1; final int[] blockerSteps = new int[blockersSize]; final int lastBlockerIndex = blockersSize-1; int blockerIndex = 0; MagicCombatCreature blocker; while (blockerIndex >= 0) { switch (blockerSteps[blockerIndex]++) { case 0: blocker = candidateBlockers[blockerIndex]; blockers.remove(blocker); lethalDamage -= blocker.power; creatures[size++] = blocker; // Lethal blocking combination. if (lethalDamage <= 0) { result.addLast(Arrays.copyOf(creatures,size)); buildBlockersForAttacker(index+1); result.removeLast(); } else if (blockerIndex < lastBlockerIndex) { blockerIndex++; } break; case 1: blocker = candidateBlockers[blockerIndex]; blockers.add(blocker); lethalDamage += blocker.power; size--; if (blockerIndex < lastBlockerIndex) { blockerIndex++; } break; case 2: blockerSteps[blockerIndex--] = 0; break; } } } private void build() { final MagicCombatCreatureBuilder creatureBuilder=new MagicCombatCreatureBuilder(game,attackingPlayer,defendingPlayer); // Check if none of the defending player's creatures can block. if (!creatureBuilder.buildBlockers()) { return; } // Check if none of the attackers can be blocked. blockers=creatureBuilder.getBlockers(); if (!creatureBuilder.buildBlockableAttackers()) { return; } attackers=new MagicCombatCreature[creatureBuilder.getAttackers().size()]; creatureBuilder.getAttackers().toArray(attackers); final boolean defending=game.getScorePlayer()==defendingPlayer; // number of blocking options is max_blocks double max_blocks = 1; for (final MagicPermanent blocker : creatureBuilder.getCandidateBlockers()) { max_blocks *= creatureBuilder.getBlockableAttackers(blocker).size(); } // determine which algorithm to use. if (fast) { results=new MagicSingleScoreRanking(defending); if (attackers.length>MAX_ATTACKERS||game.getRelativeTurn()>MAX_TURN) { combatScore=new MagicFastCombatScore(defendingPlayer,game.getScorePlayer()); } else { combatScore=new MagicGameCombatScore(game,attackingPlayer,defendingPlayer); } } else { results=new MagicMultipleScoreRanking(MAX_RESULTS,defending); combatScore=new MagicGameCombatScore(game,attackingPlayer,defendingPlayer); } // find best combinations of attackers and blockers. result=new MagicDeclareBlockersResult(0,0); position=0; if (max_blocks > MIN_WARN) { System.err.println("WARNING. Number of blocking options is " + max_blocks); } if (max_blocks > MIN_SWITCH) { buildBlockersFast(); } else { buildBlockersForAttacker(0); } } }