Package mage.player.ai

Source Code of mage.player.ai.ComputerPlayer$PickedCard

/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
*    1. Redistributions of source code must retain the above copyright notice, this list of
*       conditions and the following disclaimer.
*
*    2. Redistributions in binary form must reproduce the above copyright notice, this list
*       of conditions and the following disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/

package mage.player.ai;

import mage.MageObject;
import mage.Mana;
import mage.abilities.*;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.*;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.continious.BecomesCreatureSourceEffect;
import mage.abilities.keyword.*;
import mage.abilities.mana.ManaAbility;
import mage.abilities.mana.ManaOptions;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.decks.Deck;
import mage.cards.repository.*;
import mage.choices.Choice;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.*;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.draft.Draft;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.match.Match;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.game.tournament.Tournament;
import mage.player.ai.simulators.CombatGroupSimulator;
import mage.player.ai.simulators.CombatSimulator;
import mage.player.ai.simulators.CreatureSimulator;
import mage.player.ai.utils.RateCard;
import mage.players.Player;
import mage.players.PlayerImpl;
import mage.players.net.UserData;
import mage.players.net.UserGroup;
import mage.target.*;
import mage.target.common.*;
import mage.util.Copier;
import mage.util.TreeNode;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;


/**
*
* suitable for two player games and some multiplayer games
*
* @author BetaSteward_at_googlemail.com
*/
public class ComputerPlayer extends PlayerImpl implements Player {

    private transient final static Logger log = Logger.getLogger(ComputerPlayer.class);
    private transient Map<Mana, Card> unplayable = new TreeMap<>();
    private transient List<Card> playableNonInstant = new ArrayList<>();
    private transient List<Card> playableInstant = new ArrayList<>();
    private transient List<ActivatedAbility> playableAbilities = new ArrayList<>();
    private transient List<PickedCard> pickedCards;
    private transient List<ColoredManaSymbol> chosenColors;

    public ComputerPlayer(String name, RangeOfInfluence range) {
        super(name, range);
        human = false;
        userData = new UserData(UserGroup.COMPUTER, 64, false, null);
        pickedCards = new ArrayList<>();
    }

    protected ComputerPlayer(UUID id) {
        super(id);
        pickedCards = new ArrayList<>();
    }

    public ComputerPlayer(final ComputerPlayer player) {
        super(player);
    }

    @Override
    public boolean chooseMulligan(Game game) {
        log.debug("chooseMulligan");
        if (hand.size() < 6 || isTestMode()) {
            return false;
        }
        Set<Card> lands = hand.getCards(new FilterLandCard(), game);
        if (lands.size() < 2 || lands.size() > hand.size() - 2) {
            return true;
        }
        return false;
    }

