Guava migration - Add StreamUtils.random collectors

This commit is contained in:
Jetz
2024-09-22 14:47:57 -04:00
parent c2f72bb3c2
commit 05d58c2fce
9 changed files with 159 additions and 55 deletions

View File

@@ -2450,7 +2450,10 @@ public class ComputerUtil {
// not enough good choices, need to fill the rest
int minDiff = min - goodChoices.size();
if (minDiff > 0) {
goodChoices.addAll(Aggregates.random(CardLists.filter(validCards, ((Predicate<Card>) goodChoices::contains).negate()), minDiff));
List<Card> choices = validCards.stream()
.filter(((Predicate<Card>) goodChoices::contains).negate())
.collect(StreamUtils.random(minDiff));
goodChoices.addAll(choices);
return goodChoices;
}

View File

@@ -234,11 +234,11 @@ public abstract class DeckGeneratorBase {
addSome(targetSize - actualSize, tDeck.toFlatList());
}
else if (actualSize > targetSize) {
Predicate<PaperCard> exceptBasicLand = PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND);
for (int i = 0; i < 3 && actualSize > targetSize; i++) {
Iterable<PaperCard> matchingCards = Iterables.filter(tDeck.toFlatList(), exceptBasicLand);
List<PaperCard> toRemove = Aggregates.random(matchingCards, actualSize - targetSize);
List<PaperCard> toRemove = tDeck.toFlatList().stream()
.filter(PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND))
.collect(StreamUtils.random(actualSize - targetSize));
tDeck.removeAllFlat(toRemove);
for (PaperCard c : toRemove) {

View File

@@ -21,12 +21,10 @@ package forge.item;
import forge.StaticData;
import forge.card.CardRulesPredicates;
import forge.item.generation.BoosterGenerator;
import forge.util.Aggregates;
import forge.util.StreamUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class SealedProduct implements InventoryItemFromSet {
@@ -112,8 +110,9 @@ public abstract class SealedProduct implements InventoryItemFromSet {
}
protected List<PaperCard> getRandomBasicLands(final String setCode, final int count) {
Predicate<PaperCard> cardsRule = PaperCardPredicates.printedInSet(setCode)
.and(PaperCardPredicates.fromRules(CardRulesPredicates.IS_BASIC_LAND));
return Aggregates.random(StaticData.instance().getCommonCards().streamAllCards().filter(cardsRule).collect(Collectors.toList()), count);
return StaticData.instance().getCommonCards().streamAllCards()
.filter(PaperCardPredicates.printedInSet(setCode))
.filter(PaperCardPredicates.fromRules(CardRulesPredicates.IS_BASIC_LAND))
.collect(StreamUtils.random(count));
}
}

View File

@@ -0,0 +1,106 @@
package forge.util;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class StreamUtils {
private StreamUtils(){}
/**
* Reduces a stream to a random element of the stream. Used with {@link Stream#collect}.
* Result will be wrapped in an Optional, absent only if the stream is empty.
*/
public static <T> Collector<T, ?, Optional<T>> random() {
return new RandomCollectorSingle<>();
}
/**
* Selects a number of items randomly from this stream. Used with {@link Stream#collect}.
* @param count Number of elements to select from the stream.
*/
public static <T> Collector<T, ?, List<T>> random(int count) {
return new RandomCollectorMulti<>(count);
}
private static abstract class RandomCollector<T, O> implements Collector<T, RandomReservoir<T>, O> {
private final int size;
RandomCollector(int size) {
this.size = size;
}
@Override
public Supplier<RandomReservoir<T>> supplier() {
return () -> new RandomReservoir<>(size);
}
@Override
public BiConsumer<RandomReservoir<T>, T> accumulator() {
return RandomReservoir::accumulate;
}
@Override
public BinaryOperator<RandomReservoir<T>> combiner() {
return (first, second) -> {
//There's probably a way to adapt the Random Reservoir method
//so that two partially processed lists can be combined into one.
//But I have no idea what that is.
throw new UnsupportedOperationException("Parallel streams not supported.");
};
}
private final EnumSet<Characteristics> CHARACTERISTICS = EnumSet.of(Characteristics.UNORDERED);
@Override
public Set<Characteristics> characteristics() {
return CHARACTERISTICS;
}
}
private static class RandomCollectorSingle<T> extends RandomCollector<T, Optional<T>> {
RandomCollectorSingle() {
super(1);
}
@Override
public Function<RandomReservoir<T>, Optional<T>> finisher() {
return (chosen) -> chosen.samples.isEmpty() ? Optional.empty() : Optional.of(chosen.samples.get(0));
}
}
private static class RandomCollectorMulti<T> extends RandomCollector<T, List<T>> {
RandomCollectorMulti(int size) {
super(size);
}
@Override
public Function<RandomReservoir<T>, List<T>> finisher() {
return (chosen) -> chosen.samples;
}
}
private static class RandomReservoir<T> {
final int maxSize;
ArrayList<T> samples;
int sampleCount = 0;
public RandomReservoir(int size) {
this.maxSize = size;
this.samples = new ArrayList<>(size);
}
public void accumulate(T next) {
sampleCount++;
if(sampleCount < maxSize) {
//Add the first [maxSize] items into the result list
samples.add(next);
return;
}
//Progressively reduce odds of adding an item into the reservoir
int j = MyRandom.getRandom().nextInt(sampleCount);
if(j < maxSize)
samples.set(j, next);
}
}
}

View File

@@ -131,8 +131,9 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
cpp = CardFacePredicates.valid(valid);
}
if (randomChoice) {
final Iterable<ICardFace> cards = Iterables.filter(StaticData.instance().getCommonCards().getAllFaces(), cpp);
chosen = Aggregates.random(cards).getName();
chosen = StaticData.instance().getCommonCards().streamAllFaces()
.filter(cpp).collect(StreamUtils.random()).get()
.getName();
} else {
chosen = p.getController().chooseCardName(sa, cpp, valid, message);
}

View File

@@ -1,9 +1,9 @@
package forge.game.ability.effects;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import forge.card.CardDb;
import forge.card.CardStateName;
import forge.card.GamePieceType;
import forge.item.PaperCardPredicates;
@@ -109,25 +109,28 @@ public class PlayEffect extends SpellAbilityEffect {
}
} else if (sa.hasParam("AnySupportedCard")) {
final String valid = sa.getParam("AnySupportedCard");
List<PaperCard> cards = null;
Stream<PaperCard> cards;
CardDb cardDb = StaticData.instance().getCommonCards();
if (valid.startsWith("Names:")) {
cards = new ArrayList<>();
for (String name : valid.substring(6).split(",")) {
name = name.replace(";", ",");
cards.add(StaticData.instance().getCommonCards().getUniqueByName(name));
}
cards = Arrays.stream(valid.substring(6).split(","))
.map(name -> name.replace(";", ","))
.map(cardDb::getUniqueByName);
} else if (valid.equalsIgnoreCase("sorcery")) {
final Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.IS_SORCERY);
cards = StaticData.instance().getCommonCards().streamUniqueCards().filter(cpp).collect(Collectors.toList());
cards = cardDb.streamUniqueCards()
.filter(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SORCERY));
} else if (valid.equalsIgnoreCase("instant")) {
final Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.IS_INSTANT);
cards = StaticData.instance().getCommonCards().streamUniqueCards().filter(cpp).collect(Collectors.toList());
cards = cardDb.streamUniqueCards()
.filter(PaperCardPredicates.fromRules(CardRulesPredicates.IS_INSTANT));
} else {
//Could just return a stream of all cards, but that should probably be a specific option rather than a fallback.
//Could also just leave it null but there's currently nothing else that can happen that case.
throw new UnsupportedOperationException("Unknown parameter for AnySupportedCard: " + valid);
}
if (sa.hasParam("RandomCopied")) {
final CardCollection choice = new CardCollection();
final String num = sa.getParamOrDefault("RandomNum", "1");
int ncopied = AbilityUtils.calculateAmount(source, num, sa);
for (PaperCard cp : Aggregates.random(cards, ncopied)) {
int nCopied = AbilityUtils.calculateAmount(source, num, sa);
for (PaperCard cp : cards.collect(StreamUtils.random(nCopied))) {
final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer());
if (sa.getActivatingPlayer().isAI() && possibleCard.getRules() != null && possibleCard.getRules().getAiHints().getRemAIDecks())
continue;

View File

@@ -22,9 +22,9 @@ import forge.model.FModel;
import forge.screens.deckeditor.CDeckEditorUI;
import forge.screens.deckeditor.SEditorIO;
import forge.screens.deckeditor.views.VDeckgen;
import forge.util.Aggregates;
import forge.util.Iterables;
import forge.util.StreamUtils;
import java.util.List;
import java.util.function.Predicate;
@@ -71,8 +71,10 @@ public enum CDeckgen implements ICDoc {
final Deck randomDeck = new Deck();
final Predicate<PaperCard> notBasicLand = PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND);
final Iterable<PaperCard> source = Iterables.filter(FModel.getMagicDb().getCommonCards().getUniqueCards(), notBasicLand);
randomDeck.getMain().addAllFlat(Aggregates.random(source, 15 * 5));
List<PaperCard> randomCards = FModel.getMagicDb().getCommonCards().streamUniqueCards()
.filter(notBasicLand)
.collect(StreamUtils.random(15 * 5));
randomDeck.getMain().addAllFlat(randomCards);
for(final String landName : MagicColor.Constant.BASIC_LANDS) {
randomDeck.getMain().add(landName, 1);

View File

@@ -237,8 +237,7 @@ public class DuelScene extends ForgeScene {
DeckProxy deckProxy = null;
if (chaosBattle) {
deckProxyMapMap = DeckProxy.getAllQuestChallenges();
List<DeckProxy> decks = new ArrayList<>(deckProxyMapMap.keySet());
deckProxy = Aggregates.random(decks);
deckProxy = Aggregates.random(deckProxyMapMap.keySet());
//playerextras
List<IPaperCard> playerCards = new ArrayList<>();
for (String s : deckProxyMapMap.get(deckProxy).getLeft()) {

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import forge.item.PaperCardPredicates;
import forge.util.*;
@@ -463,25 +464,18 @@ public class DeckgenUtil {
try {
if (useGeneticAI) {
if (!selection.isEmpty())
deck = Aggregates.random(Iterables.filter(geneticAI, deckProxy -> deckProxy.getColorIdentity().sharesColorWith(ColorSet.fromNames(colors.toCharArray())))).getDeck();
deck = geneticAI.stream()
.filter(deckProxy -> deckProxy.getColorIdentity().sharesColorWith(ColorSet.fromNames(colors.toCharArray())))
.collect(StreamUtils.random()).get().getDeck();
else
deck = Aggregates.random(geneticAI).getDeck();
} else {
Predicate<DeckProxy> sizePredicate = deckProxy -> deckProxy.getMainSize() <= 60;
if (!selection.isEmpty() && selection.size() < 4) {
Predicate<DeckProxy> colorPredicate = deckProxy -> deckProxy.getColorIdentity().hasAllColors(ColorSet.fromNames(colors.toCharArray()).getColor());
Predicate<DeckProxy> pred = sizePredicate.and(colorPredicate);
if (isTheme)
deck = Aggregates.random(Iterables.filter(advThemes, pred)).getDeck();
else
deck = Aggregates.random(Iterables.filter(advPrecons, pred)).getDeck();
} else {
if (isTheme)
deck = Aggregates.random(Iterables.filter(advThemes, sizePredicate)).getDeck();
else
deck = Aggregates.random(Iterables.filter(advPrecons, sizePredicate)).getDeck();
}
Predicate<DeckProxy> predicate = deckProxy -> deckProxy.getMainSize() <= 60;
if (!selection.isEmpty() && selection.size() < 4)
predicate = predicate.and(deckProxy -> deckProxy.getColorIdentity().hasAllColors(ColorSet.fromNames(colors.toCharArray()).getColor()));
List<DeckProxy> source = isTheme ? advThemes : advPrecons;
deck = source.stream().filter(predicate).collect(StreamUtils.random()).get().getDeck();
}
} catch (Exception e) {
e.printStackTrace();
@@ -658,18 +652,15 @@ public class DeckgenUtil {
/** Generate a 2-5-color Commander deck. */
public static Deck generateCommanderDeck(boolean forAi, GameType gameType) {
final Deck deck;
IDeckGenPool cardDb = FModel.getMagicDb().getCommonCards();
PaperCard commander;
ColorSet colorID;
// Get random multicolor Legendary creature
final DeckFormat format = gameType.getDeckFormat();
Predicate<CardRules> canPlay = forAi ? DeckGeneratorBase.AI_CAN_PLAY : CardRulesPredicates.IS_KEPT_IN_RANDOM_DECKS;
Predicate<PaperCard> legal = format.isLegalCardPredicate().and(format.isLegalCommanderPredicate());
Iterable<PaperCard> legends = cardDb.getAllCards(legal.and(PaperCardPredicates.fromRules(canPlay)));
commander = Aggregates.random(legends);
PaperCard commander = FModel.getMagicDb().getCommonCards().streamAllCards()
.filter(format.isLegalCardPredicate())
.filter(format.isLegalCommanderPredicate())
.filter(PaperCardPredicates.fromRules(canPlay))
.collect(StreamUtils.random()).get();
return generateRandomCommanderDeck(commander, format, forAi, false);
}