Tiny QBN for Twine/Sugarcube

For the last year or so I’ve been working on a mostly-text game in LOVE2D with a couple of friends (a silly space adventure inspired by Oregon Trail and Seedship) with a couple friends. We’ve been using a filtered-card-deck system where events require certain true/false flags to be selectable (are we in a particular type of solar system? Are our food resources getting low? etc.). It’s extremely simple but surprisingly capable.

Since I saw the announcement about StoryNexus closing their doors to new worlds I’ve been mulling over the idea of creating some sort of tool to help fill that seemingly-under-served niche…and today I sat down and banged out some JavaScript for Twine/Sugarcube to implement a system like the one we’ve been using.

So you tag passages with requirements (“req-lowFuel”, “req-not-lowMoney”), insert matching passages (all or a random selection) wherever you want, and continue to use links as usual in Twine where that’s appropriate. There’s a helper function to generate the appropriate range flags (low/medium/high or whatever) from variables

Does that sound like something that there’s any chance people would be interested in? I’ll probably keep playing with it either way but I’ll put more effort into docs and a tutorial story if other people want to play along.

–Josh

4 Likes

Yes, please! I’m interested in seeing this.

I am also interested in seeing and experimenting with your work as it progresses.

Thank you.

Wow, two positive responses! I was expecting deafening silence. :slight_smile:

Here’s what I have so far: github.com/JoshuaGrams/tiny-qbn

I think it’s feature-complete relative to what we’re using in our Love2d game.

I wrote 700-ish words of reference documentation which I think describes everything, but it’s fairly dry. I’m working on hammering out a small demo that shows it in action. And it definitely needs a “cookbook” showing some of the more involved techniques.

Also, it all basically seems to work OK, but I’m still working through my list of corner-cases and writing tests for them, so who knows?

1 Like

I ain’t dead, I’se just procrastinatin’…

I spent three days waffling around because I don’t know how to write fiction so I kept getting bogged down on what to write, and because I find Twine so irritating to use. And then I got hopelessly sidetracked by implementing about half the puzzles from Mike Spivey’s Junior Arithmancer, but I guess that was a good test of doing more complex things with it.

But I’m getting back on track.

  • I found and fixed a bunch of bugs.
  • I moved the styling out of the Javascript and into Sugarcube widgets, which is more wordy to use but also more flexible.
  • I made things interact properly with Sugarcube’s history.
  • I think I figured out a demo story snippet that I can actually write: hopefully I can finish that this weekend.

I’m still experimenting with ways to do StoryNexus-style “show the requirements for this story” thing, and to deal with stories that should be visible even though not all their requirements are met (so you know what you’re working toward). Those things are possible already, but you have to do it yourself and it seems a bit clunky and brittle. I’m having trouble deciding whether to just document the techniques and idioms or whether I can figure out some helper macros that make it easier without restricting the possibilities too much. As with the styling, I’d like to push as much as possible into Twine code so it’s more flexible, but some things are better (or only possible) in Javascript.

1 Like

I don’t know if this helps you or not, but AXMA Story Maker’s latest version is all in JavaScript. I’m stumped on it, but I’ve always preferred ASM to Twine. Still waiting for the full English documentation - right now I’m reading the Russian doc online and auto Google-translating it which is sometimes not very helpful…

Make sure translation is on to read the site (unless you know Russian) - There is an English part of the board.

axma.info/

Huh. I had looked at AXMA a while back, but when I tried using the Google translate web-page on the manual under Firefox, it didn’t work: it just translated the base page and not the actual Javascript-presented manual pages. I’ll have to try Chrome’s built-in Google-translate…

Yeah, that works. Thanks.

Edit: I read the manual and played around with the tool for a while. It is nicer than Twine in a bunch of ways, but I have a thing about not directly donating my time and energy to support proprietary products…

OK, I think that I’ve worked out all the major kinks and am done “improving” the API. I’m really happy with this, especially for something I built in about 10 days of my spare time.

I made a three-part tutorial walking through the creation of a simple example. I should really add some more content to the last stage of the example, but I think it’s enough to get started with. I made videos of the tutorials and posted them to Youtube. They’re probably terrible, but if you prefer video to text, they exist. Though if you prefer video to text, what are you doing here? :wink:

