Brian Gordon

Monopoly board analysis

A lecture on counting and probability made me curious about the relative strengths of monopoly board positions, and before the end of the lecture I had sketched an outline of this program. The monopoly function returns the probability that a piece will be at each position of the board at the end of the given number of dice-rolls.

The strategy that I chose to solve this problem is to calculate the probability of each individual location j + k by summing the probabilities of being at each space j times the probability of rolling a k. This is only a general strategy; many complications appear because of chance cards and other special squares. Each possibility is carefully accounted for so that the probabilities always add to exactly 1.0, so the process can be iterated to produce probabilities after an arbitrary number of moves.

This code is written in Python, which I had never used before and looked interesting. Check out the monopoly board image below; the saturation values of the colors are generated by monopoly and color_map.

def monopoly(moves):
  """Return a list of the probabilities (0-1) of a monopoly piece
  being at each board position after the given number of moves.
  The probabilities are guaranteed to sum to 1."""

  #Pre-fill old with the previous move (the piece must be on Go)
  old = [0,] * 40
  old[0] = 1

  for i in range(moves):
    board = [0,] * 40

    #The probability that the piece ends up at j+k is the probability
    #that the piece was at j times the probability of rolling k to
    #the position j+k. These can be accumulated by trying each possible
    #dice roll on each possible starting position.
    for j in range(40):

      for k in range(2,13):
        cur = (j + k) % 40
        multiplier = old[j] * roll_probability(k)

        #We're handling a Chance card draw. There are sixteen chance cards.
        if cur == 7 or cur == 22 or cur == 36:
          board[0] += (1.0/16) * multiplier #Advance to Go
          board[5] += (1.0/16) * multiplier #Ride on the Reading Railroad
          board[10] += (1.0/16) * multiplier #Go directly to Jail
          board[11] += (1.0/16) * multiplier #Advance to St. Charles Place
          board[24] += (1.0/16) * multiplier #Advance to Illinois Avenue
          board[39] += (1.0/16) * multiplier #Advance to Boardwalk
          board[cur-3] += (1.0/16) * multiplier #Go back 3 spaces

          if cur == 7:
            board[12] += (1.0/16) * multiplier #Nearest utility
            board[15] += (2.0/16) * multiplier #Nearest railroad (2x)
          elif cur == 22:
            board[28] += (1.0/16) * multiplier #Nearest utility
            board[25] += (2.0/16) * multiplier #Nearest railroad (2x)
          elif cur == 36:
            board[12] += (1.0/16) * multiplier #Nearest utility
            board[5] += (2.0/16) * multiplier #Nearest railroad (2x)

          #Account for leaving this space due to "teleport" cards.
          #Ten out of the sixteen chance cards divert the piece somewhere else.
          board[cur] += multiplier * (6.0/16)

        #We're handling a Community Chest draw. There are sixteen CC cards.
        elif cur == 2 or cur == 17 or cur == 33:
          board[0] += (1.0/16) * multiplier #Advance to Go
          board[10] += (1.0/16) * multiplier #Directly to Jail

          #Account for leaving this space due to "teleport" cards.
          #Two out of the sixteen CC cards divert the piece somewhere else.
          board[cur] += multiplier * (14.0/16)

        #We're handling the Go To Jail square
        elif cur == 30:
          board[10] += (1.0) * multiplier

          #Account for leaving this space due to "teleport" cards.
          #The piece is always diverted somewhere else
          board[cur] = 0

        #We're handling a regular square with no chance of teleportation
        else:
          board[cur] += (1.0) * multiplier

    old = board

  return board

def roll_probability(sum):
  """Return the probability of getting a particular sum when rolling
  two die. Valid values for sum are 2-12."""

  return [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1][sum-2] / 36.0;

def color_map(list):
  """Map a list of numbers to discrete values in the range 0-100.
  This is useful for colorizing the relative magnitudes of each element."""

  #Scale the maximum up to 100 but preserve the zero point
  scale_factor = 100.0 / max(list);
  return map(lambda x: round(x * scale_factor), list)

Monopoly board colored by relative probability

Image: The saturation of the colors represents the relative probability of a piece being found on each square after 10 turns. The board is oriented so that Go is the bottom-right square.