Importing attributes from one game to its sequel

It seems during lockdown I have nothing better to do than work on my Squiffy game and hit my own technical limitations!

Is there any way that I could give players a code at the end of a game that they could enter at the beginning of the next game to import their character and decisions?

Outputting it would be easy enough - for example if my character was called Bob, he had black hair and blue eyes and a dog called Rover, I could produce something at the end of the game like

{charname}-{hair}-{eyes}-{dog} which would output Bob-black-blue-Rover

Or :

charname={charname}-hair={hair}-eyes={eyes}-dog={dog} to output charname=Bob-hair=black-eyes=blue-dog=Rover

But I can't figure out a way for a string like that to be imported into another game to set those attributes for the character there, eg to continue Bob's story.

Has anyone done anything like this, or see a way to do it?

Thanks again in advance!


You might give each value a coded number, like...

{if hair=black:{@hairCode=1}
{if hair=brown:{@hairCode=2}
{if hair=blonde:{@hairCode=3}
Your continue code to play the next game is {name}{hairCode}

and so on. And at the end of your game, give the code, such as:

Your continue code to play the next game is: Bob3

(obviously there would be a lot of numbers)
This would allow you to continue. But no, outside of that, I don't think you can really move data from game to game.


Sorry, this is what I get when I read too fast and answer before considering - you've got all that.

I suppose you could have users C&P that information into a text window, using something like this...

This {man-woman} needs a name!

<textarea id="text_first"></textarea>

[[Click here when you've entered it]](Start Pro name postclick)

[[Start Pro name postclick]]:

    squiffy.set("soName", jQuery("#text_first").val());

I've never tried to cut and paste into a text field but I just tried it and it seemed to work.


How would the second game be able to distinguish between the number codes if there are lots of them? (there would be quite a lot :) I mean, I could manually code it so that Bob1783 translated to that particular variation of Bob, but that would be a lot of work to do individually.

For that matter, how would you tell the second game that the Bob part was the name and the numbers were other attributes?

It would just be really cool to be able to create a character in part one, have things happen to him, and then enable to player to carry the character and choices over to part two with a code. Kind of like in Mass Effect but a bit more lo-fi!


Having just seen your second post bluevoss!

I don't want the player to have to re-input everything manually - especially as some of the attributes would be behind-the-scenes things, eg choices they'd made that cumulatively added to a score, and they wouldn't even know themselves what that score was. Eg your character has a score on a sliding scale between Altruistic and Selfish, and choices you make change your character's position on that scale to either end. It would be good to carry that over, but there'd be no way for the user to know what their AltrusiticSelfish score is (and nor would I want them to know!)


One option would be to block all the numbers together into a larger number.

Example (pseudo-code because I don't know Squiffy):

if (hair_color = "black") data = 0
if (hair_color = "brown") data = 1
if (hair_color = "bred") data = 2
if (hair_color = "bald") data = 3

// (multiply by 3 because the next section has 3 options)
data *= 3
if (hair_length = "short") data += 0
if (hair_length = "long") data += 1
if (hair_length = "extra-long") data += 2

data *= 5
if (eye_color = "pink") data += 0
if (eye_color = "brown") data += 1
if (eye_color = "blue") data += 2
if (eye_color = "green") data += 3
if (eye_color = "grey") data += 4

That gives you a number between 0 and 59 which represents every possible combination of those three attributes. Then you can decompose it by doing the process in reverse:

// get a number 'data' from the user somehow, then:

if (data % 5 = 0) eye_color = "pink"
if (data % 5 = 1) eye_color = "brown"
if (data % 5 = 2) eye_color = "blue"
if (data % 5 = 3) eye_color = "green"
if (data % 5 = 4) eye_color = "grey"
data /= 5

if (data % 3 = 0) hair_length = "short"
if (data % 3 = 1) hair_length = "long"
if (data % 3 = 2) hair_length = "extra-long"
data /= 3

if (data = 0) hair_color = "black"
if (data = 1) hair_color = "brown"
if (data = 2) hair_color = "bred"
if (data = 3) hair_color = "bald"

You can encode quite a lot of options into a single number. (As far as I understand it, Squiffy's variables are limited by Javascript's Number.MAX_SAFE_INTEGER, which allows values up to 9007199254740991. So you could put all your numeric values into a single number.

If your character name doesn't allow numbers, then you could simply stick the name on the end of that number. If you have multiple names (character name, rival's name, pet's name, etc), then you could make one of the numbers in your block be the length of each string, and use that to split them up. Or have a string that looks like 496Bob4747George9554Fido.

(Don't know how easy string splitting is in Squiffy, but you could use Javascript like:

    jQuery("#password_from_prev_game").val().replace(/^(\d+)(\D+)(\d+)(\D+)(\d+)(\D+)$/, (data, a, b, c, d, e, f) => (
      squiffy.set("first_data", a) ||
      squiffy.set("player_name", b) ||
      squiffy.set("second_data", c) ||
      squiffy.set("rival_name", d) ||
      squiffy.set("third_data", e) ||
      squiffy.set("dog_name", f)
    ));

Thanks mrangel, I'll give that a whirl! That was the kind of thing I was looking for - a way to encode the possible options into a string, have the user copy/paste the string from the first game into a textfield in the second, and then un-encode them from the string the other end. I'll have a go!


If you were stacking numbers like that (eg a morality score), you'd have something like:

data *= (max_morality - min_morality)
data += (morality - min_morality)

and on the other end:

morality = data % (max_morality - min_morality) + min_morality
data /= (max_morality - min_morality)

:)

Alternatively, if you don't mind doing more of the code in javascript, you could use JSON.stringify to turn a list of attributes into a string like {name:"Joe",pet:"Fido",age:24}, and the lz-string library to zip that string (making it not-human-readable) and base64 encode it.


Mind being blown a bit here : ) but working through one step at a time!

So the first part works - converting hair color, hair length and eye color into a number. This is what it is in Squiffy:

@title Character stats
@set hair_color=red
@set hair_length=long
@set eye_color=blue

[[Start]]

[[Start]]:

{if hair_color=black: {@data = 0}}
{if hair_color=brown: {@data = 1}}
{if hair_color=red: {@data = 2}}
{if hair_color=bald: {@data = 3}}

// (multiply by 3 because the next section has 3 options)
{@data*=3}

{if hair_length=short: {@data += 0}}
{if hair_length=long: {@data += 1}}
{if hair_length=extra-long: {@data += 2}}

{@data*= 5}

{if eye_color=pink: {@data += 0}}
{if eye_color=brown: {@data += 1}}
{if eye_color=blue: {@data += 2}}
{if eye_color=green: {@data += 3}}
{if eye_color=grey: {@data += 4}}

{data}

Run that in Squiffy, and with those options it outputs a value for data of 37 which is correct.

Converting it back is causing me more trouble!

@title Unencrypt password
@set data=37

[[Start]]

[[Start]]:

// get a number 'data' from the user somehow, then:

{if data % 5=0: {@eye_color=pink}}
{if data % 5=1: {@eye_color=brown}}
{if data % 5=2: {@eye_color=blue}}
{if data % 5=3: {@eye_color=green}}
{if data % 5=4: {@eye_color=grey}}
{@data/=5}

{eye_color}

This just outputs 'null' for {eye_color}. I'm not sure if my translation of your code into Squiffy's syntax is wrong, or if Squiffy just can't parse the % - any ideas?


(Now you've got me thinking about that, I'm wondering how I'd do it. Something like:

    var attributes_to_save = "name haircolor fish_size money pet";
    jQuery.getScript("https://mrangel.info/lz-string.js", function () {
      var values = {};
      attributes_to_save.split(/\s+/).forEach (attr => values[attr] = squiffy.get(attr));
      jQuery("input#saveString").val(LZString.compressToBase64(JSON.stringify(values)));
    });

<input readonly type="text" id="saveString" value="Generating password; please wait" />

and on the other side,

Paste your save password from the previous game here: <input type="text" id="loadString" />

(followed by, when they entered something):

    var attributes_to_load = "name pet haircolor fish_size money";
    jQuery("input#loadString").last().prop("readonly", true).each(function () { this.value && jQuery.getScript("https://mrangel.info/lz-string.js", x => {
      var values = JSON.parse(LZString.decompressFromBase64(this.value));
      attributes_to_load.split(/\s+/).forEach (attr => squiffy.set(attr, values[attr]));
    });});

(yeah, I tend to come up with messy code like this off the top of my head. I'm kind of procrastinating right now, because I've set one of my books to be free on Friday again, and I really should be setting up some kind of advertising and begging friends to reshare stuff… the part of my job I hate most)


Yeah; it looks like Squiffy doesn't understand %. Sorry; not sure what I can suggest in that case.

x % 5 is the same as x - x / 5 * 5 if division is rounded off… I'm not sure if you'd need to work that out separately before the if statements (don't know how arithmetic works in this engine)


Just tried your latest solution - the first part generates a password, but the second doesn't translate it back into the attributes.

First half:

@title testing javascript

@set name=Bob
@set haircolor=red
@set fish_size=large
@set pet=dog
@set money=5

[[start]]

[[start]]:
    var attributes_to_save = "name haircolor fish_size money pet";
    jQuery.getScript("https://mrangel.info/lz-string.js", function () {
      var values = {};
      attributes_to_save.split(/\s+/).forEach (attr => values[attr] = squiffy.get(attr));
      jQuery("input#saveString").val(LZString.compressToBase64(JSON.stringify(values)));
    });

<input readonly type="text" id="saveString" value="Generating password; please wait" />

Second part:

@title Second game


Paste your save password from the previous game here: <input type="text" id="loadString" />
[[Next]]

[[Next]]:

    var attributes_to_load = "name pet haircolor fish_size money";
    jQuery("input#loadString").last().prop("readonly", true).each(function () { this.value &&                jQuery.getScript("https://mrangel.info/lz-string.js", x => {
     var values = JSON.parse(LZString.decompressFromBase64(this.value));
    attributes_to_save.split(/\s+/).forEach (attr => squiffy.set(attr, values[attr]));
    });});
    


{haircolor}
{pet}

{haircolor} and {pet} output null. This feels like you've nearly cracked it though!


Two things:

  • It's asynchronous (there may be a fraction of a second delay while the "load" script fetches the lz-string library. So the rest of that page is displayed before the load finishes.
  • There was a typo (attributes_to_save vs attributes_to_load in the second script. I noticed and fixed it right after posting, but you must already have copied it.

This works (I actually tested it, rather than typing code on the forum this time), going to a new page after it finishes decoding the data:

@title Second game


Paste your save password from the previous game here: <input type="text" id="loadString" />
[[Next]]

[[Next]]:

    var attributes_to_load = "name pet haircolor fish_size money";
    jQuery("input#loadString").last().prop("readonly", true).each(function () { this.value &&                jQuery.getScript("https://mrangel.info/lz-string.js", x => {
     var values = JSON.parse(LZString.decompressFromBase64(this.value));
    attributes_to_load.split(/\s+/).forEach (attr => squiffy.set(attr, values[attr]));
     squiffy.story.go("after load");
    });});
    

[[after load]]:

{haircolor}
{pet}

If you want a shorter save string, you can switch out compressToBase64 and decompressFromBase64 in favour of compressToUTF16 and decompressFromUTF16. More different characters to choose from means the output string is shorter.

Base64 (uses 64 different characters: A-Z, a-z, 0-9, +, and /) gives a string like:
N4IgdghgtgpiBcIBCB7ARiANCAFhAlgE4DGKANioQiITACZYgBm+AzjgPqv4BeciZCIQDmcbFBRgYATwQBWbAAcYAF2p0UwkAF8gA===

UTF16 (using all the world's alphabets) gives a string like:
ᯡ࠽䄬஀匰ᜨȰỠ⌰͢L၅䁇æᐡ壈ℱӠӫࠡ丐ഃ䂝Ⱈ̓䢹ѢĆ㣸偱䃠Ԑ‪曠ă%浮䕐䠠彀
which most computers should be able to copy/paste even if they can't display it properly.

The strings are compressed; so saving more attributes will make the string larger, but not by much. For comparison, the uncompressed string for those examples was:
{"name":"Bob","haircolor":"red","fish_size":"large","money":5,"pet":"dog"}


Well that's just about the most awesome thing I've ever seen mrangel. It works! Beautifully! And it's going to completely transform how I write my games - they can be a true sequence with the player carrying the same character over from one to the next, preserving all their stats and choices!

I'm just made up! Thank you SO much! When its finished you'll be in the credits for certain!


I linked to the copy of lz-string.js on my server because it was quicker to write. It might be more efficient to include a copy of it with the game (if Squiffy allows you to do that).

If your games are hosted on the same website, you might be able to put the password into a cookie or LocalStorage, without needing to display it to the player – in that case, you'd probably use compress and decompress rather than compressToBase64 and decompressFromBase64. (The plain "compress" and "decompress" output binary strings, which saves more space but may not be able to be copied between applications because they contain invalid characters. They're fine for storing within the browser that generated them, though)


I have indeed downloaded a copy of lz-string.js - I wouldn't want to be hitting your server with requests every time someone's playing my game!

I think it's likely that the player will need to copy the password and use it later. At first there'll only be one game without a second to proceed into - but when I do write the second part their password will let them pick up with their character. It's also the sort of game that people will play through multiple times with multiple characters, too. Lots of potential endings, achievements etc.

What I'm finding so stimulating is that every time I have a 'can I do this with it?' moment that I can't solve, you/bluevoss/the members of this forum come up with a solution far beyond Squiffy's basic functionality. It's a fantastic resource and I'm very grateful!


There is a easy way, always the user plays both games inthe same browser/phone: the first game stock the values into the localStorage and then the second game retrieve them. It works just like the named Mass Effect and don´t need human interaction or tricky code numbers to do the job.


Thanks Belén - but that presumes the user is using the same device/browser/hasn't cleared their cache since the first game, and it would also presume that they only have a single character whose story they want to continue. The password solution is probably the right one for my needs!


You can store as many character per device as you like, it´s all about the dumping data structure.

The password solution is cool in terms of coding, but in terms of design is in the eighties (from last century), not the right call for 2020 users, just saying. Few people will go to the full process of writing a chunk of letters/numbers in somewhere (paper? notepad on device? tatto ink? xD), place it anywhere he can remenber & not get lost in combat and then retrieve & reintroduce it into the new game when it cames out. Is not how it works anymore. It looks easier to me a lost of the information that way than the possibility of cache cleared, specially on phones/tablets. As a designer your solutions should be userproof because relaying on the user behaviour usually lead to disaster. But if it works for you, then it is. :)


I can see a kind of hybrid situation that gives the best of both worlds. Could dump in LocalStorage with game start time, last saved time, which game in the series, and main character name (if relevant); then have the JS pull it out. So next to the text entry field, a drop down list pops up showing previously saved games on this browser.

If you generate a random ID for the game when you start, and then only store the latest save for each ID, that stops the list getting too cluttered. Not sure if the ID would be preserved through different games in the series or not (possibly not, because you might want to start game 2 twice from the same game-1 save and then take different options, and see how both of them pan out in game 3)

It's so tempting to see how quickly I could throw out the code for that; it doesn't seem too complex. But I just came back from a 9-mile walk, and I've got another free-book promo on Kindle tomorrow that I really need to some kind of promotion for. Perhaps I'll see what I can throw together later.


This "password" solution has my brain exploding with possibilities. Could the "password" solution can be used for save points within a squiffy story as well? Putting seen flags in at each section and saving that with the other attributes in the encoded 'password' they can then enter to be decoded the next time they want to play. I use a system which clears cache frequently, so relying on that to remember won't work.

Is there another way to get the player the code? (Hopefully this makes sense. I'm just starting with Squiffy so I don't know all the limitations yet...but I'm pondering if the code could be sent to an external link, such as a new trello entry, or a zapier call, or a google spreadsheet, which can be called by a database (linked to a player's account on a website) OR which can then be picked up by an automation and sent to the player's email. )


Is there another way to get the player the code? (Hopefully this makes sense. I'm just starting with Squiffy so I don't know all the limitations yet...but I'm pondering if the code could be sent to an external link, such as a new trello entry, or a zapier call, or a google spreadsheet, which can be called by a database (linked to a player's account on a website) OR which can then be picked up by an automation and sent to the player's email. )

Google sheets is certainly possible. Not sure about the others; depends on their APIs. The coding is likely to be relatively simple, but the process of setting up various keys and permissions so that it works might be more complex.


If it can be put into a Google Sheet...it can pretty much go anywhere at that point as intermediary. :) Now to figure out how to get it into a Sheet lol


I'm always open to better solutions, but any saved-in-browser solution would need to account for caches being cleared (I clear mine frequently for various reasons, and as significant time could pass between games in a series being available people could even have changed browser or device), cookies being turned off, multiple devices, and multiple characters :)


So. I was giving this a thought and I found a failsafe desing solution that acomplish to be selfcontained, organic and userproof: implementing a "previously on..." section in the second game, like a tv show.
It´s made under the assumption that only a few choices are relevant from the later game and not all of it.

So, the second game looks into the localStorage for first game savegames. If it founds them, show the several characters/stories for the player to choose between them and reproduce a sumary in the format of "previously on...". If there is no savegame (cache cleaned, device changed, etc...), the game shows an interactive sumary where the player choose the go of the main events in the previous game. And that´s it.

You may say: hey, the player might not remeber the choices he made in the previous game. ¿So? If he don´t remeber is because he don´t care enough about it to remenber (or don´t give a shit about it), then let he choose what it want again.


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

Support

Forums