mirror of
https://github.com/correl/mage.git
synced 2025-01-11 19:13:02 +00:00
* 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:
parent
05695ad2fc
commit
64e948e4b3
6 changed files with 143 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue