Random engine for people who like to use Squiffy as possible while tending to avoid Javascript
IFforClassroom
14 Sept 2021, 00:28Other 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)
IFforClassroom
17 Sept 2021, 06:07Hmm... Can't figure out why it won't work inside a [[]]
master section.
IFforClassroom
21 Sept 2021, 12:30Oh...
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.
mrangel
21 Sept 2021, 15:44I 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:
- master section
@set
s - master section javascript
- master section text
- current section
@set
s - current section javascript
- 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!”
mrangel
22 Sept 2021, 22:30If 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.
IFforClassroom
23 Sept 2021, 04:51Off 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.
mrangel
23 Sept 2021, 10:40Hmm… 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();
}
};
IFforClassroom
23 Sept 2021, 22:27That 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.
mrangel
23 Sept 2021, 23:05This 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.
IFforClassroom
24 Sept 2021, 05:38I see! That's perfect then.
IFforClassroom
27 Sept 2021, 00:15Hi 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.
mrangel
27 Sept 2021, 08:03Probably a mismatched bracket or something. Any errors in the javascript console?
IFforClassroom
27 Sept 2021, 08:52No, 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 [[]]:
mrangel
27 Sept 2021, 09:34OK, 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
IFforClassroom
27 Sept 2021, 11:34This 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.
mrangel
27 Sept 2021, 12:09I'll try to fix it as soon as I'm not on mobile :S Still no errors in the console when it fails?
mrangel
27 Sept 2021, 15:51OK, 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).
IFforClassroom
27 Sept 2021, 23:58A 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();
}
};
mrangel
28 Sept 2021, 00:35OK, 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?)