diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index ed5c0266d94..9d6107bb82b 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -193,6 +193,8 @@ public class PaperCard implements Comparable, InventoryItemFromSet, sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules0.getName())); } + public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common); + // Want this class to be a key for HashTable @Override public boolean equals(final Object obj) { diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java index 382104d339f..4a23e81f65d 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java @@ -122,6 +122,10 @@ public class CEditorDraftingProcess extends ACEditorBase i return; } + if (boosterDraft.getHumanPlayer().hasArchdemonCurse()) { + card = boosterDraft.getHumanPlayer().pickFromArchdemonCurse(boosterDraft.getHumanPlayer().nextChoice()); + } + // Verify if card is in the activate pack? this.getDeckManager().addItem(card, 1); @@ -144,6 +148,7 @@ public class CEditorDraftingProcess extends ACEditorBase i else { // TODO Deal Broker // Offer trades before saving + boosterDraft.postDraftActions(); this.saveDraft(); } @@ -178,9 +183,25 @@ public class CEditorDraftingProcess extends ACEditorBase i int packNumber = ((BoosterDraft) boosterDraft).getCurrentBoosterIndex() + 1; this.getCatalogManager().setCaption(localizer.getMessage("lblPackNCards", String.valueOf(packNumber))); - this.getCatalogManager().setPool(list); + + int count = list.countAll(); + + if (boosterDraft.getHumanPlayer().hasArchdemonCurse()) { + // Only show facedown cards with no information + this.getCatalogManager().setPool(generateFakePaperCards(count)); + } else { + this.getCatalogManager().setPool(list); + } } // showChoices() + private ItemPool generateFakePaperCards(int count) { + ItemPool pool = new ItemPool<>(PaperCard.class); + for (int i = 0; i < count; i++) { + pool.add(PaperCard.FAKE_CARD); + } + return pool; + } + /** *

