I know Harlowe is not the generally first choice of Twine developers, but I was inspired by @Anvilandthechipmunks’s Big Aspirations to tinker a bit myself with it more.
Figured it could be useful to have a support thread to collect some arcane knowledge not pointed out in the docs.
For instance, my current struggles:
I’m trying to figure out if I can use an array like(a: "strawberry", "apple", "pear", "hourglass")
as a(p-either:)-type
restriction to create an easily re-usable data type, but I don’t think it is?Similarly, how to copy an array, everything seems by reference? or at least also(shuffled:)
despite docs saying takesAny
seems to only care about Strings and ignores anArray
?
Known Tips & Tricks / Resources
I’ll try and edit and summarize these as they come up below in the discussion?
Arrays
Of course, as soon as I post, I think I figure it out. Various ways to copy an array:
(set: $array to (a: 1, 2, 3, 4)) <!-- Initialize an array as an example -->
<!-- Method 1, use slicer and just pass into (a: or (shuffled: -->
(shuffled: ...$array)
(a: ...$array)
<!-- Method 2, use altered to iterate over array and copy into a new one (could be useful if you want to modify at the same time -->
(set: $array2 to (altered: _a via it, ...$array))(print: $array2)
Thanks to this post which helped demystify the spread ‘…’ operator sprinkled throughout the docs…
Datamaps
If you have a datamap that represents a bunch of different stats for instance and want to find the maximal stat:
(set: const-type $max_dm to (macro: dm-type _dm, [
(set: num-type _max_value to -1)
(set: str-type _max_key to "")
(for: each _item, ...(dm-entries: _dm))[
(if: _item's value > _max_value)[
(set: _max_value to _item's value)
(set: _max_key to _item's name)
]
]
(output-data: _max_key)
]))
Similarly, if you want to retrieve a sorted list of the stats from largest to smallest:
(set: const-type $sorted_dm to (macro: dm-type _dm, [
(set: _values to (sorted: ...(dm-values: _dm)))
(set: _values to (reversed: ..._values))
(set: _entries to (dm-entries: _dm))
(set: _names to (a:))
(for: each _v, ..._values)[
(set: _find to 1st of (find: _attr where _attr's value is _v, ..._entries))
(set: _names to it + (a: _find's name))
(set: _entries to it - (a: _find))
]
(output-data: _names)
]))
If you want smallest to largest, just remove the 2nd set to _values
with the call to reversed
.
Datatypes
This also solved my other problem, where I can setup a global array like:
(set: $body_shapes to (a: "strawberry", "apple", "pear", "hourglass"))
<!-- and use elsewhere to setup/seed a typed value -->
(set: (p-either: ...$body_shapes)-type $asset to (shuffled: ...$body_shapes)'s 1st)
You can also save the datatype directly to be even more re-usable:
(set: $body_shapes to (a: "strawberry", "apple", "pear", "hourglass"))
(set: $t_body_shape to (p-either: ...$body_shapes))
<!-- and use like: -->
(set: $t_body_shape-type $asset to "strawberry")
Saving
- @hamadana’s post Big Aspirations (0.5.3) A weight gain interactive story - #422 by hamadana
- GitHub - Kajot-dev/Twine-Harlowe-Save-To_File: Harlowe utility for saving and loading game progress to encrypted file
Scope
Temporary Scope for Passage
It can be a bit frustrating to deal with temporary scoped variables as they can only exist within a hook and its descendants, this can make it a bit hard when you’re doing formatting or blocking code and trying to have a temporary variable remain in scope for your entire passage.
You can use a Unclosed Hook at the top of your passage in order to keep a temporary variable in scope for your entire passage easily:
:: IntroPassage
(set: _body_choices to (shuffled: "strawberry", "apple", "pear", "hourglass"))[==\
Subprocedures
I found this little gem pattern. If you have a piece of logic you want to run for multiple choices in a passage:
|choices>[\
* |choice1>[First Choice]
* |choice2>[Another Choice]
]|process_both)[{
<!-- Do Common logic in this hidden hook-->
(set: $choices_made to it+1)
<!-- Remove the hidden hook completely if you want -->
(replace: ?process_both)[]
}]\
(click: ?choice1)[(replace: ?choices)[{
<!-- Execute the Subprocedure -->
(show: ?process_both)
<!-- do branch specific logic -->
(set: $mood to it+1)
}
You picked the first choice...
]]\
(click: ?choice2)[(replace: ?choices)[{
(show: ?process_both)
(set: $mood to it-2)
}
You picked the second choice...
]]\
Basically just put your code in a Hidden Hook and show it when you want to run it (the one time). I imagine you may be able to just hide it again to re-hide to re-run later, but I haven’t tried that yet…
Variables
Variable names are case-sensitive, can’t contain -
, nor start with a numeral (so you can write $1.50
in story text for instance).
If you need to translate/map one value that you may be displaying in your story, for instance with a (cycling-link: 2bind _story_value, "one", "two", "three")
, then you can use a datamap to help set the value elsewhere:
(set: $number to (_story_value of (dm:
"one", 1,
"two", 2,
"three", 3
)))
Bit of a contrived example above, but can be useful for datamaps themselves to index into different keys/‘names’ as well.