    @Override
    public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game) {
        return choose(outcome, target, sourceId, game, null);
    }

    @Override
    public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map<String, Serializable> options) {
        if (log.isDebugEnabled()) {
            log.debug("chooseTarget: " + outcome.toString() + ":" + target.toString());
        }
        UUID opponentId = game.getOpponents(playerId).iterator().next();
        if (target instanceof TargetPlayer) {
            if (outcome.isGood()) {
                if (target.canTarget(playerId, game)) {
                    target.add(playerId, game);
                    return true;
                }
                if (target.isRequired(sourceId, game)) {
                    if (target.canTarget(opponentId, game)) {
                        target.add(opponentId, game);
                        return true;
                    }
                }
            } else {
                if (target.canTarget(opponentId, game)) {
                    target.add(opponentId, game);
                    return true;
                }
                if (target.isRequired(sourceId, game)) {
                    if (target.canTarget(playerId, game)) {
                        target.add(playerId, game);
                        return true;
                    }
                }
            }
            return false;
        }
        if (target instanceof TargetDiscard) {
            findPlayables(game);
            if (unplayable.size() > 0) {
                for (int i = unplayable.size() - 1; i >= 0; i--) {
                    if (target.canTarget(unplayable.values().toArray(new Card[0])[i].getId(), game)) {
                        target.add(unplayable.values().toArray(new Card[0])[i].getId(), game);
                        return true;
                    }
                }
            }
            if (hand.size() > 0) {
                for (int i = 0; i < hand.size(); i++) {
                    if (target.canTarget(hand.toArray(new UUID[0])[i], game)) {
                        target.add(hand.toArray(new UUID[0])[i], game);
                        return true;
                    }
                }
            }
            return false;
        }
        if (target instanceof TargetControlledPermanent) {
            List<Permanent> targets;
            targets = threats(playerId, sourceId, ((TargetControlledPermanent) target).getFilter(), game, target.getTargets());
            if (!outcome.isGood()) {
                Collections.reverse(targets);
            }
            for (Permanent permanent : targets) {
                if (((TargetControlledPermanent) target).canTarget(playerId, permanent.getId(), sourceId, game, false) && !target.getTargets().contains(permanent.getId())) {
                    target.add(permanent.getId(), game);
                    return true;
                }
            }
            return false;
        }
        if (target instanceof TargetPermanent) {
            List<Permanent> targets;
            if (outcome.isCanTargetAll()) {
                targets = threats(null, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
            } else {
                if (outcome.isGood()) {
                    targets = threats(playerId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
                } else {
                    targets = threats(opponentId, sourceId, ((TargetPermanent) target).getFilter(), game, target.getTargets());
                }
            }
            for (Permanent permanent : targets) {
                if (((TargetPermanent) target).canTarget(playerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) {
                    target.add(permanent.getId(), game);
                    return true;
                }
            }
            return false;
        }
        if (target instanceof TargetCardInHand) {
            List<Card> cards = new ArrayList<>();
            for (UUID cardId: ((TargetCardInHand)target).possibleTargets(sourceId, this.getId(), game)) {
                Card card = game.getCard(cardId);
                if (card != null) {
                    cards.add(card);
                }
            }           
            while(!target.isChosen() && !cards.isEmpty()) {
                Card pick = pickTarget(cards, outcome, target, null, game);
                if (pick != null) {
                    target.addTarget(pick.getId(), null, game);
                    cards.remove(pick);
                }
            }
            return target.isChosen();
        }
        if (target instanceof TargetCreatureOrPlayer) {
            List<Permanent> targets;
            TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer) target);
            if (outcome.isGood()) {
                targets = threats(playerId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets());
            } else {
                targets = threats(opponentId, sourceId, ((FilterCreatureOrPlayer) t.getFilter()).getCreatureFilter(), game, target.getTargets());
            }
            for (Permanent permanent : targets) {
                List<UUID> alreadyTargetted = target.getTargets();
                if (t.canTarget(playerId, permanent.getId(), null, game)) {
                    if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
                        target.add(permanent.getId(), game);
                        return true;
                    }
                }
            }
            if (outcome.isGood()) {
                if (target.canTarget(playerId, null, game)) {
                    target.add(playerId, game);
                    return true;
                }
            } else {
                if (target.canTarget(opponentId, null, game)) {
                    target.add(opponentId, game);
                    return true;
                }
            }
            if (!target.isRequired(sourceId, game)) {
                return false;
            }
        }

        if (target instanceof TargetPermanentOrPlayer) {
            List<Permanent> targets;
            TargetPermanentOrPlayer t = ((TargetPermanentOrPlayer) target);
            if (outcome.isGood()) {
                targets = threats(playerId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets());
            } else {
                targets = threats(opponentId, sourceId, ((FilterPermanentOrPlayer) t.getFilter()).getPermanentFilter(), game, target.getTargets());
            }
            for (Permanent permanent : targets) {
                List<UUID> alreadyTargetted = target.getTargets();
                if (t.canTarget(permanent.getId(), game)) {
                    if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
                        target.add(permanent.getId(), game);
                        return true;
                    }
                }
            }
            if (outcome.isGood()) {
                if (target.canTarget(playerId, null, game)) {
                    target.add(playerId, game);
                    return true;
                }
            } else {
                if (target.canTarget(opponentId, null, game)) {
                    target.add(opponentId, game);
                    return true;
                }
            }
            if (!target.isRequired(sourceId, game)) {
                return false;
            }
            throw new IllegalStateException("TargetPermanentOrPlayer wasn't handled. class:" + target.getClass().toString());
        }
        if (target instanceof TargetCardInGraveyard) {
            List<Card> cards = new ArrayList<>();
            for (Player player: game.getPlayers().values()) {
                for (Card card: player.getGraveyard().getCards(game)) {
                    if (target.canTarget(card.getId(), game)) {
                        cards.add(card);
                    }
                }
            }
            for (Card card: cards) {
                target.add(card.getId(), game);
                if (target.isChosen()) {
                    return true;
                }
            }
            return target.isChosen();
        }

        if (target instanceof TargetCardInYourGraveyard) {
            List<UUID> alreadyTargetted = target.getTargets();
            List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getGraveyard().getCards(game));
            while(!cards.isEmpty()) {
                Card card = pickTarget(cards, outcome, target, null, game);
                if (card != null && alreadyTargetted != null && !alreadyTargetted.contains(card.getId())) {
                    target.add(card.getId(), game);
                    return true;
                }
            }
            return false;
        }
        if (target instanceof TargetSource) {
            Set<UUID> targets;
            TargetSource t = ((TargetSource) target);
            targets = t.possibleTargets(sourceId, playerId, game);
            for (UUID targetId : targets) {
                MageObject targetObject = game.getObject(targetId);
                if (targetObject != null) {
                    List<UUID> alreadyTargetted = target.getTargets();
                    if (t.canTarget(targetObject.getId(), game)) {
                        if (alreadyTargetted != null && !alreadyTargetted.contains(targetObject.getId())) {
                            target.add(targetObject.getId(), game);
                            return true;
                        }
                    }
                }
            }
            if (!target.isRequired(sourceId, game)) {
                return false;
            }
            throw new IllegalStateException("TargetSource wasn't handled. class:" + target.getClass().toString());
        }

        throw new IllegalStateException("Target wasn't handled. class:" + target.getClass().toString());
    }

    @Override
    public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
        if (log.isDebugEnabled()) {
            log.debug("chooseTarget: " + outcome.toString() + ":" + target.toString());
        }
        UUID opponentId = game.getOpponents(playerId).iterator().next();
        if (target instanceof TargetPlayer) {
            if (outcome.isGood()) {
                if (target.canTarget(playerId, source, game)) {
                    target.addTarget(playerId, source, game);
                    return true;
                }
                if (target.isRequired(source)) {
                    if (target.canTarget(opponentId, source, game)) {
                        target.addTarget(opponentId, source, game);
                        return true;
                    }
                }
            }
            else {
                if (target.canTarget(opponentId, source, game)) {
                    target.addTarget(opponentId, source, game);
                    return true;
                }
                if (target.isRequired(source)) {
                    if (target.canTarget(playerId, source, game)) {
                        target.addTarget(playerId, source, game);
                        return true;
                    }
                }
            }
            return false;
        }
        if (target instanceof TargetDiscard || target instanceof TargetCardInHand) {
            if (outcome.isGood()) {
                ArrayList<Card> cardsInHand = new ArrayList<>(hand.getCards(game));
                while (!target.isChosen() && !cardsInHand.isEmpty() && target.getMaxNumberOfTargets() < target.getTargets().size()) {
                    Card card = pickBestCard(cardsInHand, null, target, source, game);
                    if (card != null) {
                        if (target.canTarget(card.getId(), source, game)) {
                            target.addTarget(card.getId(), source, game);
                            cardsInHand.remove(card);
                            if (target.isChosen()) {
                                return true;
                            }
                        }
                    }
                }
            }
            else {
                findPlayables(game);
                if (unplayable.size() > 0) {
                    for (int i = unplayable.size() - 1; i >= 0; i--) {
                        if (target.canTarget(unplayable.values().toArray(new Card[0])[i].getId(), source, game)) {
                            target.addTarget(unplayable.values().toArray(new Card[0])[i].getId(), source, game);
                            if (target.isChosen()) {
                                return true;
                            }
                        }
                    }
                }
                if (hand.size() > 0) {
                    for (int i = 0; i < hand.size(); i++) {
                        if (target.canTarget(hand.toArray(new UUID[0])[i], source, game)) {
                            target.addTarget(hand.toArray(new UUID[0])[i], source, game);
                            if (target.isChosen()) {
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }
        if (target instanceof TargetControlledPermanent) {
            List<Permanent> targets;
            targets = threats(playerId, source.getSourceId(), ((TargetControlledPermanent)target).getFilter(), game, target.getTargets());
            if (!outcome.isGood()) {
                Collections.reverse(targets);
            }
            for (Permanent permanent: targets) {
                if (((TargetControlledPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
                    target.addTarget(permanent.getId(), source, game);
                    if (target.getNumberOfTargets() <= target.getTargets().size() && (!outcome.isGood() || target.getMaxNumberOfTargets() <= target.getTargets().size())) {
                        return true;
                    }
                }
            }
            return target.isChosen();

        }
        if (target instanceof TargetPermanent) {
            List<Permanent> targets;
            boolean outcomeTargets = true;
            if (outcome.isGood()) {
                targets = threats(playerId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
            }
            else {
                targets = threats(opponentId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
            }           
            if (targets.isEmpty() && target.isRequired(source)) {
                targets = threats(null, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
                Collections.reverse(targets);
                outcomeTargets = false;
                //targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game);
            }
            for (Permanent permanent: targets) {
                if (((TargetPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
                    target.addTarget(permanent.getId(), source, game);
                    if (!outcomeTargets || target.getMaxNumberOfTargets() <= target.getTargets().size()) {
                        return true;
                    }
                }
            }
            return target.isChosen();
        }
        if (target instanceof TargetCreatureOrPlayer) {
            List<Permanent> targets;
            TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer)target);
            if (outcome.isGood()) {
                targets = threats(playerId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets());
            }
            else {
                targets = threats(opponentId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets());
            }

            if (targets.isEmpty()) {
                if (outcome.isGood()) {
                    if (target.canTarget(playerId, source, game)) {
                        target.addTarget(playerId, source, game);
                        return true;
                    }
                }
                else {
                    if (target.canTarget(opponentId, source, game)) {
                        target.addTarget(opponentId, source, game);
                        return true;
                    }
                }
            }

            if (targets.isEmpty() && target.isRequired(source)) {
                targets = game.getBattlefield().getActivePermanents(((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), playerId, game);
            }
            for (Permanent permanent : targets) {
                List<UUID> alreadyTargetted = target.getTargets();
                if (t.canTarget(playerId, permanent.getId(), source, game)) {
                    if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
                        target.addTarget(permanent.getId(), source, game);
                        return true;
                    }
                }
            }

            if (outcome.isGood()) {
                if (target.canTarget(playerId, source, game)) {
                    target.addTarget(playerId, source, game);
                    return true;
                }
            }
            else {
                if (target.canTarget(opponentId, source, game)) {
                    target.addTarget(opponentId, source, game);
                    return true;
                }
            }

            //if (!target.isRequired())
                return false;
        }
        if (target instanceof TargetCardInGraveyard) {
            List<Card> cards = new ArrayList<>();
            for (Player player: game.getPlayers().values()) {
                cards.addAll(player.getGraveyard().getCards(game));
            }
            Card card = pickTarget(cards, outcome, target, source, game);
            if (card != null) {
                target.addTarget(card.getId(), source, game);
                return true;
            }
            //if (!target.isRequired())
                return false;
        }
        if (target instanceof TargetCardInLibrary) {
            List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getLibrary().getCards(game));
            Card card = pickTarget(cards, outcome, target, source, game);
            if (card != null) {
                target.addTarget(card.getId(), source, game);
                return true;
            }
            return false;
        }
        if (target instanceof TargetCardInYourGraveyard) {
            List<Card> cards = new ArrayList<>(game.getPlayer(playerId).getGraveyard().getCards(game));
            while(!target.isChosen() && !cards.isEmpty()) {
                Card card = pickTarget(cards, outcome, target, source, game);
                if (card != null) {
                    target.addTarget(card.getId(), source, game);
                }
            }
            return target.isChosen();
        }
        if (target instanceof TargetCardInHand) {
            List<Card> cards = new ArrayList<>();
            cards.addAll(this.hand.getCards(game));
            while(!target.isChosen() && !cards.isEmpty()) {
                Card pick = pickTarget(cards, outcome, target, source, game);
                if (pick != null) {
                    target.addTarget(pick.getId(), source, game);
                }
            }
            return target.isChosen();
        }
        if (target instanceof TargetSpell) {
            if (game.getStack().size() > 0) {
                Iterator<StackObject> it = game.getStack().iterator();
                while (it.hasNext()) {
                    StackObject o = it.next();
                    if (o instanceof Spell && !source.getId().equals(o.getStackAbility().getId())) {
                        target.addTarget(o.getId(), source, game);
                        return true;
                    }
                }
            }
            return false;
        }       
        if (target instanceof TargetSpellOrPermanent) {
            List<Permanent> targets;
            boolean outcomeTargets = true;
            if (outcome.isGood()) {
                targets = threats(playerId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
            }
            else {
                targets = threats(opponentId, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
            }           
            if (targets.isEmpty() && target.isRequired(source)) {
                targets = threats(null, source == null?null:source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets());
                Collections.reverse(targets);
                outcomeTargets = false;
                //targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game);
            }
            for (Permanent permanent: targets) {
                if (((TargetPermanent)target).canTarget(playerId, permanent.getId(), source, game)) {
                    target.addTarget(permanent.getId(), source, game);
                    if (!outcomeTargets || target.getMaxNumberOfTargets() <= target.getTargets().size()) {
                        return true;
                    }
                }
            }          
            if (game.getStack().size() > 0) {
                Iterator<StackObject> it = game.getStack().iterator();
                while (it.hasNext()) {
                    StackObject o = it.next();
                    if (o instanceof Spell && !source.getId().equals(o.getStackAbility().getId())) {
                        target.addTarget(o.getId(), source, game);
                        return true;
                    }
                }
            }
            return false;
        }
        if (target instanceof TargetCardInOpponentsGraveyard) {
            List<Card> cards = new ArrayList<>();
            for (UUID uuid: game.getOpponents(playerId)) {
                Player player = game.getPlayer(uuid);
                if (player != null) {
                    cards.addAll(player.getGraveyard().getCards(game));
                }
            }
            Card card = pickTarget(cards, outcome, target, source, game);
            if (card != null) {
                target.addTarget(card.getId(), source, game);
                return true;
            }
            //if (!target.isRequired())
                return false;
        }
        if (target instanceof TargetDefender) {
            // TODO: Improve, now planeswalker is always chosen if it exits
            List<Permanent> targets ;
            targets = game.getBattlefield().getActivePermanents(new FilterPlaneswalkerPermanent(), opponentId, game);
            if (targets != null && !targets.isEmpty()) {
                for (Permanent planeswalker: targets) {
                    if (target.canTarget(planeswalker.getId(), source, game)) {
                        target.addTarget(planeswalker.getId(), source, game);
                    }
                    if (target.isChosen()) {
                        return true;
                    }
                }
            }
            if (!target.isChosen()) {
                if (target.canTarget(opponentId, source, game)) {
                    target.addTarget(opponentId, source, game);
                }
            }
            return target.isChosen();
        }

        if (target instanceof TargetCardInASingleGraveyard) {
            List<Card> cards = new ArrayList<>();
            for (Player player: game.getPlayers().values()) {
                cards.addAll(player.getGraveyard().getCards(game));
            }
            while(!target.isChosen() && !cards.isEmpty()) {
                Card pick = pickTarget(cards, outcome, target, source, game);
                if (pick != null) {
                    target.addTarget(pick.getId(), source, game);
                }
            }
            return target.isChosen();
        }

        if (target instanceof TargetCardInExile) {
            List<Card> cards = new ArrayList<>();
            for (UUID uuid: ((TargetCardInExile) target).possibleTargets(source.getSourceId(), source.getControllerId(), game)) {
                Card card = game.getCard(uuid);
                if (card != null) {
                    cards.add(card);
                }
            }
            while(!target.isChosen() && !cards.isEmpty()) {
                Card pick = pickTarget(cards, outcome, target, source, game);
                if (pick != null) {
                    target.addTarget(pick.getId(), source, game);
                }
            }
            return target.isChosen();
        }


        throw new IllegalStateException("Target wasn't handled. class:" + target.getClass().toString());
    }

    protected Card pickTarget(List<Card> cards, Outcome outcome, Target target, Ability source, Game game) {
        Card card;
        while (!cards.isEmpty()) {
            if (outcome.isGood()) {
                card = pickBestCard(cards, null, target, source, game);
            }
            else {
                card = pickWorstCard(cards, null, target, source, game);
            }
            if (source != null) {
                if (target.canTarget(this.getId(), card.getId(), source, game)) {
                    return card;
                }
            }
            else {
                return card;
            }
            cards.remove(card);
        }
        return null;
    }

    @Override
    public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
        if (log.isDebugEnabled()) {
            log.debug("chooseTarget: " + outcome.toString() + ":" + target.toString());
        }
        UUID opponentId = game.getOpponents(playerId).iterator().next();
        if (target instanceof TargetCreatureOrPlayerAmount) {
            if (outcome.equals(Outcome.Damage) && game.getPlayer(opponentId).getLife() <= target.getAmountRemaining()) {
                target.addTarget(opponentId, target.getAmountRemaining(), source, game);
                return true;
            }
            List<Permanent> targets;
            if (outcome.isGood()) {
                targets = threats(playerId, source.getSourceId(), new FilterCreaturePermanent(), game, target.getTargets());
            }
            else {
                targets = threats(opponentId, source.getSourceId(), new FilterCreaturePermanent(), game, target.getTargets());
            }
            for (Permanent permanent: targets) {
                if (target.canTarget(permanent.getId(), source, game)) {
                    if (permanent.getToughness().getValue() <= target.getAmountRemaining()) {
                        target.addTarget(permanent.getId(), permanent.getToughness().getValue(), source, game);
                        return true;
                    }
                }
            }
            if (outcome.isGood() && target.canTarget(playerId, playerId, source, game)) {
                target.addTarget(opponentId, target.getAmountRemaining(), source, game);
                return true;
            } else if (target.canTarget(playerId, opponentId, source, game)){
                // no permanent target so take opponent
                target.addTarget(opponentId, target.getAmountRemaining(), source, game);
                return true;
            } else if (target.canTarget(playerId, playerId, source, game)) {
                target.addTarget(opponentId, target.getAmountRemaining(), source, game);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean priority(Game game) {
        game.resumeTimer(playerId);
        log.debug("priority");
        boolean result = priorityPlay(game);
        game.pauseTimer(playerId);
        return result;
    }

    private boolean priorityPlay(Game game) {
        UUID opponentId = game.getOpponents(playerId).iterator().next();
        if (game.getActivePlayerId().equals(playerId)) {
            if (game.isMainPhase() && game.getStack().isEmpty()) {
                playLand(game);
            }
            switch (game.getTurn().getStepType()) {
                case UPKEEP:
                    findPlayables(game);
                    break;
                case DRAW:
                    logState(game);
                    break;
                case PRECOMBAT_MAIN:
                    findPlayables(game);
                    if (playableAbilities.size() > 0) {
                        for (ActivatedAbility ability: playableAbilities) {
                            if (ability.canActivate(playerId, game)) {
                                if (ability.getEffects().hasOutcome(Outcome.PutLandInPlay)) {
                                    if (this.activateAbility(ability, game)) {
                                        return true;
                                    }
                                }
                                if (ability.getEffects().hasOutcome(Outcome.PutCreatureInPlay)) {
                                    if (getOpponentBlockers(opponentId, game).size() <= 1) {
                                        if (this.activateAbility(ability, game)) {
                                            return true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    break;
                case DECLARE_BLOCKERS:
                    findPlayables(game);
                    playRemoval(game.getCombat().getBlockers(), game);
                    playDamage(game.getCombat().getBlockers(), game);
                    break;
                case END_COMBAT:
                    findPlayables(game);
                    playDamage(game.getCombat().getBlockers(), game);
                    break;
                case POSTCOMBAT_MAIN:
                    findPlayables(game);
                    if (game.getStack().isEmpty()) {
                        if (playableNonInstant.size() > 0) {
                            for (Card card: playableNonInstant) {
                                if (card.getSpellAbility().canActivate(playerId, game)) {
                                    if (this.activateAbility(card.getSpellAbility(), game)) {
                                        return true;
                                    }
                                }
                            }
                        }
                        if (playableAbilities.size() > 0) {
                            for (ActivatedAbility ability: playableAbilities) {
                                if (ability.canActivate(playerId, game)) {
                                    if (!(ability.getEffects().get(0) instanceof BecomesCreatureSourceEffect)) {
                                        if (this.activateAbility(ability, game)) {
                                            return true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    break;
            }
        }
        else {
            //respond to opponent events
            switch (game.getTurn().getStepType()) {
                case UPKEEP:
                    findPlayables(game);
                    break;
                case DECLARE_ATTACKERS:
                    findPlayables(game);
                    playRemoval(game.getCombat().getAttackers(), game);
                    playDamage(game.getCombat().getAttackers(), game);
                    break;
                case END_COMBAT:
                    findPlayables(game);
                    playDamage(game.getCombat().getAttackers(), game);
                    break;
            }
        }
        pass(game);
        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 = new LinkedHashSet<>();
        for (Card landCard:  hand.getCards(new FilterLandCard(), game)) {
            // remove lands that can not be played
            if (game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, landCard.getId(), landCard.getId(), playerId), null, game, true)) {
                break;
            }
            lands.add(landCard);
        }
        while (lands.size() > 0 && this.canPlayLand()) {
            if (lands.size() == 1) {
                this.playLand(lands.iterator().next(), game);
            }
            else {
                playALand(lands, game);
            }
        }
    }

    protected void playALand(Set<Card> lands, Game game) {
        log.debug("playALand");
        //play a land that will allow us to play an unplayable
        for (Mana mana: unplayable.keySet()) {
            for (Card card: lands) {
                for (ManaAbility ability: card.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
                    if (ability.getNetMana(game).enough(mana)) {
                        this.playLand(card, game);
                        lands.remove(card);
                        return;
                    }
                }
            }
        }
        //play a land that will get us closer to playing an unplayable
        for (Mana mana: unplayable.keySet()) {
            for (Card card: lands) {
                for (ManaAbility ability: card.getAbilities().getManaAbilities(Zone.BATTLEFIELD)) {
                    if (mana.contains(ability.getNetMana(game))) {
                        this.playLand(card, game);
                        lands.remove(card);
                        return;
                    }
                }
            }
        }
        //play first available land
        this.playLand(lands.iterator().next(), game);
        lands.remove(lands.iterator().next());
    }

    protected void findPlayables(Game game) {
        playableInstant.clear();
        playableNonInstant.clear();
        unplayable.clear();
        playableAbilities.clear();
        Set<Card> nonLands = hand.getCards(new FilterNonlandCard(), game);
        ManaOptions available = getManaAvailable(game);
        available.addMana(manaPool.getMana());

        for (Card card: nonLands) {
            ManaOptions options = card.getManaCost().getOptions();
            if (card.getManaCost().getVariableCosts().size() > 0) {
                //don't use variable mana costs unless there is at least 3 extra mana for X
                for (Mana option: options) {
                    option.add(Mana.ColorlessMana(3));
                }
            }
            for (Mana mana: options) {
                for (Mana avail: available) {
                    if (mana.enough(avail)) {
                        SpellAbility ability = card.getSpellAbility();
                        if (ability != null && ability.canActivate(playerId, game) &&
                                game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, ability.getSourceId(), ability.getSourceId(), playerId), ability, game, true)) {
                            if (card.getCardType().contains(CardType.INSTANT)
                                    || card.hasAbility(FlashAbility.getInstance().getId(), game)) {
                                playableInstant.add(card);
                            }
                            else {
                                playableNonInstant.add(card);
                            }
                        }
                    }
                    else {
                        if (!playableInstant.contains(card) && !playableNonInstant.contains(card)) {
                            unplayable.put(mana.needed(avail), card);
                        }
                    }
                }
            }
        }
        for (Permanent permanent: game.getBattlefield().getAllActivePermanents(playerId)) {
            for (ActivatedAbility ability: permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
                if (!(ability instanceof ManaAbility) && ability.canActivate(playerId, game)) {
                    if (ability instanceof EquipAbility && permanent.getAttachedTo() != null) {
                        continue;
                    }
                    ManaOptions abilityOptions = ability.getManaCosts().getOptions();
                    if (ability.getManaCosts().getVariableCosts().size() > 0) {
                        //don't use variable mana costs unless there is at least 3 extra mana for X
                        for (Mana option: abilityOptions) {
                            option.add(Mana.ColorlessMana(3));
                        }
                    }
                    if (abilityOptions.size() == 0) {
                        playableAbilities.add(ability);
                    }
                    else {
                        for (Mana mana: abilityOptions) {
                            for (Mana avail: available) {
                                if (mana.enough(avail)) {
                                    playableAbilities.add(ability);
                                }
                            }
                        }
                    }
                }
            }
        }
        for (Card card: graveyard.getCards(game)) {
            for (ActivatedAbility ability: card.getAbilities().getActivatedAbilities(Zone.GRAVEYARD)) {
                if (ability.canActivate(playerId, game)) {
                    ManaOptions abilityOptions = ability.getManaCosts().getOptions();
                    if (abilityOptions.size() == 0) {
                        playableAbilities.add(ability);
                    }
                    else {
                        for (Mana mana: abilityOptions) {
                            for (Mana avail: available) {
                                if (mana.enough(avail)) {
                                    playableAbilities.add(ability);
                                }
                            }
                        }
                    }
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("findPlayables: " + playableInstant.toString() + "---" + playableNonInstant.toString() + "---" + playableAbilities.toString() );
        }
    }

    @Override
    public boolean playMana(ManaCost unpaid, Game game) {
//        log.info("paying for " + unpaid.getText());
        ManaCost cost;
        List<Permanent> producers;
        if (unpaid instanceof ManaCosts) {
            cost = ((ManaCosts<ManaCost>)unpaid).get(0);
            producers = getSortedProducers((ManaCosts)unpaid, game);
        }
        else {
            cost = unpaid;
            producers = this.getAvailableManaProducers(game);
            producers.addAll(this.getAvailableManaProducersWithCost(game));
        }
        for (Permanent perm: producers) {
            // pay all colored costs first
            for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) {
                if (cost instanceof ColoredManaCost) {
                    if (cost.testPay(ability.getNetMana(game))) {
                        if (activateAbility(ability, game)) {
                            return true;
                        }
                    }
                }
            }
            // then pay hybrid
            for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) {
                if (cost instanceof HybridManaCost) {
                    if (cost.testPay(ability.getNetMana(game))) {
                        if (activateAbility(ability, game)) {
                            return true;
                        }
                    }
                }
            }
            // then pay mono hybrid
            for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) {
                if (cost instanceof MonoHybridManaCost) {
                    if (cost.testPay(ability.getNetMana(game))) {
                        if (activateAbility(ability, game)) {
                            return true;
                        }
                    }
                }
            }
            // finally pay generic
            for (ManaAbility ability: perm.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) {
                if (cost instanceof GenericManaCost) {
                    if (cost.testPay(ability.getNetMana(game))) {
                        if (activateAbility(ability, game)) {
                            return true;
                        }
                    }
                }
            }
        }
        // pay phyrexian life costs
        if (cost instanceof PhyrexianManaCost) {
            if (cost.pay(null, game, null, playerId, false)) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * returns a list of Permanents that produce mana sorted by the number of mana the Permanent produces
     * that match the unpaid costs in ascending order
     *
     * the idea is that we should pay costs first from mana producers that produce only one type of mana
     * and save the multi-mana producers for those costs that can't be paid by any other producers
     *
     * @param unpaid - the amount of unpaid mana costs
     * @param game
     * @return List<Permanent>
     */
    private List<Permanent> getSortedProducers(ManaCosts<ManaCost> unpaid, Game game) {
        List<Permanent> unsorted = this.getAvailableManaProducers(game);
        unsorted.addAll(this.getAvailableManaProducersWithCost(game));
        Map<Permanent, Integer> scored = new HashMap<Permanent, Integer>();
        for (Permanent permanent: unsorted) {
            int score = 0;
            for (ManaCost cost: unpaid) {
                for (ManaAbility ability: permanent.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game)) {
                    if (cost.testPay(ability.getNetMana(game))) {
                        score++;
                        break;
                    }
                }
            }
            if (score > 0) { // score mana producers that produce other mana types and have other uses higher
                score += permanent.getAbilities().getAvailableManaAbilities(Zone.BATTLEFIELD, game).size();
                score += permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD).size();
                if (!permanent.getCardType().contains(CardType.LAND)) {
                    score+=2;
                }
                else if(permanent.getCardType().contains(CardType.CREATURE)) {
                    score+=2;
                }
            }
            scored.put(permanent, score);
        }
        return sortByValue(scored);
    }

    private List<Permanent> sortByValue(Map<Permanent, Integer> map) {
        List<Entry<Permanent, Integer>> list = new LinkedList<Entry<Permanent, Integer>>(map.entrySet());
        Collections.sort(list, new Comparator<Entry<Permanent, Integer>>() {
            @Override
            public int compare(Entry<Permanent, Integer> o1, Entry<Permanent, Integer> o2) {
                return (o1.getValue().compareTo(o2.getValue()));
            }
        });
        List<Permanent> result = new ArrayList<Permanent>();
        for (Entry<Permanent, Integer> entry : list) {
            result.add(entry.getKey());
        }
        return result;
    }

    @Override
    public int announceXMana(int min, int max, String message, Game game, Ability ability) {
        log.debug("announceXMana");
        //TODO: improve this
        int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().convertedManaCost();
        if (numAvailable < 0) {
            numAvailable = 0;
        }
        return numAvailable;
    }

    @Override
    public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) {
        log.debug("announceXMana");
        //TODO: improve this
        int value = new Random().nextInt(max+1);
        if (value < max) {
            value++;
        }
        return value;
    }


    @Override
    public void abort() {
        abort = true;
    }

    @Override
    public void skip() {
    }

    @Override
    public boolean chooseUse(Outcome outcome, String message, Game game) {
        log.debug("chooseUse: " + outcome.isGood());
        // Be proactive! Always use abilities, the evaluation function will decide if it's good or not
        // Otherwise some abilities won't be used by AI like LoseTargetEffect that has "bad" outcome
        // but still is good when targets opponent
        return !outcome.equals(Outcome.AIDontUseIt); // Added for Desecration Demon sacrifice ability
    }

    @Override
    public boolean choose(Outcome outcome, Choice choice, Game game) {
        log.debug("choose 3");
        //TODO: improve this
        if (choice.getMessage() != null && choice.getMessage().equals("Choose creature type")) {
            chooseCreatureType(outcome, choice, game);
        }
        if (!choice.isChosen()) {
            int choiceIdx = (int) (Math.random()*choice.getChoices().size()+1);
            for (String next : choice.getChoices()) {
                if (--choiceIdx > 0) {
                    continue;
                }
                if (!next.isEmpty()) {
                    choice.setChoice(next);
                    break;
                }
            }
        }
        return true;
    }

    protected boolean chooseCreatureType(Outcome outcome, Choice choice, Game game) {
        if (outcome.equals(Outcome.Detriment)) {
            // choose a creature type of opponent on battlefield or graveyard
            for (Permanent permanent :game.getBattlefield().getActivePermanents(this.getId(), game)) {
                if(game.getOpponents(this.getId()).contains(permanent.getControllerId())
                        && permanent.getCardType().contains(CardType.CREATURE)
                        && permanent.getSubtype().size() > 0) {
                    if (choice.getChoices().contains(permanent.getSubtype().get(0))) {
                        choice.setChoice(permanent.getSubtype().get(0));
                        break;
                    }
                }
            }
            // or in opponent graveyard
            if (!choice.isChosen()) {
                for (UUID opponentId :game.getOpponents(this.getId())) {
                    Player opponent = game.getPlayer(opponentId);
                    for (Card card : opponent.getGraveyard().getCards(game)) {
                        if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getSubtype().size() > 0) {
                            if (choice.getChoices().contains(card.getSubtype().get(0))) {
                                choice.setChoice(card.getSubtype().get(0));
                                break;
                            }
                        }
                    }
                    if (choice.isChosen()) {
                        break;
                    }
                }
            }
        } else {
            // choose a creature type of hand or library
            for (UUID cardId :this.getHand()) {
                Card card = game.getCard(cardId);
                if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getSubtype().size() > 0) {
                    if (choice.getChoices().contains(card.getSubtype().get(0))) {
                        choice.setChoice(card.getSubtype().get(0));
                        break;
                    }
                }
            }
            if (!choice.isChosen()) {
                for (UUID cardId : this.getLibrary().getCardList()) {
                    Card card = game.getCard(cardId);
                    if (card != null && card.getCardType().contains(CardType.CREATURE) && card.getSubtype().size() > 0) {
                        if (choice.getChoices().contains(card.getSubtype().get(0))) {
                            choice.setChoice(card.getSubtype().get(0));
                            break;
                        }
                    }
                }
            }
        }
        return choice.isChosen();
    }
    @Override
    public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game)  {
        log.debug("chooseTarget");
        if (cards == null || cards.isEmpty()) {
            if (!target.isRequired(source)) {
                return false;
            }
            return true;
        }

        ArrayList<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), game));
        while (!target.doneChosing()) {
            Card card = pickTarget(cardChoices, outcome, target, source, game);
            if (card != null) {
                target.addTarget(card.getId(), source, game);
                cardChoices.remove(card);
            }
            if (outcome.equals(Outcome.Neutral) && target.getTargets().size() > target.getNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getNumberOfTargets()) / 2) {
                return true;
            }
        }
        return true;
    }

    @Override
    public boolean choose(Outcome outcome, Cards cards, TargetCard target, Game game)  {
        log.debug("choose 2");
        if (cards == null || cards.isEmpty()) {
            return true;
        }

        ArrayList<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), game));
        while (!target.doneChosing()) {
            Card card = pickTarget(cardChoices, outcome, target, null, game);
            if (card != null) {
                target.add(card.getId(), game);
                cardChoices.remove(card);
            } else {
                // We don't have any valid target to choose so stop choosing
                break;
            }
            if (outcome.equals(Outcome.Neutral) && target.getTargets().size() > target.getNumberOfTargets() + (target.getMaxNumberOfTargets() - target.getNumberOfTargets()) / 2) {
                return true;
            }
        }
        return true;
    }

    @Override
    public boolean choosePile(Outcome outcome, String message, List<? extends Card> pile1, List<? extends Card> pile2, Game game) {
        //TODO: improve this
        return true;
    }

    @Override
    public void selectAttackers(Game game, UUID attackingPlayerId) {
        log.debug("selectAttackers");
        UUID opponentId = game.getCombat().getDefenders().iterator().next();
        Attackers attackers = getPotentialAttackers(game);
        List<Permanent> blockers = getOpponentBlockers(opponentId, game);
        List<Permanent> actualAttackers = new ArrayList<>();
        if (blockers.isEmpty()) {
            actualAttackers = attackers.getAttackers();
        }
        else if (attackers.size() - blockers.size() >= game.getPlayer(opponentId).getLife()) {
            actualAttackers = attackers.getAttackers();
        }
        else {
            CombatSimulator combat = simulateAttack(attackers, blockers, opponentId, game);
            if (combat.rating > 2) {
                for (CombatGroupSimulator group: combat.groups) {
                    this.declareAttacker(group.attackers.get(0).id, group.defenderId, game, false);
                }
            }
        }
        for (Permanent attacker: actualAttackers) {
            this.declareAttacker(attacker.getId(), opponentId, game, false);
        }
    }

    @Override
    public void selectBlockers(Game game, UUID defendingPlayerId) {
        log.debug("selectBlockers");

        List<Permanent> blockers = getAvailableBlockers(game);

        CombatSimulator sim = simulateBlock(CombatSimulator.load(game), blockers, game);

        List<CombatGroup> groups = game.getCombat().getGroups();
        for (int i = 0; i< groups.size(); i++) {
            for (CreatureSimulator creature: sim.groups.get(i).blockers) {
                groups.get(i).addBlocker(creature.id, playerId, game);
            }
        }
    }

    @Override
    public int chooseReplacementEffect(Map<String, String> rEffects, Game game) {
        log.debug("chooseReplacementEffect");
        //TODO: implement this
        return 0;
    }

    @Override
    public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) {
        switch(ability.getSpellAbilityType()) {
            case SPLIT:
            case SPLIT_FUSED:
                MageObject object = game.getObject(ability.getSourceId());
                if (object != null) {
                    LinkedHashMap<UUID, ActivatedAbility> useableAbilities = getSpellAbilities(object, game.getState().getZone(object.getId()), game);
                    if (useableAbilities != null && useableAbilities.size() > 0) {
                        game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values()));
                        // TODO: Improve this
                        return (SpellAbility) useableAbilities.values().iterator().next();
                    }
                }
                return null;
            default:
                return ability;
        }
    }

    @Override
    public Mode chooseMode(Modes modes, Ability source, Game game) {
        log.debug("chooseMode");
        if (modes.getMode() != null && modes.getMaxModes() == modes.getSelectedModes().size()) {
            // mode was already set by the AI
            return modes.getMode();
        }
        //TODO: improve this;
        for (Mode mode: modes.values()) {
            if (!modes.getSelectedModes().contains(mode.getId()) // select only modes not already selected
                    && mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and where targets are available
                return mode;
            }
        }
        return null;
    }

    @Override
    public TriggeredAbility chooseTriggeredAbility(List<TriggeredAbility> abilities, Game game) {
        log.debug("chooseTriggeredAbility: " + abilities.toString());
        //TODO: improve this
        if (abilities.size() > 0) {
            return abilities.get(0);
        }
        return null;
    }

    @Override
    public void assignDamage(int damage, List<UUID> targets, String singleTargetName, UUID sourceId, Game game) {
        log.debug("assignDamage");
        //TODO: improve this
        game.getPermanent(targets.get(0)).damage(damage, sourceId, game, false, true);
    }

    @Override
    public int getAmount(int min, int max, String message, Game game) {
        log.debug("getAmount");
        if (message.startsWith("Assign damage to ")) {
            return min;
        }
        //TODO: improve this
        if (min < max && min == 0) {
            return new Random().nextInt(max+1);
        }
        return min;
    }

    @Override
    public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
        //TODO: improve this
        return attackers.iterator().next().getId();
    }

    @Override
    public UUID chooseBlockerOrder(List<Permanent> blockers, CombatGroup combatGroup, List<UUID> blockerOrder, Game game) {       
        //TODO: improve this
        return blockers.iterator().next().getId();
    }

    @Override
    protected List<Permanent> getAvailableManaProducers(Game game) {
        return super.getAvailableManaProducers(game);
    }

    @Override
    public void sideboard(Match match, Deck deck) {
        //TODO: improve this
        match.submitDeck(playerId, deck);
    }

    private static void addBasicLands(Deck deck, String landName, int number) {
        Random random = new Random();
        Set<String> landSets =  new HashSet<>();

        // decide from which sets basic lands are taken from
        for (String setCode :deck.getExpansionSetCodes()) {
            ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
            if (expansionInfo.hasBasicLands()) {
                landSets.add(expansionInfo.getCode());
            }
        }

        // if sets have no basic land, take land from block
        if (landSets.isEmpty()) {
            for (String setCode :deck.getExpansionSetCodes()) {
                ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
                ExpansionInfo [] blockSets = ExpansionRepository.instance.getSetsFromBlock(expansionInfo.getBlockName());
                for (ExpansionInfo blockSet: blockSets) {
                    if (blockSet.hasBasicLands()) {
                        landSets.add(blockSet.getCode());
                    }
                }
            }
        }
        // if still no set with lands found, take one by random
        if (landSets.isEmpty()) {
            // if sets have no basic lands and also it has no parent or parent has no lands get last set with lands
            // select a set with basic lands by random
            Random generator = new Random();
            List<ExpansionInfo> basicLandSets = ExpansionRepository.instance.getSetsWithBasicLandsByReleaseDate();
            if (basicLandSets.size() > 0) {
                landSets.add(basicLandSets.get(generator.nextInt(basicLandSets.size())).getCode());
            }
        }

        if (landSets.isEmpty()) {
            throw new IllegalArgumentException("No set with basic land was found");
        }

        CardCriteria criteria = new CardCriteria();
        if (!landSets.isEmpty()) {
            criteria.setCodes(landSets.toArray(new String[landSets.size()]));
        }
        criteria.rarities(Rarity.LAND).name(landName);
        List<CardInfo> cards = CardRepository.instance.findCards(criteria);

        if (cards.isEmpty()) {
            return;
        }

        for (int i = 0; i < number; i++) {
            Card land = cards.get(random.nextInt(cards.size())).getCard();
            deck.getCards().add(land);
        }
    }

    public static Deck buildDeck(List<Card> cardPool, final List<ColoredManaSymbol> colors) {
        Deck deck = new Deck();
        List<Card> sortedCards = new ArrayList<>(cardPool);
        Collections.sort(sortedCards, new Comparator<Card>() {
            @Override
            public int compare(Card o1, Card o2) {
                Integer score1 = RateCard.rateCard(o1, colors);
                Integer score2 = RateCard.rateCard(o2, colors);
                return score2.compareTo(score1);
            }
        });
        int cardNum = 0;
        while (deck.getCards().size() < 23 && sortedCards.size() > cardNum) {
            Card card = sortedCards.get(cardNum);
            if (!card.getSupertype().contains("Basic")) {
                deck.getCards().add(card);
                deck.getSideboard().remove(card);
            }
            cardNum++;
        }
        // add basic lands
        // TODO:  compensate for non basic lands
        Mana mana = new Mana();
        for (Card card: deck.getCards()) {
            mana.add(card.getManaCost().getMana());
        }
        double total = mana.getBlack() + mana.getBlue() + mana.getGreen() + mana.getRed() + mana.getWhite();
        int mostLand = 0;
        String mostLandName = "Forest";
        if (mana.getGreen() > 0) {
            int number = (int) Math.round(mana.getGreen() / total * 17);
            addBasicLands(deck, "Forest", number);
            mostLand = number;
        }
        if (mana.getBlack() > 0) {
            int number = (int) Math.round(mana.getBlack() / total * 17);
            addBasicLands(deck, "Swamp", number);
            if (number > mostLand) {
                mostLand = number;
                mostLandName = "Swamp";
            }
        }
        if (mana.getBlue() > 0) {
            int number = (int) Math.round(mana.getBlue() / total * 17);
            addBasicLands(deck, "Island", number);
            if (number > mostLand) {
                mostLand = number;
                mostLandName = "Island";
            }
        }
        if (mana.getWhite() > 0) {
            int number = (int) Math.round(mana.getWhite() / total * 17);
            addBasicLands(deck, "Plains", number);
            if (number > mostLand) {
                mostLand = number;
                mostLandName = "Plains";
            }
        }
        if (mana.getRed() > 0) {
            int number = (int) Math.round(mana.getRed() / total * 17);
            addBasicLands(deck, "Mountain", number);
            if (number > mostLand) {
                mostLandName = "Plains";
            }
        }

        addBasicLands(deck, mostLandName, 40 - deck.getCards().size());

        return deck;
    }

    @Override
    public void construct(Tournament tournament, Deck deck) {
        if (deck != null && deck.getCards().size() < 40 && deck.getSideboard().size() > 0 ) {
            //pick the top 23 cards
            if (chosenColors == null) {
                for (Card card: deck.getSideboard()) {
                    rememberPick(card, RateCard.rateCard(card, null));
                }
                chosenColors = chooseDeckColorsIfPossible();
            }
            deck = buildDeck(new ArrayList<>(deck.getSideboard()), chosenColors);
        }
        tournament.submitDeck(playerId, deck);
    }

    public Card pickBestCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) {
        if (cards.isEmpty()) {
            return null;
        }
        Card bestCard = null;
        int maxScore = 0;
        for (Card card : cards) {
            int score = RateCard.rateCard(card, chosenColors);
            if (bestCard == null || score > maxScore) {
                maxScore = score;
                bestCard = card;
            }
        }
        return bestCard;
    }

    public Card pickBestCard(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability source, Game game) {
        if (cards.isEmpty()) {
            return null;
        }
        Card bestCard = null;
        int maxScore = 0;
        for (Card card : cards) {
            int score = RateCard.rateCard(card, chosenColors);
            boolean betterCard = false;
            if (bestCard == null) { // we need any card to prevent NPE in callers
                betterCard = true;
            } else if (score > maxScore) { // we need better card
                if (target != null && source != null && game != null) {
                    // but also check it can be targeted
                    betterCard = target.canTarget(card.getId(), source, game);
                } else {
                    // target object wasn't provided, so acceptings it anyway
                    betterCard = true;
                }
            }
            // is it better than previous one?
            if (betterCard) {
                maxScore = score;
                bestCard = card;
            }
        }
        return bestCard;
    }

    public Card pickWorstCard(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability source, Game game) {
        if (cards.isEmpty()) {
            return null;
        }
        Card worstCard = null;
        int minScore = Integer.MAX_VALUE;
        for (Card card : cards) {
            int score = RateCard.rateCard(card, chosenColors);
            boolean worseCard = false;
            if (worstCard == null) { // we need any card to prevent NPE in callers
                worseCard = true;
            } else if (score < minScore) { // we need worse card
                if (target != null && source != null && game != null) {
                    // but also check it can be targeted
                    worseCard = target.canTarget(card.getId(), source, game);
                } else {
                    // target object wasn't provided, so accepting it anyway
                    worseCard = true;
                }
            }
            // is it worse than previous one?
            if (worseCard) {
                minScore = score;
                worstCard = card;
            }
        }
        return worstCard;
    }

    public Card pickWorstCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) {
        if (cards.isEmpty()) {
            return null;
        }
        Card worstCard = null;
        int minScore = Integer.MAX_VALUE;
        for (Card card : cards) {
            int score = RateCard.rateCard(card, chosenColors);
            if (worstCard == null || score < minScore) {
                minScore = score;
                worstCard = card;
            }
        }
        return worstCard;
    }

    @Override
    public void pickCard(List<Card> cards, Deck deck, Draft draft) {
        if (cards.isEmpty()) {
            throw new IllegalArgumentException("No cards to pick from.");
        }
        try {
            Card bestCard = pickBestCard(cards, chosenColors);
            int maxScore = RateCard.rateCard(bestCard, chosenColors);
            int pickedCardRate = RateCard.getCardRating(bestCard);

            if (pickedCardRate <= 3) {
                // if card is bad
                // try to counter pick without any color restriction
                Card counterPick = pickBestCard(cards, null);
                int counterPickScore = RateCard.getCardRating(counterPick);
                // card is really good
                // take it!
                if (counterPickScore >= 8) {
                    bestCard = counterPick;
                    maxScore =  RateCard.rateCard(bestCard, chosenColors);
                }
            }


            String colors = "not chosen yet";
            // remember card if colors are not chosen yet
            if (chosenColors == null) {
                rememberPick(bestCard, maxScore);
                chosenColors = chooseDeckColorsIfPossible();
            }
            if (chosenColors != null) {
                colors = "";
                for (ColoredManaSymbol symbol : chosenColors) {
                    colors += symbol.toString();
                }
            }
            log.debug("[DEBUG] AI picked: " + bestCard.getName() + ", score=" + maxScore + ", deck colors=" + colors);
            draft.addPick(playerId, bestCard.getId());
        } catch (Exception e) {
            e.printStackTrace();
            draft.addPick(playerId, cards.get(0).getId());
        }
    }

    /**
     * Remember picked card with its score.
     *
     * @param card
     * @param score
     */
    protected void rememberPick(Card card, int score) {
        pickedCards.add(new PickedCard(card, score));
    }

    /**
     * Choose 2 deck colors for draft:
     * 1. there should be at least 3 cards in card pool
     * 2. at least 2 cards should have different colors
     * 3. get card colors as chosen starting from most rated card
     * @return
     */
    protected List<ColoredManaSymbol> chooseDeckColorsIfPossible() {
        if (pickedCards.size() > 2) {
            // sort by score and color mana symbol count in descending order
            Collections.sort(pickedCards, new Comparator<PickedCard>() {
                @Override
                public int compare(PickedCard o1, PickedCard o2) {
                    if (o1.score.equals(o2.score)) {
                        Integer i1 = RateCard.getColorManaCount(o1.card);
                        Integer i2 = RateCard.getColorManaCount(o2.card);
                        return i2.compareTo(i1);
                    }
                    return o2.score.compareTo(o1.score);
                }
            });
            Set<String> chosenSymbols = new HashSet<>();
            for (PickedCard picked : pickedCards) {
                int differentColorsInCost = RateCard.getDifferentColorManaCount(picked.card);
                // choose only color card, but only if they are not too gold
                if (differentColorsInCost > 0 && differentColorsInCost < 3) {
                    // if some colors were already chosen, total amount shouldn't be more than 3
                    if (chosenSymbols.size() + differentColorsInCost < 4) {
                        for (String symbol : picked.card.getManaCost().getSymbols()) {
                            symbol = symbol.replace("{", "").replace("}", "");
                            if (RateCard.isColoredMana(symbol)) {
                                chosenSymbols.add(symbol);
                            }
                        }
                    }
                }
                // only two or three color decks are allowed
                if (chosenSymbols.size() 1 && chosenSymbols.size() < 4) {
                    List<ColoredManaSymbol> colorsChosen = new ArrayList<>();
                    for (String symbol : chosenSymbols) {
                        ColoredManaSymbol manaSymbol = ColoredManaSymbol.lookup(symbol.charAt(0));
                        if (manaSymbol != null) {
                            colorsChosen.add(manaSymbol);
                        }
                    }
                    if (colorsChosen.size() > 1) {
                        // no need to remember picks anymore
                        pickedCards = null;
                        return colorsChosen;
                    }
                }
            }
        }
        return null;
    }

    private class PickedCard {
        public Card card;
        public Integer score;
        public PickedCard(Card card, int score) {
            this.card = card; this.score = score;
        }
    }

    protected Attackers getPotentialAttackers(Game game) {
        log.debug("getAvailableAttackers");
        Attackers attackers = new Attackers();
        List<Permanent> creatures = super.getAvailableAttackers(game);
        for (Permanent creature: creatures) {
            int potential = combatPotential(creature, game);
            if (potential > 0 && creature.getPower().getValue() > 0) {
                List<Permanent> l = attackers.get(potential);
                if (l == null) {
                    attackers.put(potential, l = new ArrayList<>());
                }
                l.add(creature);
            }
        }   
        return attackers;
    }

    protected int combatPotential(Permanent creature, Game game) {
        log.debug("combatPotential");
        if (!creature.canAttack(game)) {
            return 0;
        }
        int potential = creature.getPower().getValue();
        potential += creature.getAbilities().getEvasionAbilities().size();
        potential += creature.getAbilities().getProtectionAbilities().size();
        potential += creature.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId())?1:0;
        potential += creature.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId())?2:0;
        potential += creature.getAbilities().containsKey(TrampleAbility.getInstance().getId())?1:0;
        return potential;
    }

    protected List<Permanent> getOpponentBlockers(UUID opponentId, Game game) {
        FilterCreatureForCombatBlock blockFilter = new FilterCreatureForCombatBlock();
        List<Permanent> blockers = game.getBattlefield().getAllActivePermanents(blockFilter, opponentId, game);
        return blockers;
    }

    protected CombatSimulator simulateAttack(Attackers attackers, List<Permanent> blockers, UUID opponentId, Game game) {
        log.debug("simulateAttack");
        List<Permanent> attackersList = attackers.getAttackers();
        CombatSimulator best = new CombatSimulator();
        int bestResult = 0;
        //use binary digits to calculate powerset of attackers
        int powerElements = (int) Math.pow(2, attackersList.size());
        for (int i = 1; i < powerElements; i++) {
            String binary = Integer.toBinaryString(i);
            while(binary.length() < attackersList.size()) {
                binary = "0" + binary;
            }
            List<Permanent> trialAttackers = new ArrayList<>();
            for (int j = 0; j < attackersList.size(); j++) {
                if (binary.charAt(j) == '1') {
                    trialAttackers.add(attackersList.get(j));
                }
            }
            CombatSimulator combat = new CombatSimulator();
            for (Permanent permanent: trialAttackers) {
                combat.groups.add(new CombatGroupSimulator(opponentId, Arrays.asList(permanent.getId()), new ArrayList<UUID>(), game));
            }
            CombatSimulator test = simulateBlock(combat, blockers, game);
            if (test.evaluate() > bestResult) {
                best = test;
                bestResult = test.evaluate();
            }
        }

        return best;
    }

    protected CombatSimulator simulateBlock(CombatSimulator combat, List<Permanent> blockers, Game game) {
        log.debug("simulateBlock");

        TreeNode<CombatSimulator> simulations;

        simulations = new TreeNode<>(combat);
        addBlockSimulations(blockers, simulations, game);
        combat.simulate();

        return getWorstSimulation(simulations);

    }

    protected void addBlockSimulations(List<Permanent> blockers, TreeNode<CombatSimulator> node, Game game) {
        int numGroups = node.getData().groups.size();
        Copier<CombatSimulator> copier = new Copier<>();
        for (Permanent blocker: blockers) {
            List<Permanent> subList = remove(blockers, blocker);
            for (int i = 0; i < numGroups; i++) {
                if (node.getData().groups.get(i).canBlock(blocker, game)) {
                    CombatSimulator combat = copier.copy(node.getData());
                    combat.groups.get(i).blockers.add(new CreatureSimulator(blocker));
                    TreeNode<CombatSimulator> child = new TreeNode<>(combat);
                    node.addChild(child);
                    addBlockSimulations(subList, child, game);
                    combat.simulate();
                }
            }
        }
    }

    protected List<Permanent> remove(List<Permanent> source, Permanent element) {
        List<Permanent> newList = new ArrayList<>();
        for (Permanent permanent: source) {
            if (!permanent.equals(element)) {
                newList.add(permanent);
            }
        }
        return newList;
    }

    protected CombatSimulator getBestSimulation(TreeNode<CombatSimulator> simulations) {
        CombatSimulator best = simulations.getData();
        int bestResult = best.evaluate();
        for (TreeNode<CombatSimulator> node: simulations.getChildren()) {
            CombatSimulator bestSub = getBestSimulation(node);
            if (bestSub.evaluate() > bestResult) {
                best = node.getData();
                bestResult = best.evaluate();
            }
        }
        return best;
    }

    protected CombatSimulator getWorstSimulation(TreeNode<CombatSimulator> simulations) {
        CombatSimulator worst = simulations.getData();
        int worstResult = worst.evaluate();
        for (TreeNode<CombatSimulator> node: simulations.getChildren()) {
            CombatSimulator worstSub = getWorstSimulation(node);
            if (worstSub.evaluate() < worstResult) {
                worst = node.getData();
                worstResult = worst.evaluate();
            }
        }
        return worst;
    }

    protected List<Permanent> threats(UUID playerId, UUID sourceId, FilterPermanent filter, Game game, List<UUID> targets) {
        List<Permanent> threats = (playerId == null || sourceId ==null) ?
                game.getBattlefield().getActivePermanents(filter, this.getId(), sourceId, game) : // all permanents within the range of the player
                game.getBattlefield().getActivePermanents(filter, playerId, sourceId, game);

        Iterator<Permanent> it = threats.iterator();
        while (it.hasNext()) { // remove permanents already targeted
            Permanent test = it.next();
            if (targets.contains(test.getId()) || (playerId != null && !test.getControllerId().equals(playerId))) {
                it.remove();
            }
        }
        Collections.sort(threats, new PermanentComparator(game));
        Collections.reverse(threats);
        return threats;
    }

    protected void logState(Game game) {
        if (log.isTraceEnabled()) {
            logList(new StringBuilder("Computer player ").append(name).append(" hand: ").toString(), new ArrayList(hand.getCards(game)));
        }
    }

    protected void logList(String message, List<MageObject> list) {
        StringBuilder sb = new StringBuilder();
        sb.append(message).append(": ");
        for (MageObject object: list) {
            sb.append(object.getName()).append(",");
        }
        log.info(sb.toString());
    }

    protected void logAbilityList(String message, List<Ability> list) {
        StringBuilder sb = new StringBuilder();
        sb.append(message).append(": ");
        for (Ability ability: list) {
            sb.append(ability.getRule()).append(",");
        }
        log.debug(sb.toString());
    }

    private void playRemoval(List<UUID> creatures, Game game) {
        for (UUID creatureId: creatures) {
            for (Card card: this.playableInstant) {
                if (card.getSpellAbility().canActivate(playerId, game)) {
                    for (Effect effect: card.getSpellAbility().getEffects()) {
                        if (effect.getOutcome().equals(Outcome.DestroyPermanent) || effect.getOutcome().equals(Outcome.ReturnToHand)) {
                            if (card.getSpellAbility().getTargets().get(0).canTarget(creatureId, card.getSpellAbility(), game)) {
                                if (this.activateAbility(card.getSpellAbility(), game)) {
                                    return;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void playDamage(List<UUID> creatures, Game game) {
        for (UUID creatureId: creatures) {
            Permanent creature = game.getPermanent(creatureId);
            for (Card card: this.playableInstant) {
                if (card.getSpellAbility().canActivate(playerId, game)) {
                    for (Effect effect: card.getSpellAbility().getEffects()) {
                        if (effect instanceof DamageTargetEffect) {
                            if (card.getSpellAbility().getTargets().get(0).canTarget(creatureId, card.getSpellAbility(), game)) {
                                if (((DamageTargetEffect)effect).getAmount() > (creature.getPower().getValue() - creature.getDamage())) {
                                    if (this.activateAbility(card.getSpellAbility(), game)) {
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        unplayable = new TreeMap<>();
        playableNonInstant = new ArrayList<>();
        playableInstant = new ArrayList<>();
        playableAbilities = new ArrayList<>();
    }

    @Override
    public void cleanUpOnMatchEnd() {
        super.cleanUpOnMatchEnd(); //To change body of generated methods, choose Tools | Templates.
    }



    @Override
    public ComputerPlayer copy() {
        return new ComputerPlayer(this);
    }

}
TOP

Related Classes of mage.player.ai.ComputerPlayer$PickedCard

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.