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 // not enough good choices, need to fill the rest
int minDiff = min - goodChoices.size(); int minDiff = min - goodChoices.size();
if (minDiff > 0) { 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; return goodChoices;
} }

View File

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

View File

@@ -21,12 +21,10 @@ package forge.item;
import forge.StaticData; import forge.StaticData;
import forge.card.CardRulesPredicates; import forge.card.CardRulesPredicates;
import forge.item.generation.BoosterGenerator; import forge.item.generation.BoosterGenerator;
import forge.util.Aggregates; import forge.util.StreamUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class SealedProduct implements InventoryItemFromSet { 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) { protected List<PaperCard> getRandomBasicLands(final String setCode, final int count) {
Predicate<PaperCard> cardsRule = PaperCardPredicates.printedInSet(setCode) return StaticData.instance().getCommonCards().streamAllCards()
.and(PaperCardPredicates.fromRules(CardRulesPredicates.IS_BASIC_LAND)); .filter(PaperCardPredicates.printedInSet(setCode))
return Aggregates.random(StaticData.instance().getCommonCards().streamAllCards().filter(cardsRule).collect(Collectors.toList()), count); .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); cpp = CardFacePredicates.valid(valid);
} }
if (randomChoice) { if (randomChoice) {
final Iterable<ICardFace> cards = Iterables.filter(StaticData.instance().getCommonCards().getAllFaces(), cpp); chosen = StaticData.instance().getCommonCards().streamAllFaces()
chosen = Aggregates.random(cards).getName(); .filter(cpp).collect(StreamUtils.random()).get()
.getName();
} else { } else {
chosen = p.getController().chooseCardName(sa, cpp, valid, message); chosen = p.getController().chooseCardName(sa, cpp, valid, message);
} }

View File

@@ -1,9 +1,9 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.stream.Stream;
import java.util.stream.Collectors;
import forge.card.CardDb;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.GamePieceType; import forge.card.GamePieceType;
import forge.item.PaperCardPredicates; import forge.item.PaperCardPredicates;
@@ -109,25 +109,28 @@ public class PlayEffect extends SpellAbilityEffect {
} }
} else if (sa.hasParam("AnySupportedCard")) { } else if (sa.hasParam("AnySupportedCard")) {
final String valid = sa.getParam("AnySupportedCard"); final String valid = sa.getParam("AnySupportedCard");
List<PaperCard> cards = null; Stream<PaperCard> cards;
CardDb cardDb = StaticData.instance().getCommonCards();
if (valid.startsWith("Names:")) { if (valid.startsWith("Names:")) {
cards = new ArrayList<>(); cards = Arrays.stream(valid.substring(6).split(","))
for (String name : valid.substring(6).split(",")) { .map(name -> name.replace(";", ","))
name = name.replace(";", ","); .map(cardDb::getUniqueByName);
cards.add(StaticData.instance().getCommonCards().getUniqueByName(name));
}
} else if (valid.equalsIgnoreCase("sorcery")) { } else if (valid.equalsIgnoreCase("sorcery")) {
final Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.IS_SORCERY); cards = cardDb.streamUniqueCards()
cards = StaticData.instance().getCommonCards().streamUniqueCards().filter(cpp).collect(Collectors.toList()); .filter(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SORCERY));
} else if (valid.equalsIgnoreCase("instant")) { } else if (valid.equalsIgnoreCase("instant")) {
final Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.IS_INSTANT); cards = cardDb.streamUniqueCards()
cards = StaticData.instance().getCommonCards().streamUniqueCards().filter(cpp).collect(Collectors.toList()); .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")) { if (sa.hasParam("RandomCopied")) {
final CardCollection choice = new CardCollection(); final CardCollection choice = new CardCollection();
final String num = sa.getParamOrDefault("RandomNum", "1"); final String num = sa.getParamOrDefault("RandomNum", "1");
int ncopied = AbilityUtils.calculateAmount(source, num, sa); int nCopied = AbilityUtils.calculateAmount(source, num, sa);
for (PaperCard cp : Aggregates.random(cards, ncopied)) { for (PaperCard cp : cards.collect(StreamUtils.random(nCopied))) {
final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer()); final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer());
if (sa.getActivatingPlayer().isAI() && possibleCard.getRules() != null && possibleCard.getRules().getAiHints().getRemAIDecks()) if (sa.getActivatingPlayer().isAI() && possibleCard.getRules() != null && possibleCard.getRules().getAiHints().getRemAIDecks())
continue; continue;

View File

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

View File

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

View File

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