4 Commits

15 changed files with 127 additions and 61 deletions

View File

@@ -0,0 +1,57 @@
Hank is a scripting language for making Interactive Fiction, such as Choose-Your-Own Adventure games.
A Hank script is really meant to be played on a computer, where the player can make choices and will only see the outcome of their own path.
If you want to give someone feedback on their Hank games, but don't have access to a computer that runs Hank, you may have to read their scripts directly.
This short guide will teach you how to read such Hank scripts in printed form.
The first thing to know is that text by itself on a line will be printed to the player as-is.
That applies to the entire first 5 lines above, and this sentence. // Anything that appears to the right of two slash marks is a comment, which the player will never see.
/*
Comments spanning multiple lines can appear wrapped by slashes and stars, like so.
Comments are usually notes to the author, or explanations of the logic in a script, to make it easier for you to read.
*/
// Scripts can also include TODO comments, which indicate something the author hasn't written or fully fleshed out yet.
// When proofreading a Hank script, think of TODO comments like an outline of a rough draft--don't judge them too harshly, but do try to imagine what the game would be like if the scene mentioned in the comment were finished.
// The next line is a DIVERT statement, indicated by an arrow. It jumps the game's story flow from one point to another. In this case, it diverts to a section called section1, which is defined immediately after the divert statement.
-> section1
== section1
If you encounter a DIVERT statement, you should stop reading the script and look for the section it refers to. Then, continue reading at that point.
Here's an example DIVERT that leads you to a label at the end of the current script. Labels aren't written the way sections are, they're written with a dash followed by a name in parentheses. See if you can follow this divert:
-> label_example
- (second_label_example) If you jumped back here from the end of the tutorial, then you know all you need to know about diverts.
Choice points are the next feature you need to understand. A series of choices can be defined with separate lines preceded by a star symbol.
->choice_example
== choice_example
* Choice 1
You picked the first choice out of 3 that appeared simultaneously. Well done!
The lines following a choice declaration are ALL printed if the player picks that choice.
Any divert statements are also followed. ->choice_example
// That divert statement brings you back to the same choice! But each choice can only be picked once.
* Choice 2
This is part of the same choice point as choice 1, even though the lines aren't immediately next to each other.
This particular line of text, and the one above it, only appear when you choose "Choice 2."
After this text shows, the game will find the GATHER that comes after Choice 3, and execute from there.
+ Choice 3
You know the drill. This is the last of 3 choices.
One thing makes it different, though: The + in front (instead of a *) makes it a permanent choice.
It won't disappear even if the player keeps clicking on it and looping back to the same spot forever!
- The dash at the start of this line signals a GATHER statement.
That means that any choice from the list above which doesn't execute its own divert statement (i.e. choices 2 and 3) will continue from the first GATHER statement that follows them, once they run out of their own lines.
-> final_advice
== final_advice
That completes your quick primer on reading Hank scripts.
One word of advice to Hank proofreaders: don't try to "debug" the code or other syntax of the script as you read it.
While there MAY be technical mistakes in a rough draft Hank script, the author is better off using digital tools to find and fix them.
Your job is mainly to examine the FLOW, STRUCTURE, and STYLE of the game. You might ask yourself:
* Is the prose clear and engaging?
* Are the events in the story well-paced?
* Does the dialogue sound natural and fun?
* Are there enough choices, and do the choices feel meaningful?
- This guide doesn't cover everything about Hank. You'll probably see parts of scripts that you don't understand. If all else fails, fall back on just critiquing the sentences of the story individually based on what you can make out! :)
Bonus points if you noticed that the above was a choice point with a gather! Good luck, and thanks for reading.
THE END
- (label_example) Nice work. If you want to be hardcore about getting a "proper" experience of the game, you should avoid peeking at any sections or labels unless a DIVERT statement tells you to. -> second_label_example

View File

@@ -2,7 +2,6 @@ IN DEBUG MODE!
* Beginning...
* Framing Hooper...
* In with Hooper...
>>> 2
> 3:
Harris opens the door and pushes me inside. "Captain," he calls. "Could I have a moment?"
The Captain, looking puzzled, steps out. The door is closed. Hooper stares at me, open—mouthed, about to say something. I probably have less than a minute before the Captain storms back in and declares this plan to be bunkum.

View File

@@ -1,30 +1,26 @@
They are keeping me waiting.
* Hut 14
>>> 0
Hut 14. The door was locked after I sat down.
> 1: Hut 14. The door was locked after I sat down.
I don't even have a pen to do any work. There's a copy of the morning's intercept in my pocket, but staring at the jumbled letters will only drive me mad.
I am not a machine, whatever they say about me.
* Think
* Plan
* Wait
>>> 0
> 1:
They suspect me to be a traitor. They think I stole the component from the calculating machine. They will be searching my bunk and cases.
When they don't find it, they'll come back and demand I talk.
I rattle my fingers on the field table.
* Plan
* Wait
>>> 0
> 1:
What I am is a problem—solver. Good with figures, quick with crosswords, excellent at chess.
But in this scenario — in this trap — what is the winning play?
# forceful: 0
* Co—operate
* Dissemble
* Divert
>>> 0
> 1:
I must co—operate. My credibility is my main asset. To contradict myself, or another source, would be fatal.
I must simply hope they do not ask the questions I do not want to answer.
Half an hour goes by before Commander Harris returns. He closes the door behind him quickly, as though afraid a loose word might slip inside.

View File

@@ -4,20 +4,17 @@ B: 0
C: 0
* Choice A
* Choice B
>>> 0
Choice A
> 1: Choice A
Loop #: 2
A: 1
B: 0
C: 0
* Choice B
>>> 0
Choice B
> 1: Choice B
Loop #: 3
A: 1
B: 1
C: 0
* Choice C
>>> 0
Choice C
> 1: Choice C
It's over!

View File

@@ -4,20 +4,17 @@ B: 0
C: 0
* Choice A
* Choice B
>>> 1
Choice B
> 2: Choice B
Loop #: 2
A: 0
B: 1
C: 0
* Choice A
>>> 0
Choice A
> 1: Choice A
Loop #: 3
A: 1
B: 1
C: 0
* Choice C
>>> 0
Choice C
> 1: Choice C
It's over!

View File

@@ -4,27 +4,22 @@ Your Hank scripts will contain the static content of your game, but they can als
You can include choices for the player.
* Door A looks promising!
* Door B
>>> 0
Door A opens and there's nothing behind it.
> 1: Door A opens and there's nothing behind it.
You can include choices for the player.
* Door B
* Choices can depend on logical conditions being truthy.
>>> 0
Door B opens but the room on the other side is identical!
> 1: Door B opens but the room on the other side is identical!
You can include choices for the player.
* Door B
* Choices can depend on logical conditions being truthy.
>>> 1
Choices can depend on logical conditions being truthy.
> 2: Choices can depend on logical conditions being truthy.
* I don't think I'll use Hank for my games.
* Hank sounds awesome, thanks!
>>> 0
I don't think I'll use Hank for my games.
> 1: I don't think I'll use Hank for my games.
Are you sure?
* Yes I'm sure.
* I've changed my mind.
>>> 0
Yes I'm sure.
> 1: Yes I'm sure.
That's perfectly valid!
That's the end of this example!
These should all say 'mouse':

View File

@@ -4,22 +4,18 @@ Your Hank scripts will contain the static content of your game, but they can als
You can include choices for the player.
* Door A looks promising!
* Door B
>>> 0
Door A opens and there's nothing behind it.
> 1: Door A opens and there's nothing behind it.
You can include choices for the player.
* Door B
* Choices can depend on logical conditions being truthy.
>>> 0
Door B opens but the room on the other side is identical!
> 1: Door B opens but the room on the other side is identical!
You can include choices for the player.
* Door B
* Choices can depend on logical conditions being truthy.
>>> 1
Choices can depend on logical conditions being truthy.
> 2: Choices can depend on logical conditions being truthy.
* I don't think I'll use Hank for my games.
* Hank sounds awesome, thanks!
>>> 1
Hank sounds awesome, thanks!
> 2: Hank sounds awesome, thanks!
That's the end of this example!
These should all say 'mouse':
mouse

