From 05d58c2fce52c329f15e370470a3d415499f07c4 Mon Sep 17 00:00:00 2001 From: Jetz Date: Sun, 22 Sep 2024 14:47:57 -0400 Subject: [PATCH] Guava migration - Add `StreamUtils.random` collectors --- .../src/main/java/forge/ai/ComputerUtil.java | 5 +- .../deck/generation/DeckGeneratorBase.java | 6 +- .../main/java/forge/item/SealedProduct.java | 11 +- .../src/main/java/forge/util/StreamUtils.java | 106 ++++++++++++++++++ .../ability/effects/ChooseCardNameEffect.java | 5 +- .../game/ability/effects/PlayEffect.java | 31 ++--- .../deckeditor/controllers/CDeckgen.java | 10 +- .../src/forge/adventure/scene/DuelScene.java | 3 +- .../src/main/java/forge/deck/DeckgenUtil.java | 37 +++--- 9 files changed, 159 insertions(+), 55 deletions(-) create mode 100644 forge-core/src/main/java/forge/util/StreamUtils.java diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 91317e57a54..70b59bfba78 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -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) goodChoices::contains).negate()), minDiff)); + List choices = validCards.stream() + .filter(((Predicate) goodChoices::contains).negate()) + .collect(StreamUtils.random(minDiff)); + goodChoices.addAll(choices); return goodChoices; } diff --git a/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java b/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java index 15a056da475..b55a94183ad 100644 --- a/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java +++ b/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java @@ -234,11 +234,11 @@ public abstract class DeckGeneratorBase { addSome(targetSize - actualSize, tDeck.toFlatList()); } else if (actualSize > targetSize) { - Predicate exceptBasicLand = PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND); for (int i = 0; i < 3 && actualSize > targetSize; i++) { - Iterable matchingCards = Iterables.filter(tDeck.toFlatList(), exceptBasicLand); - List toRemove = Aggregates.random(matchingCards, actualSize - targetSize); + List toRemove = tDeck.toFlatList().stream() + .filter(PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND)) + .collect(StreamUtils.random(actualSize - targetSize)); tDeck.removeAllFlat(toRemove); for (PaperCard c : toRemove) { diff --git a/forge-core/src/main/java/forge/item/SealedProduct.java b/forge-core/src/main/java/forge/item/SealedProduct.java index f2fb3ea7743..9dda5706c71 100644 --- a/forge-core/src/main/java/forge/item/SealedProduct.java +++ b/forge-core/src/main/java/forge/item/SealedProduct.java @@ -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 getRandomBasicLands(final String setCode, final int count) { - Predicate 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)); } } diff --git a/forge-core/src/main/java/forge/util/StreamUtils.java b/forge-core/src/main/java/forge/util/StreamUtils.java new file mode 100644 index 00000000000..f112ba1b3f5 --- /dev/null +++ b/forge-core/src/main/java/forge/util/StreamUtils.java @@ -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 Collector> 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 Collector> random(int count) { + return new RandomCollectorMulti<>(count); + } + + private static abstract class RandomCollector implements Collector, O> { + private final int size; + RandomCollector(int size) { + this.size = size; + } + + @Override + public Supplier> supplier() { + return () -> new RandomReservoir<>(size); + } + + @Override + public BiConsumer, T> accumulator() { + return RandomReservoir::accumulate; + } + + @Override + public BinaryOperator> 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 = EnumSet.of(Characteristics.UNORDERED); + @Override + public Set characteristics() { + return CHARACTERISTICS; + } + } + + private static class RandomCollectorSingle extends RandomCollector> { + RandomCollectorSingle() { + super(1); + } + + @Override + public Function, Optional> finisher() { + return (chosen) -> chosen.samples.isEmpty() ? Optional.empty() : Optional.of(chosen.samples.get(0)); + } + } + + private static class RandomCollectorMulti extends RandomCollector> { + RandomCollectorMulti(int size) { + super(size); + } + + @Override + public Function, List> finisher() { + return (chosen) -> chosen.samples; + } + } + + private static class RandomReservoir { + final int maxSize; + ArrayList 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); + } + } +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java index f47ef140fd7..866a5e8f8ff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java @@ -131,8 +131,9 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { cpp = CardFacePredicates.valid(valid); } if (randomChoice) { - final Iterable 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); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 43d9de299b7..790cb14caf3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -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 cards = null; + Stream 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 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 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; diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CDeckgen.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CDeckgen.java index fb548eb6aa0..3a3fe4d8c7a 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CDeckgen.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CDeckgen.java @@ -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 notBasicLand = PaperCardPredicates.fromRules(CardRulesPredicates.NOT_BASIC_LAND); - final Iterable source = Iterables.filter(FModel.getMagicDb().getCommonCards().getUniqueCards(), notBasicLand); - randomDeck.getMain().addAllFlat(Aggregates.random(source, 15 * 5)); + List 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); diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index 6c711650c75..a76767ae64f 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -237,8 +237,7 @@ public class DuelScene extends ForgeScene { DeckProxy deckProxy = null; if (chaosBattle) { deckProxyMapMap = DeckProxy.getAllQuestChallenges(); - List decks = new ArrayList<>(deckProxyMapMap.keySet()); - deckProxy = Aggregates.random(decks); + deckProxy = Aggregates.random(deckProxyMapMap.keySet()); //playerextras List playerCards = new ArrayList<>(); for (String s : deckProxyMapMap.get(deckProxy).getLeft()) { diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java index 0b43529be0b..5e5421a3753 100644 --- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java +++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java @@ -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 sizePredicate = deckProxy -> deckProxy.getMainSize() <= 60; - if (!selection.isEmpty() && selection.size() < 4) { - Predicate colorPredicate = deckProxy -> deckProxy.getColorIdentity().hasAllColors(ColorSet.fromNames(colors.toCharArray()).getColor()); - Predicate 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 predicate = deckProxy -> deckProxy.getMainSize() <= 60; + if (!selection.isEmpty() && selection.size() < 4) + predicate = predicate.and(deckProxy -> deckProxy.getColorIdentity().hasAllColors(ColorSet.fromNames(colors.toCharArray()).getColor())); + List 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 canPlay = forAi ? DeckGeneratorBase.AI_CAN_PLAY : CardRulesPredicates.IS_KEPT_IN_RANDOM_DECKS; - Predicate legal = format.isLegalCardPredicate().and(format.isLegalCommanderPredicate()); - Iterable 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); }