## Overview of Puzzle

Last weekend I wanted to continue learning about arduino and 3D printing, so I created a physical logic puzzle. Inspired by the XBox puzzle from the Famine Games, the Rainbow Box is a logic puzzle where the goal is to enter the word “rainbow” into the box. There is not much else I can say about the puzzle without spoiling it.

# Spoiler Warning

The rest of this post will provide the solution as well as explore the implementation of the puzzle. If you want to solve the puzzle yourself stop here and come back once you are done.

# Note

Since the seven segment display has only two columns of vertical lines it is impossible to render the entire alphabet, including "w". Which is necessary for entering the word "rainbow". Thus it is required to determine the function of each button before submitting an answer. I guess one could brute force it by trying all 32 combinations after entering "rainbo", but that defeats the goal of a puzzle.

To begin, flip the switch and wait for the screen to finish displaying the spiral. Now the box is ready to receive input. There are six buttons, one on each side of the cube. The first step in solving is to observe what each button does when pressed individually.

The blue button displays a `p`. The green button displays an `h`. The orange button displays a `b`. The red button displays an `a`. The white button displays the phrase `nope`. The yellow button displays `d`.

The white button is an obvious outlier, displaying a phrase instead of a single letter. From this fact we deduce that the white button is used to submit the current letter. Now we have to look at the colored buttons. The table below sorts the buttons alphabetically based on the letter produced when pressed.

Now that all of the pairs of buttons have been observed there is enough information to make a hypothesis about the role of each button. Start by looking at the list of single button outputs: `b`, `d`, `h`, `p`. Compare that list with the list of pairs which include the red button: `c`, `e`, `i`, and `q`. Notice that the characters in the red button list are the very next character in the alphabet. From this, the hypothesis is that the red button shifts the output by one letter. This hypothesis can be tested by pressing the red button along with one of the non-red pairs. Pressing the orange, yellow, and red buttons simulataneously reveals a `g`. Thereby confirming our hypothesis.

Using the same process shows that the orange button (`c`, `f`, `j`, `r`) shifts by two letters and the yellow button(`e`, `f`, `l`, `t`) shifts by four letters. Green (`i`, `j`, `l`, `?`) and blue (`q`, `r`, `t`, `?`) appear to shift by eight and sixteen but have an unknown letter. Based on our theory we can resolve that ambiguity by adding the red button and then shifting backwards by one letter. green-blue-red reveals a `y` which means that green-blue is `x`.

The buttons shift the display character by `1`, `2`, `4`, `8`, or `16`. All of which are powers of two. This means that the box is using each button to represent a single bit in the the letter that is displayed. The following figure shows the conversion between bits and letters.

The table above shows the buttons needed to enter each letter.

1. `r` = `10010` = blue + orange
2. `a` = `00001` = red
3. `i` = `01001` = green + red
4. `n` = `01110` = green + yellow + orange
5. `b` = `00010` = orange
6. `o` = `01111` = green + yellow + orange + red
7. `w` = `10111` = blue + yellow + orange + red

Entering each of the button combinations above produces the final message: “congrats you have found the pot of gold” which is the end of the puzzle.

## Typeface

Each letter is rendered on the seven segment display. The rendering process uses the integer computed from the current button configuration as an index into the typeface table. Each row of the table contains eight integers representing the signal to send to each segment (`HIGH` or `LOW`).

The following figure is a JavaScript implementation of the rendering process. The text input accepts a single character and displays it on the adjacent display. Some of the characters are not renderable and are represented by a single dot.

## Code

The code centers around one main data strucure, `state`. `state` represents the current state of each button (pressed or not) and the current stage of the puzzle (which letters have been correctly entered). There are then a series of functions which construct the state from the buttons, render the state on the display, and submit a letter. The `numberEntered` function is particularly interesting as it succintly summarizes the entire puzzle.

``````const int LED_A = 2;
const int LED_B = 3;
const int LED_C = 4;
const int LED_D = 5;
const int LED_E = 6;
const int LED_F = 7;
const int LED_G = 8;
const int LED_H = 9;
const int RED_PIN = 10;
const int ORANGE_PIN = 11;
const int YELLOW_PIN = 12;
const int GREEN_PIN = 13;
const int BLUE_PIN = 14;
const int SUBMIT_PIN = 15;

