* Choice of Damnations - improved AI support, fixed rollback error in AI games;

* Pain's Reward - improved AI support, fixed rollback error in AI games;
* Volcano Hellion - improved AI support, fixed rollback error in AI games;
This commit is contained in:
Oleg Agafonov 2021-02-06 12:48:53 +04:00
parent ac98a3a31a
commit f692a1f097
5 changed files with 79 additions and 35 deletions

View file

@ -50,10 +50,7 @@ import mage.players.net.UserData;
import mage.players.net.UserGroup; import mage.players.net.UserGroup;
import mage.target.*; import mage.target.*;
import mage.target.common.*; import mage.target.common.*;
import mage.util.Copier; import mage.util.*;
import mage.util.RandomUtil;
import mage.util.TournamentUtil;
import mage.util.TreeNode;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.IOException; import java.io.IOException;
@ -1666,8 +1663,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
@Override @Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) { public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) {
log.debug("announceXCost"); log.debug("announceXCost");
//TODO: improve this int value = RandomUtil.nextInt(CardUtil.overflowInc(max, 1));
int value = RandomUtil.nextInt(max + 1);
if (value < min) { if (value < min) {
value = min; value = min;
} }
@ -1964,9 +1960,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
if (message.startsWith("Assign damage to ")) { if (message.startsWith("Assign damage to ")) {
return min; return min;
} }
//TODO: improve this
if (min < max && min == 0) { if (min < max && min == 0) {
return RandomUtil.nextInt(max + 1); return RandomUtil.nextInt(CardUtil.overflowInc(max, 1));
} }
return min; return min;
} }

View file

@ -1,14 +1,12 @@
package mage.cards.c; package mage.cards.c;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.game.Game; import mage.game.Game;
@ -18,14 +16,15 @@ import mage.target.Target;
import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class ChoiceOfDamnations extends CardImpl { public final class ChoiceOfDamnations extends CardImpl {
public ChoiceOfDamnations(UUID ownerId, CardSetInfo setInfo) { public ChoiceOfDamnations(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{5}{B}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{5}{B}");
this.subtype.add(SubType.ARCANE); this.subtype.add(SubType.ARCANE);
// Target opponent chooses a number. You may have that player lose that much life. If you don't, that player sacrifices all but that many permanents. // Target opponent chooses a number. You may have that player lose that much life. If you don't, that player sacrifices all but that many permanents.
@ -63,13 +62,40 @@ class ChoiceOfDamnationsEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source));
if (targetPlayer != null) { if (targetPlayer != null) {
int amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", game); int numberPermanents = game.getState().getBattlefield().countAll(new FilterPermanent(), targetPlayer.getId(), game);
// AI hint
int amount;
if (!targetPlayer.isHuman() && !targetPlayer.isTestMode()) {
// AI as defender
int safeLifeToLost = Math.max(0, targetPlayer.getLife() / 2);
amount = Math.min(numberPermanents, safeLifeToLost);
} else {
// Human must choose
amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", game);
}
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller != null) {
if (controller.chooseUse(outcome, "Shall " + targetPlayer.getLogName() + " lose " + amount + " life?", source, game)) {
// AI hint
boolean chooseLoseLife;
if (!targetPlayer.isHuman() && !targetPlayer.isTestMode()) {
// AI as attacker
chooseLoseLife = (numberPermanents == 0 || amount <= numberPermanents || targetPlayer.getLife() < amount);
} else {
// Human must choose
chooseLoseLife = controller.chooseUse(outcome, "Shall " + targetPlayer.getLogName() + " lose " + amount + " life?", source, game);
}
if (chooseLoseLife) {
targetPlayer.loseLife(amount, game, source, false); targetPlayer.loseLife(amount, game, source, false);
} else { } else {
int numberPermanents = game.getState().getBattlefield().countAll(new FilterPermanent(), targetPlayer.getId(), game); // rules:
// If the opponent must sacrifice all but a number of permanents, that opponent chooses that many
// permanents and then sacrifices the rest. If the number chosen is greater than the number of
// permanents the opponent controls, the player sacrifices nothing.
// (2005-06-01)
if (numberPermanents > amount) { if (numberPermanents > amount) {
int numberToSacrifice = numberPermanents - amount; int numberToSacrifice = numberPermanents - amount;
Target target = new TargetControlledPermanent(numberToSacrifice, numberToSacrifice, new FilterControlledPermanent("permanent you control to sacrifice"), false); Target target = new TargetControlledPermanent(numberToSacrifice, numberToSacrifice, new FilterControlledPermanent("permanent you control to sacrifice"), false);

View file

@ -1,8 +1,5 @@
package mage.cards.p; package mage.cards.p;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -13,15 +10,16 @@ import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.players.PlayerList; import mage.players.PlayerList;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class PainsReward extends CardImpl { public final class PainsReward extends CardImpl {
public PainsReward(UUID ownerId, CardSetInfo setInfo) { public PainsReward(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}");
// Each player may bid life. You start the bidding with a bid of any number. In turn order, each player may top the high bid. The bidding ends if the high bid stands. The high bidder loses life equal to the high bid and draws four cards. // Each player may bid life. You start the bidding with a bid of any number. In turn order, each player may top the high bid. The bidding ends if the high bid stands. The high bidder loses life equal to the high bid and draws four cards.
this.getSpellAbility().addEffect(new PainsRewardEffect()); this.getSpellAbility().addEffect(new PainsRewardEffect());
@ -62,14 +60,19 @@ class PainsRewardEffect extends OneShotEffect {
playerList.setCurrent(controller.getId()); playerList.setCurrent(controller.getId());
Player winner = game.getPlayer(controller.getId()); Player winner = game.getPlayer(controller.getId());
int highBid = controller.getAmount(0, Integer.MAX_VALUE, "Choose amount of life to bid", game); int highBid = chooseLifeAmountToBid(controller, -1, game); // -1 for start with 0 min big
game.informPlayers(winner.getLogName() + " has bet " + highBid + " lifes"); game.informPlayers(winner.getLogName() + " has bet " + highBid + " lifes");
Player currentPlayer = playerList.getNextInRange(controller, game); Player currentPlayer = playerList.getNextInRange(controller, game);
while (currentPlayer != null && !Objects.equals(currentPlayer, winner)) { while (currentPlayer != null && !Objects.equals(currentPlayer, winner)) {
String text = winner.getLogName() + " has bet " + highBid + " life" + (highBid > 1 ? "s" : "") + ". Top the bid?"; String text = winner.getLogName() + " has bet " + highBid + " life" + (highBid > 1 ? "s" : "") + ". Top the bid?";
if (currentPlayer.chooseUse(Outcome.Detriment, text, source, game)) {
int newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose amount of life to bid", game); // AI hint
int safeLifeToLost = Math.min(6, currentPlayer.getLife() / 2);
Outcome aiOutcome = (highBid + 1 <= safeLifeToLost) ? Outcome.Benefit : Outcome.Detriment;
if (currentPlayer.chooseUse(aiOutcome, text, source, game)) {
int newBid = chooseLifeAmountToBid(currentPlayer, highBid, game);
if (newBid > highBid) { if (newBid > highBid) {
highBid = newBid; highBid = newBid;
winner = currentPlayer; winner = currentPlayer;
@ -86,4 +89,16 @@ class PainsRewardEffect extends OneShotEffect {
} }
return false; return false;
} }
private int chooseLifeAmountToBid(Player player, int currentBig, Game game) {
int newBid;
if (!player.isHuman() && !player.isTestMode()) {
// AI choose
newBid = currentBig + 1;
} else {
// Human choose
newBid = player.getAmount(currentBig + 1, Integer.MAX_VALUE, "Choose amount of life to bid", game);
}
return newBid;
}
} }

View file

@ -1,7 +1,5 @@
package mage.cards.v; package mage.cards.v;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@ -11,15 +9,16 @@ import mage.abilities.keyword.EchoAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
*
* @author jeffwadsworth * @author jeffwadsworth
*/ */
public final class VolcanoHellion extends CardImpl { public final class VolcanoHellion extends CardImpl {
@ -54,7 +53,7 @@ public final class VolcanoHellion extends CardImpl {
class VolcanoHellionEffect extends OneShotEffect { class VolcanoHellionEffect extends OneShotEffect {
public VolcanoHellionEffect() { public VolcanoHellionEffect() {
super(Outcome.AIDontUseIt); super(Outcome.Damage);
this.staticText = "it deals an amount of damage of your choice to you and target creature. The damage can't be prevented"; this.staticText = "it deals an amount of damage of your choice to you and target creature. The damage can't be prevented";
} }
@ -72,7 +71,20 @@ class VolcanoHellionEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
Permanent permanent = game.getPermanent(source.getFirstTarget()); Permanent permanent = game.getPermanent(source.getFirstTarget());
if (controller != null) { if (controller != null) {
int amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", game); int amount;
if (!controller.isHuman() && !controller.isTestMode()) {
// AI hint: have much life and can destroy target permanent
int safeLifeToLost = Math.min(6, controller.getLife() / 2);
if (permanent != null && permanent.getToughness().getValue() <= safeLifeToLost) {
amount = permanent.getToughness().getValue();
} else {
amount = 0;
}
} else {
//Human choose
amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", game);
}
if (amount > 0) { if (amount > 0) {
controller.damage(amount, source.getSourceId(), source, game, false, false); controller.damage(amount, source.getSourceId(), source, game, false, false);
if (permanent != null) { if (permanent != null) {

View file

@ -46,10 +46,6 @@ public class ModalDoubleFacesCardsInCommanderTest extends CardTestCommanderDuelB
addCard(Zone.LIBRARY, playerA, "Forest"); addCard(Zone.LIBRARY, playerA, "Forest");
addCard(Zone.LIBRARY, playerA, "Grizzly Bears"); addCard(Zone.LIBRARY, playerA, "Grizzly Bears");
addCard(Zone.LIBRARY, playerA, "Forest"); addCard(Zone.LIBRARY, playerA, "Forest");
//
// Exile target artifact or enchantment.
addCard(Zone.HAND, playerB, "Ironwright's Cleansing"); // {2}{W}
addCard(Zone.BATTLEFIELD, playerB, "Plains");
// prepare mdf // prepare mdf
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Prismatic Bridge");