Parsing Save File Contents Through Vorple, and a Bonus Question

Hi, a friend and I are trying to do the following:

  • Run Vorple through the browser
  • Use Vorple event listeners upon save to parse the relevant information
  • Gleaning important information from either the save file itself, or something that the author notes (event flags, stats, etc)
  • Saving that into a data structure (friend keeps saying Merkle trees whilst pointing to a board with enough red string to make a Santa suit)
  • Storing that data structure somewhere (they want to figure out a cheap way to put it in a distributed data ledger, but I also think we can store a pointer to the savefile or the important stats)
  • Reading that data structure, and from that data structure generating a save file for players to play again.

Bonus Q: Iā€™m assuming thereā€™s a way to feed a properly formatted .txt file into a program and get a compiled bare story file. Has anyone done that? If not, would I start by digging into how Borogrove works, or is there a more efficient strategy?

Weā€™re actively googling, reading through the archives, and reading through the documentation, but weā€™re more storytellers than story programmers, and wanted to both be efficient and have this thread act as a galvanizing rod of sorts. I figure every time someone asks a dupe question in a different way, the act of asking that question allows for the asker to learn different ways of framing the question, so that their Learnerā€™s Journey progresses.

Thanks,

  • A

Yes. Itā€™s easiest to download the Inform 7 IDE for your platform (Mac/Win/Linux), compile a small game in it, and look in the Results/Console tab to see what CLI commands itā€™s running under the hood.

1 Like

Inform 7 supports reading and writing to external files. If you want to get the data back into the VM and not just the JS layer, then that might be a better option.

1 Like

Interesting, I assumed it would have to be back into the JS layer, but if thereā€™s a way for us to go:

  • Player types ā€œsaveā€ to save game
  • Vorple parses safefile, saves both onto the server:
    ā€” savefile itself
    ā€” human-readable stats and flags designated by author (if the savefile isnā€™t human readable)
  • Player refreshes cache and browser (wiping savefile from browser)
  • Player types restore
    ā€” either Vorple or server-side code recognizes the returning player (user login and auth?), and presents them with option to reload the savefile that had been saved
  • Player selects the savefile theyā€™re authorized to access
  • Player resumes play

That would be ideal!

Another concern was that on the VM side weā€™d want a way for players to be able to verify that (a) the savefile hasnā€™t been corrupted or incorrectly saved, and (b) that no one is MITMing to edit the save or restore Game Shark style. How would you suggest we mitigate the risk of that as much as possible?

To get access to the save files you first need a reference to the virtual file system handler and then youā€™ll need to scan the save file directory to get the files.

const FS = vorple.file.getFS();

FS.readdir( vorple.file.SAVEFILE_PATH, (error, savepath) => {
  // The result is an array of files in the directory,
  // in this case directories that contain the save files

  // The actual path of the save files, assuming there's only one directory
  const path = vorple.file.SAVEFILE_PATH + "/" + savepath[0];

  // Read each save file
  FS.readdir( path, (error, savefiles) => {
    savefiles.forEach( filename => {
      FS.readFile( path + "/" + filename, (error, contents) => {

        // do something with the file here
        // contents is a Uint8Array

      });
    });
  });
});

The FS object is BrowserFS which emulates the Node.js fs module. You should be able to build a system that syncs the contents of that directory with the server on save and on page load.

The directory structure is vorple.file.SAVEFILE_PATH (/extended/savefiles) which contains directories that are named after the signature of the current Inform story file (a long hexadecimal string.) Those directories contain the actual save files. For example if the player names the save file ā€œMySaveā€ itā€™s stored as /extended/savefiles/476c756c000301020007fb0000099/MySave (except the 476cā€¦ part is much longer and different for all story files.) The code above assumes that there are save files from only one story file but thatā€™s not necessary always the case.

