mirror of
https://github.com/correl/mage.git
synced 2024-11-14 19:19:32 +00:00
Fixed AI freeze with non available targets
This commit is contained in:
parent
394d9716ca
commit
bd71c98e3e
6 changed files with 174 additions and 28 deletions
|
@ -1,14 +1,14 @@
|
||||||
|
|
||||||
package mage.player.ai;
|
package mage.player.ai;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.constants.RangeOfInfluence;
|
import mage.constants.RangeOfInfluence;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author ayratn
|
* @author ayratn
|
||||||
*/
|
*/
|
||||||
public class ComputerPlayer7 extends ComputerPlayer6 {
|
public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
|
@ -107,6 +107,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
|
|
||||||
protected void calculateActions(Game game) {
|
protected void calculateActions(Game game) {
|
||||||
if (!getNextAction(game)) {
|
if (!getNextAction(game)) {
|
||||||
|
Date startTime = new Date();
|
||||||
currentScore = GameStateEvaluator2.evaluate(playerId, game);
|
currentScore = GameStateEvaluator2.evaluate(playerId, game);
|
||||||
Game sim = createSimulation(game);
|
Game sim = createSimulation(game);
|
||||||
SimulationNode2.resetCount();
|
SimulationNode2.resetCount();
|
||||||
|
@ -137,6 +138,15 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
} else {
|
} else {
|
||||||
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
|
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
|
||||||
}
|
}
|
||||||
|
Date endTime = new Date();
|
||||||
|
this.setLastThinkTime((endTime.getTime() - startTime.getTime()));
|
||||||
|
|
||||||
|
/*
|
||||||
|
logger.warn("Last think time: " + this.getLastThinkTime()
|
||||||
|
+ "; actions: " + actions.size()
|
||||||
|
+ "; hand: " + this.getHand().size()
|
||||||
|
+ "; permanents: " + game.getBattlefield().getAllPermanents().size());
|
||||||
|
*/
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Next Action exists!");
|
logger.debug("Next Action exists!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ import java.util.Map.Entry;
|
||||||
public class ComputerPlayer extends PlayerImpl implements Player {
|
public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(ComputerPlayer.class);
|
private static final Logger log = Logger.getLogger(ComputerPlayer.class);
|
||||||
|
private long lastThinkTime = 0; // msecs for last AI actions calc
|
||||||
|
|
||||||
protected int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
|
protected int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
|
||||||
protected boolean ALLOW_INTERRUPT = true; // change this for test to false / debugging purposes to false to switch off interrupts while debugging
|
protected boolean ALLOW_INTERRUPT = true; // change this for test to false / debugging purposes to false to switch off interrupts while debugging
|
||||||
|
@ -450,6 +451,18 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
// source can be null (as example: legendary rule permanent selection)
|
// source can be null (as example: legendary rule permanent selection)
|
||||||
UUID sourceId = source != null ? source.getSourceId() : null;
|
UUID sourceId = source != null ? source.getSourceId() : null;
|
||||||
|
|
||||||
|
// sometimes a target selection can be made from a player that does not control the ability
|
||||||
|
UUID abilityControllerId = playerId;
|
||||||
|
if (target.getAbilityController() != null) {
|
||||||
|
abilityControllerId = target.getAbilityController();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean required = target.isRequired(sourceId, game);
|
||||||
|
Set<UUID> possibleTargets = target.possibleTargets(sourceId, abilityControllerId, game);
|
||||||
|
if (possibleTargets.isEmpty() || target.getTargets().size() >= target.getNumberOfTargets()) {
|
||||||
|
required = false;
|
||||||
|
}
|
||||||
|
|
||||||
// temp lists
|
// temp lists
|
||||||
List<Permanent> goodList = new ArrayList<>();
|
List<Permanent> goodList = new ArrayList<>();
|
||||||
List<Permanent> badList = new ArrayList<>();
|
List<Permanent> badList = new ArrayList<>();
|
||||||
|
@ -458,12 +471,6 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
List<Permanent> badList2 = new ArrayList<>();
|
List<Permanent> badList2 = new ArrayList<>();
|
||||||
List<Permanent> allList2 = new ArrayList<>();
|
List<Permanent> allList2 = new ArrayList<>();
|
||||||
|
|
||||||
// sometimes a target selection can be made from a player that does not control the ability
|
|
||||||
UUID abilityControllerId = playerId;
|
|
||||||
if (target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: improve to process multiple opponents instead random
|
// TODO: improve to process multiple opponents instead random
|
||||||
UUID randomOpponentId;
|
UUID randomOpponentId;
|
||||||
if (target.getTargetController() != null) {
|
if (target.getTargetController() != null) {
|
||||||
|
@ -569,7 +576,6 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
// use bad list only on required target and add minimum targets
|
// use bad list only on required target and add minimum targets
|
||||||
boolean required = target.isRequiredExplicitlySet() ? required = target.isRequired() : target.isRequired(source); // got that code from HumanPlayer.chooseTarget
|
|
||||||
if (required) {
|
if (required) {
|
||||||
for (Permanent permanent : badList) {
|
for (Permanent permanent : badList) {
|
||||||
if (target.getTargets().size() >= target.getMinNumberOfTargets()) {
|
if (target.getTargets().size() >= target.getMinNumberOfTargets()) {
|
||||||
|
@ -2787,4 +2793,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
// all human players converted to computer and analyse
|
// all human players converted to computer and analyse
|
||||||
this.human = false;
|
this.human = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getLastThinkTime() {
|
||||||
|
return lastThinkTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastThinkTime(long lastThinkTime) {
|
||||||
|
this.lastThinkTime = lastThinkTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class RedcapMeleeEffect extends OneShotEffect {
|
||||||
private static final Effect effect = new SacrificeControllerEffect(StaticFilters.FILTER_LAND, 1, "");
|
private static final Effect effect = new SacrificeControllerEffect(StaticFilters.FILTER_LAND, 1, "");
|
||||||
|
|
||||||
RedcapMeleeEffect() {
|
RedcapMeleeEffect() {
|
||||||
super(Outcome.Benefit);
|
super(Outcome.Damage);
|
||||||
staticText = "{this} deals 4 damage to target creature or planeswalker. " +
|
staticText = "{this} deals 4 damage to target creature or planeswalker. " +
|
||||||
"If a nonred permanent is dealt damage this way, you sacrifice a land.";
|
"If a nonred permanent is dealt damage this way, you sacrifice a land.";
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.mage.test.AI.basic;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JayDi85
|
||||||
|
*/
|
||||||
|
public class TargetRequiredTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Redcap must sacrifice target land -- it's required target, but AI don't known about that
|
||||||
|
(target can be copied as new target in effect's code)
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_chooseBadTargetOnSacrifice_WithTargets_User() {
|
||||||
|
// Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
|
||||||
|
addCard(Zone.HAND, playerA, "Redcap Melee", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
|
||||||
|
addTarget(playerA, "Mountain");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Redcap Melee", 1);
|
||||||
|
assertGraveyardCount(playerA, "Mountain", 1);
|
||||||
|
assertPermanentCount(playerA, "Mountain", 3 - 1);
|
||||||
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_chooseBadTargetOnSacrifice_WithTargets_AI() {
|
||||||
|
// Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
|
||||||
|
addCard(Zone.HAND, playerA, "Redcap Melee", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
|
||||||
|
//addTarget(playerA, "Mountain"); AI must select targets
|
||||||
|
|
||||||
|
//setStrictChooseMode(true); AI must select targets
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Redcap Melee", 1);
|
||||||
|
assertGraveyardCount(playerA, "Mountain", 1);
|
||||||
|
assertPermanentCount(playerA, "Mountain", 3 - 1);
|
||||||
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_chooseBadTargetOnSacrifice_WithoutTargets_User() {
|
||||||
|
// Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
|
||||||
|
addCard(Zone.HAND, playerA, "Redcap Melee", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Atarka Monument", 1); // {T}: Add {R} or {G}.
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
|
||||||
|
//addTarget(playerA, "Mountain"); no lands to sacrifice
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Redcap Melee", 1);
|
||||||
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_chooseBadTargetOnSacrifice_WithoutTargets_AI() {
|
||||||
|
// Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
|
||||||
|
addCard(Zone.HAND, playerA, "Redcap Melee", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Atarka Monument", 1); // {T}: Add {R} or {G}.
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
|
||||||
|
//addTarget(playerA, "Mountain"); no lands to sacrifice
|
||||||
|
|
||||||
|
//setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Redcap Melee", 1);
|
||||||
|
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,10 @@ import org.mage.test.utils.DeckTestUtils;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intended to test Mage server under different load patterns.
|
* Intended to test Mage server under different load patterns.
|
||||||
|
@ -188,7 +191,7 @@ public class LoadTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playTwoAIGame(String deckColors, String deckAllowedSets) {
|
public void playTwoAIGame(String gameName, String deckColors, String deckAllowedSets) {
|
||||||
Assert.assertFalse("need deck colors", deckColors.isEmpty());
|
Assert.assertFalse("need deck colors", deckColors.isEmpty());
|
||||||
Assert.assertFalse("need allowed sets", deckAllowedSets.isEmpty());
|
Assert.assertFalse("need allowed sets", deckAllowedSets.isEmpty());
|
||||||
|
|
||||||
|
@ -197,7 +200,7 @@ public class LoadTest {
|
||||||
|
|
||||||
// game by monitor
|
// game by monitor
|
||||||
GameTypeView gameType = monitor.session.getGameTypes().get(0);
|
GameTypeView gameType = monitor.session.getGameTypes().get(0);
|
||||||
MatchOptions gameOptions = createSimpleGameOptionsForAI(gameType, monitor.session);
|
MatchOptions gameOptions = createSimpleGameOptionsForAI(gameType, monitor.session, gameName);
|
||||||
TableView game = monitor.session.createTable(monitor.roomID, gameOptions);
|
TableView game = monitor.session.createTable(monitor.roomID, gameOptions);
|
||||||
UUID tableId = game.getTableId();
|
UUID tableId = game.getTableId();
|
||||||
|
|
||||||
|
@ -218,7 +221,11 @@ public class LoadTest {
|
||||||
|
|
||||||
checkGame = monitor.getTable(tableId);
|
checkGame = monitor.getTable(tableId);
|
||||||
TableState state = checkGame.get().getTableState();
|
TableState state = checkGame.get().getTableState();
|
||||||
logger.warn((gameView != null ? "Turn " + gameView.getTurn() + ", " + gameView.getStep().toString() + " - " : "") + state);
|
|
||||||
|
logger.warn(checkGame.get().getTableName()
|
||||||
|
+ (gameView != null ? ", turn " + gameView.getTurn() + ", " + gameView.getStep().toString() : "")
|
||||||
|
+ (gameView != null ? ", active " + gameView.getActivePlayerName() : "")
|
||||||
|
+ ", " + state);
|
||||||
|
|
||||||
if (state == TableState.FINISHED) {
|
if (state == TableState.FINISHED) {
|
||||||
break;
|
break;
|
||||||
|
@ -246,25 +253,34 @@ public class LoadTest {
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
public void test_TwoAIPlayGame_One() {
|
public void test_TwoAIPlayGame_One() {
|
||||||
playTwoAIGame("GR", "GRN");
|
playTwoAIGame("Single AI game", "GR", "GRN");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
public void test_TwoAIPlayGame_Multiple() {
|
public void test_TwoAIPlayGame_Multiple() {
|
||||||
|
|
||||||
// save random seeds for repeated results
|
int singleGameSID = -1554824422; // for one game test with same deck
|
||||||
int gamesAmount = 1000;
|
int singleGamePlaysAmount = 1000; // multiple run of one game test
|
||||||
|
|
||||||
|
// save random seeds for repeated results (in decks generating)
|
||||||
List<Integer> seedsList = new ArrayList<>();
|
List<Integer> seedsList = new ArrayList<>();
|
||||||
for (int i = 1; i <= gamesAmount; i++) {
|
if (singleGameSID != 0) {
|
||||||
seedsList.add(RandomUtil.nextInt());
|
for (int i = 1; i <= singleGamePlaysAmount; i++) {
|
||||||
|
seedsList.add(singleGameSID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int gamesAmount = 1000;
|
||||||
|
for (int i = 1; i <= gamesAmount; i++) {
|
||||||
|
seedsList.add(RandomUtil.nextInt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i <= gamesAmount; i++) {
|
for (int i = 0; i <= seedsList.size() - 1; i++) {
|
||||||
long randomSeed = seedsList.get(i);
|
long randomSeed = seedsList.get(i);
|
||||||
logger.info("Game " + i + " of " + gamesAmount + ", RANDOM seed: " + randomSeed);
|
logger.info("Game " + (i + 1) + " of " + seedsList.size() + ", RANDOM seed: " + randomSeed);
|
||||||
RandomUtil.setSeed(randomSeed);
|
RandomUtil.setSeed(randomSeed);
|
||||||
playTwoAIGame("WGUBR", "ELD");
|
playTwoAIGame("AI game #" + (i + 1), "WGUBR", "ELD");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,8 +470,8 @@ public class LoadTest {
|
||||||
return createSimpleGameOptions("Bots test game", gameTypeView, session, PlayerType.HUMAN);
|
return createSimpleGameOptions("Bots test game", gameTypeView, session, PlayerType.HUMAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatchOptions createSimpleGameOptionsForAI(GameTypeView gameTypeView, Session session) {
|
private MatchOptions createSimpleGameOptionsForAI(GameTypeView gameTypeView, Session session, String gameName) {
|
||||||
return createSimpleGameOptions("AI test game", gameTypeView, session, PlayerType.COMPUTER_MAD);
|
return createSimpleGameOptions(gameName, gameTypeView, session, PlayerType.COMPUTER_MAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadPlayer {
|
private class LoadPlayer {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import mage.filter.FilterImpl;
|
||||||
import mage.filter.FilterInPlay;
|
import mage.filter.FilterInPlay;
|
||||||
import mage.filter.predicate.mageobject.FromSetPredicate;
|
import mage.filter.predicate.mageobject.FromSetPredicate;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.Target;
|
import mage.target.Target;
|
||||||
|
@ -17,7 +18,6 @@ import mage.target.TargetImpl;
|
||||||
import mage.util.TargetAddress;
|
import mage.util.TargetAddress;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import mage.game.events.GameEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param <T>
|
* @param <T>
|
||||||
|
@ -259,8 +259,8 @@ class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxNumberOfTargets() {
|
public int getMinNumberOfTargets() {
|
||||||
return originalTarget.getMaxNumberOfTargets();
|
return originalTarget.getMinNumberOfTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -268,6 +268,11 @@ class TargetWithAdditionalFilter<T extends MageItem> extends TargetImpl {
|
||||||
originalTarget.setMinNumberOfTargets(minNumberOfTargets);
|
originalTarget.setMinNumberOfTargets(minNumberOfTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxNumberOfTargets() {
|
||||||
|
return originalTarget.getMaxNumberOfTargets();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMaxNumberOfTargets(int maxNumberOfTargets) {
|
public void setMaxNumberOfTargets(int maxNumberOfTargets) {
|
||||||
originalTarget.setMaxNumberOfTargets(maxNumberOfTargets);
|
originalTarget.setMaxNumberOfTargets(maxNumberOfTargets);
|
||||||
|
|
Loading…
Reference in a new issue