diff --git a/forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java b/forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java
index 3b38efabd61..771ad61eac8 100644
--- a/forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java
+++ b/forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java
@@ -12,6 +12,7 @@ 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.puzzle.VSubmenuTutorial;
import forge.screens.home.quest.*;
import forge.screens.home.sanctioned.VSubmenuConstructed;
import forge.screens.home.sanctioned.VSubmenuDraft;
@@ -63,6 +64,7 @@ public enum EDocID {
HOME_ACHIEVEMENTS (VSubmenuAchievements.SINGLETON_INSTANCE),
HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE),
HOME_UTILITIES (VSubmenuDownloaders.SINGLETON_INSTANCE),
+ HOME_TUTORIAL(VSubmenuTutorial.SINGLETON_INSTANCE),
HOME_PUZZLE_CREATE(VSubmenuPuzzleCreate.SINGLETON_INSTANCE),
HOME_PUZZLE_SOLVE(VSubmenuPuzzleSolve.SINGLETON_INSTANCE),
HOME_CONSTRUCTED (VSubmenuConstructed.SINGLETON_INSTANCE),
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java
index 7bbe5b2d7d6..fe610e80de0 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java
@@ -29,6 +29,7 @@ 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.puzzle.VSubmenuTutorial;
import forge.screens.home.quest.*;
import forge.screens.home.sanctioned.VSubmenuConstructed;
import forge.screens.home.sanctioned.VSubmenuDraft;
@@ -44,8 +45,8 @@ import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
-import java.util.List;
import java.util.*;
+import java.util.List;
/**
* Top level view class for home UI drag layout.
@@ -114,6 +115,7 @@ public enum VHomeUI implements IVTopLevelUI {
allSubmenus.add(VSubmenuPuzzleSolve.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPuzzleCreate.SINGLETON_INSTANCE);
+ allSubmenus.add(VSubmenuTutorial.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuPreferences.SINGLETON_INSTANCE);
allSubmenus.add(VSubmenuAchievements.SINGLETON_INSTANCE);
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java
index eee3e0e482e..ddfb8583b0d 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java
@@ -13,10 +13,11 @@ import forge.match.HostedMatch;
import forge.menus.IMenuProvider;
import forge.menus.MenuUtil;
import forge.player.GamePlayerUtil;
+import forge.properties.ForgeConstants;
import forge.puzzle.Puzzle;
import forge.puzzle.PuzzleIO;
-import forge.util.gui.SOptionPane;
import forge.util.Localizer;
+import forge.util.gui.SOptionPane;
import javax.swing.*;
import java.awt.event.ActionEvent;
@@ -53,7 +54,7 @@ public enum CSubmenuPuzzleSolve implements ICDoc, IMenuProvider {
};
private void updateData() {
- final ArrayList puzzles = PuzzleIO.loadPuzzles();
+ final ArrayList puzzles = PuzzleIO.loadPuzzles(ForgeConstants.PUZZLE_DIR);
Collections.sort(puzzles);
for(Puzzle p : puzzles) {
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java
new file mode 100644
index 00000000000..9ef4597861e
--- /dev/null
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java
@@ -0,0 +1,130 @@
+package forge.screens.home.puzzle;
+
+import forge.GuiBase;
+import forge.UiCommand;
+import forge.assets.FSkinProp;
+import forge.deck.Deck;
+import forge.game.GameRules;
+import forge.game.GameType;
+import forge.game.player.RegisteredPlayer;
+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.properties.ForgeConstants;
+import forge.puzzle.Puzzle;
+import forge.puzzle.PuzzleIO;
+import forge.util.Localizer;
+import forge.util.gui.SOptionPane;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public enum CSubmenuTutorial implements ICDoc, IMenuProvider {
+ SINGLETON_INSTANCE;
+
+ private VSubmenuTutorial view = VSubmenuTutorial.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() {
+ private static final long serialVersionUID = -367368436333443417L;
+
+ @Override public void run() {
+ startPuzzleSolve();
+ }
+ };
+
+ private void updateData() {
+ final ArrayList tutorials = PuzzleIO.loadPuzzles(ForgeConstants.TUTORIAL_DIR);
+ Collections.sort(tutorials);
+
+ for(Puzzle p : tutorials) {
+ view.getModel().addElement(p);
+ }
+ }
+
+ @Override
+ public void update() {
+ MenuUtil.setMenuProvider(this);
+ }
+
+ @Override
+ public List getMenus() {
+ final List menus = new ArrayList<>();
+ menus.add(PuzzleGameMenu.getMenu());
+ return menus;
+ }
+
+ private boolean startPuzzleSolve() {
+ final Puzzle selected = (Puzzle)view.getList().getSelectedValue();
+ if (selected == null) {
+ SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblPleaseFirstSelectAPuzzleFromList"), Localizer.getInstance().getMessage("lblNoSelectedPuzzle"), FSkinProp.ICO_ERROR);
+ return false;
+ }
+
+ 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() {
+ SOptionPane.showMessageDialog(selected.getGoalDescription(), selected.getName(), SOptionPane.INFORMATION_ICON);
+ selected.applyToGame(hostedMatch.getGame());
+ }
+ });
+
+ hostedMatch.setEndGameHook((new Runnable() {
+ @Override
+ public void run() {
+ selected.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer()));
+ }
+ }));
+
+ final List players = new ArrayList<>();
+ 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;
+ }
+}
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuTutorial.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuTutorial.java
new file mode 100644
index 00000000000..fc795c02bc3
--- /dev/null
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuTutorial.java
@@ -0,0 +1,122 @@
+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 forge.toolbox.FList;
+import forge.toolbox.FScrollPane;
+import forge.util.Localizer;
+import net.miginfocom.swing.MigLayout;
+
+import javax.swing.*;
+
+public enum VSubmenuTutorial implements IVSubmenu {
+ SINGLETON_INSTANCE;
+
+ private final FList tutorialList;
+ private final FScrollPane tutorialListPane;
+
+ final DefaultListModel model = new DefaultListModel();
+
+ private final StartButton btnStart = new StartButton();
+
+ private DragCell parentCell;
+ final Localizer localizer = Localizer.getInstance();
+ private final DragTab tab = new DragTab(localizer.getMessage("lblTutorialMode"));
+
+ private final GameLobby lobby = new LocalLobby();
+ private final VLobby vLobby = new VLobby(lobby);
+
+ VSubmenuTutorial() {
+ tutorialList = new FList<>();
+ tutorialListPane = new FScrollPane(this.tutorialList, true);
+
+ 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() {
+ final Localizer localizer = Localizer.getInstance();
+ return localizer.getMessage("lblTutorial");
+ }
+
+ @Override
+ public EDocID getItemEnum() {
+ return EDocID.HOME_TUTORIAL;
+ }
+
+ @Override
+ public EDocID getDocumentID() {
+ return EDocID.HOME_TUTORIAL;
+ }
+
+ @Override
+ public DragTab getTabLabel() {
+ return tab;
+ }
+
+ @Override
+ public CSubmenuTutorial getLayoutControl() {
+ return CSubmenuTutorial.SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public void setParentCell(DragCell cell0) {
+ this.parentCell = cell0;
+ }
+
+ @Override
+ public DragCell getParentCell() {
+ return this.parentCell;
+ }
+
+ public JList getList() {
+ return tutorialList;
+ }
+
+ 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"));
+ final Localizer localizer = Localizer.getInstance();
+ vLobby.getLblTitle().setText(localizer.getMessage("lblTutorialMode"));
+ container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
+ tutorialList.setModel(model);
+ container.add(tutorialListPane, "w 80%, h 80%, 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();
+ }
+ }
+}
diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties
index 7d1ef1cb8d7..8346e089c24 100644
--- a/forge-gui/res/languages/en-US.properties
+++ b/forge-gui/res/languages/en-US.properties
@@ -570,6 +570,9 @@ lblRandomThemeDecks=Random Theme Decks
lblRandomDecks=Random Decks
lblNetDecks=Net Decks
lblNetCommanderDecks=Net Commander Decks
+#VSubmenuTutorial
+lblTutorial=Tutorial
+lblTutorialMode=Tutorial Mode
#VSubmenuPuzzleSolve.java
lblSolve=Solve
lblPuzzleModeSolve=Puzzle Mode: Solve
diff --git a/forge-gui/res/tutorial/Spellslinger.pzl b/forge-gui/res/tutorial/Spellslinger.pzl
new file mode 100644
index 00000000000..f57eb7fa761
--- /dev/null
+++ b/forge-gui/res/tutorial/Spellslinger.pzl
@@ -0,0 +1,25 @@
+[metadata]
+Name:Spellslinger WR vs BG (Win the Game)
+URL:https://www.cardforge.org
+Goal:Win
+Turns:16
+Difficulty:Easy
+Description:This is a preconfigured game, where you need to overcome your opponents defenses by any means necessary and to reduce his/her lifepoints to 0 before you both run out of cards in your respective libraries. There are several ways to reach your goal.
+[state]
+humanlife=10
+ailife=10
+turn=1
+activeplayer=human
+activephase=MAIN1
+humanhand=Plains;Plains;Mountain;Mountain;Silverbeak Griffin;Hostile Minotaur;Charging Monstrosaur
+humanlibrary=Inspiring Captain;Knight of New Benalia;Frenzied Raptor;Moment of Triumph;Luminous Bonds;Plains;Plains
+humangraveyard=
+humanbattlefield=
+humanexile=
+humancommand=
+aihand=Swamp;Swamp;Forest;Forest;Colossal Dreadmaw;Hitchclaw Recluse;Walking Corpse
+ailibrary=Impale;Ravenous Chupacabra;Swamp;Titanic Growth;Forest;Vampire Sovereign;Greenwood Sentinel
+aigraveyard=
+aibattlefield=
+aiexile=
+aicommand=
\ No newline at end of file
diff --git a/forge-gui/src/main/java/forge/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/properties/ForgeConstants.java
index 12e9b309a5d..8cd08f0052c 100644
--- a/forge-gui/src/main/java/forge/properties/ForgeConstants.java
+++ b/forge-gui/src/main/java/forge/properties/ForgeConstants.java
@@ -69,6 +69,7 @@ public final class ForgeConstants {
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;
+ public static final String TUTORIAL_DIR = RES_DIR + "tutorial" + PATH_SEPARATOR;
public static final String DECK_GEN_DIR = RES_DIR + "deckgendecks" + PATH_SEPARATOR;
diff --git a/forge-gui/src/main/java/forge/puzzle/PuzzleIO.java b/forge-gui/src/main/java/forge/puzzle/PuzzleIO.java
index ddec7e8f2a2..2f230c0c808 100644
--- a/forge-gui/src/main/java/forge/puzzle/PuzzleIO.java
+++ b/forge-gui/src/main/java/forge/puzzle/PuzzleIO.java
@@ -16,10 +16,10 @@ public class PuzzleIO {
public static final String SUFFIX_DATA = ".pzl";
public static final String SUFFIX_COMPLETE = ".complete";
- public static ArrayList loadPuzzles() {
+ public static ArrayList loadPuzzles(String directory) {
String[] pList;
// get list of puzzles
- final File pFolder = new File(ForgeConstants.PUZZLE_DIR);
+ final File pFolder = new File(directory);
if (!pFolder.exists()) {
throw new RuntimeException("Puzzles : folder not found -- folder is " + pFolder.getAbsolutePath());
}
@@ -33,7 +33,7 @@ public class PuzzleIO {
ArrayList puzzles = Lists.newArrayList();
for (final String element : pList) {
if (element.endsWith(SUFFIX_DATA)) {
- final List pfData = FileUtil.readFile(ForgeConstants.PUZZLE_DIR + element);
+ final List pfData = FileUtil.readFile(directory + element);
String filename = element.replace(SUFFIX_DATA, "");
boolean completed = FileUtil.doesFileExist(ForgeConstants.USER_PUZZLE_DIR + element.replace(SUFFIX_DATA, SUFFIX_COMPLETE));
@@ -48,9 +48,4 @@ public class PuzzleIO {
public static Map> parsePuzzleSections(List pfData) {
return FileSection.parseSections(pfData);
}
-
-
- public static File getPuzzleFile(final String name) {
- return new File(ForgeConstants.PUZZLE_DIR, name + SUFFIX_DATA);
- }
}