mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 21:42:07 -08:00
AI: improved performance and fixed crashes on use cases with too much target options like "deals 5 damage divided as you choose" (related to #11285):
* added DebugUtil.AI_ENABLE_DEBUG_MODE for better IDE's debugging AI code; * it's a target amount optimizations; * it's use a grouping of possible targets due same static and dynamic stats (name, abilities, rules, damage, etc); * instead of going through all possible combinations, AI uses only meaningful targets from particular groups;
This commit is contained in:
parent
b4fa6ace66
commit
f17cbbe72b
9 changed files with 349 additions and 50 deletions
|
|
@ -1,7 +1,10 @@
|
|||
package org.mage.test.AI.basic;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
|
@ -11,6 +14,13 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Possible problems:
|
||||
* - big memory consumption on sims prepare (memory overflow)
|
||||
* - too many sims to calculate (AI fail on time out and do nothing)
|
||||
* <p>
|
||||
* TODO: add tests and implement best choice selection on timeout
|
||||
* (AI must make any good/bad choice on timeout with game log - not a skip)
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class SimulationPerformanceAITest extends CardTestPlayerBaseAI {
|
||||
|
|
@ -21,7 +31,7 @@ public class SimulationPerformanceAITest extends CardTestPlayerBaseAI {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_AIvsAI_Simple() {
|
||||
public void test_Simple_ShortGame() {
|
||||
// both must kill x2 bears by x2 bolts
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 2);
|
||||
|
|
@ -39,7 +49,7 @@ public class SimulationPerformanceAITest extends CardTestPlayerBaseAI {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_AIvsAI_LongGame() {
|
||||
public void test_Simple_LongGame() {
|
||||
// many bears and bolts must help to end game fast
|
||||
int maxTurn = 50;
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
|
|
@ -63,13 +73,27 @@ public class SimulationPerformanceAITest extends CardTestPlayerBaseAI {
|
|||
Assert.assertTrue("One of player must won a game before turn " + maxTurn + ", but it ends on " + currentGame, currentGame.hasEnded());
|
||||
}
|
||||
|
||||
private void runManyTargetOptionsTest(String info, int totalCreatures, int needDiedCreatures, int needPlayerLife) {
|
||||
private void runManyTargetOptionsInTrigger(String info, int totalCreatures, int needDiedCreatures, boolean isDamageRandomCreature, int needPlayerLife) {
|
||||
// When Bogardan Hellkite enters, it deals 5 damage divided as you choose among any number of targets.
|
||||
addCard(Zone.HAND, playerA, "Bogardan Hellkite", 1); // {6}{R}{R}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", totalCreatures);
|
||||
|
||||
if (isDamageRandomCreature) {
|
||||
runCode("damage creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (s, player, game) -> {
|
||||
Permanent creature = game.getBattlefield().getAllPermanents().stream()
|
||||
.filter(p -> p.getName().equals("Balduvian Bears"))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
Assert.assertNotNull(creature);
|
||||
Ability fakeAbility = new SimpleStaticAbility(null);
|
||||
fakeAbility.setControllerId(player.getId());
|
||||
fakeAbility.setSourceId(creature.getId());
|
||||
creature.damage(1, fakeAbility, game);
|
||||
});
|
||||
}
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
|
@ -80,34 +104,107 @@ public class SimulationPerformanceAITest extends CardTestPlayerBaseAI {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_AIvsAI_ManyTargetOptions_Simple() {
|
||||
public void test_ManyTargetOptions_Triggered_Single() {
|
||||
// 2 damage to bear and 3 damage to player B
|
||||
runManyTargetOptionsTest("1 target creature", 1, 1, 20 - 3);
|
||||
runManyTargetOptionsInTrigger("1 target creature", 1, 1, false, 20 - 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AIvsAI_ManyTargetOptions_Few() {
|
||||
public void test_ManyTargetOptions_Triggered_Few() {
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsTest("2 target creatures", 2, 2, 20 - 1);
|
||||
runManyTargetOptionsInTrigger("2 target creatures", 2, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AIvsAI_ManyTargetOptions_Many() {
|
||||
public void test_ManyTargetOptions_Triggered_Many() {
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsTest("5 target creatures", 2, 2, 20 - 1);
|
||||
runManyTargetOptionsInTrigger("5 target creatures", 5, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // AI code must be improved
|
||||
// TODO: need memory optimization
|
||||
// TODO: need sims/options amount optimization (example: target name + score as unique param to reduce possible options)
|
||||
// TODO: need best choice selection on timeout (AI must make any good/bad choice on timeout with game log - not a skip)
|
||||
public void test_AIvsAI_ManyTargetOptions_TooMuch() {
|
||||
// possible problems:
|
||||
// - big memory consumption on sims prepare (memory overflow)
|
||||
// - too many sims to calculate (AI fail on time out and do nothing)
|
||||
public void test_ManyTargetOptions_Triggered_TooMuch() {
|
||||
// warning, can be slow
|
||||
|
||||
// make sure targets optimization works
|
||||
// (must ignore same targets for faster calc)
|
||||
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsTest("50 target creatures", 50, 2, 20 - 1);
|
||||
runManyTargetOptionsInTrigger("50 target creatures", 50, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // TODO: AI do not support game simulations for target options in triggers
|
||||
public void test_ManyTargetOptions_Triggered_TargetGroups() {
|
||||
// make sure targets optimization can find unique creatures, e.g. damaged
|
||||
|
||||
// 4 damage to x2 bears and 1 damage to damaged bear
|
||||
runManyTargetOptionsInTrigger("5 target creatures with one damaged", 5, 3, true, 20);
|
||||
}
|
||||
|
||||
private void runManyTargetOptionsInActivate(String info, int totalCreatures, int needDiedCreatures, boolean isDamageRandomCreature, int needPlayerLife) {
|
||||
// Boulderfall deals 5 damage divided as you choose among any number of targets.
|
||||
addCard(Zone.HAND, playerA, "Boulderfall", 1); // {6}{R}{R}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", totalCreatures);
|
||||
|
||||
if (isDamageRandomCreature) {
|
||||
runCode("damage creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (s, player, game) -> {
|
||||
Permanent creature = game.getBattlefield().getAllPermanents().stream()
|
||||
.filter(p -> p.getName().equals("Balduvian Bears"))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
Assert.assertNotNull(creature);
|
||||
Ability fakeAbility = new SimpleStaticAbility(null);
|
||||
fakeAbility.setControllerId(player.getId());
|
||||
fakeAbility.setSourceId(creature.getId());
|
||||
creature.damage(1, fakeAbility, game);
|
||||
});
|
||||
}
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, "Boulderfall", 1); // if fail then AI stops before all sims ends
|
||||
assertGraveyardCount(playerB, "Balduvian Bears", needDiedCreatures);
|
||||
assertLife(playerB, needPlayerLife);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ManyTargetOptions_Activated_Single() {
|
||||
// 2 damage to bear and 3 damage to player B
|
||||
runManyTargetOptionsInActivate("1 target creature", 1, 1, false, 20 - 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ManyTargetOptions_Activated_Few() {
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsInActivate("2 target creatures", 2, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ManyTargetOptions_Activated_Many() {
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsInActivate("5 target creatures", 5, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ManyTargetOptions_Activated_TooMuch() {
|
||||
// warning, can be slow
|
||||
|
||||
// make sure targets optimization works
|
||||
// (must ignore same targets for faster calc)
|
||||
|
||||
// 4 damage to x2 bears and 1 damage to player B
|
||||
runManyTargetOptionsInActivate("50 target creatures", 50, 2, false, 20 - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ManyTargetOptions_Activated_TargetGroups() {
|
||||
// make sure targets optimization can find unique creatures, e.g. damaged
|
||||
|
||||
// 4 damage to x2 bears and 1 damage to damaged bear
|
||||
runManyTargetOptionsInActivate("5 target creatures with one damaged", 5, 3, true, 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ public class TargetAmountAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
@Test
|
||||
public void test_AI_SimulateTargets() {
|
||||
// warning, test depends on targets list optimization by AI
|
||||
|
||||
// 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue