For the past few months I have been working with a team of puzzlers to write a puzzle hunt for the DC area called DCPHR1.
A puzzle hunt is an event where teams of people compete to solve puzzles of all types at different locations around the city.
Our first hunt was run last weekend in Clarendon.
This post describes my experience writing one of the puzzles Lycanthropic Love.

Brief tangent: DCPHR is an annual event. We are always looking for more people to help write, test, and run the hunt. Email me if you are interested in participating in developing next year’s hunt.

Take a moment now to go look at the final product hosted at lycanthropic.love. The rest of this post will dive into the details of both the puzzle mechanics and their implementation in the browser with Elm. So, beware of spoilers.

I chose to work on this puzzle specifically to experiment with new browser technologies.
So I couldn’t go with plain JavaScript.
I’m drawn to languages over frameworks, which leaves me with ClojureScript, CoffeeScript, TypeScript, or Elm.
Past experience with Clojure2 and ClojureScript3 left me wanting more useful error messages and less Java dependencies.
CoffeeScript and TypeScript are supersets of JavaScript which forces them to maintain / paper over some of the JavaScript ugliness (the exact thing I was trying to avoid).
Which leaves Elm.

Elm provides a runtime which handles the interaction with the DOM and JavaScript and thus allows me to focus on my business logic.
Immutability, friendly errors, and strong typing also match closely with my recent rust experience.
So Elm it is.

Lycanthropic Love is a satirical dating app for Werewolves.
Inspired by Tinder, you are presented with matches.
For each match you are expected to swipe right for yes and left for no.
In this puzzle you are presented with 7 different profiles.
Your job is to read each profile, determine a pattern for what they like, and then select the appropriate matches.

Each profile contains a list of likes.
All of the likes have a common relationship.
The matches which should be swiped right also have this relationship.
For example, the Bare Wolf lists his likes as “Pandemonium”, “discount sales”, “education”, and “Pabst Blue Ribbon”.
Each of these items contain each vowel exactly once, also known as supervocalics4.

Once all of the matches are swiped, the results are listed on the main page as a series of paws up or down.
These five swipes can then be converted to a letter using a binary table (common encoding technique provided in the intro packet).
The answer to the puzzle is a seven letter word.

Now for the fun part: writing web page.
Let’s start with the wolf data structure (called Models in Elm).

I chose to store the matches inside of the Wolf type and the SwipeDirection inside of the Match.
This choice allowed rendering to be a straightforward tree walk, while storing a swipe requires two list lookups, both for the wolf and the match
I later found out (while watching an elm-conf presentation) that a Ziplist5 would have been the correct compromise.

To provide a taste of the rendering section, let’s look at the process for rendering the profile screen for a single wolf.
The function takes a Maybe Wolf since it is possible that a wolf hasn’t been selected yet (likely due to the refresh or back button, one of the many areas that could be improved) and returns a request to the Elm runtime (and virtual dom) to add nodes to the DOM tree.
This indirection is one of the key reasons Elm is both safe and fast.
Developers only write requests and must deal with both the success and failure of that request.
The Elm runtime then optimizes DOM insertions with one of the fastest virtual DOM implementations.

To demonstrate both the consequences of the Wolf type structure and my lack of experience with Elm, here is the code which stores the swipes. Maybe chaining and ziplists would drastically reduce the size of this code snippet.

In order to capture the swipe I had to fall back to JavaScript and the Hammer.js library.
Elm uses ports and subscriptions to communicate with JavaScript.
The code below is slightly bloated due to dynamically attaching event listeners tonodes (which represent the swipeable object) as they are added to the DOM.
The virtual dom asynchronously updates the actual dom nodes, hence the setInterval to periodically test whether the node is ready or not.

Despite some less than ideal choices, Elm was easy to get started with.
By forcing me to think about my data model up front I ended up with a cleaner application.
I look forward to using Elm on my next project.

DCPHR is pronunced “Decipher” and stands for DC Puzzle Hunt. We never came up with a useable acronym which used the “R”.
↩