mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
This commit is contained in:
parent
4a458800aa
commit
b839c7bf87
2 changed files with 112 additions and 77 deletions
|
|
@ -50,21 +50,29 @@ public class SimulationTriggersAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
@Test
|
||||
public void test_DeepglowSkate_PerformanceOnTooManyChoices() {
|
||||
// bug: game freeze with 100% CPU usage
|
||||
// https://github.com/magefree/mage/issues/9438
|
||||
int cardsCount = 2; // 2+ cards will generate too much target options for simulations
|
||||
int boostMultiplier = (int) Math.pow(2, cardsCount);
|
||||
int quantity = 1;
|
||||
String[] cardNames = {
|
||||
"Island", "Plains", "Swamp", "Mountain",
|
||||
"Runeclaw Bear", "Absolute Law", "Gilded Lotus", "Alpha Myr"
|
||||
};
|
||||
|
||||
// When Deepglow Skate enters the battlefield, double the number of each kind of counter on any number
|
||||
// of target permanents.
|
||||
addCard(Zone.HAND, playerA, "Deepglow Skate", cardsCount); // {4}{U}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 5 * cardsCount);
|
||||
//
|
||||
addCard(Zone.HAND, playerA, "Deepglow Skate", 1); // {4}{U}
|
||||
// Bloat the battlefield with permanents (possible targets)
|
||||
for (String card : cardNames) {
|
||||
addCard(Zone.BATTLEFIELD, playerA, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerB, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerC, card, quantity);
|
||||
addCard(Zone.BATTLEFIELD, playerD, card, quantity);
|
||||
}
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ajani, Adversary of Tyrants", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ajani, Caller of the Pride", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Ajani Goldmane", 1); // x4 loyalty
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Ajani, Inspiring Leader", 1); // x5 loyalty
|
||||
//
|
||||
|
||||
// Players can't activate planeswalkers' loyalty abilities.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Immortal Sun", 1); // disable planeswalkers usage by AI
|
||||
|
||||
|
|
@ -75,9 +83,9 @@ public class SimulationTriggersAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Deepglow Skate", cardsCount);
|
||||
assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * boostMultiplier);
|
||||
assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * boostMultiplier);
|
||||
assertPermanentCount(playerA, "Deepglow Skate", 1);
|
||||
assertCounterCount(playerA, "Ajani, Adversary of Tyrants", CounterType.LOYALTY, 4 * 2);
|
||||
assertCounterCount(playerA, "Ajani, Caller of the Pride", CounterType.LOYALTY, 4 * 2);
|
||||
assertCounterCount(playerB, "Ajani Goldmane", CounterType.LOYALTY, 4);
|
||||
assertCounterCount(playerB, "Ajani, Inspiring Leader", CounterType.LOYALTY, 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,54 +26,19 @@ public class TargetOptimization {
|
|||
|
||||
// for up to or any amount - limit max game sims to analyse
|
||||
// (it's useless to calc all possible combinations on too much targets)
|
||||
static public int AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE = 7;
|
||||
static public int AI_MAX_POSSIBLE_TARGETS_TO_CHOOSE = 18;
|
||||
|
||||
public static void optimizePossibleTargets(Ability source, Game game, Set<UUID> possibleTargets, int maxPossibleTargetsToSimulate) {
|
||||
// remove duplicated/same creatures
|
||||
// example: distribute 3 damage between 10+ same tokens
|
||||
// example: target x1 from x10 forests - it's useless to recalc each forest
|
||||
|
||||
if (possibleTargets.size() < maxPossibleTargetsToSimulate) {
|
||||
if (possibleTargets.size() <= maxPossibleTargetsToSimulate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// split targets by groups
|
||||
Map<UUID, String> targetGroups = new HashMap<>();
|
||||
possibleTargets.forEach(id -> {
|
||||
String groupKey = "";
|
||||
|
||||
// player
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
groupKey = getTargetGroupKeyAsPlayer(player);
|
||||
}
|
||||
|
||||
// game object
|
||||
MageObject object = game.getObject(id);
|
||||
if (object != null) {
|
||||
groupKey = object.getName();
|
||||
if (object instanceof Permanent) {
|
||||
groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object);
|
||||
} else if (object instanceof Card) {
|
||||
groupKey += getTargetGroupKeyAsCard(game, (Card) object);
|
||||
} else {
|
||||
groupKey += getTargetGroupKeyAsOther(game, object);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown - use all
|
||||
if (groupKey.isEmpty()) {
|
||||
groupKey = id.toString();
|
||||
}
|
||||
|
||||
targetGroups.put(id, groupKey);
|
||||
});
|
||||
|
||||
Map<String, List<UUID>> groups = new HashMap<>();
|
||||
targetGroups.forEach((id, groupKey) -> {
|
||||
groups.computeIfAbsent(groupKey, k -> new ArrayList<>());
|
||||
groups.get(groupKey).add(id);
|
||||
});
|
||||
Map<String, ArrayList<UUID>> targetGroups = createGroups(game, possibleTargets, maxPossibleTargetsToSimulate, false);
|
||||
|
||||
// optimize logic:
|
||||
// - use one target from each target group all the time
|
||||
|
|
@ -81,7 +46,7 @@ public class TargetOptimization {
|
|||
|
||||
// use one target per group
|
||||
Set<UUID> newPossibleTargets = new HashSet<>();
|
||||
groups.forEach((groupKey, groupTargets) -> {
|
||||
targetGroups.forEach((groupKey, groupTargets) -> {
|
||||
UUID targetId = RandomUtil.randomFromCollection(groupTargets);
|
||||
if (targetId != null) {
|
||||
newPossibleTargets.add(targetId);
|
||||
|
|
@ -91,13 +56,13 @@ public class TargetOptimization {
|
|||
|
||||
// use random target until fill condition
|
||||
while (newPossibleTargets.size() < maxPossibleTargetsToSimulate) {
|
||||
String groupKey = RandomUtil.randomFromCollection(groups.keySet());
|
||||
String groupKey = RandomUtil.randomFromCollection(targetGroups.keySet());
|
||||
if (groupKey == null) {
|
||||
break;
|
||||
}
|
||||
List<UUID> groupTargets = groups.getOrDefault(groupKey, null);
|
||||
List<UUID> groupTargets = targetGroups.getOrDefault(groupKey, null);
|
||||
if (groupTargets == null || groupTargets.isEmpty()) {
|
||||
groups.remove(groupKey);
|
||||
targetGroups.remove(groupKey);
|
||||
continue;
|
||||
}
|
||||
UUID targetId = RandomUtil.randomFromCollection(groupTargets);
|
||||
|
|
@ -112,6 +77,49 @@ public class TargetOptimization {
|
|||
possibleTargets.addAll(newPossibleTargets);
|
||||
}
|
||||
|
||||
private static Map<String, ArrayList<UUID>> createGroups(Game game, Set<UUID> possibleTargets, int maxPossibleTargetsToSimulate, boolean isLoose) {
|
||||
Map<String, ArrayList<UUID>> targetGroups = new HashMap<>();
|
||||
|
||||
possibleTargets.forEach(id -> {
|
||||
String groupKey = "";
|
||||
|
||||
// player
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
groupKey = getTargetGroupKeyAsPlayer(player);
|
||||
}
|
||||
|
||||
// game object
|
||||
MageObject object = game.getObject(id);
|
||||
if (object != null) {
|
||||
groupKey = object.getName();
|
||||
if (object instanceof Permanent) {
|
||||
groupKey += getTargetGroupKeyAsPermanent(game, (Permanent) object, isLoose);
|
||||
} else if (object instanceof Card) {
|
||||
groupKey += getTargetGroupKeyAsCard(game, (Card) object, isLoose);
|
||||
} else {
|
||||
groupKey += getTargetGroupKeyAsOther(game, object);
|
||||
}
|
||||
}
|
||||
|
||||
// unknown - use all
|
||||
if (groupKey.isEmpty()) {
|
||||
groupKey = id.toString();
|
||||
}
|
||||
|
||||
targetGroups.computeIfAbsent(groupKey, k -> new ArrayList<>()).add(id);
|
||||
});
|
||||
|
||||
if (targetGroups.size() > maxPossibleTargetsToSimulate && !isLoose) {
|
||||
// If too many possible target groups, regroup with less specific characteristics
|
||||
return createGroups(game, possibleTargets, maxPossibleTargetsToSimulate, true);
|
||||
}
|
||||
|
||||
// Return appropriate target groups or, if still too many possible targets after loose grouping,
|
||||
// allow optimizePossibleTargets (defined above) to choose random targets within limit
|
||||
return targetGroups;
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsPlayer(Player player) {
|
||||
// use all
|
||||
return String.join(";", Arrays.asList(
|
||||
|
|
@ -120,11 +128,12 @@ public class TargetOptimization {
|
|||
));
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsPermanent(Game game, Permanent permanent) {
|
||||
private static String getTargetGroupKeyAsPermanent(Game game, Permanent permanent, boolean isLoose) {
|
||||
// split by name and stats
|
||||
// TODO: rework and combine with PermanentEvaluator (to use battlefield score)
|
||||
|
||||
// try to use short text/hash for lesser data on debug
|
||||
if (!isLoose) {
|
||||
return String.join(";", Arrays.asList(
|
||||
permanent.getName(),
|
||||
String.valueOf(permanent.getControllerId().hashCode()),
|
||||
|
|
@ -140,9 +149,20 @@ public class TargetOptimization {
|
|||
String.valueOf(permanent.getRules(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
else {
|
||||
return String.join(";", Arrays.asList(
|
||||
String.valueOf(permanent.getControllerId().hashCode()),
|
||||
String.valueOf(permanent.getPower().getValue()),
|
||||
String.valueOf(permanent.getToughness().getValue()),
|
||||
String.valueOf(permanent.getDamage()),
|
||||
String.valueOf(permanent.getCardType(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsCard(Game game, Card card) {
|
||||
private static String getTargetGroupKeyAsCard(Game game, Card card, boolean isLoose) {
|
||||
// split by name and stats
|
||||
if (!isLoose) {
|
||||
return String.join(";", Arrays.asList(
|
||||
card.getName(),
|
||||
String.valueOf(card.getOwnerId().hashCode()),
|
||||
|
|
@ -153,6 +173,13 @@ public class TargetOptimization {
|
|||
String.valueOf(card.getRules(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
else {
|
||||
return String.join(";", Arrays.asList(
|
||||
String.valueOf(card.getOwnerId().hashCode()),
|
||||
String.valueOf(card.getCardType(game).toString().hashCode())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTargetGroupKeyAsOther(Game game, MageObject item) {
|
||||
// use all
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue