mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28: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,6 +555,8 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
|
||||||
|
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();
|
AiCardMemory aiMemory = ((PlayerControllerAi)ai.getController()).getAi().getCardMemory();
|
||||||
for (Card attacker : this.attackers) {
|
for (Card attacker : this.attackers) {
|
||||||
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
if (aiMemory.isRememberedCard(attacker, AiCardMemory.MemorySet.MANDATORY_ATTACKERS)) {
|
||||||
@@ -562,6 +564,7 @@ public class AiAttackController {
|
|||||||
attackersLeft.remove(attacker);
|
attackersLeft.remove(attacker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Exalted
|
// Exalted
|
||||||
if (combat.getAttackers().isEmpty()) {
|
if (combat.getAttackers().isEmpty()) {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.CreatureEvaluator;
|
import forge.ai.CreatureEvaluator;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -17,10 +20,29 @@ public class GameStateEvaluator {
|
|||||||
this.debugging = debugging;
|
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) {
|
public Score getScoreForGameState(Game game, Player aiPlayer) {
|
||||||
if (game.isGameOver()) {
|
if (game.isGameOver()) {
|
||||||
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
|
return game.getOutcome().getWinningPlayer() == aiPlayer ? new Score(Integer.MAX_VALUE) : new Score(Integer.MIN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Combat combat = simulateUpcomingCombatThisTurn(game);
|
||||||
|
|
||||||
int score = 0;
|
int score = 0;
|
||||||
// TODO: more than 2 players
|
// TODO: more than 2 players
|
||||||
int myCards = 0;
|
int myCards = 0;
|
||||||
@@ -32,21 +54,21 @@ public class GameStateEvaluator {
|
|||||||
theirCards++;
|
theirCards++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameSimulator.debugPrint("My cards in hand: " + myCards);
|
debugPrint("My cards in hand: " + myCards);
|
||||||
GameSimulator.debugPrint("Their cards in hand: " + theirCards);
|
debugPrint("Their cards in hand: " + theirCards);
|
||||||
if (myCards > aiPlayer.getMaxHandSize()) {
|
if (myCards > aiPlayer.getMaxHandSize()) {
|
||||||
// Count excess cards for less.
|
// Count excess cards for less.
|
||||||
score += myCards - aiPlayer.getMaxHandSize();
|
score += myCards - aiPlayer.getMaxHandSize();
|
||||||
myCards = aiPlayer.getMaxHandSize();
|
myCards = aiPlayer.getMaxHandSize();
|
||||||
}
|
}
|
||||||
score += 5 * myCards - 4 * theirCards;
|
score += 5 * myCards - 4 * theirCards;
|
||||||
GameSimulator.debugPrint(" My life: " + aiPlayer.getLife());
|
debugPrint(" My life: " + aiPlayer.getLife());
|
||||||
score += 2 * aiPlayer.getLife();
|
score += 2 * aiPlayer.getLife();
|
||||||
int opponentIndex = 1;
|
int opponentIndex = 1;
|
||||||
int opponentLife = 0;
|
int opponentLife = 0;
|
||||||
for (Player opponent : game.getPlayers()) {
|
for (Player opponent : game.getPlayers()) {
|
||||||
if (opponent != aiPlayer) {
|
if (opponent != aiPlayer) {
|
||||||
GameSimulator.debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
|
debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
|
||||||
opponentLife += opponent.getLife();
|
opponentLife += opponent.getLife();
|
||||||
opponentIndex++;
|
opponentIndex++;
|
||||||
}
|
}
|
||||||
@@ -55,7 +77,7 @@ public class GameStateEvaluator {
|
|||||||
int summonSickScore = score;
|
int summonSickScore = score;
|
||||||
PhaseType gamePhase = game.getPhaseHandler().getPhase();
|
PhaseType gamePhase = game.getPhaseHandler().getPhase();
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
int value = evalCard(game, aiPlayer, c);
|
int value = evalCard(game, aiPlayer, c, combat);
|
||||||
int summonSickValue = value;
|
int summonSickValue = value;
|
||||||
// To make the AI hold-off on playing creatures in MAIN1 if they give no other benefits,
|
// 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.
|
// 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();
|
str += " " + c.getNetPower() + "/" + c.getNetToughness();
|
||||||
}
|
}
|
||||||
if (c.getController() == aiPlayer) {
|
if (c.getController() == aiPlayer) {
|
||||||
GameSimulator.debugPrint(" Battlefield: " + str + " = " + value);
|
debugPrint(" Battlefield: " + str + " = " + value);
|
||||||
score += value;
|
score += value;
|
||||||
summonSickScore += summonSickValue;
|
summonSickScore += summonSickValue;
|
||||||
} else {
|
} else {
|
||||||
GameSimulator.debugPrint(" Battlefield: " + str + " = -" + value);
|
debugPrint(" Battlefield: " + str + " = -" + value);
|
||||||
score -= value;
|
score -= value;
|
||||||
summonSickScore -= summonSickValue;
|
summonSickScore -= summonSickValue;
|
||||||
}
|
}
|
||||||
String nonAbilityText = c.getNonAbilityText();
|
String nonAbilityText = c.getNonAbilityText();
|
||||||
if (!nonAbilityText.isEmpty()) {
|
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);
|
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.
|
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
// Ignore temp boosts post combat, since it's a waste.
|
// 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
|
// TODO: Make this smarter. Right now, it only looks if the creature will attack - but
|
||||||
// with that creature - or if you're just temporarily pumping down.
|
// 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
|
// Also, sometimes temp boosts post combat could be useful - e.g. if you then want to make your
|
||||||
// creature fight another, etc.
|
// 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);
|
int result = eval.evaluateCreature(c);
|
||||||
return result;
|
return result;
|
||||||
} else if (c.isLand()) {
|
} else if (c.isLand()) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import forge.ai.simulation.GameStateEvaluator.Score;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class SimulationController {
|
public class SimulationController {
|
||||||
private static int MAX_DEPTH = 5;
|
private static int MAX_DEPTH = 2;
|
||||||
|
|
||||||
private int recursionDepth;
|
private int recursionDepth;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user