github.com/JoshuaGrams/tiny-qbn … d-examples

If anybody takes a look at this, I’m open to suggestions of where to go next with the documentation. Or feature requests: I’ve built three half-baked test pieces (plus my experience with the Love2D game) but it’s always possible (even likely?) that there are things I haven’t anticipated. Twine puts some significant limitations on things, but if you have needs that this doesn’t meet, I’ll see if I can reasonably do anything about it.

–Josh

Well. I started trying to implement some StoryNexus inspired convenience functions, ran smack into SugarCube’s guard rails, and set it aside for a couple days to think about how to redesign things to get them to work within the constraints. Aaaand then got into a slump for several months where I didn’t accomplish much on any of my projects. Bleh.

But I’m going to NarraScope (in about a week! it’s coming up so fast!) and that has me thinking about IF, so I’m putting some time into this again. I picked up my four failed scribbled draft redesigns and put the pieces together into something that should be reasonably comfortable to use. I got the hard part (I think) into a working-but-mostly-untested state. So I can now present a menu of choices that are filtered like little cards by writing something like:

<<choices 'someChoices'>>
    <<when>>req-somewhat_uncoordinated<<offer>>blah blah blah
    <<when>>sticky-card req-very_charming<<offer>>other thing
<</choices>>

And I worked around the issue with not being able to store actual passage objects in SugarCube variables (they get destroyed when you go to the next turn), and made a macro <<fillhand $hand handsize passages>> to help maintain a persistent hand of cards that the user can choose from.

And you can wrap a whole card in a <<card>>cover<<contents>>...<</card>> to have “two-sided” cards, and extra requirements (tagged with also-...) for whether the contents should be available. Though AFAICS there’s no way for me to detect whether a card is single- or double-sided, so the author is responsible for using only one or the other, or somehow making sure they don’t get mixed. Oh well.

I still have to make some sort of conditional link macro for difficulty requirements (“A modest challenge”, “A very chancy challenge” and the like). I’m not sure how much of a pain that will be.

I also haven’t yet started to design a good interface for displaying requirements to the player. Presumably you want to be able to have images/icons for at least some of the requirements. Or maybe filter them to display some requirements in a different place than others? That’s next on my list. I think some of the techniques I’m already using will make the JavaScript/SugarCube side of things pretty straightforward. So it’s just a naming/interface-design thing.

Anyway. I’m hoping to get the rest of the code drafted before I have to break for NarraScope, but it will easily take me the rest of the month to get everything reasonably tested and documented, and to go over the StoryNexus reference doc again to make sure I’m not missing anything major. But it feels good to have figured out how to make some of this work, so I wanted to post about it. I’m pushing the code to a branch on GitHub (there should be a “branch” pulldown somewhere).

1 Like

I’ve made reasonable progress the last three days…

Friday, June 7

I spent a little time thinking about how to implement difficulty
checks (should be trivial) and displaying requirements (not so
much).

But the big thing I did was to go through the entire StoryNexus
Reference Guide and summarize it. It’s amazing how much of it is
about different ways to display or check qualities. Where do they
appear in the UI (if anywhere)? Do we display them as strings or
numbers (or icons, or progress bars)? Do the strings identify
single values or ranges of values?

Now I can see why Alexis Kennedy said,
“the fundamental characteristic of ‘qualities’ in the Failbetter
sense is that they are all created equal […] This was a
deliberate design decision […] but now I think it erases too
many potentially useful distinctions. And, in fact, the subsequent
development […] saw many, many attempts to add, or hack in,
different ways to describe the value of, and changes to,
qualities.”

But I still think that problem goes away if you have a set of
building blocks for quality-based narratives instead of a machine
which plays a particular kind of quality-based narrative and
requires new buttons and levers and knobs for every variation that
it wants to allow. I don’t think the StoryNexus restriction of
“all qualities are non-negative integers” is the real problem.
It’s more a question of whether users can extend the system and
rearrange it to suit their needs.

A naive approach to the building blocks style does have its own
difficulties. If you just hand new authors a giant set of blocks
and say, “here, go build something”, that’s overwhelming. How
many different pieces do you have to understand? How many
different possibilities do you have to discard to find a design
you like? But if it’s introduced well it can be very beginner
friendly. You need a good default structure which just works
straight out of the box, and which is relatively easy to customize.
And you need a good cookbook explaining how to do specific common
tasks.

Saturday, June 8

I went through my StoryNexus summary and made a list of things to
implement. Most of them are interface pieces that can be add-ons
and are also easy to implement individually. So I have lots of
little easy tasks now. But there are a couple of things that need
to go into the core. Filtering/sorting cards by priority, for
instance. And I probably need some support for choices succeeding
or failing based on a skill check and a random die roll. And
support for a cancel button (the “Perhaps not” buttons in
Failbetter games). But I think all of those are straightforward.

So the big piece that’s left is displaying requirements so players
know why a particular card or choice is locked. I have an idea how
that will work, but I’ll probably let that marinate for a few days
while I implement some of the little stuff.

Sunday, June 9

I didn’t get any more done yesterday because farm work went into
the evening. But this morning I wrote a first draft of Basic
Abilities (StoryNexus’s stats which get better or worse as your
attempts succeed or fail) and a <<gotoresult>> macro for use in
<<link>>s and <<button>>s. The difficulty checks are code, not
tags, so it’s separate from the other requirements, but I think it
will be OK. It’s fairly readable. If you define a skill:

<<set $cunning to new BasicAbility('broad', 25)>>

Then you can make a button that checks it:

<<button "Button Title">>\
<<skillcheck $cunning 80>>\
<<gotoresult "Result">>\
<</button>>

It will go to the passage “Result Success” if the skill check
succeeds, or “Result” if it doesn’t. If you create “Rare Result”
or “Rare Result Success” passages, it will go to those 20% of the
time. And of course the difficulty could be a variable instead of
a hard-coded number. And it works with <<link>> as well as
<<button>>. And if you leave out the skill check, it will always
go to “Result” (or occasionally “Rare Result”).

This took me much longer than I would have liked (I spent about
two hours fiddling with it), but I’m still figuring out how to
design interfaces that work with the grain of Twine and SugarCube.
I think I’m getting a handle on it, so hopefully the other “simple” pieces will be
quicker. Other types of stat/trait objects should certainly be
very easy. I have JavaScript code somewhere for Choice of Games’s
Fairmath, Inkle’s ratio-of-choices, and Chris Crawford’s Bounded
Numbers, so I might throw those in there just for fun. We’ll see
how my time goes.

I forgot how much other stuff I needed to do this week. The farm is in full swing, and on Monday I spent my spare time working on another project.

My a cappella group rehearses on Tuesday evenings, but I did do some work on how single-use choices get removed from the deck (I was a little confused because cards get removed once the user sees their contents, but choices get removed only when you click a link or button to go to the result of the choice). I refreshed my memory of how Ink and ChoiceScript do things. It’s interesting that Ink presents single-use choices first, while ChoiceScript defaults to reusable choices and you have to say *hide_reuse or *disable_reuse. Though you can also use one of those commands at the top level to change the default. Smart design.

On Wednesday I refined the choice code a bit by adding some code to track which section you’re in so you don’t have to repeat it by hand. SugarCube only gives you the current top-level passage: not any included passages or anything. I wanted the current card or choice as well.

Also on Wednesday I finally got fed up with pasting into Twine’s “Story Javascript” for testing. So I spent a while looking over Twee2 and Tweego trying to decide which one would fit my needs better. They both have about the same number of disadvantages, so I flipped a coin and went with Tweego.

And on Thursday I wrote a first draft of card priority. By default it selects higher priority cards first, but it also allows excluding lower priority cards altogether. So you can use priority as a measure of importance (show these cards first), or you can let urgent events lock out others (you can’t stop to haggle with a street vendor for a meat pie when you’re in the middle of a sword-fight).

I think that’s all the coding things on my list except the big one of displaying requirements to the player.

I haven’t pushed any of this to GitHub because I haven’t tested any of it very well. But I’ll probably put in a couple hours tomorrow morning to kill time until the conference opens…

3 Likes

Hey look, progress. Prompted by Emily Short’s recent storylets articles and @axodys’s message yesterday, I spent some ridiculous amount of time tying off loose ends and updating documentation.

It now does “split” cards, with separate cover and contents in the same Twine passage. Similarly, you can write a bunch of storylet-like “choices” in a single passage, and filter which ones get shown based on the current story state.

You can also show the player the <<requirements>> for the current card or choice, though it’s a very basic display and I’m sure I’ll need to figure out how to improve it.

Edit: Oh yeah, it also does priority now. So you can have storylets completely displace others (this scary thing is happening and you can’t do anything else until you deal with it) or just be pushed to the top of the list (if you’re showing the user a limited selection of the available storylets, lower-priority ones will never be randomly chosen over higher-priority ones).

I got all three of the old text-based tutorials up-to-date with the current code, and wrote a very minimal example of how to use the split cards. The <<choices>> macro isn’t documented yet, except for a line in the quick-reference.

I think I’ll have time this weekend to write two quick tutorials for the new features, and maybe record video versions for some of the five if I’m feeling ambitious? We’ll see how it goes. I did the old videos in only one or two takes with no editing beyond trimming the ends and maybe noise reduction, so they didn’t really take that long.

4 Likes

Urgh. And then I discover that I designed the priority feature wrong: individual storylets should be able to be “urgent” and disable everything else or merely “important” and be randomly chosen ahead of less important ones. I made it a global decision.

And my inline choices code has a major bug that limits what kind of code you can put in a choice. So I’ll have to rework that. But I don’t think it will require any user-visible changes, so that’s something.

Well. Maybe I can finish the tutorial for the features that do work and throw together the video versions today.

2 Likes

Wow! I was mostly offline all weekend and didn’t see this until this morning, but looking forward to testing out these new features today. Thanks for putting in more of your time on this project.

The card priority functionality hadn’t occurred to me, but sounds really useful. One thing I’ve been pondering is a different way card drawing frequency could work. Right now random works as an additional conditional and can only decrease the regularity with which a card is drawn (unless I’m misunderstanding how the drawing/filtering code works). I wonder how hard it would be to implement a system where a card is treated as if there are multiple copies of it in the deck with a special tag. If it is sticky then it would remain the same, but if it was non-sticky it could decrease by 1 each time it was drawn.

Yeah, there are a million ways you could do priority and frequency, some of which are more complex to implement than others. So far I’ve avoided going down that rabbit hole…

Hrm. If you want it to be able to draw multiple copies of the same card in a single selection, that would be a bit of work. But if you just want a card to be sticky for a limited number of times, that’s easy enough: try <<set $copiesUsed to {}>> in your StoryInit (be sure to restart your game to make sure that variable gets set), and put the following in your story javascript:

<<usecopy>>
Macro.add('usecopy', {
	handler: function() {
		let title = this.args[0] || QBN.current || passage()
		let p = Story.get(title)
		if(p) {
			for(let i=0; i<p.tags.length; ++i) {
				let match = /^copies-(\d+)$/.exec(p.tags[i])
				if(match) {
					let copies = parseInt(match[1])
					let used = State.variables.copiesUsed
					if(used[title] == null) used[title] = 0
					used[title] += 1
					if(copies - used[title] <= 0) {
						let T = JSON.stringify(title)
						$(this.output).wiki("<<removecard "+T+" true>>")
					}
					break
				}
			}
		}
	}
})

Then tag your multi-copy cards with sticky-card and copies-3 or whatever, and put <<usecopy>> in the contents section of each card.

That’s awesome, thanks for the thorough code example! Looking forward to testing this one out.

I finally got videos recorded and uploaded, so the YouTube playlist now has up-to-date versions of my three original walkthroughs, and a new one for the cards-with-covers example.

1 Like

Moving right along…I added a tiny example (twee, html) for inline choices: things like the products in my Localvore example which need to be filtered like cards but are only available in one place so it’s unnecessarily tedious to make a separate passage for each one. I also wrote a walkthrough explaining the example and uploaded a video version to YouTube.

Huh. These examples seem so short, but that makes almost an hour of video now…

1 Like

Today I simplified storylet priority to use only the three levels that StoryNexus offers. Cards are usually normal. If you tag them important, then they are chosen in preference to normal cards. If you tag them urgent, they block non-urgent cards from being selected at the same time.

I wrote an example (twee, html) and a walkthrough explaining it, and the video is on the playlist.

That’s all the major features working and documented. I still have another six tutorials planned, and it seems like they’re taking me between two and three hours each? I have to figure out the details of what I want to cover, sketch in an example, debug it, write 600-1200 words explaining it, then record and upload a 10-15-minute video. At this point I’m just blasting through the videos in a single take and any bobbles just help show how you debug the stuff, so that’s no big deal. But the rest is a big chunk of time.

Yeah. I’m still hoping to get through all of that by the end of the year, but we’ll see if I feel like putting that much more work into it. The code is getting to be pretty solid: the last couple changes have been easy refactorings and the <<choices>> fix didn’t affect the interface at all, it just removed a limitation. And the remaining features are minor and they all worked last I checked, so I think the interface is stable.

The remaining tutorials on my list are (in no particular order):

  • Debugging: common ways things go wrong, and how to diagnose them.
  • <<fillhand>> draws cards to refill an existing list up to a given size.
  • <<addcard>> and <<removecard>> allow you to remove cards (even sticky cards) from the deck, or add a discarded card back in.
  • I have some stat stuff that does StoryNexus’s Basic Abilities, which have a success chance based on your level relative to the challenge level, and your stats improve the more you use them.
  • If I’m doing stats, I feel like I should also throw in Choice of Games’ Fairmath, and inkle’s preferred ratio-of-positive/negative-to-total-choices, and maybe Chris Crawford’s bounded numbers?
  • Setting up to work with Twee and some cross-platform text editor? If you’re making heavy use of storylets, the connections between passages are being made at runtime, so the Twine editor can’t show you arrows. And making heavy use of tags and creating lots of passages is tedious in the Twine editor. So using Twee makes more sense here than in general. Though maybe someone else has a good getting-started-with-Twee video that I could point people to?

If anyone is reading (or watching) these and has priorities, or has other things they’d like me to cover instead, let me know. Or just let me know if these seem useful or hopelessly obtuse. I like to think I’m good at explaining things, but I don’t really have any idea if I’m going about this in a halfway competent manner.

4 Likes

Yeah… I got involved with holiday stuff and didn’t do any more work on this. Oh well. The repository got 50-ish views and only the first video got anywhere near 10 views, so it’s not like I’m letting lots of people down.


I’m having trouble figuring out any good use for a persistent hand of cards. I put that in because it seemed like a major feature of StoryNexus. But looking at the docs it seems like they may have moved away from it later on. It seems like a design problem waiting to happen. If you have a set of cards that you keep around, then you have to make sure that you can raise your stats to let you play them, and never have stat changes make them unplayable (or make sure there’s a way to discard them).

I dunno. It seems like you could do much the same thing with just qualities and requirements, and in ways that are less likely to break. But if anyone has ideas for a short scenario where you would want to hold a specific set of storylets and play them later, I’m looking for ideas.


I realized recently that you can use <<addcard>> as a convenient method for linear stories whose storylets are separated in time. One of the good things about doing storylets in a hypertext tool like Twine is that you can build plain old branching narrative structures “inside” a storylet whenever you want. But what if you want, say, an ongoing discussion (argument? running gag?) with an NPC?

The storylets might be separated by arbitrary amounts of other content, so you can’t just use links. But creating a progress variable seems clunky. You have to remember to increment it every time, and make sure that there’s a storylet that happens at every increment. And what if you want to insert another storylet in the middle of the arc? You would have to renumber everything that came after.

So I finally realized that you can just make passages that have requirements (“you are in the same location with the NPC”) but aren’t marked as cards (except for the first). They aren’t in the deck, so they will never be found. Then you can just “link” them from the previous storylet by saying <<addcard "argue with Lloyd about poultry management">> or whatever.

And that’s all you have to do. No managing an extra “story-arc-progress” number. If you want to add more storylets in the middle, you only have to fix the link in the storylet just before where you’re adding the new one(s). And…hmm. I bet I could even make the <<addcard>> macro accept Twine link syntax as well as quoted strings, and then the editor could show arrows between the parts of the sequence. How cool would that be?

1 Like