I recently did some quick Python scripting to help with a design I’m working on. It’s a board game, not a computer game — but I was able to write some short scripts that simulated key parts of the game, and report on the results. By mining the data generated by those scripts, I was able to discover some interesting relationships in the game’s design.
The Test Case
People’s Republic is a multi-player card game that lies somewhere between Werewolf and Battlestar Galactica. You play as a powerful faction in the government of a fictional cold-war-era eastern european communist country. Superficially, you’re all working to build a worker’s utopia, against the forces of capitalist decadence and corruption. If you do, those players that gathered the most power during the struggle end up winning. But if you fail, the player with the least power sells out their country to the forces of evil and wins.
So it’s a traitor game, but its a *variable* traitor game. In games like Battlestar Galactica or Shadows over Camelot, the traitor is determined by random draw. In Peoples Republic, being the traitor is a decision, akin to shooting the moon in the card game Hearts.
A Problem of Proportion
One of the biggest challenges in People’s Republic has been balancing the scoring opportunities. Every turn, a number of alignment tokens are drawn, and the players work to either score them or block them as a whole. As a player, when you’re deciding how to play, you’re looking at how much you’re scoring, relative to the other players.
When I’m designing, I use the “dial” metaphor to describe different parts of the game that could be set at different levels. In this case, there were several dials involved with this particular mechanic:
- How many alignments each player had
- The number of tokens for each alignment
- How many total tokens are drawn per turn
On top of that, “number of players” is its own, special dial: whatever dial combinations I choose have to work with a range of player counts.
It was easy to see that different dial combinations would create different populations of token sets. By picking extremes, I could see wildly different behavior arising:
I ran a few playtests with different dial settings. I found that combinations that looked good on paper didn’t necessarily work in the wild. There were statistical forces at work that were beyond my immediate ken.
Enter the Python
I needed to test one part of the game: the scoring distribution. I needed to test lots of different combinations of dial settings. I didn’t want to try to make my playtesters suffer through any more bad combinations. Theorycraft was only getting me so far. What to do?
When in doubt, try Python! Python is a popular programming language. One of its many strengths: its really easy to whip up a quick script to do… whatever you want, really.
And here is your Obligatory XKCD cartoon about python:
So I figured I’d have a go at using Monte Carlo methods to gain more insight into the dial settings, and how they affected each other.
Now, doesn’t *that* sound fancy? Let me dispel your fantasies as quickly as possible: it was not. Yes, there is a real field of mathematics that does all sorts of cool stuff, and it is called Monte Carlo. Those gentle readers who are seeking a better understanding of it should look elsewhere. I, on the other hand, used the abridged technique:
Armed with my trusty text editor and a donkey-load of coffee, I proceeded to hack out a script. Those who prefer to cut to the chase may see it here. If you are savoring the drama, read on.
Part One: The Simulation
The first step was describing the game in code — at least, the part of it that I wanted to exercise. I only really needed to write enough code to represent the abovementioned dials, and their interactions.
The actual game logic isn’t that important; here’s what the code does:
Part Two: Running it a Million Times
Once you’ve got a class that runs the game and tracks the results, it’s easy to run it a million times:
Again, never mind the poorly-named variables that are specific to People’s Republic. The important bit here is that it’s running through tons of different permutations of dial settings. For each permutation, it’s running a 1000-round simulation of the game.
Part Three: See What Happens
Well, that’s great! We’re running all these simulations… but what are the results? I decided to keep it as simple as possible: I had my script print the results in columns, separated by tabs:
It was then a simple matter to run the script…
C:\PRScripting\python pr.py > prout.txt
… then open the file in OpenOffice…
And voila! Data to explore! I now know, for every dial setting combination, what proportion of the rounds will favor a team win vs an anti-team win. I can use OpenOffice to sort, filter, and browse the data, looking for combinations that have the kind of proportions I’m looking for.
And Again… And Again… And Again…
I’ll definitely try this technique again, the next time I’m faced with a bunch of dials and a game mechanic that I can easily model in code. There’s a lot of things I would do differently, primarily having to do with separating the game logic from the execution and reporting. But for a short afternoon’s work, I got a lot of visibility into a system that I couldn’t have had otherwise.
Have you ever used scripting to help solve a design problem? Got any thoughts on different tools or approaches? Do you actually know something about Monte Carlo simulations? Leave us a comment!
Latest posts by Stephen DeBaun (see all)
- Go Big Or Go Home: Running an Awesome Event at a Con – August 27, 2014
- Expand Different: Have More Fun by Reducing Choices – August 4, 2014
- Fine-tuning Scoring on Hundred Kings War – July 14, 2014
7 Readers CommentedJoin discussion
Awesome article. I’ve used python scripts to figure out dice combo probabilities when the math for multiple dependent rolls gets beyond my skills to figure out.
Thanks Brad! Were there any specific libraries you took advantage of? Or did you just roll-your-own?
I was idly thinking about what could be done by way of some sort of DSL for dealing with these kinds of things. Python might not be the right language for that, though.
Nope, just rolled my own. I have a push-your-luck game where you have to roll various dice combos and needed to figure out how they ranked in difficulty.
Awesome Stephen! You are truly turning game design up to 11.
One thing to keep in mind about monte carlo methods is that often as game designers we’re working with such small data sets that it’s easier and more accurate to iterate every possibility than to check a bunch of random samples. I’ve seen people run a thousand random trials to test the results of a D&D stat roll, when at 4d6 drop 1, there are only about a thousand permutations of the dice!
@Alan: for those of us who lack the maths but have the scripting, it can be easier to write a script that generates the results. 🙂
For this particular problem, it wasn’t a single probability I was looking for; I was trying to understand how the probabilities changed over many combinations of settings. It’s not particularly obvious from the screenshot of the data, but there were a lot of interesting relationships that were not obvious.
Interesting concept. I think I’d stick with Excel VBA though as it can pretty much do the same thing and I know it much better. 🙂 (Someday I’ll learn Python.)