Monopoly Simulation: Hack a board game in Python and learn MatPlotLib

By Russell Barnes. Posted

Hack the board game of Monopoly by figuring out which properties are best to buy. This tutorial simulates a game and figures out the winning squares

Monopoly is a great board game that can be the subject of many arguments. After playing the game for a long time, players seem to land on some board squares more than others. This tutorial simulates a game in Python, and figures out the winning squares. These are they displayed using MatPlotLib.

Some people seem to favour the second street (light blue), while others choose the fourth (orange). Therefore, to settle these arguments, a simple discrete event simulation can be built that describes the game and predicts where the players will land on average.

See also:

In this series, the simulation will be built from simple components into a complex model of the board that predicts the probability of where players will land.

Welcome to the world of ‘discrete event simulations’. These are used to model all sorts of effects, from when trains arrive to particle collisions at the highest energies.

The simulations are constructed from an understanding of the expected probability distributions. These are combined together to form the final event. In the case of trains, the train timetable and the distribution of arrival times can be used as an input probability distribution. For particle physics simulations, the initial probability distributions are taken from theoretical models.

In the case of our Monopoly game, the random elements are the dice and the order of the cards in the Chance and Community Chest decks. Once you’ve got these, you can play games of Monopoly and discover which squares are landed on the most. Armed with this info you can be sure to buy these squares, and improve your chance of winning a game.

In this article, a simple simulation of just the board and the dice is introduced. The first building block in this is the simulation of rolling two dice.

Simulate Monopoly: Rolling the dice

Players move around a Monopoly board by throwing two dice. An ideal six-sided die has the same probability of landing on each side. Therefore, the probability of throwing any of the numbers is the same:

P(1) = P(2) = P(3) = P(4) = P(5) = P(6)

The total probability for all allowed states is defined as one. Therefore, each of the sides of the die has a probability of 1/6 of being selected.

In the Monopoly board game, two dice are rolled rather than one. The probably of throwing a total value on two dice is not the same for the all of the possible total values, since there is more than one way of producing some of the outcomes. The probability of rolling a total of twelve is:

P{6,6} = P(6) x P(6) = 1/36

…since there is only one way to create this total value. However, the probability of rolling a total value of five is the sum of the probability for each combination:

P{2,3} + P{3,2} + P{1,4} + P{4,1} = 4/36 = 1/9

Monopoly Simulation: simulating two dice

The effect of throwing a perfect six-sided die can be simulated by using a random number generator to generate integers between one and six. The Python random library provides a function, randint, that allows integers to be generated between two limits.

The standard random number generator that is packaged with Python is not perfectly random, but is sufficiently random to be used for this project. It generates numbers from a random series, which is initialised from a random number seed. If the seed is not set within the program, then a different seed is chosen each time the Python program that uses the library starts.

Rolling two dice can be simulated by running the randint function twice and adding the result together, where each randint function call produces an independent value.

The twoDice.py program simulates rolling two dice 100 times. The program contains a simple function called rollTwoDice that calls the randint function twice, adds the return values together and returns the result. When the program starts, it creates a list called counters. This list has twelve elements set to zero.

# Import the random package
import random

def rollTwoDice():
  # Generate two random numbers within the 
  # range 1 <= i <= 6 and add them together
  return random.randint(1,6) + random.randint(1,6)


# A list to contain the total value rolled.
# There are 12 elements, because total value you can roll is 12.
counters=[0.]*12

# Roll two dice 100 times.
nRolls = 100
for i in range(nRolls):
  totalValue = rollTwoDice()  # roll the dice

  # Python list indices count from zero. Therefore, have to remove            # one from the totalValue to put it into the right index in the list.
  # count this total value  
  counters[totalValue-1] = counters[totalValue-1] + 1. 
  

# Total probability is always defined as 1.
# Therefore, have to divide by the total number of counted values.
for i in range(len(counters)):
  counters[i] = counters[i] / float(nRolls)
  
# Now print out the probabilities for each of the combinations
print("The probabilities of rolling a total value using two dice:")
for i in range(len(counters)):
  # Need to add one, since Python counts from zero.
  print(" P("+str(i+1)+")="+str(counters[i]))
print(
"where P(n) is the probability of rolling a total of n on two dice.")

A floating point value is used in this list, since the final probability values are floating point numbers. Once the list has been created, the program goes into a loop calling rollTwoDice and recording the values returned in the counters list. Once the simulated dice have been rolled 100 times, all of the counter values are divided by the number of dice rolls. This creates a probability distribution function for a total value on two dice.

Try running the twoDice.py program by typing:

./twoDice.py

Then use a text editor, such as IDLE, to increase the value of nRolls by a factor of 100 or 1000 and rerun the program. As the number of rolls becomes very large, the probability distribution function reported by the program approaches the theoretical probability values for the different results of rolling two dice.

