mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 04:42:07 -08:00
AI: reworked blockers selections:
* fixed game freezes for no-possible block configurations like Menace (#13290); * fixed computer cheating to ignore block requirements like Menace (now AI will choose all required blockers instead 1); * improved computer logic for blockers selection (try to sacrifice a creature instead game loose, simple use cases only); * added freeze protection for bad or unsupported attacker-block configuration; * refactor: deleted outdated AI code;
This commit is contained in:
parent
a99871988d
commit
92b7ed8efc
12 changed files with 495 additions and 202 deletions
|
|
@ -52,7 +52,7 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_Block_1_small_attacker_vs_1_small_blocker() {
|
||||
public void test_Block_1_small_attacker_vs_1_small_blocker_same() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Arbor Elf", 1); // 1/1
|
||||
|
||||
|
|
@ -72,6 +72,54 @@ public class BlockSimulationAITest extends CardTestPlayerBaseWithAIHelps {
|
|||
assertGraveyardCount(playerB, "Arbor Elf", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Block_1_small_attacker_vs_1_small_blocker_better() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
|
||||
//addCard(Zone.BATTLEFIELD, playerB, "Elvish Archers"); // 2/1 first strike
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Dregscape Zombie", 1); // 2/1
|
||||
|
||||
attack(1, playerA, "Arbor Elf");
|
||||
|
||||
// ai must ignore block to keep better creature alive
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkBlockers("no blockers", 1, playerB, "");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20 - 1);
|
||||
assertPermanentCount(playerA, "Arbor Elf", 1);
|
||||
assertPermanentCount(playerB, "Dregscape Zombie", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Block_1_small_attacker_vs_1_small_blocker_better_but_player_die() {
|
||||
addCustomEffect_TargetDamage(playerA, 19);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Arbor Elf", 1); // 1/1
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Dregscape Zombie", 1); // 2/1
|
||||
|
||||
// prepare 1 life
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target damage 19", playerB);
|
||||
|
||||
attack(1, playerA, "Arbor Elf");
|
||||
|
||||
// ai must keep better blocker in normal case, but now it must protect from lose and sacrifice it
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkBlockers("x1 blocker", 1, playerB, "Dregscape Zombie");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 1);
|
||||
assertGraveyardCount(playerA, "Arbor Elf", 1);
|
||||
assertGraveyardCount(playerB, "Dregscape Zombie", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Block_1_big_attacker_vs_1_small_blocker() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_nothing() {
|
||||
public void test_MustBeBlocked_nothing_human() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
|
||||
|
|
@ -587,12 +587,30 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_1_blocker() {
|
||||
public void test_MustBeBlocked_nothing_AI() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("no blocker", 1, playerB, "");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20 - 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_1_blocker_human() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Alpha Myr", 1); // 2/1
|
||||
|
||||
// auto-choose blocker
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("forced x1 blocker", 1, playerB, "Alpha Myr");
|
||||
|
|
@ -606,15 +624,36 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_many_blockers_good() {
|
||||
public void test_MustBeBlocked_1_blocker_AI() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Alpha Myr", 1); // 2/1
|
||||
|
||||
// auto-choose blocker with AI
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("forced x1 blocker", 1, playerB, "Alpha Myr");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, "Fear of Being Hunted", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_many_blockers_good_AI() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 10); // 3/3
|
||||
|
||||
// TODO: human logic can't be tested (until isHuman replaced by ~isComputer), so current use case will
|
||||
// take first available blocker
|
||||
// ai must choose any bear
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("x1 optimal blocker", 1, playerB, "Spectral Bears");
|
||||
|
||||
|
|
@ -627,15 +666,15 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlocked_many_blockers_bad() {
|
||||
public void test_MustBeBlocked_many_blockers_bad_AI() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1
|
||||
|
||||
// TODO: human logic can't be tested (until isHuman replaced by ~isComputer), so current use case will
|
||||
// take first available blocker
|
||||
// ai don't want but must choose any bad memnite
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("x1 optimal blocker", 1, playerB, "Memnite");
|
||||
|
||||
|
|
@ -645,12 +684,11 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
assertLife(playerB, 20);
|
||||
assertPermanentCount(playerA, "Fear of Being Hunted", 1);
|
||||
assertGraveyardCount(playerB, "Memnite", 1); // x1 blocker die
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// TODO: enable and duplicate for AI -- after implement choose blocker logic and isHuman replace by ~isComputer
|
||||
public void test_MustBeBlocked_many_blockers_optimal() {
|
||||
public void test_MustBeBlocked_many_blockers_optimal_AI() {
|
||||
// Fear of Being Hunted must be blocked if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Fear of Being Hunted"); // 4/2
|
||||
//
|
||||
|
|
@ -659,7 +697,9 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Deadbridge Goliath", 1); // 5/5
|
||||
|
||||
// ai must choose optimal creature to kill but survive
|
||||
attack(1, playerA, "Fear of Being Hunted");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Fear of Being Hunted");
|
||||
checkBlockers("x1 optimal blocker", 1, playerB, "Deadbridge Goliath");
|
||||
|
||||
|
|
@ -669,6 +709,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, "Fear of Being Hunted", 1);
|
||||
assertGraveyardCount(playerB, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -697,7 +738,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBlocking_full_blockers() {
|
||||
public void test_MustBlocking_full_blockers_human() {
|
||||
// All creatures able to block target creature this turn do so.
|
||||
addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G}
|
||||
|
|
@ -725,7 +766,37 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBlocking_many_blockers() {
|
||||
public void test_MustBlocking_full_blockers_AI() {
|
||||
// All creatures able to block target creature this turn do so.
|
||||
addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G}
|
||||
//
|
||||
// Menace
|
||||
// Each creature you control with menace can't be blocked except by three or more creatures.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
|
||||
|
||||
// prepare
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder");
|
||||
|
||||
// ai must choose all blockers anyway
|
||||
attack(1, playerA, "Sonorous Howlbonder");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
setChoiceAmount(playerA, 1); // assign damage to 1 of 3 blocking memnites
|
||||
checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder");
|
||||
checkBlockers("x3 blockers", 1, playerB, "Memnite", "Memnite", "Memnite");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, "Sonorous Howlbonder", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBlocking_many_blockers_human() {
|
||||
// possible bug: AI's blockers auto-fix assign too many blockers (e.g. x10 instead x3 by required effect)
|
||||
|
||||
// All creatures able to block target creature this turn do so.
|
||||
|
|
@ -754,6 +825,38 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
assertGraveyardCount(playerA, "Sonorous Howlbonder", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBlocking_many_blockers_AI() {
|
||||
// possible bug: AI's blockers auto-fix assign too many blockers (e.g. x10 instead x3 by required effect)
|
||||
|
||||
// All creatures able to block target creature this turn do so.
|
||||
addCard(Zone.HAND, playerA, "Bloodscent"); // {3}{G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); // {3}{G}
|
||||
//
|
||||
// Menace
|
||||
// Each creature you control with menace can't be blocked except by three or more creatures.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Sonorous Howlbonder"); // 2/2
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 5); // 1/1
|
||||
|
||||
// prepare
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bloodscent", "Sonorous Howlbonder");
|
||||
|
||||
// ai must choose all blockers
|
||||
attack(1, playerA, "Sonorous Howlbonder");
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
setChoiceAmount(playerA, 1); // assign damage to 1 of 3 blocking memnites
|
||||
checkAttackers("x1 attacker", 1, playerA, "Sonorous Howlbonder");
|
||||
checkBlockers("all blockers", 1, playerB, "Memnite", "Memnite", "Memnite", "Memnite", "Memnite");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, "Sonorous Howlbonder", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// TODO: need exception fix - java.lang.UnsupportedOperationException: Sonorous Howlbonder is blocked by 1 creature(s). It has to be blocked by 3 or more.
|
||||
|
|
@ -814,6 +917,7 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
@Test
|
||||
@Ignore // TODO: need improve of block configuration auto-fix (block by x2 instead x1)
|
||||
// looks like it's impossible for auto-fix (it's can remove blocker, but not add)
|
||||
public void test_MustBeBlockedWithMenace_all_blockers() {
|
||||
// At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s
|
||||
// power until end of turn. That creature must be blocked this combat if able.
|
||||
|
|
@ -844,7 +948,8 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
|
||||
@Test
|
||||
@Ignore // TODO: need improve of block configuration auto-fix (block by x2 instead x1)
|
||||
public void test_MustBeBlockedWithMenace_many_blockers() {
|
||||
// looks like it's impossible for auto-fix (it's can remove blocker, but not add)
|
||||
public void test_MustBeBlockedWithMenace_many_low_blockers_human() {
|
||||
// At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s
|
||||
// power until end of turn. That creature must be blocked this combat if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3
|
||||
|
|
@ -872,6 +977,42 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
assertGraveyardCount(playerA, "Alley Strangler", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // TODO: blockWithGoodTrade2 must support additional restrictions
|
||||
public void test_MustBeBlockedWithMenace_many_low_blockers_AI() {
|
||||
// At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s
|
||||
// power until end of turn. That creature must be blocked this combat if able.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Neyith of the Dire Hunt"); // 3/3
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
|
||||
//
|
||||
// Menace
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Alley Strangler", 1); // 2/3
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 10); // 1/1
|
||||
|
||||
// If the target creature has menace, two creatures must block it if able.
|
||||
// (2020-06-23)
|
||||
|
||||
// AI must be forced to choose min x2 low blockers (it's possible to fail here after AI logic improve someday)
|
||||
|
||||
addTarget(playerA, "Alley Strangler"); // boost target
|
||||
setChoice(playerA, true); // boost target
|
||||
attack(1, playerA, "Alley Strangler");
|
||||
setChoiceAmount(playerA, 1); // assign damage to 1 of 2 blocking memnites
|
||||
setChoiceAmount(playerA, 1); // assign damage to 2 of 2 blocking memnites
|
||||
aiPlayStep(1, PhaseStep.DECLARE_BLOCKERS, playerB);
|
||||
checkAttackers("x1 attacker", 1, playerA, "Alley Strangler");
|
||||
checkBlockers("x2 blockers", 1, playerB, "Memnite", "Memnite");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20);
|
||||
assertGraveyardCount(playerA, "Alley Strangler", 0);
|
||||
assertGraveyardCount(playerB, "Memnite", 2); // x2 blockers must die
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MustBeBlockedWithMenace_low_blockers_auto() {
|
||||
// At the beginning of combat on your turn, you may pay {2}{R/G}. If you do, double target creature’s
|
||||
|
|
@ -985,7 +1126,6 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBaseWithAIHelps {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore // TODO: need to fix
|
||||
public void test_MustBeBlockedWithMenace_low_big_blockers_AI() {
|
||||
// bug: #13290, AI can try to use bigger creature to block
|
||||
|
||||
|
|
|
|||
|
|
@ -1996,7 +1996,7 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
}
|
||||
}
|
||||
checkMultipleBlockers(game, blockedCreaturesList);
|
||||
checkMultipleBlockers(game, blockedCreaturesList); // search wrong block commands
|
||||
|
||||
// AI FULL play if no actions available
|
||||
if (!mustBlockByAction && (this.AIPlayer || this.AIRealGameSimulation)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue