mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
- refactor: migrated AI's target amount code to shared selection logic; - ai: fixed game freezes on some use cases; - tests: added AI's testable dialogs for target amount; - tests: improved load tests result table, added game cycles stats; - Dwarven Catapult - fixed game error on usage;
This commit is contained in:
parent
8e7a7e9fc6
commit
f3e18e245f
23 changed files with 502 additions and 390 deletions
|
|
@ -41,6 +41,15 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
|
||||||
this.targetsMax = targetsMax;
|
this.targetsMax = targetsMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ChooseAmountTestableDialog aiMustChoose(boolean resStatus, int targetsCount) {
|
||||||
|
// TODO: AI use default distribution, imrove someday
|
||||||
|
TargetTestableResult res = ((TargetTestableResult) this.getResult());
|
||||||
|
res.aiAssertEnabled = true;
|
||||||
|
res.aiAssertResStatus = resStatus;
|
||||||
|
res.aiAssertTargetsCount = targetsCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||||
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
|
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
|
||||||
|
|
@ -65,53 +74,55 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
|
||||||
|
|
||||||
List<Boolean> isYous = Arrays.asList(false, true);
|
List<Boolean> isYous = Arrays.asList(false, true);
|
||||||
|
|
||||||
|
// current AI will choose 1 target and assign all values to it (except with outcome.Damage)
|
||||||
|
// TODO: add use cases for damage effects?
|
||||||
for (boolean isYou : isYous) {
|
for (boolean isYou : isYous) {
|
||||||
// up to
|
// up to
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5).aiMustChoose(false, 0));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5).aiMustChoose(true, 1));
|
||||||
|
|
||||||
// need target
|
// need target
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3).aiMustChoose(false, 0));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5).aiMustChoose(false, 0));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5).aiMustChoose(true, 1));
|
||||||
//
|
//
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3).aiMustChoose(true, 1));
|
||||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5));
|
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5).aiMustChoose(true, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ public class GameView implements Serializable {
|
||||||
// TODO: implement and support in admin tools
|
// TODO: implement and support in admin tools
|
||||||
private int totalErrorsCount;
|
private int totalErrorsCount;
|
||||||
private int totalEffectsCount;
|
private int totalEffectsCount;
|
||||||
|
private int gameCycle;
|
||||||
|
|
||||||
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
|
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
|
||||||
Player createdForPlayer = null;
|
Player createdForPlayer = null;
|
||||||
|
|
@ -214,6 +215,7 @@ public class GameView implements Serializable {
|
||||||
this.rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
|
this.rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
|
||||||
this.totalErrorsCount = game.getTotalErrorsCount();
|
this.totalErrorsCount = game.getTotalErrorsCount();
|
||||||
this.totalEffectsCount = game.getTotalEffectsCount();
|
this.totalEffectsCount = game.getTotalEffectsCount();
|
||||||
|
this.gameCycle = game.getState().getApplyEffectsCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPaid(UUID uuid, StackAbility stackAbility) {
|
private void checkPaid(UUID uuid, StackAbility stackAbility) {
|
||||||
|
|
@ -358,4 +360,8 @@ public class GameView implements Serializable {
|
||||||
public int getTotalEffectsCount() {
|
public int getTotalEffectsCount() {
|
||||||
return this.totalEffectsCount;
|
return this.totalEffectsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGameCycle() {
|
||||||
|
return this.gameCycle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
if (effect != null
|
if (effect != null
|
||||||
&& stackObject.getControllerId().equals(playerId)) {
|
&& stackObject.getControllerId().equals(playerId)) {
|
||||||
Target target = effect.getTarget();
|
Target target = effect.getTarget();
|
||||||
if (!target.isChoiceCompleted(game)) {
|
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
|
||||||
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
|
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
|
||||||
Game sim = game.createSimulationForAI();
|
Game sim = game.createSimulationForAI();
|
||||||
StackAbility newAbility = (StackAbility) stackObject.copy();
|
StackAbility newAbility = (StackAbility) stackObject.copy();
|
||||||
|
|
@ -849,10 +849,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
if (targets.isEmpty()) {
|
if (targets.isEmpty()) {
|
||||||
return super.chooseTarget(outcome, cards, target, source, game);
|
return super.chooseTarget(outcome, cards, target, source, game);
|
||||||
}
|
}
|
||||||
if (!target.isChoiceCompleted(game)) {
|
|
||||||
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||||
|
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
for (UUID targetId : targets) {
|
for (UUID targetId : targets) {
|
||||||
target.addTarget(targetId, source, game);
|
target.addTarget(targetId, source, game);
|
||||||
if (target.isChoiceCompleted(game)) {
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
targets.clear();
|
targets.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -867,10 +869,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
if (targets.isEmpty()) {
|
if (targets.isEmpty()) {
|
||||||
return super.choose(outcome, cards, target, source, game);
|
return super.choose(outcome, cards, target, source, game);
|
||||||
}
|
}
|
||||||
if (!target.isChoiceCompleted(game)) {
|
|
||||||
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||||
|
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
for (UUID targetId : targets) {
|
for (UUID targetId : targets) {
|
||||||
target.add(targetId, game);
|
target.add(targetId, game);
|
||||||
if (target.isChoiceCompleted(game)) {
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
targets.clear();
|
targets.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ import mage.cards.repository.CardInfo;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
import mage.choices.Choice;
|
import mage.choices.Choice;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.counters.CounterType;
|
|
||||||
import mage.filter.FilterPermanent;
|
import mage.filter.FilterPermanent;
|
||||||
import mage.filter.StaticFilters;
|
|
||||||
import mage.filter.common.FilterCreatureForCombatBlock;
|
import mage.filter.common.FilterCreatureForCombatBlock;
|
||||||
import mage.filter.common.FilterLandCard;
|
import mage.filter.common.FilterLandCard;
|
||||||
import mage.filter.common.FilterNonlandCard;
|
import mage.filter.common.FilterNonlandCard;
|
||||||
|
|
@ -158,16 +156,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// controller hints:
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||||
// - target.getTargetController(), this.getId() -- player that must makes choices (must be same with this.getId)
|
|
||||||
// - target.getAbilityController(), abilityControllerId -- affected player/controller for all actions/filters
|
|
||||||
// - affected controller can be different from target controller (another player makes choices for controller)
|
|
||||||
// sometimes a target selection can be made from a player that does not control the ability
|
|
||||||
UUID abilityControllerId = playerId;
|
|
||||||
if (target.getTargetController() != null
|
|
||||||
&& target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing to choose, e.g. X=0
|
// nothing to choose, e.g. X=0
|
||||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
|
@ -175,13 +164,11 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// default logic for any targets
|
// default logic for any targets
|
||||||
boolean isAddedSomething = false;
|
|
||||||
PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game);
|
PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game);
|
||||||
possibleTargetsSelector.findNewTargets(fromCards);
|
possibleTargetsSelector.findNewTargets(fromCards);
|
||||||
// good targets -- choose as much as possible
|
// good targets -- choose as much as possible
|
||||||
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||||
target.add(item.getId(), game);
|
target.add(item.getId(), game);
|
||||||
isAddedSomething = true;
|
|
||||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -192,9 +179,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
target.add(item.getId(), game);
|
target.add(item.getId(), game);
|
||||||
isAddedSomething = true;
|
|
||||||
}
|
}
|
||||||
return isAddedSomething;
|
|
||||||
|
return target.isChosen(game) && !target.getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -273,147 +260,154 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||||
// TODO: make same code for chooseTarget (without filter and target type dependence)
|
|
||||||
|
|
||||||
|
// nothing to choose, e.g. X=0
|
||||||
|
target.prepareAmount(source, game);
|
||||||
if (target.getAmountRemaining() <= 0) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||||
UUID sourceId = source != null ? source.getSourceId() : null;
|
|
||||||
|
|
||||||
// sometimes a target selection can be made from a player that does not control the ability
|
|
||||||
UUID abilityControllerId = playerId;
|
|
||||||
if (target.getTargetController() != null
|
|
||||||
&& target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
// process multiple opponents by random
|
|
||||||
List<UUID> opponents = new ArrayList<>(game.getOpponents(getId(), true));
|
|
||||||
Collections.shuffle(opponents);
|
|
||||||
|
|
||||||
List<Permanent> targets;
|
|
||||||
|
|
||||||
// ONE KILL PRIORITY: player -> planeswalker -> creature
|
|
||||||
if (outcome == Outcome.Damage) {
|
|
||||||
// player kill
|
|
||||||
for (UUID opponentId : opponents) {
|
|
||||||
Player opponent = game.getPlayer(opponentId);
|
|
||||||
if (opponent != null
|
|
||||||
&& target.canTarget(abilityControllerId, opponentId, source, game)
|
|
||||||
&& opponent.getLife() <= target.getAmountRemaining()) {
|
|
||||||
return tryAddTarget(target, opponentId, opponent.getLife(), source, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// permanents kill
|
|
||||||
for (UUID opponentId : opponents) {
|
|
||||||
targets = threats(opponentId, source, StaticFilters.FILTER_PERMANENT_CREATURE_OR_PLANESWALKER_A, game, target.getTargets());
|
|
||||||
|
|
||||||
// planeswalker kill
|
|
||||||
for (Permanent permanent : targets) {
|
|
||||||
if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
|
||||||
int loy = permanent.getCounters(game).getCount(CounterType.LOYALTY);
|
|
||||||
if (loy <= target.getAmountRemaining()) {
|
|
||||||
return tryAddTarget(target, permanent.getId(), loy, source, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// creature kill
|
|
||||||
for (Permanent permanent : targets) {
|
|
||||||
if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
|
||||||
if (permanent.getToughness().getValue() <= target.getAmountRemaining()) {
|
|
||||||
return tryAddTarget(target, permanent.getId(), permanent.getToughness().getValue(), source, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NORMAL PRIORITY: planeswalker -> player -> creature
|
|
||||||
// own permanents will be checked multiple times... that's ok
|
|
||||||
for (UUID opponentId : opponents) {
|
|
||||||
if (outcome.isGood()) {
|
|
||||||
targets = threats(getId(), source, StaticFilters.FILTER_PERMANENT, game, target.getTargets());
|
|
||||||
} else {
|
|
||||||
targets = threats(opponentId, source, StaticFilters.FILTER_PERMANENT, game, target.getTargets());
|
|
||||||
}
|
|
||||||
|
|
||||||
// planeswalkers
|
|
||||||
for (Permanent permanent : targets) {
|
|
||||||
if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
|
||||||
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// players
|
|
||||||
if (outcome.isGood() && target.canTarget(abilityControllerId, getId(), source, game)) {
|
|
||||||
return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game);
|
|
||||||
}
|
|
||||||
if (!outcome.isGood() && target.canTarget(abilityControllerId, opponentId, source, game)) {
|
|
||||||
return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
// creature
|
|
||||||
for (Permanent permanent : targets) {
|
|
||||||
if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
|
||||||
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BAD PRIORITY, e.g. need bad target on yourself or good target on opponent
|
|
||||||
// priority: creature (non killable, killable) -> planeswalker -> player
|
|
||||||
if (!target.isRequired(sourceId, game)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (UUID opponentId : opponents) {
|
|
||||||
if (!outcome.isGood()) {
|
|
||||||
// bad on yourself, uses the weakest targets
|
|
||||||
targets = threats(getId(), source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false);
|
|
||||||
} else {
|
|
||||||
targets = threats(opponentId, source, StaticFilters.FILTER_PERMANENT, game, target.getTargets(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// creatures - non killable (TODO: add extra skill checks like indestructible)
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||||
for (Permanent permanent : targets) {
|
|
||||||
if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
// nothing to choose, e.g. X=0
|
||||||
int safeDamage = Math.min(permanent.getToughness().getValue() - 1, target.getAmountRemaining());
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
if (safeDamage > 0) {
|
return false;
|
||||||
return tryAddTarget(target, permanent.getId(), safeDamage, source, game);
|
}
|
||||||
|
|
||||||
|
PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game);
|
||||||
|
possibleTargetsSelector.findNewTargets(null);
|
||||||
|
|
||||||
|
// nothing to choose, e.g. no valid targets
|
||||||
|
if (!possibleTargetsSelector.hasAnyTargets()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KILL PRIORITY
|
||||||
|
if (outcome == Outcome.Damage) {
|
||||||
|
// opponent first
|
||||||
|
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||||
|
if (target.getAmountRemaining() <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId()) || !(item instanceof Player)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||||
|
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
|
||||||
|
target.addTarget(item.getId(), leftLife, source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// creatures - all
|
// opponent's creatures second
|
||||||
for (Permanent permanent : targets) {
|
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||||
if (permanent.isCreature(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId()) || (item instanceof Player)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||||
|
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
|
||||||
|
target.addTarget(item.getId(), leftLife, source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// planeswalkers
|
// opponent's any
|
||||||
for (Permanent permanent : targets) {
|
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||||
if (permanent.isPlaneswalker(game) && target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return tryAddTarget(target, permanent.getId(), target.getAmountRemaining(), source, game);
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// own - non-killable
|
||||||
|
for (MageItem item : possibleTargetsSelector.getBadTargets()) {
|
||||||
|
if (target.getAmountRemaining() <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// stop as fast as possible on bad outcome
|
||||||
|
if (target.isChosen(game)) {
|
||||||
|
return !target.getTargets().isEmpty();
|
||||||
|
}
|
||||||
|
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
|
||||||
|
if (leftLife > 1) {
|
||||||
|
target.addTarget(item.getId(), Math.min(leftLife - 1, target.getAmountRemaining()), source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// own - any
|
||||||
|
for (MageItem item : possibleTargetsSelector.getBadTargets()) {
|
||||||
|
if (target.getAmountRemaining() <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// stop as fast as possible on bad outcome
|
||||||
|
if (target.isChosen(game)) {
|
||||||
|
return !target.getTargets().isEmpty();
|
||||||
|
}
|
||||||
|
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.isChosen(game);
|
||||||
}
|
}
|
||||||
// players
|
|
||||||
for (UUID opponentId : opponents) {
|
// non-damage effect like counters - give all to first valid item
|
||||||
if (target.canTarget(abilityControllerId, getId(), source, game)) {
|
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
|
||||||
// on itself
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return tryAddTarget(target, getId(), target.getAmountRemaining(), source, game);
|
break;
|
||||||
} else if (target.canTarget(abilityControllerId, opponentId, source, game)) {
|
}
|
||||||
// on opponent
|
if (target.contains(item.getId())) {
|
||||||
return tryAddTarget(target, opponentId, target.getAmountRemaining(), source, game);
|
continue;
|
||||||
|
}
|
||||||
|
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MageItem item : possibleTargetsSelector.getBadTargets()) {
|
||||||
|
if (target.getAmountRemaining() <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target.contains(item.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// stop as fast as possible on bad outcome
|
||||||
|
if (target.isChosen(game)) {
|
||||||
|
return !target.getTargets().isEmpty();
|
||||||
|
}
|
||||||
|
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
|
||||||
|
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's ok on no targets available
|
return target.isChosen(game) && !target.getTargets().isEmpty();
|
||||||
log.warn("No proper AI target handling or can't find permanents/cards to target: " + target.getClass().getName());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1451,9 +1445,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sometimes a target selection can be made from a player that does not control the ability
|
// sometimes a target selection can be made from a player that does not control the ability
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = this.getId();
|
||||||
if (target != null && target.getAbilityController() != null) {
|
if (target != null) {
|
||||||
abilityControllerId = target.getAbilityController();
|
abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Card bestCard = null;
|
Card bestCard = null;
|
||||||
|
|
@ -1490,9 +1484,9 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sometimes a target selection can be made from a player that does not control the ability
|
// sometimes a target selection can be made from a player that does not control the ability
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = this.getId();
|
||||||
if (target != null && target.getAbilityController() != null) {
|
if (target != null) {
|
||||||
abilityControllerId = target.getAbilityController();
|
abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Card worstCard = null;
|
Card worstCard = null;
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,7 @@ public class PossibleTargetsComparator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getScoreFromLife(MageItem item) {
|
public static int getLifeForDamage(MageItem item, Game game) {
|
||||||
// TODO: replace permanent/card life by battlefield score?
|
|
||||||
int res = 0;
|
int res = 0;
|
||||||
if (item instanceof Player) {
|
if (item instanceof Player) {
|
||||||
res = ((Player) item).getLife();
|
res = ((Player) item).getLife();
|
||||||
|
|
@ -71,12 +70,16 @@ public class PossibleTargetsComparator {
|
||||||
}
|
}
|
||||||
res = Math.max(0, card.getToughness().getValue() - damage);
|
res = Math.max(0, card.getToughness().getValue() - damage);
|
||||||
}
|
}
|
||||||
// instant
|
|
||||||
if (res == 0) {
|
|
||||||
res = card.getManaValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getScoreFromLife(MageItem item) {
|
||||||
|
// TODO: replace permanent/card life by battlefield score?
|
||||||
|
int res = getLifeForDamage(item, game);
|
||||||
|
if (res == 0 && item instanceof Card) {
|
||||||
|
res = ((Card) item).getManaValue();
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,13 +142,6 @@ public class PossibleTargetsComparator {
|
||||||
.thenComparing(BY_ID);
|
.thenComparing(BY_ID);
|
||||||
public final Comparator<MageItem> ANY_MOST_VALUABLE_LAST = ANY_MOST_VALUABLE_FIRST.reversed();
|
public final Comparator<MageItem> ANY_MOST_VALUABLE_LAST = ANY_MOST_VALUABLE_FIRST.reversed();
|
||||||
|
|
||||||
/**
|
|
||||||
* Default sorting for good effects - put own and biggest items to the top
|
|
||||||
*/
|
|
||||||
public final Comparator<MageItem> MY_MOST_VALUABLE_FIRST = BY_ME
|
|
||||||
.thenComparing(ANY_MOST_VALUABLE_FIRST);
|
|
||||||
public final Comparator<MageItem> MY_MOST_VALUABLE_LAST = MY_MOST_VALUABLE_FIRST.reversed();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorting for discard effects - put the biggest unplayable at the top, lands at the end anyway
|
* Sorting for discard effects - put the biggest unplayable at the top, lands at the end anyway
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -85,13 +85,13 @@ public class PossibleTargetsSelector {
|
||||||
private void sortByMostValuableTargets() {
|
private void sortByMostValuableTargets() {
|
||||||
if (isGoodEffect()) {
|
if (isGoodEffect()) {
|
||||||
// for good effect must choose the biggest objects
|
// for good effect must choose the biggest objects
|
||||||
this.me.sort(comparators.MY_MOST_VALUABLE_FIRST);
|
this.me.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||||
this.opponents.sort(comparators.MY_MOST_VALUABLE_LAST);
|
this.opponents.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||||
this.any.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
this.any.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||||
} else {
|
} else {
|
||||||
// for bad effect must choose the smallest objects
|
// for bad effect must choose the smallest objects
|
||||||
this.me.sort(comparators.MY_MOST_VALUABLE_LAST);
|
this.me.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||||
this.opponents.sort(comparators.MY_MOST_VALUABLE_FIRST);
|
this.opponents.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||||
this.any.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
this.any.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,4 +181,8 @@ public class PossibleTargetsSelector {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasAnyTargets() {
|
||||||
|
return !this.any.isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,10 +19,10 @@ public final class MagicAbility {
|
||||||
put(DoubleStrikeAbility.getInstance().getRule(), 100);
|
put(DoubleStrikeAbility.getInstance().getRule(), 100);
|
||||||
put(new ExaltedAbility().getRule(), 10);
|
put(new ExaltedAbility().getRule(), 10);
|
||||||
put(FirstStrikeAbility.getInstance().getRule(), 50);
|
put(FirstStrikeAbility.getInstance().getRule(), 50);
|
||||||
put(FlashAbility.getInstance().getRule(), 0);
|
put(FlashAbility.getInstance().getRule(), 20);
|
||||||
put(FlyingAbility.getInstance().getRule(), 50);
|
put(FlyingAbility.getInstance().getRule(), 50);
|
||||||
put(new ForestwalkAbility().getRule(), 10);
|
put(new ForestwalkAbility().getRule(), 10);
|
||||||
put(HasteAbility.getInstance().getRule(), 0);
|
put(HasteAbility.getInstance().getRule(), 20);
|
||||||
put(IndestructibleAbility.getInstance().getRule(), 150);
|
put(IndestructibleAbility.getInstance().getRule(), 150);
|
||||||
put(InfectAbility.getInstance().getRule(), 60);
|
put(InfectAbility.getInstance().getRule(), 60);
|
||||||
put(IntimidateAbility.getInstance().getRule(), 50);
|
put(IntimidateAbility.getInstance().getRule(), 50);
|
||||||
|
|
@ -47,7 +47,7 @@ public final class MagicAbility {
|
||||||
if (!scores.containsKey(ability.getRule())) {
|
if (!scores.containsKey(ability.getRule())) {
|
||||||
//System.err.println("Couldn't find ability score: " + ability.getClass().getSimpleName() + " - " + ability.toString());
|
//System.err.println("Couldn't find ability score: " + ability.getClass().getSimpleName() + " - " + ability.toString());
|
||||||
//TODO: add handling protection from ..., levelup, kicker, etc. abilities
|
//TODO: add handling protection from ..., levelup, kicker, etc. abilities
|
||||||
return 0;
|
return 2; // more abilities - more score in any use cases
|
||||||
}
|
}
|
||||||
return scores.get(ability.getRule());
|
return scores.get(ability.getRule());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -288,9 +288,15 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||||
|
|
||||||
|
// nothing to choose
|
||||||
|
target.prepareAmount(source, game);
|
||||||
if (target.getAmountRemaining() <= 0) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
|
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
|
||||||
if (possibleTargets.isEmpty()) {
|
if (possibleTargets.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -690,12 +690,8 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose one or multiple permanents
|
// choose one or multiple targets
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null
|
|
||||||
&& target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
options = new HashMap<>();
|
options = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
@ -782,11 +778,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose one or multiple targets
|
// choose one or multiple targets
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Serializable> options = new HashMap<>();
|
Map<String, Serializable> options = new HashMap<>();
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
|
|
@ -869,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null
|
|
||||||
&& target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
} else {
|
|
||||||
abilityControllerId = playerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
|
|
||||||
|
|
@ -966,13 +952,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null
|
|
||||||
&& target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
} else {
|
|
||||||
abilityControllerId = playerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source);
|
boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source);
|
||||||
|
|
@ -1042,18 +1022,20 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nothing to choose
|
||||||
|
target.prepareAmount(source, game);
|
||||||
if (target.getAmountRemaining() <= 0) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
int amountTotal = target.getAmountTotal(game, source);
|
int amountTotal = target.getAmountTotal(game, source);
|
||||||
if (amountTotal == 0) {
|
if (amountTotal == 0) {
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,12 @@ import mage.util.CardUtil;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author MarcoMarin
|
* @author MarcoMarin
|
||||||
*/
|
*/
|
||||||
public final class DwarvenCatapult extends CardImpl {
|
public final class DwarvenCatapult extends CardImpl {
|
||||||
|
|
||||||
public DwarvenCatapult(UUID ownerId, CardSetInfo setInfo) {
|
public DwarvenCatapult(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{X}{R}");
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{R}");
|
||||||
|
|
||||||
// Dwarven Catapult deals X damage divided evenly, rounded down, among all creatures target opponent controls.
|
// Dwarven Catapult deals X damage divided evenly, rounded down, among all creatures target opponent controls.
|
||||||
this.getSpellAbility().addTarget(new TargetOpponent());
|
this.getSpellAbility().addTarget(new TargetOpponent());
|
||||||
|
|
@ -55,10 +54,11 @@ class DwarvenCatapultEffect extends OneShotEffect {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
int howMany = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, source.getFirstTarget(), game).size();
|
int howMany = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, source.getFirstTarget(), game).size();
|
||||||
int amount = CardUtil.getSourceCostsTag(game, source, "X", 0)/howMany;
|
if (howMany > 0) {
|
||||||
|
int amount = CardUtil.getSourceCostsTag(game, source, "X", 0) / howMany;
|
||||||
DamageAllControlledTargetEffect dmgEffect = new DamageAllControlledTargetEffect(amount, new FilterCreaturePermanent());
|
return new DamageAllControlledTargetEffect(amount, new FilterCreaturePermanent()).apply(game, source);
|
||||||
return dmgEffect.apply(game, source);
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package mage.cards.i;
|
package mage.cards.i;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -19,7 +18,6 @@ public final class IvoryMask extends CardImpl {
|
||||||
public IvoryMask(UUID ownerId, CardSetInfo setInfo) {
|
public IvoryMask(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}{W}");
|
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}{W}");
|
||||||
|
|
||||||
|
|
||||||
// You have shroud.
|
// You have shroud.
|
||||||
this.addAbility(new SimpleStaticAbility(new GainAbilityControllerEffect(ShroudAbility.getInstance())));
|
this.addAbility(new SimpleStaticAbility(new GainAbilityControllerEffect(ShroudAbility.getInstance())));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ enum NecroticPlagueAdjuster implements TargetAdjuster {
|
||||||
ability.setControllerId(creatureController.getId());
|
ability.setControllerId(creatureController.getId());
|
||||||
ability.getTargets().clear();
|
ability.getTargets().clear();
|
||||||
TargetPermanent target = new TargetOpponentsCreaturePermanent();
|
TargetPermanent target = new TargetOpponentsCreaturePermanent();
|
||||||
|
target.setAbilityController(ability.getControllerId());
|
||||||
target.setTargetController(creatureController.getId());
|
target.setTargetController(creatureController.getId());
|
||||||
ability.getTargets().add(target);
|
ability.getTargets().add(target);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,52 +21,81 @@ import org.mage.test.serverside.base.CardTestPlayerBaseAI;
|
||||||
public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
|
|
||||||
// TODO: enable _target_ tests after computerPlayer.chooseTarget will be reworks like chooseTargetAmount
|
// TODO: enable _target_ tests after computerPlayer.chooseTarget will be reworks like chooseTargetAmount
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
public void test_Target_PriorityDamageToGoodOpponent() {
|
||||||
public void test_target_PriorityKillByBigPT() {
|
|
||||||
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
//
|
//
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
|
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2
|
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability
|
addCard(Zone.BATTLEFIELD, playerA, "Ashcoat Bear", 3); // 2/2 with ability
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3
|
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability
|
|
||||||
|
|
||||||
|
// AI must make damage to opponent
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
|
||||||
|
|
||||||
|
setStrictChooseMode(false);
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
assertLife(playerB, 20 - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Target_PriorityDamageToBadLowestCreature() {
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// You have shroud.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Ivory Mask", 1);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 3); // 2/2
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Memnite", 3); // 1/1
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Ashcoat Bear", 3); // 2/2 with ability
|
||||||
|
|
||||||
|
// AI can't target opponent so target own lowest creature
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
|
||||||
|
|
||||||
|
setStrictChooseMode(false);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
assertLife(playerB, 20);
|
||||||
|
assertPermanentCount(playerA, "Memnite", 3 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Target_PriorityDamageToBiggestCreature() {
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// You have shroud.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Ivory Mask", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Ivory Mask", 1);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3
|
||||||
|
//addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability TODO: add after AI will simulation simple choices too
|
||||||
|
|
||||||
|
// AI must choose biggest creature to kill from opponent - 4/3
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
showBattlefield("as", 1, PhaseStep.PRECOMBAT_MAIN, playerB);
|
||||||
|
|
||||||
|
setStrictChooseMode(false);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
assertLife(playerB, 20);
|
||||||
assertPermanentCount(playerB, "Memnite", 3);
|
assertPermanentCount(playerB, "Memnite", 3);
|
||||||
assertPermanentCount(playerB, "Balduvian Bears", 3);
|
assertPermanentCount(playerB, "Balduvian Bears", 3);
|
||||||
assertPermanentCount(playerB, "Ashcoat Bear", 3);
|
assertPermanentCount(playerB, "Ashcoat Bear", 3);
|
||||||
assertPermanentCount(playerB, "Golden Bear", 3 - 1);
|
assertPermanentCount(playerB, "Golden Bear", 3 - 1);
|
||||||
assertPermanentCount(playerB, "Battering Sliver", 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void test_target_PriorityByKillByLowPT() {
|
|
||||||
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
|
||||||
//
|
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Memnite", 3); // 1/1
|
|
||||||
//addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 3); // 2/2
|
|
||||||
//addCard(Zone.BATTLEFIELD, playerB, "Ashcoat Bear", 3); // 2/2 with ability
|
|
||||||
//addCard(Zone.BATTLEFIELD, playerB, "Golden Bear", 3); // 4/3
|
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 3); // 4/4 with ability
|
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
|
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
|
||||||
execute();
|
|
||||||
|
|
||||||
assertPermanentCount(playerB, "Memnite", 3 - 1);
|
|
||||||
//assertPermanentCount(playerB, "Balduvian Bears", 3);
|
|
||||||
//assertPermanentCount(playerB, "Ashcoat Bear", 3);
|
|
||||||
//assertPermanentCount(playerB, "Golden Bear", 3);
|
|
||||||
assertPermanentCount(playerB, "Battering Sliver", 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -153,7 +182,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
|
|
||||||
// TARGET AMOUNT
|
// TARGET AMOUNT
|
||||||
@Test
|
@Test
|
||||||
public void test_targetAmount_PriorityKillByBigPT() {
|
public void test_TargetAmount_PriorityKillByBigPT() {
|
||||||
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
//
|
//
|
||||||
|
|
@ -176,7 +205,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_targetAmount_PriorityByKillByLowPT() {
|
public void test_TargetAmount_PriorityByKillByLowPT() {
|
||||||
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
//
|
//
|
||||||
|
|
@ -199,7 +228,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_targetAmount_PriorityKillByExtraPoints() {
|
public void test_TargetAmount_PriorityKillByExtraPoints() {
|
||||||
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||||
//
|
//
|
||||||
|
|
@ -222,7 +251,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_targetAmount_NormalCase() {
|
public void test_TargetAmount_NormalCase() {
|
||||||
Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(), new ManaCostsImpl<>("{R}"));
|
Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(), new ManaCostsImpl<>("{R}"));
|
||||||
ability.addTarget(new TargetCreaturePermanentAmount(3, 0, 3));
|
ability.addTarget(new TargetCreaturePermanentAmount(3, 0, 3));
|
||||||
addCustomCardWithAbility("damage 3", playerA, ability);
|
addCustomCardWithAbility("damage 3", playerA, ability);
|
||||||
|
|
@ -247,7 +276,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_targetAmount_BadCase() {
|
public void test_TargetAmount_BadCase() {
|
||||||
// choose targets as enters battlefield (e.g. can't be canceled)
|
// choose targets as enters battlefield (e.g. can't be canceled)
|
||||||
SpellAbility spell = new SpellAbility(new ManaCostsImpl<>("{R}"), "damage 3", Zone.HAND);
|
SpellAbility spell = new SpellAbility(new ManaCostsImpl<>("{R}"), "damage 3", Zone.HAND);
|
||||||
Ability ability = new EntersBattlefieldTriggeredAbility(new DamageMultiEffect());
|
Ability ability = new EntersBattlefieldTriggeredAbility(new DamageMultiEffect());
|
||||||
|
|
@ -263,14 +292,12 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "damage 3");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "damage 3");
|
||||||
|
|
||||||
// must damage x3 Balduvian Bears by -1 to keep alive
|
// up to target is optional, so AI must choose nothing due only bad targets
|
||||||
checkDamage("pt after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 1);
|
|
||||||
// showBattlefield("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
|
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertPermanentCount(playerA, "damage 3", 1);
|
assertLife(playerA, 20);
|
||||||
|
assertLife(playerB, 20);
|
||||||
assertPermanentCount(playerA, "Memnite", 3);
|
assertPermanentCount(playerA, "Memnite", 3);
|
||||||
assertPermanentCount(playerA, "Balduvian Bears", 3);
|
assertPermanentCount(playerA, "Balduvian Bears", 3);
|
||||||
assertPermanentCount(playerA, "Ashcoat Bear", 3);
|
assertPermanentCount(playerA, "Ashcoat Bear", 3);
|
||||||
|
|
@ -280,7 +307,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore // do not enable it in production, only for devs
|
@Ignore // do not enable it in production, only for devs
|
||||||
public void test_targetAmount_Performance() {
|
public void test_TargetAmount_Performance() {
|
||||||
int cardsMultiplier = 3;
|
int cardsMultiplier = 3;
|
||||||
|
|
||||||
Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(), new ManaCostsImpl<>("{R}"));
|
Ability ability = new SimpleActivatedAbility(Zone.ALL, new DamageMultiEffect(), new ManaCostsImpl<>("{R}"));
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,22 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
public class VivienTest extends CardTestPlayerBase {
|
public class VivienTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVivienArkbowRangerAbility1NoTargets() {
|
public void test_Distribute_NoTargets() {
|
||||||
setStrictChooseMode(true);
|
|
||||||
// +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn.
|
// +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn.
|
||||||
// −3: Target creature you control deals damage equal to its power to target creature or planeswalker.
|
// −3: Target creature you control deals damage equal to its power to target creature or planeswalker.
|
||||||
// −5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand.
|
// −5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand.
|
||||||
addCard(Zone.HAND, playerA, "Vivien, Arkbow Ranger"); // Planeswalker {1}{G}{G}{G} - starts with 4 Loyality counters
|
addCard(Zone.HAND, playerA, "Vivien, Arkbow Ranger"); // Planeswalker {1}{G}{G}{G} - starts with 4 Loyality counters
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
||||||
|
|
||||||
|
// You can activate Vivien’s first ability without choosing any target creatures. The counters won’t be
|
||||||
|
// put on anything. This is a change from previous rules regarding distributing counters.
|
||||||
|
// (2019-07-12)
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vivien, Arkbow Ranger", true);
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vivien, Arkbow Ranger", true);
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Distribute");
|
||||||
addTargetAmount(playerA, TestPlayer.TARGET_SKIP); // stop choosing (not targets)
|
addTargetAmount(playerA, TestPlayer.TARGET_SKIP); // stop choosing (not targets)
|
||||||
|
|
||||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Distribute");
|
setStrictChooseMode(true);
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
|
|
@ -36,8 +39,7 @@ public class VivienTest extends CardTestPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVivienArkbowRangerAbilityOnePossibleTargetWithOne() {
|
public void test_Distribute_OneTarget() {
|
||||||
setStrictChooseMode(true);
|
|
||||||
// +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn.
|
// +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn.
|
||||||
// −3: Target creature you control deals damage equal to its power to target creature or planeswalker.
|
// −3: Target creature you control deals damage equal to its power to target creature or planeswalker.
|
||||||
// −5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand.
|
// −5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand.
|
||||||
|
|
@ -49,16 +51,16 @@ public class VivienTest extends CardTestPlayerBase {
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vivien, Arkbow Ranger", true);
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vivien, Arkbow Ranger", true);
|
||||||
|
|
||||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Distribute");
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Distribute");
|
||||||
addTargetAmount(playerA, "Silvercoat Lion", 1);
|
addTargetAmount(playerA, "Silvercoat Lion", 2);
|
||||||
addTargetAmount(playerA, TestPlayer.TARGET_SKIP); // stop choosing (one target)
|
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertPermanentCount(playerA, "Vivien, Arkbow Ranger", 1);
|
assertPermanentCount(playerA, "Vivien, Arkbow Ranger", 1);
|
||||||
assertCounterCount("Vivien, Arkbow Ranger", CounterType.LOYALTY, 5);
|
assertCounterCount("Vivien, Arkbow Ranger", CounterType.LOYALTY, 5);
|
||||||
|
|
||||||
assertPowerToughness(playerB, "Silvercoat Lion", 2 + 1, 2 + 1);
|
assertPowerToughness(playerB, "Silvercoat Lion", 2 + 2, 2 + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore // TODO: enable and fix all failed dialogs
|
|
||||||
public void test_RunAll_AI() {
|
public void test_RunAll_AI() {
|
||||||
// it's impossible to setup 700+ dialogs, so all choices made by AI
|
// it's impossible to setup 700+ dialogs, so all choices made by AI
|
||||||
// current AI uses only simple choices in dialogs, not simulations
|
// current AI uses only simple choices in dialogs, not simulations
|
||||||
|
|
@ -108,7 +107,7 @@ public class TestableDialogsTest extends CardTestPlayerBaseWithAIHelps {
|
||||||
@Test
|
@Test
|
||||||
@Ignore // debug only - run single dialog by reg number
|
@Ignore // debug only - run single dialog by reg number
|
||||||
public void test_RunSingle_Debugging() {
|
public void test_RunSingle_Debugging() {
|
||||||
int needRegNumber = 93;
|
int needRegNumber = 557;
|
||||||
|
|
||||||
prepareCards();
|
prepareCards();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -918,12 +918,17 @@ public class LoadTest {
|
||||||
public int getTotalEffectsCount() {
|
public int getTotalEffectsCount() {
|
||||||
return finalGameView == null ? 0 : this.finalGameView.getTotalEffectsCount();
|
return finalGameView == null ? 0 : this.finalGameView.getTotalEffectsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGameCycle() {
|
||||||
|
return finalGameView == null ? 0 : this.finalGameView.getGameCycle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LoadTestGameResultsList extends HashMap<Integer, LoadTestGameResult> {
|
private static class LoadTestGameResultsList extends HashMap<Integer, LoadTestGameResult> {
|
||||||
|
|
||||||
private static final String tableFormatHeader = "|%-10s|%-15s|%-20s|%-10s|%-10s|%-10s|%-10s|%-10s|%-15s|%-15s|%-10s|%n";
|
// index, name, random sid, game cycle, errors, effects, turn, life p1, life p2, creatures p1, creatures p2, =time, sec, ~time, sec
|
||||||
private static final String tableFormatData = "|%-10s|%15s|%20s|%10s|%10s|%10s|%10s|%10s|%15s|%15s|%10s|%n";
|
private static final String tableFormatHeader = "|%-10s|%-15s|%-20s|%-10s|%-10s|%-10s|%-10s|%-10s|%-10s|%-15s|%-15s|%-15s|%-15s|%n";
|
||||||
|
private static final String tableFormatData = "|%-10s|%15s|%20s|%10s|%10s|%10s|%10s|%10s|%10s|%15s|%15s|%15s|%15s|%n";
|
||||||
|
|
||||||
public LoadTestGameResult createGame(int index, String name, long randomSeed) {
|
public LoadTestGameResult createGame(int index, String name, long randomSeed) {
|
||||||
if (this.containsKey(index)) {
|
if (this.containsKey(index)) {
|
||||||
|
|
@ -939,6 +944,7 @@ public class LoadTest {
|
||||||
"index",
|
"index",
|
||||||
"name",
|
"name",
|
||||||
"random sid",
|
"random sid",
|
||||||
|
"game cycles",
|
||||||
"errors",
|
"errors",
|
||||||
"effects",
|
"effects",
|
||||||
"turn",
|
"turn",
|
||||||
|
|
@ -946,8 +952,8 @@ public class LoadTest {
|
||||||
"life p2",
|
"life p2",
|
||||||
"creatures p1",
|
"creatures p1",
|
||||||
"creatures p2",
|
"creatures p2",
|
||||||
"time, sec",
|
"=time, sec",
|
||||||
"time per turn, sec"
|
"~time, sec"
|
||||||
);
|
);
|
||||||
System.out.printf(tableFormatHeader, data.toArray());
|
System.out.printf(tableFormatHeader, data.toArray());
|
||||||
}
|
}
|
||||||
|
|
@ -961,6 +967,7 @@ public class LoadTest {
|
||||||
String.valueOf(gameResult.index), //"index",
|
String.valueOf(gameResult.index), //"index",
|
||||||
gameResult.name, //"name",
|
gameResult.name, //"name",
|
||||||
String.valueOf(gameResult.randomSeed), // "random sid",
|
String.valueOf(gameResult.randomSeed), // "random sid",
|
||||||
|
String.valueOf(gameResult.getGameCycle()), // "game cycles",
|
||||||
String.valueOf(gameResult.getTotalErrorsCount()), // "errors",
|
String.valueOf(gameResult.getTotalErrorsCount()), // "errors",
|
||||||
String.valueOf(gameResult.getTotalEffectsCount()), // "effects",
|
String.valueOf(gameResult.getTotalEffectsCount()), // "effects",
|
||||||
gameResult.getTurnInfo(), //"turn",
|
gameResult.getTurnInfo(), //"turn",
|
||||||
|
|
@ -979,15 +986,16 @@ public class LoadTest {
|
||||||
"TOTAL/AVG", //"index",
|
"TOTAL/AVG", //"index",
|
||||||
String.valueOf(this.size()), //"name",
|
String.valueOf(this.size()), //"name",
|
||||||
"total, secs: " + String.format("%.3f", (float) this.getTotalDurationMs() / 1000), // "random sid",
|
"total, secs: " + String.format("%.3f", (float) this.getTotalDurationMs() / 1000), // "random sid",
|
||||||
String.valueOf(this.getTotalErrorsCount()), // errors
|
"~" + this.getAvgGameCycle(), // game cycles
|
||||||
String.valueOf(this.getAvgEffectsCount()), // effects
|
"=" + this.getTotalErrorsCount(), // errors
|
||||||
String.valueOf(this.getAvgTurn()), // turn
|
"~" + this.getAvgEffectsCount(), // effects
|
||||||
String.valueOf(this.getAvgLife1()), // life p1
|
"~" + this.getAvgTurn(), // turn
|
||||||
String.valueOf(this.getAvgLife2()), // life p2
|
"~" + this.getAvgLife1(), // life p1
|
||||||
String.valueOf(this.getAvgCreaturesCount1()), // creatures p1
|
"~" + this.getAvgLife2(), // life p2
|
||||||
String.valueOf(this.getAvgCreaturesCount2()), // creatures p2
|
"~" + this.getAvgCreaturesCount1(), // creatures p1
|
||||||
String.valueOf(String.format("%.3f", (float) this.getAvgDurationMs() / 1000)), // time, sec
|
"~" + this.getAvgCreaturesCount2(), // creatures p2
|
||||||
String.valueOf(String.format("%.3f", (float) this.getAvgDurationPerTurnMs() / 1000)) // time per turn, sec
|
"~" + String.format("%.3f", (float) this.getAvgDurationMs() / 1000), // time, sec
|
||||||
|
"~" + String.format("%.3f", (float) this.getAvgDurationPerTurnMs() / 1000) // time per turn, sec
|
||||||
);
|
);
|
||||||
System.out.printf(tableFormatData, data.toArray());
|
System.out.printf(tableFormatData, data.toArray());
|
||||||
}
|
}
|
||||||
|
|
@ -996,6 +1004,10 @@ public class LoadTest {
|
||||||
return this.values().stream().mapToInt(LoadTestGameResult::getTotalErrorsCount).sum();
|
return this.values().stream().mapToInt(LoadTestGameResult::getTotalErrorsCount).sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getAvgGameCycle() {
|
||||||
|
return this.size() == 0 ? 0 : this.values().stream().mapToInt(LoadTestGameResult::getGameCycle).sum() / this.size();
|
||||||
|
}
|
||||||
|
|
||||||
private int getAvgEffectsCount() {
|
private int getAvgEffectsCount() {
|
||||||
return this.size() == 0 ? 0 : this.values().stream().mapToInt(LoadTestGameResult::getTotalEffectsCount).sum() / this.size();
|
return this.size() == 0 ? 0 : this.values().stream().mapToInt(LoadTestGameResult::getTotalEffectsCount).sum() / this.size();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,7 @@ public class TestPlayer implements Player {
|
||||||
if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) {
|
if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) {
|
||||||
// supports only to set the complete amount to one target
|
// supports only to set the complete amount to one target
|
||||||
TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget.getOriginalTarget();
|
TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget.getOriginalTarget();
|
||||||
targetAmount.setAmount(ability, game);
|
targetAmount.prepareAmount(ability, game);
|
||||||
int amount = targetAmount.getAmountRemaining();
|
int amount = targetAmount.getAmountRemaining();
|
||||||
targetAmount.addTarget(id, amount, ability, game);
|
targetAmount.addTarget(id, amount, ability, game);
|
||||||
targetsSet++;
|
targetsSet++;
|
||||||
|
|
@ -2101,10 +2101,7 @@ public class TestPlayer implements Player {
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
return "Target: null";
|
return "Target: null";
|
||||||
}
|
}
|
||||||
UUID abilityControllerId = getId();
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
|
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
|
||||||
|
|
||||||
return "Target: selected " + target.getSize() + ", possible " + possibleTargets.size()
|
return "Target: selected " + target.getSize() + ", possible " + possibleTargets.size()
|
||||||
|
|
@ -2274,10 +2271,7 @@ public class TestPlayer implements Player {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId = this.getId();
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: warning, some cards call player.choose methods instead target.choose, see #8254
|
// TODO: warning, some cards call player.choose methods instead target.choose, see #8254
|
||||||
// most use cases - discard and other cost with choice like that method
|
// most use cases - discard and other cost with choice like that method
|
||||||
|
|
@ -2509,11 +2503,7 @@ public class TestPlayer implements Player {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
|
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
|
||||||
UUID abilityControllerId = this.getId();
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
UUID sourceId = source != null ? source.getSourceId() : null;
|
|
||||||
|
|
||||||
assertAliasSupportInTargets(true);
|
assertAliasSupportInTargets(true);
|
||||||
if (!targets.isEmpty()) {
|
if (!targets.isEmpty()) {
|
||||||
|
|
@ -2817,10 +2807,7 @@ public class TestPlayer implements Player {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
|
public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) {
|
||||||
UUID abilityControllerId = this.getId();
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
if (target.getTargetController() != null && target.getAbilityController() != null) {
|
|
||||||
abilityControllerId = target.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAliasSupportInTargets(false);
|
assertAliasSupportInTargets(false);
|
||||||
if (!targets.isEmpty()) {
|
if (!targets.isEmpty()) {
|
||||||
|
|
@ -4329,12 +4316,20 @@ public class TestPlayer implements Player {
|
||||||
// chooseTargetAmount calls for EACH target cycle (e.g. one target per click, see TargetAmount)
|
// chooseTargetAmount calls for EACH target cycle (e.g. one target per click, see TargetAmount)
|
||||||
// if use want to stop choosing then chooseTargetAmount must return false (example: up to xxx)
|
// if use want to stop choosing then chooseTargetAmount must return false (example: up to xxx)
|
||||||
|
|
||||||
|
// nothing to choose
|
||||||
|
target.prepareAmount(source, game);
|
||||||
if (target.getAmountRemaining() <= 0) {
|
if (target.getAmountRemaining() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||||
|
|
||||||
assertAliasSupportInTargets(true);
|
assertAliasSupportInTargets(true);
|
||||||
if (!targets.isEmpty()) {
|
|
||||||
|
while (!targets.isEmpty()) {
|
||||||
|
|
||||||
// skip targets
|
// skip targets
|
||||||
if (targets.get(0).equals(TARGET_SKIP)) {
|
if (targets.get(0).equals(TARGET_SKIP)) {
|
||||||
|
|
@ -4386,7 +4381,11 @@ public class TestPlayer implements Player {
|
||||||
// can select
|
// can select
|
||||||
target.addTarget(possibleTarget, targetAmount, source, game);
|
target.addTarget(possibleTarget, targetAmount, source, game);
|
||||||
targets.remove(0);
|
targets.remove(0);
|
||||||
return true; // one target per choose call
|
// allow test player to choose as much as possible until skip command
|
||||||
|
if (target.getAmountRemaining() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break; // try next target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1226,10 +1226,7 @@ public abstract class AbilityImpl implements Ability {
|
||||||
for (Mode mode : modes.values()) {
|
for (Mode mode : modes.values()) {
|
||||||
boolean validTargets = true;
|
boolean validTargets = true;
|
||||||
for (Target target : mode.getTargets()) {
|
for (Target target : mode.getTargets()) {
|
||||||
UUID abilityControllerId = controllerId;
|
UUID abilityControllerId = target.getAffectedAbilityControllerId(controllerId);
|
||||||
if (target.getTargetController() != null) {
|
|
||||||
abilityControllerId = target.getTargetController();
|
|
||||||
}
|
|
||||||
if (!target.canChoose(abilityControllerId, ability, game)) {
|
if (!target.canChoose(abilityControllerId, ability, game)) {
|
||||||
validTargets = false;
|
validTargets = false;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,9 @@ public interface Target extends Copyable<Target>, Serializable {
|
||||||
* Warning, for "up to" targets it will return true all the time, so make sure your dialog
|
* Warning, for "up to" targets it will return true all the time, so make sure your dialog
|
||||||
* use do-while logic and call "choose" one time min or use isChoiceCompleted
|
* use do-while logic and call "choose" one time min or use isChoiceCompleted
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // TODO: replace with UUID abilityControllerId, Ability source, Game game
|
||||||
boolean isChosen(Game game);
|
boolean isChosen(Game game);
|
||||||
|
|
||||||
@Deprecated // TODO: replace usage in cards by full version from choose methods
|
|
||||||
boolean isChoiceCompleted(Game game);
|
|
||||||
|
|
||||||
boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game);
|
boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game);
|
||||||
|
|
||||||
void clearChosen();
|
void clearChosen();
|
||||||
|
|
@ -189,14 +187,18 @@ public interface Target extends Copyable<Target>, Serializable {
|
||||||
Target copy();
|
Target copy();
|
||||||
|
|
||||||
// some targets are chosen from players that are not the controller of the ability (e.g. Pandemonium)
|
// some targets are chosen from players that are not the controller of the ability (e.g. Pandemonium)
|
||||||
|
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
|
||||||
void setTargetController(UUID playerId);
|
void setTargetController(UUID playerId);
|
||||||
|
|
||||||
UUID getTargetController();
|
UUID getTargetController();
|
||||||
|
|
||||||
|
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
|
||||||
void setAbilityController(UUID playerId);
|
void setAbilityController(UUID playerId);
|
||||||
|
|
||||||
UUID getAbilityController();
|
UUID getAbilityController();
|
||||||
|
|
||||||
|
UUID getAffectedAbilityControllerId(UUID choosingPlayerId);
|
||||||
|
|
||||||
Player getTargetController(Game game, UUID playerId);
|
Player getTargetController(Game game, UUID playerId);
|
||||||
|
|
||||||
int getTargetTag();
|
int getTargetTag();
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,15 @@ import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Distribute value between targets list (damage, counters, etc)
|
||||||
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public abstract class TargetAmount extends TargetImpl {
|
public abstract class TargetAmount extends TargetImpl {
|
||||||
|
|
||||||
boolean amountWasSet = false;
|
boolean amountWasSet = false;
|
||||||
DynamicValue amount;
|
DynamicValue amount;
|
||||||
int remainingAmount;
|
int remainingAmount; // before any change to it - make sure you call prepareAmount
|
||||||
|
|
||||||
protected TargetAmount(DynamicValue amount, int minNumberOfTargets, int maxNumberOfTargets) {
|
protected TargetAmount(DynamicValue amount, int minNumberOfTargets, int maxNumberOfTargets) {
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
|
|
@ -46,15 +48,42 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isChosen(Game game) {
|
public boolean isChosen(Game game) {
|
||||||
return isChoiceCompleted(game);
|
if (!super.isChosen(game)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// selection not started
|
||||||
|
if (!amountWasSet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// distribution
|
||||||
|
if (getMinNumberOfTargets() == 0 && this.targets.isEmpty()) {
|
||||||
|
// allow 0 distribution, e.g. for "up to" targets like Vivien, Arkbow Ranger
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// need full distribution
|
||||||
|
return remainingAmount == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isChoiceCompleted(Game game) {
|
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||||
return amountWasSet
|
// make sure target request called one time minimum (for "up to" targets)
|
||||||
&& (remainingAmount == 0
|
// choice is selected after any addTarget call (by test, AI or human players)
|
||||||
|| (getMinNumberOfTargets() < getMaxNumberOfTargets()
|
if (!isChoiceSelected()) {
|
||||||
&& getTargets().size() >= getMinNumberOfTargets()));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure selected targets are valid
|
||||||
|
if (!isChosen(game)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need auto-choose here? See super
|
||||||
|
|
||||||
|
// all other use cases are fine
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -68,9 +97,14 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmount(Ability source, Game game) {
|
/**
|
||||||
remainingAmount = amount.calculate(game, source, null);
|
* Prepare new targets for choosing
|
||||||
amountWasSet = true;
|
*/
|
||||||
|
public void prepareAmount(Ability source, Game game) {
|
||||||
|
if (!amountWasSet) {
|
||||||
|
remainingAmount = amount.calculate(game, source, null);
|
||||||
|
amountWasSet = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DynamicValue getAmount() {
|
public DynamicValue getAmount() {
|
||||||
|
|
@ -83,12 +117,11 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
|
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
|
||||||
if (!amountWasSet) {
|
prepareAmount(source, game);
|
||||||
setAmount(source, game);
|
|
||||||
}
|
|
||||||
if (amount <= remainingAmount) {
|
if (amount <= remainingAmount) {
|
||||||
super.addTarget(id, amount, source, game, skipEvent);
|
|
||||||
remainingAmount -= amount;
|
remainingAmount -= amount;
|
||||||
|
super.addTarget(id, amount, source, game, skipEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,37 +133,70 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game) {
|
||||||
|
throw new IllegalArgumentException("Wrong code usage. TargetAmount must be called by player.chooseTarget, not player.choose");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated // TODO: replace by player.chooseTargetAmount call
|
||||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||||
Player player = game.getPlayer(playerId);
|
Player targetController = getTargetController(game, playerId);
|
||||||
if (player == null) {
|
if (targetController == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amountWasSet) {
|
prepareAmount(source, game);
|
||||||
setAmount(source, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (remainingAmount > 0) {
|
chosen = false;
|
||||||
chosen = false;
|
do {
|
||||||
if (!player.canRespond()) {
|
int prevTargetsCount = this.getTargets().size();
|
||||||
chosen = isChosen(game);
|
|
||||||
|
// stop by disconnect
|
||||||
|
if (!targetController.canRespond()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
|
||||||
chosen = isChosen(game);
|
// MAKE A CHOICE
|
||||||
break;
|
if (isRandom()) {
|
||||||
|
// random choice
|
||||||
|
throw new IllegalArgumentException("Wrong code usage. TargetAmount do not support random choices");
|
||||||
|
} else {
|
||||||
|
// player's choice
|
||||||
|
|
||||||
|
// TargetAmount do not support auto-choice
|
||||||
|
|
||||||
|
// manual
|
||||||
|
|
||||||
|
// stop by cancel/done
|
||||||
|
if (!targetController.chooseTargetAmount(outcome, this, source, game)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue to next target
|
||||||
}
|
}
|
||||||
|
|
||||||
chosen = isChosen(game);
|
chosen = isChosen(game);
|
||||||
}
|
|
||||||
|
|
||||||
return isChosen(game);
|
// stop by full complete
|
||||||
|
if (isChoiceCompleted(targetController.getId(), source, game)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop by nothing to choose (actual for human and done button?)
|
||||||
|
if (prevTargetsCount == this.getTargets().size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// can select next target
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
chosen = isChosen(game);
|
||||||
|
return chosen && !this.getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
final public List<? extends TargetAmount> getTargetOptions(Ability source, Game game) {
|
final public List<? extends TargetAmount> getTargetOptions(Ability source, Game game) {
|
||||||
if (!amountWasSet) {
|
prepareAmount(source, game);
|
||||||
setAmount(source, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TargetAmount> options = new ArrayList<>();
|
List<TargetAmount> options = new ArrayList<>();
|
||||||
Set<UUID> possibleTargets = possibleTargets(source.getControllerId(), source, game);
|
Set<UUID> possibleTargets = possibleTargets(source.getControllerId(), source, game);
|
||||||
|
|
@ -370,9 +436,8 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTargetAmount(UUID targetId, int amount, Ability source, Game game) {
|
public void setTargetAmount(UUID targetId, int amount, Ability source, Game game) {
|
||||||
if (!amountWasSet) {
|
prepareAmount(source, game);
|
||||||
setAmount(source, game);
|
|
||||||
}
|
|
||||||
remainingAmount -= (amount - this.getTargetAmount(targetId));
|
remainingAmount -= (amount - this.getTargetAmount(targetId));
|
||||||
this.setTargetAmount(targetId, amount, game);
|
this.setTargetAmount(targetId, amount, game);
|
||||||
}
|
}
|
||||||
|
|
@ -396,4 +461,13 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
// Each of these targets must receive at least one of whatever is being divided.
|
// Each of these targets must receive at least one of whatever is being divided.
|
||||||
return amount instanceof StaticValue && max == ((StaticValue) amount).getValue();
|
return amount instanceof StaticValue && max == ((StaticValue) amount).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (amountWasSet) {
|
||||||
|
return super.toString() + String.format(" (remain amount %d of %s)", this.remainingAmount, this.amount.toString());
|
||||||
|
} else {
|
||||||
|
return super.toString() + String.format(" (remain not prepared, %s)", this.amount.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -260,11 +260,6 @@ public abstract class TargetImpl implements Target {
|
||||||
return chosen || (targets.size() >= getMinNumberOfTargets() && targets.size() <= getMaxNumberOfTargets());
|
return chosen || (targets.size() >= getMinNumberOfTargets() && targets.size() <= getMaxNumberOfTargets());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isChoiceCompleted(Game game) {
|
|
||||||
return isChoiceCompleted(null, null, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||||
// make sure target request called one time minimum (for "up to" targets)
|
// make sure target request called one time minimum (for "up to" targets)
|
||||||
|
|
@ -406,10 +401,7 @@ public abstract class TargetImpl implements Target {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
|
||||||
abilityControllerId = this.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
chosen = false;
|
chosen = false;
|
||||||
do {
|
do {
|
||||||
|
|
@ -444,7 +436,7 @@ public abstract class TargetImpl implements Target {
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
chosen = isChosen(game);
|
chosen = isChosen(game);
|
||||||
return this.getTargets().size() > 0;
|
return chosen && !this.getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -454,10 +446,7 @@ public abstract class TargetImpl implements Target {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
|
||||||
abilityControllerId = this.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UUID> randomPossibleTargets = new ArrayList<>(possibleTargets(playerId, source, game));
|
List<UUID> randomPossibleTargets = new ArrayList<>(possibleTargets(playerId, source, game));
|
||||||
|
|
||||||
|
|
@ -527,7 +516,7 @@ public abstract class TargetImpl implements Target {
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
chosen = isChosen(game);
|
chosen = isChosen(game);
|
||||||
return this.getTargets().size() > 0;
|
return chosen && !this.getTargets().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -726,6 +715,20 @@ public abstract class TargetImpl implements Target {
|
||||||
return abilityController;
|
return abilityController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getAffectedAbilityControllerId(UUID choosingPlayerId) {
|
||||||
|
// controller hints:
|
||||||
|
// - target.getTargetController(), this.getId(), choosingPlayerId -- player that must makes choices (must be same with this.getId)
|
||||||
|
// - target.getAbilityController(), abilityControllerId -- affected player/controller for all actions/filters
|
||||||
|
// - affected controller can be different from target controller (another player makes choices for controller)
|
||||||
|
// sometimes a target selection can be made from a player that does not control the ability
|
||||||
|
UUID abilityControllerId = choosingPlayerId;
|
||||||
|
if (this.getAbilityController() != null) {
|
||||||
|
abilityControllerId = this.getAbilityController();
|
||||||
|
}
|
||||||
|
return abilityControllerId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player getTargetController(Game game, UUID playerId) {
|
public Player getTargetController(Game game, UUID playerId) {
|
||||||
if (getTargetController() != null) {
|
if (getTargetController() != null) {
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
|
||||||
return unchosenIndex < res.size() ? res.get(unchosenIndex) : null;
|
return unchosenIndex < res.size() ? res.get(unchosenIndex) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChoiceCompleted(Game game) {
|
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||||
return stream().allMatch(t -> t.isChoiceCompleted(game));
|
return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearChosen() {
|
public void clearChosen() {
|
||||||
|
|
@ -101,9 +101,7 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
|
||||||
|
|
||||||
// stop on cancel/done
|
// stop on cancel/done
|
||||||
if (!target.choose(outcome, playerId, sourceId, source, game)) {
|
if (!target.choose(outcome, playerId, sourceId, source, game)) {
|
||||||
if (!target.isChosen(game)) {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// target done, can take next one
|
// target done, can take next one
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,7 @@ public class TargetCardInLibrary extends TargetCard {
|
||||||
Cards cardsId = new CardsImpl();
|
Cards cardsId = new CardsImpl();
|
||||||
cards.forEach(cardsId::add);
|
cards.forEach(cardsId::add);
|
||||||
|
|
||||||
UUID abilityControllerId = playerId;
|
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
|
||||||
abilityControllerId = this.getAbilityController();
|
|
||||||
}
|
|
||||||
|
|
||||||
chosen = false;
|
chosen = false;
|
||||||
do {
|
do {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue