mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28:00 +00:00
341 lines
14 KiB
Java
341 lines
14 KiB
Java
package forge.game;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
|
|
import org.apache.commons.lang3.tuple.Pair;
|
|
|
|
import com.google.common.base.Predicate;
|
|
import com.google.common.base.Predicates;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Lists;
|
|
|
|
import forge.Card;
|
|
import forge.CardLists;
|
|
import forge.CardPredicates;
|
|
import forge.GameLogEntryType;
|
|
import forge.Singletons;
|
|
import forge.card.CardDb;
|
|
import forge.card.CardEdition;
|
|
import forge.card.trigger.Trigger;
|
|
import forge.card.trigger.TriggerHandler;
|
|
import forge.deck.CardPool;
|
|
import forge.deck.Deck;
|
|
import forge.deck.DeckSection;
|
|
import forge.game.player.Player;
|
|
import forge.game.player.PlayerType;
|
|
import forge.game.zone.PlayerZone;
|
|
import forge.game.zone.ZoneType;
|
|
import forge.gui.GuiDialog;
|
|
import forge.item.PaperCard;
|
|
import forge.item.IPaperCard;
|
|
import forge.properties.ForgePreferences;
|
|
import forge.properties.ForgePreferences.FPref;
|
|
import forge.util.Aggregates;
|
|
import forge.util.MyRandom;
|
|
import forge.util.TextUtil;
|
|
import forge.util.maps.CollectionSuppliers;
|
|
import forge.util.maps.HashMapOfLists;
|
|
import forge.util.maps.MapOfLists;
|
|
|
|
/**
|
|
* Methods for all things related to starting a new game.
|
|
* All of these methods can and should be static.
|
|
*/
|
|
public class GameNew {
|
|
|
|
public static final ForgePreferences preferences = forge.Singletons.getModel().getPreferences();
|
|
|
|
public static void putCardsOnBattlefield(Player player, Iterable<? extends IPaperCard> cards) {
|
|
PlayerZone bf = player.getZone(ZoneType.Battlefield);
|
|
if (cards != null) {
|
|
for (final IPaperCard cp : cards) {
|
|
Card c = cp.toForgeCard(player);
|
|
c.setOwner(player);
|
|
bf.add(c);
|
|
c.setSickness(true);
|
|
c.setStartsGameInPlay(true);
|
|
c.refreshUniqueNumber();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private static void initVariantsZones(final Player player, final RegisteredPlayer psc) {
|
|
PlayerZone com = player.getZone(ZoneType.Command);
|
|
|
|
// Mainly for avatar, but might find something else here
|
|
for (final IPaperCard c : psc.getCardsInCommand(player)) {
|
|
com.add(c.toForgeCard(player));
|
|
}
|
|
|
|
// Schemes
|
|
List<Card> sd = new ArrayList<Card>();
|
|
for(IPaperCard cp : psc.getSchemes(player)) sd.add(cp.toForgeCard(player));
|
|
if ( !sd.isEmpty()) {
|
|
for(Card c : sd) {
|
|
player.getZone(ZoneType.SchemeDeck).add(c);
|
|
}
|
|
player.getZone(ZoneType.SchemeDeck).shuffle();
|
|
}
|
|
|
|
|
|
// Planes
|
|
List<Card> l = new ArrayList<Card>();
|
|
for(IPaperCard cp : psc.getPlanes(player)) l.add(cp.toForgeCard(player));
|
|
if ( !l.isEmpty() ) {
|
|
for(Card c : l) {
|
|
player.getZone(ZoneType.PlanarDeck).add(c);
|
|
}
|
|
player.getZone(ZoneType.PlanarDeck).shuffle();
|
|
}
|
|
|
|
}
|
|
|
|
private static Set<PaperCard> getRemovedAnteCards(Deck toUse) {
|
|
final String keywordToRemove = "Remove CARDNAME from your deck before playing if you're not playing for ante.";
|
|
Set<PaperCard> myRemovedAnteCards = new HashSet<PaperCard>();
|
|
for ( Entry<DeckSection, CardPool> ds : toUse ) {
|
|
for (Entry<PaperCard, Integer> cp : ds.getValue()) {
|
|
if ( Iterables.contains(cp.getKey().getRules().getMainPart().getKeywords(), keywordToRemove) )
|
|
myRemovedAnteCards.add(cp.getKey());
|
|
}
|
|
}
|
|
|
|
for(PaperCard cp: myRemovedAnteCards) {
|
|
for ( Entry<DeckSection, CardPool> ds : toUse ) {
|
|
ds.getValue().remove(cp, Integer.MAX_VALUE);
|
|
}
|
|
}
|
|
return myRemovedAnteCards;
|
|
}
|
|
|
|
private static void preparePlayerLibrary(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil, Random generator) {
|
|
PlayerZone library = player.getZone(zoneType);
|
|
for (final Entry<PaperCard, Integer> stackOfCards : section) {
|
|
final PaperCard cp = stackOfCards.getKey();
|
|
for (int i = 0; i < stackOfCards.getValue(); i++) {
|
|
|
|
PaperCard cpi = cp;
|
|
// apply random pictures for cards
|
|
if (preferences.getPrefBoolean(FPref.UI_RANDOM_CARD_ART)) {
|
|
cpi = CardDb.instance().getCard(cp.getName(), cp.getEdition(), -1);
|
|
if ( cp.isFoil() )
|
|
cpi = CardDb.instance().getFoiled(cpi);
|
|
}
|
|
|
|
final Card card = cpi.toForgeCard(player);
|
|
|
|
// Assign card-specific foiling or random foiling on approximately 1:20 cards if enabled
|
|
CardEdition.FoilType foilType = Singletons.getModel().getEditions().get(card.getCurSetCode()).getFoilType();
|
|
if (foilType != CardEdition.FoilType.NOT_SUPPORTED && (cp.isFoil() || (canRandomFoil && MyRandom.percentTrue(5)))) {
|
|
int iFoil = 0;
|
|
|
|
switch(foilType) {
|
|
case MODERN:
|
|
iFoil = generator.nextInt(9) + 1; // modern foils in slots 1-10
|
|
break;
|
|
case OLD_STYLE:
|
|
iFoil = generator.nextInt(9) + 11; // old style foils in slots 11-20
|
|
break;
|
|
default:
|
|
System.out.println(String.format("Unexpected foil type for card %s in edition %s.", card.getName(), card.getCurSetCode()));
|
|
}
|
|
|
|
card.setFoil(iFoil);
|
|
}
|
|
|
|
library.add(card);
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is where the computer cheats
|
|
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
|
|
|
/**
|
|
* <p>
|
|
* smoothComputerManaCurve.
|
|
* </p>
|
|
*
|
|
* @param in
|
|
* an array of {@link forge.Card} objects.
|
|
* @return an array of {@link forge.Card} objects.
|
|
*/
|
|
private static Iterable<Card> smoothComputerManaCurve(final Iterable<Card> in) {
|
|
final List<Card> library = Lists.newArrayList(in);
|
|
CardLists.shuffle(library);
|
|
|
|
// remove all land, keep non-basicland in there, shuffled
|
|
List<Card> land = CardLists.filter(library, CardPredicates.Presets.LANDS);
|
|
for (Card c : land) {
|
|
if (c.isLand()) {
|
|
library.remove(c);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// mana weave, total of 7 land
|
|
// The Following have all been reduced by 1, to account for the
|
|
// computer starting first.
|
|
library.add(5, land.get(0));
|
|
library.add(6, land.get(1));
|
|
library.add(8, land.get(2));
|
|
library.add(9, land.get(3));
|
|
library.add(10, land.get(4));
|
|
|
|
library.add(12, land.get(5));
|
|
library.add(15, land.get(6));
|
|
} catch (final IndexOutOfBoundsException e) {
|
|
System.err.println("Error: cannot smooth mana curve, not enough land");
|
|
return in;
|
|
}
|
|
|
|
// add the rest of land to the end of the deck
|
|
for (int i = 0; i < land.size(); i++) {
|
|
if (!library.contains(land.get(i))) {
|
|
library.add(land.get(i));
|
|
}
|
|
}
|
|
|
|
// check
|
|
for (int i = 0; i < library.size(); i++) {
|
|
System.out.println(library.get(i));
|
|
}
|
|
|
|
return library;
|
|
} // smoothComputerManaCurve()
|
|
|
|
private static List<PaperCard> getCardsAiCantPlayWell(final Deck toUse) {
|
|
List<PaperCard> result = new ArrayList<PaperCard>();
|
|
|
|
for ( Entry<DeckSection, CardPool> ds : toUse ) {
|
|
for (Entry<PaperCard, Integer> cp : ds.getValue()) {
|
|
if ( cp.getKey().getRules().getAiHints().getRemAIDecks() )
|
|
result.add(cp.getKey());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Constructor for new game allowing card lists to be put into play
|
|
* immediately, and life totals to be adjusted, for computer and human.
|
|
*
|
|
* TODO: Accept something like match state as parameter. Match should be aware of players,
|
|
* their decks and other special starting conditions.
|
|
* @param forceAnte Forces ante on or off no matter what your preferences
|
|
*/
|
|
public static void newGame(final Game game, final boolean canRandomFoil, boolean useAnte) {
|
|
|
|
Card.resetUniqueNumber();
|
|
// need this code here, otherwise observables fail
|
|
Trigger.resetIDs();
|
|
TriggerHandler trigHandler = game.getTriggerHandler();
|
|
trigHandler.clearDelayedTrigger();
|
|
|
|
// friendliness
|
|
final Set<PaperCard> rAICards = new HashSet<PaperCard>();
|
|
|
|
MapOfLists<Player, PaperCard> removedAnteCards = new HashMapOfLists<Player, PaperCard>(CollectionSuppliers.<PaperCard>hashSets());
|
|
|
|
GameType gameType = game.getType();
|
|
boolean isFirstGame = game.getMatch().getPlayedGames().isEmpty();
|
|
boolean canSideBoard = !isFirstGame && gameType.isSideboardingAllowed();
|
|
|
|
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
|
for (int i = 0; i < playersConditions.size(); i++) {
|
|
Player player = game.getPlayers().get(i);
|
|
final RegisteredPlayer psc = playersConditions.get(i);
|
|
|
|
putCardsOnBattlefield(player, psc.getCardsOnBattlefield(player));
|
|
initVariantsZones(player, psc);
|
|
|
|
if (canSideBoard) {
|
|
Deck sideboarded = player.getController().sideboard(psc.getCurrentDeck(), gameType);
|
|
psc.setCurrentDeck(sideboarded);
|
|
} else {
|
|
psc.restoreOriginalDeck();
|
|
}
|
|
Deck myDeck = psc.getCurrentDeck();
|
|
boolean hasSideboard = myDeck.has(DeckSection.Sideboard);
|
|
|
|
Set<PaperCard> myRemovedAnteCards = useAnte ? null : getRemovedAnteCards(myDeck);
|
|
Random generator = MyRandom.getRandom();
|
|
|
|
preparePlayerLibrary(player, ZoneType.Library, myDeck.getMain(), canRandomFoil, generator);
|
|
if(hasSideboard)
|
|
preparePlayerLibrary(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), canRandomFoil, generator);
|
|
|
|
// Shuffling
|
|
if (player.getLobbyPlayer().getType() == PlayerType.COMPUTER && preferences.getPrefBoolean(FPref.UI_SMOOTH_LAND)) {
|
|
// AI may do this instead of shuffling its deck
|
|
final Iterable<Card> c1 = GameNew.smoothComputerManaCurve(player.getCardsIn(ZoneType.Library));
|
|
player.getZone(ZoneType.Library).setCards(c1);
|
|
} else {
|
|
player.shuffle();
|
|
}
|
|
|
|
if(isFirstGame && player.getLobbyPlayer().getType() == PlayerType.COMPUTER) {
|
|
rAICards.addAll(getCardsAiCantPlayWell(myDeck));
|
|
}
|
|
|
|
if( myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty() )
|
|
removedAnteCards.addAll(player, myRemovedAnteCards);
|
|
}
|
|
|
|
if (!rAICards.isEmpty()) {
|
|
String message = TextUtil.buildFourColumnList("AI deck contains the following cards that it can't play or may be buggy:", rAICards);
|
|
if (GameType.Quest == game.getType() || GameType.Sealed == game.getType() || GameType.Draft == game.getType()) {
|
|
// log, but do not visually warn. quest decks are supposedly already vetted by the quest creator,
|
|
// sealed and draft decks do not get any AI-unplayable picks but may contain several
|
|
// received/picked but unplayable cards in the sideboard.
|
|
System.err.println(message);
|
|
} else {
|
|
GuiDialog.message(message);
|
|
}
|
|
}
|
|
|
|
if (!removedAnteCards.isEmpty()) {
|
|
StringBuilder ante = new StringBuilder("The following ante cards were removed:\n\n");
|
|
for (Entry<Player, Collection<PaperCard>> ants : removedAnteCards.entrySet()) {
|
|
ante.append(TextUtil.buildFourColumnList("From the " + ants.getKey().getName() + "'s deck:", ants.getValue()));
|
|
}
|
|
GuiDialog.message(ante.toString());
|
|
}
|
|
}
|
|
|
|
static List<Pair<Player, Card>> chooseCardsForAnte(final Game game) {
|
|
List<Pair<Player, Card>> anteed = new ArrayList<Pair<Player,Card>>();
|
|
|
|
for (final Player p : game.getPlayers()) {
|
|
final List<Card> lib = p.getCardsIn(ZoneType.Library);
|
|
Predicate<Card> goodForAnte = Predicates.not(CardPredicates.Presets.BASIC_LANDS);
|
|
Card ante = Aggregates.random(Iterables.filter(lib, goodForAnte));
|
|
if (ante == null) {
|
|
game.getGameLog().add(GameLogEntryType.ANTE, "Only basic lands found. Will ante one of them");
|
|
ante = Aggregates.random(lib);
|
|
}
|
|
anteed.add(Pair.of(p, ante));
|
|
}
|
|
return anteed;
|
|
}
|
|
|
|
static void moveCardsToAnte(List<Pair<Player, Card>> cards) {
|
|
for(Pair<Player, Card> kv : cards) {
|
|
Player p = kv.getKey();
|
|
p.getGame().getAction().moveTo(ZoneType.Ante, kv.getValue());
|
|
p.getGame().getGameLog().add(GameLogEntryType.ANTE, p + " anted " + kv.getValue());
|
|
}
|
|
}
|
|
|
|
// this is where the computer cheats
|
|
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
|
|
|
}
|