BookmarkSubscribeRSS Feed

Monopoly #1 - Basic Example for the Visit Frequency of the Fields

Started ‎02-12-2026 by
Modified ‎03-15-2026 by
Views 259

Introduction

 

In 1903, Elizabeth J. Magie Phillips, who was an anti-monopolist, created the game in order to show the negative aspects of concentrating land in private monopolies. She filed a patent and her game “Landlord’s Game” was self-published in 1906. In the 1930s Parker Brothers issued a game called “Monopoly”. Over the years “Monopoly” evolved and many variations in the layout of the board game and the game rules were created. Today a large number of different variations per country exists. There are game layouts with 40 and game layouts with 52 fields. Specific editions for selected cities and countries exist.

 

This article series explains how you can create a monte carlo simulation or digital twin for the Monopoly(r) board game using SAS Viya. The article series features the following SAS components

  • SAS datastep for the simulation
  • SAS procedures for the data preparation and the visualizations in SGPLOT graphs
  • SAS IML for the calculation of stationary visit probabilities
  • SAS Visual Analytics for a visualization dashboard

 

Overview of the article series of the Monopoly board game simulation in SAS Viya

 

 

 

Overview of of the architecture of the SAS program for the simulation

 

Figure 26.2 shows a generic simulation architecture that can be used to simulate the Monopoly board game. This simulation architecture, however, not only applies to a Monopoly board game simulation, but might be generically used.


 

 mnp overview.jpg

 

Simulation Core

 

The simulation core is implemented as a SAS DATA step. All steps except the last two steps are implemented in a single DATA step. The DO loops for the GAMES, the ROUNDS, and the PLAYERS are nested in this DATA step. This is important for performance considerations and is considered as a best practice. Compare also Wicklin [11] and Wicklin [12].

 

Random Numbers

 

Random number generators are used:

  • to simulate the result of the different dice in the board game
  • to generate random behavior when picking an instruction card from the pile

The game instructions as well as players’ behavior is implemented using different code statements and value assignments.

The values and outcomes for each player’s turn are stored and updated in the respective variables and the respective record is output to the simulation data.

 

Post-Processing of the Simulation Data

 

In the next step, the simulation data are prepared for the particular analyses. Aggregations, transpositions, and further data management take place to prepare the final analysis tables.

These tables are then used to calculate the output statistics, generate graphical and tabular output, and create the result data sets.

 

Explanation of the SAS Code step-by-step

 

Step 1.1 - Defining the macro variables

 

In the first step you define the macro variables for your simulation: 

  • Number of Players
  • Number of Games to be simulated
  • Number of Rounds to be played for each game
  • Seed for the random number generator (positive number create a reproducible sequence of numbers for future runs)

 

%let cnt_players = 5;
%let cnt_games   = 1000;
%let cnt_rounds  = 100;

%let seed  = 1;

*** not relevant for this simulation, will only be used in the next articles;
%let ConsiderJail = no;
%let Doublet3Jail = no;
%let ConsiderChance = no;

%let ScenarioName = "1. Basic Game - Dice only";

 

Step 1.2 - Initialization of the datastep, looping over games and initializing the game

 

  • An ARRAY is used for for the PlayerPos1 - PlayerPos5 variables which hold the position of the respective player
  • The random number generator is initialized using CALL STREAMINIT
  • The first DO LOOP iterates over the games.
  • For each game the variables are initialized and each player is set to position 1.
    • Note that we can use a DO loop over players here as we defined an ARRAY earlier.

 

data work.MNP_Sim1(where=(Round ne 0)) ;
  format Game Round Player Dice1 Dice2 DiceSum 8.;

  ARRAY PlayerPos     {&cnt_players.} PlayerPos1  - PlayerPos&cnt_players. ;
  call streaminit(&seed);

  do Game = 1 to &cnt_games;

        *** Init-Block;
        Round=0; Dice1=.; Dice2=.; 
        do Player = 1 to &cnt_players; 
            PlayerPos[Player]=1; 
        end;

 

Step 1.3 - Looping over Rounds and Players within each game

 

In the next step you 

  • Add nest two DO LOOPs for the rounds and the players
  • Roll the dice using the RAND('UNIFORM') function
  • Sum up the value of the dice with the SUM function
  • To derive the new location for each player (which again is referred to using the PlayerPos-ARRAY)
    • the DICESUM is added to the PLAYERPOS
    • if the player overruns field 40, PLAYERPOS must be set to respective position starting with position 1. This is done using the MOD function
  • you output the generated record from each simulation step using the OUTPUT statement (don't forget!!)
  • you close the DO Loops and the datastep.

 

        do Round = 1 to &cnt_rounds;
            do Player = 1 to &cnt_players;

                Dice1 = ceil(rand('Uniform')*6);    
                Dice2 = ceil(rand('Uniform')*6);
                DiceSum = sum(Dice1,Dice2);

                PlayerPos[Player] + DiceSum;
                PlayerPos[Player] = mod(PlayerPos[Player]-1,40)+1;

                output;
                
              end; *** Player;
        end; *** Round;

 end; *** Game;
run;

 

Step 1.4 - Only the keep the last record for each round

 

The simulation also loops over players and outputs a record for each position. However you only need the state after als players have moved the token. Therefore you need to only keep this record. This can be done using  BY processing in a datastep and using the "LAST." notation to access the respective datastep variable.

 

data work.MNP_Sim1_LastRec;
 set work.MNP_Sim1;
 by Game Round Player;
 drop dice1 dice2 dicesum;
  *** Keep only last record per round;
  if last.round then output;
run;

Note that you could achieve a similar results by moving the OUTPUT statement in the simulation code in step 1.3 from the PLAYER LOOP to the GAME LOOP. However I left it intentionially there as we might need the intermediates steps for profit calculations .

 

Step 1.5 - Transposing the PLAYER position from columns into rows

 

the above code stores the GAMES and the ROUNDS in rows, but the PLAYER position in  columns. In order to get that data also into row, you need to transpose it BY GAME and ROUND, using the TRANSPOSE procedure.

 

proc transpose data=work.MNP_Sim1_LastRec 
                out=work.Player_Long;
 by game round;
 var PlayerPos: ;
run;

 

1.6 - Preparing the results table

 

You now have all your information already in the PLAYER_LONG table to view your results and create graphs. The following datastep prepares the data in a nicer way and already adds metadata information for each simulation run.

 

data work.Player_Location;
 length ScenarioName $40 ConsiderJail ConsiderChance Doublet3Jail $3;
 set work.Player_Long;
 Player=input(compress(_name_,,"ul"),3.);
 Feature=strip(compress(tranwrd(_name_,'Player',''),,"d"));
 if Feature = "Pos" then Feature = "Location";
 rename col1 = Value;
 drop _name_;
  • You extract the player number in variable PLAYER, by only keeping the numeric values in PLAYER1, PLAYER2, ...
  • You rename the feature that is derived from this simulation to LOCATION. Note that we will add additional features for the player like RANK and BALANCE in future simulations.
  • You store the field number in variable VALUE.

 

 ScenarioName = &ScenarioName;
 ConsiderJail = upcase("&ConsiderJail");
 ConsiderChance = upcase("&ConsiderChance");
 Doublet3Jail = upcase("&Doublet3Jail");

run;

Finally you can add variables the describe the settings for the simulation. This is relevant when we append this data to a simulation result repository.

 

1.7 - Creating a histogram for the distribution of the locations

 

Finally you use the SGPLOT procedure to create a histogram for the locations.

Note that we

  • use the macro variable &scenarioname to have a flexible title.
  • specify BINSTART and BINWIDTH to have a separate bar in the histogram for each field
  • use the YAXIS statement to specify the MAX for the value scale.

 

proc sgplot data=work.player_location;
 title Scenario: &scenarioname.;
 histogram value / binstart=1 binwidth=1;
 yaxis max = 6;
run;
title;

 

The resulting histogram reveals what we would have assumed.

  • There is a quite even distribution of the visit frequency, because we did not consider any additional instructions yet.
  • We see a small bump around fields 6-9 because in the first round all players start at position 1 and the sum of two dice is tringle distributed with a peak at 7.
 

Scen1.png

 

Videos for Section 1

 

The following videos have been recorded for the SAS ask-the-expert session on Monopoly simulations which I used to talk about them in the session. (note I forgot to turn of the microphone when recording it, this is why you hear me clicking). 

 

Video 1 shows the code described in section 1.1 to 1.7

 

(view in My Videos)


Video 2 shows what happens if you run the simulation for games with only 10 rounds.

 

You see a more dramatic peak around fields 6-9.

 

(view in My Videos)

 

Step 2 - Considering the "Roll-the-dice again" rule when rolling doubles

 

In some games there is the rule that you must roll the dice again after you rolled a double. The value of your dice rolls is added up for a maximum of 3 doubles and then your new position is identified.

 

In order to achieve this in your SAS program (10.1b MNP Basic Model Doubles.sas) you just extend your code as follows:

  • In case you have a double (DICE1 = DICE2)  your DICESUM is calculated
  • You roll the dice again and add up the sum of the 2nd roll again to DICESUM.
  • You repeat this in case you again roll a dice.

The rest of the code stays as is.

 

 

                Dice1 = ceil(rand('Uniform')*6);    
                Dice2 = ceil(rand('Uniform')*6);
                DiceSum = sum(Dice1,Dice2);

                if Dice1 = Dice2 then do; ** First Doublet;
                   Dice1 = ceil(rand('Uniform')*6);    
                   Dice2 = ceil(rand('Uniform')*6);
                   DiceSum + sum(Dice1,Dice2);

                   if Dice1 = Dice2 then do; ** Second Doublet;
                      Dice1 = ceil(rand('Uniform')*6);    
                      Dice2 = ceil(rand('Uniform')*6);
                      DiceSum + sum(Dice1,Dice2);

                 
                    end; ** Second Doublet;
                end; ** First Doublet;

                    PlayerPos[Player] + DiceSum;
                    PlayerPos[Player] = mod(PlayerPos[Player]-1,40)+1;

 

Video 3 - Shows the code that also considers "roll dice again" after a double has been rolled

 

(view in My Videos)

 

 

Related Articles and Links

 

 

[11] Wicklin, R. 2015. “Ten Tips for Simulating Data with SAS®.SAS Global Forum Proceedings. https://support.sas.com/resources/papers/proceedings15/SAS1387-2015.pdf (Paper SAS1387-2015).

[12] Wicklin, R. 2013. Simulating Data with SAS®. Cary, NC: SAS Institute Inc.

 

Comments

Super fun learning example @gsvolba!  Thanks for sharing! 

Contributors
Version history
Last update:
‎03-15-2026 03:15 PM
Updated by:

Catch up on SAS Innovate 2026

Dive into keynotes, announcements and breakthroughs on demand.

Explore Now →

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags