com.bladecoder.ink.runtime.Story - java examples

Here are the examples of the java api com.bladecoder.ink.runtime.Story taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

136 Examples 7

19 View Complete Implementation : StoryUtils.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * Returns the specified variable as a Number.
 *
 * @param story   The story to get the variable from.
 * @param varName The name of the variable (case-sensitive).
 * @return The Number or null if the variable does not exist or is not a number.
 */
public static Number getNumber(Story story, String varName) {
    // sometimes numbers are strings
    try {
        return (Number) story.getVariablesState().get(varName);
    } catch (java.lang.ClreplacedCastException e) {
        String numStr = (String) story.getVariablesState().get(varName);
        if (StringUtils.isEmpty(numStr)) {
            return null;
        }
        // try parse as long first
        try {
            return Long.parseLong(numStr);
        } catch (NumberFormatException nfe1) {
            // try parse as decimal
            try {
                return Double.parseDouble(numStr);
            } catch (NumberFormatException nfe2) {
                return null;
            }
        }
    }
}

19 View Complete Implementation : StoryUtils.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * Returns the specified variable as a String.
 *
 * @param story   The story to get the variable from.
 * @param varName The name of the variable (case-sensitive).
 * @return The String or null if the variable does not exist or it is not a
 *         string.
 */
public static String getString(Story story, String varName) {
    try {
        return (String) story.getVariablesState().get(varName);
    } catch (java.lang.ClreplacedCastException cce) {
        return null;
    }
}

19 View Complete Implementation : StoryUtils.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * This method returns the boolean value of the specified story variable. Note
 * Ink stores booleans values as integers, 1 is true. This method returns false
 * if variable does not exist.
 *
 * @param story   The story to extract the variable from.
 * @param varName The name of variable. Case sensitive.
 * @return The value of variable, or false if the variable does not exist.
 */
public static boolean getBoolean(Story story, String varName) {
    Integer boolVal = (Integer) story.getVariablesState().get(varName);
    if (boolVal == null) {
        return false;
    }
    try {
        return boolVal.equals(1);
    } catch (IllegalArgumentException e) {
        return false;
    }
}

19 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * This method can be overridden to manipulate the Story object used by the bot
 * just after the story state has been loaded from the session. This method is
 * useful for setting story variables based on external data.
 *
 * @param story The story whose state has just been loaded.
 */
protected void afterStoryStateLoaded(Story story) {
// do nothing
}

19 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * This method can be overridden to manipulate the Story object used by the bot
 * just after it is created. Note the bot may create the story multiple times.
 * This method is useful for registering external functions with the Ink
 * runtime.
 *
 * @param story The just created story.
 */
protected void afterStoryCreated(Story story) {
// do nothing
}

19 View Complete Implementation : TestUtils.java
Copyright MIT License
Author : bladecoder
public static final boolean isEnded(Story story) {
    return !story.canContinue() && story.getCurrentChoices().size() == 0;
}

19 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * This method can be overridden to manipulate the results of an intent match.
 * It allows the match to be manipulated before the clreplaced uses it to progress
 * the ink story.
 *
 * @param intentMatch The intent match.
 * @param session     The current user's session.
 * @param story       The current story.
 */
protected void afterIntentMatch(IntentMatch intentMatch, Session session, Story story) {
// do nothing
}

18 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test variable observers.
 */
@Test
public void variableObservers() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/variable-observers.ink.json");
    Story story = new Story(json);
    story.observeVariable("x", new VariableObserver() {

        @Override
        public void call(String variableName, Object newValue) {
            if (!"x".equals(variableName))
                replacedert.fail();
            try {
                if ((int) newValue != variableObserversExceptedValue)
                    replacedert.fail();
                variableObserversExceptedValue = 10;
            } catch (Exception e) {
                replacedert.fail();
            }
        }
    });
    TestUtils.nextAll(story, text);
    story.chooseChoiceIndex(0);
    TestUtils.nextAll(story, text);
}

18 View Complete Implementation : TestUtils.java
Copyright MIT License
Author : bladecoder
public static final void nextAll(Story story, List<String> text) throws StoryException, Exception {
    while (story.canContinue()) {
        String line = story.Continue();
        System.out.print(line);
        if (!line.trim().isEmpty())
            text.add(line.trim());
    }
    if (story.hasError()) {
        fail(TestUtils.joinText(story.getCurrentErrors()));
    }
}

18 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
private void setSlotValuesInInk(Collection<SlotMatch> slotMatches, Story story) throws Exception {
    for (SlotMatch slotMatch : slotMatches) {
        if (slotMatch.getValue() instanceof Number) {
            story.getVariablesState().set(slotMatch.getSlot().getName().toLowerCase(), slotMatch.getValue());
        } else {
            story.getVariablesState().set(slotMatch.getSlot().getName().toLowerCase(), slotMatch.getValue().toString());
        }
    }
}

18 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * Processes a story line triggering any InkFunctions that are found.
 *
 * @param line            The story line to process.
 * @param response        The response to populate.
 * @param currentResponse The current response.
 * @param session         The current session.
 * @param intentMatch     The current intent match.
 * @param story           The current story.
 */
private void processStoryLine(String line, StringBuffer response, CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story) {
    log.debug("Line {}", line);
    String trimmedLine = line.trim();
    if (trimmedLine.startsWith("::")) {
        String functionName = trimmedLine.split(" ")[0].substring(2).trim();
        String param = trimmedLine.substring(functionName.length() + 2).trim();
        InkBotFunction function = inkBotFunctions.get(functionName.toLowerCase());
        if (function != null) {
            function.execute(currentResponse, session, intentMatch, story, param);
        } else {
            log.warn("Did not find function named {}", functionName);
        }
    } else {
        response.append(line);
    }
}

18 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
private void getResponseText(Session session, CurrentResponse currentResponse, Story story, IntentMatch intentMatch, String preText) throws StoryException, Exception {
    // reset reprompt, hint and quick replies
    currentResponse.setReprompt(null);
    currentResponse.setHint(null);
    currentResponse.setResponseQuickReplies(null);
    // get the story output and build the reponse
    StringBuffer response = processStory(session, currentResponse, story, intentMatch);
    // add any pretext if we have it
    // remove any trailing \n and trim to ensure we actually have some
    preText = StringUtils.chomp(preText).trim();
    // content
    if (StringUtils.isNotBlank(preText)) {
        response.insert(0, "\n");
        response.insert(0, preText);
    }
    currentResponse.setResponseText(response.toString());
}

18 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
// @Test
public void testListRange() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/list-range.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("Pound, Pizza, Euro, Pasta, Dollar, Curry, Paella\nEuro, Pasta, Dollar, Curry\nTwo, Three, Four, Five, Six\nPizza, Pasta\n", story.continueMaximally());
}

18 View Complete Implementation : StoryUtils.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * Returns the specified variable as a LocalDate. Note LocalDates should be
 * stored as an ink string in the format yyyy-MM-dd.
 *
 * @param story   The story to get the variable from.
 * @param varName The name of the variable (case-sensitive).
 * @return The LocalDate or null if the variable does not exist or is not a
 *         LocalDate.
 */
public static LocalDate getLocalDate(Story story, String varName) {
    String dateStr = (String) story.getVariablesState().get(varName);
    if (StringUtils.isEmpty(dateStr)) {
        return null;
    }
    try {
        return LocalDate.parse(dateStr);
    } catch (DateTimeParseException e) {
        return null;
    }
}

17 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- testListBasicOperations"
 */
@Test
public void testListBasicOperations() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/basic-operations.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("b, d\na, b, c, e\nb, c\n0\n1\n1\n", story.continueMaximally());
}

17 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestMoreListOperations"
 */
@Test
public void testMoreListOperations() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/more-list-operations.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("1\nl\nn\nl, m\nn\n", story.continueMaximally());
}

17 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestListMixedItems"
 */
@Test
public void testListMixedItems() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/list-mixed-items.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("a, y, c\n", story.continueMaximally());
}

17 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestEmptyListOrigin"
 */
@Test
public void testEmptyListOrigin() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/empty-list-origin.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("a, b\n", story.continueMaximally());
}

17 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test the Profiler.
 */
@Test
public void profiler() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/jump-knot.ink.json");
    Story story = new Story(json);
    Profiler profiler = story.startProfiling();
    story.choosePathString("two");
    TestUtils.nextAll(story, text);
    story.choosePathString("three");
    TestUtils.nextAll(story, text);
    story.choosePathString("one");
    TestUtils.nextAll(story, text);
    story.choosePathString("two");
    TestUtils.nextAll(story, text);
    String reportStr = profiler.report();
    story.endProfiling();
    System.out.println("PROFILER REPORT: " + reportStr);
}

17 View Complete Implementation : TunnelSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- Test for tunnel onwards divert override"
 */
@Test
public void testTunnelOnwardsDivertOverride() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/tunnels/tunnel-onwards-divert-override.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("This is A\nNow in B.\n", story.continueMaximally());
}

17 View Complete Implementation : StoryManager.java
Copyright Apache License 2.0
Author : bladecoder
public clreplaced StoryManager implements Serializable {

    private static final String TAG = "StoryManager";

    public static final String GAMESTATE_EXT = ".gamestate.v1";

    private static final String GAMESTATE_FILENAME = "default" + GAMESTATE_EXT;

    public final static char NAME_VALUE_TAG_SEPARATOR = ':';

    public final static char NAME_VALUE_PARAM_SEPARATOR = '=';

    private final static String PARAM_SEPARATOR = ",";

    public final static char COMMAND_MARK = '>';

    private static ResourceBundle i18n;

    private Story story = null;

    private ExternalFunctions externalFunctions;

    private String storyName;

    private StoryListener l;

    // save all spit texts
    private List<Line> record = new ArrayList<Line>();

    public StoryManager() {
        externalFunctions = new ExternalFunctions();
    }

    public void newStory(InputStream is) throws Exception {
        String json = getJsonString(is);
        story = new Story(json);
        externalFunctions.bindExternalFunctions(this);
    }

    public void newStory(String storyName) throws Exception {
        FileHandle replacedet = EnginereplacedetManager.getInstance().getreplacedet(storyName);
        try {
            long initTime = System.currentTimeMillis();
            newStory(replacedet.read());
            Gdx.app.debug(TAG, "INK STORY LOADING TIME (ms): " + (System.currentTimeMillis() - initTime));
            this.storyName = storyName;
            loadI18NBundle();
            record.clear();
            if (l != null)
                l.newGame();
        } catch (Exception e) {
            Gdx.app.error(TAG, "Cannot load Ink Story: " + storyName + " " + e.getMessage());
        }
    }

    private void loadI18NBundle() {
        if (storyName != null && EnginereplacedetManager.getInstance().getreplacedet(storyName + "-ink.properties").exists())
            i18n = I18N.getBundle(storyName + "-ink", true);
    }

    public List<Line> getRecod() {
        return record;
    }

    public String translateLine(String line) {
        if (line.charAt(0) == I18N.PREFIX) {
            String key = line.substring(1);
            // In ink, several keys can be included in the same line.
            String[] keys = key.split("@");
            String translated = "";
            for (String k : keys) {
                try {
                    translated += i18n.getString(k);
                } catch (Exception e) {
                    Gdx.app.error(TAG, "MISSING TRANSLATION KEY: " + key);
                    return key;
                }
            }
            return translated;
        }
        return line;
    }

    public String getVariable(String name) {
        return story.getVariablesState().get(name).toString();
    }

    public boolean compareVariable(String name, String value) {
        if (story.getVariablesState().get(name) instanceof InkList) {
            return ((InkList) story.getVariablesState().get(name)).ContainsItemNamed(value);
        } else {
            return story.getVariablesState().get(name).toString().equals(value);
        }
    }

    public void setVariable(String name, String value) throws Exception {
        if (story.getVariablesState().get(name) instanceof InkList) {
            InkList rawList = (InkList) story.getVariablesState().get(name);
            if (rawList.getOrigins() == null) {
                List<String> names = rawList.getOriginNames();
                if (names != null) {
                    ArrayList<ListDefinition> origins = new ArrayList<ListDefinition>();
                    for (String n : names) {
                        ListDefinition def = story.getListDefinitions().getDefinition(n);
                        if (!origins.contains(def))
                            origins.add(def);
                    }
                    rawList.setOrigins(origins);
                }
            }
            rawList.addItem(value);
        } else
            story.getVariablesState().set(name, value);
    }

    public void next() {
        String line = null;
        HashMap<String, String> currentLineParams = new HashMap<String, String>(0);
        if (story.canContinue()) {
            try {
                line = story.Continue();
                currentLineParams.clear();
                // Remove trailing '\n'
                if (!line.isEmpty())
                    line = line.substring(0, line.length() - 1);
                if (!line.isEmpty()) {
                    Gdx.app.debug(TAG, "INK LINE: " + line);
                    processParams(story.getCurrentTags(), currentLineParams);
                    // PROCESS COMMANDS
                    if (line.charAt(0) == COMMAND_MARK) {
                        processCommand(currentLineParams, line);
                    } else {
                        processTextLine(currentLineParams, line);
                    }
                } else {
                    Gdx.app.debug(TAG, "INK EMPTY LINE!");
                }
            } catch (Exception e) {
                Gdx.app.error(TAG, e.getMessage(), e);
            }
            if (story.getCurrentErrors() != null && !story.getCurrentErrors().isEmpty()) {
                Gdx.app.error(TAG, story.getCurrentErrors().get(0));
            }
        } else if (hasChoices()) {
            if (l != null)
                l.choices(getChoices());
        } else {
            if (l != null)
                l.end();
        }
    }

    private void processParams(List<String> input, HashMap<String, String> output) {
        for (String t : input) {
            String key;
            String value;
            int i = t.indexOf(NAME_VALUE_TAG_SEPARATOR);
            // support ':' and '=' as param separator
            if (i == -1)
                i = t.indexOf(NAME_VALUE_PARAM_SEPARATOR);
            if (i != -1) {
                key = t.substring(0, i).trim();
                value = t.substring(i + 1, t.length()).trim();
            } else {
                key = t.trim();
                value = null;
            }
            Gdx.app.debug(TAG, "PARAM: " + key + " value: " + value);
            output.put(key, value);
        }
    }

    private void processCommand(HashMap<String, String> params, String line) {
        String commandName = null;
        String[] commandParams = null;
        int i = line.indexOf(NAME_VALUE_TAG_SEPARATOR);
        if (i == -1) {
            commandName = line.substring(1).trim();
        } else {
            commandName = line.substring(1, i).trim();
            commandParams = line.substring(i + 1).split(PARAM_SEPARATOR);
            processParams(Arrays.asList(commandParams), params);
        }
        if ("debug".equals(commandName)) {
            Gdx.app.debug(TAG, params.get("text"));
        } else {
            if (l != null)
                l.command(commandName, params);
        }
    }

    private void processTextLine(HashMap<String, String> params, String line) {
        // Get actor name from Line. Actor is separated by '>' character.
        // ej. "Johnny> Hello punks!"
        if (!params.containsKey("actor")) {
            int idx = line.indexOf(COMMAND_MARK);
            if (idx != -1) {
                params.put("actor", line.substring(0, idx).trim());
                line = line.substring(idx + 1).trim();
            }
        }
        String tline = translateLine(line);
        Line line2 = new Line(tline, params);
        record.add(line2);
        if (l != null) {
            l.line(line2);
        }
    }

    public Story getStory() {
        return story;
    }

    public void run(String path) throws Exception {
        if (story == null) {
            Gdx.app.error(TAG, "Ink Story not loaded!");
            return;
        }
        story.choosePathString(path);
    }

    public boolean hasChoices() {
        return (story != null && story.getCurrentChoices().size() > 0);
    }

    public List<String> getChoices() {
        List<Choice> options = story.getCurrentChoices();
        List<String> choices = new ArrayList<String>(options.size());
        for (Choice o : options) {
            String line = o.getText();
            int idx = line.indexOf(StoryManager.COMMAND_MARK);
            if (idx != -1) {
                line = line.substring(idx + 1).trim();
            }
            choices.add(translateLine(line));
        }
        return choices;
    }

    private String getJsonString(InputStream is) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        try {
            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            // Replace the BOM mark
            if (line != null)
                line = line.replace('\uFEFF', ' ');
            while (line != null) {
                sb.append(line);
                sb.append("\n");
                line = br.readLine();
            }
            return sb.toString();
        } finally {
            br.close();
        }
    }

    public void selectChoice(int i) {
        try {
            story.chooseChoiceIndex(i);
        } catch (Exception e) {
            Gdx.app.error(TAG, e.getMessage(), e);
        }
    }

    public void setStoryListener(StoryListener sl) {
        this.l = sl;
    }

    public StoryListener getStoryListener() {
        return l;
    }

    public boolean savedGameExists() {
        return savedGameExists(GAMESTATE_FILENAME);
    }

    public boolean savedGameExists(String filename) {
        return EnginereplacedetManager.getInstance().getUserFile(filename).exists() || FileUtils.exists(EnginereplacedetManager.getInstance().getreplacedet("tests/" + filename));
    }

    public void loadGameState() throws IOException {
        long initTime = System.currentTimeMillis();
        loadGameState(GAMESTATE_FILENAME);
        Gdx.app.debug(TAG, "GAME STATE LOADING TIME (ms): " + (System.currentTimeMillis() - initTime));
    }

    public void loadGameState(String filename) throws IOException {
        FileHandle savedFile = null;
        if (EnginereplacedetManager.getInstance().getUserFile(filename).exists())
            savedFile = EnginereplacedetManager.getInstance().getUserFile(filename);
        else
            savedFile = EnginereplacedetManager.getInstance().getreplacedet("tests/" + filename);
        loadGameState(savedFile);
        if (l != null)
            l.loadGame();
    }

    public void loadGameState(FileHandle savedFile) throws IOException {
        Gdx.app.debug(TAG, "LOADING GAME STATE");
        if (savedFile.exists()) {
            JsonValue root = new JsonReader().parse(savedFile.reader("UTF-8"));
            Json json = new Json();
            json.setIgnoreUnknownFields(true);
            read(json, root);
        } else {
            throw new IOException("LOADGAMESTATE: no saved game exists");
        }
    }

    public void saveGameState() throws IOException {
        saveGameState(GAMESTATE_FILENAME);
    }

    public void removeGameState(String filename) throws IOException {
        EnginereplacedetManager.getInstance().getUserFile(filename).delete();
    }

    public void saveGameState(String filename) throws IOException {
        Gdx.app.debug(TAG, "SAVING GAME STATE");
        Json json = new Json();
        json.setOutputType(OutputType.javascript);
        String s = null;
        if (Gdx.app.getLogLevel() == Application.LOG_DEBUG)
            s = json.prettyPrint(this);
        else
            s = json.toJson(this);
        Writer w = EnginereplacedetManager.getInstance().getUserFile(filename).writer(false, "UTF-8");
        try {
            w.write(s);
            w.flush();
        } catch (IOException e) {
            throw new IOException("ERROR SAVING GAME", e);
        } finally {
            w.close();
        }
    }

    @Override
    public void write(Json json) {
        // SAVE STORY
        json.writeValue("storyName", storyName);
        if (story != null) {
            json.writeValue("record", record, ArrayList.clreplaced, Line.clreplaced);
            try {
                json.writeValue("story", story.getState().toJson());
            } catch (Exception e) {
                Gdx.app.error(TAG, e.getMessage(), e);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void read(Json json, JsonValue jsonData) {
        // READ STORY
        String storyName = json.readValue("storyName", String.clreplaced, jsonData);
        String storyString = json.readValue("story", String.clreplaced, jsonData);
        if (storyString != null) {
            try {
                newStory(storyName);
                long initTime = System.currentTimeMillis();
                story.getState().loadJson(storyString);
                Gdx.app.debug(TAG, "INK SAVED STATE LOADING TIME (ms): " + (System.currentTimeMillis() - initTime));
            } catch (Exception e) {
                Gdx.app.error(TAG, e.getMessage(), e);
            }
            record = json.readValue("record", ArrayList.clreplaced, Line.clreplaced, jsonData);
        }
    }
}

17 View Complete Implementation : InkManager.java
Copyright Apache License 2.0
Author : bladecoder
public clreplaced InkManager implements Serializable {

    public final static int KEY_SIZE = 10;

    public final static char NAME_VALUE_TAG_SEPARATOR = ':';

    public final static char NAME_VALUE_PARAM_SEPARATOR = '=';

    private final static String PARAM_SEPARATOR = ",";

    public final static char COMMAND_MARK = '>';

    private ResourceBundle i18n;

    private Story story = null;

    private ExternalFunctions externalFunctions;

    private ActionCallback cb;

    // Depending on the reading order of Inventory, InkManager and Actor verbs,
    // the verbCallbacks may not exist. So, we search the Cb lazily when needed.
    private String sCb;

    private boolean wasInCutmode;

    private String storyName;

    private final World w;

    private InkVerbRunner inkVerbRunner = new InkVerbRunner();

    private Thread loaderThread;

    public InkManager(World w) {
        this.w = w;
        externalFunctions = new ExternalFunctions();
    }

    public void newStory(final String name) throws Exception {
        loadThreaded(name, null);
    }

    private void loadStory(String name) {
        try {
            FileHandle replacedet = EnginereplacedetManager.getInstance().getreplacedet(EnginereplacedetManager.MODEL_DIR + name + EnginereplacedetManager.INK_EXT);
            long initTime = System.currentTimeMillis();
            String json = getJsonString(replacedet.read());
            story = new Story(json);
            externalFunctions.bindExternalFunctions(w, this);
            storyName = name;
            loadI18NBundle();
            EngineLogger.debug("INK STORY LOADING TIME (ms): " + (System.currentTimeMillis() - initTime));
        } catch (Exception e) {
            EngineLogger.error("Cannot load Ink Story: " + name + " " + e.getMessage());
            story = null;
            storyName = null;
        }
    }

    private void loadStoryState(String stateString) {
        try {
            long initTime = System.currentTimeMillis();
            story.getState().loadJson(stateString);
            EngineLogger.debug("INK *SAVED STATE* LOADING TIME (ms): " + (System.currentTimeMillis() - initTime));
        } catch (Exception e) {
            EngineLogger.error("Cannot load Ink Story State for: " + storyName + " " + e.getMessage());
        }
    }

    public void loadI18NBundle() {
        if (getStoryName() != null && EnginereplacedetManager.getInstance().getModelFile(storyName + "-ink.properties").exists())
            i18n = w.getI18N().getBundle(EnginereplacedetManager.MODEL_DIR + storyName + "-ink", true);
    }

    public String translateLine(String line) {
        if (line.charAt(0) == I18N.PREFIX) {
            String key = line.substring(1);
            // In ink, several keys can be included in the same line.
            String[] keys = key.split("@");
            String translated = "";
            for (String k : keys) {
                try {
                    // some untranslated words may follow the key
                    String k2 = k.substring(0, 10);
                    translated += i18n.getString(k2);
                    if (k.length() > 10) {
                        String trailing = k.substring(10);
                        translated += trailing;
                    }
                } catch (Exception e) {
                    EngineLogger.error("MISSING TRANSLATION KEY: " + key);
                    return key;
                }
            }
            // In translated lines, spaces can be escaped with '_'.
            translated = translated.replace('_', ' ');
            return translated;
        }
        return line;
    }

    public String getVariable(String name) {
        return story.getVariablesState().get(name).toString();
    }

    public boolean compareVariable(String name, String value) {
        waitIfNotLoaded();
        if (story.getVariablesState().get(name) instanceof InkList) {
            return ((InkList) story.getVariablesState().get(name)).ContainsItemNamed(value);
        } else {
            return story.getVariablesState().get(name).toString().equals(value == null ? "" : value);
        }
    }

    public void setVariable(String name, String value) throws Exception {
        waitIfNotLoaded();
        if (story.getVariablesState().get(name) instanceof InkList) {
            InkList rawList = (InkList) story.getVariablesState().get(name);
            if (rawList.getOrigins() == null) {
                List<String> names = rawList.getOriginNames();
                if (names != null) {
                    ArrayList<ListDefinition> origins = new ArrayList<>();
                    for (String n : names) {
                        ListDefinition def = story.getListDefinitions().getListDefinition(n);
                        if (!origins.contains(def))
                            origins.add(def);
                    }
                    rawList.setOrigins(origins);
                }
            }
            rawList.addItem(value);
        } else
            story.getVariablesState().set(name, value);
    }

    private void continueMaximally() {
        waitIfNotLoaded();
        String line = null;
        // We create a new InkVerbRunner every ink loop to avoid pending cb.resume() to
        // execute in the new actions
        inkVerbRunner.cancel();
        inkVerbRunner = new InkVerbRunner();
        HashMap<String, String> currentLineParams = new HashMap<>();
        while (story.canContinue()) {
            try {
                line = story.Continue();
                currentLineParams.clear();
                // Remove trailing '\n'
                if (!line.isEmpty())
                    line = line.substring(0, line.length() - 1);
                if (!line.isEmpty()) {
                    if (EngineLogger.debugMode())
                        EngineLogger.debug("INK LINE: " + translateLine(line));
                    processParams(story.getCurrentTags(), currentLineParams);
                    // PROCESS COMMANDS
                    if (line.charAt(0) == COMMAND_MARK) {
                        processCommand(currentLineParams, line);
                    } else {
                        processTextLine(currentLineParams, line);
                    }
                } else {
                    EngineLogger.debug("INK EMPTY LINE!");
                }
            } catch (Exception e) {
                EngineLogger.error(e.getMessage(), e);
            }
            if (story.getCurrentErrors() != null && !story.getCurrentErrors().isEmpty()) {
                EngineLogger.error(story.getCurrentErrors().get(0));
            }
        }
        if (inkVerbRunner.getActions().size() > 0) {
            inkVerbRunner.run(null, null);
        } else {
            if (hasChoices()) {
                wasInCutmode = w.inCutMode();
                w.setCutMode(false);
                w.getListener().dialogOptions();
            } else if (cb != null || sCb != null) {
                if (cb == null) {
                    cb = ActionCallbackSerializer.find(w, w.getCurrentScene(), sCb);
                }
                ActionCallback tmpcb = cb;
                cb = null;
                tmpcb.resume();
            }
        }
    }

    private void processParams(List<String> input, HashMap<String, String> output) {
        for (String t : input) {
            String key;
            String value;
            int i = t.indexOf(NAME_VALUE_TAG_SEPARATOR);
            // support ':' and '=' as param separator
            if (i == -1)
                i = t.indexOf(NAME_VALUE_PARAM_SEPARATOR);
            if (i != -1) {
                key = t.substring(0, i).trim();
                value = t.substring(i + 1, t.length()).trim();
            } else {
                key = t.trim();
                value = null;
            }
            EngineLogger.debug("PARAM: " + key + " value: " + value);
            output.put(key, value);
        }
    }

    private void processCommand(HashMap<String, String> params, String line) {
        String commandName = null;
        String[] commandParams = null;
        int i = line.indexOf(NAME_VALUE_TAG_SEPARATOR);
        if (i == -1) {
            commandName = line.substring(1).trim();
        } else {
            commandName = line.substring(1, i).trim();
            commandParams = line.substring(i + 1).split(PARAM_SEPARATOR);
            processParams(Arrays.asList(commandParams), params);
        }
        if ("LeaveNow".equals(commandName)) {
            boolean init = true;
            String initVerb = null;
            if (params.get("init") != null)
                init = Boolean.parseBoolean(params.get("init"));
            if (params.get("initVerb") != null)
                initVerb = params.get("initVerb");
            w.setCurrentScene(params.get("scene"), init, initVerb);
        } else {
            // Some preliminar validation to see if it's an action
            if (commandName.length() > 0) {
                // Try to create action by default
                Action action;
                try {
                    Clreplaced<?> c = ActionFactory.getClreplacedTags().get(commandName);
                    if (c == null && commandName.indexOf('.') == -1) {
                        commandName = "com.bladecoder.engine.actions." + commandName + "Action";
                    }
                    action = ActionFactory.create(commandName, params);
                    action.init(w);
                    inkVerbRunner.getActions().add(action);
                } catch (ClreplacedNotFoundException | ReflectionException e) {
                    EngineLogger.error(e.getMessage(), e);
                }
            } else {
                EngineLogger.error("Ink command not found: " + commandName);
            }
        }
    }

    private void processTextLine(HashMap<String, String> params, String line) {
        // Get actor name from Line. Actor is separated by ':'.
        // ej. "Johnny: Hello punks!"
        if (!params.containsKey("actor")) {
            int idx = line.indexOf(COMMAND_MARK);
            if (idx != -1) {
                params.put("actor", line.substring(0, idx).trim());
                line = line.substring(idx + 1).trim();
            }
        }
        if (!params.containsKey("actor") && w.getCurrentScene().getPlayer() != null) {
            // params.put("actor", Scene.VAR_PLAYER);
            if (!params.containsKey("type")) {
                params.put("type", Type.SUBreplacedLE.toString());
            }
        } else if (params.containsKey("actor") && !params.containsKey("type")) {
            params.put("type", Type.TALK.toString());
        } else if (!params.containsKey("type")) {
            params.put("type", Type.SUBreplacedLE.toString());
        }
        params.put("text", translateLine(line));
        try {
            Action action = null;
            if (!params.containsKey("actor")) {
                action = ActionFactory.create("Text", params);
            } else {
                action = ActionFactory.create("Say", params);
            }
            action.init(w);
            inkVerbRunner.getActions().add(action);
        } catch (ClreplacedNotFoundException | ReflectionException e) {
            EngineLogger.error(e.getMessage(), e);
        }
    }

    public Story getStory() {
        return story;
    }

    public void runPath(String path, ActionCallback cb) throws Exception {
        waitIfNotLoaded();
        if (story == null) {
            EngineLogger.error("Ink Story not loaded!");
            return;
        }
        this.cb = cb;
        story.choosePathString(path);
        continueMaximally();
    }

    public boolean hasChoices() {
        waitIfNotLoaded();
        return (story != null && inkVerbRunner.getActions().size() == 0 && story.getCurrentChoices().size() > 0);
    }

    public List<String> getChoices() {
        List<Choice> options = story.getCurrentChoices();
        List<String> choices = new ArrayList<>(options.size());
        for (Choice o : options) {
            String line = o.getText();
            // the line maybe empty in default choices.
            if (line.isEmpty())
                continue;
            int idx = line.indexOf(InkManager.COMMAND_MARK);
            if (idx != -1) {
                line = line.substring(idx + 1).trim();
            }
            choices.add(translateLine(line));
        }
        return choices;
    }

    private String getJsonString(InputStream is) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        try {
            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            // Replace the BOM mark
            if (line != null)
                line = line.replace('\uFEFF', ' ');
            while (line != null) {
                sb.append(line);
                sb.append("\n");
                line = br.readLine();
            }
            return sb.toString();
        } finally {
            br.close();
        }
    }

    public void selectChoice(int i) {
        w.setCutMode(wasInCutmode);
        try {
            story.chooseChoiceIndex(i);
            continueMaximally();
        } catch (Exception e) {
            EngineLogger.error(e.getMessage(), e);
        }
    }

    public String getStoryName() {
        return storyName;
    }

    public void setStoryName(String storyName) {
        this.storyName = storyName;
    }

    private void waitIfNotLoaded() {
        if (loaderThread != null && loaderThread.isAlive()) {
            EngineLogger.debug(">>> Loader thread not finished. Waiting for it!!!");
            try {
                loaderThread.join();
            } catch (InterruptedException e) {
            }
        }
    }

    private void loadThreaded(final String name, final String state) {
        EngineLogger.debug("LOADING INK STORY: " + name + (state == null ? "" : " WITH SAVED STATE."));
        loaderThread = new Thread() {

            @Override
            public void run() {
                if (name != null)
                    loadStory(name);
                if (state != null)
                    loadStoryState(state);
            }
        };
        loaderThread.start();
    }

    public InkVerbRunner getVerbRunner() {
        return inkVerbRunner;
    }

    @Override
    public void write(Json json) {
        BladeJson bjson = (BladeJson) json;
        World w = bjson.getWorld();
        json.writeValue("storyName", storyName);
        if (bjson.getMode() == Mode.STATE) {
            json.writeValue("wasInCutmode", wasInCutmode);
            if (cb == null && sCb != null)
                cb = ActionCallbackSerializer.find(w, w.getCurrentScene(), sCb);
            if (cb != null)
                json.writeValue("cb", ActionCallbackSerializer.find(w, w.getCurrentScene(), cb));
            // SAVE ACTIONS
            json.writeArrayStart("actions");
            for (Action a : inkVerbRunner.getActions()) {
                ActionUtils.writeJson(a, json);
            }
            json.writeArrayEnd();
            json.writeValue("ip", inkVerbRunner.getIP());
            json.writeArrayStart("actionsSer");
            for (Action a : inkVerbRunner.getActions()) {
                if (a instanceof Serializable) {
                    json.writeObjectStart();
                    ((Serializable) a).write(json);
                    json.writeObjectEnd();
                }
            }
            json.writeArrayEnd();
            // SAVE STORY
            if (story != null) {
                try {
                    json.writeValue("story", story.getState().toJson());
                } catch (Exception e) {
                    EngineLogger.error(e.getMessage(), e);
                }
            }
        }
    }

    @Override
    public void read(Json json, JsonValue jsonData) {
        BladeJson bjson = (BladeJson) json;
        World w = bjson.getWorld();
        final String name = json.readValue("storyName", String.clreplaced, jsonData);
        if (bjson.getMode() == Mode.MODEL) {
            story = null;
            storyName = name;
            // Only load in new game.
            // If the SAVED_GAME_VERSION property exists we are loading a saved
            // game and we will load the story in the STATE mode.
            if (bjson.getInit()) {
                loadThreaded(name, null);
            }
        } else {
            wasInCutmode = json.readValue("wasInCutmode", Boolean.clreplaced, jsonData);
            sCb = json.readValue("cb", String.clreplaced, jsonData);
            // READ ACTIONS
            JsonValue actionsValue = jsonData.get("actions");
            inkVerbRunner = new InkVerbRunner();
            for (int i = 0; i < actionsValue.size; i++) {
                JsonValue aValue = actionsValue.get(i);
                Action a = ActionUtils.readJson(w, json, aValue);
                inkVerbRunner.getActions().add(a);
            }
            inkVerbRunner.setIP(json.readValue("ip", Integer.clreplaced, jsonData));
            actionsValue = jsonData.get("actionsSer");
            int i = 0;
            for (Action a : inkVerbRunner.getActions()) {
                if (a instanceof Serializable && i < actionsValue.size) {
                    if (actionsValue.get(i) == null)
                        break;
                    ((Serializable) a).read(json, actionsValue.get(i));
                    i++;
                }
            }
            // READ STORY
            final String storyString = json.readValue("story", String.clreplaced, jsonData);
            if (storyString != null) {
                loadThreaded(name, storyString);
            }
        }
    }

    public final clreplaced InkVerbRunner implements VerbRunner {

        private ArrayList<Action> actions;

        private int ip = -1;

        private boolean cancelled = false;

        public InkVerbRunner() {
            actions = new ArrayList<>();
        }

        @Override
        public void resume() {
            ip++;
            nextStep();
        }

        @Override
        public ArrayList<Action> getActions() {
            return actions;
        }

        @Override
        public void run(String currentTarget, ActionCallback cb) {
            ip = 0;
            nextStep();
        }

        @Override
        public int getIP() {
            return ip;
        }

        @Override
        public void setIP(int ip) {
            this.ip = ip;
        }

        @Override
        public void cancel() {
            cancelled = true;
            ip = actions.size();
        }

        @Override
        public String getCurrentTarget() {
            return null;
        }

        private void nextStep() {
            if (cancelled)
                return;
            if (ip < 0) {
                continueMaximally();
            } else {
                boolean stop = false;
                while (ip < actions.size() && !stop && !cancelled) {
                    Action a = actions.get(ip);
                    try {
                        if (a.run(this))
                            stop = true;
                        else
                            ip++;
                    } catch (Exception e) {
                        EngineLogger.error("EXCEPTION EXECUTING ACTION: " + a.getClreplaced().getSimpleName(), e);
                        ip++;
                    }
                }
                if (ip >= actions.size() && !stop)
                    continueMaximally();
            }
        }
    }
}

17 View Complete Implementation : AddQuickReplyFunction.java
Copyright Apache License 2.0
Author : rabidgremlin
/*
   * (non-Javadoc)
   * 
   * @see com.rabidgremlin.mutters.bot.ink.InkBotFunction#respondexecute(
   * CurrentResponse currentResponse, Session session, IntentMatch intentMatch,
   * Story story, String param)
   */
@Override
public void execute(CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story, String param) {
    currentResponse.addResponseQuickReply(param);
}

17 View Complete Implementation : SetLongTermAttributeFunction.java
Copyright Apache License 2.0
Author : rabidgremlin
/*
   * (non-Javadoc)
   * 
   * @see com.rabidgremlin.mutters.bot.ink.InkBotFunction#respondexecute(
   * CurrentResponse currentResponse, Session session, IntentMatch intentMatch,
   * Story story, String param)
   */
@Override
public void execute(CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story, String param) {
    FunctionDetails details = FunctionHelper.parseFunctionString(param);
    if (details.getFunctionParams() == null) {
        throw new IllegalArgumentException("Missing name and value values for SET_LONG_TERM_ATTR");
    }
    String name = details.getFunctionParams().get("name");
    if (name == null) {
        throw new IllegalArgumentException("Missing name value for SET_LONG_TERM_ATTR");
    }
    String value = details.getFunctionParams().get("value");
    if (value == null) {
        throw new IllegalArgumentException("Missing value value for SET_LONG_TERM_ATTR");
    }
    session.setLongTermAttribute(name, value);
}

17 View Complete Implementation : SetRepromptFunction.java
Copyright Apache License 2.0
Author : rabidgremlin
/*
   * (non-Javadoc)
   * 
   * @see com.rabidgremlin.mutters.bot.ink.InkBotFunction#respondexecute(
   * CurrentResponse currentResponse, Session session, IntentMatch intentMatch,
   * Story story, String param)
   */
@Override
public void execute(CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story, String param) {
    currentResponse.setReprompt(param);
}

17 View Complete Implementation : InkBot.java
Copyright Apache License 2.0
Author : rabidgremlin
/**
 * Processes the story until the next set of choices, triggering any
 * InkFunctions along the way.
 *
 * @param session         The current session.
 * @param currentResponse The current response.
 * @param story           The current story.
 * @param intentMatch     The current intent match.
 * @return String buffer containing output.
 * @throws StoryException Thrown if there is an error.
 * @throws Exception      Thrown if there is an error.
 */
private StringBuffer processStory(Session session, CurrentResponse currentResponse, Story story, IntentMatch intentMatch) throws StoryException, Exception {
    StringBuffer response = new StringBuffer();
    while (story.canContinue()) {
        String line = story.Continue();
        // log any warnings
        // in theory we should be getting a warning if state wasn't restored correctly
        // so we can handle the issue. this is currently not happening.
        // See TestSessionRestore for current hack
        if (story.hasWarning()) {
            log.warn("Ink story has warnings: {}", story.getCurrentWarnings());
        }
        // log any errors
        if (story.hasError()) {
            log.error("Ink story has errors: {}", story.getCurrentErrors());
        }
        processStoryLine(line, response, currentResponse, session, intentMatch, story);
    }
    // strip any leading \n deals with some ink inconsistencies such as in switch
    // statements
    if (response.length() > 0 && response.charAt(0) == '\n') {
        response.deleteCharAt(0);
    }
    // chop off last \n
    if (response.length() > 0 && response.charAt(response.length() - 1) == '\n') {
        response.setLength(response.length() - 1);
    }
    return response;
}

17 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestEmptyListOriginAfterreplacedignment"
 */
@Test
public void testEmptyListOriginAfterreplacedignment() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/empty-list-origin-after-replacedignment.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("a, b, c\n", story.continueMaximally());
}

16 View Complete Implementation : ThreadSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- Exception on threads to add additional choices (#5)
 */
@Test
public void testThread() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/threads/thread-bug.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("Here is some gold. Do you want it?\n", story.continueMaximally());
    replacedert.replacedertEquals(2, story.getCurrentChoices().size());
    replacedert.replacedertEquals("No", story.getCurrentChoices().get(0).getText());
    replacedert.replacedertEquals("Yes", story.getCurrentChoices().get(1).getText());
    story.chooseChoiceIndex(0);
    replacedert.replacedertEquals("No\nTry again!\n", story.continueMaximally());
    replacedert.replacedertEquals(2, story.getCurrentChoices().size());
    replacedert.replacedertEquals("No", story.getCurrentChoices().get(0).getText());
    replacedert.replacedertEquals("Yes", story.getCurrentChoices().get(1).getText());
    story.chooseChoiceIndex(1);
    replacedert.replacedertEquals("Yes\nYou win!\n", story.continueMaximally());
}

16 View Complete Implementation : RemoveLongTermAttributeFunction.java
Copyright Apache License 2.0
Author : rabidgremlin
/*
   * (non-Javadoc)
   * 
   * @see com.rabidgremlin.mutters.bot.ink.InkBotFunction#respondexecute(
   * CurrentResponse currentResponse, Session session, IntentMatch intentMatch,
   * Story story, String param)
   */