MatPlotLib in Python

The difference between the theoretical probability values and the observed probability values occurs due to statistical uncertainties. As the number of rolls increases, the statistical uncertainty on the resulting probability distribution function drops.

Modelling the board

The Monopoly board has 40 squares. A player starts from the ‘GO’ square and then moves around the board in a clockwise direction until the game finishes. Each of these squares can be given a number from zero to 39, where the ‘GO’ square is zero and the last square on the board is 39. As with the twoDice.py program, the number of times each square is chosen can be counted using a Python list with 40 elements. The only other information that is needed is the current player position.

A simulation of the Monopoly board without any other effects is given in the boardOnly.py program. This program includes the rollTwoDice function, as well as a function called plot. The latter uses the pyplot functions bar and show to produce a bar chart that is shown on the screen. The pyplot function is part of Matplotlib. Therefore, before running this program, the Matplotlib library must be installed.

sudo apt-get install python-matplotlib

The program starts by initialising a Python list called counters with 40 elements that all contain zero. The values are set to be floating points, since the values will be divided by the total number of entries at the end of the program. The number of dice rolls is set to be 1,000,000, so that the entries in the resulting probability distribution function have a low statistical uncertainty. The current position is set to be zero, which corresponds to the ‘GO’ square, then the program loops until the number of dice rolls has been achieved.

Each time the program loops, the rollTwoDice function is used to create a new total value. The total value from the two simulated dice rolls is then added to the current position. If the new position is beyond the 39th square, the total number of squares is removed so that the new position is still on the board. Then the new position is recorded in the counters list.

After the player has gone around the board a few times, the frequency distribution recorded in the counters list approaches the theoretical distribution. The final frequency distribution is then divided by the total number of moves recorded to form the probability distribution function for the board. Rather than just printing out the different probability values, they can also be shown as a histogram using the Matplotlib library. This is achieved by calling the plot function. The resulting plot stays on the screen until the associated window is closed.

# Import the random package
import random

# Import the matplotlib pyplot package
import matplotlib.pyplot as pyplot

def rollTwoDice():
  # Generate two random numbers within the range 
  # 1 <= i <= 6 and add them together
  return random.randint(1,6) + random.randint(1,6)

# A function to create a histogram
def plot(x, y):
  pyplot.bar(x, y)
  pyplot.show()

# The number of squares on the board
nsquares = 40

# A list to contain the total value rolled.
counters=[0.]*nsquares

# A variable to hold the current position
currentPosition = 0

# Set the number of rolls
nRolls = 1000000

# Print a message
print("Rolling two dice " + str(nRolls) + " times...")

# Roll the dice
for i in range(nRolls):
  # roll the dice
  totalValue = rollTwoDice()

  # Move the player to the next position
  currentPosition = currentPosition + totalValue

  # If the player has moved past last square, wrap board around.
  if currentPosition >= nsquares:
    currentPosition = currentPosition - nsquares

  # Count the current position on the board
  counters[currentPosition] = counters[currentPosition] + 1.

# Total probability is always defined as 1.
# Therefore, have to divide by the total number of counted values.
for i in range(len(counters)):
  counters[i] = counters[i] / float(nRolls)
  
# Now print out the probabilities for each of the combinations
print("The probabilities of landing on a given Monopoly square after " + str(nRolls) + " rolls")
for i in range(len(counters)):
  # Need to add one, since Python counts from zero.
  print(" P("+str(i)+")="+str(counters[i]))
print("where P(n) is the probability of landing on the nth Monopoly board square")

# Create a bar chart display
plot(range(len(counters)),counters)

Try running the boardOnly.py program by typing:

./boardOnly.py

The simulation takes several seconds to run, since it is set to run a large number of dice rolls by default. When it finishes, the Matplotlib window will appear, with the histogram of the different probability states. To make the program as simple as possible, the plot is not labelled. However, the x-axis label of ‘Monopoly board square’ and the y-axis label of ‘P(square)’ can be set by calling other Matplotlib functions.

The probability distribution generated by running the boardOnly.py program is not flat because of statistical uncertainty on each counter value. As the number of rolls becomes bigger, the distribution becomes flatter.

The boardOnly.py program produces a basic probability distribution function shape that will no longer be flat when the other effects of the Monopoly board game are added.

In our next article the most important effect in the Monopoly board game will be added. Then other features will be added towards the complexity of the full game. As with all complex simulations, this will allow each of the effects to be observed and quantified. By the end of the series, it will be clear which properties should be bought to win the game.

 

From The MagPi store

Subscribe

Subscribe to the newsletter

Get every issue delivered directly to your inbox and keep up to date with the latest news, offers, events, and more.