AI: improved combat support:

* added support to attack battle permanents (#10246);
* fixed that AI was able to attack multiple targets by single creature (#7434);
* added docs;
* added todos with another bugs or possible problems with AI combat;
This commit is contained in:
Oleg Agafonov 2024-01-19 23:37:35 +04:00
parent 6858d43547
commit e4157fefb8
16 changed files with 239 additions and 89 deletions

View file

@ -43,7 +43,7 @@ import java.util.stream.Collectors;
/**
* AI: server side bot with game simulations (mad bot, part of implementation)
*
* @author nantuko
* @author nantuko, JayDi85
*/
public class ComputerPlayer6 extends ComputerPlayer {
@ -887,7 +887,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) {
Player attackingPlayer = game.getPlayer(activePlayerId);
// check alpha strike first (all in attack to kill)
// check alpha strike first (all in attack to kill a player)
for (UUID defenderId : game.getOpponents(playerId)) {
Player defender = game.getPlayer(defenderId);
if (!defender.isInGame()) {
@ -908,7 +908,9 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
}
// check all other actions
// TODO: add game simulations here to find best attackers/blockers combination
// find safe attackers (can't be killed by blockers)
for (UUID defenderId : game.getOpponents(playerId)) {
Player defender = game.getPlayer(defenderId);
if (!defender.isInGame()) {
@ -983,37 +985,64 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
}
// now we have a list of all attackers that can safely attack:
// first check to see if any Planeswalkers can be killed
// find possible target for attack (priority: planeswalker -> battle -> player)
int totalPowerOfAttackers = 0;
int loyaltyCounters = 0;
for (Permanent planeswalker : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, defender.getId(), game)) {
if (planeswalker != null) {
loyaltyCounters = planeswalker.getCounters(game).getCount(CounterType.LOYALTY);
// verify the attackers can kill the planeswalker, otherwise attack the player
for (Permanent attacker : attackersToCheck) {
totalPowerOfAttackers += attacker.getPower().getValue();
int usedPowerOfAttackers = 0;
for (Permanent attacker : attackersToCheck) {
totalPowerOfAttackers += attacker.getPower().getValue();
}
// TRY ATTACK PLANESWALKER + BATTLE
List<Permanent> possiblePermanentDefenders = new ArrayList<>();
// planeswalker first priority
game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, activePlayerId, game)
.stream()
.filter(p -> p.canBeAttacked(null, defenderId, game))
.forEach(possiblePermanentDefenders::add);
// battle second priority
game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_BATTLE, activePlayerId, game)
.stream()
.filter(p -> p.canBeAttacked(null, defenderId, game))
.forEach(possiblePermanentDefenders::add);
for (Permanent permanentDefender : possiblePermanentDefenders) {
if (usedPowerOfAttackers >= totalPowerOfAttackers) {
break;
}
int currentCounters;
if (permanentDefender.isPlaneswalker(game)) {
currentCounters = permanentDefender.getCounters(game).getCount(CounterType.LOYALTY);
} else if (permanentDefender.isBattle(game)) {
currentCounters = permanentDefender.getCounters(game).getCount(CounterType.DEFENSE);
} else {
// impossible error (SBA must remove all planeswalkers/battles with 0 counters before declare attackers)
throw new IllegalStateException("AI: can't find counters for defending permanent " + permanentDefender.getName(), new Throwable());
}
// attack anyway (for kill or damage)
// TODO: add attackers optimization here (1 powerfull + min number of additional permanents,
// current code uses random/etb order)
for (Permanent attackingPermanent : attackersToCheck) {
if (attackingPermanent.isAttacking()) {
// already used for another target
continue;
}
if (totalPowerOfAttackers < loyaltyCounters) {
attackingPlayer.declareAttacker(attackingPermanent.getId(), permanentDefender.getId(), game, true);
currentCounters -= attackingPermanent.getPower().getValue();
usedPowerOfAttackers += attackingPermanent.getPower().getValue();
if (currentCounters <= 0) {
break;
}
// kill the Planeswalker
for (Permanent attacker : attackersToCheck) {
loyaltyCounters -= attacker.getPower().getValue();
attackingPlayer.declareAttacker(attacker.getId(), planeswalker.getId(), game, true);
if (loyaltyCounters <= 0) {
break;
}
}
}
}
// TRY ATTACK PLAYER
// any remaining attackers go for the player
for (Permanent attackingPermanent : attackersToCheck) {
// if not already attacking a Planeswalker...
if (!attackingPermanent.isAttacking()) {
attackingPlayer.declareAttacker(attackingPermanent.getId(), defenderId, game, true);
if (attackingPermanent.isAttacking()) {
continue;
}
attackingPlayer.declareAttacker(attackingPermanent.getId(), defenderId, game, true);
}
}
}

View file

@ -44,7 +44,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
private boolean priorityPlay(Game game) {
if (lastLoggedTurn != game.getTurnNum()) {
lastLoggedTurn = game.getTurnNum();
logger.info("======================= Turn: " + game.getTurnNum() + " [" + game.getPlayer(game.getActivePlayerId()).getName() + "] =========================================");
logger.info("======================= Turn: " + game.getState().toString() + " [" + game.getPlayer(game.getActivePlayerId()).getName() + "] =========================================");
}
logState(game);
logger.debug("Priority -- Step: " + (game.getTurnStepType() + " ").substring(0, 25) + " ActivePlayer-" + game.getPlayer(game.getActivePlayerId()).getName() + " PriorityPlayer-" + name);

View file

@ -198,9 +198,13 @@ public final class CombatUtil {
return blockers;
}
/**
* @deprecated TODO: unused, can be deleted?
*/
public static SurviveInfo willItSurvive(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
Game sim = game.copy();
// TODO: bugged, miss combat.clear code (possible bugs - wrong blocker declare by AI on multiple options?)
Combat combat = sim.getCombat();
combat.setAttacker(attackingPlayerId);
combat.setDefenders(sim);
@ -232,40 +236,6 @@ public final class CombatUtil {
return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), !sim.getBattlefield().containsPermanent(blocker.getId()));
}
public static SurviveInfo getCombatInfo(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker) {
Game sim = game.copy();
Combat combat = sim.getCombat();
combat.setAttacker(attackingPlayerId);
combat.setDefenders(sim);
UUID defenderId = sim.getCombat().getDefenders().iterator().next();
boolean triggered = false;
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defendingPlayerId, defendingPlayerId));
sim.checkStateAndTriggered();
while (!sim.getStack().isEmpty()) {
triggered = true;
sim.getStack().resolve(sim);
sim.applyEffects();
}
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
simulateStep(sim, new CombatDamageStep(true));
simulateStep(sim, new CombatDamageStep(false));
simulateStep(sim, new EndOfCombatStep());
// The following commented out call produces random freezes.
//sim.checkStateAndTriggered();
while (!sim.getStack().isEmpty()) {
triggered = true;
sim.getStack().resolve(sim);
sim.applyEffects();
}
return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), false, sim.getPlayer(defenderId), triggered);
}
protected static void simulateStep(Game game, Step step) {
game.getPhase().setStep(step);
if (!step.skipStep(game, game.getActivePlayerId())) {
@ -339,6 +309,7 @@ public final class CombatUtil {
Game sim = game.copy();
// TODO: bugged, miss combat.clear code (possible bugs - wrong blocker declare by AI on multiple options?)
Combat combat = sim.getCombat();
combat.setAttacker(attackingPlayerId);
combat.setDefenders(sim);