For a story I'm working on, I implemented a simple random-walk control in debug mode.
Pressing the "walk" button (shortcut key period) will draw an outline on a random passage link. Pressing it again will visit that link.
Pressing the "back" button (shortcut key comma) will do the reverse.
Does this seem like something that's generally useful? I've included the implementation below, and I'd be happy to make a PR to integrate it into Sugarcube-2 (and/or -3).
One subtlety of the implementation: It tries to do a shuffle, not uniform random, so in principle you could use walk/back to do a complete depth-first traversal of deterministic story graphs.
(This will probably need tweaking if the story has text inputs that expect to read comma or period.)
random-walk.css
```css
.random-walk {
bottom: 100px;
display: flex;
flex-direction: column;
font-size: 18px;
gap: 8px;
position: fixed;
right: 4px;
}
.random-walk a {
background-color: #eee;
border: 1px solid transparent;
border-radius: 4px;
color: #000;
cursor: pointer;
opacity: 0.3;
padding: 2px 4px;
}
.random-walk a:hover {
background-color: #333;
border-color: #eee;
color: #eee;
}
.random-walk-chosen {
outline: 1px solid #9cc;
}
```
random-walk.js
```js
/** Random walk */
function randomWalkSetup() {
if (!Config.debug) return;
const back = () => {
const chosen = $('#passages .random-walk-chosen');
if (chosen.length > 0) {
chosen.removeClass('random-walk-chosen');
} else {
Engine.backward();
}
};
const visited = new Set();
const lastPick = {};
const id = el => [$(el).attr('data-passage'), $(el).text()].join('|');
const walk = () => {
const chosen = $('#passages .random-walk-chosen');
if (chosen.length === 1) {
chosen.click();
return;
}
chosen.removeClass('random-walk-chosen');
const here = State.passage;
const all = $('#passages a[data-passage]');
if (all.length === 0) return;
let avail = all.filter((i, el) => !visited.has(id(el)));
if (avail.length === 0) {
all.each((i, el) => visited.delete(id(el)));
avail = all;
if (avail.length !== 1) {
avail = avail.filter((i, el) => id(el) !== lastPick[here]);
}
}
const i = random(avail.length - 1);
$(avail[i]).addClass('random-walk-chosen');
avail[i].scrollIntoView();
lastPick[here] = id(avail[i]);
visited.add(lastPick[here]);
};
$(document).on(":passageend", () => {
$(".random-walk").remove();
const div = $("
").appendTo("#story");
$('back')
.on("click", back).appendTo(div);
$('walk')
.on("click", walk).appendTo(div);
});
$(document).on("keydown", ev => {
switch (ev.key) {
case ",": back(); break;
case ".": walk(); break;
}
});
}
randomWalkSetup();
```
I think this would be a useful addition. Although is a back button really nessacary? We already have a way to do that in the debug menu with the turns counter.
Also, during testing I discovered the walk button was covered up by the debug menu if it's pulled out. Quick fix:
"back" here isn't exactly the same as "previous turn", because "walk" is two steps. First press of "walk" will select a link, second "walk" will go to that link. If you "walk", then "back", it will un-select the link, rather than going to the previous turn, which is a quick way of choosing a different link.
Equivalently, you could "walk, walk, back", so I guess it isn't really necessary to have a special-case back, but I like the light-weight way of changing the random choice.
Alternatively, it could be a "reject this choice, choose another" button, but that feels more complicated to use: three controls instead of two.
I think if something like this were integrated into Sugarcube, I'd put it in the history controls, something like this:
back: if a link is selected, de-select it. otherwise previous turn. (ctrl-comma)
forward: if a link is selected and it's the next in history, next turn. otherwise select next link in history. (ctrl-period)
random: if a link is selected, visit it (or next turn if it's the next in history). otherwise select a random link. (ctrl-slash)
I'd have to play with it a bit, but that seems to me like it would be fluid and unsurprising.
For a story I'm working on, I implemented a simple random-walk control in debug mode.
Does this seem like something that's generally useful? I've included the implementation below, and I'd be happy to make a PR to integrate it into Sugarcube-2 (and/or -3).
One subtlety of the implementation: It tries to do a shuffle, not uniform random, so in principle you could use walk/back to do a complete depth-first traversal of deterministic story graphs.
(This will probably need tweaking if the story has text inputs that expect to read comma or period.)
random-walk.css
```css .random-walk { bottom: 100px; display: flex; flex-direction: column; font-size: 18px; gap: 8px; position: fixed; right: 4px; } .random-walk a { background-color: #eee; border: 1px solid transparent; border-radius: 4px; color: #000; cursor: pointer; opacity: 0.3; padding: 2px 4px; } .random-walk a:hover { background-color: #333; border-color: #eee; color: #eee; } .random-walk-chosen { outline: 1px solid #9cc; } ```random-walk.js
```js /** Random walk */ function randomWalkSetup() { if (!Config.debug) return; const back = () => { const chosen = $('#passages .random-walk-chosen'); if (chosen.length > 0) { chosen.removeClass('random-walk-chosen'); } else { Engine.backward(); } }; const visited = new Set(); const lastPick = {}; const id = el => [$(el).attr('data-passage'), $(el).text()].join('|'); const walk = () => { const chosen = $('#passages .random-walk-chosen'); if (chosen.length === 1) { chosen.click(); return; } chosen.removeClass('random-walk-chosen'); const here = State.passage; const all = $('#passages a[data-passage]'); if (all.length === 0) return; let avail = all.filter((i, el) => !visited.has(id(el))); if (avail.length === 0) { all.each((i, el) => visited.delete(id(el))); avail = all; if (avail.length !== 1) { avail = avail.filter((i, el) => id(el) !== lastPick[here]); } } const i = random(avail.length - 1); $(avail[i]).addClass('random-walk-chosen'); avail[i].scrollIntoView(); lastPick[here] = id(avail[i]); visited.add(lastPick[here]); }; $(document).on(":passageend", () => { $(".random-walk").remove(); const div = $("I think this would be a useful addition. Although is a back button really nessacary? We already have a way to do that in the debug menu with the turns counter.
Also, during testing I discovered the walk button was covered up by the debug menu if it's pulled out. Quick fix:
This does sacrifice visibility of the back button, but again, you can do that with the turns dropdown.
"back" here isn't exactly the same as "previous turn", because "walk" is two steps. First press of "walk" will select a link, second "walk" will go to that link. If you "walk", then "back", it will un-select the link, rather than going to the previous turn, which is a quick way of choosing a different link.
Equivalently, you could "walk, walk, back", so I guess it isn't really necessary to have a special-case back, but I like the light-weight way of changing the random choice.
Alternatively, it could be a "reject this choice, choose another" button, but that feels more complicated to use: three controls instead of two.
I think if something like this were integrated into Sugarcube, I'd put it in the history controls, something like this:
I'd have to play with it a bit, but that seems to me like it would be fluid and unsurprising.