@Override
public void execute(CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story, String param) {
    FunctionDetails details = FunctionHelper.parseFunctionString(param);
    if (details.getFunctionParams() == null) {
        throw new IllegalArgumentException("Missing name and value values for REMOVE_LONG_TERM_ATTR");
    }
    String name = details.getFunctionParams().get("name");
    if (name == null) {
        throw new IllegalArgumentException("Missing name value for REMOVE_LONG_TERM_ATTR");
    }
    session.removeLongTermAttribute(name);
}

16 View Complete Implementation : SetHintFunction.java
Copyright Apache License 2.0
Author : rabidgremlin
/*
   * (non-Javadoc)
   * 
   * @see com.rabidgremlin.mutters.bot.ink.InkBotFunction#respondexecute(
   * CurrentResponse currentResponse, Session session, IntentMatch intentMatch,
   * Story story, String param)
   */
@Override
public void execute(CurrentResponse currentResponse, Session session, IntentMatch intentMatch, Story story, String param) {
    currentResponse.setHint(param);
}

16 View Complete Implementation : ListSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestListSaveLoad"
 */
@Test
public void testListSaveLoad() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/lists/list-save-load.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("a, x, c\n", story.continueMaximally());
    String savedState = story.getState().toJson();
    // Compile new version of the story
    story = new Story(json);
    // Load saved game
    story.getState().loadJson(savedState);
    story.choosePathString("elsewhere");
    // FIXME: This is the test from the C# impl. Is it correct?
    // replacedert.replacedertEquals("a, x, c, z\n", story.continueMaximally());
    replacedert.replacedertEquals("z\n", story.continueMaximally());
}

