AI: improved stability and bug fixes (related to #13290):

- bug's reason: wrong usage of canTarget, add/addTarget, getOpponents, etc;
- fixed that it can target dead players in some use cases (close #13507);
- fixed that it wrongly choose targets in bad/good effects in some use cases;
- fixed that it can't find valid targets in some use cases;
- fixed game freezes and errors with some cards;
This commit is contained in:
Oleg Agafonov 2025-04-19 07:04:55 +04:00
parent b915c6590b
commit 3dc606501d
10 changed files with 219 additions and 204 deletions

View file

@ -391,7 +391,7 @@ public final class SystemUtil {
// 3. system commands // 3. system commands
if (runGroup.isSpecialCommand) { if (runGroup.isSpecialCommand) {
Player opponent = game.getPlayer(game.getOpponents(feedbackPlayer.getId()).stream().findFirst().orElse(null)); Player opponent = game.getPlayer(game.getOpponents(feedbackPlayer.getId(), true).stream().findFirst().orElse(null));
String info; String info;
switch (runGroup.name) { switch (runGroup.name) {

View file

@ -976,7 +976,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
Player attackingPlayer = game.getPlayer(activePlayerId); Player attackingPlayer = game.getPlayer(activePlayerId);
// check alpha strike first (all in attack to kill a player) // check alpha strike first (all in attack to kill a player)
for (UUID defenderId : game.getOpponents(playerId)) { for (UUID defenderId : game.getOpponents(playerId, true)) {
Player defender = game.getPlayer(defenderId); Player defender = game.getPlayer(defenderId);
if (!defender.isInGame()) { if (!defender.isInGame()) {
continue; continue;
@ -999,7 +999,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
// TODO: add game simulations here to find best attackers/blockers combination // TODO: add game simulations here to find best attackers/blockers combination
// find safe attackers (can't be killed by blockers) // find safe attackers (can't be killed by blockers)
for (UUID defenderId : game.getOpponents(playerId)) { for (UUID defenderId : game.getOpponents(playerId, true)) {
Player defender = game.getPlayer(defenderId); Player defender = game.getPlayer(defenderId);
if (!defender.isInGame()) { if (!defender.isInGame()) {
continue; continue;

View file

@ -32,7 +32,8 @@ public final class GameStateEvaluator2 {
public static PlayerEvaluateScore evaluate(UUID playerId, Game game, boolean useCombatPermanentScore) { public static PlayerEvaluateScore evaluate(UUID playerId, Game game, boolean useCombatPermanentScore) {
// TODO: add multi opponents support, so AI can take better actions // TODO: add multi opponents support, so AI can take better actions
Player player = game.getPlayer(playerId); Player player = game.getPlayer(playerId);
Player opponent = game.getPlayer(game.getOpponents(playerId).stream().findFirst().orElse(null)); // must find all leaved opponents too
Player opponent = game.getPlayer(game.getOpponents(playerId, false).stream().findFirst().orElse(null));
if (opponent == null) { if (opponent == null) {
return new PlayerEvaluateScore(playerId, WIN_GAME_SCORE); return new PlayerEvaluateScore(playerId, WIN_GAME_SCORE);
} }

View file

@ -200,7 +200,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
Ability ability1 = iterator.next(); Ability ability1 = iterator.next();
if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) { if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) {
Permanent permanent = game.getPermanent(ability1.getFirstTarget()); Permanent permanent = game.getPermanent(ability1.getFirstTarget());
if (permanent != null && !game.getOpponents(playerId).contains(permanent.getControllerId())) { if (permanent != null && !game.getOpponents(playerId, true).contains(permanent.getControllerId())) {
iterator.remove(); iterator.remove();
continue; continue;
} }
@ -216,11 +216,11 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
Ability ability1 = iterator.next(); Ability ability1 = iterator.next();
if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) { if (ability1.getTargets().size() == 1 && ability1.getTargets().get(0).getTargets().size() == 1) {
Permanent permanent = game.getPermanent(ability1.getFirstTarget()); Permanent permanent = game.getPermanent(ability1.getFirstTarget());
if (permanent != null && game.getOpponents(playerId).contains(permanent.getControllerId())) { if (permanent != null && game.getOpponents(playerId, true).contains(permanent.getControllerId())) {
iterator.remove(); iterator.remove();
continue; continue;
} }
if (game.getOpponents(playerId).contains(ability1.getFirstTarget())) { if (game.getOpponents(playerId, true).contains(ability1.getFirstTarget())) {
iterator.remove(); iterator.remove();
} }
} }
@ -233,7 +233,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
public List<Combat> addAttackers(Game game) { public List<Combat> addAttackers(Game game) {
Map<Integer, Combat> engagements = new HashMap<>(); Map<Integer, Combat> engagements = new HashMap<>();
//useful only for two player games - will only attack first opponent //useful only for two player games - will only attack first opponent
UUID defenderId = game.getOpponents(playerId).iterator().next(); UUID defenderId = game.getOpponents(playerId, true).iterator().next();
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
//use binary digits to calculate powerset of attackers //use binary digits to calculate powerset of attackers
int powerElements = (int) Math.pow(2, attackersList.size()); int powerElements = (int) Math.pow(2, attackersList.size());

View file

@ -59,6 +59,8 @@ import java.util.Map.Entry;
/** /**
* AI: basic server side bot with simple actions support (game, draft, construction/sideboarding) * AI: basic server side bot with simple actions support (game, draft, construction/sideboarding)
* <p>
* TODO: combine choose and chooseTarget to single logic to use shared code
* *
* @author BetaSteward_at_googlemail.com, JayDi85 * @author BetaSteward_at_googlemail.com, JayDi85
*/ */
@ -166,17 +168,10 @@ public class ComputerPlayer extends PlayerImpl {
required = false; required = false;
} }
UUID randomOpponentId; UUID randomOpponentId = getRandomOpponent(game);
if (target.getTargetController() != null) {
randomOpponentId = getRandomOpponent(target.getTargetController(), game);
} else if (abilityControllerId != null) {
randomOpponentId = getRandomOpponent(abilityControllerId, game);
} else {
randomOpponentId = getRandomOpponent(playerId, game);
}
if (target.getOriginalTarget() instanceof TargetPlayer) { if (target.getOriginalTarget() instanceof TargetPlayer) {
return setTargetPlayer(outcome, target, null, abilityControllerId, randomOpponentId, game, required); return selectPlayer(outcome, target, abilityControllerId, randomOpponentId, game, required);
} }
if (target.getOriginalTarget() instanceof TargetDiscard) { if (target.getOriginalTarget() instanceof TargetDiscard) {
@ -184,7 +179,7 @@ public class ComputerPlayer extends PlayerImpl {
// discard not playable first // discard not playable first
if (!unplayable.isEmpty()) { if (!unplayable.isEmpty()) {
for (int i = unplayable.size() - 1; i >= 0; i--) { for (int i = unplayable.size() - 1; i >= 0; i--) {
if (target.canTarget(abilityControllerId, unplayable.values().toArray(new Card[0])[i].getId(), null, game)) { if (target.canTarget(abilityControllerId, unplayable.values().toArray(new Card[0])[i].getId(), source, game)) {
target.add(unplayable.values().toArray(new Card[0])[i].getId(), game); target.add(unplayable.values().toArray(new Card[0])[i].getId(), game);
if (target.isChosen(game)) { if (target.isChosen(game)) {
return true; return true;
@ -194,7 +189,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
if (!hand.isEmpty()) { if (!hand.isEmpty()) {
for (int i = 0; i < hand.size(); i++) { for (int i = 0; i < hand.size(); i++) {
if (target.canTarget(abilityControllerId, hand.toArray(new UUID[0])[i], null, game)) { if (target.canTarget(abilityControllerId, hand.toArray(new UUID[0])[i], source, game)) {
target.add(hand.toArray(new UUID[0])[i], game); target.add(hand.toArray(new UUID[0])[i], game);
if (target.isChosen(game)) { if (target.isChosen(game)) {
return true; return true;
@ -254,7 +249,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (target.canTarget(abilityControllerId, permanent.getId(), null, game) && !target.getTargets().contains(permanent.getId())) { if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) {
// stop to add targets if not needed and outcome is no advantage for AI player // stop to add targets if not needed and outcome is no advantage for AI player
if (target.getNumberOfTargets() == target.getTargets().size()) { if (target.getNumberOfTargets() == target.getTargets().size()) {
if (outcome.isGood() && hasOpponent(permanent.getControllerId(), game)) { if (outcome.isGood() && hasOpponent(permanent.getControllerId(), game)) {
@ -285,10 +280,10 @@ public class ComputerPlayer extends PlayerImpl {
} }
while ((outcome.isGood() ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game)) while ((outcome.isGood() ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game))
&& !cards.isEmpty()) { && !cards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cards, outcome, target, null, game); Card card = selectCard(abilityControllerId, cards, outcome, target, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), null, game); target.add(card.getId(), game);
cards.remove(pick); cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -304,7 +299,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
List<UUID> alreadyTargetted = target.getTargets(); List<UUID> alreadyTargetted = target.getTargets();
if (target.canTarget(abilityControllerId, permanent.getId(), null, game)) { if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) { if (alreadyTargetted != null && !alreadyTargetted.contains(permanent.getId())) {
target.add(permanent.getId(), game); target.add(permanent.getId(), game);
return true; return true;
@ -312,11 +307,11 @@ public class ComputerPlayer extends PlayerImpl {
} }
} }
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, null, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
target.add(abilityControllerId, game); target.add(getId(), game);
return true; return true;
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, null, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.add(randomOpponentId, game); target.add(randomOpponentId, game);
return true; return true;
} }
@ -337,7 +332,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
List<UUID> alreadyTargeted = target.getTargets(); List<UUID> alreadyTargeted = target.getTargets();
if (target.canTarget(abilityControllerId, permanent.getId(), null, game)) { if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) {
target.add(permanent.getId(), game); target.add(permanent.getId(), game);
return true; return true;
@ -345,23 +340,23 @@ public class ComputerPlayer extends PlayerImpl {
} }
} }
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, null, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
target.add(abilityControllerId, game); target.add(getId(), game);
return true; return true;
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, null, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.add(randomOpponentId, game); target.add(randomOpponentId, game);
return true; return true;
} }
if (!target.isRequired(sourceId, game) || target.getNumberOfTargets() == 0) { if (!target.isRequired(sourceId, game) || target.getNumberOfTargets() == 0) {
return false; return false;
} }
if (target.canTarget(abilityControllerId, randomOpponentId, null, game)) { if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.add(randomOpponentId, game); target.add(randomOpponentId, game);
return true; return true;
} }
if (target.canTarget(abilityControllerId, abilityControllerId, null, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
target.add(abilityControllerId, game); target.add(getId(), game);
return true; return true;
} }
if (outcome.isGood()) { // no other valid targets so use a permanent if (outcome.isGood()) { // no other valid targets so use a permanent
@ -371,7 +366,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
List<UUID> alreadyTargeted = target.getTargets(); List<UUID> alreadyTargeted = target.getTargets();
if (target.canTarget(abilityControllerId, permanent.getId(), null, game)) { if (target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) { if (alreadyTargeted != null && !alreadyTargeted.contains(permanent.getId())) {
target.add(permanent.getId(), game); target.add(permanent.getId(), game);
return true; return true;
@ -385,7 +380,7 @@ public class ComputerPlayer extends PlayerImpl {
List<Card> cards = new ArrayList<>(); List<Card> cards = new ArrayList<>();
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) { for (Card card : player.getGraveyard().getCards(game)) {
if (target.canTarget(card.getId(), source, game)) { if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
cards.add(card); cards.add(card);
} }
} }
@ -395,10 +390,10 @@ public class ComputerPlayer extends PlayerImpl {
boolean isRealGood = outcome.isGood() || outcome == Outcome.Exile; boolean isRealGood = outcome.isGood() || outcome == Outcome.Exile;
while ((isRealGood ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game)) while ((isRealGood ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game))
&& !cards.isEmpty()) { && !cards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cards, outcome, target, null, game); Card card = selectCard(abilityControllerId, cards, outcome, target, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), null, game); target.add(card.getId(), game);
cards.remove(pick); cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} else { } else {
break; break;
} }
@ -412,7 +407,7 @@ public class ComputerPlayer extends PlayerImpl {
List<Card> cards = new ArrayList<>(); List<Card> cards = new ArrayList<>();
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) { for (Card card : player.getGraveyard().getCards(game)) {
if (target.canTarget(abilityControllerId, card.getId(), null, game)) { if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
cards.add(card); cards.add(card);
} }
} }
@ -422,10 +417,10 @@ public class ComputerPlayer extends PlayerImpl {
boolean isRealGood = outcome.isGood() || outcome == Outcome.Exile; boolean isRealGood = outcome.isGood() || outcome == Outcome.Exile;
while ((isRealGood ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game)) while ((isRealGood ? target.getTargets().size() < target.getMaxNumberOfTargets() : !target.isChosen(game))
&& !cards.isEmpty()) { && !cards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cards, outcome, target, null, game); Card card = selectCard(abilityControllerId, cards, outcome, target, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), null, game); target.add(card.getId(), game);
cards.remove(pick); cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} else { } else {
break; break;
} }
@ -440,7 +435,7 @@ public class ComputerPlayer extends PlayerImpl {
TargetCard originalTarget = (TargetCard) target.getOriginalTarget(); TargetCard originalTarget = (TargetCard) target.getOriginalTarget();
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(originalTarget.getFilter(), game)); List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards(originalTarget.getFilter(), game));
while (!cards.isEmpty()) { while (!cards.isEmpty()) {
Card card = pickTarget(abilityControllerId, cards, outcome, target, null, game); Card card = selectCard(abilityControllerId, cards, outcome, target, game);
if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) {
target.add(card.getId(), game); target.add(card.getId(), game);
if (target.isChosen(game)) { if (target.isChosen(game)) {
@ -465,7 +460,7 @@ public class ComputerPlayer extends PlayerImpl {
List<Card> cards = game.getExile().getCards(filter, game); List<Card> cards = game.getExile().getCards(filter, game);
while (!cards.isEmpty()) { while (!cards.isEmpty()) {
Card card = pickTarget(abilityControllerId, cards, outcome, target, null, game); Card card = selectCard(abilityControllerId, cards, outcome, target, game);
if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) { if (card != null && alreadyTargeted != null && !alreadyTargeted.contains(card.getId())) {
target.add(card.getId(), game); target.add(card.getId(), game);
if (target.isChosen(game)) { if (target.isChosen(game)) {
@ -483,7 +478,7 @@ public class ComputerPlayer extends PlayerImpl {
MageObject targetObject = game.getObject(targetId); MageObject targetObject = game.getObject(targetId);
if (targetObject != null) { if (targetObject != null) {
List<UUID> alreadyTargeted = target.getTargets(); List<UUID> alreadyTargeted = target.getTargets();
if (target.canTarget(abilityControllerId, targetObject.getId(), null, game)) { if (target.canTarget(abilityControllerId, targetObject.getId(), source, game)) {
if (alreadyTargeted != null && !alreadyTargeted.contains(targetObject.getId())) { if (alreadyTargeted != null && !alreadyTargeted.contains(targetObject.getId())) {
target.add(targetObject.getId(), game); target.add(targetObject.getId(), game);
return true; return true;
@ -501,10 +496,10 @@ public class ComputerPlayer extends PlayerImpl {
Cards cards = new CardsImpl(possibleTargets); Cards cards = new CardsImpl(possibleTargets);
List<Card> possibleCards = new ArrayList<>(cards.getCards(game)); List<Card> possibleCards = new ArrayList<>(cards.getCards(game));
while (!target.isChosen(game) && !possibleCards.isEmpty()) { while (!target.isChosen(game) && !possibleCards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, possibleCards, outcome, target, null, game); Card card = selectCard(abilityControllerId, possibleCards, outcome, target, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), null, game); target.add(card.getId(), game);
possibleCards.remove(pick); possibleCards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -515,15 +510,15 @@ public class ComputerPlayer extends PlayerImpl {
List<Card> cardsInCommandZone = new ArrayList<>(); List<Card> cardsInCommandZone = new ArrayList<>();
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
for (Card card : game.getCommanderCardsFromCommandZone(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { for (Card card : game.getCommanderCardsFromCommandZone(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) {
if (target.canTarget(abilityControllerId, card.getId(), null, game)) { if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
cardsInCommandZone.add(card); cardsInCommandZone.add(card);
} }
} }
} }
while (!target.isChosen(game) && !cardsInCommandZone.isEmpty()) { while (!target.isChosen(game) && !cardsInCommandZone.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cardsInCommandZone, outcome, target, null, game); Card pick = selectCard(abilityControllerId, cardsInCommandZone, outcome, target, game);
if (pick != null) { if (pick != null) {
target.addTarget(pick.getId(), null, game); target.add(pick.getId(), game);
cardsInCommandZone.remove(pick); cardsInCommandZone.remove(pick);
} }
} }
@ -561,18 +556,10 @@ public class ComputerPlayer extends PlayerImpl {
List<Permanent> badList = new ArrayList<>(); List<Permanent> badList = new ArrayList<>();
List<Permanent> allList = new ArrayList<>(); List<Permanent> allList = new ArrayList<>();
// TODO: improve to process multiple opponents instead random UUID randomOpponentId = getRandomOpponent(game);
UUID randomOpponentId;
if (target.getTargetController() != null) {
randomOpponentId = getRandomOpponent(target.getTargetController(), game);
} else if (source != null && source.getControllerId() != null) {
randomOpponentId = getRandomOpponent(source.getControllerId(), game);
} else {
randomOpponentId = getRandomOpponent(playerId, game);
}
if (target.getOriginalTarget() instanceof TargetPlayer) { if (target.getOriginalTarget() instanceof TargetPlayer) {
return setTargetPlayer(outcome, target, source, abilityControllerId, randomOpponentId, game, required); return selectPlayerTarget(outcome, target, source, abilityControllerId, randomOpponentId, game, required);
} }
// Angel of Serenity trigger // Angel of Serenity trigger
@ -636,7 +623,7 @@ public class ComputerPlayer extends PlayerImpl {
while (!target.isChosen(game) while (!target.isChosen(game)
&& !cardsInHand.isEmpty() && !cardsInHand.isEmpty()
&& target.getMaxNumberOfTargets() > target.getTargets().size()) { && target.getMaxNumberOfTargets() > target.getTargets().size()) {
Card card = pickBestCard(cardsInHand, Collections.emptyList(), target, source, game); Card card = selectBestCardTarget(cardsInHand, Collections.emptyList(), target, source, game);
if (card != null) { if (card != null) {
if (target.canTarget(abilityControllerId, card.getId(), source, game)) { if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
@ -746,8 +733,8 @@ public class ComputerPlayer extends PlayerImpl {
if (targets.isEmpty()) { if (targets.isEmpty()) {
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
return tryAddTarget(target, randomOpponentId, source, game); return tryAddTarget(target, randomOpponentId, source, game);
@ -767,8 +754,8 @@ public class ComputerPlayer extends PlayerImpl {
} }
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
return tryAddTarget(target, randomOpponentId, source, game); return tryAddTarget(target, randomOpponentId, source, game);
@ -789,8 +776,8 @@ public class ComputerPlayer extends PlayerImpl {
if (targets.isEmpty()) { if (targets.isEmpty()) {
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
return tryAddTarget(target, randomOpponentId, source, game); return tryAddTarget(target, randomOpponentId, source, game);
@ -828,8 +815,8 @@ public class ComputerPlayer extends PlayerImpl {
// possible good/bad players // possible good/bad players
if (targets.isEmpty()) { if (targets.isEmpty()) {
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
return tryAddTarget(target, randomOpponentId, source, game); return tryAddTarget(target, randomOpponentId, source, game);
@ -853,21 +840,21 @@ public class ComputerPlayer extends PlayerImpl {
// try target player as normal // try target player as normal
if (outcome.isGood()) { if (outcome.isGood()) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
return tryAddTarget(target, randomOpponentId, source, game); return tryAddTarget(target, randomOpponentId, source, game);
} }
// try target player as bad (bad on itself, good on opponent) // try target player as bad (bad on itself, good on opponent)
for (UUID opponentId : game.getOpponents(abilityControllerId)) { for (UUID opponentId : game.getOpponents(getId(), true)) {
if (target.canTarget(abilityControllerId, opponentId, source, game)) { if (target.canTarget(abilityControllerId, opponentId, source, game)) {
return tryAddTarget(target, opponentId, source, game); return tryAddTarget(target, opponentId, source, game);
} }
} }
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, abilityControllerId, source, game); return tryAddTarget(target, getId(), source, game);
} }
return false; return false;
@ -878,7 +865,7 @@ public class ComputerPlayer extends PlayerImpl {
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
cards.addAll(player.getGraveyard().getCards(game)); cards.addAll(player.getGraveyard().getCards(game));
} }
Card card = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (card != null) { if (card != null) {
return tryAddTarget(target, card.getId(), source, game); return tryAddTarget(target, card.getId(), source, game);
} }
@ -888,7 +875,7 @@ public class ComputerPlayer extends PlayerImpl {
if (target.getOriginalTarget() instanceof TargetCardInLibrary) { if (target.getOriginalTarget() instanceof TargetCardInLibrary) {
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getLibrary().getCards(game)); List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getLibrary().getCards(game));
Card card = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (card != null) { if (card != null) {
return tryAddTarget(target, card.getId(), source, game); return tryAddTarget(target, card.getId(), source, game);
} }
@ -898,10 +885,10 @@ public class ComputerPlayer extends PlayerImpl {
if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) { if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) {
List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game)); List<Card> cards = new ArrayList<>(game.getPlayer(abilityControllerId).getGraveyard().getCards((FilterCard) target.getFilter(), game));
while (!target.isChosen(game) && !cards.isEmpty()) { while (!target.isChosen(game) && !cards.isEmpty()) {
Card card = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (card != null) { if (card != null) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
cards.remove(card); // pickTarget don't remove cards (only on second+ tries) cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -958,13 +945,13 @@ public class ComputerPlayer extends PlayerImpl {
if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) { if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) {
List<Card> cards = new ArrayList<>(); List<Card> cards = new ArrayList<>();
for (UUID uuid : game.getOpponents(abilityControllerId)) { for (UUID uuid : game.getOpponents(getId(), true)) {
Player player = game.getPlayer(uuid); Player player = game.getPlayer(uuid);
if (player != null) { if (player != null) {
cards.addAll(player.getGraveyard().getCards(game)); cards.addAll(player.getGraveyard().getCards(game));
} }
} }
Card card = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (card != null) { if (card != null) {
return tryAddTarget(target, card.getId(), source, game); return tryAddTarget(target, card.getId(), source, game);
} }
@ -984,10 +971,10 @@ public class ComputerPlayer extends PlayerImpl {
cards.addAll(player.getGraveyard().getCards(game)); cards.addAll(player.getGraveyard().getCards(game));
} }
while (!target.isChosen(game) && !cards.isEmpty()) { while (!target.isChosen(game) && !cards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), source, game); target.addTarget(card.getId(), source, game);
cards.remove(pick); // pickTarget don't remove cards (only on second+ tries) cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -1012,10 +999,10 @@ public class ComputerPlayer extends PlayerImpl {
} }
} }
while (!target.isChosen(game) && !cards.isEmpty()) { while (!target.isChosen(game) && !cards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), source, game); target.addTarget(card.getId(), source, game);
cards.remove(pick); // pickTarget don't remove cards (only on second+ tries) cards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -1053,7 +1040,7 @@ public class ComputerPlayer extends PlayerImpl {
cards.addAll(player.getGraveyard().getCards(game)); cards.addAll(player.getGraveyard().getCards(game));
cards.addAll(game.getBattlefield().getAllActivePermanents(new FilterPermanent(), player.getId(), game)); cards.addAll(game.getBattlefield().getAllActivePermanents(new FilterPermanent(), player.getId(), game));
} }
Card card = pickTarget(abilityControllerId, cards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cards, outcome, target, source, game);
if (card != null) { if (card != null) {
return tryAddTarget(target, card.getId(), source, game); return tryAddTarget(target, card.getId(), source, game);
} }
@ -1063,10 +1050,10 @@ public class ComputerPlayer extends PlayerImpl {
Cards cards = new CardsImpl(possibleTargets); Cards cards = new CardsImpl(possibleTargets);
List<Card> possibleCards = new ArrayList<>(cards.getCards(game)); List<Card> possibleCards = new ArrayList<>(cards.getCards(game));
while (!target.isChosen(game) && !possibleCards.isEmpty()) { while (!target.isChosen(game) && !possibleCards.isEmpty()) {
Card pick = pickTarget(abilityControllerId, possibleCards, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, possibleCards, outcome, target, source, game);
if (pick != null) { if (card != null) {
target.addTarget(pick.getId(), source, game); target.addTarget(card.getId(), source, game);
possibleCards.remove(pick); possibleCards.remove(card); // selectCard don't remove cards (only on second+ tries)
} }
} }
return target.isChosen(game); return target.isChosen(game);
@ -1075,18 +1062,28 @@ public class ComputerPlayer extends PlayerImpl {
throw new IllegalStateException("Target wasn't handled in computer's chooseTarget method: " + target.getClass().getCanonicalName()); throw new IllegalStateException("Target wasn't handled in computer's chooseTarget method: " + target.getClass().getCanonicalName());
} //end of chooseTarget method } //end of chooseTarget method
protected Card pickTarget(UUID abilityControllerId, List<Card> cards, Outcome outcome, Target target, Ability source, Game game) { protected Card selectCard(UUID abilityControllerId, List<Card> cards, Outcome outcome, Target target, Game game) {
return selectCardInner(abilityControllerId, cards, outcome, target, null, game);
}
protected Card selectCardTarget(UUID abilityControllerId, List<Card> cards, Outcome outcome, Target target, Ability targetingSource, Game game) {
return selectCardInner(abilityControllerId, cards, outcome, target, targetingSource, game);
}
/**
* @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
*/
protected Card selectCardInner(UUID abilityControllerId, List<Card> cards, Outcome outcome, Target target, Ability targetingSource, Game game) {
Card card; Card card;
while (!cards.isEmpty()) { while (!cards.isEmpty()) {
if (outcome.isGood()) { if (outcome.isGood()) {
card = pickBestCard(cards, Collections.emptyList(), target, source, game); card = selectBestCardInner(cards, Collections.emptyList(), target, targetingSource, game);
} else { } else {
card = pickWorstCard(cards, Collections.emptyList(), target, source, game); card = selectWorstCardInner(cards, Collections.emptyList(), target, targetingSource, game);
} }
if (!target.getTargets().contains(card.getId())) { if (!target.getTargets().contains(card.getId())) {
if (source != null) { if (targetingSource != null) {
if (target.canTarget(abilityControllerId, card.getId(), source, game)) { if (target.canTarget(abilityControllerId, card.getId(), targetingSource, game)) {
return card; return card;
} }
} else { } else {
@ -1107,15 +1104,15 @@ public class ComputerPlayer extends PlayerImpl {
UUID sourceId = source != null ? source.getSourceId() : null; UUID sourceId = source != null ? source.getSourceId() : null;
// process multiple opponents by random // sometimes a target selection can be made from a player that does not control the ability
List<UUID> opponents; UUID abilityControllerId = playerId;
if (target.getTargetController() != null) { if (target.getTargetController() != null
opponents = new ArrayList<>(game.getOpponents(target.getTargetController())); && target.getAbilityController() != null) {
} else if (source != null && source.getControllerId() != null) { abilityControllerId = target.getAbilityController();
opponents = new ArrayList<>(game.getOpponents(source.getControllerId()));
} else {
opponents = new ArrayList<>(game.getOpponents(getId()));
} }
// process multiple opponents by random
List<UUID> opponents = new ArrayList<>(game.getOpponents(getId(), true));
Collections.shuffle(opponents); Collections.shuffle(opponents);
List<Permanent> targets; List<Permanent> targets;
@ -1126,7 +1123,7 @@ public class ComputerPlayer extends PlayerImpl {
for (UUID opponentId : opponents) { for (UUID opponentId : opponents) {
Player opponent = game.getPlayer(opponentId); Player opponent = game.getPlayer(opponentId);
if (opponent != null if (opponent != null
&& target.canTarget(getId(), opponentId, source, game) && target.canTarget(abilityControllerId, opponentId, source, game)
&& opponent.getLife() <= target.getAmountRemaining()) { && opponent.getLife() <= target.getAmountRemaining()) {
return tryAddTarget(target, opponentId, opponent.getLife(), source, game); return tryAddTarget(target, opponentId, opponent.getLife(), source, game);
} }
@ -1138,7 +1135,7 @@ public class ComputerPlayer extends PlayerImpl {
// planeswalker kill // planeswalker kill
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isPlaneswalker(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
int loy = permanent.getCounters(game).getCount(CounterType.LOYALTY); int loy = permanent.getCounters(game).getCount(CounterType.LOYALTY);
if (loy <= target.getAmountRemaining()) { if (loy <= target.getAmountRemaining()) {
return tryAddTarget(target, permanent.getId(), loy, source, game); return tryAddTarget(target, permanent.getId(), loy, source, game);
@ -1148,7 +1145,7 @@ public class ComputerPlayer extends PlayerImpl {
// creature kill // creature kill
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isCreature(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if (permanent.getToughness().getValue() <= target.getAmountRemaining()) { if (permanent.getToughness().getValue() <= target.getAmountRemaining()) {
return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game); return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game);
} }
@ -1168,22 +1165,22 @@ public class ComputerPlayer extends PlayerImpl {
// planeswalkers // planeswalkers
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isPlaneswalker(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
} }
} }
// players // players
if (outcome.isGood() && target.canTarget(getId(), getId(), source, game)) { if (outcome.isGood() && target.canTarget(abilityControllerId, getId(), source, game)) {
return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game);
} }
if (!outcome.isGood() && target.canTarget(getId(), opponentId, source, game)) { if (!outcome.isGood() && target.canTarget(abilityControllerId, opponentId, source, game)) {
return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game);
} }
// creature // creature
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isCreature(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
} }
} }
@ -1196,7 +1193,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
for (UUID opponentId : opponents) { for (UUID opponentId : opponents) {
if (!outcome.isGood()) { if (!outcome.isGood()) {
// bad on yourself, uses weakest targets // bad on yourself, uses the weakest targets
targets = threats(getId(), source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false); targets = threats(getId(), source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false);
} else { } else {
targets = threats(opponentId, source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false); targets = threats(opponentId, source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false);
@ -1204,7 +1201,7 @@ public class ComputerPlayer extends PlayerImpl {
// creatures - non killable (TODO: add extra skill checks like undestructeable) // creatures - non killable (TODO: add extra skill checks like undestructeable)
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isCreature(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
int safeDamage = Math.min(permanent.getToughness().getValue() - 1, target.getAmountRemaining()); int safeDamage = Math.min(permanent.getToughness().getValue() - 1, target.getAmountRemaining());
if (safeDamage > 0) { if (safeDamage > 0) {
return tryAddTarget(target, permanent.getId(), safeDamage, source, game); return tryAddTarget(target, permanent.getId(), safeDamage, source, game);
@ -1214,23 +1211,25 @@ public class ComputerPlayer extends PlayerImpl {
// creatures - all // creatures - all
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isCreature(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
} }
} }
// planeswalkers // planeswalkers
for (Permanent permanent : targets) { for (Permanent permanent : targets) {
if (permanent.isPlaneswalker(game) && target.canTarget(getId(), permanent.getId(), source, game)) { if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
} }
} }
} }
// players // players
for (UUID opponentId : opponents) { for (UUID opponentId : opponents) {
if (target.canTarget(getId(), getId(), source, game)) { if (target.canTarget(abilityControllerId, getId(), source, game)) {
// on itself
return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game); return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game);
} else if (target.canTarget(getId(), opponentId, source, game)) { } else if (target.canTarget(abilityControllerId, opponentId, source, game)) {
// on opponent
return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game); return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game);
} }
} }
@ -1249,7 +1248,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
private boolean priorityPlay(Game game) { private boolean priorityPlay(Game game) {
UUID opponentId = game.getOpponents(playerId).iterator().next(); UUID opponentId = getRandomOpponent(game);
if (game.isActivePlayer(playerId)) { if (game.isActivePlayer(playerId)) {
if (game.isMainPhase() && game.getStack().isEmpty()) { if (game.isMainPhase() && game.getStack().isEmpty()) {
playLand(game); playLand(game);
@ -1978,7 +1977,7 @@ public class ComputerPlayer extends PlayerImpl {
if (outcome == Outcome.Detriment) { if (outcome == Outcome.Detriment) {
// choose a creature type of opponent on battlefield or graveyard // choose a creature type of opponent on battlefield or graveyard
for (Permanent permanent : game.getBattlefield().getActivePermanents(this.getId(), game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(this.getId(), game)) {
if (game.getOpponents(this.getId()).contains(permanent.getControllerId()) if (game.getOpponents(getId(), true).contains(permanent.getControllerId())
&& permanent.getCardType(game).contains(CardType.CREATURE) && permanent.getCardType(game).contains(CardType.CREATURE)
&& !permanent.getSubtype(game).isEmpty()) { && !permanent.getSubtype(game).isEmpty()) {
if (choice.getChoices().contains(permanent.getSubtype(game).get(0).toString())) { if (choice.getChoices().contains(permanent.getSubtype(game).get(0).toString())) {
@ -1989,7 +1988,7 @@ public class ComputerPlayer extends PlayerImpl {
} }
// or in opponent graveyard // or in opponent graveyard
if (!choice.isChosen()) { if (!choice.isChosen()) {
for (UUID opponentId : game.getOpponents(this.getId())) { for (UUID opponentId : game.getOpponents(getId(), true)) {
Player opponent = game.getPlayer(opponentId); Player opponent = game.getPlayer(opponentId);
for (Card card : opponent.getGraveyard().getCards(game)) { for (Card card : opponent.getGraveyard().getCards(game)) {
if (card != null && card.getCardType(game).contains(CardType.CREATURE) && !card.getSubtype(game).isEmpty()) { if (card != null && card.getCardType(game).contains(CardType.CREATURE) && !card.getSubtype(game).isEmpty()) {
@ -2046,7 +2045,7 @@ public class ComputerPlayer extends PlayerImpl {
// we still use playerId when getting cards even if they don't control the search // we still use playerId when getting cards even if they don't control the search
List<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), playerId, source, game)); List<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), playerId, source, game));
while (!target.doneChoosing(game)) { while (!target.doneChoosing(game)) {
Card card = pickTarget(abilityControllerId, cardChoices, outcome, target, source, game); Card card = selectCardTarget(abilityControllerId, cardChoices, outcome, target, source, game);
if (card != null) { if (card != null) {
target.addTarget(card.getId(), source, game); target.addTarget(card.getId(), source, game);
cardChoices.remove(card); cardChoices.remove(card);
@ -2077,10 +2076,10 @@ public class ComputerPlayer extends PlayerImpl {
List<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), abilityControllerId, source, game)); List<Card> cardChoices = new ArrayList<>(cards.getCards(target.getFilter(), abilityControllerId, source, game));
while (!target.doneChoosing(game)) { while (!target.doneChoosing(game)) {
Card card = pickTarget(abilityControllerId, cardChoices, outcome, target, source, game); Card card = selectCard(abilityControllerId, cardChoices, outcome, target, game);
if (card != null) { if (card != null) {
target.add(card.getId(), game); target.add(card.getId(), game);
cardChoices.remove(card); cardChoices.remove(card); // selectCard don't remove cards (only on second+ tries)
} else { } else {
// We don't have any valid target to choose so stop choosing // We don't have any valid target to choose so stop choosing
return target.getTargets().size() >= target.getNumberOfTargets(); return target.getTargets().size() >= target.getNumberOfTargets();
@ -2385,26 +2384,28 @@ public class ComputerPlayer extends PlayerImpl {
tournament.submitDeck(playerId, deck); tournament.submitDeck(playerId, deck);
} }
public Card pickBestCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) { public Card selectBestCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) {
if (cards.isEmpty()) { return selectBestCardInner(cards, chosenColors, null, null, null);
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) { public Card selectBestCardTarget(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability targetingSource, Game game) {
return selectBestCardInner(cards, chosenColors, target, targetingSource, game);
}
/**
* @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
*/
public Card selectBestCardInner(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability targetingSource, Game game) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return null; return null;
} }
// sometimes a target selection can be made from a player that does not control the ability
UUID abilityControllerId = playerId;
if (target != null && target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
}
Card bestCard = null; Card bestCard = null;
int maxScore = 0; int maxScore = 0;
for (Card card : cards) { for (Card card : cards) {
@ -2413,9 +2414,9 @@ public class ComputerPlayer extends PlayerImpl {
if (bestCard == null) { // we need any card to prevent NPE in callers if (bestCard == null) { // we need any card to prevent NPE in callers
betterCard = true; betterCard = true;
} else if (score > maxScore) { // we need better card } else if (score > maxScore) { // we need better card
if (target != null && source != null && game != null) { if (target != null && targetingSource != null && game != null) {
// but also check it can be targeted // but also check it can be targeted
betterCard = target.canTarget(getId(), card.getId(), source, game); betterCard = target.canTarget(abilityControllerId, card.getId(), targetingSource, game);
} else { } else {
// target object wasn't provided, so accepting it anyway // target object wasn't provided, so accepting it anyway
betterCard = true; betterCard = true;
@ -2430,10 +2431,28 @@ public class ComputerPlayer extends PlayerImpl {
return bestCard; return bestCard;
} }
public Card pickWorstCard(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability source, Game game) { public Card selectWorstCard(List<Card> cards, List<ColoredManaSymbol> chosenColors) {
return selectWorstCardInner(cards, chosenColors, null, null, null);
}
public Card selectWorstCardTarget(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability targetingSource, Game game) {
return selectWorstCardInner(cards, chosenColors, target, targetingSource, game);
}
/**
* @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
*/
public Card selectWorstCardInner(List<Card> cards, List<ColoredManaSymbol> chosenColors, Target target, Ability targetingSource, Game game) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return null; return null;
} }
// sometimes a target selection can be made from a player that does not control the ability
UUID abilityControllerId = playerId;
if (target != null && target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
}
Card worstCard = null; Card worstCard = null;
int minScore = Integer.MAX_VALUE; int minScore = Integer.MAX_VALUE;
for (Card card : cards) { for (Card card : cards) {
@ -2442,9 +2461,9 @@ public class ComputerPlayer extends PlayerImpl {
if (worstCard == null) { // we need any card to prevent NPE in callers if (worstCard == null) { // we need any card to prevent NPE in callers
worseCard = true; worseCard = true;
} else if (score < minScore) { // we need worse card } else if (score < minScore) { // we need worse card
if (target != null && source != null && game != null) { if (target != null && targetingSource != null && game != null) {
// but also check it can be targeted // but also check it can be targeted
worseCard = target.canTarget(getId(), card.getId(), source, game); worseCard = target.canTarget(abilityControllerId, card.getId(), targetingSource, game);
} else { } else {
// target object wasn't provided, so accepting it anyway // target object wasn't provided, so accepting it anyway
worseCard = true; worseCard = true;
@ -2459,36 +2478,20 @@ public class ComputerPlayer extends PlayerImpl {
return worstCard; 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 @Override
public void pickCard(List<Card> cards, Deck deck, Draft draft) { public void pickCard(List<Card> cards, Deck deck, Draft draft) {
if (cards.isEmpty()) { if (cards.isEmpty()) {
throw new IllegalArgumentException("No cards to pick from."); throw new IllegalArgumentException("No cards to pick from.");
} }
try { try {
Card bestCard = pickBestCard(cards, chosenColors); Card bestCard = selectBestCard(cards, chosenColors);
int maxScore = RateCard.rateCard(bestCard, chosenColors); int maxScore = RateCard.rateCard(bestCard, chosenColors);
int pickedCardRate = RateCard.getBaseCardScore(bestCard); int pickedCardRate = RateCard.getBaseCardScore(bestCard);
if (pickedCardRate <= 30) { if (pickedCardRate <= 30) {
// if card is bad // if card is bad
// try to counter pick without any color restriction // try to counter pick without any color restriction
Card counterPick = pickBestCard(cards, Collections.emptyList()); Card counterPick = selectBestCard(cards, Collections.emptyList());
int counterPickScore = RateCard.getBaseCardScore(counterPick); int counterPickScore = RateCard.getBaseCardScore(counterPick);
// card is really good // card is really good
// take it! // take it!
@ -2901,12 +2904,20 @@ public class ComputerPlayer extends PlayerImpl {
return before != after; return before != after;
} }
private boolean selectPlayer(Outcome outcome, Target target, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) {
return selectPlayerInner(outcome, target, null, abilityControllerId, randomOpponentId, game, required);
}
private boolean selectPlayerTarget(Outcome outcome, Target target, Ability targetingSource, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) {
return selectPlayerInner(outcome, target, targetingSource, abilityControllerId, randomOpponentId, game, required);
}
/** /**
* Sets a possible target player. Depends on bad/good outcome * Sets a possible target player. Depends on bad/good outcome
* *
* @param source null on choose and non-null on chooseTarget * @param targetingSource null on non-target choice like choose and source on targeting choice like chooseTarget
*/ */
private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) { private boolean selectPlayerInner(Outcome outcome, Target target, Ability targetingSource, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) {
Outcome affectedOutcome; Outcome affectedOutcome;
if (abilityControllerId == this.playerId) { if (abilityControllerId == this.playerId) {
// selects for itself // selects for itself
@ -2917,36 +2928,36 @@ public class ComputerPlayer extends PlayerImpl {
} }
if (target.getOriginalTarget() instanceof TargetOpponent) { if (target.getOriginalTarget() instanceof TargetOpponent) {
if (source == null) { if (targetingSource == null) {
if (target.canTarget(randomOpponentId, game)) { if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game); target.add(randomOpponentId, game);
return true; return true;
} }
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) {
target.addTarget(randomOpponentId, source, game); target.addTarget(randomOpponentId, targetingSource, game);
return true; return true;
} }
for (UUID possibleOpponentId : game.getOpponents(abilityControllerId)) { for (UUID possibleOpponentId : game.getOpponents(getId(), true)) {
if (source == null) { if (targetingSource == null) {
if (target.canTarget(possibleOpponentId, game)) { if (target.canTarget(possibleOpponentId, game)) {
target.add(possibleOpponentId, game); target.add(possibleOpponentId, game);
return true; return true;
} }
} else if (target.canTarget(abilityControllerId, possibleOpponentId, source, game)) { } else if (target.canTarget(abilityControllerId, possibleOpponentId, targetingSource, game)) {
target.addTarget(possibleOpponentId, source, game); target.addTarget(possibleOpponentId, targetingSource, game);
return true; return true;
} }
} }
return false; return false;
} }
UUID sourceId = source != null ? source.getSourceId() : null; UUID sourceId = targetingSource != null ? targetingSource.getSourceId() : null;
if (target.getOriginalTarget() instanceof TargetPlayer) { if (target.getOriginalTarget() instanceof TargetPlayer) {
if (affectedOutcome.isGood()) { if (affectedOutcome.isGood()) {
if (source == null) { if (targetingSource == null) {
// good // good
if (target.canTarget(abilityControllerId, game)) { if (target.canTarget(getId(), game)) {
target.add(abilityControllerId, game); target.add(getId(), game);
return true; return true;
} }
if (target.isRequired(sourceId, game)) { if (target.isRequired(sourceId, game)) {
@ -2957,38 +2968,38 @@ public class ComputerPlayer extends PlayerImpl {
} }
} else { } else {
// good // good
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), targetingSource, game)) {
target.addTarget(abilityControllerId, source, game); target.addTarget(getId(), targetingSource, game);
return true; return true;
} }
if (target.isRequired(sourceId, game)) { if (target.isRequired(sourceId, game)) {
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) {
target.addTarget(randomOpponentId, source, game); target.addTarget(randomOpponentId, targetingSource, game);
return true; return true;
} }
} }
} }
} else if (source == null) { } else if (targetingSource == null) {
// bad // bad
if (target.canTarget(randomOpponentId, game)) { if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game); target.add(randomOpponentId, game);
return true; return true;
} }
if (target.isRequired(sourceId, game)) { if (target.isRequired(sourceId, game)) {
if (target.canTarget(abilityControllerId, game)) { if (target.canTarget(getId(), game)) {
target.add(abilityControllerId, game); target.add(getId(), game);
return true; return true;
} }
} }
} else { } else {
// bad // bad
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) { if (target.canTarget(abilityControllerId, randomOpponentId, targetingSource, game)) {
target.addTarget(randomOpponentId, source, game); target.addTarget(randomOpponentId, targetingSource, game);
return true; return true;
} }
if (required) { if (required) {
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) { if (target.canTarget(abilityControllerId, getId(), targetingSource, game)) {
target.addTarget(abilityControllerId, source, game); target.addTarget(getId(), targetingSource, game);
return true; return true;
} }
} }
@ -3001,13 +3012,10 @@ public class ComputerPlayer extends PlayerImpl {
/** /**
* Returns an opponent by random * Returns an opponent by random
*
* @param abilityControllerId
* @param game
* @return
*/ */
private UUID getRandomOpponent(UUID abilityControllerId, Game game) { @Deprecated // TODO: rework all usages and replace to all possible opponents instead single
return RandomUtil.randomFromCollection(game.getOpponents(abilityControllerId)); private UUID getRandomOpponent(Game game) {
return RandomUtil.randomFromCollection(game.getOpponents(getId(), true));
} }
@Override @Override

View file

@ -34,7 +34,7 @@ public class ActionSimulator {
} }
public int evaluateState() { public int evaluateState() {
Player opponent = game.getPlayer(game.getOpponents(player.getId()).stream().findFirst().orElse(null)); Player opponent = game.getPlayer(game.getOpponents(player.getId(), true).stream().findFirst().orElse(null));
if (opponent == null) { if (opponent == null) {
return Integer.MAX_VALUE; return Integer.MAX_VALUE;
} }

View file

@ -17,7 +17,7 @@ public class SelectAttackersNextAction implements MCTSNodeNextAction{
attacks = player.getAttacks(game); attacks = player.getAttacks(game);
else else
attacks = getAttacks(player, fullStateValue, game); attacks = getAttacks(player, fullStateValue, game);
UUID defenderId = game.getOpponents(player.getId()).iterator().next(); UUID defenderId = game.getOpponents(player.getId(), true).iterator().next();
for (List<UUID> attack: attacks) { for (List<UUID> attack: attacks) {
Game sim = game.createSimulationForAI(); Game sim = game.createSimulationForAI();
MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId()); MCTSPlayer simPlayer = (MCTSPlayer) sim.getPlayer(player.getId());

View file

@ -141,7 +141,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override @Override
public void selectAttackers(Game game, UUID attackingPlayerId) { public void selectAttackers(Game game, UUID attackingPlayerId) {
//useful only for two player games - will only attack first opponent //useful only for two player games - will only attack first opponent
UUID defenderId = game.getOpponents(playerId).iterator().next(); UUID defenderId = game.getOpponents(playerId, true).iterator().next();
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
//use binary digits to calculate powerset of attackers //use binary digits to calculate powerset of attackers
int powerElements = (int) Math.pow(2, attackersList.size()); int powerElements = (int) Math.pow(2, attackersList.size());

View file

@ -36,7 +36,7 @@ public class AttackIfAbleTargetRandomOpponentSourceEffect extends OneShotEffect
if (controller == null) { if (controller == null) {
return false; return false;
} }
List<UUID> opponents = new ArrayList<>(game.getOpponents(controller.getId())); List<UUID> opponents = new ArrayList<>(game.getOpponents(controller.getId(), true));
Player opponent = game.getPlayer(opponents.get(RandomUtil.nextInt(opponents.size()))); Player opponent = game.getPlayer(opponents.get(RandomUtil.nextInt(opponents.size())));
if (opponent != null) { if (opponent != null) {
game.informPlayers(opponent.getLogName() + " was chosen at random."); game.informPlayers(opponent.getLogName() + " was chosen at random.");

View file

@ -55,6 +55,9 @@ public interface Target extends Serializable {
boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game); boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game);
/**
* Add target from targeting methods like chooseTarget (will check and generate target events and effects)
*/
void addTarget(UUID id, Ability source, Game game); void addTarget(UUID id, Ability source, Game game);
void addTarget(UUID id, int amount, Ability source, Game game); void addTarget(UUID id, int amount, Ability source, Game game);
@ -90,6 +93,9 @@ public interface Target extends Serializable {
boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game); boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game);
/**
* Add target from non targeting methods like choose
*/
void add(UUID id, Game game); void add(UUID id, Game game);
void remove(UUID targetId); void remove(UUID targetId);