static const int typeface[32][8] =
{ {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }
, {HIGH, HIGH, HIGH, LOW,  HIGH, HIGH, HIGH, LOW }
, {LOW,  LOW,  HIGH, HIGH, HIGH, HIGH, HIGH, LOW }
, {HIGH, LOW,  LOW,  HIGH, HIGH, HIGH, LOW,  LOW }
, {LOW,  HIGH, HIGH, HIGH, HIGH, LOW,  HIGH, LOW }
, {HIGH, LOW,  LOW,  HIGH, HIGH, HIGH, HIGH, LOW }
, {HIGH, LOW,  LOW,  LOW,  HIGH, HIGH, HIGH, LOW }
, {HIGH, HIGH, HIGH, HIGH, LOW,  HIGH, HIGH, LOW }
, {LOW,  LOW,  HIGH, LOW,  HIGH, HIGH, HIGH, LOW }
, {LOW,  LOW,  LOW,  LOW,  HIGH, HIGH, LOW,  LOW }
, {LOW,  HIGH, HIGH, HIGH, HIGH, LOW,  LOW,  LOW }
, {LOW,  HIGH, HIGH, LOW,  HIGH, HIGH, HIGH, HIGH}
, {LOW,  LOW,  LOW,  HIGH, HIGH, HIGH, LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  HIGH}
, {LOW,  LOW,  HIGH, LOW,  HIGH, LOW,  HIGH, LOW }
, {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, LOW,  LOW }
, {HIGH, HIGH, LOW,  LOW,  HIGH, HIGH, HIGH, LOW }
, {HIGH, HIGH, HIGH, LOW,  LOW,  HIGH, HIGH, LOW }
, {LOW,  LOW,  LOW,  LOW,  HIGH, LOW,  HIGH, LOW }
, {HIGH, LOW,  HIGH, HIGH, LOW,  HIGH, HIGH, LOW }
, {LOW,  LOW,  LOW,  HIGH, HIGH, HIGH, HIGH, LOW }
, {LOW,  LOW,  HIGH, HIGH, HIGH, LOW,  LOW,  LOW }
, {LOW,  HIGH, HIGH, HIGH, HIGH, HIGH, LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  HIGH}
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  HIGH}
, {LOW,  HIGH, HIGH, HIGH, LOW,  HIGH, HIGH, LOW }
, {HIGH, HIGH, LOW,  HIGH, HIGH, LOW,  HIGH, LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }
, {LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW } };

void setup() {
pinMode(LED_A, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(LED_C, OUTPUT);
pinMode(LED_D, OUTPUT);
pinMode(LED_E, OUTPUT);
pinMode(LED_F, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_H, OUTPUT);
pinMode(RED_PIN,    INPUT);
pinMode(ORANGE_PIN, INPUT);
pinMode(YELLOW_PIN, INPUT);
pinMode(GREEN_PIN,  INPUT);
pinMode(BLUE_PIN,   INPUT);
pinMode(SUBMIT_PIN, INPUT);

spiral();
}

enum stage {
START,
FAIL,
R,
RA,
RAI,
RAIN,
RAINB,
RAINBO,
WIN,
};

struct state {
enum stage progress;
bool redPressed;
bool orangePressed;
bool yellowPressed;
bool greenPressed;
bool bluePressed;
bool submitPressed;
};

void loop() {
static struct state s = {START, false, false, false, false, false, false};
renderState(&s);
if(s.submitPressed) { submit(&s); }
}

void spiral() {
spiralOn();
spiralOff();
}

void spiralOn() {
digitalWrite(LED_A, HIGH);
delay(100);
digitalWrite(LED_B, HIGH);
delay(100);
digitalWrite(LED_C, HIGH);
delay(100);
digitalWrite(LED_D, HIGH);
delay(100);
digitalWrite(LED_E, HIGH);
delay(100);
digitalWrite(LED_F, HIGH);
delay(100);
}

void spiralOff() {
digitalWrite(LED_A, LOW);
delay(100);
digitalWrite(LED_B, LOW);
delay(100);
digitalWrite(LED_C, LOW);
delay(100);
digitalWrite(LED_D, LOW);
delay(100);
digitalWrite(LED_E, LOW);
delay(100);
digitalWrite(LED_F, LOW);
delay(100);
}

if (digitalRead(RED_PIN) == HIGH) { s->redPressed = true; } else { s->redPressed = false; }
if (digitalRead(ORANGE_PIN) == HIGH) { s->orangePressed = true; } else { s->orangePressed = false; }
if (digitalRead(YELLOW_PIN) == HIGH) { s->yellowPressed = true; } else { s->yellowPressed = false; }
if (digitalRead(GREEN_PIN) == HIGH) { s->greenPressed = true; } else { s->greenPressed = false; }
if (digitalRead(BLUE_PIN) == HIGH) { s->bluePressed = true; } else { s->bluePressed = false; }
if (digitalRead(SUBMIT_PIN) == HIGH) { s->submitPressed = true; } else { s->submitPressed = false; }
}