* getPlayersDeck. diff --git a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java index fcd0f5467c3..dc4b3028ed0 100644 --- a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java +++ b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java @@ -1,8 +1,10 @@ package forge; +import forge.card.CardEdition; import forge.deck.CardPool; import forge.deck.Deck; import forge.game.card.Card; +import forge.gamemodes.limited.DraftPack; import forge.gamemodes.limited.IBoosterDraft; import forge.gamemodes.limited.IDraftLog; import forge.gamemodes.limited.LimitedPlayer; @@ -79,6 +81,11 @@ public class BoosterDraftTest implements IBoosterDraft { return hasNextChoice(); } + @Override + public DraftPack addBooster(CardEdition edition) { + return null; + } + public List getChosenCards() { return null; } @@ -111,4 +118,7 @@ public class BoosterDraftTest implements IBoosterDraft { public LimitedPlayer getPlayer(int i) { return null; } + + @Override + public void postDraftActions() {} } diff --git a/forge-gui/res/cardsfolder/a/archdemon_of_paliano.txt b/forge-gui/res/cardsfolder/a/archdemon_of_paliano.txt new file mode 100644 index 00000000000..b46f58b1c85 --- /dev/null +++ b/forge-gui/res/cardsfolder/a/archdemon_of_paliano.txt @@ -0,0 +1,8 @@ +Name:Archdemon of Paliano +ManaCost:2 B B +Types:Creature Demon +PT:5/4 +Draft:Draft CARDNAME face up. +Draft:As long as CARDNAME is face up during the draft, you can’t look at booster packs and must draft cards at random. After you draft three cards this way, turn CARDNAME face down. (You may look at cards as you draft them.) +K:Flying +Oracle:Draft Archdemon of Paliano face up.\n\nAs long as Archdemon of Paliano is face up during the draft, you can’t look at booster packs and must draft cards at random. After you draft three cards this way, turn Archdemon of Paliano face down. (You may look at cards as you draft them.)\nFlying \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/canal_dredger.txt b/forge-gui/res/cardsfolder/c/canal_dredger.txt new file mode 100644 index 00000000000..58b116b43c1 --- /dev/null +++ b/forge-gui/res/cardsfolder/c/canal_dredger.txt @@ -0,0 +1,8 @@ +Name:Canal Dredger +ManaCost:4 +Types:Artifact Creature Construct +PT:1/5 +Draft:Draft Canal Dredger face up. +Draft:Each player passes the last card from each booster pack to a player who drafted a card named CARDNAME. +A:AB$ ChangeZone | Cost$ T | ValidTgts$ Card.YouOwn | TgtPrompt$ Select target card in your graveyard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put target card from your graveyard on the bottom of your library. +Oracle:Draft Canal Dredger face up.\nEach player passes the last card from each booster pack to a player who drafted a card named Canal Dredger.\n{T}: Put target card from your graveyard on the bottom of your library. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/d/deal_broker.txt b/forge-gui/res/cardsfolder/d/deal_broker.txt new file mode 100644 index 00000000000..74849064e11 --- /dev/null +++ b/forge-gui/res/cardsfolder/d/deal_broker.txt @@ -0,0 +1,10 @@ +Name:Deal Broker +ManaCost:3 +PT:2/3 +Types:Artifact Creature Construct +Draft:Draft CARDNAME face up. +Draft:Immediately after the draft, you may reveal a card in your card pool. Each other player may offer you one card in their card pool in exchange. You may accept any one offer. +A:AB$ Draw | Cost$ T | Defined$ You | NumCards$ 1 | SubAbility$ Discard | SpellDescription$ Draw a card, then discard a card. +SVar:Discard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose +Oracle:Draft Deal Broker face up.\nImmediately after the draft, you may reveal a card in your card pool. Each other player may offer you one card in their card pool in exchange. You may accept any one offer.\n{T}: Draw a card, then discard a card. + diff --git a/forge-gui/res/cardsfolder/l/lore_seeker.txt b/forge-gui/res/cardsfolder/l/lore_seeker.txt new file mode 100644 index 00000000000..14614cf8428 --- /dev/null +++ b/forge-gui/res/cardsfolder/l/lore_seeker.txt @@ -0,0 +1,7 @@ +Name:Lore Seeker +ManaCost:2 +PT:2/2 +Types:Artifact Creature Construct +Draft:Reveal CARDNAME as you draft it. +Draft:After you draft CARDNAME, you may add a booster pack to the draft. (Your next pick is from that booster pack. Pass it to the next player and it’s drafted this draft round.) +Oracle:Reveal Lore Seeker as you draft it. After you draft Lore Seeker, you may add a booster pack to the draft. (Your next pick is from that booster pack. Pass it to the next player and it’s drafted this draft round.) \ No newline at end of file diff --git a/forge-gui/res/editions/Conspiracy Take the Crown.txt b/forge-gui/res/editions/Conspiracy Take the Crown.txt index 70f7fe7701c..1a40caa4c25 100644 --- a/forge-gui/res/editions/Conspiracy Take the Crown.txt +++ b/forge-gui/res/editions/Conspiracy Take the Crown.txt @@ -238,6 +238,7 @@ ScryfallCode=CN2 [Draft Matters] Adriana's Valor Animus of Predation +Archdemon of Paliano Assemble the Rank and Vile Custodi Peacekeeper Echoing Boon @@ -254,6 +255,7 @@ Noble Banneret Paliano Vanguard Pyretic Hunter Regicide +Spire Phantasm Sovereign's Realm Summoner's Bond Weight Advantage diff --git a/forge-gui/res/editions/Conspiracy.txt b/forge-gui/res/editions/Conspiracy.txt index 2c60fc063e6..871cd12021c 100644 --- a/forge-gui/res/editions/Conspiracy.txt +++ b/forge-gui/res/editions/Conspiracy.txt @@ -226,16 +226,16 @@ ScryfallCode=CNS 1 Agent of Acquisitions|CNS #1 Backup Plan|CNS 1 Brago's Favor|CNS -#1 Canal Dredger|CNS +1 Canal Dredger|CNS 1 Cogwork Grinder|CNS 1 Cogwork Librarian|CNS 1 Cogwork Spy|CNS 1 Cogwork Tracker|CNS -#1 Deal Broker|CNS +1 Deal Broker|CNS 1 Double Stroke|CNS 1 Immediate Action|CNS 1 Iterative Analysis|CNS -#1 Lore Seeker|CNS +1 Lore Seeker|CNS 1 Lurking Automaton|CNS 1 Muzzio's Preparations|CNS 1 Paliano, the High City|CNS diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java index 724c532d05e..b1f307c0bf6 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java @@ -50,7 +50,7 @@ import java.util.*; */ public class BoosterDraft implements IBoosterDraft { - private static int nextId = 0; + private int nextId = 0; private static final int N_PLAYERS = 8; public static final String FILE_EXT = ".draft"; private final List players = new ArrayList<>(); @@ -271,6 +271,11 @@ public class BoosterDraft implements IBoosterDraft { } } + public DraftPack addBooster(CardEdition edition) { + final IUnOpenedProduct product = new UnOpenedProduct(FModel.getMagicDb().getBoosters().get(edition.getCode())); + return new DraftPack(product.get(), nextId++); + } + @Override public boolean isPileDraft() { return false; @@ -441,25 +446,52 @@ public class BoosterDraft implements IBoosterDraft { } // Do any players have a Canal Dredger? + List dredgers = new ArrayList<>(); + for (LimitedPlayer pl : this.players) { + if (pl.hasCanalDredger()) { + dredgers.add(pl); + } + } for (int i = 0; i < N_PLAYERS; i++) { - DraftPack passingPack = this.players.get(i).passPack(); + LimitedPlayer pl = this.players.get(i); + DraftPack passingPack = pl.passPack(); if (passingPack == null) continue; - if (!passingPack.isEmpty()) { - if (passingPack.size() == 1) { - // TODO Canal Dredger for passing a pack with a single card in it - - } - - int passTo = (i + adjust + N_PLAYERS) % N_PLAYERS; - this.players.get(passTo).receiveOpenedPack(passingPack); - this.players.get(passTo).adjustPackNumber(adjust, packsInDraft); - } else { + LimitedPlayer passToPlayer = null; + if (passingPack.isEmpty()) { packsInDraft--; + continue; } + + if (passingPack.size() == 1) { + if (dredgers.size() == 1) { + passToPlayer = dredgers.get(0); + } else if (dredgers.size() > 1) { + // Multiple dredgers, so we need to choose one to pass to + if (dredgers.contains(pl)) { + // If the current player has a Canal Dredger, they should pass to themselves + passToPlayer = pl; + } else if (pl instanceof LimitedPlayerAI) { + // Maybe the AI could have more knowledge about the other players. + // Like don't pass to players that have revealed certain cards or colors + // But random is probably fine for now + Collections.shuffle(dredgers); + passToPlayer = dredgers.get(0); + } else { + // Human player, so we need to ask them + passToPlayer = SGuiChoose.one("Which player with Canal Dredger should we pass the last card to?", dredgers); + } + } + } + + if (passToPlayer == null) { + passToPlayer = this.players.get((i + adjust + N_PLAYERS) % N_PLAYERS); + } + + passToPlayer.receiveOpenedPack(passingPack); } } @@ -467,8 +499,6 @@ public class BoosterDraft implements IBoosterDraft { // Loop through players 1-7 to draft their current pack for (int i = 1; i < N_PLAYERS; i++) { LimitedPlayer pl = this.players.get(i); - // TODO Agent of Acquisitions activation to loop the entire pack? - if (pl.shouldSkipThisPick()) { continue; } @@ -525,6 +555,22 @@ public class BoosterDraft implements IBoosterDraft { return passPack; } + public void postDraftActions() { + List brokers = new ArrayList<>(); + for (LimitedPlayer pl : this.players) { + if (pl.hasBrokers()) { + brokers.add(pl); + } + } + + Collections.shuffle(brokers); + for(LimitedPlayer pl : brokers) { + pl.activateBrokers(this.players); + } + + } + + private static String choosePackByPack(final List setz, int packs) { StringBuilder sb = new StringBuilder(); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java index 61f7baa244a..e0772bfb8a5 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java @@ -66,7 +66,7 @@ public class CardRanker { return sortAndCreateList(cardScores); } - private static List> getScores(Iterable cards) { + public static List> getScores(Iterable cards) { List> cardScores = new ArrayList<>(); List cache = Lists.newArrayList(cards); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java index 2f1fd74edf3..1dddd6725dc 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java @@ -31,12 +31,12 @@ import forge.item.PaperCard; * @version $Id$ */ public interface IBoosterDraft { - int getRound(); CardPool nextChoice(); boolean setChoice(PaperCard c); boolean hasNextChoice(); boolean isRoundOver(); + DraftPack addBooster(CardEdition edition); Deck[] getDecks(); // size 7, all the computers decks LimitedPlayer[] getOpposingPlayers(); // size 7, all the computers LimitedPlayer getHumanPlayer(); @@ -47,8 +47,7 @@ public interface IBoosterDraft { void setLogEntry(IDraftLog draftingProcess); IDraftLog getDraftLog(); + void postDraftActions(); LimitedPlayer getNeighbor(LimitedPlayer p, boolean left); LimitedPlayer getPlayer(int i); - - } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java index 13797ef6082..d48d47cb994 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -3,12 +3,14 @@ package forge.gamemodes.limited; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import forge.card.CardEdition; import forge.card.MagicColor; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; import forge.gui.util.SGuiChoose; import forge.item.PaperCard; +import forge.model.FModel; import forge.util.TextUtil; import java.util.*; @@ -25,6 +27,9 @@ public class LimitedPlayer { protected Queue unopenedPacks; protected List removedFromCardPool = new ArrayList<>(); + protected List archdemonFavors; + protected int dealBrokers = 0; + private static final int AgentAcquisitionsCanDraftAll = 1; private static final int AgentAcquisitionsIsDraftingAll = 1 << 1; private static final int AgentAcquisitionsSkipDraftRound = 1 << 2; @@ -41,6 +46,8 @@ public class LimitedPlayer { private static final int LeovoldsOperativeExtraDraft = 1 << 13; private static final int LeovoldsOperativeSkipNext = 1 << 14; private static final int SpyNextCardDrafted = 1 << 15; + private static final int CanalDredgerLastPick = 1 << 16; + private static final int ArchdemonOfPalianoCurse = 1 << 17; private int playerFlags = 0; @@ -57,6 +64,7 @@ public class LimitedPlayer { packQueue = new LinkedList<>(); unopenedPacks = new LinkedList<>(); + archdemonFavors = new ArrayList<>(); this.draft = draft; } @@ -87,8 +95,6 @@ public class LimitedPlayer { public PaperCard chooseCard() { // A non-AI LimitedPlayer chooses cards via the UI instead of this function - // TODO Archdemon of Paliano random draft while active - return null; } @@ -295,8 +301,6 @@ public class LimitedPlayer { if (Iterables.contains(draftActions, "You may look at the next card drafted from this booster pack.")) { playerFlags |= SpyNextCardDrafted; } else if (fromPlayer != null && Iterables.contains(draftActions, "Note the player who passed CARDNAME to you.")) { - // Note who passed it to you. - // If you receive last card from Canal Dredger, we need to figure out who last had the pack? List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); note.add(String.valueOf(fromPlayer.order)); addLog(name() + " revealed " + bestPick.getName() + " and noted " + fromPlayer.name() + " passed it."); @@ -304,6 +308,8 @@ public class LimitedPlayer { playerFlags |= SearcherNoteNext; } else if (Iterables.contains(draftActions, "The next time a player drafts a card from this booster pack, guess that card’s name. Then that player reveals the drafted card.")) { chooseFrom.setAwaitingGuess(this, handleSpirePhantasm(chooseFrom)); + } else if (Iterables.contains(draftActions, "After you draft CARDNAME, you may add a booster pack to the draft. (Your next pick is from that booster pack. Pass it to the next player and it’s drafted this draft round.)")) { + addSingleBoosterPack(); } addLog(name() + " revealed " + bestPick.getName() + " as " + name() + " drafted it."); @@ -337,12 +343,16 @@ public class LimitedPlayer { playerFlags |= LeovoldsOperativeExtraDraft; } else if (Iterables.contains(draftActions, "Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn CARDNAME face down and you can’t draft cards for the rest of this draft round. (You may look at booster packs passed to you.)")) { playerFlags |= AgentAcquisitionsCanDraftAll; + } else if (Iterables.contains(draftActions, "Each player passes the last card from each booster pack to a player who drafted a card named CARDNAME.")) { + playerFlags |= CanalDredgerLastPick; + } else if (Iterables.contains(draftActions, "As long as CARDNAME is face up during the draft, you can’t look at booster packs and must draft cards at random. After you draft three cards this way, turn CARDNAME face down. (You may look at cards as you draft them.)")) { + playerFlags |= ArchdemonOfPalianoCurse; + archdemonFavors.add(3); + } else if (Iterables.contains(draftActions, "Immediately after the draft, you may reveal a card in your card pool. Each other player may offer you one card in their card pool in exchange. You may accept any one offer.")) { + dealBrokers++; } } - // TODO Lore Seeker - // This adds a pack and MIGHT screw up all of our assumptions about pack passing. Do this last probably - return true; } @@ -354,7 +364,12 @@ public class LimitedPlayer { } public DraftPack nextChoice() { - return packQueue.peek(); + DraftPack pack = packQueue.peek(); + if (pack != null) { + adjustPackNumber(pack); + } + + return pack; } public void newPack() { @@ -363,9 +378,9 @@ public class LimitedPlayer { packQueue.add(unopenedPacks.poll()); playerFlags &= ~AgentAcquisitionsSkipDraftRound; } - public void adjustPackNumber(int adjust, int numPacks) { - // I shouldn't need this since DraftPack has this info - currentPack = (currentPack + adjust + numPacks) % numPacks; + + public void adjustPackNumber(DraftPack pack) { + currentPack = pack.getId(); } public DraftPack passPack() { @@ -386,6 +401,10 @@ public class LimitedPlayer { return skipping; } + public boolean hasCanalDredger() { + return (playerFlags & CanalDredgerLastPick) != CanalDredgerLastPick; + } + public void receiveUnopenedPack(DraftPack pack) { unopenedPacks.add(pack); } @@ -669,11 +688,110 @@ public class LimitedPlayer { pack.resetAwaitingGuess(); } - - /* - public void addSingleBoosterPack(boolean random) { - // TODO Lore Seeker - // Generate booster pack then, insert it "before" the pack we're currently drafting from + public boolean hasArchdemonCurse() { + return (playerFlags & ArchdemonOfPalianoCurse) == ArchdemonOfPalianoCurse; + } + + public boolean hasBrokers() { + return dealBrokers > 0; + } + + public void reduceArchdemonOfPalianoCurse() { + if (hasArchdemonCurse()) { + archdemonFavors.replaceAll(integer -> integer - 1); + archdemonFavors.removeIf(integer -> integer <= 0); + if (archdemonFavors.isEmpty()) { + playerFlags &= ~ArchdemonOfPalianoCurse; + } + } + } + + public PaperCard pickFromArchdemonCurse(DraftPack chooseFrom) { + Collections.shuffle(chooseFrom); + reduceArchdemonOfPalianoCurse(); + return chooseFrom.get(0); + } + + public void addSingleBoosterPack() { + // if this is just a normal draft, allow picking a pack from any set + // If this is adventure or quest or whatever then we should limit it to something + List possibleEditions = Lists.newArrayList(Iterables.filter(FModel.getMagicDb().getEditions(), CardEdition.Predicates.CAN_MAKE_BOOSTER)); + CardEdition edition = chooseEdition(possibleEditions); + if (edition == null) { + addLog(name() + " chose not to add a booster pack to the draft."); + return; + } + + packQueue.add(draft.addBooster(edition)); + addLog(name() + " added " + edition.getName() + " to be drafted this round"); + } + + protected CardEdition chooseEdition(List possibleEditions) { + return SGuiChoose.oneOrNone("Choose a booster pack to add to the draft", possibleEditions); + } + + public void activateBrokers(List players) { + while(dealBrokers > 0) { + dealBrokers--; + addLog(name() + " activated Deal Broker."); + + PaperCard exchangeCard = chooseExchangeCard(null); + Map offers = new HashMap<>(); + for(LimitedPlayer player : players) { + if (player == this) { + continue; + } + + PaperCard offer = player.chooseExchangeCard(exchangeCard); + if (offer == null) { + continue; + } + + addLog(player.name() + " offered " + offer.getName() + " to " + name() + " for " + exchangeCard.getName()); + offers.put(offer, player); + } + + PaperCard exchangeOffer = chooseCardToExchange(exchangeCard, offers); + if (exchangeOffer == null) { + addLog(name() + " chose not to accept any offers."); + continue; + } + exchangeAcceptedOffer(exchangeCard, offers.get(exchangeOffer), exchangeOffer); + } + } + + protected PaperCard chooseExchangeCard(PaperCard offer) { + // Choose a card in your deck to trade for offer + List deckCards = deck.getOrCreate(DeckSection.Sideboard).toFlatList(); + + if (offer == null) { + return SGuiChoose.oneOrNone("Choose a card to offer for trade: ", deckCards); + } + + return SGuiChoose.oneOrNone("Choose a card to trade for " + offer.getName() + ": ", deckCards); + } + + protected PaperCard chooseCardToExchange(PaperCard exchangeCard, Map offers) { + return SGuiChoose.oneOrNone("Choose a card to accept trade of " + exchangeCard + ": ", offers.keySet()); + } + + protected void exchangeAcceptedOffer(PaperCard exchangeCard, LimitedPlayer player, PaperCard offer) { + addLog(name() + " accepted the offer of " + exchangeCard + " for " + offer + " from " + player.name() + "."); + + player.getDeck().removeCardName(offer.getName()); + player.getDeck().get(DeckSection.Sideboard).add(exchangeCard); + deck.removeCardName(exchangeCard.getName()); + deck.get(DeckSection.Sideboard).add(offer); + + // Exchange noted information + player.getDraftNotes().getOrDefault(offer.getName(), Lists.newArrayList()).forEach(note -> { + List noteList = noted.computeIfAbsent(offer.getName(), k -> Lists.newArrayList()); + noteList.add(note); + }); + + this.getDraftNotes().getOrDefault(exchangeCard.getName(), Lists.newArrayList()).forEach(note -> { + List noteList = player.getDraftNotes().computeIfAbsent(exchangeCard.getName(), k -> Lists.newArrayList()); + noteList.add(note); + }); } - */ } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java index ab5bc6e83c1..bd2e17d10b3 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java @@ -1,15 +1,21 @@ package forge.gamemodes.limited; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import forge.card.CardEdition; import forge.card.ColorSet; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; +import forge.deck.generation.DeckGeneratorBase; import forge.item.PaperCard; import forge.localinstance.properties.ForgePreferences; import forge.util.MyRandom; +import org.apache.commons.lang3.tuple.Pair; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import static forge.gamemodes.limited.CardRanker.getOrderedRawScores; @@ -39,16 +45,19 @@ public class LimitedPlayerAI extends LimitedPlayer { System.out.println("Player[" + order + "] pack: " + chooseFrom); } - // TODO Archdemon of Paliano random draft while active + PaperCard bestPick; + if (hasArchdemonCurse()) { + bestPick = pickFromArchdemonCurse(chooseFrom); + } else { + final ColorSet chosenColors = deckCols.getChosenColors(); + final boolean canAddMoreColors = deckCols.canChoseMoreColors(); - final ColorSet chosenColors = deckCols.getChosenColors(); - final boolean canAddMoreColors = deckCols.canChoseMoreColors(); + List rankedCards = rankCardsInPack(chooseFrom, pool.toFlatList(), chosenColors, canAddMoreColors); + bestPick = rankedCards.get(0); - List rankedCards = rankCardsInPack(chooseFrom, pool.toFlatList(), chosenColors, canAddMoreColors); - PaperCard bestPick = rankedCards.get(0); - - if (canAddMoreColors) { - deckCols.addColorsOf(bestPick); + if (canAddMoreColors) { + deckCols.addColorsOf(bestPick); + } } if (ForgePreferences.DEV_MODE) { @@ -222,4 +231,65 @@ public class LimitedPlayerAI extends LimitedPlayer { return draftedThisRound < 3; } + @Override + protected CardEdition chooseEdition(List possibleEditions) { + Collections.shuffle(possibleEditions); + return possibleEditions.get(0); + } + + @Override + protected PaperCard chooseExchangeCard(PaperCard offer) { + final ColorSet colors = deckCols.getChosenColors(); + List deckCards = deck.getOrCreate(DeckSection.Sideboard).toFlatList(); + + DeckGeneratorBase.MatchColorIdentity hasColor = new DeckGeneratorBase.MatchColorIdentity(colors); + Iterable colorList = Iterables.filter(deckCards, + Predicates.not(Predicates.compose(hasColor, PaperCard.FN_GET_RULES))); + + PaperCard exchangeCard = null; + + if (offer == null) { + // Choose the highest rated card outside your colors + List rankedColorList = CardRanker.rankCardsInDeck(colorList); + return rankedColorList.get(0); + } + + // Choose a card in my deck outside my colors with similar value + List> rankedColorList = CardRanker.getScores(colorList); + double score = CardRanker.getRawScore(offer); + double closestScore = Double.POSITIVE_INFINITY; + + for (Pair pair : rankedColorList) { + double diff = Math.abs(pair.getLeft() - score); + if (diff < closestScore) { + closestScore = diff; + exchangeCard = pair.getRight(); + } + } + + return exchangeCard; + } + + protected PaperCard chooseCardToExchange(PaperCard exchangeCard, Map offers) { + double score = CardRanker.getRawScore(exchangeCard); + List> rankedColorList = CardRanker.getScores(offers.keySet()); + final ColorSet colors = deckCols.getChosenColors(); + for(Pair pair : rankedColorList) { + ColorSet cardColors = pair.getRight().getRules().getColorIdentity(); + if (!cardColors.hasNoColorsExcept(colors)) { + continue; + } + + if (score < pair.getLeft()) { + return pair.getRight(); + } + + double threshold = Math.abs(pair.getLeft() - score) / pair.getLeft(); + if (threshold < 0.1) { + return pair.getRight(); + } + } + + return null; + } }