A Weird Imagination

Devlog: Anagram Bagels: Part 2

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:

  1. It should be possible to easily share a puzzle with another person in the form of a link.

  2. 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.)

  3. The game should gracefully handle the common mobile browser behavior of reloading the page if it hasn't been viewed in a while.

  4. 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

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

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

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

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:

  1. The link should take you to the same puzzle with no guesses filled in.
  2. 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.

  1. The URLs for these behavior summaries are written omitting everything before the final / as that part depends on where the game is hosted. 

  2. 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. 


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.