Here's a board game simulation I wrote in Python to work out which strategy is the best to win the kid's game Hoot Owl Hoot!
My 5-year-old loves to play this board game made by Peaceable Kingdom where the players work cooperatively to move the owls along a path to the nest before running out of time.
We were all playing one day and ended up debating on what the best strategy is to win - do you always move the piece that can go the farthest, or do you always move the piece in the back? It didn't seem like a clear answer at first - there are several game pieces and one moves on each turn to a color space chosen from the shuffled deck of cards, but they can jump over other pieces if there is one occupying the space they would otherwise land on. I thought it was best to move the one in the back so as to not strand one far behind the others (and therefore maximize the chance of getting to jump over spaces), while my wife thought you should always figure out which piece can go the farthest and move that one.
Not satisfied with just not knowing the answer, I wrote a Python script that simulates the game. In this script only four owls are used (the way we usually play) and it doesn't bother including the sun chip - for my purposes it was sufficient to just see which strategy won in the fewest moves.
The game is fairly simple to simulate - I made an array that holds the actual colors on the path which I copied from the game board, and there is an array of owl positions on that path.
Each turn, a random color value is chosen and then some simple logic can move an owl forward to the next matching color while also checking for owls occupying the space that it could jump over and checking if it has reached the nest:
I wrote functions that would be used to evaluate the potential moves for each strategy by simply finding the rearmost owl:
and finding the longest potential move:
This got wrapped up in the main program that is run in a terminal and lets you choose the number of iterations to run and then gives the statistics on the result:
It turns out that the two strategies are remarkably close, despite leading to very different games each time, but if you run it through very many iterations, the longest move strategy tends to be best most of the time:
Here's the code. It's a bit messy with some nearly-duplicate functions since I built it up layer-by-layer and included a lot of stuff earlier on to print out the whole game board and detail each move to make sure the algorithms were correct before just running them without printouts to collect the statistics.
I found this interesting, and couldn't help but think about how your code does a random draw for the deck of cards. However, in practice the deck has 36 color cards (six of each color) -- at least, this is my takeaway from reading the rules online.
ReplyDeleteThis means that the deck has a fixed distribution that could, in practice, affect the results. Drawing a Red now means a lower chance of a Red later. Does this matter? I decided to find out!
I replaced the card drawing code in your script with one that (clumsily, I had to learn enough Python to do it) simulates drawing from a deck of 36 cards (6 of each color) and reshuffles the deck if it ever empties during a game.
Then I ran the simulation a whole bunch. Here is the business end of a 1000-game set:
game 998: long strategy won in 20 moves, rear strategy won in 25 moves
game 999: long strategy won in 22 moves, rear strategy won in 25 moves
game 1000: long strategy won in 24 moves, rear strategy won in 26 moves
long strategy won in 24.363 moves and rear strategy won in 25.607 moves on average
[24.363, 25.607]
long strategy won 646 times, rear won 211 times, 143 ties
longest move strategy was best in this set
Reshuffles:160
Observations with accurately-simulated deck:
- The long strategy clearly wins more (like, A LOT MORE) 646 wins vs 211
- Over 10% of games require a reshuffle (which suggests deck distribution does play a part)
In your original script (random color draw) 1000 games:
- Long strategy wins, but by less of a margin (498 vs 340 in your original script)
Anyway, I thought that was neat. Same result, but wider margin.
Wow this is great, thank you for posting. I realized after I wrote this up that doing a random card instead of an actual fixed deck was an issue, but I didn't expect it to make such a big difference
DeleteIt's not really an accurate representation of the game without the sun cards. The sun cards limit the number of total turns. The only way you make it through the entire deck is if a sun card is the last card of the deck, and if the owls haven't made it yet, then it's a loss. Everyone loses or everyone wins. I suspect that an accurate simulation (including 3 cards drawn per player) might uncover some other nuances of the game and perhaps some alternate strategies. But then that wouldn't be as simple to test, sooo....
DeleteThe board in your code only has 37 spaces, but the board in the picture has 39.
ReplyDeleteIn the picture... Blue, Purple, and Red each have 7 spots, while Yellow, Green, and Orange only have six.
If your going to go through all the trouble, you might as well simulate the sun cards and spots as well. And would it be that much more trouble to add some graphics? lol :-) Nice work btw.
The game lets you pick a number of owls to start with to adjust the difficulty, and we usually do 4 (which leaves the first 2 spaces unused), so that's what I simulated
DeleteOh, I see. That makes sense.
DeleteYou have a really cool blog here. I'm always on the lookout for Python projects to hone my skills on and I'd like to do something like this. Your other projects are really cool as well. Thanks for posting them.