A Word Cookies Worksheet App

My girlfriend and I like to play a word-search game called Word Cookies!®, by BitMango. The game presents a collection of letters and an empty list of words of various lengths. It’s your job to assemble the available letters into dictionary words that fill in the blanks. On the screen, the words are ordered by length and then alphabetically, with one exception: the biggest word (usually that uses all the available letters) is displayed at top of the screen.

The game also has a Daily Puzzle, which adds an additional challenge: The game chooses a word randomly, and you need to guess that particular word, in that particular slot in the list, in order to get points. To solve these puzzles, it’s not enough just to guess any word in the puzzle. You need to guess the exact word the game has randomly chosen.

We’ve developed a method to maximize our success with these puzzles: We start by writing down as many words as we can, sorted by number of letters and alphabetically, as in the game. We usually do this on a piece of paper or in a notes app. Then as we guess each word in the level, we write down next to it the number of the slot in which it appeared in order on the screen.

But more than once, we’ve accidentally mis-alphabetized a word and missed a point because of such silly mistakes. It occurred to me that I could develop a simple application to help us assemble these lists, and eliminate the risk of human error.

(You can see my work in my Word Cookies Worksheet repo on GitHub.)

First steps

For a framework, I chose React. I could have used almost any framework and platform. There are about a million twenty-three suitable ones. Anything with a GUI library would have worked. But I had experimented with React a bit, and I actually don’t hate JSX as a templating system. I also knew I could easily deploy it to a static web server and then run it on any browser, whether on a laptop, smartphone, or tablet. I could use web local storage to persist state, and that would work well enough.

I did not start with Flux or Redux. Rather—after running Create React App and adjusting its output—I started, as I always do, with the core of the app, the data model.

Building the data model

I needed a data store that could store a list of words and associated tags. I needed to add and remove words, and add and remove tags on words. And I needed to sort the list by number of letters and then alphabetically, as in the game. And I needed to display the assembled list.

Building it felt like an episode of Uncle Bob’s The Craftsman series, and that’s as it ought. I created a WordStore module and its associated test module, and I began with a simple unit test:

test('new data model has no words', () => {
    let wd = new WordStore();
    expect(wd.words).toEqual([]);
});

The variable name wd was originally a shortened version of “word data,” as it’s the data model. I eventually settled on store for the data-model class, as opposed to data for the records contained therein. However, I didn’t update the variable name in the unit tests. Oops. In future revisions, the WordStore class may be further refactored into separate pieces to handle data storage and business logic—like Redux does—but we’re not there yet, and I don’t even know whether this application will ever be complicated enough to justify that complexity.

Getting this test to pass was straightforward enough:

class WordStore {
    get words() {
        return [];
    }
}

Through simple test-driven development, I worked up to a suite of tests and corresponding behaviors. The data model adds, deduplicates, and sorts words. It can also remove, tag, and untag words. And all words are uppercased before being processed, and the data model returns all words in uppercase form.

What surprised me most is that the core data structure of the class is simply an unsorted array that contains a WordData object that looks like {word, tag} (where tag is optional). Each new word is pushed onto the end of the array. Tagging and untagging an existing word involves manipulating the data stored in the WordData object.

Before I had set about writing this code, I had been playing with various data structures in my mind: a write-only event-log, maps to make fetching all the N-letter words fast, a sorted list or tree. But the list of words is always going to be short: a few dozen at most. Really, how efficient does the code need to be? How clever and convoluted does it really need to be? The way it’s implemented now, it actually sorts the words into the order (by length, then alphabetically) every single time the list is queried. And there’s only one way to query the list, that is, to get the entire sorted list. And that seems to work just fine. How the words are sorted, that’s one of the rules of the game; but I’m leaving it up to the presentation code to split up word groups by length or to make any other layout choices.

If I were to make one optimization, it might be to sort the words as they are being added to the list, rather than on retrieval. That’s a simple refactoring to make, as I fortunately have a complete unit-test suite.

A prototype user interface

I also created a prototype UI—messy, awkward, no unit tests—just to exercise the data model. It turned out that the UI code needed a clone of the WordData, and I decided to put that into the model in the words accessor. But now that function is doing too much: it’s retrieving, sorting, and cloning the data. (And the cloning feature is not yet official, as it’s not yet unit-tested!) So that refactoring I mentioned above is looking better, and this is the next story on this project.

But the prototype UI gave us enough to try out the app. It works quite well, and I have some ideas for where to go on it, based on how we’re already using it.

There was one issue: When entering a word using auto-complete on a smartphone or tablet, the string can have spaces appended. It occurred to me that the data model needs to trim each word of whitespace before processing it, so I addressed that in a separate commit.

Next steps

As you can probably tell, I’m using a simple Agile (XP-style) development process, encompassing principles like Do The Simplest Thing That Could Possibly Work and You Aren’t Gonna Need It. (See also Martin Fowler’s treatment of YAGNI.)

Right now, whenever you refresh the web page, the data model reinitializes, and the list of words disappears. After I refactor the data model, as I mentioned above, I want to have it trigger an event whenever the data is updated. This event can be used by the UI to rerender the list of words (which will come in handy as the UI becomes more complex), but it can also be used to save the words to web local storage. Add a feature to initialize the data model with a previously saved list, and now the app state can persist across refreshes.

Another missing piece: I haven’t yet set up CI/CD. Because this project is so simple, it is enough to run the tests and build in my IDE and to deploy manually using rsync. However, that also means setting up CI/CD is incredibly simple.

This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

66 Responses to "A Word Cookies Worksheet App"

Leave a reply