mirror of
https://github.com/magefree/mage.git
synced 2025-12-29 23:12:10 -08:00
[LTR] Add Goldberry, River-Daughter (#10524)
* Added Goldberry * Slight optimizaztion * Happy Path Test * More unhappy tests * Sanity check for Goldberry's counter choices * Updated player.getMultiAmount to support individual constraints * Some cleanup Also modified ResourcefulDefense to use new multi amount api * Updated logging * Added hint for number of counters * Fixed issue with Resourceful Defense * Improvements to defaults Default list will properly make sure to stay within individual maximums If a player is asked for a choice that isn't actually a choice because each choice's min and max are equal, instead the default response is immediately returned. This helps with situations like moving a counter off of Goldberry when she only has one counter on her. * -1/-1 Counter test * Fixed issue with -1/-1 counters * Adjusted dialog to properly enforce constraints
This commit is contained in:
parent
fe1efef25b
commit
a36a7d9b7f
23 changed files with 678 additions and 180 deletions
|
|
@ -1,19 +1,19 @@
|
|||
package mage.constants;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum MultiAmountType {
|
||||
|
||||
MANA("Add mana", "Distribute mana among colors"),
|
||||
DAMAGE("Assign damage", "Assign damage among targets"),
|
||||
P1P1("Add +1/+1 counters", "Distribute +1/+1 counters among creatures");
|
||||
P1P1("Add +1/+1 counters", "Distribute +1/+1 counters among creatures"),
|
||||
COUNTERS("Choose counters", "Move counters");
|
||||
|
||||
private final String title;
|
||||
private final String header;
|
||||
|
|
@ -31,63 +31,125 @@ public enum MultiAmountType {
|
|||
return header;
|
||||
}
|
||||
|
||||
public static List<Integer> prepareDefaltValues(int count, int min, int max) {
|
||||
public static List<Integer> prepareDefaltValues(List<MultiAmountMessage> constraints, int min, int max) {
|
||||
// default values must be assigned from first to last by minimum values
|
||||
List<Integer> res = new ArrayList<>();
|
||||
if (count == 0) {
|
||||
List<Integer> res = constraints.stream().map(m -> m.min > Integer.MIN_VALUE ? m.min : (0 < max ? 0 : max))
|
||||
.collect(Collectors.toList());
|
||||
if (res.isEmpty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// fill list
|
||||
IntStream.range(0, count).forEach(i -> res.add(0));
|
||||
int total = res.stream().reduce(0, Integer::sum);
|
||||
|
||||
// fill values
|
||||
if (min > 0) {
|
||||
res.set(0, min);
|
||||
// Fill values until we reach the overall minimum. Do this by filling values up until either their max or however much is leftover, starting with the first option.
|
||||
if (min > 0 && total < min) {
|
||||
int left = min - total;
|
||||
for (int i = 0; i < res.size(); i++) {
|
||||
// How much space there is left to add to
|
||||
if (constraints.get(i).max == Integer.MAX_VALUE || constraints.get(i).max - res.get(i) > left) {
|
||||
res.set(i, res.get(i) + left);
|
||||
break;
|
||||
} else {
|
||||
int add = constraints.get(i).max - res.get(i);
|
||||
res.set(i, constraints.get(i).max);
|
||||
|
||||
left -= add;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static List<Integer> prepareMaxValues(int count, int min, int max) {
|
||||
// fill max values as much as possible
|
||||
List<Integer> res = new ArrayList<>();
|
||||
if (count == 0) {
|
||||
return res;
|
||||
public static List<Integer> prepareMaxValues(List<MultiAmountMessage> constraints, int min, int max) {
|
||||
if (constraints.isEmpty()) {
|
||||
return new ArrayList<Integer>();
|
||||
}
|
||||
|
||||
// fill list
|
||||
int startingValue = max / count;
|
||||
IntStream.range(0, count).forEach(i -> res.add(startingValue));
|
||||
// Start by filling in minimum values where it makes sense
|
||||
int default_val = max / constraints.size();
|
||||
List<Integer> res = constraints.stream()
|
||||
.map(m -> m.min > Integer.MIN_VALUE ? m.min : (default_val < m.max ? default_val : m.max))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// fill values
|
||||
// from first to last until complete
|
||||
List<Integer> resIndexes = new ArrayList<>(res.size());
|
||||
IntStream.range(0, res.size()).forEach(resIndexes::add);
|
||||
// infinite iterator (no needs with starting values use, but can be used later for different logic)
|
||||
Iterator<Integer> resIterator = Iterables.cycle(resIndexes).iterator();
|
||||
int valueInc = 1;
|
||||
int valueTotal = startingValue * count;
|
||||
while (valueTotal < max) {
|
||||
int currentIndex = resIterator.next();
|
||||
int newValue = CardUtil.overflowInc(res.get(currentIndex), valueInc);
|
||||
res.set(currentIndex, newValue);
|
||||
valueTotal += valueInc;
|
||||
// Total should fall between the sum of all of the minimum values and max (in the case that everything was filled with default_value).
|
||||
// So, we'll never start with too much.
|
||||
int total = res.stream().reduce(0, Integer::sum);
|
||||
|
||||
// So add some values evenly until we hit max
|
||||
while (total < max) {
|
||||
// Find the most amount we can add to several items at once without going over the maximum values
|
||||
int addable = Integer.MIN_VALUE;
|
||||
List<Integer> consider = new ArrayList<Integer>();
|
||||
for (int i = 0; i < res.size(); i++) {
|
||||
|
||||
if (constraints.get(i).max == Integer.MAX_VALUE) {
|
||||
consider.add(i);
|
||||
} else {
|
||||
int diff = constraints.get(i).max - res.get(i);
|
||||
if (diff > 0) {
|
||||
consider.add(i);
|
||||
if (diff < addable) {
|
||||
addable = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We hit max for all of the individual constraints - so this is as far as we can go.
|
||||
if (consider.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (addable > Integer.MIN_VALUE && total + addable * consider.size() < max) {
|
||||
for (int i : consider) {
|
||||
res.set(i, res.get(i) + addable);
|
||||
}
|
||||
total += addable * consider.size();
|
||||
} else {
|
||||
addable = (max - total) / consider.size();
|
||||
int extras = (max - total) % consider.size();
|
||||
|
||||
for (int i = 0; i < consider.size(); i++) {
|
||||
// Remove from the end options first
|
||||
int idx = consider.get(i);
|
||||
|
||||
// Add the extras evenly to the first options
|
||||
if (i < extras) {
|
||||
res.set(idx, res.get(idx) + addable + 1);
|
||||
} else {
|
||||
res.set(idx, res.get(idx) + addable);
|
||||
}
|
||||
}
|
||||
|
||||
total = max;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static boolean isGoodValues(List<Integer> values, int count, int min, int max) {
|
||||
if (values.size() != count) {
|
||||
public static boolean isGoodValues(List<Integer> values, List<MultiAmountMessage> constraints, int min, int max) {
|
||||
if (values.size() != constraints.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentSum = values.stream().mapToInt(i -> i).sum();
|
||||
int currentSum = 0;
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
int value = values.get(i);
|
||||
|
||||
if (value < constraints.get(i).min || value > constraints.get(i).max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSum += value;
|
||||
}
|
||||
|
||||
return currentSum >= min && currentSum <= max;
|
||||
}
|
||||
|
||||
public static List<Integer> parseAnswer(String answerToParse, int count, int min, int max, boolean returnDefaultOnError) {
|
||||
public static List<Integer> parseAnswer(String answerToParse, List<MultiAmountMessage> constraints, int min,
|
||||
int max, boolean returnDefaultOnError) {
|
||||
List<Integer> res = new ArrayList<>();
|
||||
|
||||
// parse
|
||||
|
|
@ -99,9 +161,9 @@ public enum MultiAmountType {
|
|||
}
|
||||
|
||||
// data check
|
||||
if (returnDefaultOnError && !isGoodValues(res, count, min, max)) {
|
||||
if (returnDefaultOnError && !isGoodValues(res, constraints, min, max)) {
|
||||
// on broken data - return default
|
||||
return prepareDefaltValues(count, min, max);
|
||||
return prepareDefaltValues(constraints, min, max);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import mage.players.PlayerList;
|
|||
import mage.players.Players;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.MessageToClient;
|
||||
import mage.util.MultiAmountMessage;
|
||||
import mage.util.functions.CopyApplier;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
|
@ -301,7 +302,7 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
void fireGetAmountEvent(UUID playerId, String message, int min, int max);
|
||||
|
||||
void fireGetMultiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options);
|
||||
void fireGetMultiAmountEvent(UUID playerId, List<MultiAmountMessage> messages, int min, int max, Map<String, Serializable> options);
|
||||
|
||||
void fireChoosePileEvent(UUID playerId, String message, List<? extends Card> pile1, List<? extends Card> pile2);
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import mage.target.TargetPlayer;
|
|||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.MessageToClient;
|
||||
import mage.util.MultiAmountMessage;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.util.functions.CopyApplier;
|
||||
import mage.watchers.Watcher;
|
||||
|
|
@ -2885,7 +2886,8 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void fireGetMultiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
public void fireGetMultiAmountEvent(UUID playerId, List<MultiAmountMessage> messages, int min, int max,
|
||||
Map<String, Serializable> options) {
|
||||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import mage.cards.Cards;
|
|||
import mage.choices.Choice;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -60,11 +61,11 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
|||
private List<? extends Card> pile1;
|
||||
private List<? extends Card> pile2;
|
||||
private Choice choice;
|
||||
private List<String> messages;
|
||||
private List<MultiAmountMessage> messages;
|
||||
|
||||
private PlayerQueryEvent(UUID playerId, String message, List<? extends Ability> abilities, Set<String> choices,
|
||||
Set<UUID> targets, Cards cards, QueryType queryType, int min, int max, boolean required,
|
||||
Map<String, Serializable> options, List<String> messages) {
|
||||
Set<UUID> targets, Cards cards, QueryType queryType, int min, int max,
|
||||
boolean required, Map<String, Serializable> options, List<MultiAmountMessage> messages) {
|
||||
super(playerId);
|
||||
|
||||
CardUtil.checkSetParamForSerializationCompatibility(choices);
|
||||
|
|
@ -216,8 +217,10 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
|||
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.AMOUNT, min, max, false, null, null);
|
||||
}
|
||||
|
||||
public static PlayerQueryEvent multiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
return new PlayerQueryEvent(playerId, null, null, null, null, null, QueryType.MULTI_AMOUNT, min, max, false, options, messages);
|
||||
public static PlayerQueryEvent multiAmountEvent(UUID playerId, List<MultiAmountMessage> messages, int min,
|
||||
int max, Map<String, Serializable> options) {
|
||||
return new PlayerQueryEvent(playerId, null, null, null, null, null, QueryType.MULTI_AMOUNT, min, max, false,
|
||||
options, messages);
|
||||
}
|
||||
|
||||
public static PlayerQueryEvent pickCard(UUID playerId, String message, List<Card> booster, int time) {
|
||||
|
|
@ -300,7 +303,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
|||
return choice;
|
||||
}
|
||||
|
||||
public List<String> getMessages() {
|
||||
public List<MultiAmountMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import mage.cards.Card;
|
|||
import mage.cards.Cards;
|
||||
import mage.choices.Choice;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -84,7 +85,8 @@ public class PlayerQueryEventSource implements EventSource<PlayerQueryEvent>, Se
|
|||
dispatcher.fireEvent(PlayerQueryEvent.amountEvent(playerId, message, min, max));
|
||||
}
|
||||
|
||||
public void multiAmount(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
public void multiAmount(UUID playerId, List<MultiAmountMessage> messages, int min, int max,
|
||||
Map<String, Serializable> options) {
|
||||
dispatcher.fireEvent(PlayerQueryEvent.multiAmountEvent(playerId, messages, min, max, options));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,11 @@ import mage.target.TargetAmount;
|
|||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
import mage.util.Copyable;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -746,7 +748,27 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
* @param game Game
|
||||
* @return List of integers with size equal to messages.size(). The sum of the integers is equal to max.
|
||||
*/
|
||||
List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game);
|
||||
default List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type,
|
||||
Game game) {
|
||||
List<MultiAmountMessage> constraints = messages.stream().map(s -> new MultiAmountMessage(s, 0, max))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return getMultiAmountWithIndividualConstraints(outcome, constraints, min, max, type, game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Player distributes amount among multiple options
|
||||
*
|
||||
* @param outcome AI hint
|
||||
* @param messages List of options to distribute amount among. Each option has a constraint on the min, max chosen for it
|
||||
* @param totalMin Total minimum amount to be distributed
|
||||
* @param totalMax Total amount to be distributed
|
||||
* @param type MultiAmountType enum to set dialog options such as title and header
|
||||
* @param game Game
|
||||
* @return List of integers with size equal to messages.size(). The sum of the integers is equal to max.
|
||||
*/
|
||||
List<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> messages, int min,
|
||||
int max, MultiAmountType type, Game game);
|
||||
|
||||
void sideboard(Match match, Deck deck);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import mage.target.Target;
|
|||
import mage.target.TargetAmount;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -207,7 +208,8 @@ public class StubPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||
public List<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> messages,
|
||||
int min, int max, MultiAmountType type, Game game) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
17
Mage/src/main/java/mage/util/MultiAmountMessage.java
Normal file
17
Mage/src/main/java/mage/util/MultiAmountMessage.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package mage.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
// Author: alexander-novo
|
||||
// A helper class for facilitating the multi-choose dialog
|
||||
public class MultiAmountMessage implements Serializable {
|
||||
public String message;
|
||||
public int min;
|
||||
public int max;
|
||||
|
||||
public MultiAmountMessage(String message, int min, int max) {
|
||||
this.message = message;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue