Backup Plan with hot swappable Input

This commit is contained in:
Chris H
2024-06-30 12:30:05 -04:00
parent 7b3d98aabd
commit b03b3517ea
12 changed files with 279 additions and 120 deletions

View File

@@ -36,6 +36,7 @@ import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
@@ -853,6 +854,22 @@ public class PlayerControllerAi extends PlayerController {
return brains.chooseSaToActivateFromOpeningHand(usableFromOpeningHand);
}
@Override
public PlayerZone chooseStartingHand(List<PlayerZone> zones) {
// Rate all the hands using the AI's hand evaluation function
int bestScore = Integer.MIN_VALUE;
PlayerZone bestZone = null;
for (PlayerZone zone : zones) {
int score = ComputerUtil.scoreHand(zone.getCards(), this.player, 0);
if (score > bestScore) {
bestScore = score;
bestZone = zone;
}
}
return bestZone;
}
@Override
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
return brains.chooseNumber(sa, title, min, max);

View File

@@ -31,6 +31,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.event.*;
import forge.game.extrahands.BackupPlanService;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mulligan.MulliganService;
@@ -2074,7 +2075,10 @@ public class GameAction {
p1.drawCards(p1.getStartingHandSize());
}
// If pl has Backup Plan as a Conspiracy draw that many extra hands
BackupPlanService backupPlans = new BackupPlanService(p1);
if (backupPlans.initializeExtraHands()) {
backupPlans.chooseHand();
}
}
// Choose starting hand for each player with multiple hands

View File

@@ -0,0 +1,78 @@
package forge.game.extrahands;
import com.google.common.collect.Lists;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import java.util.List;
public class BackupPlanService {
private final Player player;
private boolean multipleHands = false;
List<PlayerZone> hands = Lists.newArrayList();
private PlayerZone hand;
public BackupPlanService(Player p1) {
this.player = p1;
}
public boolean initializeExtraHands() {
hand = player.getZone(ZoneType.Hand);
hands.add(hand);
// If pl has Backup Plan as a Conspiracy draw that many extra hands
if (player.getExtraZones() == null) {
return multipleHands;
}
for(PlayerZone extraHand : player.getExtraZones()) {
if (extraHand.getZoneType() == ZoneType.ExtraHand) {
player.drawCards(7, extraHand);
multipleHands = true;
hands.add(extraHand);
// If we figure out how to render the zone in the UI, do it here
}
}
player.updateZoneForView(hand);
return multipleHands;
}
public void chooseHand() {
if (!multipleHands) {
return;
}
PlayerZone library = player.getZone(ZoneType.Library);
// Choose one of the starting hands and recycle the rest
PlayerZone startingHand = player.getController().chooseStartingHand(hands);
if (startingHand == hand) {
for(PlayerZone extraHand : player.getExtraZones()) {
if (extraHand.getZoneType() == ZoneType.ExtraHand) {
for (Card c : Lists.newArrayList(extraHand.getCards().iterator())) {
player.getGame().getAction().moveTo(library, c, null);
}
}
}
} else {
for (Card c : Lists.newArrayList(hand.getCards().iterator())) {
player.getGame().getAction().moveTo(library, c, null);
}
for(PlayerZone extraHand : player.getExtraZones()) {
boolean starting = startingHand.equals(extraHand);
for (Card c : Lists.newArrayList(extraHand.getCards().iterator())) {
if (starting) {
player.getGame().getAction().moveTo(hand, c, null);
} else {
player.getGame().getAction().moveTo(library, c, null);
}
}
}
}
player.resetExtraZones(ZoneType.ExtraHand);
player.updateZoneForView(player.getZone(ZoneType.Hand));
}
}

View File

@@ -156,6 +156,8 @@ public class Player extends GameEntity implements Comparable<Player> {
private List<Card> completedDungeons = new ArrayList<>();
private final Map<ZoneType, PlayerZone> zones = Maps.newEnumMap(ZoneType.class);
private List<PlayerZone> extraZones = null;
private final Map<Long, Integer> adjustLandPlays = Maps.newHashMap();
private final Set<Long> adjustLandPlaysInfinite = Sets.newHashSet();
private Map<Card, Card> maingameCardsMap = Maps.newHashMap();
@@ -1198,9 +1200,18 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final CardCollectionView drawCards(final int n) {
return drawCards(n, null, AbilityKey.newMap());
return drawCards(n, null, AbilityKey.newMap(), this.getZone(ZoneType.Hand));
}
public final CardCollectionView drawCards(final int n, PlayerZone zone) {
return drawCards(n, null, AbilityKey.newMap(), zone);
}
public final CardCollectionView drawCards(final int n, SpellAbility cause, Map<AbilityKey, Object> params) {
return drawCards(n, cause, params, this.getZone(ZoneType.Hand));
}
public final CardCollectionView drawCards(final int n, SpellAbility cause, Map<AbilityKey, Object> params, PlayerZone zone) {
final CardCollection drawn = new CardCollection();
if (n <= 0) {
return drawn;
@@ -1225,7 +1236,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (gameStarted && !canDraw()) {
return drawn;
}
drawn.addAll(doDraw(toReveal, cause, params));
drawn.addAll(doDraw(toReveal, cause, params, zone));
}
// reveal multiple drawn cards when playing with the top of the library revealed
@@ -1240,7 +1251,7 @@ public class Player extends GameEntity implements Comparable<Player> {
/**
* @return a CardCollectionView of cards actually drawn
*/
private CardCollectionView doDraw(Map<Player, CardCollection> revealed, SpellAbility sa, Map<AbilityKey, Object> params) {
private CardCollectionView doDraw(Map<Player, CardCollection> revealed, SpellAbility sa, Map<AbilityKey, Object> params, PlayerZone hand) {
final CardCollection drawn = new CardCollection();
final PlayerZone library = getZone(ZoneType.Library);
@@ -1275,7 +1286,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
}
c = game.getAction().moveToHand(c, cause, params);
c = game.getAction().moveTo(hand, c, cause, params);
drawn.add(c);
// CR 121.6c additional actions can't be performed when draw gets replaced
@@ -1341,6 +1352,15 @@ public class Player extends GameEntity implements Comparable<Player> {
}
}
public final List<PlayerZone> getExtraZones() {
return extraZones;
}
public void resetExtraZones(ZoneType type) {
extraZones.removeIf(z -> z.getZoneType().equals(type));
}
public final CardCollectionView getCardsIn(final ZoneType zoneType) {
return getCardsIn(zoneType, true);
}
@@ -2964,6 +2984,15 @@ public class Player extends GameEntity implements Comparable<Player> {
continue;
}
}
if (Objects.equals(conspire.getName(), "Backup Plan")) {
PlayerZone hand = new PlayerZone(ZoneType.ExtraHand, this);
if (this.extraZones == null) {
this.extraZones = new ArrayList<>();
}
this.extraZones.add(hand);
}
com.add(conspire);
}

View File

@@ -1,20 +1,9 @@
package forge.game.player;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import forge.game.*;
import forge.game.mana.ManaCostBeingPaid;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.LobbyPlayer;
import forge.card.ColorSet;
import forge.card.ICardFace;
@@ -22,12 +11,9 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.*;
import forge.game.GameOutcome.AnteResult;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardView;
import forge.game.card.CounterType;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
@@ -35,18 +21,23 @@ import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.ITriggerEvent;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* A prototype for player controller class
@@ -192,6 +183,7 @@ public abstract class PlayerController {
public abstract CardCollectionView chooseCardsToDelve(int genericAmount, CardCollection grave);
public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);
public abstract List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand);
public abstract PlayerZone chooseStartingHand(List<PlayerZone> zones);
public abstract Mana chooseManaFromPool(List<Mana> manaChoices);
public abstract String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional);

View File

@@ -1,13 +1,12 @@
package forge.game.zone;
import com.google.common.base.Function;
import forge.util.Localizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.base.Function;
import forge.util.Localizer;
/**
* The Enum Zone.
*/
@@ -26,6 +25,8 @@ public enum ZoneType {
SchemeDeck(true, "lblSchemeDeckZone"),
PlanarDeck(true, "lblPlanarDeckZone"),
Subgame(true, "lblSubgameZone"),
// ExtraHand is used for Backup Plan for temporary extra hands
ExtraHand(true, "lblHandZone"),
None(true, "lblNoneZone");
public static final List<ZoneType> STATIC_ABILITIES_SOURCE_ZONES = Arrays.asList(Battlefield, Graveyard, Exile, Command, Stack/*, Hand*/);

View File

@@ -205,6 +205,7 @@ public enum CSubmenuDraft implements ICDoc {
final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer());
starter.add(human);
human.setId(0);
human.assignConspiracies();
for(Map.Entry<Integer, Deck> aiDeck : aiMap.entrySet()) {
RegisteredPlayer aiPlayer = new RegisteredPlayer(aiDeck.getValue()).setPlayer(GamePlayerUtil.createAiPlayer());
aiPlayer.setId(aiDeck.getKey());

View File

@@ -3,6 +3,7 @@ package forge.gamesimulationtests.util;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import forge.LobbyPlayer;
import forge.ai.ComputerUtil;
@@ -37,6 +38,7 @@ import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.gamesimulationtests.util.card.CardSpecification;
import forge.gamesimulationtests.util.card.CardSpecificationHandler;
@@ -51,9 +53,11 @@ import forge.util.MyRandom;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists;
import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Default harmless implementation for tests.
@@ -310,6 +314,11 @@ public class PlayerControllerForTests extends PlayerController {
return usableFromOpeningHand;
}
@Override
public PlayerZone chooseStartingHand(List<PlayerZone> zones) {
return zones.get(0);
}
@Override
public Mana chooseManaFromPool(List<Mana> manaChoices) {
return chooseItem(manaChoices);

View File

@@ -0,0 +1,4 @@
Name:Backup Plan
Types:Conspiracy
Text:Draw an additional hand of seven cards as the game begins. Before taking mulligans, shuffle all but one of your hands into your library.
Oracle: (Start the game with this conspiracy face up in the command zone.)\nDraw an additional hand of seven cards as the game begins. Before taking mulligans, shuffle all but one of your hands into your library.

View File

@@ -224,7 +224,7 @@ ScryfallCode=CNS
1 Advantageous Proclamation|CNS
1 Aether Searcher|CNS
1 Agent of Acquisitions|CNS
#1 Backup Plan|CNS
1 Backup Plan|CNS
1 Brago's Favor|CNS
1 Canal Dredger|CNS
1 Cogwork Grinder|CNS

View File

@@ -0,0 +1,75 @@
package forge.gamemodes.match.input;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.player.PlayerControllerHuman;
import java.util.Deque;
import java.util.List;
public class InputChooseStartingHand extends InputSyncronizedBase {
// An input for choosing multiple different starting hands for Backup Plan
// Ideally all would render at once, but for now we're just going to show the primary, and loop through each hand
// When you like one, click OK and the rest will be cleared
private final Deque<PlayerZone> hands;
PlayerZone primaryHand = null;
Game game;
public InputChooseStartingHand(final PlayerControllerHuman controller, final Player humanPlayer) {
super(controller);
game = humanPlayer.getGame();
primaryHand = humanPlayer.getZone(ZoneType.Hand);
hands = Lists.newLinkedList();
for(PlayerZone extraHand : humanPlayer.getExtraZones()) {
if (extraHand.getZoneType() == ZoneType.ExtraHand) {
hands.add(extraHand);
}
}
}
public PlayerZone getSelectedHand() {
// We're going to be manipulating what the primary hand is during this process so always return it
return primaryHand;
}
@Override
protected void showMessage() {
if (hands.isEmpty()) {
stop();
}
getController().getGui().updateButtons(getOwner(), "View Next", "Accept Hand", true, true, true);
showMessage("Select the currently viewed hand to start the game with. The other " + hands.size() + " hand(s) will be shuffled into your library.");
}
@Override
protected final void onCancel() {
stop();
}
@Override
protected final void onOk() {
// Rotate hands. Take everything in current hand, add it to the next hand
// Then take everything in the next hand and add it to the current hand
PlayerZone nextExtraHand = hands.poll();
assert nextExtraHand != null;
List<Card> currentList = Lists.newArrayList(primaryHand.getCards().iterator());
List<Card> extraList = Lists.newArrayList(nextExtraHand.getCards().iterator());
primaryHand.setCards(extraList);
nextExtraHand.setCards(currentList);
hands.add(nextExtraHand);
}
@Override
public String getActivateAction(Card card) {
return null;
}
}

View File

@@ -1,87 +1,24 @@
package forge.player;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.TreeSet;
import forge.game.player.actions.SelectCardAction;
import forge.game.player.actions.SelectPlayerAction;
import forge.game.trigger.TriggerType;
import forge.game.mana.ManaCostBeingPaid;
import forge.gamemodes.match.input.*;
import forge.trackable.TrackableCollection;
import forge.util.ImageUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.*;
import forge.LobbyPlayer;
import forge.StaticData;
import forge.ai.GameState;
import forge.ai.PlayerControllerAi;
import forge.card.CardDb;
import forge.card.CardSplitType;
import forge.card.CardStateName;
import forge.card.ColorSet;
import forge.card.ICardFace;
import forge.card.MagicColor;
import forge.card.*;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckRecognizer;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityView;
import forge.game.GameEntityViewMap;
import forge.game.GameLogEntryType;
import forge.game.GameObject;
import forge.game.GameType;
import forge.game.PlanarDice;
import forge.game.*;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFaceView;
import forge.game.card.CardLists;
import forge.game.card.CardPlayOption;
import forge.game.card.CardPredicates;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -93,28 +30,23 @@ import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.player.DelayedReveal;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController;
import forge.game.player.PlayerView;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.*;
import forge.game.player.actions.SelectCardAction;
import forge.game.player.actions.SelectPlayerAction;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.SpellAbilityView;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility;
import forge.game.zone.MagicStack;
import forge.game.zone.PlayerZone;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.gamemodes.match.NextGameDecision;
import forge.gamemodes.match.input.*;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.gui.control.FControlGamePlayback;
@@ -130,16 +62,21 @@ import forge.localinstance.achievements.AchievementCollection;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import forge.util.CardTranslation;
import forge.util.DeckAIUtils;
import forge.util.ITriggerEvent;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.MessageUtil;
import forge.util.TextUtil;
import forge.trackable.TrackableCollection;
import forge.util.*;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import io.sentry.Sentry;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* A prototype for player controller class
@@ -1653,6 +1590,18 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return result;
}
@Override
public PlayerZone chooseStartingHand(List<PlayerZone> zones) {
// Create new zone objects in the UI temporarily.
// Spawn a new input dialog, it works by selecting a card in the zone you want and clicking OK
// The card will then extract the PlayerZone via the card that is chosen and return it to this function
// Which will then return the PlayerZone to the caller
player.updateZoneForView(player.getZone(ZoneType.Hand));
final InputChooseStartingHand inp = new InputChooseStartingHand(this, player);
inp.showAndWait();
return inp.getSelectedHand();
}
@Override
public boolean chooseBinary(final SpellAbility sa, final String question, final BinaryChoiceType kindOfChoice,
final Boolean defaultVal) {