Random engine for people who like to use Squiffy as possible while tending to avoid Javascript

Other people have already figured this out. But sure would have made my earlier games a lot easier to write if I'd known it sooner. I hope it will help somebody who wants an easy way to handle random selections.

You just paste the [[random]] section anywhere in your game and then follow the pure-Squiffy examples to declare new random choices.

@start start

Unfortunately, this only works with one random per section.

[[start]]:
@set random = friend:Alice Liu| Bob| Cindy| Dan| Ebert| Flor| Grant| Hanna

You want some I cream. You choose a friend to [[call]](random, next=two) at random.

[[two]]:
{@random=flavor: chocolate| taro| vanilla| strawberry banana| mango}
{friend} picks up the phone.

"Sure, I could go for some ice cream. What [[kind]](random, next=three) do you want?"

[[three]]:
"I'm having {flavor} today. [[You]](random, next=You)?"

@set random = flavor1: chocolate| taro| vanilla| strawberry banana| mango

[[You]]:
@set random = flavor2: chocolate| taro| vanilla| strawberry banana| mango
@set next = You1

    squiffy.story.go("random");
    

[[You1]]:

"Uh... {flavor1}, I guess? Or{if flavor1=@flavor2:... No. Yeah. {flavor2}.}{else: maybe {flavor2}?}"

[[random]]:
    var random = squiffy.get("random");
    var attribute = random.split(":")[0];
    var choice = random.split(":")[1];
    var choice = choice.split("|")[(Math.floor(Math.random()* choice.split("|").length))];
    squiffy.set(attribute, choice);

    var next = squiffy.get("next");
    squiffy.story.go(next)

Hmm... Can't figure out why it won't work inside a [[]] master section.


Oh...

I just stumbled on this post from mrangel, which appears to do this much more effectively. I can't get it to work at the moment, but I probably pasted something wrong.


I just stumbled on this post from mrangel, which appears to do this much more effectively. I can't get it to work at the moment, but I probably pasted something wrong.

(edit: typos)

I'm not sure if I tested all of that. I included some rough code off the top of my head as an example of what it could do, but I was focusing more on the means of loading code reliably in a different context. Maybe I messed something up.

I think your version has the advantage of being a lot less code :)

Hmm... Can't figure out why it won't work inside a [[]] master section.

How are you trying to include it? Could be something in the order of execution. If I remember correctly, the order is:

  1. master section @sets
  2. master section javascript
  3. master section text
  4. current section @sets
  5. current section javascript
  6. current section text

So if you're putting the JS in the master section, it will run before any attributes are set by the current section.

Hmm…

Off the top of my head, perhaps you could have the master section look at the variables set for the current section. So something like:

[[]]:
    if (squiffy.story.section.attributes) {
        var regex = /^random:\s*(\w+)\s*=\s*(.+)/i;
        var matches, attr, options;
        squiffy.story.section.attributes.forEach (line => {
            if (matches = regex.exec(line)) {
                options = matches[2].split("|");
                squiffy.set(matches[1], options[Math.floor(Math.random() * options.length)]);
            }
        });
    }

[[some random section]]:

@set random:flavor = lime|orange|cherry|roast pork and blue cheese

“Ooh, there's an ice cream shop!” Jade shrieks excitedly. “I wonder if they have {flavor} sorbet, it's my favourite!”

If you don't mind tweaking javascript, you could edit your story.js file after compiling the game; or even change this in whichever file in the Squiffy compiler contains this, so you can use it in all your games. Find this section and add piece to one line:

    squiffy.story.run = function(section) {
        if (section.clear) {
            squiffy.ui.clearScreen();
        }
        if (section.attributes) {
            processAttributes(section.attributes.map(line => line.replace(/^random\s*:\s*(\w+)\s*=\s*(.+)/i, (line, attr, options) => (options = options.split("|")) ? attr + " = " + options[Math.floor(Math.random() * options.length)] : line)));
        }
        if (section.js) {
            section.js();
        }
    };

Then you can use stuff like @set random:colour = red|green|blue|fuchsia whenever you want.
It's shorter code, and more efficient, but a little ugly.


Off the top of my head, perhaps you could have the master section look at the variables set for the current section. So something like:
That works wonders! And it even lets me do multiple randoms in the same section and process them immediately!!!!! Your next script absolutely works to change the story.js file! Just... Thank you! So. Much. mrangel.


Hmm… thinking a little more about this.

[[]]:
    if (squiffy.story.section.attributes) {
        var regex = /^random:\s*(\w+(?:[,\s]+\w+)*)\s*=\s*(.+)/i;
        var matches, attr, options;
        squiffy.story.section.attributes.forEach (line => {
            if (matches = regex.exec(line)) {
                options = matches[2].split("|");
                matches[1].split(/[,\s]+/).forEach(attr => squiffy.set(attr,
                    options.length ? options.splice(Math.floor(Math.random() * options.length),1)[0] : attr
                ));
            }
        });
    }

[[some section]]:
@set random:john,bob,carl = peach|plum|melon|tomato|banana

John tried to think of something to say, but all he could come up with was: “My favourite fruit is {john}!”

“Eww, {john}?” Bob answered. “I only eat {bob}.”

"Don't be mean. At least I’m not Carl, and his obsession with {carl}.”

(off the top of my head, running it more than once. I changed options[randomnumber] to options.splice(randomnumber, 1)[0] so that it removes the selected option, meaning that you get 3 different values.

Or the story.js version:

    squiffy.story.run = function(section) {
        if (section.clear) {
            squiffy.ui.clearScreen();
        }
        if (section.attributes) {
            var parts, options;
            processAttributes(section.attributes.map(line => (parts = line.match(/^random\s*:\s*([\w,]+)\s*=\s*(.+)/i)) ? options = match[2].split("|") && match[1].split(",").map(attr => options.length ? attr + " = " + options.splice(Math.floor(Math.random() * options.length), 1)[0] : "Not enough options for "+attr):line));
        }
        if (section.js) {
            section.js();
        }
    };

That lets you label each pick yourself! Maybe some day I'll learn abbreviated JS so I can read it. Gotta slowly learn long-form first.

This tutorial explains that, for some reason, JS array randomization methods usually become less random after each pick, and recommends using a formula such as Fisher-Yates.


This tutorial explains that, for some reason, JS array randomization methods usually become less random after each pick, and recommends using a formula such as Fisher-Yates.

They're talking about taking an array, and changing the order of elements within the same array.

Using Array.prototype.sort doesn't work properly because it's optimised - it makes guesses about which order elements should be in based on the order of elements that are already compared, in order to minimise the number of times it has to actually compare two elements. And that is pretty wonky.

My method is just to pick a random element from the array for each attribute you give it; that will be suitably random. Fisher-Yates is the same method, but with some extra code added so that the array you feed into it and the array you put the shuffled values into can be the same array.

Hope that makes sense… some of this stuff is a lot easier to do than to explain.


I see! That's perfect then.


Hi mrangel,

I followed your instructions by finding C:\Program Files (x86)\Squiffy\resources\app\node_modules\squiffy\squiffy.template.js . There may be a bug or something. The code caused both app and browser to display a blank screen with only a blue restart button, and no alert when clicked.


Probably a mismatched bracket or something. Any errors in the javascript console?


No, the console just remains blank. The first version with only simple random works perfectly. The one with plural non-repeating attribute names is the culprit.

Both versions work beautifully in [[]]:


OK, looks like I've got some missing brackets in there. Dumb oversight.

Let's try adding some extra whitespace to make it more readable

    squiffy.story.run = function(section) {
        if (section.clear) {
            squiffy.ui.clearScreen();
        }
        if (section.attributes) {
            var parts, options;
            processAttributes(section.attributes.map(line => (
                parts = line.match(/^random\s*:\s*([\w,]+)\s*=\s*(.+)/i)) ? 
                    ((options = parts[2].split("|")) &&
                    (parts[1].split(",").map(attr => 
                        options.length ?
                            attr + " = " + options.splice(Math.floor(Math.random() * options.length), 1)[0]
                        : "Not enough options for "+attr
                    )))
                : line
            ).flat());
        }
        if (section.js) {
            section.js();
        }
    };

I think the issue might have been precedence there. I added a few more () to tidy it up. That works in my javascript console; does it work in Squiffy?

If so, it's probably safe to cut out all the extra space between lines


This one only blanks the screen if I use any kind of far-left @, such as @set/unset/inc/dec. But the random function still doesn't work.


I'll try to fix it as soon as I'm not on mobile :S Still no errors in the console when it fails?


OK, I just tried it and it works for me. Not sure what's different (I'm copying it into the compiled story.js for a test game rather than squiffy-template.js, but as far as I understand that should do exactly the same thing).


A bit of an "aha!" this morning, but not entirely fixed yet. Your 'white space' version blanks the screen of the Squiffy editor app only. Yesterday I only checked the browser with the 'ugly' version. I checked again this morning after seeing your message. It works in the browser, just not in the editor.

Frankly, I'll take it. The editor output has been out of sync with browser output for a long time now. This will just force me to stop frustrating myself with it.

I'm sorry for all the nuisance.

Edit: from Chat GPT (March 22, 2023)

squiffy.story.run = function(section) {
    if (section.clear) {
        squiffy.ui.clearScreen();
    }
    if (section.attributes) {
        var parts, options, processedLines = [];
        section.attributes.forEach(function(line) {
            if (parts = line.match(/^random\s*:\s*([\w,]+)\s*=\s*(.+)/i)) {
                options = parts[2].split("|");
                parts[1].split(",").forEach(function(attr) {
                    if (options.length) {
                        processedLines.push(attr + " = " + options.splice(Math.floor(Math.random() * options.length), 1)[0]);
                    } else {
                        processedLines.push("Not enough options for "+attr);
                    }
                });
            } else {
                processedLines.push(line);
            }
        });
        processAttributes(processedLines);
    }
    if (section.js) {
        section.js();
    }
};



OK, that's really weird. I'll need to get electron working so I can test it.

(Apparently there's a chance that it might not work properly in IE… does anybody still use that?)


This topic is now closed. Topics are closed after 180 days of inactivity.

Support

Forums