Speech functions I created for "A Campfire Tale"

- Posted in Coding by

I use these two functions to format speech in my current WIP, "A Campfire Tale":

An update on my WIP Twinework in Sugarcube

- Posted in Coding by

 work in progress image

I recently reported on my blog about my WIP, A Campfire Tale. That was two days ago. Since then, I've made significant progress. In the past two days, I've added about 7Kb of a combination of narrative and Twinescript.

The last forty-eight hours' work has been on I guess what could be called my first puzzle of the game (and, indeed, or anything I've ever coded). Basically, I'm trying to set up some inventory-manipulation to make the player feel clever while advancing the story.

So far, I've implemented examining items, and I'm working on item combination logic.

A Sugarcube Widget to Display Debug-Mode Message

- Posted in Coding by

Here's a widget I wrote on 5/8/25 to display helpful developer messages during the development phase:

Obviously, this widget relies upon a story variable, $releaseVersionOfGame, because it checks this boolean. Ideally, this boolean would be set in passage StoryInit in your Sugarcube game.

Then, whenever you want to show a message in a passage (for instance, to reassure yourself that $inventory has the contents you think it has), you do something like this:

Recipe for an Inventory system in Twine Sugarcube

- Posted in Coding by

The anatomy of an inventory system: (a) the arbitrarily long return function (b) creating the YourInventory passage with a [noreturn] tag (c) creating the StoryMenu passage so we can link to YourInventory (d) give the player some starting inventory items (optional)


(a) The Arbitrarily Long Return function

/* Trigger the following code at the start of navigation to a new passage. */
$(document).on(":passagestart", function (event) {
        /* Make sure the current passage doesn't have a "noreturn" tag. */
        if (!tags().includes("noreturn")) {
                /* If it doesn't, then set $return to the current passage name. */
                State.variables.return = passage();
        }
});

In case the above inline code doesn't display correctly, or breaks at some point, here is the Github gist of the above Arbitrarily Long Return function.


Next I provide the code for the YourInventory passage and the two widgets it makes use of.

the passage code:

<<nobr>>
<<if $inventory eq undefined>>
Inventory is undefined
<<else>>
<<InventoryItemCount>>
<<InventoryItemListing>>
<</if>>
<</nobr>>

and the code for the two widgets:

<<widget "InventoryItemCount">>
/* This widget prints a message to the browser window telling the player how many items are in her inventory. I still need to add the Twinescript needed to accomplish this -- but all I have to do is copy/paste it from the YourInventory passage. */ 
<<nobr>>Your inventory contains:
<<if $inventory.length lt 1>>
nothing
<<elseif $inventory.length eq 1>>
1 item
<<else>>
<<= $inventory.length>> items
<</if>>
<</nobr>><</widget>>

<<widget "InventoryItemListing">>
/* This widget prints the inventory's items,
one per line */
<<nobr>><<if $inventory.length eq 1>>
$inventory[0]
<</if>>
<<if $inventory.length gt 1>>
<<= $inventory.join(`<br>`)>><br>
<</if>>
<</nobr>><</widget>>

And, against future need, here is a gist of the above passage code and code for the two widgets.

One option for making the YourInventory passage available during play is to add the following line to special passage StoryMenu:

<<link "Inventory">><<goto YourInventory>><</link>>

One way to give the player some beginning inventory items would be to set them in special passage StoryInit:

<<set $inventory to ['a canteen', 'a pocket knife', 'a handkerchief', 'a flashlight', 'a revolver']>>

Change font face and size in Twine with Sugarcue

- Posted in Coding by

Thanks to Josh Grams, I now understand how to set up players/readers of my twines with the ability to change font face and font size. Below I link to the scripts you need to drop, respectively, into the javascript and stylesheet special passages in the desktop Twine 2.10.0 app. Here is the zip containing the needed JS and CSS. I host a demo of this here and here, and mention it in my Twine Tidbits on the local copy of my blog.

Implementation of a quiz in sugarcube 2.37.3

- Posted in Coding by

Here is code I developed in Twee3 format to implement a quiz in a Twine project using Twine 2.10.0 and Sugarcube 2.37.3 — utilizing three widgets I made, combined with passages that present quiz items.

Radio buttons are used for multiple-choice ABCD responses. It's a simple 10-item quiz. The $knight* variables are setup in the :: StoryInit passage of my Twee3 source.

Here is a download of the code, and below is a listing of it.

A sugarcube widget just saved me 12 Kb

- Posted in Coding by

I'm proud of a programming accomplishment I've made: I've been developing an introductory tutorial series covering Twine 2.10.0 and Sugarcube 2.37.3 — for an interested coworker.

I've been naming the tutorial passages after this convention:

TwineLesson001Passage001
TwineLesson001Passage002
etc.

Well, I had been using a block of Twinescript like the following in order to implement some cute back/forward navigation icons (shown below):

But I got to thinking to myself, "Self, there has to be a way to use a widget or function so that I'm not pasting the equivalent of a small paragraph in every single tutorial passage, just to implement this."

It took me awhile to incrementally write code, test, repeat to get the above. Just three lessons into a tutorial series (weighing in at 51 passages), my Twee3 file was 39 Kb. Using the above widget allowed me to trim that down to 27 Kb.

Linking to passages by URL

- Posted in Coding by

Put the following code into the Javascript section of your Twine project:

/* Anchor Link to Passage - Start */
if ("onhashchange" in window) {  // event supported
    window.onhashchange = function () {
            hashChanged();
    };
} else {  // event not supported
    window.setInterval(function () {
            if (window.location.hash != setup.storedHash) {
                    hashChanged();
            }
    }, 100);
}
function hashChanged() {
    if (Engine.isIdle()) {
            if (window.location.hash && (setup.storedHash != window.location.hash)) {
                    setup.storedHash = window.location.hash;
                    var anchor = decodeURI(window.location.hash.substring(1));
                    if (Story.has(anchor) && (passage() !== anchor)) {
                            Engine.play(anchor);
                    }
            } else {
                    // Comment out the following line of code if you don't want the
                    // anchor link of the current passage displayed in the URL bar.
                    window.location.hash = encodeURI(passage());
            }
            // Comment out the following line of code if you don't want the
            // title of the page set to the passage name.
            document.title = passage();
    } else {
            setTimeout(hashChanged, 100);
    }
}
$(document).on(':passageend', function () { hashChanged(); });
/* Anchor Link to Passage - End */

Next, put the following into a header passage, but feel free to restrict its use with passage tags.

Page 1 of 2