In the last post, we took a quick look at the basics of Monte Carlo simulation, and used a simple simulation to get the probabilities of various outcomes in the first roll of Zombie Dice. In this post, we’ll extend our simulation to play turns for us, based on a strategy that we can define. We’ll try several different strategies of varying complexities and see how well we do!
Use Your Braaaains
One of the great qualities of Zombie Dice is that it really lends itself to taunting your friends. Games that are too random and require no skill (Candyland, anyone?) get boring very fast. Zombie Dice manages to leverage a large amount of randomness with the right amount of skill. Skill includes peer-pressuring your opponents into rolling when they have two shotguns and all the red die are used up. “Come on, what are the odds?” (see the last post for how to get that answer).
Strategy comes into play when you’re looking at the color of the die in front of you, the relative amount of points you have (compared to your fellow zombies), and other factors that make you more or less risk-averse. If you’re behind, you might throw caution to the wind and go big, otherwise you have no chance of winning. If you have a decent lead, slow and steady may very well win the race.
For the really nerdy players (guilty), we might be curious as to what kind of strategies we should adopt during the game, and how well they might do. Are there any simple heuristics that we can bring along that are effective and easy to remember?
Dinner vs Bullets
Using the code from the last post to generate rolls, and some new code that manages the die and the rolling, we can define a strategy with a simple function. In my code, the function knows everything the player knows: the number of brains gathered on the turn, the number of shots taken, and which die are left in the cup.
Here is a really simple strategy (in Python):
def simple_strategy(die_cup, brains, shotguns, brain_die, shotgun_die): if shotguns <= 1: return True elif shotguns == 2 and brains == 0: return True else: return False
Our simple strategy always rolls (returns True) if we have 1 or fewer shotguns. It is also willing to take a risk with 2 shotguns and no brains gathered (what is there to lose?). Otherwise, it stops rolling. Running the simulator 10,000 times (for 10K turns), we get the distribution of brains gained for each turn:
Let’s check the convergence of the expected value, just to make sure that we don’t have an incorrect estimate:
The convergence looks alright. The uncertainty isn’t reducing much more, but that’s probably a function of the game (and hopefully not numpy’s RNG or my coding).
Our simple strategy gets an average of 2.18 points per turn. At that rate, we’d reach 13 points in about 6 turns. During the simulation, I also kept track of the number of rolls in each turn. This lets us see at what point in the turn the player gets all shot full of holes. Here’s a scatterplot of roll counts vs. final score, where larger dots mean more turns that fell into that point:
The player seems to lose the most on the second roll, but most of the action takes place on the second roll (either lose, or reach 2 shotguns with some brains).
But That Runner Looked So Tasty!
Another simple strategy might be to stop rolling and bank your brains after a certain number of points have been reached. This strategy would accept not hitting the really big jackpots, but also try not to lose a good turn. Here’s the code for that strategy:
def max_limit(die_cup, brains, shotguns, brain_die, shotgun_die, brain_lim=4): if brains >= brain_lim: return False elif shotguns == 2 and brains == 0: return True elif shotguns == 2: return False else: return True
It’s the same as the last strategy, except we stop when a brain limit has been met. We can vary that limit, run thousands of turns, and try to find the limit that gives the best expected brains gained on a turn.
Running that sweep gives the following results (with the uncertainty band shown):
The best value is about 2.2 points per turn, and that happens when we stop rolling when we are at or over 6 brains. Maybe we can get some more points out of the strategy if we allowing rolling over the brain limit for 1 or fewer shotguns in play. Here’s what that gives us:
We’ve manage to eke out about .05 points per turn. We are also much more uncertain about these results than for the previous one. The lower 95% bounds are at 2.2, though, which means that’s we’re pretty certain that it’ll be better than the simple strategy. The drop at 6 points is likely due to the cup getting low or being refilled.
The next option is to start taking more risks on two shotguns if we have a low number of brains. We’ll also go for it if we haven’t seen any shotguns yet.
def riskier(die_cup, brains, shotguns, brain_die, shotgun_die, brain_lim=4): if brains < brain_lim: return True elif shotguns == 0: return True else: return False
The shotgun == 0 case will probably have the effect of raising the expected return for low brain limits, because it’ll probably buy us one more roll which should generally return at least 1 shotgun. Here are the results for that strategy:
That didn’t go so well! We only get an expected value of 2 brains per turn. Let’s increase the shotgun limit to roll if we are over the limit for 0 or 1 shotguns.
Not so much progress, but a little bit better.
Eat Brains, Get Smarter
It’s probably time to start getting smarter about the strategies to try to push up our expected points. For one thing, players intuitively increase their belief that their next roll will be good for them if there are more red die in the brains or shotgun piles. We can explicitly calculate the odds of losing on the next roll, and use that probability to guide our strategy.
For efficiency, I built up a dictionary of all possible die combinations (in the cup) and calculated the odds of losing, along with the expected points, for the next roll from that cup. The strategy function is simple. It just looks at the cup, grabs the losing odds, and decides to roll if the losing odds are less than the cutoff. I ran a sweep over all cutoff points for those odds, and here are the results:
We got a little bit close to the 2.2 that we’ve been seeing, but not consistently close. This is probably because the strategy is too generic, and while it might help us take advantage of the ‘all the reds on the table’ cases, it washes out for all the other arrangements.
Maybe avoiding losing isn’t the best way to look at it (because we could get only runners and shotguns). Instead of the probability of losing, let’s use the probability of getting any points:
So close! Let’s re-run the simulations over a smaller region and see what we get:
Close, as usual, but no cigar (do zombies smoke?). Notice how jagged the plot is? That’s because there are only 390 unique sets of die that can be in the cup. With only 390 probability values, you’ll get the kind of jagged, discrete type behavior that you see above.
One More Roll Couldn’t Hurt…
We’ve tried some simple strategies, and tried some that are based on the actual odds of getting points or losing. Of all the strategies, the best one seems to be limiting your maximum progress (for safety), but avoiding that rule when its safer. Is somewhere around 2.25 points the maximum expected value of a turn? Maybe! Or maybe there’s a better strategy out there.
I also didn’t cover end game strategy, which is its own problem. In that case, being the first to cross 13 points leaves you in an interesting position. You have to balance your chance of winning with everyone else’s chance of beating you. Player order and scores are needed to make that call. For a player on their last turn (trying to beat the winner), their strategy reduces to “keep rolling until you win or bust”.
What kinds of strategies do you use, or think might work well? Share in the comments and I might even have some time to run them in the simulation!
Also, keep in mind that there are many different ways to measure Zombie Dice performance. We could have tried to optimize for non-losing turns, for example. If you have better suggestions for measuring Zombie Dice performance, share them!
There are many other games that follow this kind of idea. Yahtzee is the most obvious one. In Yahtzee, the die aren’t withheld based on strict rules, but based on the player’s preferences. You can do the same kind of simulation, though, and try out different strategies. One thing to do is control the amount of looking ahead that you do and see how that impacts the results. It’s also, of course, fun to play with how risk-averse a player is.
Take it to the next level and have multiple simulated players go head to head with different strategies!
Finally, if you’re thinking about designing your own game, consider using simulation as a way to test balance before you test with people. Live testing can be time-consuming, and if you can detect any problem areas (or fun killing meta gaming), then you can hopefully use that live testing time more effectively.
Happy simulating and gaming!