Initial checkin for Puzzle Mode

This commit is contained in:
Sol
2017-04-08 15:17:07 +00:00
parent 5592139454
commit 836ebdc5e6
27 changed files with 908 additions and 90 deletions

9
.gitattributes vendored
View File

@@ -993,6 +993,11 @@ forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.ja
forge-gui-desktop/src/main/java/forge/screens/home/online/OnlineMenu.java -text
forge-gui-desktop/src/main/java/forge/screens/home/online/VSubmenuOnlineLobby.java -text
forge-gui-desktop/src/main/java/forge/screens/home/package-info.java -text
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java -text
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java -text
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/PuzzleGameMenu.java -text
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuPuzzleCreate.java -text
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuPuzzleSolve.java -text
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuChallenges.java -text
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuDuels.java -text
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestData.java -text
@@ -1347,6 +1352,7 @@ forge-gui-mobile/src/forge/screens/gauntlet/NewGauntletScreen.java -text
forge-gui-mobile/src/forge/screens/home/HomeScreen.java -text
forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java -text
forge-gui-mobile/src/forge/screens/home/NewGameMenu.java -text
forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java -text
forge-gui-mobile/src/forge/screens/limited/DraftingProcessScreen.java -text
forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java -text
forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java -text
@@ -18806,6 +18812,7 @@ forge-gui/res/music/menus/Evil[!!-~]March.mp3 -text
forge-gui/res/music/menus/Heroic[!!-~]Age.mp3 -text
forge-gui/res/music/menus/Lord[!!-~]of[!!-~]the[!!-~]Land.mp3 -text
forge-gui/res/music/menus/The[!!-~]Pyre.mp3 -text
forge-gui/res/puzzle/PS1.pzl -text
forge-gui/res/quest/bazaar/ape_pet_l1.txt -text
forge-gui/res/quest/bazaar/ape_pet_l2.txt -text
forge-gui/res/quest/bazaar/ape_pet_l3.txt -text
@@ -20844,6 +20851,8 @@ forge-gui/src/main/java/forge/player/package-info.java -text
forge-gui/src/main/java/forge/properties/ForgeConstants.java -text
forge-gui/src/main/java/forge/properties/ForgePreferences.java svneol=native#text/plain
forge-gui/src/main/java/forge/properties/package-info.java svneol=native#text/plain
forge-gui/src/main/java/forge/puzzle/Puzzle.java -text
forge-gui/src/main/java/forge/puzzle/PuzzleIO.java -text
forge-gui/src/main/java/forge/quest/BoosterUtils.java svneol=native#text/plain
forge-gui/src/main/java/forge/quest/IQuestEvent.java -text
forge-gui/src/main/java/forge/quest/IQuestRewardCard.java -text

View File

@@ -14,6 +14,7 @@ import com.google.common.collect.Lists;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -31,7 +32,7 @@ import forge.util.collect.FCollectionView;
public abstract class GameState {
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
static {
ZONES.put(ZoneType.Battlefield, "play");
ZONES.put(ZoneType.Battlefield, "battlefield");
ZONES.put(ZoneType.Hand, "hand");
ZONES.put(ZoneType.Graveyard, "graveyard");
ZONES.put(ZoneType.Library, "library");
@@ -41,8 +42,15 @@ public abstract class GameState {
private int humanLife = -1;
private int computerLife = -1;
private String humanCounters = "";
private String computerCounters = "";
private final Map<ZoneType, String> humanCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
private final Map<ZoneType, String> aiCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
private final Map<Integer, Card> idToCard = new HashMap<>();
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
private String tChangePlayer = "NONE";
private String tChangePhase = "NONE";
@@ -56,6 +64,14 @@ public abstract class GameState {
StringBuilder sb = new StringBuilder();
sb.append(String.format("humanlife=%d\n", humanLife));
sb.append(String.format("ailife=%d\n", computerLife));
if (!humanCounters.isEmpty()) {
sb.append(String.format("humancounters=%s\n", humanCounters));
}
if (!computerCounters.isEmpty()) {
sb.append(String.format("aicounters=%s\n", computerCounters));
}
sb.append(String.format("activeplayer=%s\n", tChangePlayer));
sb.append(String.format("activephase=%s\n", tChangePhase));
appendCards(humanCardTexts, "human", sb);
@@ -65,7 +81,7 @@ public abstract class GameState {
private void appendCards(Map<ZoneType, String> cardTexts, String categoryPrefix, StringBuilder sb) {
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
sb.append(String.format("%scardsin%s=%s\n", categoryPrefix, ZONES.get(kv.getKey()), kv.getValue()));
sb.append(String.format("%s%s=%s\n", categoryPrefix, ZONES.get(kv.getKey()), kv.getValue()));
}
}
@@ -82,6 +98,9 @@ public abstract class GameState {
}
humanLife = human.getLife();
computerLife = ai.getLife();
humanCounters = countersToString(human.getCounters());
computerCounters = countersToString(ai.getCounters());
tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human";
tChangePhase = game.getPhaseHandler().getPhase().toString();
aiCardTexts.clear();
@@ -111,43 +130,61 @@ public abstract class GameState {
newText.append(c.getPaperCard().getName());
}
if (c.isCommander()) {
newText.append("|IsCommander:True");
newText.append("|IsCommander");
}
if (zoneType == ZoneType.Battlefield) {
if (c.isTapped()) {
newText.append("|Tapped:True");
newText.append("|Tapped");
}
if (c.isSick()) {
newText.append("|SummonSick:True");
newText.append("|SummonSick");
}
if (c.isFaceDown()) {
newText.append("|FaceDown:True");
newText.append("|FaceDown");
if (c.isManifested()) {
newText.append(":Manifested");
}
}
Map<CounterType, Integer> counters = c.getCounters();
if (!counters.isEmpty()) {
newText.append("|Counters:");
boolean start = true;
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
String str = kv.getKey().toString();
int count = kv.getValue();
for (int i = 0; i < count; i++) {
if (!start) {
newText.append(",");
}
newText.append(str);
start = false;
}
}
newText.append(countersToString(counters));
}
if (c.getEquipping() != null) {
newText.append("|Attaching:").append(c.getEquipping().getId());
} else if (c.getFortifying() != null) {
newText.append("|Attaching:").append(c.getFortifying().getId());
} else if (c.getEnchantingCard() != null) {
newText.append("|Attaching:").append(c.getEnchantingCard().getId());
}
if (!c.getEnchantedBy(false).isEmpty() || !c.getEquippedBy(false).isEmpty() || !c.getFortifiedBy(false).isEmpty()) {
newText.append("|Id:").append(c.getId());
}
}
cardTexts.put(zoneType, newText.toString());
}
private String[] parseLine(String line) {
private String countersToString(Map<CounterType, Integer> counters) {
boolean first = true;
StringBuilder counterString = new StringBuilder();
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
if (!first) {
counterString.append(",");
}
first = false;
counterString.append(String.format("%s=%d", kv.getKey().toString(), kv.getValue()));
}
return counterString.toString();
}
private String[] splitLine(String line) {
if (line.charAt(0) == '#') {
return null;
}
final String[] tempData = line.split("=");
final String[] tempData = line.split("=", 2);
if (tempData.length >= 2) {
return tempData;
}
@@ -163,32 +200,91 @@ public abstract class GameState {
String line;
while ((line = br.readLine()) != null) {
String[] keyValue = parseLine(line);
if (keyValue == null) {
continue;
}
final String categoryName = keyValue[0].toLowerCase();
final String categoryValue = keyValue[1];
parseLine(line);
}
}
if (categoryName.equals("humanlife")) humanLife = Integer.parseInt(categoryValue);
else if (categoryName.equals("ailife")) computerLife = Integer.parseInt(categoryValue);
public void parse(List<String> lines) {
for(String line : lines) {
parseLine(line);
}
}
else if (categoryName.equals("activeplayer")) tChangePlayer = categoryValue.trim().toLowerCase();
else if (categoryName.equals("activephase")) tChangePhase = categoryValue;
protected void parseLine(String line) {
String[] keyValue = splitLine(line);
if (keyValue == null) return;
else if (categoryName.equals("humancardsinplay")) humanCardTexts.put(ZoneType.Battlefield, categoryValue);
else if (categoryName.equals("aicardsinplay")) aiCardTexts.put(ZoneType.Battlefield, categoryValue);
else if (categoryName.equals("humancardsinhand")) humanCardTexts.put(ZoneType.Hand, categoryValue);
else if (categoryName.equals("aicardsinhand")) aiCardTexts.put(ZoneType.Hand, categoryValue);
else if (categoryName.equals("humancardsingraveyard")) humanCardTexts.put(ZoneType.Graveyard, categoryValue);
else if (categoryName.equals("aicardsingraveyard")) aiCardTexts.put(ZoneType.Graveyard, categoryValue);
else if (categoryName.equals("humancardsinlibrary")) humanCardTexts.put(ZoneType.Library, categoryValue);
else if (categoryName.equals("aicardsinlibrary")) aiCardTexts.put(ZoneType.Library, categoryValue);
else if (categoryName.equals("humancardsinexile")) humanCardTexts.put(ZoneType.Exile, categoryValue);
else if (categoryName.equals("aicardsinexile")) aiCardTexts.put(ZoneType.Exile, categoryValue);
else if (categoryName.equals("humancardsincommand")) humanCardTexts.put(ZoneType.Command, categoryValue);
else if (categoryName.equals("aicardsincommand")) aiCardTexts.put(ZoneType.Command, categoryValue);
else System.out.println("Unknown key: " + categoryName);
final String categoryName = keyValue[0].toLowerCase();
final String categoryValue = keyValue[1];
if (categoryName.startsWith("active")) {
if (categoryName.endsWith("player"))
tChangePlayer = categoryValue.trim().toLowerCase();
if (categoryName.endsWith("phase"))
tChangePhase = categoryValue.trim().toUpperCase();
return;
}
boolean isHuman = categoryName.startsWith("human");
if (categoryName.endsWith("life")) {
if (isHuman)
humanLife = Integer.parseInt(categoryValue);
else
computerLife = Integer.parseInt(categoryValue);
}
else if (categoryName.endsWith("counters")) {
if (isHuman)
humanCounters = categoryValue;
else
computerCounters = categoryValue;
}
else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
if (isHuman)
humanCardTexts.put(ZoneType.Battlefield, categoryValue);
else
aiCardTexts.put(ZoneType.Battlefield, categoryValue);
}
else if (categoryName.endsWith("hand")) {
if (isHuman)
humanCardTexts.put(ZoneType.Hand, categoryValue);
else
aiCardTexts.put(ZoneType.Hand, categoryValue);
}
else if (categoryName.endsWith("graveyard")) {
if (isHuman)
humanCardTexts.put(ZoneType.Graveyard, categoryValue);
else
aiCardTexts.put(ZoneType.Graveyard, categoryValue);
}
else if (categoryName.equals("library")) {
if (isHuman)
humanCardTexts.put(ZoneType.Library, categoryValue);
else
aiCardTexts.put(ZoneType.Library, categoryValue);
}
else if (categoryName.equals("exile")) {
if (isHuman)
humanCardTexts.put(ZoneType.Exile, categoryValue);
else
aiCardTexts.put(ZoneType.Exile, categoryValue);
}
else if (categoryName.equals("command")) {
if (isHuman)
humanCardTexts.put(ZoneType.Command, categoryValue);
else
aiCardTexts.put(ZoneType.Command, categoryValue);
}
else {
System.out.println("Unknown key: " + categoryName);
}
}
@@ -196,27 +292,54 @@ public abstract class GameState {
game.getAction().invoke(new Runnable() {
@Override
public void run() {
final Player human = game.getPlayers().get(0);
final Player ai = game.getPlayers().get(1);
Player newPlayerTurn = tChangePlayer.equals("human") ? newPlayerTurn = human : tChangePlayer.equals("ai") ? newPlayerTurn = ai : null;
PhaseType newPhase = tChangePhase.trim().equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase);
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
setupPlayerState(humanLife, humanCardTexts, human);
setupPlayerState(computerLife, aiCardTexts, ai);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
applyGameOnThread(game);
}
});
}
protected void applyGameOnThread(final Game game) {
final Player human = game.getPlayers().get(0);
final Player ai = game.getPlayers().get(1);
Player newPlayerTurn = tChangePlayer.equals("human") ? human : tChangePlayer.equals("ai") ? ai : null;
PhaseType newPhase = tChangePhase.equals("none") ? null : PhaseType.smartValueOf(tChangePhase);
// Set stack to resolving so things won't trigger/effects be checked right away
game.getStack().setResolving(true);
if (!humanCounters.isEmpty()) {
applyCountersToGameEntity(human, humanCounters);
}
if (!computerCounters.isEmpty()) {
applyCountersToGameEntity(ai, computerCounters);
}
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
setupPlayerState(humanLife, humanCardTexts, human);
setupPlayerState(computerLife, aiCardTexts, ai);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
game.getStack().setResolving(false);
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
}
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
entity.setCounters(new HashMap<CounterType, Integer>());
String[] allCounterStrings = counterString.split(",");
for (final String counterPair : allCounterStrings) {
String[] pair = counterPair.split("=", 2);
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), false, false);
}
}
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p) {
// Lock check static as we setup player state
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
String value = kv.getValue();
@@ -245,7 +368,12 @@ public abstract class GameState {
// var as-is.
c.setCounters(new HashMap<CounterType, Integer>());
p.getZone(ZoneType.Hand).add(c);
p.getGame().getAction().moveToPlay(c);
if (c.isAura()) {
p.getGame().getAction().moveToPlay(c);
} else {
p.getGame().getAction().moveToPlay(c);
}
c.setTapped(tapped);
c.setSickness(sickness);
c.setCounters(counters);
@@ -254,6 +382,19 @@ public abstract class GameState {
zone.setCards(kv.getValue());
}
}
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
Card attachedTo = idToCard.get(entry.getValue());
Card attacher = entry.getKey();
if (attacher.isEquipment()) {
attacher.equipCard(attachedTo);
} else if (attacher.isAura()) {
attacher.enchantEntity(attachedTo);
} else if (attacher.isFortified()) {
attacher.fortifyCard(attachedTo);
}
}
}
/**
@@ -265,7 +406,7 @@ public abstract class GameState {
* an array of {@link java.lang.String} objects.
* @param player
* a {@link forge.game.player.Player} object.
* @return a {@link forge.CardList} object.
* @return a {@link CardCollectionView} object.
*/
private CardCollectionView processCardsForZone(final String[] data, final Player player) {
final CardCollection cl = new CardCollection();
@@ -286,22 +427,29 @@ public abstract class GameState {
if (info.startsWith("Set:")) {
c.setSetCode(info.substring(info.indexOf(':') + 1));
hasSetCurSet = true;
} else if (info.equalsIgnoreCase("Tapped:True")) {
}
else if (info.startsWith("Tapped")) {
c.tap();
} else if (info.startsWith("Counters:")) {
final String[] counterStrings = info.substring(info.indexOf(':') + 1).split(",");
for (final String counter : counterStrings) {
c.addCounter(CounterType.valueOf(counter), 1, true);
}
} else if (info.equalsIgnoreCase("SummonSick:True")) {
applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
} else if (info.startsWith("SummonSick")) {
c.setSickness(true);
} else if (info.equalsIgnoreCase("FaceDown:True")) {
} else if (info.startsWith("FaceDown")) {
c.setState(CardStateName.FaceDown, true);
} else if (info.equalsIgnoreCase("IsCommander:True")) {
if (info.endsWith("Manifested")) {
c.setManifested(true);
}
} else if (info.startsWith("IsCommander")) {
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
c.setCommander(true);
player.setCommanders(Lists.newArrayList(c));
player.getZone(ZoneType.Command).add(Player.createCommanderEffect(player.getGame(), c));
} else if (info.startsWith("Id:")) {
int id = Integer.parseInt(info.substring(4));
idToCard.put(id, c);
} else if (info.startsWith("Attaching:")) {
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
cardToAttachId.put(c, id);
}
}

View File

@@ -94,13 +94,15 @@ public enum DeckFormat {
PlanarConquest ( Range.between(40, Integer.MAX_VALUE), Range.is(0), 1),
Vanguard ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
Planechase ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
Archenemy ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4);
Archenemy ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
Puzzle ( Range.between(0, Integer.MAX_VALUE), Range.is(0), 4);
private final Range<Integer> mainRange;
private final Range<Integer> sideRange; // null => no check
private final int maxCardCopies;
private final Predicate<CardRules> cardPoolFilter;
private final static String ADVPROCLAMATION = "Advantageous Proclamation";
private final static String SOVREALM = "Sovereign's Realm";
private DeckFormat(Range<Integer> mainRange0, Range<Integer> sideRange0, int maxCardCopies0) {
this(mainRange0, sideRange0, maxCardCopies0, null);
@@ -168,11 +170,13 @@ public enum DeckFormat {
int min = getMainRange().getMinimum();
int max = getMainRange().getMaximum();
boolean noBasicLands = false;
// Adjust minimum base on number of Advantageous Proclamation or similar cards
CardPool conspiracies = deck.get(DeckSection.Conspiracy);
if (conspiracies != null) {
min -= (5 * conspiracies.countByName(ADVPROCLAMATION, true));
min -= (5 * conspiracies.countByName(ADVPROCLAMATION, false));
noBasicLands = conspiracies.countByName(SOVREALM, false) > 0;
}
if (hasCommander()) { // 1 Commander, or 2 Partner Commanders

View File

@@ -17,6 +17,7 @@
*/
package forge.game;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -1434,6 +1435,10 @@ public class GameAction {
}
public void startGame(GameOutcome lastGameOutcome) {
startGame(lastGameOutcome, null);
}
public void startGame(GameOutcome lastGameOutcome, Runnable startGameHook) {
Player first = determineFirstTurnPlayer(lastGameOutcome);
GameType gameType = game.getRules().getGameType();
@@ -1444,6 +1449,9 @@ public class GameAction {
// Where there are none, it should bring up speed controls
game.fireEvent(new GameEventGameStarted(gameType, first, game.getPlayers()));
// Emissary's Plot
// runPreOpeningHandActions(first);
game.setAge(GameStage.Mulligan);
for (final Player p1 : game.getPlayers()) {
p1.drawCards(p1.getMaxHandSize());
@@ -1453,8 +1461,9 @@ public class GameAction {
}
// Choose starting hand for each player with multiple hands
performMulligans(first, game.getRules().hasAppliedVariant(GameType.Commander));
if (game.getRules().getGameType() != GameType.Puzzle) {
performMulligans(first, game.getRules().hasAppliedVariant(GameType.Commander));
}
if (game.isGameOver()) { break; } // conceded during "mulligan" prompt
game.setAge(GameStage.Play);
@@ -1472,8 +1481,8 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.NewGame, runParams, true);
//</THIS CODE WILL WORK WITH PHASE = NULL>
game.getPhaseHandler().startFirstTurn(first);
game.getPhaseHandler().startFirstTurn(first, startGameHook);
//after game ends, ensure Auto-Pass canceled for all players so it doesn't apply to next game
for (Player p : game.getRegisteredPlayers()) {
p.getController().autoPassCancel();
@@ -1487,15 +1496,20 @@ public class GameAction {
// Only cut/coin toss if it's the first game of the match
Player goesFirst = null;
// 904.6: in Archenemy games the Archenemy goes first
if (game != null && game.getRules().hasAppliedVariant(GameType.Archenemy)) {
for (Player p : game.getPlayers()) {
if (p.isArchenemy()) {
return p;
if (game != null) {
if (game.getRules().getGameType().equals(GameType.Puzzle)) {
return game.getPlayers().get(0);
}
// 904.6: in Archenemy games the Archenemy goes first
if (game.getRules().hasAppliedVariant(GameType.Archenemy)) {
for (Player p : game.getPlayers()) {
if (p.isArchenemy()) {
return p;
}
}
}
}
// Power Play - Each player with a Power Play in the CommandZone becomes the Starting Player
Set<Player> powerPlayers = Sets.newHashSet();
for (Card c : game.getCardsIn(ZoneType.Command)) {
@@ -1629,6 +1643,31 @@ public class GameAction {
}
}
private void runPreOpeningHandActions(final Player first) {
Player takesAction = first;
do {
//
List<Card> ploys = CardLists.filter(takesAction.getCardsIn(ZoneType.Command), new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.getName().equals("Emissary's Ploy");
}
});
int chosen = 1;
List<Integer> cmc = Lists.newArrayList(1, 2, 3);
for (Card c : ploys) {
if (!cmc.isEmpty()) {
chosen = takesAction.getController().chooseNumber(c.getSpellPermanent(), "Emissary's Ploy", cmc, c.getOwner());
cmc.remove((Object)chosen);
}
c.setChosenNumber(chosen);
}
takesAction = game.getNextPlayerAfter(takesAction);
} while (takesAction != first);
}
private void runOpeningHandActions(final Player first) {
Player takesAction = first;

View File

@@ -354,7 +354,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canReceiveCounters(final CounterType type);
abstract protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents);
abstract public void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents);
abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters();

View File

@@ -17,6 +17,7 @@ public enum GameType {
Quest (DeckFormat.QuestDeck, true, true, false, "Quest", ""),
QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", ""),
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "Planar Conquest", ""),
Puzzle (DeckFormat.Puzzle, false, false, false, "Puzzle", "Solve a puzzle from the given game state"),
Constructed (DeckFormat.Constructed, false, true, true, "Constructed", ""),
DeckManager (DeckFormat.Constructed, false, true, true, "Deck Manager", ""),
Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", "Each player has a special \"Avatar\" card that affects the game."),

View File

@@ -80,6 +80,10 @@ public class Match {
}
public void startGame(final Game game) {
startGame(game, null);
}
public void startGame(final Game game, Runnable startGameHook) {
prepareAllZones(game);
if (rules.useAnte()) { // Deciding which cards go to ante
Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity());
@@ -92,7 +96,8 @@ public class Match {
}
GameOutcome lastOutcome = gamesPlayed.isEmpty() ? null : gamesPlayed.get(gamesPlayed.size() - 1);
game.getAction().startGame(lastOutcome);
game.getAction().startGame(lastOutcome, startGameHook);
if (rules.useAnte()) {
executeAnte(game);

View File

@@ -987,7 +987,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
@Override
protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
public void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
int addAmount = n;
if(addAmount < 0) {
addAmount = 0; // As per rule 107.1b

View File

@@ -878,6 +878,10 @@ public class PhaseHandler implements java.io.Serializable {
private final static boolean DEBUG_PHASES = false;
public void startFirstTurn(Player goesFirst) {
startFirstTurn(goesFirst, null);
}
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
StopWatch sw = new StopWatch();
if (phase != null) {
@@ -891,6 +895,11 @@ public class PhaseHandler implements java.io.Serializable {
// don't even offer priority, because it's untap of 1st turn now
givePriorityToPlayer = false;
if (startGameHook != null) {
startGameHook.run();
givePriorityToPlayer = true;
}
// MAIN GAME LOOP
while (!game.isGameOver()) {
if (givePriorityToPlayer) {

View File

@@ -884,7 +884,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
@Override
protected void addCounter(CounterType counterType, int n, boolean applyMultiplier, boolean fireEvents) {
public void addCounter(CounterType counterType, int n, boolean applyMultiplier, boolean fireEvents) {
if (!canReceiveCounters(counterType)) {
return;
}

View File

@@ -16,6 +16,8 @@ import forge.screens.home.gauntlet.VSubmenuGauntletContests;
import forge.screens.home.gauntlet.VSubmenuGauntletLoad;
import forge.screens.home.gauntlet.VSubmenuGauntletQuick;
import forge.screens.home.online.VSubmenuOnlineLobby;
import forge.screens.home.puzzle.VSubmenuPuzzleCreate;
import forge.screens.home.puzzle.VSubmenuPuzzleSolve;
import forge.screens.home.quest.VSubmenuChallenges;
import forge.screens.home.quest.VSubmenuDuels;
import forge.screens.home.quest.VSubmenuQuestData;
@@ -71,6 +73,8 @@ public enum EDocID {
HOME_ACHIEVEMENTS (VSubmenuAchievements.SINGLETON_INSTANCE),
HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE),
HOME_UTILITIES (VSubmenuDownloaders.SINGLETON_INSTANCE),
HOME_PUZZLE_CREATE(VSubmenuPuzzleCreate.SINGLETON_INSTANCE),
HOME_PUZZLE_SOLVE(VSubmenuPuzzleSolve.SINGLETON_INSTANCE),
HOME_CONSTRUCTED (VSubmenuConstructed.SINGLETON_INSTANCE),
HOME_DRAFT (VSubmenuDraft.SINGLETON_INSTANCE),
HOME_SEALED (VSubmenuSealed.SINGLETON_INSTANCE),

View File

@@ -1,8 +1,5 @@
package forge.screens.deckeditor.views;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID;
@@ -11,6 +8,9 @@ import forge.item.InventoryItem;
import forge.itemmanager.ItemManager;
import forge.itemmanager.ItemManagerContainer;
import forge.screens.deckeditor.controllers.CCardCatalog;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
/**
* Assembles Swing components of card catalog in deck editor.

View File

@@ -11,6 +11,7 @@ public enum EMenuGroup {
SANCTIONED ("Sanctioned Formats"),
ONLINE ("Online Multiplayer"),
QUEST ("Quest Mode"),
PUZZLE ("Puzzle Mode"),
GAUNTLET ("Gauntlets"),
SETTINGS ("Game Settings");

View File

@@ -32,6 +32,8 @@ import javax.swing.JPanel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import forge.screens.home.puzzle.VSubmenuPuzzleCreate;
import forge.screens.home.puzzle.VSubmenuPuzzleSolve;
import net.miginfocom.swing.MigLayout;
import forge.Singletons;
import forge.assets.FSkinProp;
@@ -56,6 +58,7 @@ import forge.screens.home.quest.VSubmenuQuestPrefs;
import forge.screens.home.sanctioned.VSubmenuConstructed;
import forge.screens.home.sanctioned.VSubmenuDraft;
import forge.screens.home.sanctioned.VSubmenuSealed;
import forge.screens.home.sanctioned.VSubmenuWinston;
import forge.screens.home.settings.VSubmenuAchievements;
import forge.screens.home.settings.VSubmenuAvatars;
import forge.screens.home.settings.VSubmenuDownloaders;
@@ -132,6 +135,9 @@ public enum VHomeUI implements IVTopLevelUI {
allSubmenus.add(VSubmenuGauntletLoad.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuGauntletContests.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPuzzleSolve.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPuzzleCreate.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPreferences.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuAchievements.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuAvatars.SINGLETON_INSTANCE);

View File

@@ -0,0 +1,37 @@
package forge.screens.home.puzzle;
import forge.gui.framework.ICDoc;
import forge.menus.IMenuProvider;
import forge.menus.MenuUtil;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public enum CSubmenuPuzzleCreate implements ICDoc, IMenuProvider {
SINGLETON_INSTANCE;
private VSubmenuPuzzleCreate view = VSubmenuPuzzleCreate.SINGLETON_INSTANCE;
@Override
public void register() {
}
@Override
public void initialize() {
}
@Override
public void update() {
MenuUtil.setMenuProvider(this);
}
@Override
public List<JMenu> getMenus() {
final List<JMenu> menus = new ArrayList<JMenu>();
menus.add(PuzzleGameMenu.getMenu());
return menus;
}
}

View File

@@ -0,0 +1,113 @@
package forge.screens.home.puzzle;
import forge.GuiBase;
import forge.UiCommand;
import forge.deck.Deck;
import forge.game.GameRules;
import forge.game.GameType;
import forge.game.player.RegisteredPlayer;
import forge.gauntlet.GauntletData;
import forge.gauntlet.GauntletIO;
import forge.gui.SOverlayUtils;
import forge.gui.framework.ICDoc;
import forge.match.HostedMatch;
import forge.menus.IMenuProvider;
import forge.menus.MenuUtil;
import forge.player.GamePlayerUtil;
import forge.puzzle.Puzzle;
import forge.puzzle.PuzzleIO;
import forge.quest.QuestUtil;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public enum CSubmenuPuzzleSolve implements ICDoc, IMenuProvider {
SINGLETON_INSTANCE;
private VSubmenuPuzzleSolve view = VSubmenuPuzzleSolve.SINGLETON_INSTANCE;
@Override
public void register() {
}
@Override
public void initialize() {
view.getList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
updateData();
view.getBtnStart().addActionListener(
new ActionListener() { @Override
public void actionPerformed(final ActionEvent e) { startPuzzleSolve(); } });
}
private final UiCommand cmdStart = new UiCommand() {
@Override public void run() {
startPuzzleSolve();
}
};
private void updateData() {
final ArrayList<Puzzle> puzzles = PuzzleIO.loadPuzzles();
for(Puzzle p : puzzles) {
view.getModel().addElement(p);
}
}
@Override
public void update() {
MenuUtil.setMenuProvider(this);
}
@Override
public List<JMenu> getMenus() {
final List<JMenu> menus = new ArrayList<JMenu>();
menus.add(PuzzleGameMenu.getMenu());
return menus;
}
private boolean startPuzzleSolve() {
final Puzzle selected = (Puzzle)view.getList().getSelectedValue();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SOverlayUtils.startGameOverlay();
SOverlayUtils.showOverlay();
}
});
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();
hostedMatch.setStartGameHook(new Runnable() {
@Override
public final void run() {
selected.applyToGame(hostedMatch.getGame());
}
});
final List<RegisteredPlayer> players = new ArrayList<RegisteredPlayer>();
final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer());
human.setStartingHand(0);
players.add(human);
final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer());
ai.setStartingHand(0);
players.add(ai);
GameRules rules = new GameRules(GameType.Puzzle);
rules.setGamesPerMatch(1);
hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SOverlayUtils.hideOverlay();
}
});
return true;
}
}

View File

@@ -0,0 +1,19 @@
package forge.screens.home.puzzle;
import forge.model.FModel;
import forge.properties.ForgePreferences;
import javax.swing.*;
import java.awt.event.KeyEvent;
public class PuzzleGameMenu {
private PuzzleGameMenu() { }
private static ForgePreferences prefs = FModel.getPreferences();
public static JMenu getMenu() {
JMenu menu = new JMenu("Puzzle");
menu.setMnemonic(KeyEvent.VK_G);
return menu;
}
}

View File

@@ -0,0 +1,94 @@
package forge.screens.home.puzzle;
import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID;
import forge.interfaces.IPlayerChangeListener;
import forge.match.GameLobby;
import forge.match.LocalLobby;
import forge.net.event.UpdateLobbyPlayerEvent;
import forge.screens.home.EMenuGroup;
import forge.screens.home.IVSubmenu;
import forge.screens.home.VHomeUI;
import forge.screens.home.VLobby;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public enum VSubmenuPuzzleCreate implements IVSubmenu<CSubmenuPuzzleCreate> {
SINGLETON_INSTANCE;
private DragCell parentCell;
private final DragTab tab = new DragTab("Puzzle Mode: Create");
private final GameLobby lobby = new LocalLobby();
private final VLobby vLobby = new VLobby(lobby);
VSubmenuPuzzleCreate() {
lobby.setListener(vLobby);
vLobby.setPlayerChangeListener(new IPlayerChangeListener() {
@Override public final void update(final int index, final UpdateLobbyPlayerEvent event) {
lobby.applyToSlot(index, event);
}
});
vLobby.update(false);
}
@Override
public EMenuGroup getGroupEnum() {
return EMenuGroup.PUZZLE;
}
@Override
public String getMenuTitle() {
return "Create";
}
@Override
public EDocID getItemEnum() {
return EDocID.HOME_PUZZLE_CREATE;
}
@Override
public EDocID getDocumentID() {
return EDocID.HOME_PUZZLE_CREATE;
}
@Override
public DragTab getTabLabel() {
return tab;
}
@Override
public CSubmenuPuzzleCreate getLayoutControl() {
return CSubmenuPuzzleCreate.SINGLETON_INSTANCE;
}
@Override
public void setParentCell(DragCell cell0) {
this.parentCell = cell0;
}
@Override
public DragCell getParentCell() {
return this.parentCell;
}
@Override
public void populate() {
final JPanel container = VHomeUI.SINGLETON_INSTANCE.getPnlDisplay();
container.removeAll();
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
vLobby.getLblTitle().setText("Puzzle Mode: Create");
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
if (container.isShowing()) {
container.validate();
container.repaint();
}
}
}

View File

@@ -0,0 +1,112 @@
package forge.screens.home.puzzle;
import forge.gui.framework.DragCell;
import forge.gui.framework.DragTab;
import forge.gui.framework.EDocID;
import forge.interfaces.IPlayerChangeListener;
import forge.match.GameLobby;
import forge.match.LocalLobby;
import forge.net.event.UpdateLobbyPlayerEvent;
import forge.screens.home.*;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public enum VSubmenuPuzzleSolve implements IVSubmenu<CSubmenuPuzzleSolve> {
SINGLETON_INSTANCE;
private final JList puzzleList;
final DefaultListModel model = new DefaultListModel();
private final StartButton btnStart = new StartButton();
private DragCell parentCell;
private final DragTab tab = new DragTab("Puzzle Mode: Solve");
private final GameLobby lobby = new LocalLobby();
private final VLobby vLobby = new VLobby(lobby);
VSubmenuPuzzleSolve() {
puzzleList = new JList<>();
lobby.setListener(vLobby);
vLobby.setPlayerChangeListener(new IPlayerChangeListener() {
@Override public final void update(final int index, final UpdateLobbyPlayerEvent event) {
lobby.applyToSlot(index, event);
}
});
vLobby.update(false);
}
@Override
public EMenuGroup getGroupEnum() {
return EMenuGroup.PUZZLE;
}
@Override
public String getMenuTitle() {
return "Solve";
}
@Override
public EDocID getItemEnum() {
return EDocID.HOME_PUZZLE_SOLVE;
}
@Override
public EDocID getDocumentID() {
return EDocID.HOME_PUZZLE_SOLVE;
}
@Override
public DragTab getTabLabel() {
return tab;
}
@Override
public CSubmenuPuzzleSolve getLayoutControl() {
return CSubmenuPuzzleSolve.SINGLETON_INSTANCE;
}
@Override
public void setParentCell(DragCell cell0) {
this.parentCell = cell0;
}
@Override
public DragCell getParentCell() {
return this.parentCell;
}
public JList getList() {
return puzzleList;
}
public DefaultListModel getModel() {
return model;
}
public StartButton getBtnStart() {
return btnStart;
}
@Override
public void populate() {
final JPanel container = VHomeUI.SINGLETON_INSTANCE.getPnlDisplay();
container.removeAll();
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
vLobby.getLblTitle().setText("Puzzle Mode: Solve");
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
puzzleList.setModel(model);
container.add(puzzleList, "w 80%, h 200px!, gap 0 0 0px 0px, span 2, al center");
container.add(btnStart, "w 98%!, ax center, gap 1% 0 20px 20px, span 2");
if (container.isShowing()) {
container.validate();
container.repaint();
}
}
}

View File

@@ -11,6 +11,7 @@ import forge.properties.ForgePreferences.FPref;
import forge.screens.FScreen;
import forge.screens.constructed.ConstructedScreen;
import forge.screens.gauntlet.NewGauntletScreen;
import forge.screens.home.puzzle.PuzzleScreen;
import forge.screens.limited.NewDraftScreen;
import forge.screens.limited.NewSealedScreen;
import forge.screens.planarconquest.NewConquestScreen;
@@ -24,6 +25,7 @@ public class NewGameMenu extends FPopupMenu {
BoosterDraft("Booster Draft", FSkinImage.HAND, NewDraftScreen.class),
SealedDeck("Sealed Deck", FSkinImage.PACK, NewSealedScreen.class),
QuestMode("Quest Mode", FSkinImage.QUEST_ZEP, NewQuestScreen.class),
PuzzleMode("Puzzle Mode", FSkinImage.QUEST_BOOK, PuzzleScreen.class),
PlanarConquest("Planar Conquest", FSkinImage.MULTIVERSE, NewConquestScreen.class),
Gauntlet("Gauntlet", FSkinImage.ALPHASTRIKE, NewGauntletScreen.class);

View File

@@ -0,0 +1,42 @@
package forge.screens.home.puzzle;
import forge.assets.FSkinFont;
import forge.screens.LaunchScreen;
import forge.screens.home.NewGameMenu;
import forge.toolbox.FLabel;
import forge.toolbox.FTextArea;
import forge.util.ThreadUtil;
import forge.util.Utils;
public class PuzzleScreen extends LaunchScreen {
private static final float PADDING = Utils.scale(10);
private final FTextArea lblDesc = add(new FTextArea(false,
"Puzzle Mode loads in a puzzle that you have to win in a predetermined time/way."));
public PuzzleScreen() {
super(null, NewGameMenu.getMenu());
lblDesc.setFont(FSkinFont.get(12));
lblDesc.setTextColor(FLabel.INLINE_LABEL_COLOR);
}
@Override
protected void doLayoutAboveBtnStart(float startY, float width, float height) {
float x = PADDING;
float y = startY + PADDING;
float w = width - 2 * PADDING;
float h = height - y - PADDING;
lblDesc.setBounds(x, y, w, h);
}
@Override
protected void startMatch() {
ThreadUtil.invokeInGameThread(new Runnable() { //must run in game thread to prevent blocking UI thread
@Override
public void run() {
// Load selected puzzle
}
});
}
}

View File

@@ -0,0 +1,13 @@
[metadata]
Name:Possibility Storm #1
URL:https://i.redd.it/wws1h03gy7ky.png
Goal:Win
Turns:1
[state]
ActivePlayer=Human
ActivePhase=Main1
HumanLife=20
AILife=9
HumanPlay=Key to the City; Ravenous Intruder; Ruinous Gremlin; Island; Island; Island; Island; Island; Mountain; Mountain
HumanHand=Welcome to the Fold; Chilling Grasp; Flame Lash; Confirm Suspicions
AIPlay=Workshop Assistant; Thriving Ibex

View File

@@ -54,6 +54,7 @@ public class HostedMatch {
private Match match;
private Game game;
private String title;
private Runnable startGameHook = null;
private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList();
private Map<RegisteredPlayer, IGuiGame> guis;
private int humanCount;
@@ -62,7 +63,10 @@ public class HostedMatch {
private final Map<PlayerControllerHuman, NextGameDecision> nextGameDecisions = Maps.newHashMap();
private boolean isMatchOver = false;
public HostedMatch() {
public HostedMatch() {}
public void setStartGameHook(Runnable hook) {
startGameHook = hook;
}
private static GameRules getDefaultRules(final GameType gameType) {
@@ -216,9 +220,8 @@ public class HostedMatch {
playbackControl.setGame(game);
game.subscribeToEvents(playbackControl);
}
// Actually start the game!
match.startGame(game);
match.startGame(game, startGameHook);
// After game is over...
isMatchOver = match.isMatchOver();

View File

@@ -62,6 +62,8 @@ public final class ForgeConstants {
public static final String MUSIC_DIR = RES_DIR + "music" + PATH_SEPARATOR;
public static final String LANG_DIR = RES_DIR + "languages" + PATH_SEPARATOR;
public static final String EFFECTS_DIR = RES_DIR + "effects" + PATH_SEPARATOR;
public static final String PUZZLE_DIR = RES_DIR + "puzzle" + PATH_SEPARATOR;
private static final String QUEST_DIR = RES_DIR + "quest" + PATH_SEPARATOR;
public static final String QUEST_WORLD_DIR = QUEST_DIR + "world" + PATH_SEPARATOR;

View File

@@ -108,6 +108,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
SUBMENU_ONLINE ("false"),
SUBMENU_GAUNTLET ("false"),
SUBMENU_QUEST ("false"),
SUBMENU_PUZZLE("false"),
SUBMENU_SETTINGS ("false"),
SUBMENU_UTILITIES ("false"),

View File

@@ -0,0 +1,104 @@
package forge.puzzle;
import forge.ai.GameState;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
import forge.model.FModel;
import java.util.List;
import java.util.Map;
public class Puzzle extends GameState implements InventoryItem {
String name;
String goal;
String url;
int turns;
public Puzzle(Map<String, List<String>> puzzleLines) {
loadMetaData(puzzleLines.get("metadata"));
loadGameState(puzzleLines.get("state"));
// Generate goal enforcement
}
private void loadMetaData(List<String> metadataLines) {
for(String line : metadataLines) {
String[] split = line.split(":");
if ("Name".equalsIgnoreCase(split[0])) {
this.name = split[1];
} else if ("Goal".equalsIgnoreCase(split[0])) {
this.goal = split[1];
} else if ("Url".equalsIgnoreCase(split[0])) {
this.url = split[1];
} else if ("Turns".equalsIgnoreCase(split[0])) {
this.turns = Integer.parseInt(split[1]);
}
}
}
private void loadGameState(List<String> stateLines) {
this.parse(stateLines);
}
public IPaperCard getPaperCard(final String cardName) {
return FModel.getMagicDb().getCommonCards().getCard(cardName);
}
public void addGoalEnforcement(Game game) {
Player human = null;
for(Player p : game.getPlayers()) {
if (p.getController().isGuiPlayer()) {
human = p;
}
}
Card goalCard = new Card(-1, game);
goalCard.setOwner(human);
goalCard.setImageKey("t:puzzle");
goalCard.setName("Puzzle Goal");
goalCard.addType("Effect");
{
final String loseTrig = "Mode$ Phase | Phase$ Cleanup | TriggerZones$ Command | Static$ True | " +
"ValidPlayer$ You | TriggerDescription$ At the beginning of your cleanup step, you lose the game.";
final String loseEff = "DB$ LosesGame | Defined$ You";
final Trigger loseTrigger = TriggerHandler.parseTrigger(loseTrig, goalCard, true);
loseTrigger.setOverridingAbility(AbilityFactory.getAbility(loseEff, goalCard));
goalCard.addTrigger(loseTrigger);
}
human.getZone(ZoneType.Command).add(goalCard);
}
@Override
protected void applyGameOnThread(final Game game) {
super.applyGameOnThread(game);
addGoalEnforcement(game);
}
@Override
public String getItemType() {
return "Puzzle";
}
@Override
public String getImageKey(boolean altState) {
return null;
}
@Override
public String getName() {
return name;
}
public String toString() { return name; }
}

View File

@@ -0,0 +1,50 @@
package forge.puzzle;
import com.google.common.collect.Lists;
import forge.properties.ForgeConstants;
import forge.util.FileSection;
import forge.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PuzzleIO {
public static final String TXF_PROMPT = "[New Puzzle]";
public static final String SUFFIX_DATA = ".pzl";
public static ArrayList<Puzzle> loadPuzzles() {
String[] pList;
// get list of custom draft files
final File pFolder = new File(ForgeConstants.PUZZLE_DIR);
if (!pFolder.exists()) {
throw new RuntimeException("Puzzles : folder not found -- folder is " + pFolder.getAbsolutePath());
}
if (!pFolder.isDirectory()) {
throw new RuntimeException("Puzzles : not a folder -- " + pFolder.getAbsolutePath());
}
pList = pFolder.list();
ArrayList<Puzzle> puzzles = Lists.newArrayList();
for (final String element : pList) {
if (element.endsWith(SUFFIX_DATA)) {
final List<String> pfData = FileUtil.readFile(ForgeConstants.PUZZLE_DIR + element);
puzzles.add(new Puzzle(parsePuzzleSections(pfData)));
}
}
return puzzles;
}
public static final Map<String, List<String>> parsePuzzleSections(List<String> pfData) {
return FileSection.parseSections(pfData);
}
public static File getPuzzleFile(final String name) {
return new File(ForgeConstants.PUZZLE_DIR, name + SUFFIX_DATA);
}
}