Parsing data from the Inform save file isnā€™t feasible, sending a separate meta file is a better approach. Also note that save files arenā€™t compatible between game versions so when you compile a new version in Inform the old save files wonā€™t work with it anymore. If you donā€™t want saves to break after an update you need a different system, for example custom save files based on checkpoints, depending on the type of the game.

1 Like

We were worried about that; do you think a feasible work-around would be:

  1. Intra-checkpoint saves would be restored through the save file. (vorple.file.getFS())
  2. Saves at the point of the checkpoint would need to basically import { stats} from '[wherever the stats were saved]' in order to work?
  3. For situations where the writer updates a work, the only way to build off of that would seemingly be figure out a way for people to go back to the last chapter, and replay it so that the additional flags that the author is now interested in can be assessed accurately?

I donā€™t recommend this. It really canā€™t be made to work reliably. Tell people that when the game is updated, you have to start at a checkpoint.

1 Like

You can do a checkpoint system by reading and writing files within Inform (chapters 23.11.ā€“23.14 in Writing with Inform.) Write the game state data to a file and restore it by reading the file and applying the data to objects and variables. Itā€™s simpler that way than by doing it with Javascript. You can use the filesystem to read the files written by Inform if you need them in the Javascript code.

1 Like

Understood. Super grateful of your collective help here!

So narratively, the onus is on the author to find a way to incorporate those added flags, like through the achievements table?

Additionally, I seem to be running into trouble making a new column where the values are variables. At the moment, checking the calendar gives the correct date when on Borogrove or running vorple on a site in-browser, but calling the date variable when I type ā€œfull scoreā€ (using the cooking example) returns a number where there should be something like March 21, 2021, so Iā€™m unsure if the issue is that the value is being saved on the table, or calling that value makes it return a number.

Iā€™m using the Grandfather clock example as a template, and added a column for Day, both to get the day in an in-story table and to see if I could save variables that arenā€™t ā€˜nativeā€™ to Inform.

Code snippet is below:

The grandfather clock is in the Kitchen. "A grandfather clock shows that the current time is [synchronized time]." The description is "The clock shows the time [synchronized time]." The grandfather clock is fixed in place.

The calendar is in the Kitchen. "There's a calendar on the wall." The description is "Today's date has been circled: [today's date]."

The current date is a thing that varies. To say the current date:
	say today's date.

To say today's date:
	if Vorple is supported:
		execute JavaScript command "var today=new Date(); return [bracket]'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'[close bracket][bracket]today.getMonth()[close bracket]+' '+today.getDate()+', '+today.getFullYear()";
		say the text returned by the JavaScript command;
		let a current date be the text returned by the JavaScript command;
	otherwise:
		say "March 9, 2017."; 
		say line break;
		say "Looking closer, you see something  'time knows no weight this far from the source' to the right of the hung calendar, along the walls.";
		say line break;
		say "The first letters were etched with what seems like fingernails, and by the end of the phrase, the letters are spelled with something dark red, with a hint of orange." [just some random date for non-Vorple interpreters]

