There were two non-trivial aspects of the design of Anagram Bagels: puzzle generation, which I discussed in my last post, and how to handle saving and sharing puzzles, which I will discuss in this post. I wanted an intuitive design that satisfied the following constraints:
-
It should be possible to easily share a puzzle with another person in the form of a link.
-
The difference between a link to the game and a link to a specific puzzle should be clear. (So the user doesn't accidentally bookmark a link to a specific puzzle when meaning to bookmark the game.)
-
The game should gracefully handle the common mobile browser behavior of reloading the page if it hasn't been viewed in a while.
-
Opening multiple instances of the game in separate tabs shouldn't break anything. (This is the default for web sites, so it's true unless doing something to actively break this assumption.)
Initial design: store state in fragment#
/
1 starts a random puzzle./#fragment
starts the puzzle identified byfragment
.- Once a puzzle has been started, the fragment in the URL bar
is automatically changed to
/#fragment
to match the current puzzle.
In this design, the current URL would always be the link to the current puzzle so it could be shared with someone else (1✓) or, if the browser reloaded, the same puzzle would be loaded (3✓) (although the guesses would be lost (3?)). A more major downside of this design is that if the player bookmarks the page, they'll always get the same puzzle (2✗).
Next iteration: extra action to copy link#
/
starts a random puzzle./#fragment
starts the puzzle identified byfragment
.- Once a puzzle has been started, the URL bar is automatically changed
to
/
. - The
/#fragment
URL can be copied using the "Copy Permalink" button on the settings screen.
The "Copy Permalink" button provides a way to get the link with the fragment identifying the current puzzle (1✓), even though as soon as the puzzle loads, the fragment is deleted from the current location. "Copy Permalink" is a common enough pattern on websites that this isn't likely to be confusing. And this fixes the issue of the bookmark always going back to the same puzzle (2✓). On the other hand, it makes losing progress due to the page reloading much worse: instead of losing just all of the guesses, it also loses the puzzle (as going to the page without a fragment makes it generate a new random puzzle) (3✗).
Final design: remember state independent of tab#
/
resumes the most recently played puzzle (of any tab open or closed) including guesses or starts a new random one if there is no saved puzzle./#fragment
starts (or resumes) the puzzle identified byfragment
.- Once a puzzle has been started, the URL bar is automatically changed
to
/
. - The
/#fragment
URL can be copied using the "Copy Permalink" button on the settings screen. - The active puzzle can be changed using the list of active puzzles on the settings screen or by clicking the "Generate new game" button there.
In order to satisfy the competing concerns of making the bookmarked page
sensible and not losing state when the browser reloads unexpectedly,
clearly we need some place to save the game state other than the
fragment. The solution I used is localStorage
. The
complication is that localStorage
is shared across all of the tabs on
the same website, while fragments are nicely tab-specific. Which means
now I need semantics for what happens when the game is being played in
multiple tabs at once, which is not an unlikely scenario if two people
are playing and sharing interesting puzzles with each other. Then they
will have current puzzle they are working on one tab, and they may open
a new tab with a puzzle shared by their friend.
The player should be able to open a link from their friend in a new tab and switch between the two tabs and play both puzzles simultaneously. Or close one of them and return to that puzzle later. Or close both and return to both puzzles later, including choosing which one to return to. Also, while in the middle of a puzzle, they should have the option to start a new puzzle.
These requirements are satisfied by storing in localStorage
a list
of in-progress puzzles and the guesses the player has made on them,
along with a timestamp of the last access used to select the most recent
puzzle when the game is opened. Whenever a puzzle is loaded, this list
is checked to see if the player has already made any guesses on it.
In the settings menu there is a drop-down of puzzles in progress so
the player can switch. And the "Generate New Game" button no longer
warns about losing progress because the game remembers the previous
puzzle. When a player finishes a puzzle, they are taken to their next
in-progress puzzle or to a newly generated one if they have finished
their last in-progress puzzle.
In addition to satisfying all four requirements listed above, this design has the extra bonus of saving progress even if the game is closed, as one would expect from a phone app. The only downside is that if a browser session with multiple active puzzles decides to reload all tabs, then all of the tabs will have the same puzzle until the player manually selects to switch them to different in-progress puzzles.
Alternative design: extra action to bookmark page#
/
starts a random puzzle./#fragment
starts the puzzle identified byfragment
./#fragment,guesses
starts the puzzle identified byfragment
and loads the state specified byguesses
.- Once a puzzle has been started and whenever a new guess is made,
the URL bar is automatically changed to
/#fragment,guesses
. - The
/#fragment
URL can be copied using the "Copy Permalink" button on the settings screen. - The
/
URL can be bookmarked by using the "Bookmarkable Link" button on the settings screen before bookmarking the page.
An alternative to the design complication introduced by the "final
design" described above would be to target requirement 2 more
directly by making the user have to select an option to get a
bookmarkable page. The UI would look like an option in the settings
telling the user to click it before bookmarking the page. The
implementation would change the fragment to indicate it's the
bookmarkable version of the page and perhaps use localStorage
to
determine if it had been loaded since the last time the bookmarkable
link was requested. Then it would redirect to the actual game which
would store its state in the fragment as described in the initial
design.
I decided this would be too unintuitive as it's not really how anything else works. Also, the final design acts more like a phone app in that closing the game and opening it again restores the state while this design relies on keeping the tab open in the web browser.
What's in the fragment, anyway?#
My requirements for the fragment used in permalinks were:
- The link should take you to the same puzzle with no guesses filled in.
- Looking at the link shouldn't accidentally give away the answer. (But don't try too hard, since the answer is definitely available to JavaScript debugging tools.)
The first requirement is fairly straight-forward: just serialize the puzzle definition to a string. Unfortunately, the puzzle definition necessarily includes the answer to the puzzle.
My first idea to obscure the fragment was to use Base64
encoding, which is probably good enough on its own, but I decided that
someone sufficiently familiar with Base64 might recognize the encoding
of letters and accidentally spoil the puzzle for themselves,
so I wanted a reversible string scrambling algorithm
that would be easy for a computer but definitely no human would be
able to do in their head. This sounds a lot like what encryption is
supposed to do if you have the key, so I just used the JavaScript
encryption support2 with a hard-coded key.
The result was encryptStringNoKey()
and decryptStringNoKey()
in
scramblestring.js, which do AES encryption and
decryption using a key of all zeros, which, while trivial to break, a
human isn't likely to be able to do in their head accidentally.
-
The URLs for these behavior summaries are written omitting everything before the final
/
as that part depends on where the game is hosted. ↩ -
That documentation warns that if you don't know what you're doing, your code using that library is unlikely to be secure. Which is fine in this case as I'm only trying to obscure the string from someone not trying to read it. ↩
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.