View File

@@ -3,27 +3,23 @@ Loop #: 1
* A series of choices
* In Hank script embedded in Haxe script embedded in Hank script!
* What could be more fun?
>>> 2
What could be more fun?
> 3: What could be more fun?
A brief moment of sanity.
This test does nutty stuff with the Hank/Haxe embedding paradigm.
Loop #: 2
* A series of choices
* In Hank script embedded in Haxe script embedded in Hank script!
* Gosh this sucks
>>> 2
Gosh this sucks
> 3: Gosh this sucks
A brief moment of sanity.
This test does nutty stuff with the Hank/Haxe embedding paradigm.
One more thing. We need all of the lines
* after a choice
>>> 0
after a choice to be executed
> 1: after a choice to be executed
BEFORE the gather is processed.
What up
One more thing. We need all of the lines
* after a choice
>>> 0
after a choice to be executed
> 1: after a choice to be executed
BEFORE the gather is processed.
Now it's really over!

View File

@@ -0,0 +1,4 @@
- (start)
// Taken from WritingWithInk.md
The Ratbear {&{wastes no time and |}swipes|scratches} {&at you|into your {&leg|arm|cheek}}.
-> start

View File

@@ -0,0 +1,12 @@
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your leg.
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your arm.
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your cheek.
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your leg.
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your arm.
The Ratbear wastes no time and swipes at you.
The Ratbear scratches into your cheek.

View File

@@ -883,9 +883,8 @@ class Story {
stepLine();
}
// Log the choice's index to the transcript
logToTranscript('>>> ${index}');
logToTranscript(choiceTaken.text);
// Log the choice's index to the transcript (1-indexed for human readability)
logToTranscript('> ${index+1}: ${choiceTaken.text}');
return choiceTaken.text;
}

View File

@@ -67,16 +67,17 @@ class StoryTestCase extends utest.Test {
assertComplexEquals(HasChoices(choices), frame, story.lastLineID);
continue;
} else if (StringTools.startsWith(line, ">>>")) {
// Make the choice given.
var output = story.choose(Std.parseInt(StringTools.trim(line.substr(3))));
} else if (StringTools.startsWith(line, ">")) {
// Make the choice given, and check for expected output.
line = StringTools.ltrim(line.substr(1));
var firstColonIdx = line.indexOf(':');
var index = Std.parseInt(line.substr(0, firstColonIdx))-1;
var expectedOutput = StringTools.trim(line.substr(firstColonIdx+1));
var output = story.choose(index);
if (debugPrints) {
trace(output);
}
if (fullTranscript || transcriptLines.length > i+1) {
// Assert that its output equals the transcript's expected choice output.
Assert.equals(transcriptLines[++i], output);
}
Assert.equals(expectedOutput, output);
} else if (StringTools.startsWith(line, "#")) {
// Allow comments in a transcript that need not be validated in any way
if (debugPrints) {

View File

@@ -65,10 +65,17 @@ class Util {
return false;
}
/**
Strip whichever of the given prefix list comes first at the start of a string
**/
public static function stripPrefixes(str: String, prefixes: Array<String>) {
for (prefix in prefixes) {
str = StringTools.replace(str, prefix, '');
if (str.indexOf(prefix) == 0) {
str = StringTools.replace(str, prefix, '');
return StringTools.trim(str);
}
}
return StringTools.trim(str);
return str;
}
}

View File

@@ -3,6 +3,6 @@ import utest.Test;
class TestMain extends Test {
public static function main() {
utest.UTest.run([new AltTest(), new StoryTest()]);
utest.UTest.run([new AltTest(), new StoryTest(), new UtilTest()]);
}
}

10
tests/UtilTest.hx Normal file
View File

@@ -0,0 +1,10 @@
package tests;
import utest.Test;
class UtilTest extends Test {
var exampleStr1 = "here is a string {with enclosures {all nested} {up in it}}";
public function testNestedEnclosure1() {
}
}