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/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/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/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/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/CSubmenuDuels.java -text
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestData.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/HomeScreen.java -text
forge-gui-mobile/src/forge/screens/home/LoadGameMenu.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/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/DraftingProcessScreen.java -text
forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java -text forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java -text
forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.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/Heroic[!!-~]Age.mp3 -text
forge-gui/res/music/menus/Lord[!!-~]of[!!-~]the[!!-~]Land.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/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_l1.txt -text
forge-gui/res/quest/bazaar/ape_pet_l2.txt -text forge-gui/res/quest/bazaar/ape_pet_l2.txt -text
forge-gui/res/quest/bazaar/ape_pet_l3.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/ForgeConstants.java -text
forge-gui/src/main/java/forge/properties/ForgePreferences.java svneol=native#text/plain 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/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/BoosterUtils.java svneol=native#text/plain
forge-gui/src/main/java/forge/quest/IQuestEvent.java -text forge-gui/src/main/java/forge/quest/IQuestEvent.java -text
forge-gui/src/main/java/forge/quest/IQuestRewardCard.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.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.effects.DetachedCardEffect; import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -31,7 +32,7 @@ import forge.util.collect.FCollectionView;
public abstract class GameState { public abstract class GameState {
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>(); private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
static { static {
ZONES.put(ZoneType.Battlefield, "play"); ZONES.put(ZoneType.Battlefield, "battlefield");
ZONES.put(ZoneType.Hand, "hand"); ZONES.put(ZoneType.Hand, "hand");
ZONES.put(ZoneType.Graveyard, "graveyard"); ZONES.put(ZoneType.Graveyard, "graveyard");
ZONES.put(ZoneType.Library, "library"); ZONES.put(ZoneType.Library, "library");
@@ -41,8 +42,15 @@ public abstract class GameState {
private int humanLife = -1; private int humanLife = -1;
private int computerLife = -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> humanCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
private final Map<ZoneType, String> aiCardTexts = 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 tChangePlayer = "NONE";
private String tChangePhase = "NONE"; private String tChangePhase = "NONE";
@@ -56,6 +64,14 @@ public abstract class GameState {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(String.format("humanlife=%d\n", humanLife)); sb.append(String.format("humanlife=%d\n", humanLife));
sb.append(String.format("ailife=%d\n", computerLife)); 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("activeplayer=%s\n", tChangePlayer));
sb.append(String.format("activephase=%s\n", tChangePhase)); sb.append(String.format("activephase=%s\n", tChangePhase));
appendCards(humanCardTexts, "human", sb); appendCards(humanCardTexts, "human", sb);
@@ -65,7 +81,7 @@ public abstract class GameState {
private void appendCards(Map<ZoneType, String> cardTexts, String categoryPrefix, StringBuilder sb) { private void appendCards(Map<ZoneType, String> cardTexts, String categoryPrefix, StringBuilder sb) {
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) { 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(); humanLife = human.getLife();
computerLife = ai.getLife(); computerLife = ai.getLife();
humanCounters = countersToString(human.getCounters());
computerCounters = countersToString(ai.getCounters());
tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human"; tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human";
tChangePhase = game.getPhaseHandler().getPhase().toString(); tChangePhase = game.getPhaseHandler().getPhase().toString();
aiCardTexts.clear(); aiCardTexts.clear();
@@ -111,43 +130,61 @@ public abstract class GameState {
newText.append(c.getPaperCard().getName()); newText.append(c.getPaperCard().getName());
} }
if (c.isCommander()) { if (c.isCommander()) {
newText.append("|IsCommander:True"); newText.append("|IsCommander");
} }
if (zoneType == ZoneType.Battlefield) { if (zoneType == ZoneType.Battlefield) {
if (c.isTapped()) { if (c.isTapped()) {
newText.append("|Tapped:True"); newText.append("|Tapped");
} }
if (c.isSick()) { if (c.isSick()) {
newText.append("|SummonSick:True"); newText.append("|SummonSick");
} }
if (c.isFaceDown()) { if (c.isFaceDown()) {
newText.append("|FaceDown:True"); newText.append("|FaceDown");
if (c.isManifested()) {
newText.append(":Manifested");
}
} }
Map<CounterType, Integer> counters = c.getCounters(); Map<CounterType, Integer> counters = c.getCounters();
if (!counters.isEmpty()) { if (!counters.isEmpty()) {
newText.append("|Counters:"); newText.append("|Counters:");
boolean start = true; newText.append(countersToString(counters));
for(Entry<CounterType, Integer> kv : counters.entrySet()) { }
String str = kv.getKey().toString(); if (c.getEquipping() != null) {
int count = kv.getValue(); newText.append("|Attaching:").append(c.getEquipping().getId());
for (int i = 0; i < count; i++) { } else if (c.getFortifying() != null) {
if (!start) { newText.append("|Attaching:").append(c.getFortifying().getId());
newText.append(","); } else if (c.getEnchantingCard() != null) {
} newText.append("|Attaching:").append(c.getEnchantingCard().getId());
newText.append(str); }
start = false;
} if (!c.getEnchantedBy(false).isEmpty() || !c.getEquippedBy(false).isEmpty() || !c.getFortifiedBy(false).isEmpty()) {
} newText.append("|Id:").append(c.getId());
} }
} }
cardTexts.put(zoneType, newText.toString()); 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) == '#') { if (line.charAt(0) == '#') {
return null; return null;
} }
final String[] tempData = line.split("="); final String[] tempData = line.split("=", 2);
if (tempData.length >= 2) { if (tempData.length >= 2) {
return tempData; return tempData;
} }
@@ -163,32 +200,91 @@ public abstract class GameState {
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
String[] keyValue = parseLine(line); parseLine(line);
if (keyValue == null) { }
continue; }
}
final String categoryName = keyValue[0].toLowerCase();
final String categoryValue = keyValue[1];
if (categoryName.equals("humanlife")) humanLife = Integer.parseInt(categoryValue); public void parse(List<String> lines) {
else if (categoryName.equals("ailife")) computerLife = Integer.parseInt(categoryValue); for(String line : lines) {
parseLine(line);
}
}
else if (categoryName.equals("activeplayer")) tChangePlayer = categoryValue.trim().toLowerCase(); protected void parseLine(String line) {
else if (categoryName.equals("activephase")) tChangePhase = categoryValue; String[] keyValue = splitLine(line);
if (keyValue == null) return;
else if (categoryName.equals("humancardsinplay")) humanCardTexts.put(ZoneType.Battlefield, categoryValue); final String categoryName = keyValue[0].toLowerCase();
else if (categoryName.equals("aicardsinplay")) aiCardTexts.put(ZoneType.Battlefield, categoryValue); final String categoryValue = keyValue[1];
else if (categoryName.equals("humancardsinhand")) humanCardTexts.put(ZoneType.Hand, categoryValue);
else if (categoryName.equals("aicardsinhand")) aiCardTexts.put(ZoneType.Hand, categoryValue); if (categoryName.startsWith("active")) {
else if (categoryName.equals("humancardsingraveyard")) humanCardTexts.put(ZoneType.Graveyard, categoryValue); if (categoryName.endsWith("player"))
else if (categoryName.equals("aicardsingraveyard")) aiCardTexts.put(ZoneType.Graveyard, categoryValue); tChangePlayer = categoryValue.trim().toLowerCase();
else if (categoryName.equals("humancardsinlibrary")) humanCardTexts.put(ZoneType.Library, categoryValue); if (categoryName.endsWith("phase"))
else if (categoryName.equals("aicardsinlibrary")) aiCardTexts.put(ZoneType.Library, categoryValue); tChangePhase = categoryValue.trim().toUpperCase();
else if (categoryName.equals("humancardsinexile")) humanCardTexts.put(ZoneType.Exile, categoryValue); return;
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); boolean isHuman = categoryName.startsWith("human");
else System.out.println("Unknown key: " + categoryName);
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() { game.getAction().invoke(new Runnable() {
@Override @Override
public void run() { public void run() {
final Player human = game.getPlayers().get(0); applyGameOnThread(game);
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
} }
}); });
} }
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) { 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); Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) { for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
String value = kv.getValue(); String value = kv.getValue();
@@ -245,7 +368,12 @@ public abstract class GameState {
// var as-is. // var as-is.
c.setCounters(new HashMap<CounterType, Integer>()); c.setCounters(new HashMap<CounterType, Integer>());
p.getZone(ZoneType.Hand).add(c); 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.setTapped(tapped);
c.setSickness(sickness); c.setSickness(sickness);
c.setCounters(counters); c.setCounters(counters);
@@ -254,6 +382,19 @@ public abstract class GameState {
zone.setCards(kv.getValue()); 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. * an array of {@link java.lang.String} objects.
* @param player * @param player
* a {@link forge.game.player.Player} object. * 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) { private CardCollectionView processCardsForZone(final String[] data, final Player player) {
final CardCollection cl = new CardCollection(); final CardCollection cl = new CardCollection();
@@ -286,22 +427,29 @@ public abstract class GameState {
if (info.startsWith("Set:")) { if (info.startsWith("Set:")) {
c.setSetCode(info.substring(info.indexOf(':') + 1)); c.setSetCode(info.substring(info.indexOf(':') + 1));
hasSetCurSet = true; hasSetCurSet = true;
} else if (info.equalsIgnoreCase("Tapped:True")) { }
else if (info.startsWith("Tapped")) {
c.tap(); c.tap();
} else if (info.startsWith("Counters:")) { } else if (info.startsWith("Counters:")) {
final String[] counterStrings = info.substring(info.indexOf(':') + 1).split(","); applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
for (final String counter : counterStrings) { } else if (info.startsWith("SummonSick")) {
c.addCounter(CounterType.valueOf(counter), 1, true);
}
} else if (info.equalsIgnoreCase("SummonSick:True")) {
c.setSickness(true); c.setSickness(true);
} else if (info.equalsIgnoreCase("FaceDown:True")) { } else if (info.startsWith("FaceDown")) {
c.setState(CardStateName.FaceDown, true); 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? // TODO: This doesn't seem to properly restore the ability to play the commander. Why?
c.setCommander(true); c.setCommander(true);
player.setCommanders(Lists.newArrayList(c)); player.setCommanders(Lists.newArrayList(c));
player.getZone(ZoneType.Command).add(Player.createCommanderEffect(player.getGame(), 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), PlanarConquest ( Range.between(40, Integer.MAX_VALUE), Range.is(0), 1),
Vanguard ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4), Vanguard ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
Planechase ( 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> mainRange;
private final Range<Integer> sideRange; // null => no check private final Range<Integer> sideRange; // null => no check
private final int maxCardCopies; private final int maxCardCopies;
private final Predicate<CardRules> cardPoolFilter; private final Predicate<CardRules> cardPoolFilter;
private final static String ADVPROCLAMATION = "Advantageous Proclamation"; 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) { private DeckFormat(Range<Integer> mainRange0, Range<Integer> sideRange0, int maxCardCopies0) {
this(mainRange0, sideRange0, maxCardCopies0, null); this(mainRange0, sideRange0, maxCardCopies0, null);
@@ -168,11 +170,13 @@ public enum DeckFormat {
int min = getMainRange().getMinimum(); int min = getMainRange().getMinimum();
int max = getMainRange().getMaximum(); int max = getMainRange().getMaximum();
boolean noBasicLands = false;
// Adjust minimum base on number of Advantageous Proclamation or similar cards // Adjust minimum base on number of Advantageous Proclamation or similar cards
CardPool conspiracies = deck.get(DeckSection.Conspiracy); CardPool conspiracies = deck.get(DeckSection.Conspiracy);
if (conspiracies != null) { 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 if (hasCommander()) { // 1 Commander, or 2 Partner Commanders

View File

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

View File

@@ -17,6 +17,7 @@ public enum GameType {
Quest (DeckFormat.QuestDeck, true, true, false, "Quest", ""), Quest (DeckFormat.QuestDeck, true, true, false, "Quest", ""),
QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", ""), QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", ""),
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "Planar Conquest", ""), 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", ""), Constructed (DeckFormat.Constructed, false, true, true, "Constructed", ""),
DeckManager (DeckFormat.Constructed, false, true, true, "Deck Manager", ""), 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."), 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) { public void startGame(final Game game) {
startGame(game, null);
}
public void startGame(final Game game, Runnable startGameHook) {
prepareAllZones(game); prepareAllZones(game);
if (rules.useAnte()) { // Deciding which cards go to ante if (rules.useAnte()) { // Deciding which cards go to ante
Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity()); 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); GameOutcome lastOutcome = gamesPlayed.isEmpty() ? null : gamesPlayed.get(gamesPlayed.size() - 1);
game.getAction().startGame(lastOutcome);
game.getAction().startGame(lastOutcome, startGameHook);
if (rules.useAnte()) { if (rules.useAnte()) {
executeAnte(game); executeAnte(game);

View File

@@ -987,7 +987,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
@Override @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; int addAmount = n;
if(addAmount < 0) { if(addAmount < 0) {
addAmount = 0; // As per rule 107.1b 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; private final static boolean DEBUG_PHASES = false;
public void startFirstTurn(Player goesFirst) { public void startFirstTurn(Player goesFirst) {
startFirstTurn(goesFirst, null);
}
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
if (phase != null) { 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 // don't even offer priority, because it's untap of 1st turn now
givePriorityToPlayer = false; givePriorityToPlayer = false;
if (startGameHook != null) {
startGameHook.run();
givePriorityToPlayer = true;
}
// MAIN GAME LOOP // MAIN GAME LOOP
while (!game.isGameOver()) { while (!game.isGameOver()) {
if (givePriorityToPlayer) { if (givePriorityToPlayer) {

View File

@@ -884,7 +884,7 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
@Override @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)) { if (!canReceiveCounters(counterType)) {
return; return;
} }

View File

@@ -16,6 +16,8 @@ import forge.screens.home.gauntlet.VSubmenuGauntletContests;
import forge.screens.home.gauntlet.VSubmenuGauntletLoad; import forge.screens.home.gauntlet.VSubmenuGauntletLoad;
import forge.screens.home.gauntlet.VSubmenuGauntletQuick; import forge.screens.home.gauntlet.VSubmenuGauntletQuick;
import forge.screens.home.online.VSubmenuOnlineLobby; 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.VSubmenuChallenges;
import forge.screens.home.quest.VSubmenuDuels; import forge.screens.home.quest.VSubmenuDuels;
import forge.screens.home.quest.VSubmenuQuestData; import forge.screens.home.quest.VSubmenuQuestData;
@@ -71,6 +73,8 @@ public enum EDocID {
HOME_ACHIEVEMENTS (VSubmenuAchievements.SINGLETON_INSTANCE), HOME_ACHIEVEMENTS (VSubmenuAchievements.SINGLETON_INSTANCE),
HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE), HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE),
HOME_UTILITIES (VSubmenuDownloaders.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_CONSTRUCTED (VSubmenuConstructed.SINGLETON_INSTANCE),
HOME_DRAFT (VSubmenuDraft.SINGLETON_INSTANCE), HOME_DRAFT (VSubmenuDraft.SINGLETON_INSTANCE),
HOME_SEALED (VSubmenuSealed.SINGLETON_INSTANCE), HOME_SEALED (VSubmenuSealed.SINGLETON_INSTANCE),

View File

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

View File

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

View File

@@ -32,6 +32,8 @@ import javax.swing.JPanel;
import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import forge.screens.home.puzzle.VSubmenuPuzzleCreate;
import forge.screens.home.puzzle.VSubmenuPuzzleSolve;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import forge.Singletons; import forge.Singletons;
import forge.assets.FSkinProp; 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.VSubmenuConstructed;
import forge.screens.home.sanctioned.VSubmenuDraft; import forge.screens.home.sanctioned.VSubmenuDraft;
import forge.screens.home.sanctioned.VSubmenuSealed; import forge.screens.home.sanctioned.VSubmenuSealed;
import forge.screens.home.sanctioned.VSubmenuWinston;
import forge.screens.home.settings.VSubmenuAchievements; import forge.screens.home.settings.VSubmenuAchievements;
import forge.screens.home.settings.VSubmenuAvatars; import forge.screens.home.settings.VSubmenuAvatars;
import forge.screens.home.settings.VSubmenuDownloaders; import forge.screens.home.settings.VSubmenuDownloaders;
@@ -132,6 +135,9 @@ public enum VHomeUI implements IVTopLevelUI {
allSubmenus.add(VSubmenuGauntletLoad.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuGauntletLoad.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuGauntletContests.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuGauntletContests.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPuzzleSolve.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPuzzleCreate.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPreferences.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuPreferences.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuAchievements.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuAchievements.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuAvatars.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.FScreen;
import forge.screens.constructed.ConstructedScreen; import forge.screens.constructed.ConstructedScreen;
import forge.screens.gauntlet.NewGauntletScreen; import forge.screens.gauntlet.NewGauntletScreen;
import forge.screens.home.puzzle.PuzzleScreen;
import forge.screens.limited.NewDraftScreen; import forge.screens.limited.NewDraftScreen;
import forge.screens.limited.NewSealedScreen; import forge.screens.limited.NewSealedScreen;
import forge.screens.planarconquest.NewConquestScreen; import forge.screens.planarconquest.NewConquestScreen;
@@ -24,6 +25,7 @@ public class NewGameMenu extends FPopupMenu {
BoosterDraft("Booster Draft", FSkinImage.HAND, NewDraftScreen.class), BoosterDraft("Booster Draft", FSkinImage.HAND, NewDraftScreen.class),
SealedDeck("Sealed Deck", FSkinImage.PACK, NewSealedScreen.class), SealedDeck("Sealed Deck", FSkinImage.PACK, NewSealedScreen.class),
QuestMode("Quest Mode", FSkinImage.QUEST_ZEP, NewQuestScreen.class), QuestMode("Quest Mode", FSkinImage.QUEST_ZEP, NewQuestScreen.class),
PuzzleMode("Puzzle Mode", FSkinImage.QUEST_BOOK, PuzzleScreen.class),
PlanarConquest("Planar Conquest", FSkinImage.MULTIVERSE, NewConquestScreen.class), PlanarConquest("Planar Conquest", FSkinImage.MULTIVERSE, NewConquestScreen.class),
Gauntlet("Gauntlet", FSkinImage.ALPHASTRIKE, NewGauntletScreen.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 Match match;
private Game game; private Game game;
private String title; private String title;
private Runnable startGameHook = null;
private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList(); private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList();
private Map<RegisteredPlayer, IGuiGame> guis; private Map<RegisteredPlayer, IGuiGame> guis;
private int humanCount; private int humanCount;
@@ -62,7 +63,10 @@ public class HostedMatch {
private final Map<PlayerControllerHuman, NextGameDecision> nextGameDecisions = Maps.newHashMap(); private final Map<PlayerControllerHuman, NextGameDecision> nextGameDecisions = Maps.newHashMap();
private boolean isMatchOver = false; private boolean isMatchOver = false;
public HostedMatch() { public HostedMatch() {}
public void setStartGameHook(Runnable hook) {
startGameHook = hook;
} }
private static GameRules getDefaultRules(final GameType gameType) { private static GameRules getDefaultRules(final GameType gameType) {
@@ -216,9 +220,8 @@ public class HostedMatch {
playbackControl.setGame(game); playbackControl.setGame(game);
game.subscribeToEvents(playbackControl); game.subscribeToEvents(playbackControl);
} }
// Actually start the game! // Actually start the game!
match.startGame(game); match.startGame(game, startGameHook);
// After game is over... // After game is over...
isMatchOver = match.isMatchOver(); 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 MUSIC_DIR = RES_DIR + "music" + PATH_SEPARATOR;
public static final String LANG_DIR = RES_DIR + "languages" + 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 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; private static final String QUEST_DIR = RES_DIR + "quest" + PATH_SEPARATOR;
public static final String QUEST_WORLD_DIR = QUEST_DIR + "world" + 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_ONLINE ("false"),
SUBMENU_GAUNTLET ("false"), SUBMENU_GAUNTLET ("false"),
SUBMENU_QUEST ("false"), SUBMENU_QUEST ("false"),
SUBMENU_PUZZLE("false"),
SUBMENU_SETTINGS ("false"), SUBMENU_SETTINGS ("false"),
SUBMENU_UTILITIES ("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);
}
}