16 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function fallback.
 */
@Test
public void externalFunctionFallback() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-2-arg.ink.json");
    Story story = new Story(json);
    story.setAllowExternalFunctionFallbacks(true);
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is 7.0.", text.get(0));
}

16 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Read the visit counts from code.
 */
@Test
public void readVisitCounts() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/read-visit-counts.ink.json");
    Story story = new Story(json);
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(4, story.getState().visitCountAtPathString("two.s2"));
    replacedert.replacedertEquals(5, story.getState().visitCountAtPathString("two"));
}

16 View Complete Implementation : TestUtils.java
Copyright MIT License
Author : bladecoder
public static final List<String> runStory(String filename, List<Integer> choiceList, List<String> errors) throws Exception {
    // 1) Load story
    String json = getJsonString(filename);
    // System.out.println(json);
    Story story = new Story(json);
    List<String> text = new ArrayList<>();
    // System.out.println(story.BuildStringOfHierarchy());
    int choiceListIndex = 0;
    while (story.canContinue() || story.getCurrentChoices().size() > 0) {
        // 2) Game content, line by line
        while (story.canContinue()) {
            String line = story.Continue();
            System.out.print(line);
            text.add(line);
        }
        if (story.hasError()) {
            for (String errorMsg : story.getCurrentErrors()) {
                System.out.println(errorMsg);
                errors.add(errorMsg);
            }
        }
        // 3) Display story.currentChoices list, allow player to choose one
        if (story.getCurrentChoices().size() > 0) {
            for (Choice c : story.getCurrentChoices()) {
                System.out.println(c.getText());
                text.add(c.getText() + "\n");
            }
            if (choiceList == null || choiceListIndex >= choiceList.size())
                story.chooseChoiceIndex((int) (Math.random() * story.getCurrentChoices().size()));
            else {
                story.chooseChoiceIndex(choiceList.get(choiceListIndex));
                choiceListIndex++;
            }
        }
    }
    return text;
}

