Home | Updates | Now | LinkedIn | GitHub | Use of AI

A thousand-year-old game and a thirty-year-old trick

By Magnus Hultberg • 27 May 2026

Last edited: 27 May 2026

Hnefatafl (pronounced "NEF-ah-tah-fel") is an ancient Norse strategy game. The board is an 11×11 grid, with twelve defenders and a king trying to escape to the corners, and twenty-four attackers trying to surround the king before he can get there.

Jarl Rognvald listed mastery of it among his nine great accomplishments. The gods themselves play with golden pieces in the Poetic Edda. Carl Linnaeus documented a Sámi variant on his 1732 expedition to Lapland, played on embroidered reindeer hide with pieces called "Swedes" and "Muscovites" - a nod to Sweden's rivalry with the Grand Duchy of Moscow. The Vikings absolutely loved this game.

I've known about it for years and never tried to play it. The other day I realised I could just build it. So I did. A weekend of reading about ninth-century Norse warfare later, plus a fair amount of Claude Code, and here we are. You can play the game right here: hnefatafl.hultberg.org

Three things stood out from the build, and the third one is what I found the most interesting.

building-hnefatafl-doom-trick

Going all-in on Cloudflare was harder than I expected

The plan was a clean stack: one supplier, no recurring cost. Cloudflare Workers and Hono for the app, a D1 database for any state I needed to keep, KV store and Email Sending for the magic link sign-up flow (instead of relying on Supabase and Resend, two great services I have used in the past).

The first four were fine, and familiar territory by now. Email Sending took far too long to sort out.

Turns out Cloudflare Email Sending doesn't deal with sender emails on sub domains without a lot of complicated setups (if at all, I still don't know), and testing on your own email that's verified in the Cloudflare setup masks that entirely... We got there in the end.

Lesson: don't mess with sub domains if you're going natively Cloudflare for sending notification emails. But do use it, once it works it's great. Would have saved a lot of time and frustration debugging and clicking around the Cloudflare dashboard if I'd known this before I started.

A game is not your average web app

This might be obvious. It is. I just didn't really think through the implications before I started.

Most web apps can afford a few steps of onboarding to get to the action. Users have a job to do, they'll tolerate friction if the up front value is clear. A game is different. You can't tell if something is fun until you've tried it. If the first interaction is a sign-up form, why bother? If the rules tutorial is six paragraphs long, same outcome.

So I had three things I wanted to nail.

Get them playing fast. The home page is "click here to play". No account, no preamble, no friction. The board appears, with cues on what to do to get started, and the rules tucked behind a button for anyone who wants them.

Make the game feel good. Most of the implementation time went here, and the next section is about one specific piece of it.

Engineer a sign-up moment that doesn't feel like an interruption. My thinking is that the right moment is right after a game ends, win or lose. The player is invested, they finished a round. Presumably they want to remember their score, and try to beat it. Offering to save their progress in that moment lands very differently from asking up front.

None of this is novel. Game designers must have known this for decades. I'd just never built a game before, so I had to think about it.

The fun bit: making flat images look three-dimensional

The pieces needed to look good. Illustrated, characterful, not just coloured circles on squares. The board renders with commonly available web techniques, and properly modelling a 3D Viking warrior in Blender or some other 3D tool I'd have no idea how to use is a lot of work. It's also expensive to render thirty-something pieces of that complexity on a phone.

So I asked Gemini to generate the artwork instead. Four images of a warrior viewed from four directions, four images of the king. They came out beautifully I think: full-bodied characters with shields and helmets and visible personality, the sort of thing a Norse jarl might recognise.

Which leaves the actual problem. How do you make a flat image look three-dimensional without doing any 3D modelling?

Claude pointed me at a technique called cross-plane billboarding. The Doom engine used a version of it in 1993 to render enemies as sprites that always faced the camera.

The gist: instead of building 3D geometry for the warrior, you make two flat rectangular planes and arrange them in a cross, like a plus sign viewed from above. One plane carries the front and back images. The other rotates 90° and carries the left and right images. Because the planes intersect at right angles, no matter which angle you look from, you always see two faces at once. The overlapping silhouettes read as volume. The brain does the work, the device barely breaks a sweat.

Two small tricks make it work. The illustrations had white backgrounds, so a tiny bit of shader code tells the device to skip any pixel that isn't part of the warrior. The same shader can also be used to apply a colour tint, so a single set of artwork covers both ivory defenders and wooden attackers.

The characters also don't sit dead-centre in the source image; their feet land near the bottom. Without correction the pieces appear to hover above the board. A small offset fixes that.

That's the whole technique. Eight PNG files, two crossed planes per piece, one shader, one offset. Runs smoothly on my phone.

The thing I would never have arrived at on my own is that old video game technique. I asked Claude how to render attractive-looking pieces on a constrained device, and what came back wasn't some modern fancy rendering engine or 3D modelling tool. It was a trick from the early 1990s. The Doom engine did it. And the original Tomb Raider trees, apparently. So did countless arcade cabinets. I didn't know any of this before I started. Claude told me. What made Claude suggest it... I have no idea.

Side note: Doom almost got me expelled from my university. True story.

Three things stuck with me

I started this project wanting to recreate a piece of history. What kept me going was the joy of learning. And ending up using an old graphics trick from 1993 is what I found most interesting.

There's a curious thought there. When you reach for the newest framework by default, you might miss older solutions that map better to constrained time and constrained tools. The shader trick wasn't clever modern engineering. It was game development folklore that Claude offered up when I described the problem clearly (and probably whined a bit about wanting to stick to simple means that don't explode my little brain).

Who would have thought. A thousand-year-old game made to look awesome by borrowing a trick from a thirty-year-old one.

Home | Updates | Now | LinkedIn | GitHub | Use of AI