mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Some improvements to simulated AI:
- When evaluating creatures, ignore temporary boosts unless the creature is likely to participate in combat. - Set default search depth to 2.
This commit is contained in:
@@ -555,11 +555,14 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
||||
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
|
||||
for (Card attacker : this.attackers) {
|
||||
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
if (ai.getController() instanceof PlayerControllerAi) {
|
||||
// Only do this if |ai| is actually an AI - as we could be trying to predict how the human will attack.
|
||||
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
|
||||
for (Card attacker : this.attackers) {
|
||||
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package forge.ai.simulation;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.CreatureEvaluator;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -17,10 +20,29 @@ public class GameStateEvaluator {
|
||||
this.debugging = debugging;
|
||||
}
|
||||
|
||||
private static void debugPrint(String s) {
|
||||
//System.err.println(s);
|
||||
GameSimulator.debugPrint(s);
|
||||
}
|
||||
|
||||
private Combat simulateUpcomingCombatThisTurn(Game game) {
|
||||
PhaseHandler handler = game.getPhaseHandler();
|
||||
if (handler.getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
||||
return null;
|
||||
}
|
||||
AiAttackController aiAtk = new AiAttackController(handler.getPlayerTurn());
|
||||
Combat combat = new Combat(handler.getPlayerTurn());
|
||||
aiAtk.declareAttackers(combat);
|
||||
return combat;
|
||||
}
|
||||
|
||||
public Score getScoreForGameState(Game game, Player aiPlayer) {
|
||||
if (game.isGameOver()) {
|
||||
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
Combat combat = simulateUpcomingCombatThisTurn(game);
|
||||
|
||||
int score = 0;
|
||||
// TODO: more than 2 players
|
||||
int myCards = 0;
|
||||
@@ -32,21 +54,21 @@ public class GameStateEvaluator {
|
||||
theirCards++;
|
||||
}
|
||||
}
|
||||
GameSimulator.debugPrint("My cards in hand: " + myCards);
|
||||
GameSimulator.debugPrint("Their cards in hand: " + theirCards);
|
||||
debugPrint("My cards in hand: " + myCards);
|
||||
debugPrint("Their cards in hand: " + theirCards);
|
||||
if (myCards > aiPlayer.getMaxHandSize()) {
|
||||
// Count excess cards for less.
|
||||
score += myCards - aiPlayer.getMaxHandSize();
|
||||
myCards = aiPlayer.getMaxHandSize();
|
||||
}
|
||||
score += 5 * myCards - 4 * theirCards;
|
||||
GameSimulator.debugPrint(" My life: " + aiPlayer.getLife());
|
||||
debugPrint(" My life: " + aiPlayer.getLife());
|
||||
score += 2 * aiPlayer.getLife();
|
||||
int opponentIndex = 1;
|
||||
int opponentLife = 0;
|
||||
for (Player opponent : game.getPlayers()) {
|
||||
if (opponent != aiPlayer) {
|
||||
GameSimulator.debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
|
||||
debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
|
||||
opponentLife += opponent.getLife();
|
||||
opponentIndex++;
|
||||
}
|
||||
@@ -55,7 +77,7 @@ public class GameStateEvaluator {
|
||||
int summonSickScore = score;
|
||||
PhaseType gamePhase = game.getPhaseHandler().getPhase();
|
||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
int value = evalCard(game, aiPlayer, c);
|
||||
int value = evalCard(game, aiPlayer, c, combat);
|
||||
int summonSickValue = value;
|
||||
// To make the AI hold-off on playing creatures in MAIN1 if they give no other benefits,
|
||||
// keep track of the score while treating summon sick creatures as having a value of 0.
|
||||
@@ -67,32 +89,35 @@ public class GameStateEvaluator {
|
||||
str += " " + c.getNetPower() + "/" + c.getNetToughness();
|
||||
}
|
||||
if (c.getController() == aiPlayer) {
|
||||
GameSimulator.debugPrint(" Battlefield: " + str + " = " + value);
|
||||
debugPrint(" Battlefield: " + str + " = " + value);
|
||||
score += value;
|
||||
summonSickScore += summonSickValue;
|
||||
} else {
|
||||
GameSimulator.debugPrint(" Battlefield: " + str + " = -" + value);
|
||||
debugPrint(" Battlefield: " + str + " = -" + value);
|
||||
score -= value;
|
||||
summonSickScore -= summonSickValue;
|
||||
}
|
||||
String nonAbilityText = c.getNonAbilityText();
|
||||
if (!nonAbilityText.isEmpty()) {
|
||||
GameSimulator.debugPrint(" "+nonAbilityText.replaceAll("CARDNAME", c.getName()));
|
||||
debugPrint(" "+nonAbilityText.replaceAll("CARDNAME", c.getName()));
|
||||
}
|
||||
}
|
||||
GameSimulator.debugPrint("Score = " + score);
|
||||
debugPrint("Score = " + score);
|
||||
return new Score(score, summonSickScore);
|
||||
}
|
||||
|
||||
protected int evalCard(Game game, Player aiPlayer, Card c) {
|
||||
protected int evalCard(Game game, Player aiPlayer, Card c, Combat combat) {
|
||||
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
|
||||
if (c.isCreature()) {
|
||||
// Ignore temp boosts post combat, since it's a waste.
|
||||
// TODO: Make this smarter. Temp boosts pre-combat are also useless if there's no plan to attack
|
||||
// with that creature - or if you're just temporarily pumping down.
|
||||
// TODO: Make this smarter. Right now, it only looks if the creature will attack - but
|
||||
// does not consider things like blocks or the outcome of combat.
|
||||
// Also, sometimes temp boosts post combat could be useful - e.g. if you then want to make your
|
||||
// creature fight another, etc.
|
||||
ignoreTempBoosts = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||
ignoreTempBoosts = true;
|
||||
if (combat != null && combat.isAttacking(c)) {
|
||||
ignoreTempBoosts = false;
|
||||
}
|
||||
int result = eval.evaluateCreature(c);
|
||||
return result;
|
||||
} else if (c.isLand()) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import forge.ai.simulation.GameStateEvaluator.Score;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class SimulationController {
|
||||
private static int MAX_DEPTH = 5;
|
||||
private static int MAX_DEPTH = 2;
|
||||
|
||||
private int recursionDepth;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user