3 Commits

Author SHA1 Message Date
843e01e888 Improve debugging flow 2019-01-25 11:02:37 -05:00
712852673a Finished fallback behavior 2019-01-25 10:35:32 -05:00
e67f68a51b WIP fallback choices 2019-01-20 17:22:33 -05:00
5 changed files with 123 additions and 27 deletions

View File

@@ -0,0 +1,18 @@
-> start
== start
* Sometimes
* you try everything
* and everything
* and nothing seems to get you anywhere.
* -> end
- -> start
== end
This is what fallback choices are for!
* One other thing -> end
* ->
You can put content in a fallback choice using the "choice then arrow" syntax.
/* TODO test the situation where we want to allow multiple fallbacks, each one having a different condition */

View File

@@ -0,0 +1,24 @@
* Sometimes
* you try everything
* and everything
* and nothing seems to get you anywhere.
>>> 3
and nothing seems to get you anywhere.
* Sometimes
* you try everything
* and everything
>>> 0
Sometimes
* you try everything
* and everything
>>> 1
and everything
* you try everything
>>> 0
you try everything
This is what fallback choices are for!
* One other thing
>>> 0
One other thing
This is what fallback choices are for!
You can put content in a fallback choice using the "choice then arrow" syntax.

View File

@@ -5,10 +5,10 @@ class RunStoryDemo {
var input = Sys.stdin(); var input = Sys.stdin();
var debug = false; var debug = false;
trace("Enter a path to a hank story file. Prepend with * for debug prints. (default is examples/main.hank): "); trace("Enter a path to a hank story file. Prepend with * for debug prints. (default is examples/main/main.hank): ");
var path = input.readLine(); var path = input.readLine();
path = if (path.length == 0) { path = if (path.length == 0) {
"examples/main.hank"; "examples/main/main.hank";
} else { } else {
if (path.charAt(0) == '*') { if (path.charAt(0) == '*') {
debug = true; debug = true;
@@ -18,7 +18,7 @@ class RunStoryDemo {
} }
} }
var story: Story = new Story(false, StringTools.replace(path, '.hank', '.hog')); var story: Story = new Story(false, StringTools.replace(path, '.hank', '.hlog'));
story.loadScript(path); story.loadScript(path);
var frame = StoryFrame.Finished; var frame = StoryFrame.Finished;
do { do {

View File

@@ -721,7 +721,17 @@ class Story {
} }
var availableChoices = [for (choice in collectChoicesToDisplay()) choice.text]; var availableChoices = [for (choice in collectChoicesToDisplay()) choice.text];
return if (availableChoices.length > 0) HasChoices(availableChoices) else Error("Ran out of available choices."); if (availableChoices.length > 0) {
return HasChoices(availableChoices);
}
else {
var fallback = collectFallbackChoice();
if (fallback != null) {
return chooseFallbackChoice(fallback);
} else {
throw 'Ran out of available choices at ${currentLineMapKey()}.';
}
}
// Execute gathers by updating the choice depth and continuing from that point // Execute gathers by updating the choice depth and continuing from that point
case Gather(label, depth, nextPartType): case Gather(label, depth, nextPartType):
@@ -867,20 +877,8 @@ class Story {
case Some(target): case Some(target):
divert(target); divert(target);
case None: case None:
// Otherwise, find the line where the choice taken occurs // Otherwise, follow the choice's branch.
for (i in currentLineIdx...scriptLines.length) { gotoChoiceBranch(choiceTaken);
switch (scriptLines[i].type) {
case DeclareChoice(choice):
if (choice.id == choiceTaken.id) {
gotoLine(i);
break;
}
default:
}
}
// Move to the line following this choice.
stepLine();
} }
// Log the choice's index to the transcript // Log the choice's index to the transcript
@@ -889,6 +887,23 @@ class Story {
return choiceTaken.text; return choiceTaken.text;
} }
private function gotoChoiceBranch(choiceTaken: Choice) {
// find the line where the choice taken occurs
for (i in currentLineIdx...scriptLines.length) {
switch (scriptLines[i].type) {
case DeclareChoice(choice):
if (choice.id == choiceTaken.id) {
gotoLine(i);
break;
}
default:
}
}
// Move to the line following this choice.
stepLine();
}
/** /**
Search for the next gather that applies to the current choice depth, and follow it. Search for the next gather that applies to the current choice depth, and follow it.
@return If a suitable gather is found, Empty. Otherwise, an Error frame. @return If a suitable gather is found, Empty. Otherwise, an Error frame.
@@ -974,6 +989,35 @@ class Story {
return choices; return choices;
} }
private function collectFallbackChoice() {
var choices = collectRawChoices();
for (choice in choices) {
if (choice.text.length == 0) {
return choice;
}
}
return null;
}
/**
Return the first frame following a fallback choice, pushing the flow forward down that branch.
**/
private function chooseFallbackChoice(choice: Choice): StoryFrame {
switch (choice.divertTarget) {
case Some(target):
// "choice then arrow syntax" should simply evaluate the lines following this choice
if (target.length == 0) {
gotoChoiceBranch(choice);
return nextFrame();
} else {
return divert(target);
}
default:
throw 'Syntax error in fallback choice: no ->';
}
}
/** /**
Check if a choice's display condition is satisfied Check if a choice's display condition is satisfied
**/ **/
@@ -1044,7 +1088,11 @@ class Story {
// Check that the choice hasn't been chosen before if it is a one-time-only // Check that the choice hasn't been chosen before if it is a one-time-only
if (choicesEliminated.indexOf(choice.id) == -1) { if (choicesEliminated.indexOf(choice.id) == -1) {
// fill the choice's h expressions after removing the flag expression // fill the choice's h expressions after removing the flag expression
choices.push(choiceToDisplay(choice, chosen)); var displayChoice = choiceToDisplay(choice, chosen);
// Don't display fallback choices (choices with no text)
if (StringTools.trim(displayChoice.text.split('->')[0]).length > 0) {
choices.push(displayChoice);
}
} else { } else {
// debugTrace('can\'t display "${choice.text}" because it has expired.'); // debugTrace('can\'t display "${choice.text}" because it has expired.');
} }

View File

@@ -37,7 +37,7 @@ class StoryTestCase extends utest.Test {
try { try {
frame = story.nextFrame(); frame = story.nextFrame();
} catch (e: Dynamic) { } catch (e: Dynamic) {
trace('Error at ${story.lastLineID}'); trace('Error at ${story.lastLineID} while validating ${transcriptFile}');
throw e; throw e;
} }
@@ -69,13 +69,19 @@ class StoryTestCase extends utest.Test {
continue; continue;
} else if (StringTools.startsWith(line, ">>>")) { } else if (StringTools.startsWith(line, ">>>")) {
// Make the choice given. // Make the choice given.
var output = story.choose(Std.parseInt(StringTools.trim(line.substr(3)))); try {
if (debugPrints) { var output = story.choose(Std.parseInt(StringTools.trim(line.substr(3))));
trace(output); if (debugPrints) {
} trace(output);
if (fullTranscript || transcriptLines.length > i+1) { }
// Assert that its output equals the transcript's expected choice output. if (fullTranscript || transcriptLines.length > i+1) {
Assert.equals(transcriptLines[++i], output); // Assert that its output equals the transcript's expected choice output.
Assert.equals(transcriptLines[++i], output);
}
} catch (e: Dynamic) {
trace('Error at ${story.lastLineID} while validating ${transcriptFile}');
throw e;
} }
} else if (StringTools.startsWith(line, "#")) { } else if (StringTools.startsWith(line, "#")) {
// Allow comments in a transcript that need not be validated in any way // Allow comments in a transcript that need not be validated in any way