Closed #44.
This commit is contained in:
30
examples/divert-to-choice/main.hank
Normal file
30
examples/divert-to-choice/main.hank
Normal file
@@ -0,0 +1,30 @@
|
||||
~ var target=null;
|
||||
-> start
|
||||
== start
|
||||
Mind: {if (unreachable_knot.choice2) "blown" else "not blown"}
|
||||
{sequence:
|
||||
~target = ->unreachable_knot.choice1;|
|
||||
~target = ->unreachable_knot.choice2;|
|
||||
~target = ->unreachable_knot.choice3;
|
||||
}
|
||||
|
||||
-> @target
|
||||
|
||||
|
||||
== unreachable_knot
|
||||
|
||||
This text should be like a ninja. Completely silent.
|
||||
|
||||
* (choice1) Choice 1 [Ninja!] has been made.
|
||||
The content following choice 1 will be played out.
|
||||
** Including deeper choices!
|
||||
** Beep Boop
|
||||
* (choice2) Choice 2 has been made.
|
||||
* (choice3) [Ninja!]
|
||||
-> finish
|
||||
|
||||
- ->start
|
||||
|
||||
== finish
|
||||
|
||||
Is your mind blown yet?
|
||||
10
examples/divert-to-choice/test1.hlog
Normal file
10
examples/divert-to-choice/test1.hlog
Normal file
@@ -0,0 +1,10 @@
|
||||
Mind: not blown
|
||||
Choice 1 has been made.
|
||||
The content following choice 1 will be played out.
|
||||
* Including deeper choices!
|
||||
* Beep Boop
|
||||
> 1: Including deeper choices!
|
||||
Mind: not blown
|
||||
Choice 2 has been made.
|
||||
Mind: blown
|
||||
Is your mind blown yet?
|
||||
15
hank/Alt.hx
15
hank/Alt.hx
@@ -36,8 +36,9 @@ class Alt {
|
||||
|
||||
public static function parse(buffer: HankBuffer): Option<Alt> {
|
||||
var rawExpr = buffer.findNestedExpression('{', '}').unwrap().checkValue();
|
||||
// trace(rawExpr);
|
||||
var expr = rawExpr.substr(1, rawExpr.length-2);
|
||||
|
||||
var expr = rawExpr.substr(1, rawExpr.length-2).ltrim();
|
||||
// trace (expr);
|
||||
|
||||
// Sequences are the default behavior
|
||||
var behavior = Sequence;
|
||||
@@ -45,14 +46,22 @@ class Alt {
|
||||
if (expr.startsWith(prefix)) {
|
||||
expr = expr.substr(prefix.length).trim();
|
||||
behavior = behaviorMap[prefix];
|
||||
break; // <-- Finally figured that one out.
|
||||
}
|
||||
}
|
||||
// trace (behavior);
|
||||
var outputsBuffer = HankBuffer.Dummy(expr);
|
||||
var eachOutputExpr = outputsBuffer.rootSplit('|');
|
||||
|
||||
|
||||
|
||||
if (eachOutputExpr.length == 1) {
|
||||
return None; // If no pipe is present, it's not an alt
|
||||
}
|
||||
var outputs = [for(outputExpr in eachOutputExpr) Output.parse(HankBuffer.Dummy(outputExpr), true)];
|
||||
// There can't be newlines preceding the arms of alt expressions, or everything following the newline will disappear -- hence ltrim() below.
|
||||
var outputs = [for(outputExpr in eachOutputExpr) Output.parse(HankBuffer.Dummy(outputExpr.ltrim()), true)];
|
||||
|
||||
// trace(outputs);
|
||||
|
||||
buffer.take(rawExpr.length);
|
||||
return Some(new Alt(behavior, outputs));
|
||||
|
||||
@@ -42,7 +42,7 @@ class HankInterp extends Interp {
|
||||
}
|
||||
// TODO Divert target variables are just StoryNode values
|
||||
case EUnop("->", true, e):
|
||||
trace(e);
|
||||
// trace(e);
|
||||
var targetWithDots = '';
|
||||
var trailingDot = false;
|
||||
while (true) {
|
||||
@@ -50,6 +50,7 @@ class HankInterp extends Interp {
|
||||
case EIdent(lastTarget):
|
||||
targetWithDots = lastTarget + '.' + targetWithDots;
|
||||
if (trailingDot) targetWithDots = targetWithDots.substr(0,targetWithDots.length-1);
|
||||
// trace('final target is $targetWithDots');
|
||||
var node = story.resolveNodeInScope(targetWithDots);
|
||||
trace (node);
|
||||
return node;
|
||||
|
||||
@@ -11,7 +11,8 @@ import hank.Alt.AltInstance;
|
||||
enum OutputType {
|
||||
Text(t: String); // Pure text that is always displayed
|
||||
AltExpression(a: Alt);
|
||||
HExpression(h: String); // An embedded Haxe expression whose value will be inserted
|
||||
HExpression(h: String); // A Haxe expression inside braces whose value will be inserted
|
||||
HCall(h: String); // An embedded Haxe expression preceded by ~. Will not be inserted
|
||||
InlineDivert(t: String); // A divert statement on the same line as an output sequence.
|
||||
ToggleOutput(o: Output, invert: Bool); // Output that will sometimes be displayed (i.e. [bracketed] section in a choice text or the section following the bracketed section)
|
||||
}
|
||||
@@ -78,11 +79,20 @@ class Output {
|
||||
if (peekLine.length < endSegment) {
|
||||
var text = buffer.takeLine().unwrap();
|
||||
if (text.length > 0) {
|
||||
if (text.ltrim().startsWith('~'))
|
||||
parts.push(HCall(text.ltrim().substr(1)));
|
||||
else
|
||||
parts.push(Text(text));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
var text = buffer.take(endSegment);
|
||||
// If text starts with ~, it's actually a silent HCall.
|
||||
if (text.ltrim().startsWith('~'))
|
||||
parts.push(HCall(text.ltrim().substr(1)));
|
||||
|
||||
else
|
||||
|
||||
parts.push(Text(text));
|
||||
}
|
||||
} else {
|
||||
@@ -168,6 +178,8 @@ class Output {
|
||||
|
||||
diverted = false; // Clear the diverted flag
|
||||
|
||||
// trace(parts);
|
||||
|
||||
for (part in parts) {
|
||||
switch (part) {
|
||||
case Text(t):
|
||||
@@ -196,7 +208,9 @@ class Output {
|
||||
else {
|
||||
fullOutput += value;
|
||||
}
|
||||
|
||||
case HCall(h):
|
||||
hInterface.runEmbeddedHaxe(h,scope);
|
||||
// Don't append anything
|
||||
case InlineDivert(t):
|
||||
// follow the divert. If the next expression is an output, concatenate the pieces together. Otherwise, terminate formatting
|
||||
story.divertTo(t);
|
||||
|
||||
@@ -297,6 +297,8 @@ class Story {
|
||||
|
||||
|
||||
var newScopes = if (target.startsWith("@")) hInterface.getVariable(target.substr(1)) else resolveNodeInScope(target);
|
||||
// trace('$target is $newScopes');
|
||||
|
||||
var targetIdx = newScopes[0].astIndex;
|
||||
|
||||
if (targetIdx == -1) {
|
||||
@@ -361,8 +363,16 @@ class Story {
|
||||
// if the choice has a label, increment its view count
|
||||
switch (choice.label) {
|
||||
case Some(l):
|
||||
var node = resolveNodeInScope(l)[0];
|
||||
viewCounts[node] += 1;
|
||||
var node = switch (resolveNodeInScope(l)) {
|
||||
case []:
|
||||
// the choice is being diverted to, which means it's out of scope. Find it from the StoryTree's choice map another way
|
||||
storyTree.nodeForChoice(choice.id);
|
||||
|
||||
case nodePath:
|
||||
nodePath[0];
|
||||
};
|
||||
|
||||
viewCounts[node] += 1;
|
||||
case None:
|
||||
}
|
||||
weaveDepth = choice.depth + 1;
|
||||
|
||||
@@ -138,6 +138,9 @@ class StoryTestCase extends utest.Test {
|
||||
// Make the choice given, and check for expected output.
|
||||
line = line.substr(1).ltrim();
|
||||
var firstColonIdx = line.indexOf(':');
|
||||
if (firstColonIdx == -1) {
|
||||
throw 'Choice line in transcript does not specify expected output: > $line';
|
||||
}
|
||||
var index = Std.parseInt(line.substr(0, firstColonIdx))-1;
|
||||
var expectedOutput = line.substr(firstColonIdx+1).trim();
|
||||
// trace('expecting: ${expectedOutput}');
|
||||
|
||||
@@ -7,7 +7,14 @@ import hank.LogUtil.watch;
|
||||
class StoryNode {
|
||||
public var astIndex(default, null): Int;
|
||||
var children: Map<String, StoryNode> = new Map();
|
||||
|
||||
/**
|
||||
The root StoryNode maps all choice IDs to the choices' nodes, so that when diverting to choices, we can easily resolve out-of-scope targets to increment their view counts
|
||||
*/
|
||||
var choiceNodes: Map<Int, StoryNode> = new Map();
|
||||
public function nodeForChoice(id: Int) {
|
||||
return choiceNodes[id];
|
||||
}
|
||||
|
||||
public function toString() {
|
||||
return '{Node@${astIndex}: ${[for(key in children.keys()) key]}}';
|
||||
}
|
||||
@@ -68,7 +75,27 @@ class StoryNode {
|
||||
var node = new StoryNode(exprIndex);
|
||||
lastKnot.addChild(name, node);
|
||||
lastStitch = node;
|
||||
case EGather(Some(name), _, _) | EChoice({id: _, onceOnly: _, label: Some(name), condition: _, depth: _, output: _, divertTarget: _}):
|
||||
case EGather(Some(name), _, _):
|
||||
|
||||
leafNode(exprIndex, root, lastKnot, lastStitch, name);
|
||||
|
||||
case EChoice({id: choiceId, onceOnly: _, label: Some(name), condition: _, depth: _, output: _, divertTarget: _}):
|
||||
|
||||
var choiceNode = leafNode(exprIndex, root, lastKnot, lastStitch, name);
|
||||
|
||||
// keep a map of choiceId to node
|
||||
root.choiceNodes[choiceId] = choiceNode;
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
exprIndex++;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static function leafNode(exprIndex, root, lastKnot, lastStitch, name) {
|
||||
var node = new StoryNode(exprIndex);
|
||||
if (lastKnot == null) {
|
||||
// watch(root);
|
||||
@@ -80,12 +107,6 @@ class StoryNode {
|
||||
// watch(lastStitch);
|
||||
lastStitch.addChild(name, node);
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
exprIndex++;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user