void renderIndex(unsigned int index) {
if(index > 31) { index = 0; }
const int* pattern = *(typeface+index);
digitalWrite(LED_A, *pattern++);
digitalWrite(LED_B, *pattern++);
digitalWrite(LED_C, *pattern++);
digitalWrite(LED_D, *pattern++);
digitalWrite(LED_E, *pattern++);
digitalWrite(LED_F, *pattern++);
digitalWrite(LED_G, *pattern++);
digitalWrite(LED_H, *pattern++);
}

void renderCharacter(char c) {
if(c >= 'A' && c <= 'Z') {
renderIndex(c - 'A' + 1);
} else if(c >= 'a' && c <= 'z') {
renderIndex(c - 'a' + 1);
} else {
renderIndex(0);
}
}

void renderString(char* s) {
while (*s) {
renderCharacter(*s++);
delay(400);
}
}

void renderState(const struct state* s) {
renderIndex(numberEntered(s));
}

void submit(struct state* s) {
switch(s->progress) {
case START:
if(letterEntered(s) == 'R') { s->progress = R; renderString((char*) "r"); } else { s->progress = FAIL; }
break;
case R:
if(letterEntered(s) == 'A') { s->progress = RA; renderString((char*) "a"); } else { s->progress = FAIL; }
break;
case RA:
if(letterEntered(s) == 'I') { s->progress = RAI; renderString((char*) "i"); } else { s->progress = FAIL; }
break;
case RAI:
if(letterEntered(s) == 'N') { s->progress = RAIN; renderString((char*) "n"); } else { s->progress = FAIL; }
break;
case RAIN:
if(letterEntered(s) == 'B') { s->progress = RAINB; renderString((char*) "b"); } else { s->progress = FAIL; }
break;
case RAINB:
if(letterEntered(s) == 'O') { s->progress = RAINBO; renderString((char*) "o"); } else { s->progress = FAIL; }
break;
case RAINBO:
if(letterEntered(s) == 'W') {
s->progress = WIN;
renderString((char*) "congrats you have found the pot of gold");
} else {
s->progress = FAIL;
}
break;
default:
break;
}

if(s->progress == FAIL) {
renderString((char*) "nope");
s->progress = START;
}
}

unsigned int numberEntered(const struct state* s) {
return (s->redPressed    << 0)
| (s->orangePressed << 1)
| (s->yellowPressed << 2)
| (s->greenPressed  << 3)
| (s->bluePressed   << 4);
}

char letterEntered(const struct state* s) {
return numberEntered(s) + 'A' - 1;
}
``````

## 3D Models

I created the panels for the box with 123D design. I started by measuring the battery holder since it is the largest component. Then I added some buffer and ended up with each panel being 70mm x 70mm x 5mm. Next I worked out the joints. This was accomplished by adding 5mm x 5mm x 5mm cubes along each edge of each panel, forming a complete 80mm x 80mm x 80mm box. With all of the pieces in place I started merging the cubes along each edge to one of the two adjacent panels, alternating between each panel to form a finger joint. When I reached the corners I arbitrarily chose one of the three panels to join the final 8 cubes to.

With the box completed I measured each of the physical components (switches and display). Then I placed corresponding holes around the box where I wanted the components to be attached. After the model was finished it was just a matter of printing.

## Assembly

Physically assembling the box was the most challenging aspect of this project. While I have spent many years learning how to think in three dimensions and how to model the world in software, I have spent zero time soldering outside of assembling my ergodox3. A few burnt fingers and ugly solder joints later I managed to build a working prototype.

Once completing the box I realized that I spent zero time considering the mechanical properties of the wires and solder joints. This oversight makes the box fragile and replacing the batteries almost always requires pulling out the soldering iron and reattaching wires. I have already purchased screw terminals, zipties, and a hot glue gun to avoid the same error when building the next puzzle.

