mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Commit initial version of experimental AI simulation code.
It's disabled by default. Set SpellAbilityPicker.USE_SIMULATION = true to enable. It's nowhere near complete and there are still tons of issues to work out, but it works a lot of the time.
This commit is contained in:
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -147,6 +147,11 @@ forge-ai/src/main/java/forge/ai/ability/UntapAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/VoteAi.java -text
|
||||
forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java -text
|
||||
forge-ai/src/main/java/simulation/GameCopier.java -text
|
||||
forge-ai/src/main/java/simulation/GameSimulator.java -text
|
||||
forge-ai/src/main/java/simulation/GameStateEvaluator.java -text
|
||||
forge-ai/src/main/java/simulation/PossibleTargetSelector.java -text
|
||||
forge-ai/src/main/java/simulation/SpellAbilityPicker.java -text
|
||||
forge-core/.classpath -text
|
||||
forge-core/.project -text
|
||||
forge-core/.settings/org.eclipse.core.resources.prefs -text
|
||||
|
||||
@@ -28,6 +28,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import simulation.SpellAbilityPicker;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -102,6 +104,7 @@ public class AiController {
|
||||
private final Game game;
|
||||
private final AiCardMemory memory;
|
||||
public boolean bCheatShuffle;
|
||||
private SpellAbilityPicker simPicker;
|
||||
|
||||
public boolean canCheatShuffle() {
|
||||
return bCheatShuffle;
|
||||
@@ -127,6 +130,7 @@ public class AiController {
|
||||
player = computerPlayer;
|
||||
game = game0;
|
||||
memory = new AiCardMemory();
|
||||
simPicker = new SpellAbilityPicker(game, player);
|
||||
}
|
||||
|
||||
private CardCollection getAvailableCards() {
|
||||
@@ -1207,6 +1211,10 @@ public class AiController {
|
||||
if (all == null || all.isEmpty())
|
||||
return null;
|
||||
|
||||
SpellAbility simSa = simPicker.chooseSpellAbilityToPlay(getOriginalAndAltCostAbilities(all), skipCounter);
|
||||
if (simSa != null)
|
||||
return simSa;
|
||||
|
||||
Collections.sort(all, saComparator); // put best spells first
|
||||
|
||||
for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
|
||||
|
||||
195
forge-ai/src/main/java/simulation/GameCopier.java
Normal file
195
forge-ai/src/main/java/simulation/GameCopier.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package simulation;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GameRules;
|
||||
import forge.game.Match;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class GameCopier {
|
||||
private static final ZoneType[] ZONES = new ZoneType[] {
|
||||
ZoneType.Battlefield,
|
||||
ZoneType.Hand,
|
||||
ZoneType.Graveyard,
|
||||
ZoneType.Library,
|
||||
ZoneType.Exile,
|
||||
};
|
||||
|
||||
private Game origGame;
|
||||
private BiMap<Player, Player> playerMap = HashBiMap.create();
|
||||
private BiMap<Card, Card> cardMap = HashBiMap.create();
|
||||
|
||||
public GameCopier(Game origGame) {
|
||||
this.origGame = origGame;
|
||||
}
|
||||
|
||||
public Game makeCopy() {
|
||||
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
|
||||
List<RegisteredPlayer> newPlayers = new ArrayList<>();
|
||||
for (RegisteredPlayer p : origPlayers) {
|
||||
newPlayers.add(clonePlayer(p));
|
||||
}
|
||||
GameRules currentRules = origGame.getRules();
|
||||
Match newMatch = new Match(currentRules, newPlayers);
|
||||
Game newGame = new Game(newPlayers, currentRules, newMatch);
|
||||
for (int i = 0; i < origGame.getPlayers().size(); i++) {
|
||||
Player origPlayer = origGame.getPlayers().get(i);
|
||||
Player newPlayer = newGame.getPlayers().get(i);
|
||||
newPlayer.setLife(origPlayer.getLife(), null);
|
||||
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
|
||||
newPlayer.setPoisonCounters(origPlayer.getPoisonCounters(), null);
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
||||
playerMap.put(origPlayer, newPlayer);
|
||||
}
|
||||
|
||||
Player newPlayerTurn = playerMap.get(origGame.getPhaseHandler().getPlayerTurn());
|
||||
newGame.getPhaseHandler().devModeSet(origGame.getPhaseHandler().getPhase(), newPlayerTurn);
|
||||
newGame.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
|
||||
copyGameState(newGame);
|
||||
|
||||
newGame.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
newGame.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
|
||||
return newGame;
|
||||
}
|
||||
|
||||
private RegisteredPlayer clonePlayer(RegisteredPlayer p) {
|
||||
RegisteredPlayer clone = new RegisteredPlayer(p.getDeck());
|
||||
clone.setPlayer(p.getPlayer());
|
||||
return clone;
|
||||
}
|
||||
|
||||
private void copyGameState(Game newGame) {
|
||||
for (ZoneType zone : ZONES) {
|
||||
for (Card card : origGame.getCardsIn(zone)) {
|
||||
addCard(newGame, zone, card);
|
||||
}
|
||||
}
|
||||
for (Card card : origGame.getCardsIn(ZoneType.Battlefield)) {
|
||||
Card otherCard = cardMap.get(card);
|
||||
if (card.isEnchanting()) {
|
||||
otherCard.setEnchanting(cardMap.get(card.getEnchanting()));
|
||||
}
|
||||
if (card.isEquipping()) {
|
||||
otherCard.setEquipping(cardMap.get(card.getEquipping()));
|
||||
}
|
||||
if (card.isFortifying()) {
|
||||
otherCard.setFortifying(cardMap.get(card.getFortifying()));
|
||||
}
|
||||
if (card.getCloneOrigin() != null) {
|
||||
otherCard.setCloneOrigin(cardMap.get(card.getCloneOrigin()));
|
||||
}
|
||||
if (card.getHaunting() != null) {
|
||||
otherCard.setHaunting(cardMap.get(card.getHaunting()));
|
||||
}
|
||||
if (card.getEffectSource() != null) {
|
||||
otherCard.setEffectSource(cardMap.get(card.getEffectSource()));
|
||||
}
|
||||
if (card.isPaired()) {
|
||||
otherCard.setPairedWith(cardMap.get(card.getPairedWith()));
|
||||
}
|
||||
otherCard.setCommander(card.isCommander());
|
||||
// TODO: Verify that the above relationships are preserved bi-directionally or not.
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addCard(Game newGame, ZoneType zone, Card c) {
|
||||
Player owner = playerMap.get(c.getOwner());
|
||||
Card newCard = null;
|
||||
if (c.isToken()) {
|
||||
String tokenStr = new CardFactory.TokenInfo(c).toString();
|
||||
// TODO: Use a version of the API that doesn't return a list (i.e. these shouldn't be affected
|
||||
// by doubling season, etc).
|
||||
newCard = CardFactory.makeToken(CardFactory.TokenInfo.fromString(tokenStr), owner).get(0);
|
||||
} else {
|
||||
newCard = Card.fromPaperCard(c.getPaperCard(), owner);
|
||||
}
|
||||
cardMap.put(c, newCard);
|
||||
|
||||
Player zoneOwner = owner;
|
||||
if (zone == ZoneType.Battlefield) {
|
||||
// TODO: Controllers' list with timestamps should be copied.
|
||||
zoneOwner = playerMap.get(c.getController());
|
||||
newCard.setController(zoneOwner, 0);
|
||||
newCard.addTempPowerBoost(c.getTempPowerBoost());
|
||||
newCard.addTempToughnessBoost(c.getTempToughnessBoost());
|
||||
|
||||
newCard.setChangedCardTypes(c.getChangedCardTypes());
|
||||
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
|
||||
for (String kw : c.getHiddenExtrinsicKeywords())
|
||||
newCard.addHiddenExtrinsicKeyword(kw);
|
||||
newCard.setExtrinsicKeyword((ArrayList<String>) c.getExtrinsicKeyword().clone());
|
||||
if (c.isTapped()) {
|
||||
newCard.setTapped(true);
|
||||
}
|
||||
if (c.isSick()) {
|
||||
newCard.setSickness(true);
|
||||
}
|
||||
if (c.isFaceDown()) {
|
||||
newCard.setState(CardStateName.FaceDown, true);
|
||||
if (c.isManifested()) {
|
||||
newCard.setManifested(true);
|
||||
// TODO: Should be able to copy other abilities...
|
||||
newCard.addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(newCard, newCard.getManaCost()));
|
||||
}
|
||||
}
|
||||
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
if (!counters.isEmpty()) {
|
||||
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
|
||||
String str = kv.getKey().toString();
|
||||
int count = kv.getValue();
|
||||
newCard.addCounter(CounterType.valueOf(str), count, false);
|
||||
}
|
||||
}
|
||||
// TODO: Other chosen things...
|
||||
if (c.getChosenPlayer() != null) {
|
||||
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
||||
}
|
||||
// TODO: FIXME
|
||||
if (c.hasRemembered()) {
|
||||
for (Object o : c.getRemembered()) {
|
||||
System.out.println("Remembered: " + o + o.getClass());
|
||||
//newCard.addRemembered(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoneOwner.getZone(zone).add(newCard);
|
||||
}
|
||||
|
||||
|
||||
public GameObject find(GameObject o) {
|
||||
GameObject result = cardMap.get(o);
|
||||
if (result != null)
|
||||
return result;
|
||||
// TODO: Have only one GameObject map?
|
||||
return playerMap.get(o);
|
||||
}
|
||||
public GameObject reverseFind(GameObject o) {
|
||||
GameObject result = cardMap.inverse().get(o);
|
||||
if (result != null)
|
||||
return result;
|
||||
// TODO: Have only one GameObject map?
|
||||
return playerMap.inverse().get(o);
|
||||
}
|
||||
}
|
||||
151
forge-ai/src/main/java/simulation/GameSimulator.java
Normal file
151
forge-ai/src/main/java/simulation/GameSimulator.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package simulation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Ability;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class GameSimulator {
|
||||
private Game origGame;
|
||||
private GameCopier copier;
|
||||
private Game simGame;
|
||||
private Player aiPlayer;
|
||||
private Player opponent;
|
||||
private GameStateEvaluator eval;
|
||||
|
||||
public GameSimulator(final Game origGame) {
|
||||
this.origGame = origGame;
|
||||
copier = new GameCopier(origGame);
|
||||
simGame = copier.makeCopy();
|
||||
// TODO:
|
||||
aiPlayer = simGame.getPlayers().get(1);
|
||||
opponent = simGame.getPlayers().get(0);
|
||||
eval = new GameStateEvaluator();
|
||||
int simScore = eval.getScoreForGameState(simGame, aiPlayer, opponent);
|
||||
int origScore = getScoreForOrigGame();
|
||||
debugPrint = true;
|
||||
if (simScore != origScore) {
|
||||
// Print debug info.
|
||||
eval.getScoreForGameState(simGame, aiPlayer, opponent);
|
||||
getScoreForOrigGame();
|
||||
throw new RuntimeException("Game copy error");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean debugPrint;
|
||||
public static void debugPrint(String str) {
|
||||
if (debugPrint) {
|
||||
System.out.println(str);
|
||||
}
|
||||
}
|
||||
|
||||
private SpellAbility findSaInSimGame(SpellAbility sa) {
|
||||
Card origHostCard = sa.getHostCard();
|
||||
ZoneType zone = origHostCard.getZone().getZoneType();
|
||||
for (Card c : simGame.getCardsIn(zone)) {
|
||||
if (!c.getOwner().getController().isAI()) {
|
||||
continue;
|
||||
}
|
||||
debugPrint(c.getName()+"->");
|
||||
if (c.getName().equals(origHostCard.getName())) {
|
||||
for (SpellAbility cSa : c.getSpellAbilities()) {
|
||||
debugPrint(" "+cSa);
|
||||
if (cSa.getDescription().equals(sa.getDescription())) {
|
||||
return cSa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public int simulateSpellAbility(SpellAbility origSa) {
|
||||
// TODO: optimize: prune identical SA (e.g. two of the same card in hand)
|
||||
|
||||
boolean found = false;
|
||||
SpellAbility sa = findSaInSimGame(origSa);
|
||||
if (sa != null) {
|
||||
found = true;
|
||||
} else {
|
||||
System.err.println("SA not found! " + sa);
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
Player origActivatingPlayer = sa.getActivatingPlayer();
|
||||
sa.setActivatingPlayer(aiPlayer);
|
||||
if (origSa.usesTargeting()) {
|
||||
for (GameObject o : origSa.getTargets().getTargets()) {
|
||||
debugPrint("Copying over target " +o);
|
||||
debugPrint(" found: "+copier.find(o));
|
||||
sa.getTargets().add(copier.find(o));
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint("Simulating playing sa: " + sa + " found="+found);
|
||||
if (sa == Ability.PLAY_LAND_SURROGATE) {
|
||||
aiPlayer.playLand(sa.getHostCard(), false);
|
||||
}
|
||||
else {
|
||||
// TODO: should simulate all possible targets...
|
||||
if (!sa.getAllTargetChoices().isEmpty()) {
|
||||
debugPrint("Targets: ");
|
||||
for (TargetChoices target : sa.getAllTargetChoices()) {
|
||||
System.out.print(target.getTargetedString());
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame);
|
||||
}
|
||||
|
||||
if (simGame.getStack().isEmpty()) {
|
||||
System.err.println("Stack empty: " + sa);
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
opponent.runWithController(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Set<Card> allAffectedCards = new HashSet<Card>();
|
||||
do {
|
||||
// Resolve the top effect on the stack.
|
||||
simGame.getStack().resolveStack();
|
||||
// Evaluate state based effects as a result of resolving stack.
|
||||
// Note: Needs to happen after resolve stack rather than at the
|
||||
// top of the loop to ensure state effects are evaluated after the
|
||||
// last resolved effect
|
||||
simGame.getAction().checkStateEffects(false, allAffectedCards);
|
||||
// Add any triggers as a result of resolving the effect.
|
||||
simGame.getStack().addAllTriggeredAbilitiesToStack();
|
||||
// Continue until stack is empty.
|
||||
} while (!simGame.getStack().isEmpty() && !simGame.isGameOver());
|
||||
}
|
||||
}, new PlayerControllerAi(simGame, opponent, opponent.getLobbyPlayer()));
|
||||
|
||||
// TODO: If this is during combat, before blockers are declared,
|
||||
// we should simulate how combat will resolve and evaluate that
|
||||
// state instead!
|
||||
debugPrint("SimGame:");
|
||||
int score = eval.getScoreForGameState(simGame, aiPlayer, opponent);
|
||||
|
||||
sa.setActivatingPlayer(origActivatingPlayer);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
public int getScoreForOrigGame() {
|
||||
// TODO: Make this logic more bulletproof.
|
||||
Player origAiPlayer = origGame.getPlayers().get(1);
|
||||
Player origOpponent = origGame.getPlayers().get(0);
|
||||
return eval.getScoreForGameState(origGame, origAiPlayer, origOpponent);
|
||||
}
|
||||
|
||||
}
|
||||
73
forge-ai/src/main/java/simulation/GameStateEvaluator.java
Normal file
73
forge-ai/src/main/java/simulation/GameStateEvaluator.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package simulation;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class GameStateEvaluator {
|
||||
|
||||
public int getScoreForGameState(Game game, Player aiPlayer, Player opponent) {
|
||||
if (game.isGameOver()) {
|
||||
return game.getOutcome().getWinningPlayer() == aiPlayer ? Integer.MAX_VALUE : Integer.MIN_VALUE;
|
||||
}
|
||||
int score = 0;
|
||||
// TODO: more than 2 players
|
||||
int myCards = 0;
|
||||
int theirCards = 0;
|
||||
for (Card c : game.getCardsIn(ZoneType.Hand)) {
|
||||
if (c.getController() == aiPlayer) {
|
||||
myCards++;
|
||||
} else {
|
||||
theirCards++;
|
||||
}
|
||||
}
|
||||
GameSimulator.debugPrint("My cards in hand: " + myCards);
|
||||
GameSimulator.debugPrint("Their cards in hand: " + theirCards);
|
||||
score += 3 * myCards - 3 * theirCards;
|
||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
int value = evalCard(c);
|
||||
String str = c.toString();
|
||||
if (c.isCreature()) {
|
||||
str += " " + c.getNetPower() + "/" + c.getNetToughness();
|
||||
}
|
||||
if (c.getController() == aiPlayer) {
|
||||
GameSimulator.debugPrint(" Battlefield: " + str + " = " + value);
|
||||
score += value;
|
||||
} else {
|
||||
GameSimulator.debugPrint(" Battlefield: " + str + " = -" + value);
|
||||
score -= value;
|
||||
}
|
||||
String nonAbilityText = c.getNonAbilityText();
|
||||
if (!nonAbilityText.isEmpty()) {
|
||||
GameSimulator.debugPrint(" "+nonAbilityText.replaceAll("CARDNAME", c.getName()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
GameSimulator.debugPrint(" My life: " + aiPlayer.getLife());
|
||||
score += aiPlayer.getLife();
|
||||
GameSimulator.debugPrint(" Opponent life: -" + opponent.getLife());
|
||||
score -= opponent.getLife();
|
||||
GameSimulator.debugPrint("Score = " + score);
|
||||
return score;
|
||||
}
|
||||
|
||||
private static int evalCard(Card c) {
|
||||
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
|
||||
if (c.isCreature()) {
|
||||
return ComputerUtilCard.evaluateCreature(c);
|
||||
} else if (c.isLand()) {
|
||||
return 100;
|
||||
} else if (c.isEnchantingCard()) {
|
||||
// TODO: Should provide value in whatever it's enchanting?
|
||||
// Else the computer would think that casting a Lifelink enchantment
|
||||
// on something that already has lifelink is a net win.
|
||||
return 0;
|
||||
} else {
|
||||
// e.g. a 5 CMC permanent results in 200, whereas a 5/5 creature is ~225
|
||||
return 50 + 30 * c.getCMC();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class PossibleTargetSelector {
|
||||
private SpellAbility sa;
|
||||
private TargetRestrictions tgt;
|
||||
private int targetIndex;
|
||||
private List<GameObject> validTargets;
|
||||
|
||||
public PossibleTargetSelector(Game game, Player self, SpellAbility sa) {
|
||||
this.sa = sa;
|
||||
this.tgt = sa.getTargetRestrictions();
|
||||
this.targetIndex = 0;
|
||||
this.validTargets = new ArrayList<GameObject>();
|
||||
if (tgt.canTgtPermanent() || tgt.canTgtCreature()) {
|
||||
// TODO: What about things that target enchantments and such?
|
||||
validTargets.addAll(CardUtil.getValidCardsToTarget(tgt, sa));
|
||||
}
|
||||
if (tgt.canTgtPlayer()) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p != self || !tgt.canOnlyTgtOpponent()) {
|
||||
validTargets.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean selectNextTargets() {
|
||||
if (targetIndex >= validTargets.size()) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
int index = targetIndex;
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
sa.getTargets().add(validTargets.get(index++));
|
||||
}
|
||||
// TODO: smarter about multiple targets, identical targets, etc...
|
||||
targetIndex++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
109
forge-ai/src/main/java/simulation/SpellAbilityPicker.java
Normal file
109
forge-ai/src/main/java/simulation/SpellAbilityPicker.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
|
||||
public class SpellAbilityPicker {
|
||||
private static boolean USE_SIMULATION = false;
|
||||
private Game game;
|
||||
private Player player;
|
||||
|
||||
public SpellAbilityPicker(Game game, Player player) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSpellAbilityToPlay(final ArrayList<SpellAbility> originalAndAltCostAbilities, boolean skipCounter) {
|
||||
if (!USE_SIMULATION)
|
||||
return null;
|
||||
|
||||
System.out.println("----\nchooseSpellAbilityToPlay game " + game.toString());
|
||||
System.out.println("---- (phase = " + game.getPhaseHandler().getPhase() + ")");
|
||||
|
||||
ArrayList<SpellAbility> candidateSAs = new ArrayList<>();
|
||||
for (final SpellAbility sa : originalAndAltCostAbilities) {
|
||||
// Don't add Counterspells to the "normal" playcard lookups
|
||||
if (skipCounter && sa.getApi() == ApiType.Counter) {
|
||||
continue;
|
||||
}
|
||||
sa.setActivatingPlayer(player);
|
||||
|
||||
AiPlayDecision opinion = canPlayAndPayForSim(sa);
|
||||
System.out.println(" " + opinion + ": " + sa);
|
||||
// PhaseHandler ph = game.getPhaseHandler();
|
||||
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
||||
|
||||
if (opinion != AiPlayDecision.WillPlay)
|
||||
continue;
|
||||
candidateSAs.add(sa);
|
||||
}
|
||||
if (candidateSAs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
SpellAbility bestSa = null;
|
||||
System.out.println("Evaluating...");
|
||||
GameSimulator simulator = new GameSimulator(game);
|
||||
// FIXME: This is wasteful, we should re-use the same simulator...
|
||||
int bestSaValue = simulator.getScoreForOrigGame();
|
||||
for (final SpellAbility sa : candidateSAs) {
|
||||
int value = evaluateSa(sa);
|
||||
if (value > bestSaValue) {
|
||||
bestSaValue = value;
|
||||
bestSa = sa;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("BEST: " + bestSa + " SCORE: " + bestSaValue);
|
||||
return bestSa;
|
||||
}
|
||||
|
||||
private AiPlayDecision canPlayAndPayForSim(final SpellAbility sa) {
|
||||
if (!sa.canPlay()) {
|
||||
return AiPlayDecision.CantPlaySa;
|
||||
}
|
||||
if (sa.getConditions() != null && !sa.getConditions().areMet(sa)) {
|
||||
return AiPlayDecision.CantPlaySa;
|
||||
}
|
||||
|
||||
/*
|
||||
AiPlayDecision op = canPlaySa(sa);
|
||||
if (op != AiPlayDecision.WillPlay) {
|
||||
return op;
|
||||
}
|
||||
*/
|
||||
return ComputerUtilCost.canPayCost(sa, player) ? AiPlayDecision.WillPlay : AiPlayDecision.CantAfford;
|
||||
}
|
||||
|
||||
private int evaluateSa(SpellAbility sa) {
|
||||
System.out.println("Evaluate SA: " + sa);
|
||||
if (!sa.usesTargeting()) {
|
||||
GameSimulator simulator = new GameSimulator(game);
|
||||
return simulator.simulateSpellAbility(sa);
|
||||
}
|
||||
PossibleTargetSelector selector = new PossibleTargetSelector(game, player, sa);
|
||||
int bestScore = Integer.MIN_VALUE;
|
||||
TargetChoices tgt = null;
|
||||
while (selector.selectNextTargets()) {
|
||||
System.out.println("Trying targets: " + sa.getTargets().getTargetedString());
|
||||
GameSimulator simulator = new GameSimulator(game);
|
||||
int score = simulator.simulateSpellAbility(sa);
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
tgt = sa.getTargets();
|
||||
sa.resetTargets();
|
||||
}
|
||||
}
|
||||
if (tgt != null) {
|
||||
sa.setTargets(tgt);
|
||||
}
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2397,6 +2397,10 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return Collections.unmodifiableMap(changedCardTypes);
|
||||
}
|
||||
|
||||
public Map<Long, KeywordsChange> getChangedCardKeywords() {
|
||||
return changedCardKeywords;
|
||||
}
|
||||
|
||||
public final void addChangedCardTypes(final CardType addType, final CardType removeType,
|
||||
final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes,
|
||||
final boolean removeCreatureTypes, final long timestamp) {
|
||||
@@ -6443,4 +6447,18 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return keywords;
|
||||
}
|
||||
}
|
||||
|
||||
public void setChangedCardTypes(Map<Long, CardChangedType> changedCardTypes) {
|
||||
this.changedCardTypes.clear();
|
||||
for (Entry<Long, CardChangedType> entry : changedCardTypes.entrySet()) {
|
||||
this.changedCardTypes.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void setChangedCardKeywords(Map<Long, KeywordsChange> changedCardKeywords) {
|
||||
this.changedCardKeywords.clear();
|
||||
for (Entry<Long, KeywordsChange> entry : changedCardKeywords.entrySet()) {
|
||||
this.changedCardKeywords.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user