I recently built a point-and-click visual semaphore decoder for puzzling (link).As far as I can tell, it is the only semaphore decoder online that lets you enter inputs visually (but let me know if there are others!).
I've been doing a lot of puzzles lately with my friend Johnny, and these puzzles often involve flag semaphore, a visual representation of letters using the position of two flags.These are often encoded as times on a clock,but sometimes also as things like tree branches ordance moves.
It's usually fairly quick to recognize a code that could be semaphore:there are two distinct lines radiating out from a single point.But even if I know that, it's still pretty tedious to actually decode it.For encodings that are easily representable by text, there's often very good online tools to solve them -for example, dCode has nice decoders for things likeMorse code,Caesar cipher,ADFGVX, and so on.But none of the existing tools I found are very good for solving visual encodings like semaphore.
It's not just that the encoding is visual.For example, the pigpen cipher is very visual, but it's comparatively painless to decode.Why? It has a visual hierarchy: even though there are four different subcharts in the lookup table, it is immediately obvious which one to look at from the shape of the code and the dot (or absence of a dot).Similarly, if you wanted to decode Morse code by hand, you can use a Morse decoding tree to quickly converge on the right letter.
In semaphore, there isn't a clear way to do this: no text representation, no hierarchy, no ordering.Hunting for the right shape in the lookup table is tedious.Quick, what does the semaphore with left flag horizontal and right flag at a 45 degree angle towards the ground mean?You don't really have a search strategy besides looking through every single letter. The best alternative I've seen is the "pie-slice" semaphore chart, but even that one takes feels slow and painful for me to read (even though in theory it should be just like the pigpen chart).
I thought the best way to do this would be a visual decoder, where you can just position the "flags" the way you want it to.I couldn't find anything on the web that did this, not even close.So, I made it.
My first attempt at making this was pretty questionable in hindsight.About a year ago I found out about the $1 Unistroke Recognizer from Brad Myers' course 05-640 Interaction Techniques at CMU.It's seriously impressive - the demo on the linked site is incredible, especially when you consider that it's trained on only one example of each gesture.
So anyways, I suddenly remembered the existence of the $1 Unistroke Recognizer and thought, perfect!I'll be able to just use my mouse and squiggle an angle that vaguely resembles the semaphore position, and then it'll magically machine-learningly decode it for me.I tried it on a couple user-defined semaphore positions and it worked!
So then I made a copy of the code locally so I could persist the templates of each letter.I threw one example of each letter in and tested and... found the results were confused.Flag positions that looked nothing alike were being mistakenly recognized!Even worse, these matches had high confidence.I went back to the original paper and found a clue:
Ah! Normally this is useful: it means that you can draw a gesture at a different angle, size, and position from the template and still have it be recognized.But in my case, every single semaphore letter is a rotation of another one.We actually don't want any of these invariants, so I spent some time digging in the code to try and disable this.
After doing that and adding a few more examples for each letter, accuracy improved, but it was still far lower than you would want for a computer-aided puzzling tool.The results were especially wrong for D, R, and L, which are all straight lines - I believe this is related to the "indicative angle" step in the $1 paper, which relies on the centroid of the stroke and the starting point.It's pretty hard to draw a straight line, so this metric varies wildly for straight-ish strokes that, to the human eye, look very similar.
I showed these results to Johnny as well as my roommate Ishraq, and they said almost identical things, to the effect ofCool demo, except for the part where it doesn't really work. Why are you trying to machine learn your way out of a simple problem like this? Isn't this a really small space with a small set of fixed positions?And... they were right.So I scrapped this approach -someday I'll have a legitimate reason to use the $1 Recognizer, someday -and went back to rethink the idea.
A semaphore letter (from here on shortened to "semaphore" for brevity) consists of exactly two distinct flag positions.There are only eight possible flag positions, set at 45 degree angles from one another.Since there are so few valid positions, I realized these can just be clickable elements.Once the user clicks two flag positions, it's just a (painless, computerized) table lookup to find which letter it represents.
I've written some vanilla Javascript before, but I thought it would be a little tedious to add all the event listeners, keep track of state, and so on.I've also written a decent amount of React, but I didn't want to deal with setting up webpack or Babel or whatever for this little project.So I figured this would be a good opportunity to try and learn Vue -I glanced over the Vue introduction and liked what I saw, copy-pasted the starter code, and got started.
I wrote the basic implementation in about a day and cleaned it up in another half-day.There's just two Vue components: a flag, which represents a possibly-selectable flag using a clickable div with an position (angle) property;and the semaphore-decoder, which creates eight flags at the appropriate angles, handles the flag click events, and displays the results.
Writing Vue is a great experience.The concepts of v-bind and v-model are easy to pick up, and the documentation overall is very helpful (and searchable, too).I only needed to go to non-official documentation once, to figure out how to attach a global keydown listener (spoiler: you can't, but there's decent workarounds).And I was slowed down a bit by simple syntax errors since I was writing in a single file with string templates, so no IDE support.
But those are both pretty trivial issues.I spent a lot more time messing with CSS to get the flags to display correctly than I did with any Vue issue.I'll definitely use Vue again the next time I have a small project like this.In particular I really like the $emit event mechanism for communicating from child to parent -it feels a lot more natural than passing callbacks in React: like throwing an exception versus passing a continuation.
After the basic implementation, I had a single-letter semaphore decoder - great!But most of the time you actually need to decode several letters at a time.Clicking twice and writing the letter down on paper, or worse, alt-tabbing to type the letter, didn't seem like a good choice.So, I added a text buffer that you can push letters to by hitting the Enter key.
Originally this text buffer was a simple div, so you couldn't change the results after they were pushed.There did exist a Clear button to empty the buffer entirely, but losing progress is never fun.Johnny pointed out that it would be a lot better if it was a text input box, so you could just manipulate text in the normal ways: hit backspace to delete a single character, select-all, copy-paste, and all that.
There's also a quick link to anagram your current result on Nutrimatic, a powerful pattern-matching tool.Often the letters in the semaphore message will be jumbled, and you need to use additional information to get the correct ordering.However, it's often faster to just directly anagram the letters.Using Nutrimatic also synergizes with the textbox, because you can use Nutrimatic's pattern language syntax: like capital A (to represent any letter), if you're unsure of a symbol - and that will be anagrammed as well.
With some more testing, I found that using Enter to add a letter to the input box had an issue:if you had the "Clear" button focused, hitting Enter would write the letter, and then trigger the clear event and immediately delete it.I initially changed the button to be a div styled to look like a button (which can't be focused), but realized later that this problem is unavoidable for links.So, I moved the button to be . (period) instead, since you might want to type actual letters, and modifier keys like Ctrl and Shift resulted in weird interactions like accidentally double-adding the current letter if you Ctrl-Tab to a different tab.
Finally, I tried the site on mobile, and it was hot garbage.The flags were too thin to click on, and the text was all tiny.So I spent some time making it usable on mobile, starting with Skeleton and eventually just setting a pretty small max-width on everything, even on desktop.After I got it working on the Responsive Design Mode in Firefox DevTools, I spun up a local webserver (python -m http.server) and pulled up the site on my phone... only to see a different flavor of hot garbage. At least it was new and exciting garbage.
Now all of the flags were unreasonably tiny and unclickably close together, for some reason.I wanted to pull up DevTools on my phone, but apparently you can't do that at all.Luckily, it turns out you can do remote debugging over USB on Android!I tried it, and it turned out my mobile Firefox version was too old (68.6.0) for my browser (76.0.1) to remote debug.Then, I installed Firefox Nightly on my phone, fired it up, and found... that the site rendered correctly?And then I had nothing to actually remote debug, which is too bad.
So, the site is currently broken on Firefox on Android (at least, on my phone).I think the root cause is probably related to how CSS viewport units are handled, since that's how the flag size is calculated.But there's reason to believe that it will work in a few browser updates since it works on Nightly.
4a15465005