## Transportation

In order to store and transport the cube I took the assembled puzzle to the container store and tried fitting it into a bunch of different boxes. I ended up using one of the cylindrical gift boxes. Then I created spacers for the top and bottom to prevent the puzzle from bouncing around. I also attached some foam (left over from one of my pelican cases) to the top spacer to reduce damage from any impacts or vibrations.

1. This ordering mechanic was intentionally created to reduce the amount of memorization required for entering letters.
2. Created with Fritzing which was surprisingly intuitive. Highly recommended. NixOS even has a package for it.
3. Speaking of mechanical keyboards, I should really get around to writing that post.

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

``````type alias Wolf =
{ name            : String
, likes           : List String
, matches         : List Match
}

type alias Match =
{ name            : String
, shouldLike      : Bool
, swipe           : Maybe SwipeDirection
}

type SwipeDirection
= Left
| Right
``````

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.

``````renderCurrent : Maybe Wolf -> Html Msg
renderCurrent wolf =
case wolf of
Just wolf ->
let dislikes =
case List.length(wolf.dislikes) of
0 -> div [] []
_ -> div []
[ h3 [] [ text "Dislikes" ]
, div [] [ text (String.join ", " wolf.dislikes) ]
]
in
div [ class "wolf-profile" ]
[ wolfIcon (onClick NoOp) wolf
, h1 [] [ text wolf.name ]
-- , h2 [] [ text wolf.epitat ]
, h3 [] [ text "Likes" ]
, div [] [ text ( String.join ", " wolf.likes ) ]
, dislikes
, a [ class "button",  onClick ShowMatches ] [ text "View Matches" ]
, a [ class "button", onClick Logout ] [ text "Back" ]
]
Nothing -> div [ class "error" ] [ text "Render Current triggered without a wolf" ]
``````

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.

``````Swipe direction ->
case state.mode of
All -> (state, Cmd.none)
Current -> (state, Cmd.none)
_ ->
case lookupWolf state.currentWolf state.wolves of
Just wolf ->
let state =
storeSwipe direction state
in
case state.currentMatch of
Just matchIndex ->
case List.head (List.drop matchIndex wolf.matches) of
Just matchedWolf ->
case optionallyIncrement state.currentMatch of
Just n ->
case n >= List.length(wolf.matches) of
True ->
( { state
| currentMatch = Just 0
, mode = All
}
)
False ->
( { state
| currentMatch = Just n
}
, Cmd.none
)
Nothing ->
( { state
| currentMatch = Nothing
, mode = All
}
)
Nothing -> (
{ state
| mode = All
, currentMatch = Nothing
}
Nothing ->(state, Cmd.none)
Nothing -> ( -- Landing page was swiped away
{ state
| mode = All
}
``````

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.

``````var app = Elm.Main.fullscreen()

app.ports.match.subscribe(function(id) {
var interval = setInterval(function() {
var el = document.getElementById(id)
if(el) { window.clearInterval(interval) }
else { return }
var hammertime = new Hammer(el, null)
el.style.display = "block"
var element_width = el.getBoundingClientRect().width / 2
el.style.left = window.innerWidth / 2 - element_width + "px"

// ignore pan event which comes after swipe due to javascript event ordering
var swiped = false

if(registered) { return }
hammertime.on("pan", function (event) {
if(swiped) { swiped = false; return }
el.style.left = event.center.x - element_width + "px"
})

hammertime.on('swipe', function(ev) {
swiped = true

if(ev.direction == Hammer.DIRECTION_RIGHT) {
app.ports.swipe.send("right")
} else if(ev.direction == Hammer.DIRECTION_LEFT) {
app.ports.swipe.send("left")
} else {
return
}

element_width = el.getBoundingClientRect().width / 2
el.style.left = (window.innerWidth / 2) - element_width + "px"
})

registered = true
})
}, 10)
``````

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.

1. DCPHR is pronunced “Decipher” and stands for DC Puzzle Hunt. We never came up with a useable acronym which used the “R”.
2. Clojure projects: neighborhoods and nuc cluster
3. ClojureScript project: corewar
4. Clever readers will notice that “supervocalics” is also supervocalic.
5. A ziplist stores a list as three components `Before`, `Current`, `After`; where `Before` and `After` are lists themselves.