mirror of
https://github.com/magefree/mage.git
synced 2025-12-24 04:22:01 -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
|
|
@ -1226,10 +1226,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
for (Mode mode : modes.values()) {
|
||||
boolean validTargets = true;
|
||||
for (Target target : mode.getTargets()) {
|
||||
UUID abilityControllerId = controllerId;
|
||||
if (target.getTargetController() != null) {
|
||||
abilityControllerId = target.getTargetController();
|
||||
}
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(controllerId);
|
||||
if (!target.canChoose(abilityControllerId, ability, game)) {
|
||||
validTargets = false;
|
||||
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
|
||||
* 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);
|
||||
|
||||
@Deprecated // TODO: replace usage in cards by full version from choose methods
|
||||
boolean isChoiceCompleted(Game game);
|
||||
|
||||
boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game);
|
||||
|
||||
void clearChosen();
|
||||
|
|
@ -189,14 +187,18 @@ public interface Target extends Copyable<Target>, Serializable {
|
|||
Target copy();
|
||||
|
||||
// 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);
|
||||
|
||||
UUID getTargetController();
|
||||
|
||||
// TODO: research usage of setTargetController and setAbilityController - target adjusters must set it both, example: Necrotic Plague
|
||||
void setAbilityController(UUID playerId);
|
||||
|
||||
UUID getAbilityController();
|
||||
|
||||
UUID getAffectedAbilityControllerId(UUID choosingPlayerId);
|
||||
|
||||
Player getTargetController(Game game, UUID playerId);
|
||||
|
||||
int getTargetTag();
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ import java.util.*;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Distribute value between targets list (damage, counters, etc)
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public abstract class TargetAmount extends TargetImpl {
|
||||
|
||||
boolean amountWasSet = false;
|
||||
DynamicValue amount;
|
||||
int remainingAmount;
|
||||
int remainingAmount; // before any change to it - make sure you call prepareAmount
|
||||
|
||||
protected TargetAmount(DynamicValue amount, int minNumberOfTargets, int maxNumberOfTargets) {
|
||||
this.amount = amount;
|
||||
|
|
@ -46,15 +48,42 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
|
||||
@Override
|
||||
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
|
||||
public boolean isChoiceCompleted(Game game) {
|
||||
return amountWasSet
|
||||
&& (remainingAmount == 0
|
||||
|| (getMinNumberOfTargets() < getMaxNumberOfTargets()
|
||||
&& getTargets().size() >= getMinNumberOfTargets()));
|
||||
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||
// make sure target request called one time minimum (for "up to" targets)
|
||||
// choice is selected after any addTarget call (by test, AI or human players)
|
||||
if (!isChoiceSelected()) {
|
||||
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
|
||||
|
|
@ -68,9 +97,14 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
this.amount = amount;
|
||||
}
|
||||
|
||||
public void setAmount(Ability source, Game game) {
|
||||
remainingAmount = amount.calculate(game, source, null);
|
||||
amountWasSet = true;
|
||||
/**
|
||||
* Prepare new targets for choosing
|
||||
*/
|
||||
public void prepareAmount(Ability source, Game game) {
|
||||
if (!amountWasSet) {
|
||||
remainingAmount = amount.calculate(game, source, null);
|
||||
amountWasSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public DynamicValue getAmount() {
|
||||
|
|
@ -83,12 +117,11 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
|
||||
@Override
|
||||
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
|
||||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
prepareAmount(source, game);
|
||||
|
||||
if (amount <= remainingAmount) {
|
||||
super.addTarget(id, amount, source, game, skipEvent);
|
||||
remainingAmount -= amount;
|
||||
super.addTarget(id, amount, source, game, skipEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,37 +133,70 @@ public abstract class TargetAmount extends TargetImpl {
|
|||
}
|
||||
|
||||
@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) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player == null) {
|
||||
Player targetController = getTargetController(game, playerId);
|
||||
if (targetController == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
prepareAmount(source, game);
|
||||
|
||||
while (remainingAmount > 0) {
|
||||
chosen = false;
|
||||
if (!player.canRespond()) {
|
||||
chosen = isChosen(game);
|
||||
chosen = false;
|
||||
do {
|
||||
int prevTargetsCount = this.getTargets().size();
|
||||
|
||||
// stop by disconnect
|
||||
if (!targetController.canRespond()) {
|
||||
break;
|
||||
}
|
||||
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
||||
chosen = isChosen(game);
|
||||
break;
|
||||
|
||||
// MAKE A CHOICE
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
final public List<? extends TargetAmount> getTargetOptions(Ability source, Game game) {
|
||||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
prepareAmount(source, game);
|
||||
|
||||
List<TargetAmount> options = new ArrayList<>();
|
||||
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) {
|
||||
if (!amountWasSet) {
|
||||
setAmount(source, game);
|
||||
}
|
||||
prepareAmount(source, game);
|
||||
|
||||
remainingAmount -= (amount - this.getTargetAmount(targetId));
|
||||
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.
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChoiceCompleted(Game game) {
|
||||
return isChoiceCompleted(null, null, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||
// make sure target request called one time minimum (for "up to" targets)
|
||||
|
|
@ -406,10 +401,7 @@ public abstract class TargetImpl implements Target {
|
|||
return false;
|
||||
}
|
||||
|
||||
UUID abilityControllerId = playerId;
|
||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
||||
abilityControllerId = this.getAbilityController();
|
||||
}
|
||||
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||
|
||||
chosen = false;
|
||||
do {
|
||||
|
|
@ -444,7 +436,7 @@ public abstract class TargetImpl implements Target {
|
|||
} while (true);
|
||||
|
||||
chosen = isChosen(game);
|
||||
return this.getTargets().size() > 0;
|
||||
return chosen && !this.getTargets().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -454,10 +446,7 @@ public abstract class TargetImpl implements Target {
|
|||
return false;
|
||||
}
|
||||
|
||||
UUID abilityControllerId = playerId;
|
||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
||||
abilityControllerId = this.getAbilityController();
|
||||
}
|
||||
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||
|
||||
List<UUID> randomPossibleTargets = new ArrayList<>(possibleTargets(playerId, source, game));
|
||||
|
||||
|
|
@ -527,7 +516,7 @@ public abstract class TargetImpl implements Target {
|
|||
} while (true);
|
||||
|
||||
chosen = isChosen(game);
|
||||
return this.getTargets().size() > 0;
|
||||
return chosen && !this.getTargets().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -726,6 +715,20 @@ public abstract class TargetImpl implements Target {
|
|||
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
|
||||
public Player getTargetController(Game game, UUID playerId) {
|
||||
if (getTargetController() != null) {
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
|
|||
return unchosenIndex < res.size() ? res.get(unchosenIndex) : null;
|
||||
}
|
||||
|
||||
public boolean isChoiceCompleted(Game game) {
|
||||
return stream().allMatch(t -> t.isChoiceCompleted(game));
|
||||
public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) {
|
||||
return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game));
|
||||
}
|
||||
|
||||
public void clearChosen() {
|
||||
|
|
@ -101,9 +101,7 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
|
|||
|
||||
// stop on cancel/done
|
||||
if (!target.choose(outcome, playerId, sourceId, source, game)) {
|
||||
if (!target.isChosen(game)) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// target done, can take next one
|
||||
|
|
|
|||
|
|
@ -76,10 +76,7 @@ public class TargetCardInLibrary extends TargetCard {
|
|||
Cards cardsId = new CardsImpl();
|
||||
cards.forEach(cardsId::add);
|
||||
|
||||
UUID abilityControllerId = playerId;
|
||||
if (this.getTargetController() != null && this.getAbilityController() != null) {
|
||||
abilityControllerId = this.getAbilityController();
|
||||
}
|
||||
UUID abilityControllerId = this.getAffectedAbilityControllerId(playerId);
|
||||
|
||||
chosen = false;
|
||||
do {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue