I launched the Corn Maze app for the Crow River Winery for 2026 (year 4) in September. There was no corn maze in 2025, but I’m happy to have it back this year.
Background
If you are unfamiliar with this project, I have a family connection to a winery in Hutchinson, Minnesota. Most years, they do a corn maze in the fall. Many years ago, I had the idea of creating digital games to play while exploring the corn maze. You can read previous blog posts about this project from 2022 and 2024.
QR Codes
The biggest change to the whole setup is how I’m handling the QR codes. One of the choices I made with my initial design was to have QR codes that were just a random (short) string, something like maEIM4Mmw. It didn’t mean anything, and I just had a list of these strings matched up with a “waypoint” in the maze. This turned out to be a huge mistake. As I observed the players trying to use the QR codes, they kept trying to scan them with whatever camera app they had on their phone. Of course, that didn’t work. They had to use the QR code scanner in the Corn Maze app. Figuring that out was more than the casual Corn Maze explorer could be bothered to do.
It took me a while of surreptitiously listening in on people in the maze before I figured this out, but once I did, it was one of those forehead-slapping moments.
My thinking at the time was that I needed to make the QR codes kinda small. I wanted them to be easy to scan, and I figured encoding a smaller amount of data would be worth it. A full URL would be three times longer. If the squares in the QR codes were smaller, people would have to get the camera closer to the QR code and maybe there would errors. I was wrong. Phone cameras are really good at reading QR codes.
The physical nature of the corn maze is part of what makes this project so much fun. But it does mean there are real limitations in what you can do, and you have to commit to things like the design of the maze and waypoint signs. This year, we got new waypoint signs, which let me correct the errors of the past.
Now each QR code is a full URL (unique to that waypoint), and it works whether you scan it with the Corn Maze app or with whatever built-in QR code scanner comes on the phone.
QR codes are also just a better tool than they used to post pandemic.
NFC Tags
Another thing I finally got working this year was NFC tags (as an alternative to QR codes). Is it a solution in search of a problem? Perhaps. But I’ve wanted to do a project with NFC tags for a while now, so I went ahead with it. The NFC tags have the same URLs as the QR codes, so they’re an alternative. We’ll see if I notice anyone actually using them. I’m not sure if it’s a nicer experience to take that extra step to get close enough to the sign to scan the NFC tag, or to hold up the phone and aim it at the QR code.
The NFC tags also had a bit of a learning curve. The first ones I bought were not designed to work when attached to metal signs. I had to buy special NFC tags with an insulator so they still worked when attached to metal signs.
Native Apps
This year I made native apps for iOS and Android. Why would I do this? Three versions of the software? I’m already complaining about how I don’t have enough time. This seems like a bad idea.
Well, it might be, but it has been fun. I got inspired to go down this road for two reasons. First, while Android does support the PWA APIs for NFC tags, iOS does not. If I wanted all users to be able to use the NFC tags, I would need a native app. This turned out to be only partially true, given how I set up the URLs, but the point is that I thought I needed to. The second reason was that I was getting more and more comfortable with LLMs, and it’s really cheap, both in terms of time and money, to just ask an LLM to build something and see what happens. I pointed the LLM at my existing web app’s codebase, and it quickly had an app that was already feeling like a better user experience than the web version.
Of course, I still got burned by the process. The building of the apps was fun. The App Store approval process was not very much fun. It was educational though, and hopefully what I learned will help the next app approval process go better.
Ink Engine
One of the highlights of this spring’s Minnebar was a talk I went to by Brent Anderson that gave a great introduction to the Ink Language. The Ink was something I had looked at for the Corn Maze app before, but I don’t think I “got it”, and passed on it thinking it was not a good fit.
After getting a handle on what it was good for, I wanted to use it for the Corn Maze app. However, I didn’t want an interpreter or more languages in my Elm app. From my years of listening to Elm podcasts, I had the idea of “code generation” in the back of my mind as a possible option here. And with these LLMs at my disposal, I figured I should run an experiment.
I have been learning OCaml in fits and starts over the past few years, and I have heard it is a great language for parsing. So I started building an OCaml command-line tool that would parse Ink stories and generate Elm code. It took longer than I thought; I had to iterate a bunch to get the Ink stories modeled in Elm the way I wanted them to be. You could tell the LLMs just wanted a bunch of “strings”, but the whole point was to leverage the Elm type system, so back and forth we went.
The LLM was good at writing lots of Ink example stories that tested various parts of the Ink language, so we had a very nice set of tests pretty quickly. I also wrote a “command-line” engine for running (playing) the transpiled stories (well… I guess… they were transpiled twice: Ink -> Elm -> JavaScript), so I could just play them, and we could have end-to-end tests too. We ended up with:
- parser AST OCaml tests
- Elm tests
- End to end tests
Extending Ink
As I got deeper into the Ink language and actually started implementing the existing trivia/games from previous years in Ink, I realized there was another good reason to use code generation: I wanted to extend the Ink language. For instance, Ink stories just keep going until the player reaches the end. That makes a lot of sense. But in the Corn Maze, I need to stop the story when the user reaches the end of the content at a “Waypoint.” Then the story continues when the user arrives at another “waypoint.” The Ink concept of a “knot” mapped pretty well onto the concept of a “Waypoint,” but it needed some extra stuff to flag it as a “Waypoint” and a new keyword to signify that the “Waypoint” was done but the story should continue.
I still need to sit with the ways I’ve extended Ink a bit longer. They seem like “safe” extensions, but it’s possible there are more elegant approaches I could take.
Ink Code Generation Status
What I have now is not a “full” implementation of Ink. It’s mostly just the features I’ve needed (or thought I needed) for the games I’ve written so far.
I’m also not quite sure what the boundaries should be in the generated code. What I think I want is all the content and logic in the generated code—all the types and lots of helper functions—but to leave the “engine” out of it, or at least the part of the engine responsible for displaying the game.
So far, it hasn’t been that important to have a “stable” code-generation output, because the LLM I’ve been using has been pretty good at adapting to changes in the generated code. But eventually, I will want the generated code to be pretty stable.
Supporting Multiple Languages
Elm was a good language to start with because its strong type system helped hammer things into a correct shape. However, if I wanted an iOS app, I wanted the tool to also generate Swift code, and for the Android app, I wanted it to generate Kotlin code.
Once I had the Elm code generation working well, adding the additional languages (with LLMs) went pretty smoothly. I just told the LLM to add all the same tests in all the target languages. I don’t know Swift and Kotlin well, especially not well enough to be able to say what “idiomatic” code should look like in those languages. I need to spend some more time learning those languages and reviewing the generated code. However, when I brought the generated code into the iOS and Android apps, it seemed to be integrated without too much fuss, so I took that as a good sign.
A New Game for 2026
I started working on several new games this year, and hopefully I will get another one or two “done” before the maze closes this year, but I did get one over the finish line.
It started as a very simple “Discovery” game, basically just a checklist for visiting all the waypoints in the maze. I turned that into a game for learning about cold-climate grapes, where every waypoint shows a little bit about a different variety of grape. It’s not the most exciting thing in the world, but it was also easy to make.
The Future
I feel like I’m on pretty good footing with the apps this year. Now that the iOS app is live and the Android app should be soon, releasing new versions before the start of the maze next year should happen a lot more quickly. I already shipped an update to the iOS version of the app and the approval process was a lot quicker.
Ink is a very powerful engine, and I have a lot of ideas for games I want to make with it. I don’t think I will need to spend much time in the future building the engine and I can focus on making the games themselves. But as always, we’ll see, there may still be some fatal flaw my design for all this.