To synchronize clocks:
	if Vorple is supported:
		[we can't set the time directly so we have to calculate the time difference between the story time and the real time in minutes and increment the story time by that amount.]
		execute JavaScript command "var now=new Date(); return now.getHours()*60+now.getMinutes()";
		let real time be the number returned by the JavaScript command;
		let story time be ((the hours part of the time of day) * 60) + the minutes part of the time of day;
		let time difference be real time - story time;
		increase the time of day by time difference minutes. 
		
To say synchronized time:
	synchronize clocks;
	say the time of day.
[Grandfather clock bit END]

Table of Tasks Achieved
Points	Citation	Timestamp	Day (value) 
3	"sauteeing onions"	a time	--
3	"reconstituting apricots"	--	--
1	"flattening chicken"	--	--
1	"unwrapping goat cheese"	--	--


To record (T - text) as achieved:
	choose row with a citation of T in the Table of Tasks Achieved;			
	if there is no timestamp entry:
		now timestamp entry is the time of day;
		now day entry is the current date;	
		increase the score by the points entry.
		

Requesting the full score is an action out of world. Understand "full" or "full score" as requesting the full score.

Carry out requesting the full score:
	if the score is 0, say "You have achieved nothing towards supper." instead;
	repeat through the Table of Tasks Achieved in reverse timestamp order:
		say "[day entry]: [timestamp entry]: [citation entry] ([points entry])."

Table of Rankings
Score	Rank
0	"Rank Amateur"
2	"would-be Bobby Flay"
5	"Alton Brown"
8	"Julia Child"


Hey all, sorry to respond to my own post instead of editing (was unable to find it for my last post)

To bookend this thread, I think I figured it out:

  • What I did was make the date column explicitly track the in-game date
  • Then for requesting full score, I added an extra column that leveraged the synchronized time from the grandfather clock snippet.

From here, hoping to use the other Vorple features for what I need. Thank you to everyone who helped (will refrain from tagging to not clog up notifications!).

The grandfather clock is in the Kitchen. "A grandfather clock shows that the current time is [synchronized time]." The description is "The clock shows the time [synchronized time]." The grandfather clock is fixed in place.

The calendar is in the Kitchen. "There's a calendar on the wall." The description is "Today's date has been circled: [current date]."

The current date is a text variable. To say the current date:
	say today's date;
	if Vorple is not supported:
		say line break;
		say "Looking closer, you see something  'time knows no weight this far from the source' to the right of the hung calendar, along the walls.";
		say line break;
		say "The first letters were etched with what seems like fingernails, and by the end of the phrase, the letters are spelled with something dark red, with a hint of orange." [just some random date for non-Vorple interpreters]


To say today's date:
	if Vorple is supported:
		execute JavaScript command "var today=new Date(); return [bracket]'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'[close bracket][bracket]today.getMonth()[close bracket]+' '+today.getDate()+', '+today.getFullYear()";
		say the text returned by the JavaScript command;
	otherwise:
		say "March 9, 2017(?)"; 
		
To synchronize clocks:
	if Vorple is supported:
		[we can't set the time directly so we have to calculate the time difference between the story time and the real time in minutes and increment the story time by that amount.]
		execute JavaScript command "var now=new Date(); return now.getHours()*60+now.getMinutes()";
		let real time be the number returned by the JavaScript command;
		let story time be ((the hours part of the time of day) * 60) + the minutes part of the time of day;
		let time difference be real time - story time;
		increase the time of day by time difference minutes. 
		
To say synchronized time:
	synchronize clocks;
	say the time of day.
[Grandfather clock bit END]

[To check walla:
	say "It is currently [synchronized time]".]

Table of Tasks Achieved
Points	Citation	Timestamp	Day (text) [that won t work]
3	"sauteeing onions"	a time	"[today's date]"
3	"reconstituting apricots"	--	"[today's date]"
1	"flattening chicken"	--	"[today's date]"
1	"unwrapping goat cheese"	--	"[today's date]"


To record (T - text) as achieved:
	choose row with a citation of T in the Table of Tasks Achieved;			
	if there is no timestamp entry:
		now timestamp entry is the time of day;
	if there is no day entry:	
		now day entry is the current date;
	increase the score by the points entry.
		[execute JavaScript command "var Table=[bracket] [close bracket]; return new Table  "]
		

Requesting the full score is an action out of world. Understand "full" or "full score" as requesting the full score.

Carry out requesting the full score:
	if the score is 0, say "You have achieved nothing towards supper." instead;
	repeat through the Table of Tasks Achieved in reverse timestamp order:
		say "[day entry] | In-game - [timestamp entry] | IRL: [synchronized time] | [citation entry] | ([points entry])."

Table of Rankings
Score	Rank
0	"Rank Amateur"
2	"would-be Bobby Flay"
5	"Alton Brown"
8	"Julia Child"
1 Like