Fixed AI freeze with non available targets

This commit is contained in:
Oleg Agafonov 2019-12-21 18:10:29 +04:00
parent 394d9716ca
commit bd71c98e3e
6 changed files with 174 additions and 28 deletions

View file

@ -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!");
} }

View file

@ -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;
}
} }

View file

@ -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.";
} }

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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);