From 1bf591a5735068d1853be13c5a4255962835d5fe Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 5 Sep 2017 20:10:16 +0100 Subject: [PATCH] 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. --- emcc.c | 43 +++++++++++++++++++++++++++++++++++++++++++ emccpre.js | 26 ++++++++++++++++++++++++++ emccx.json | 3 +++ html/jspage.pl | 1 + 4 files changed, 73 insertions(+) diff --git a/emcc.c b/emcc.c index ca033cb..26e1ac2 100644 --- a/emcc.c +++ b/emcc.c @@ -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 * that's the most convenient thing in Emscripten, but it's not main() diff --git a/emccpre.js b/emccpre.js index efd54ae..16702bb 100644 --- a/emccpre.js +++ b/emccpre.js @@ -296,6 +296,32 @@ function initPuzzle() { 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"); gametypesubmenus.push(gametypelist); diff --git a/emccx.json b/emccx.json index e03f7e2..25d08fe 100644 --- a/emccx.json +++ b/emccx.json @@ -18,6 +18,9 @@ '_timer_callback', // Callback from button presses in the UI outside the canvas '_command', + // Game-saving functions + '_get_save_file', + '_free_save_file', // Callbacks to return values from dialog boxes '_dlg_return_sval', '_dlg_return_ival', diff --git a/html/jspage.pl b/html/jspage.pl index f828ffe..20e2a78 100755 --- a/html/jspage.pl +++ b/html/jspage.pl @@ -209,6 +209,7 @@ ${unfinishedpara} >
  • New game
  • Enter game ID
  • Enter random seed
  • Download save file
  • Type...