If you lack the imagination to understand how a game like this could have dozens of possible endings, you desperately need to play it. My favourite path so far through the game was to
add a teabag, then hot water, then remove the teabag, then add some milk, then add a second teabag, then drink it.
Genuinely can’t stop laughing at this masterpiece.
My favourite video game Easter egg is found in Ultima Underworld II: Labyrinth of Worlds1.
Released early in 1993 after missing a target of Christmas 19922,
it undersold despite being almost universally well-received by reviewers3.
Developed by Looking Glass Technologies, it used an enhanced version of the engine they’d used for the game’s prequel a year earlier4.
The engine is particularly cool for it’s time; it’s sometimes compared to Wolfenstein5, but
that’s not entirely fair… on Wolfenstein! The original version of Underworld‘s 3D engine predated Wolfenstein… and yet supported several features that
Wolfenstein lacked, like the ability for the player to look up and down and jump over chasms, for example.
The team’s expertise and code would eventually be used to produce System Shock in 1994. The team’s producer, Warren Spector,
would eventually draw from his experience of the Ultima Underworld games when he went on to make Thief: The Dark Project and Deus Ex.
But the technology of Ultima Underworld II and its prequel aren’t as interesting as its approach to storytelling and gameplay. They’re:
What’s being described there is what we’d now call emergent gameplay, and while it wasn’t completely new in 19937 it was still uncommon enough to be
noteworthy.
The Easter Egg
The Ultima series are riddled with Easter eggs, but my favourite is one that I feel is well-hidden, beautiful… and heavily laden
with both fan service and foreshadowing!
To find the Easter egg, you must first travel to Anodunos. This city was once the capital of a tropical city-state which had become allied to the Guardian, the the principal antagonist
of Ultima VII through IX.
After the city’s major, Beatrice, attempted to put an end to the red titan’s growing demands, the Guardian cursed the city fountain to radiate out a magical cold that eventually froze
the entire settlement under a cave of ice.
On the Eastern bank of the city’s river we find the remnants of the workshop of the magician Alorik, and in it – if we look in the right place8
– a secret door. We can’t open it though: unusually for a secret door in this game, it’s locked.
I didn’t even find this chamber on my first playthrough of the game. It was only on my second, while using the Map Area spell to help me to draw accurate maps of the entire game world,
that I found the room… and even then I spent some time hunting for a switch on the “outside” before eventually giving up and teleporting into the secret room.
There’s valuable treasure here including a sceptre of mana restoration, a “grav” runestone (probably still easier to get than the one at the Scintillus Academy), but what’s most
interesting is the crystal ball, which the player can look into to see a vision of another place and, in the case of this orb, another time.
The first time you look into it, you’re told:
You see yourself striving against the forces of Mondain and Minax in the lands of ancient Sosaria.9
Mondain and Minax are the antagonists in Ultima I and Ultima II. We’re seeing the earliest parts of the player character’s adventures.
If we look into the crystal ball a second time:
You see yourself climbing to the peak of Olympus Mons on the planet Mars.
This is a reference to the plot of Ultima: Worlds of Adventure 2: Martian Dreams… which is a… weird choice of game to reference.
In my mind, a more logical leap forward in time might have been to jump to Ultima IV10, in which the protagonist
first becomes the Avatar of the Eight Virtues and the Hero of Britannia. Martian Dreams is… a sequel to a spinoff of Ultima VI. So why pick that?
Martian Dreams starts with a friend of the Avatar’s from Earth facilitating the Avatar and their companions to set out on an adventure to the planet Mars. That friend is called
Dr. Spector, obviously named for Warren Spector, who helped develop Ultima VI and, of
course, this game. This usual choice of vision of the past is a cryptic nod to the producer of Underworld II.
Let’s look again:
You see yourself in the Deep Forest, speaking with the peace-loving simian race of Emps.
This one’s a reference to Ultima VII, the game whose story immediately precedes this one. The Deep Forest seems an strange part of the adventure to choose, though. The Avatar
goes to the Deep Forest where, via some emps and then a wisp are eventually lead to the Time Lord11.
The Time Lord provides a whole heap of exposition and clues that the Avatar needs to eventually close the Black Gate and win the game.
Do these references serve to hint that this crystal ball, too, is a source of exposition and guidance? Let’s see what it says next.
You see yourself peering into a crystal ball.
I remember the moment I first saw this happen in the game: serious chills! You’ve just found a long-lost, centuries-buried secret chamber, in which there’s a crystal ball. You peer into
it and observe a series of moments from throughout your life. You continue to watch, and eventually you see yourself, staring into the crystal ball: you’re seeing the present. So what’s
next?
If you look again, you’re asking to see… the unwritten future:
You see yourself winding a great war horn in the throne room of Castle British.
To save Britannia in Ultima Underworld II, the Avatar needs to exploit symmetries implicit in The Guardian’s spellcasting to travel to eight different parallel worlds, find a
place from which His power stems, dispel it, encase themselves in a shell of basilisk oil-infused magic mud, immerse themselves in lava to bake it on, find a magic sigil, consume a
djinn… it’s a whole thing. But ultimately it all leads to a climactic end scene in which the Avatar raises a horn retrieved from the Tomb of Praecor Loth and blows it to shatter a dome
of blackrock.
If you happen to find this clue on your first playthrough, it’s helpful exposition.
But that’s the end of this game, right? How can we possibly peer into the orb again?
You see yourself sailing through majestic pillars cropping up out of the sea, on a voyage of discovery.
What’s being described there is the opening scene from the next game in the series, the as-yet-unreleased Ultima VII Part 2: Serpent Isle!
This vision is a teaser of what’s to come. That’s just… magical, for both the character and the player.
The character uses fortune-telling magic to see their future, but the player is also seeing their future: if they’re playing Ultima Underworld II at or close to its
release date, or they’re playing through the games in chronological order, they’re in a literal sense being shown what comes next in their life. That’s really cool.12
Let’s look again:
You see the obscure form of an old and dear friend, as he sacrifices his life for the good of all.
Some time after the party arrives on Serpent Isle, the Avatar’s companions are possessed by the Banes of Chaos and go on a murderous rampage. Later, there’s a ritual that will save the
world, but at the cost of the death of one of the heroes. The Avatar is willing to make the ultimate sacrifice, but in the end Sir Dupre takes his place, unwilling to live within
himself after seeing the carnage he has wrought.
At the end of Serpent Isle, the Avatar is plucked out of space and time and deposited into Pagan, The Guardian’s home base. The plot of Ultima VIII and Ultima
IX revolve around the Avatar working to return to a radically-changed Britannia, attempting to fight The Guardian and bring to an end the Age of Armageddon, and ultimately
merging and become one with Him before vanishing completely from the world.
Which is why it’s perhaps quite fitting that if the Avatar in Underworld II looks into the orb one final time, they’re told…
You see nothing.
That’s it. That’s the end.
The end of the vision, certainly, but also: a vision of the end.
Depending on how you count the Ultima games13, this is the 13th of 17 in the series. We’re approaching the
final chapter, and this Easter egg foreshadows that finale.
I feel hugely privileged that I got to experience it “organically”, by accident, as its authors presumably intended, back in 1993. But it also makes me happy to be able to share the
story of it with you14.
If you haven’t seen it yet, you might enjoy watching the vlog version of this post, through which my enthusiasm for the topic might be more-palpable.
Footnotes
1 I’ve doubtless mentioned Ultima Underworld II before: for example both it and
Ultima VII, as well as NetHack (mentioned elsewhere in this post) made it into my 2007 list of top 10 computer games that stole my life.
3 It suffered perhaps for the time of year it was released, but perhaps also for the fact
that 1993 was a big year for video games and it was competing with The 7th Guest, Star Wars: Rebel Assault, Return to Zork , Myst, Disney’s
Aladdin and, of course – later in the year – Doom.
4 Director/designer Paul Neurath apparently sang the praises of his team for improving
texture mapping and viewport size constraints, and he’s right: they’re a huge improvement on Underworld I‘s. Neurath would later go on create the crowdfunded “spiritual
successor” Underworld Ascendant, which was critically panned, which just goes to show that sometimes it’s better to get a tight
team together and make it “until it’s done” than to put your half-baked idea on Kickstarter and hope you can work it out what you’re making before the money runs out.
5 Like Wolfenstein, the engine uses a mixture of software-rendered 3D (for walls
and furniture) overlaid with traditionally-produced sprites (for characters and items).
6 All executed over a year before the release of the very first Elder Scrolls
game. Just sayin’.
7 That king of emergent gameplay NetHack was showcasing emergent gameplay in
a fantasy roleplaying game way back in the 1980s!
8 An interesting quirk of the game was that if you turned the graphics settings down to
their lowest, secret doors would become just as visible as regular doors. If you’re sure there is one but you can’t quite find it, tweaking your graphics settings is much easier than
casting a spell!
9 Do you like the “in the style of Underworld II” scrolls I’ve used in this post?
I’ve made available the source code you need
if you want to use them yourself.
10Ultima IV is my personal favourite Ultima game, but I see the
argument of people who claim that Ultima VII is the best of the series.
11 The Time Lord turns up throughout the game series. Way back in Ultima III,
he appears in the Dungeon of Time where he provides a clue essential to defeating Exodus, and he appears or is referenced in most games from Ultima VII onwards. He doesn’t
seem to appear in Ultima IV through Ultima VI, except… in Ultima IX, which wouldn’t be released until six years after Underworld II, it’s revealed
that the Time Lord is the true identity of the seer Hawkwind… who provided the same kind of exposition and guidance in Ultima IV!
12 How did the Underworld II team know with such certainty what was being
planned for Serpent Isle? At some point in 1992 project director Jeff George left Origin Studios and was replaced by lead designer Bill Armintrout, and the role of producer was assigned to… Warren Spector again! For some time, Spector was involved with both
projects, providing an easy conduit for inter-team leaks.
13 How you count Ultima games and what specifically should be counted is a
source of controversy in fan circles.
14 I’m sure many people reading this will have heard me talk about this particular
Easter egg in-person before, over the last couple of decades. Some of you might even have heard me threaten to write a blog post about it, someday. Well: now I have. Tada! It only
took me thirty years after experiencing it to write about it here, which is still faster than some things I’ve blogged about!
Do you play in your daily life? What says “playtime” to you?
How do I play? Let me count the ways!
RPGs
I’m involved in no fewer than three different RPG campaigns (DMing the one for
The Levellers) right now, plus periodic one-shots. I love a good roleplaying game, especially one that puts character-building and storytelling
above rules-lawyering and munchkinery, specifically because that kind of collaborative, imaginative experience feels more like the kind of thing we call “play” when
done it’s done by children!
Videogames
I don’t feel like I get remotely as much videogaming time as I used to, and in theory I’ve become more-selective about exactly what I spend my time on1.
Board Games
Similarly, I don’t feel like I get as much time to grind through my oversized board games collection as I used to2,
but that’s improving as the kids get older and can be roped-into a wider diversity of games3.
Escape Rooms
I love a good escape room, and I can’t wait until the kids are old enough for (more of) them too so I’ve an excuse to do more of them. When we’re not playing conventional escape rooms,
Ruth and I can sometimes be found playing board game-style boxed “kit” ones (which have very variable quality, in my experience) and we’ve
recently tried a little Escape Academy.
They’re not the only satnav-based activities I do at least partially “for fun” though! I contribute to OpenStreetMap, often through the
“gamified” experience of the StreetComplete app, and I’m very slowly creeping up the leader board at OpenBenches. Are these “play”? Sure, maybe.
And all of the above is merely the structured kinds of play I engage in. Playing “let’s pretend”-style games with the kids (even when they make it really, really
weird) adds a whole extra aspect. Also there’s the increasingly-rare murder mystery parties we sometimes hold: does that count as roleplaying, or some other kind of play?
Suffice to say, there’s plenty of play in my life, it’s quite varied and diverse, and there is, if anything, not enough of it!
Footnotes
1 I say that, and yet somehow Steam tells me that one of my most-played games this year
was Starfield, which was… meh? Apparently compelling enough
that I’ve “ascended” twice, but in hindsight I wish I hadn’t bothered.
2 Someday my group and I will finish Pandemic Legacy: Season 2 so we can get
started on Season 0 which has sat
unplayed on my shelves since I got it… oooh… two or three years ago‽
3 This Christmas, I got each of them their first “legacy” game: Zombie Kids for the younger one, My City for the elder. They both seem pretty good.
4Geocaching is where you use military satellite networks to find lost tupperware. Geohashing uses the same technology but what you find is a whole
lot of nothing. I don’t think I can explain why I find the latter more-compelling.
There are video games that I’ve spent
many years playing (sometimes on-and-off) before finally beating them for the first time. I spent three years playing Dune II before I finally beat it as every house. It took twice that to reach the end of Ultima Underworld II. But
today, I can add a new contender1 to that list.
Today, over thirty-five years after I first played it, I finally completed Wonder Boy.
My first experience of the game, in the 1980s, was on a coin-op machine where I’d discovered I could get away with trading the 20p piece I’d been given by my parents to use as a deposit
on a locker that week for two games on the machine. I wasn’t very good at it, but something about the cutesy graphics and catchy chip-tune music grabbed my attention and it became my
favourite arcade game.
I played it once or twice more when I found it in arcades, as an older child. I played various console ports of it and found them disappointing. I tried it a couple of times in MAME. But I didn’t really put any effort into it until a hotel we stayed at during a family holiday to Paris in October had a bank of free-to-play arcade machines
rigged with Pandora’s Box clones so they could be used to play a few thousand different arcade classics. Including Wonder Boy.
Off the back of all the fun the kids had, it’s perhaps no surprise that I arranged for a similar machine to be delivered to us as a gift “to the family”2
this Christmas.
And so my interest in the game was awakened and I threw easily a hundred pounds worth of free-play games of Wonder Boy3 over the last few days. Until…
…today, I finally defeated the seventh ogre4,
saved the kingdom, etc. It was a hell of a battle. I can’t count how many times I pressed the “insert coin” button on that final section, how many little axes I’d throw into the beast’s
head while dodging his fireballs, etc.
So yeah, that’s done, now. I guess I can get back to finishing Wonder Boy: The Dragon’s Trap, the 2017 remake of a 1989 game I
adored!5
It’s aged amazingly well!
Footnotes
1 This may be the final record for time spent playing a video game before beating it,
unless someday I ever achieve a (non-cheating) NetHack ascension.
2 The kids have had plenty of enjoyment out of it so far, but their time on the machine is
somewhat eclipsed by Owen playing Street Fighter II Turbo and Streets of Rage on it and, of course, by my rediscovered obsession with Wonder Boy.
3 The arcade cabinet still hasn’t quite paid for itself in tenpences-saved,
despite my grinding of Wonder Boy. Yet.
4 I took to calling the end-of-world bosses “ogres” when my friends and I swapped tips for
the game back in the late 80s, and I refuse to learn any different name for them.[footnote], saved Tina[footnote]Apparently the love interest has a name. Who knew?
5 I completed the original Wonder Boy III: The Dragon’s Trap on a Sega Master
System borrowed from my friend Daniel back in around 1990, so it’s not a contender for the list either.
Foundry is a wonderful virtual tabletop tool well-suited to playing tabletop roleplaying games with your friends, no
matter how far away they are. It compares very favourably to the market leader Roll20, once you
get past some of the initial set-up challenges and a moderate learning curve.
You can run it on your own computer and let your friends “connect in” to it, so long as you’re able to reconfigure your router a little, but you’ll be limited by the speed of your home
Internet connection and people won’t be able to drop in and e.g. tweak their character sheet except when you’ve specifically got the application running.
A generally better option is to host your Foundry server in the cloud. For most of its history, I’ve run mine on Fox, my NAS, but I’ve recently set one up on a more-conventional cloud virtual machine too. A couple of
friends have asked me about how to set up their own, so here’s a quick guide:
You will need…
A Foundry license ($50 USD / £48 GBP, one-off payment1)
A domain name for which you control the DNS records; you’ll need to point a domain, like “danq.me” (or a subdomain of it, e.g.
“vtt.danq.me”), at an IP address you’ll get later by creating an “A” record: your domain name registrar can probably help with this –
I mostly use Gandi and, ignoring my frustration with
recent changes to their email services, I think they’re great
An account with a cloud hosting provider: this example uses Linode but you can adapt for any of them
A basic level of comfort with the command-line
1. Spin up a server
Getting a virtual server is really easy nowadays.
You’ll need:
The operating system to be Debian 12 (or else you’ll need to adapt the instructions below)
The location to be somewhere convenient for your players: pick a server location that’s relatively-local to the majority of them to optimise for connection speeds
An absolute minimum of 1GB of storage space, I’d recommend plenty more: The Levellers’ campaign currently uses about 10GB for all of its various maps, art, videos,
and game data, so give yourself some breathing room (space is pretty cheap) – I’ve gone with 80GB for this example, because that’s what comes as standard with the 2
CPU/4GB RAM server that Linode offer
Choose a root password when you set up your server. If you’re a confident SSH user, add your public key so you can log in easily (and then
disable password authentication
entirely!).
For laziness, this guide has you run Foundry as root on your new server. Ensure you understand the implications of this.2
2. Point your (sub)domain at it
DNS propogation can be pretty fast, but… sometimes it isn’t. So get this step underway before you need it.
Your newly-created server will have an IP address, and you’ll be told what it is. Put that IP address into an A-record for your domain.
3. Configure your server
In my examples, my domain name is vtt.danq.me and my server is at 1.2.3.4. Yours will be different!
Connect to your new server using SSH. Your host might even provide a web interface if you don’t have an SSH client installed: e.g. Linode’s “Launch LISH Console” button will do pretty-much exactly that for you. Log in as root using the password you chose
when you set up the server (or your SSH private key, if that’s your preference). Then, run each of the commands below in order (the full script is available as a single file if you
prefer).
3.1. Install prerequisites
You’ll need unzip (to decompress Foundry), nodejs (to run Foundry), ufw (a firewall, to prevent unexpected surprises), nginx (a
webserver, to act as a reverse proxy to Foundry), certbot (to provide a free SSL certificate for Nginx),
nvm (to install pm2) and pm2 (to keep Foundry running in the background). You can install them all like this:
By default, Foundry runs on port 30000. If we don’t configure it carefully, it can be accessed directly, which isn’t what we intend: we want connections to go through the webserver
(over https, with http redirecting to https). So we configure our firewall to allow only these ports to be accessed. You’ll also want ssh enabled so we can remotely connect into the
server, unless you’re exclusively using an emergency console like LISH for this purpose:
Putting the domain name we’re using into a variable for the remainder of the instructions saves us from typing it out again and again. Make sure you type your domain name (that
you pointed to your server in step 2), not mine (vtt.danq.me):
DOMAIN=vtt.danq.me
3.4. Get an SSL certificate with automatic renewal
So long as the DNS change you made has propogated, this should Just Work. If it doesn’t, you might need to wait for a bit then try
again.
3.5. Configure Nginx to act as a reverse proxy for Foundry
You can, of course, manually write the Nginx configuration file: just remove the > /etc/nginx/sites-available/foundry from the end of the printf line to see
the configuration it would write and then use/adapt to your satisfaction.
set +H
printf "server {\n listen 80;\n listen [::]:80;\n server_name $DOMAIN;\n\n # Redirect everything except /.well-known/* (used for ACME) to HTTPS\n root /var/www/html/;\n if (\$request_uri !~ \"^/.well-known/\") {\n return 301 https://\$host\$request_uri;\n }\n}\n\nserver {\n listen 443 ssl http2;\n listen [::]:443 ssl http2;\n server_name $DOMAIN;\n\n ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;\n ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;\n\n client_max_body_size 300M;\n\n location / {\n # Set proxy headers\n proxy_set_header Host \$host;\n proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \$scheme;\n\n # These are important to support WebSockets\n proxy_set_header Upgrade \$http_upgrade;\n proxy_set_header Connection \"Upgrade\";\n\n proxy_pass http://127.0.0.1:30000/;\n }\n}\n" > /etc/nginx/sites-available/foundry
ln -sf /etc/nginx/sites-available/foundry /etc/nginx/sites-enabled/foundry
service nginx restart
3.6. Install Foundry
3.6.1. Create a place for Foundry to live
mkdir {vtt,data}
cd vtt
3.6.2. Download and decompress it
Substitute in your Timed URL in place of <url from website> (keep the quotation marks – " –
though!):
wget -O foundryvtt.zip "<url from website>"
unzip foundryvtt.zip
rm foundryvtt.zip
3.6.3. Configure PM2 to run Foundry and keep it running
Now you’re finally ready to launch Foundry! We’ll use PM2 to get it to run automatically in the background and keep running:
You can watch the logs for Foundry with PM2, too. It’s a good idea to take a quick peep at them to check it launched okay (press CTRL-C to exit):
pm2 logs 0
4. Start adventuring!
Provide your license key to get started, and then immediately change the default password: a new instance of Foundry has a blank default password, which means that
anybody on Earth can administer your server: get that changed to something secure!
Now you’re running on Foundry!
Footnotes
1Which currency you pay in, and therefore how much you pay, for a Foundry license depends on where in the world you are
where your VPN endpoint says you are. You might like to plan accordingly.
2 Running Foundry as root is dangerous, and you should consider the risks for yourself.
Adding a new user is relatively simple, but for a throwaway server used for a single game session and then destroyed, I wouldn’t bother. Specifically, the risk is that a vulnerability
in Foundry, if exploited, could allow an attacker to reconfigure any part of your new server, e.g. to host content of their choice or to relay spam emails. Running as a non-root user
means that an attacker who finds such a vulnerability can only trash your Foundry instance.
Woodward Draw by Daniel Linssen is the kind of game that my inner
Scrabble player both loves and hates. I’ve been playing on and off for the last three days to complete it, and it’s been great. While not perfectly polished1 and with a few rough
edges2, it’s still a great example of
what one developer can do with a little time.
It deserves a hat tip of respect, but I hope you’ll give it more than that by going and playing it (it’s free, and you can play online or download a copy3). I should probably check
out their other games!
Footnotes
1 At one point the background colour, in order to match a picture word, changed to almost
the same colour as the text of the three words to find!
2 The tutorial-like beginning is a bit confusing until you realise that you have to play
the turn you’re told to, to begin with, for example.
Known Leaders is an open-source
program that combines WikiData with a random generator to come up with almost-invariably inaccurate but sometimes hilarious facts. Jim Kang came up with it during Recurse Center‘s Never Graduate Week. Go have a play, or read more about how and why he made it.
I just spent a lightweight week in Rome with fellow members of Automattic‘s Team Fire.
Among our goals for the week was an attempt to strengthen the definition of who are team are, what we work on, and how and why we do so. That’s
basically a team-level identity, mission, vision, and values, right?
Normally when you play Dixit, you select a card from your hand – each shows a unique piece of artwork – and try to describe it in a way that’s precise enough that some
of the other players will later be able to pick it out of a line-up, but ambiguous enough that not all the other players will. It’s a delicate balancing act. Even when our old
Geek Night was in full swing we didn’t used to play it often because our well-established group’s cornucopia of in-jokes and references made it trivially easy to “target”
your descriptions at specific players1, but it’s still a solid icebreaker activity.
Perhaps it was the fantasy artwork that inspired us or maybe it just says something about how my team sees themselves, but what we came up with had a certain… swords-and-sorcery… even
Dungeons & Dragons… feel to it.
Ou team’s new identity isn’t finalised, but I love the fact that we’ve been able to inject a bit of fun and whimsy into it. At our last draft, my team looks to be defined as comprising:
Gareth, level 62 Pathfinder, leading the way through the wilds
Bero, Level 5 Battlesmith, currently lost in the void
Dan (me!), Level 5 Arcane Trickster, breaking locks and stealing treasure
Cem, Level 4 Dragonslayer, smashing doors and bugs alike
Lae, Level 7 Pirate, seabound rogue with eyes on the horizon
Kyle, Level 5 Apprentice Bard, master of words and magic
Simran, Level 6 Apprentice Code Witch, weaving spells from nature
I think that’s pretty awesome.
Footnotes
1 Also: I don’t own any of the expansion packs and playing with the same cards over and
over again gets a bit samey.
2 The “levels” are simply the number of years each teammate has been an Automattician,
plus one.
Last night I had a nightmare about Dungeons & Dragons. Specifically, about the group I DM for on alternate Fridays.
In their last session the
party – somewhat uncharacteristically – latched onto a new primary plot hook rightaway. Instead of rushing off onto some random side quest threw themselves directly into this new
mission.
This effectively kicked off a new chapter of their story, so I’ve been doing some prep-work this last week or so. Y’know: making battlemaps, stocking treasure chests with mysterious and
powerful magical artefacts, and inventing a plethora of characters for the party to either befriend or kill (or, knowing this party: both).
Anyway: in the dream, I sat down to complete the prep-work I want to get done before this week’s play session. I re-checked my notes about what the adventurers had gotten up to
last time around, and… panicked! I was wrong, they hadn’t thrown themselves off the side of a city floating above the first layer of Hell at all! I’d mis-remembered completely
and they’d actually just ventured into a haunted dungeon. I’d been preparing all the wrong things and now there wasn’t time to correct my mistakes!
This is, of course, an example of the “didn’t prepare for the test” trope of dreams. Clearly I’m still feeling underprepared for this week’s game! But probably a bigger reason for the
dream, and remembering it, was that I’ve had a cold and kept waking up to cough.
This was a delightful vlog. It really adds personality to what might otherwise have been a story only about technology and history.
I subscribed to Codex’s vlog like… four years ago? He went dark soon afterwards, but thanks to the magic of RSS, I got
notified as soon as he came back from his hiatus.
I swear that I used to be good at Mastermind when I was a kid. But now, when it’s my turn to break
the code that one of our kids has chosen, I fail more often than I succeed. That’s no good!
Mastermind and me
Maybe it’s because I’m distracted; multitasking doesn’t help problem-solving. Or it’s because we’re “Super” Mastermind, which differs from the one I had as a child in that
eight (not six) peg colours are available and secret codes are permitted to have duplicate peg colours. These changes increase the possible permutations from 360 to 4,096, but the
number of guesses allowed only goes up from 8 to 10. That’s hard.
Hey, that’s an idea. Let’s crack the code… by writing some code!
Representing a search space
The search space for Super Mastermind isn’t enormous, and it lends itself to some highly-efficient computerised storage.
There are 8 different colours of peg. We can express these colours as a number between 0 and 7, in three bits of binary, like this:
Decimal
Binary
Colour
0
000
Red
1
001
Orange
2
010
Yellow
3
011
Green
4
100
Blue
5
101
Pink
6
110
Purple
7
111
White
There are four pegs in a row, so we can express any given combination of coloured pegs as a 12-bit binary number. E.g. 100 110 111 010 would represent the
permutation blue (100), purple (110), white (111), yellow (010). The total search space, therefore, is the range of numbers from
000000000000 through 111111111111… that is: decimal 0 through 4,095:
Decimal
Binary
Colours
0
000000000000
Red, red, red, red
1
000000000001
Red, red, red, orange
2
000000000010
Red, red, red, yellow
…………
4092
111111111100
White, white, white, blue
4093
111111111101
White, white, white, pink
4094
111111111110
White, white, white, purple
4095
111111111111
White, white, white, white
Whenever we make a guess, we get feedback in the form of two variables: each peg that is in the right place is a bull; each that represents a peg in the secret code but
isn’t in the right place is a cow (the names come from Mastermind’s precursor, Bulls & Cows). Four bulls
would be an immediate win (lucky!), any other combination of bulls and cows is still valuable information. Even a zero-score guess is valuable- potentially very valuable! – because it
tells the player that none of the pegs they’ve guessed appear in the secret code.
Solving with Javascript
The latest versions of Javascript support binary literals and bitwise operations, so we can encode and decode between arrays of four coloured pegs (numbers 0-7) and the number 0-4,095
representing the guess as shown below. Decoding uses an AND bitmask to filter to the requisite digits then divides by the order of magnitude. Encoding is just a reduce
function that bitshift-concatenates the numbers together.
/** * Decode a candidate into four peg values by using binary bitwise operations. */function decodeCandidate(candidate){
return [
(candidate &0b111000000000) /0b001000000000,
(candidate &0b000111000000) /0b000001000000,
(candidate &0b000000111000) /0b000000001000,
(candidate &0b000000000111) /0b000000000001
];
}
/** * Given an array of four integers (0-7) to represent the pegs, in order, returns a single-number * candidate representation. */function encodeCandidate(pegs) {
return pegs.reduce((a, b)=>(a <<3) + b);
}
With this, we can simply:
Produce a list of candidate solutions (an array containing numbers 0 through 4,095).
Choose one candidate, use it as a guess, and ask the code-maker how it scores.
Eliminate from the candidate solutions list all solutions that would not score the same number of bulls and cows for the guess that was made.
Repeat from step #2 until you win.
Step 3’s the most important one there. Given a function getScore( solution, guess ) which returns an array of [ bulls, cows ] a given guess would
score if faced with a specific solution, that code would look like this (I’m convined there must be a more-performant way to eliminate candidates from the list with XOR
bitmasks, but I haven’t worked out what it is yet):
/** * Given a guess (array of four integers from 0-7 to represent the pegs, in order) and the number * of bulls (number of pegs in the guess that are in the right place) and cows (number of pegs in the * guess that are correct but in the wrong place), eliminates from the candidates array all guesses * invalidated by this result. Return true if successful, false otherwise. */function eliminateCandidates(guess, bulls, cows){
const newCandidatesList = data.candidates.filter(candidate=>{
const score = getScore(candidate, guess);
return (score[0] == bulls) && (score[1] == cows);
});
if(newCandidatesList.length ==0) {
alert('That response would reduce the candidate list to zero.');
returnfalse;
}
data.candidates = newCandidatesList;
chooseNextGuess();
returntrue;
}
I continued in this fashion to write a full solution (source code). It uses ReefJS for
component rendering and state management, and you can try it for yourself right in your web browser. If you play against the online version I mentioned you’ll need to transpose the colours in your head: the physical version I play with the kids has pink and
purple pegs, but the online one replaces these with brown and black.
Testing the solution
Let’s try it out against the online version:
As expected, my code works well-enough to win the game every time I’ve tried, both against computerised and in-person opponents. So – unless you’ve been actively thinking about the
specifics of the algorithm I’ve employed – it might surprise you to discover that… my solution is very-much a suboptimal one!
My solution is suboptimal
A couple of games in, the suboptimality of my solution became pretty visible. Sure, it still won every game, but it was a blunt instrument, and anybody who’s seriously thought about
games like this can tell you why. You know how when you play e.g. Wordle (but not in “hard mode”) you sometimes want to type in a word that can’t possibly be the
solution because it’s the best way to rule in (or out) certain key letters? This kind of strategic search space bisection reduces the mean number of guesses you need to solve the
puzzle, and the same’s true in Mastermind. But because my solver will only propose guesses from the list of candidate solutions, it can’t make this kind of improvement.
Search space bisection is also used in my adverserial hangman game, but in this case the aim is to split the search space in such a way that no
matter what guess a player makes, they always find themselves in the larger remaining portion of the search space, to maximise the number of guesses they have to make. Y’know, because
it’s evil.
There are mathematically-derived heuristics to optimise Mastermind strategy. The first
of these came from none other than Donald Knuth (legend of computer science, mathematics, and pipe organs) back in 1977. His solution,
published at probably the height of the game’s popularity in the amazingly-named Journal of Recreational Mathematics, guarantees a solution to the six-colour version of the
game within five guesses. Ville [2013] solved an
optimal solution for a seven-colour variant, but demonstrated how rapidly the tree of possible moves grows and the need for early pruning – even with powerful modern computers – to
conserve memory. It’s a very enjoyable and readable paper.
But for my purposes, it’s unnecessary. My solver routinely wins within six, maybe seven guesses, and by nonchalantly glancing at my phone in-between my guesses I can now reliably guess
our children’s codes quickly and easily. In the end, that’s what this was all about.