15 View Complete Implementation : ChoiceSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- not be shown if it is a fallback choice and there are non-fallback
 * choices available"
 */
@Test
public void fallbackChoice() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/choices/fallback-choice.ink.json");
    Story story = new Story(json);
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(2, story.getCurrentChoices().size());
}

15 View Complete Implementation : ChoiceSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- not be visible if their conditions evaluate to 0"
 */
@Test
public void conditionalChoice() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/choices/conditional-choice.ink.json");
    Story story = new Story(json);
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(4, story.getCurrentChoices().size());
}

15 View Complete Implementation : FunctionSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- TestEvaluatingFunctionVariableStateBug"
 */
@Test
public void evaluatingFunctionVariableStateBug() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/function/evaluating-function-variablestate-bug.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("Start\n", story.Continue());
    replacedert.replacedertEquals("In tunnel.\n", story.Continue());
    Object funcResult = story.evaluateFunction("function_to_evaluate");
    replacedert.replacedertEquals("RIGHT", (String) funcResult);
    replacedert.replacedertEquals("End\n", story.Continue());
}

15 View Complete Implementation : MiscTest.java
Copyright MIT License
Author : bladecoder
/**
 * Issue: https://github.com/bladecoder/blade-ink/issues/15
 */
@Test
public void issue15() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/misc/issue15.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("This is a test\n", story.Continue());
    while (story.canContinue()) {
        // System.out.println(story.buildStringOfHierarchy());
        String line = story.Continue();
        if (line.startsWith("SET_X:")) {
            story.getVariablesState().set("x", 100);
        } else {
            replacedert.replacedertEquals("X is set\n", line);
        }
    }
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Jump to knot from code.
 */
