Yeah, that’s about right:
Tag: games
Golden Axed
Hot on the heels of my victory over Wonder Boy 35 years after I first played it, I can now finally claim to have beaten Golden Axe, 25 years after I first played it.
Couldn’t have done it without my magic-wielding 7-year-old co-op buddy.
[Bloganuary] Playtime
This post is part of my attempt at Bloganuary 2024. Today’s prompt is:
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.
GNSS Activities
I’m sure everybody knows I do a modest amount of geocaching and geohashing.4
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.
4 Geocaching 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.
Wonder Boy
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.6, saved Tina7Apparently 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.
Easy FoundryVTT Cloud Hosting
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
- Approximately 2 CPUs and 4GB of RAM, per Foundry’s recommended server specifications
- 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:
apt update apt upgrade apt install -y unzip nodejs ufw nginx certbot nvm npm install -g pm2
3.2. Enable firewall
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:
ufw allow ssh ufw allow http ufw allow https ufw enable
3.3. Specify domain name
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.
certbot certonly --agree-tos --register-unsafely-without-email --rsa-key-size 4096 --webroot -w /var/www/html -d $DOMAIN
Don’t continue past this point until you’ve succeeded in getting the SSL certificate sorted.
The certificate will renew itself automatically, but you also need Nginx to restart itself whenever that happens. You can set that up like this:
printf "#!/bin/bash\nservice nginx restart\n" > /etc/letsencrypt/renewal-hooks/post/restart-nginx.sh chmod +x /etc/letsencrypt/renewal-hooks/post/restart-nginx.sh
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:
pm2 start --name "Foundry" node -- resources/app/main.js --dataPath=/root/data
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
1 Which 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
This is a repost promoting content originally published elsewhere. See more things Dan's reposted.
- Explore the set of 4 letter words
- Either change one letter of the previous word
- Or rearrange all the letters of the previous word
- Find all 105 picture words!
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.
3 Downloadable version is Windows only.
Paul Labyrinth
This is a repost promoting content originally published elsewhere. See more things Dan's reposted.
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.
Have Fun with Missions, Visions, and Values
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?
Fellow Automattician Ben Dwyer recently wrote about his experience of using a deck of Dixit cards to help his team refine their values in a fun and engaging way. I own a Dixit set, so we decided to give it a go too.
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.
Nightmares & Noggins
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.
Right, better do a little more prep work!
Satoru Iwata’s first commercial game has a secret
This is a repost promoting content originally published elsewhere. See more things Dan's reposted.
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.
Sisyphus: The Board Game (Digital Edition)
I’m off work sick today: it’s just a cold, but it’s had a damn good go at wrecking my lungs and I feel pretty lousy. You know how when you’ve got too much of a brain-fog to trust yourself with production systems but you still want to write code (or is that just me?), so this morning I threw together a really, really stupid project which you can play online here.
It’s inspired by a toot by Mason”Tailsteak” Williams (whom I’ve mentioned before once or twice). At first I thought I’d try to calculate the odds of winning at his proposed game, or how many times one might expect to play before winning, but I haven’t the brainpower for that in my snot-addled brain. So instead I threw together a terrible, terrible digital implementation.
Go play it if, like me, you’ve got nothing smarter that your brain can be doing today.
All the game descriptions from the sale
This is a repost promoting content originally published elsewhere. See more things Dan's reposted.
You’ve got 37 unpaid parking tickets. You just got pulled over for speeding. In your defense, you were texting your sister about how drunk you are. Plus there’s all that blood on your windshield. Obviously you know it’s deer blood, but the police officers walking toward your vehicle don’t. Still, in the time it takes them to figure that out, maybe you’ll sober up. Or escape on foot! Either way, it’ll probably be fine.
User Tags: Poor Choices / Story Rich / Multiple Endings / Parkour
Collect dead seagulls and build a zoo to house them all. Beautify the zoo with artistic flair and deodorizing sprays. Design creative group promotions to stir up interest! Is that a customer? You’d better hope it’s not the owner of the live seagull zoo down the street, because he’s probably got some questions.
User Tags: Hard Work / Supply / Demand / Diseases & Parasites
…
Claire Hummel produced fake video game art for the Steam Summer Sale, which was already excellent, but when @g-a-y-g-o-y-l-e reblogged, asking for more context, Claire delivered and then some. Every single one of these “game descriptions” is a special kind of comedy gold… and yet somehow believable from the store that sells us Dream Daddy, IKEA VR Pancake Kitchen, Organ Trail, Oh… Sir!! The Insult Simulator, and Goat Simulator (all of which I own copies of). Go read the full list.
Beating Children at Mastermind [Video]
This post is also available as an article. So if you'd rather read a conventional blog post of this content, you can!
This video accompanies a blog post of the same title. The content is basically the same – if you prefer videos, watch this video. If you prefer blog posts, go read the blog post. You might also like to play with my Mastermind solver or view the source code.
Beating Children at Mastermind
This post is also available as a video. If you'd prefer to watch/listen to me talk about this topic, give it a look.
This blog post is also available as a video. Would you prefer to watch/listen to me tell you about how I’ve implemented a tool to help me beat the kids when we play Mastermind?
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.
Or maybe it’s just that I’ve gotten lazy and I’m now more-likely to try to “solve” a puzzle using a computer to try to crack a code using my brain alone. See for example my efforts to determine the hardest hangman words and make an adverserial hangman game, to generate solvable puzzles for my lock puzzle game, to cheat at online jigsaws, or to balance my D&D-themed Wordle clone.
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
|
|
White, white, white, blue |
4093
|
|
White, white, white, pink |
4094
|
|
White, white, white, purple |
4095
|
|
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.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
/** * 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):
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
/** * 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.'); return false; } data.candidates = newCandidatesList; chooseNextGuess(); return true; } |
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.
Printing Maps from Dungeondraft
I really love Dungeondraft, an RPG battle map generator. It’s got great compatibility with online platforms like Foundry VTT and Roll20, but if you’re looking to make maps for tabletop play, there’s a few tips I can share:
Planning and designing
Dungeondraft has (or can be extended with) features to support light levels and shadow-casting obstructions, openable doors and windows, line-of sight etc… great to have when you’re building for Internet-enabled tabletops, but pointless when you’re planning to print out your map! Instead:
- Think about scale: I’m printing to A4 sheets and using inch-size squares, so every 11 x 8 squares equates to one sheet of paper. Knowing this, I can multiply-up to a whole number of sheets of paper and this informs my decisions about how to best make use of the maps (and what will and won’t fit on my dining table!).
- Focus on legibility: Your printer probably won’t have the same kind of resolution as your screen, and your players can’t “zoom in” to get details. Play with the grid styles (under Map Settings) to find what works best for you, and try not to clash with your floor patterns. If you’re printing in monochrome, use the “Printer-Friendly” camera filter (also under Map Settings, or in the Export Options dialog) to convert to gorgeous line-art. Make sure critical elements have sufficient contrast that they’ll stand out when printed or your players might walk right over that chest, campfire, or bookshelf.
- Think about exposure: You don’t get digital “fog of war” on the tabletop! Think about how you’re going to reveal the map to your players: plan to print in multiple sections to put together, jigsaw style, or have card to “cover” bits of the map. Think about how the tool can help you here: e.g. if you’ve got multiple buildings the players can explore, use a higher “level” or roof layer to put roofs on your buildings, then print the relevant parts of that level separately: now you’ve got a thematic cover-up that you can remove to show the insides of the building. Go the other way around for secret doors: print the empty wall on your main map (so players can’t infer the location of the secret door by the inclusion of a cover-up) and the secret door/passage on the overlay, so you can stick it onto the map when they find it.
Printing it out
There’s no “print” option in Dungeondraft, so – especially if your map spans multiple “pages” – you’ll need a multi-step process to printing it out. With a little practice, it’s not too hard or time-consuming, though:
Export your map (level by level) from Dungeondraft as PNG files. The default settings are fine, but pay attention to the “Overlay level” setting if you’re using smart or complex cover-ups as described above.
To easily spread your map across multiple pages, you’ll need to convert it to a PDF. I’m using Gimp to do this. Simply open the PNG in Gimp, make any post-processing/last minute changes that you couldn’t manage in Dungeondraft, then click File >
Export As… and change the filename to have a .pdf
extension. You could print directly from Gimp, but in my experience PDF reader software does a much better job at multi-page printing.
Open your PDF in an appropriate reader application with good print management. I’m using Foxit, which is… okay? Print it, selecting “tile large pages” to tell it to print across multiple sheets. Assuming you’ve produced a map an appropriate size for your printer’s margins, your preview should be perfect. If not, you can get away with reducing the zoom level by up to a percent or two without causing trouble for your miniatures. If you’d like the page breaks to occur at specific places (for exposure/reveal reasons), go back to Gimp and pad one side of the image by increasing the canvas size.
Check the level of “overlap” specified: I like to keep mine low and use the print margins as the overlapping part of my maps when I tape them together, but you’ll want to see how your printer behaves and adapt accordingly.
If you’re sticking together multiple pages to make a single large map, trim off the bottom and right margins of each page: if you printed with cut marks, this is easy enough even without a guillotine. Then tape them together on the underside, taking care to line-up the features on the map (it’s not just your players who’ll appreciate a good, visible grid: it’s useful when lining-up your printouts to stick, too!).
I keep my maps rolled-up in a box. If you do this too, just be ready with some paperweights to keep the edges from curling when you unfurl them across your gaming table. Or cut into separate rooms and mount to stiff card for that “jigsaw” effect! Whatever works best for you!