Support for saving games in Javascript puzzles.

This is done by getting midend_serialise to produce the complete
saved-game file as an in-memory string buffer, and then encoding that
into a data: URI which we provide to the user as a hyperlink in a
dialog box. The hyperlink has the 'download' attribute, which means
clicking on it should automatically offer to save the file, and also
lets me specify a not-too-silly default file name.
This commit is contained in:
Simon Tatham
2017-09-05 20:10:16 +01:00
parent b31ea22167
commit 1bf591a573
4 changed files with 73 additions and 0 deletions

43
emcc.c
View File

@ -756,6 +756,49 @@ void command(int n)
} }
} }
/* ----------------------------------------------------------------------
* Called from JS to prepare a save-game file, and free one after it's
* been used.
*/
struct savefile_write_ctx {
char *buffer;
size_t pos;
};
static void savefile_write(void *wctx, void *buf, int len)
{
struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
if (ctx->buffer)
memcpy(ctx->buffer + ctx->pos, buf, len);
ctx->pos += len;
}
char *get_save_file(void)
{
struct savefile_write_ctx ctx;
size_t size;
/* First pass, to count up the size */
ctx.buffer = NULL;
ctx.pos = 0;
midend_serialise(me, savefile_write, &ctx);
size = ctx.pos;
/* Second pass, to actually write out the data */
ctx.buffer = snewn(size, char);
ctx.pos = 0;
midend_serialise(me, savefile_write, &ctx);
assert(ctx.pos == size);
return ctx.buffer;
}
void free_save_file(char *buffer)
{
sfree(buffer);
}
/* ---------------------------------------------------------------------- /* ----------------------------------------------------------------------
* Setup function called at page load time. It's called main() because * Setup function called at page load time. It's called main() because
* that's the most convenient thing in Emscripten, but it's not main() * that's the most convenient thing in Emscripten, but it's not main()

View File

@ -296,6 +296,32 @@ function initPuzzle() {
command(9); command(9);
}; };
// 'number' is used for C pointers
get_save_file = Module.cwrap('get_save_file', 'number', []);
free_save_file = Module.cwrap('free_save_file', 'void', ['number']);
document.getElementById("save").onclick = function(event) {
if (dlg_dimmer === null) {
var savefile_ptr = get_save_file();
var savefile_text = Pointer_stringify(savefile_ptr);
free_save_file(savefile_ptr);
dialog_init("Download saved-game file");
dlg_form.appendChild(document.createTextNode(
"Click to download the "));
var a = document.createElement("a");
a.download = "puzzle.sav";
a.href = "data:application/octet-stream," +
encodeURIComponent(savefile_text);
a.appendChild(document.createTextNode("saved-game file"));
dlg_form.appendChild(a);
dlg_form.appendChild(document.createTextNode("."));
dlg_form.appendChild(document.createElement("br"));
dialog_launch(function(event) {
dialog_cleanup();
});
}
};
gametypelist = document.getElementById("gametype"); gametypelist = document.getElementById("gametype");
gametypesubmenus.push(gametypelist); gametypesubmenus.push(gametypelist);

View File

@ -18,6 +18,9 @@
'_timer_callback', '_timer_callback',
// Callback from button presses in the UI outside the canvas // Callback from button presses in the UI outside the canvas
'_command', '_command',
// Game-saving functions
'_get_save_file',
'_free_save_file',
// Callbacks to return values from dialog boxes // Callbacks to return values from dialog boxes
'_dlg_return_sval', '_dlg_return_sval',
'_dlg_return_ival', '_dlg_return_ival',

View File

@ -209,6 +209,7 @@ ${unfinishedpara}
><li id="new">New game</li ><li id="new">New game</li
><li id="specific">Enter game ID</li ><li id="specific">Enter game ID</li
><li id="random">Enter random seed</li ><li id="random">Enter random seed</li
><li id="save">Download save file</li
></ul></li ></ul></li
><li>Type...<ul id="gametype"></ul></li ><li>Type...<ul id="gametype"></ul></li
><li class="separator"></li ><li class="separator"></li