@Test
public void jumpKnot() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/jump-knot.ink.json");
    Story story = new Story(json);
    story.choosePathString("two");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("Two", text.get(0));
    text.clear();
    story.choosePathString("three");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("Three", text.get(0));
    text.clear();
    story.choosePathString("one");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("One", text.get(0));
    text.clear();
    story.choosePathString("two");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("Two", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function one argument call.
 */
@Test
public void externalFunctionOneArgument() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-1-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction1<Integer, Boolean>() {

        @Override
        protected Boolean call(Integer arg) {
            return arg != 1;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is 0.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Jump to sreplacedch from code.
 */
@Test
public void jumpSreplacedch() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/jump-sreplacedch.ink.json");
    Story story = new Story(json);
    story.choosePathString("two.sthree");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("Two.3", text.get(0));
    text.clear();
    story.choosePathString("one.stwo");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("One.2", text.get(0));
    text.clear();
    story.choosePathString("one.sone");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("One.1", text.get(0));
    text.clear();
    story.choosePathString("two.stwo");
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("Two.2", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function zero arguments call.
 */
@Test
public void externalFunctionZeroArguments() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-0-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction0<String>() {

        @Override
        protected String call() {
            return "Hello world";
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is Hello world.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function two arguments call. Overrides coerce methods.
 */
@Test
public void externalFunctionTwoArgumentsCoerceOverride() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-2-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction2<Integer, Integer, Integer>() {

        @Override
        protected Integer coerceArg0(Object arg) throws Exception {
            return story.tryCoerce(arg, Integer.clreplaced);
        }

        @Override
        protected Integer coerceArg1(Object arg) throws Exception {
            return story.tryCoerce(arg, Integer.clreplaced);
        }

        @Override
        protected Integer call(Integer x, Integer y) {
            return x - y;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is -1.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function one argument call. Overrides coerce method.
 */
@Test
public void externalFunctionOneArgumentCoerceOverride() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-1-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction1<Boolean, Boolean>() {

        @Override
        protected Boolean coerceArg(Object arg) throws Exception {
            return story.tryCoerce(arg, Boolean.clreplaced);
        }

        @Override
        protected Boolean call(Boolean arg) {
            return !arg;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is 0.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
@Test
public void testLoadSave() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/runtime/load-save.ink.json");
    Story story = new Story(json);
    List<String> text = new ArrayList<String>();
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("We arrived into London at 9.45pm exactly.", text.get(0));
    // String choicesText = getChoicesText(story);
    // replacedertThat(choicesText, is(
    // "0:\"There is not a moment to lose!\"\n1:\"Monsieur, let us savour this moment!\"\n2:We hurried home\n"));
    // save the game state
    String saveString = story.getState().toJson();
    // recreate game and load state
    story = new Story(json);
    story.getState().loadJson(saveString);
    story.chooseChoiceIndex(0);
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals("\"There is not a moment to lose!\" I declared.", text.get(1));
    replacedert.replacedertEquals("We hurried home to Savile Row as fast as we could.", text.get(2));
    // check that we are at the end
    replacedert.replacedertEquals(false, story.canContinue());
    replacedert.replacedertEquals(0, story.getCurrentChoices().size());
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function three arguments call.
 */
@Test
public void externalFunctionThreeArguments() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-3-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction3<Integer, Integer, Integer, Integer>() {

        @Override
        protected Integer call(Integer x, Integer y, Integer z) {
            return x + y + z;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is 6.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function three arguments call. Overrides coerce methods.
 */
@Test
public void externalFunctionThreeArgumentsCoerceOverride() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-3-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction3<Integer, Integer, Integer, Integer>() {

        @Override
        protected Integer coerceArg0(Object arg) throws Exception {
            return story.tryCoerce(arg, Integer.clreplaced);
        }

        @Override
        protected Integer coerceArg1(Object arg) throws Exception {
            return story.tryCoerce(arg, Integer.clreplaced);
        }

        @Override
        protected Integer coerceArg2(Object arg) throws Exception {
            return story.tryCoerce(arg, Integer.clreplaced);
        }

        @Override
        protected Integer call(Integer x, Integer y, Integer z) {
            return x + y + z;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is 6.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function two arguments call.
 */
@Test
public void externalFunctionTwoArguments() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-2-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction2<Integer, Float, Integer>() {

        @Override
        protected Integer call(Integer x, Float y) {
            return (int) (x - y);
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is -1.", text.get(0));
}

15 View Complete Implementation : RuntimeSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * Test external function call.
 */
@Test
public void externalFunction() throws Exception {
    List<String> text = new ArrayList<String>();
    String json = TestUtils.getJsonString("inkfiles/runtime/external-function-2-arg.ink.json");
    final Story story = new Story(json);
    story.bindExternalFunction("externalFunction", new ExternalFunction<Integer>() {

        @Override
        public Integer call(Object[] args) throws Exception {
            int x = story.tryCoerce(args[0], Integer.clreplaced);
            int y = story.tryCoerce(args[1], Integer.clreplaced);
            return x - y;
        }
    });
    TestUtils.nextAll(story, text);
    replacedert.replacedertEquals(1, text.size());
    replacedert.replacedertEquals("The value is -1.", text.get(0));
}

15 View Complete Implementation : ThreadSpecTest.java
Copyright MIT License
Author : bladecoder
/**
 * "- Exception on threads to add additional choices (#5)
 */
@Test
public void testThreadBug() throws Exception {
    String json = TestUtils.getJsonString("inkfiles/threads/thread-bug.ink.json");
    Story story = new Story(json);
    replacedert.replacedertEquals("Here is some gold. Do you want it?\n", story.continueMaximally());
    replacedert.replacedertEquals(2, story.getCurrentChoices().size());
    replacedert.replacedertEquals("No", story.getCurrentChoices().get(0).getText());
    replacedert.replacedertEquals("Yes", story.getCurrentChoices().get(1).getText());
    // Convert state to JSON then load it back in
    String stateJson = story.getState().toJson();
    story = new Story(json);
    story.getState().loadJson(stateJson);
    // + Works correctly if choiceIdx is 1 ('Yes', defined in the knot)
    // + Throws exception if choiceIdx is 0 ('No', defined in the thread)
    story.chooseChoiceIndex(0);
    replacedert.replacedertEquals("No\nTry again!\n", story.continueMaximally());
    replacedert.replacedertEquals(2, story.getCurrentChoices().size());
    replacedert.replacedertEquals("No", story.getCurrentChoices().get(0).getText());
    replacedert.replacedertEquals("Yes", story.getCurrentChoices().get(1).getText());
    story.chooseChoiceIndex(1);
    replacedert.replacedertEquals("Yes\nYou win!\n", story.continueMaximally());
}