* AI: improved game performance by x10 for cards with target amount;

AI: added targeting name and amount info to simulation logs;
AI: removed duplicated target variations from target amount simulations;
This commit is contained in:
Oleg Agafonov 2020-12-24 15:02:28 +04:00
parent 05695ad2fc
commit 64e948e4b3
6 changed files with 143 additions and 35 deletions

View file

@ -1,5 +1,6 @@
package mage.player.ai;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
@ -29,6 +30,7 @@ import mage.player.ai.util.CombatInfo;
import mage.player.ai.util.CombatUtil;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetAmount;
import mage.target.TargetCard;
import mage.target.Targets;
import mage.util.RandomUtil;
@ -37,6 +39,7 @@ import org.apache.log4j.Logger;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* @author nantuko
@ -155,7 +158,13 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
boolean usedStack = false;
while (actions.peek() != null) {
Ability ability = actions.poll();
logger.info(new StringBuilder("===> Act [").append(game.getPlayer(playerId).getName()).append("] Action: ").append(ability.toString()).toString());
// log example: ===> Act [PlayerA] Action: Cast Blessings of Nature (target 1; target 2)
logger.info(new StringBuilder("===> Act [")
.append(game.getPlayer(playerId).getName())
.append("] Action: ")
.append(ability.toString())
.append(listTargets(game, ability.getTargets(), " (targeting %s)", ""))
.toString());
if (!ability.getTargets().isEmpty()) {
for (Target target : ability.getTargets()) {
for (UUID id : target.getTargets()) {
@ -506,7 +515,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter)
.append(" <").append(val).append("> (").append(action)
.append(action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "")
.append(listTargets(game, action.getTargets())).append(')')
.append(listTargets(game, action.getTargets(), " (targeting %s)", "")).append(')')
.append(logger.isTraceEnabled() ? " #" + newNode.hashCode() : "");
SimulationNode2 logNode = newNode;
while (logNode.getChildren() != null
@ -541,7 +550,11 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
if (depth == maxDepth) {
GameStateEvaluator2.PlayerEvaluateScore score = GameStateEvaluator2.evaluate(this.getId(), bestNode.game);
String scoreInfo = " [" + score.getPlayerInfoShort() + "-" + score.getOpponentInfoShort() + "]";
logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + bestNode.getAbilities().toString());
String abilitiesInfo = bestNode.getAbilities()
.stream()
.map(a -> a.toString() + listTargets(game, a.getTargets(), " (targeting %s)", ""))
.collect(Collectors.joining("; "));
logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo);
node.children.clear();
node.children.add(bestNode);
node.setScore(bestNode.getScore());
@ -1009,17 +1022,36 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
return suggestedActions.size();
}
protected String listTargets(Game game, Targets targets) {
StringBuilder sb = new StringBuilder();
if (targets != null) {
for (Target target : targets) {
sb.append('[').append(target.getTargetedName(game)).append(']');
}
if (sb.length() > 0) {
sb.insert(0, " targeting ");
/**
* Return info about targets list (targeting objects)
*
* @param game
* @param targets
* @param format example: my %s in data
* @param emptyText default text for empty targets list
* @return
*/
protected String listTargets(Game game, Targets targets, String format, String emptyText) {
List<String> res = new ArrayList<>();
for (Target target : targets) {
for (UUID id : target.getTargets()) {
MageObject object = game.getObject(id);
if (object != null) {
String prefix = "";
if (target instanceof TargetAmount) {
prefix = " " + target.getTargetAmount(id) + "x ";
}
res.add(prefix + object.getIdName());
}
}
}
return sb.toString();
String info = String.join("; ", res);
if (info.isEmpty()) {
return emptyText;
} else {
return String.format(format, info);
}
}
@Override

View file

@ -1130,7 +1130,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
log.warn("No proper AI target handling: " + target.getClass().getName());
// it's ok on no targets available
log.warn("No proper AI target handling or can't find permanents/cards to target: " + target.getClass().getName());
return false;
}

View file

@ -1,7 +1,5 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.counter.DistributeCountersEffect;
import mage.abilities.keyword.MiracleAbility;
@ -11,15 +9,15 @@ import mage.constants.CardType;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanentAmount;
import java.util.UUID;
/**
*
* @author North
*/
public final class BlessingsOfNature extends CardImpl {
public BlessingsOfNature(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{G}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}");
// Distribute four +1/+1 counters among any number of target creatures.
this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures"));

View file

@ -0,0 +1,53 @@
package org.mage.test.AI.basic;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author JayDi85
*/
public class TargetAmountAITest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_AI_ChooseTargets() {
// Distribute four +1/+1 counters among any number of target creatures.
addCard(Zone.HAND, playerA, "Blessings of Nature", 1); // {4}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 4); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 4); // 2/2
// ai must choose by special dialog, not full simulation
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blessings of Nature");
setStopAt(1, PhaseStep.END_TURN);
//setStrictChooseMode(true); // ai must choose
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 4, 2 + 4); // boost one creature (it's just a choose code, so can be different from simulation results)
}
@Test
public void test_AI_SimulateTargets() {
// Distribute four +1/+1 counters among any number of target creatures.
addCard(Zone.HAND, playerA, "Blessings of Nature", 1); // {4}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 4); // 2/2
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 4); // 2/2
// AI must put creatures on own permanents (all in one creature to boost it)
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStopAt(1, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1); // boost each possible creatures
assertPowerToughness(playerB, "Balduvian Bears", 2, 2); // no boost for enemy
}
}

View file

@ -79,8 +79,6 @@ public class CastSplitCardsWithSpliceTest extends CardTestPlayerBase {
addTarget(playerA, "Bow of Nylea"); // target right
// must used all mana
//showAvaileableMana("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();

View file

@ -6,10 +6,7 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.Outcome;
import mage.game.Game;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -52,10 +49,10 @@ public abstract class TargetAmount extends TargetImpl {
@Override
public boolean doneChosing() {
return amountWasSet
&& (remainingAmount == 0
|| (getMinNumberOfTargets() < getMaxNumberOfTargets()
&& getTargets().size() >= getMinNumberOfTargets()));
return amountWasSet
&& (remainingAmount == 0
|| (getMinNumberOfTargets() < getMaxNumberOfTargets()
&& getTargets().size() >= getMinNumberOfTargets()));
}
@Override
@ -100,14 +97,14 @@ public abstract class TargetAmount extends TargetImpl {
}
chosen = isChosen();
while (remainingAmount > 0) {
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
return chosen;
}
chosen = isChosen();
}
return chosen = true;
}
@Override
public List<? extends TargetAmount> getTargetOptions(Ability source, Game game) {
List<TargetAmount> options = new ArrayList<>();
@ -115,21 +112,50 @@ public abstract class TargetAmount extends TargetImpl {
addTargets(this, possibleTargets, options, source, game);
// debug target variations
//printTargetsVariations(possibleTargets, options);
return options;
}
protected void addTargets(TargetAmount target, Set<UUID> targets, List<TargetAmount> options, Ability source, Game game) {
private void printTargetsVariations(Set<UUID> possibleTargets, List<TargetAmount> options) {
// debug target variations
// permanent index + amount
// example: 7 -> 2; 8 -> 3; 9 -> 1
List<UUID> list = new ArrayList<>(possibleTargets);
HashMap<UUID, Integer> targetNumbers = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
targetNumbers.put(list.get(i), i);
}
List<String> res = options
.stream()
.map(t -> t.getTargets()
.stream()
.map(id -> targetNumbers.get(id) + " -> " + t.getTargetAmount(id))
.sorted()
.collect(Collectors.joining("; ")))
.collect(Collectors.toList());
Collections.sort(res);
System.out.println();
System.out.println(res.stream().collect(Collectors.joining("\n")));
System.out.println();
}
protected void addTargets(TargetAmount target, Set<UUID> possibleTargets, List<TargetAmount> options, Ability source, Game game) {
if (!amountWasSet) {
setAmount(source, game);
}
for (UUID targetId : targets) {
Set<UUID> usedTargets = new HashSet<>();
for (UUID targetId : possibleTargets) {
usedTargets.add(targetId);
for (int n = 1; n <= target.remainingAmount; n++) {
TargetAmount t = target.copy();
t.addTarget(targetId, n, source, game, true);
if (t.remainingAmount > 0) {
if (targets.size() > 1) {
Set<UUID> newTargets = targets.stream().filter(newTarget -> !newTarget.equals(targetId)).collect(Collectors.toSet());
addTargets(t, newTargets, options, source, game);
if (possibleTargets.size() > 1) {
// don't use that target again
Set<UUID> newPossibleTargets = possibleTargets.stream().filter(newTarget -> !usedTargets.contains(newTarget)).collect(Collectors.toSet());
addTargets(t, newPossibleTargets, options, source, game);
}
} else {
options.add(t);