← Back to writing

8Words: LLM Generated Puzzles

Following on from my post about turning 8Words into an editorial game I set about actually implementing it. It's extremely simple, but its my first LLM-enabled application so I'm documenting it.

A thought I have on LLM-driven applications is that there are at \least two kinds:

  • LLM-enhanced applications where the application is fairly similar to things we’ve seen before especially in terms of what it can do and what it’s used for.
  • LLM-enabled applications on the other hand are use cases that were previously impossible or prohibitively expensive now accessible thanks to this technology.

I’ll caveat my categorisation by saying that different parts of the application can have different paradigms here. For example, a simple CRUD application can be LLM-enabled by having users interface with it via natural language.

I think this distinction is useful for deciding how to approach building the application. It determines where in the stack the LLM sits as well as when and how it is called by the surrounding application. Without it, especially to a humble CRUD engineer like me, it’s easy to fall into a trap of just thinking about it as a black box, especially in cases where it doesn’t need to be.

Overview of 8Words

So, let’s talk about what we are building here specifically. The target state is a simple game where the player can see a number of puzzles and pick one to solve. The puzzles are straightforward, the player receives the first word and then has to guess successive words, each linked to the last.

The architecture is fairly simple: client, server and storage.

Client.Server.Storage.png

A simple data model as well to support it. Each puzzle is stored as a word_set which has attributes like name, a comma separated list of words and a published date.

There are two endpoints on the server:

  • GET word_sets that returns an array off word set summaries (ie containing the title and published date)
  • GET word_sets/:id which returns a specific word set in full, with all the information needed to play the game.
  • A small note here, I have this endpoint return all the words in the word set. I might change it in the future, since it means people can “cheat” by inspecting the network request. On the other hand, it’s just a silly game, who cares?

The client consumes those endpoints and uses them to drive all the gameplay behaviour

  • Show user list of puzzles, user selects one
  • Show first word in puzzle and show first letter of next word, hide others
  • Keep track of number of guesses, on each wrong guess, reveal a random previously hidden letter from the current word
  • Game ends after 6 incorrect guesses, player has lost or after all words have been guessed

That’s the entire game, nothing for the LLM to enhance or enable. Well, not in the player facing side anyway.

Games like this are what I call “editorial games”. To recap, an editorial game requires new levels or chapters to continually be drafted. The game state can’t be reset lie you would in classic board games like Monopoly or Chess. The levels aren’t dynamic with secrets that may be uncovered with replays like Mario. When a level is cleared, unless you forget, you’ll get no thrill from redoing it. I’ve called them editorial games because the examples I could think of, like a crossword puzzle, were typically in editorial publications like a newspaper.

When I first thought about building games, I was wary of ones that would need my continuous curation in that way, since I imagined I would grow tired and abandon it in no time. I admire people who are able to, but I didn’t think I had the attention span or discipline for it. Cassidy Williams, whose newsletter I read, published a Jumblie puzzle everyday for a whole year before eventually stopping. In her post, she talked about needing a human in the loop because of the constraints of the game. For 8Words, it’s a much simpler premise and so, hopefully, today’s models should be up to the task of being our publisher.

LLM.Edits.png

So, to the client, server and storage we now add a worker. This worker is a simple background task running on a timer, which connects to an LLM provider with a prompt to return some structured data that fits the game’s rules, perform some validation on that data and then inserts it into the database.

And that’s our application, not novel or bespoke, the worker inserting new puzzles could have been an admin interface where I wrote new puzzles as I came up with them. But this enhances that design so that when I inevitably abandon this project, there’ll still be puzzles to play for anyone who wants to.