Monopoly -- Forum

Pages: 123
I'm trying to write snippet code about monopoly and I would like to know what data structures I should use for a playercount function (like choosing between 2-4 players) and implementing driving code for inserting players into a dedicated data structure for later use (i.e. giving players $1500 to start the game and adjusting the currency based on what property they would land on).

I'm leaning towards a queue but tell me if there is a better solution.
Last edited on
The best data structure depends on how you will use it.

I think you are implying that you want a collection of some sort, but are unsure which one. A queue is a collection, but there are obviously others, too.

What operations do you plan to use with this data structure?
How do you plan to access the individual members?

Without knowing how you plan to use the collection, I can't give advice on what to use. Without any context, my first thought is that a queue doesn't buy you much for this application. Based on my own preconceived ideas of how to go about this, I might lean towards a vector, or if I really wanted to go with the overkill, a map or unordered map. But it all depends on how you design the system.
My plan for operations used on individual members/nodes is a add/substract method for each player when they land on a property or community chest. Such as buying a property, subtracting money when they land on a player owner property, landing on chance/community chest, or passing go.
it seems like a player in monopoly would be an object that had
- money (init 1500)
- get out of jail card(s), or things like utility/RR owned for handy ref?
- possibly other things (a board position, a token, a name, etc)
and you would have a small array like container of these (eg 2 to 4 of them, but the game allows a lot more than 4 players at once).

you probably want a container of all the properties, which have an owner, a count of houses, a status for mortgaged, associated properties (have to own all same color to buy a house, have to build evenly, etc)

you probably want a container of cards, for the 2 decks
not sure what else I am missing, havent played in years... proabably a 'board' or overall graphical object for all the drawing/UI stuff


I don't know that a queue is great here. It may not be 'wrong' but ask yourself what features of a queue you need? It makes some sense to go through the players 1 by one, pull a player off, take their turn, put back on, but that could make it tricksy to pay another player, how to find and get at them... when in doubt, use a vector, until you can demonstrate you need to do something vectors are bad at (like inserting in the middle repeatedly). When designing, just say 'container' at first, the details can wait until you know more.
Last edited on
You are thinking smart with the idea of using multiple structures/containers for the guts of a game.

The actual layout of each requires a lot of thought, though. How are you going to represent each part of the game? And how to all those parts interact.
Here's my official list of structures I plan to use for each:

1. For Community chest/Chance I only plan to have 5 cards for each and I am not worried about get out of jail free at this moment in time mainly just money and space cards. I assume this will use a stack mainly.

2. For Properties/The board I have no clue what I would use for this (possibly a simple vector or array because of the board being static). Would also consider other structures if necessary.

3. For Players I'll use a queue likely enough though the code needs drawing up. I will also have the players auto buy the properties if not owned by a player

4. Dice roll could be a simple array function with a sole purpose of returning the sum of two 6 sided dice. I also want to include a roll-off for who gets to go first.

*I will not be using hotels and houses for simplicity sake.
*Doubles are also not included for simplicity sake and getting out of jail would take three turns after which the user would need to pay $50.

Sample code for the main so far:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main(int argc, char const *argv[])
{
    DynamicQueue<int> playerlist;
    int players;
    do{
        cout << "How many players? ";
        cin >> players;
        switch (players)
        {
        case 2:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        case 3:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        case 4:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        default:
            cout << "Players should be between 2-4" << endl;
            break;
    }

    } while (players > 4 || players < 2);
    int rolloff[players];
    srand(time(0));
    for (int i = 0; i < players; i++)
    {
        rolloff[i] = rollDie();
        cout << rolloff[i] << endl;
    }

    return 0; 
}


Code for rolldie:

1
2
3
4
5
6
7
8
9
10
int rollDie()
{
    int roll;
    int min = 2; // the min number a die can roll is 2
    int max = 12;

    roll = rand() % (max - min + 1) + min;

    return roll;
}
Last edited on
Double reply -- New code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
int main(int argc, char const *argv[])
{
    DynamicQueue<int> playerlist;
    int players, maxRoll;

    //********************************************
    // *Used for Player queue
    // *Takes an input based on playercount 2-4
    //********************************************

    do
    {
        cout << "How many players? ";
        cin >> players;
        switch (players)
        {
        case 2:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        case 3:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        case 4:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        default:
            cout << "Players should be between 2-4" << endl;
            break;
        }

        //********************************************
        // * Rolloff function
        // * Singular usage at beginning of game
        //********************************************

    } while (players > 4 || players < 2);
    int rolloff[players];
    srand(time(0));
    for (int i = 0; i < players; i++)
    {
        rolloff[i] = rollDie();
        cout << rolloff[i] << endl;
    }

    int n = sizeof(rolloff) / sizeof(rolloff[0]);
    // Intialize the value of max and index
    int max = INT_MIN;
    int index = -1;
    // Iterate the array
    for (int i = 0; i < n; i++)
    {
        if (rolloff[i] > max)
        {
            // If current value is greater than max
            // value then replace it with max value
            max = rolloff[i];
            index = i;
        }
    }
    cout << "The Maximum Roll was ";
    cout << max << " by Player " << index + 1 << endl;

    vector<string> board = fillboard();
    cout << "\nThe Gameboard:\n"
         << endl;
    for (int i = 0; i < board.size(); i++)
    {
        cout << board[i] << endl;
    }

    return 0;
}


Output:
How many players? 3
11
11
7
The Maximum Roll was 11 by Player 1

The board

GO
Mediterranean Avenue ($60)
Community Chest
Baltic Avenue ($60)
Income Tax (Pay $200)
Reading Railroad ($200)
Oriental Avenue ($100)
Chance
Vermont Avenue ($100)
Connecticut Avenue ($120)
Jail
St. Charles Place ($140)
Electric Company ($150)
States Avenue ($140)
Virginia Avenue ($160)
Pennsylvania Railroad ($200)
St. James Place ($180)
Community Chest
Tennessee Avenue ($180)
New York Avenue ($200)
Free Parking
Kentucky Avenue ($220)
Chance
Indiana Avenue ($220)
Illinois Avenue ($240)
B&O Railroad ($200)
Atlantic Avenue ($260)
Ventnor Avenue ($260)
Water Works ($150)
Marvin Gardens ($280)
Go To Jail
Pacific Avenue ($300)
North Carolina Avenue ($300)
Community Chest
Pennsylvania Avenue ($320)
Short Line ($200)
Chance
Park Place ($350)
Luxury Tax (Pay $75)
Boardwalk ($400)


For the dice roll for "11" it choses at random who wins the rolloff.
jzilliam wrote:
1
2
3
4
int min = 2;  // the min number a die can roll is 2
int max = 12;

roll = rand() % (max - min + 1) + min;

Note that this will not give you the same probability distribution as rolling two 6 sided dice.
its not wrong, but that switch is a whole lot of code to set up a mere 4 entries.
I will ask again what the queue brings to the table?
compared to
vector<int> players(4,1500);
...get # of players 2-4
resize to # of players (will delete entries for 2 or 3)
4 lines of code tops.
I don't know if there is a cute way to express that with a queue, because I never use them.
Last edited on
Note that this will not give you the same probability distribution as rolling two 6 sided dice.


Yes I agree that the average is bumped from 6 to 7 however this is a temp proof of concept and is subject to change. I did have an array type for dice for the 1st gen and wanted to clean up the spaghetti code I had prior to the code post
here is a simple queue setup to reduce the bloat:

1
2
3
4
5
6
7
8
9
int main()
{	
  queue<int> q({1500,1500,1500,1500});
  int num;
  cin >> num; //assuming num is 2,3,4 only! you still need to validate this etc.
  if(num <= 3) q.pop(); //its 2 or 3, remove 1 is valid. 
  if(num == 2) q.pop(); //its 2, remove another one. 
  cout << q.size();   
}


its not exactly right (or is it? rusty!) but normal instead of uniform distro over 2-12 would be so close a user won't notice.
https://www.thedarkfortress.co.uk/tech_reports/2_dice_rolls.php#.Y2QzlXbMLb0
and
https://cdn.scribbr.com/wp-content/uploads/2020/10/standard-normal-distribution-1024x633.png

https://cplusplus.com/reference/random/normal_distribution/
Last edited on
Yes I agree that the average is bumped from 6 to 7 ...

The average is not the problem. 7 is the correct average.

The difference is that with your code all outcomes are equally likely (about 9% each) but in real monopoly some outcomes are much more likely than others (less than 3% chance of getting 12 compared to almost 17% chance of getting 7).

https://mathinsight.org/image/two_dice_distribution

In response to what jonnin said: This is NOT a normal distribution.

Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <random>
#include <ctime>
using namespace std;

mt19937 gen( time( 0 ) );
discrete_distribution<int> dist{ 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1 };

int roll2dice()
{
   return 2 + dist( gen );
}

int main()
{
   const int nrolls = 1000000;
   int num[13] = {};
   for ( int i = 0; i < nrolls; i++ ) num[roll2dice()]++;
   cout << "i" << '\t' << "fraction\n";
   for ( int i = 2; i <= 12; i++ ) cout << i << '\t' << ( num[i] + 0.0 ) / nrolls << '\n';
}
Why complicate this so much? Just write a function that rolls one die. Then call it twice and compute the sum. It doesn't need to be harder than that.
Since this is C++ code you should probably use a C++ random engine and distribution.

https://en.cppreference.com/w/cpp/numeric/random

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <random>

int roll_1_die()
{
   // create one time a C++ random engine
   // seeding a non-deterministic C++ random engine
   // less problematical than using a time
   static std::mt19937 png(std::random_device {} ());

   // create one time a distribution representing a 6 sided die
   static std::uniform_int_distribution<int> dist(1, 6);

   // roll the die and return the result
   return dist(png);
}

int roll_2_dice()
{
   // roll two dice, add the result and return
   return (roll_1_die() + roll_1_die());
}

int main()
{
   unsigned num_rolls { 25 };

   std::cout << "Let's roll some dice!\n";

   for ( unsigned i { }; i < num_rolls; ++i )
   {
      if ( 0 == (i % 10) ) { std::cout << '\n'; }

      std::cout << roll_2_dice() << '\t';
   }
   std::cout << '\n';
}

See also: https://open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3551.pdf
Using the C random functions in C++ code has some potentially serious bias problems.

https://web.archive.org/web/20180123103235/http://cpp.indi.frih.net/blog/2014/12/the-bell-has-tolled-for-rand/
There's a more elegant way to write the switch statement by changing the order of the cases and using [[fallthrough]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (players)
        {
        case 4:
            playerlist.enqueue(1500);    
            [[fallthrough]];
        case 3:
            playerlist.enqueue(1500);
            [[fallthrough]];
        case 2:
            playerlist.enqueue(1500);
            playerlist.enqueue(1500);
            break;
        default:
            cout << "Players should be between 2-4" << endl;
            break;
        }


However, in looking at your code, I think it's unlikely that playerlist will end up being a queue of ints. playerlist should be a collection of Player objects. A Player object should contain all attributes about an individual player.
As Jonin mentioned:
- Amount of cash
- Token
- Player name
- Board position
- Owned properties
- etc.

For your consideration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <string>
#include <iostream>
#include <vector>
using namespace std;

struct Player
{
    int     cash;
    int     board_pos;
    int     token;
    string  player_name;
};

int main(int argc, char const* argv[])
{
    vector<Player>  playerlist;
    
    int players;
    do {
        cout << "How many players? ";
        cin >> players;        
    } while (players > 4 || players < 2);
    //  Populate the playerlist
    for (int i = 0; i < players; i++)
    {
        Player      player;
        //  Populate player info
        playerlist.push_back(player);
    } 
    for (auto player : playerlist)
    {   //  Range based for loop Iterates through each player
    }
}


Last edited on
A variation is to move who owns what property into the structure describing each lot.

The decisions on segmenting out the various containers needed and how they interact should be planned out in detail long before writing a single line of code. It is easier to change a container's contents, etc. on paper.
IMO you should first design the data structures and then consider the containers and the actions required on them. This should point you towards one of the standard containers. Rule of thumb - unless otherwise indicated use a std::vector.

Without doing a whole lot of design work, I'd think std::vector for the players and a std::queue for the Community Chest/Chance cards. Don't forget that you'll also probably need a banker and a way of representing the board. How the board is represented (??? a std::vector of some polymorphic object - or a std::vector of a std::variant) - together with the properties owned and house/hotels built etc is probably the more interesting (complex) and IMO it's what I'd be designing first. There's probably a relationship between the board and the players container. Until you're designed these on paper and played some rounds using it on paper I wouldn't be thinking about writing any code. Design first then code. In the scheme of things, how to roll a couple of die etc etc shouldn't be high on the implementation priority list.
In response to what jonnin said: This is NOT a normal distribution.

Right, thanks for that. I brushed up a little & did a quick'n'dirty empirical curve fit just to see what we could see, though.
not too shabby against the actual probability -- the user would never know the difference: (for reference 2.7,5.5,8.3,11,14,16.5 ... vs 2,5,8,11,13,14...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{  
  std::default_random_engine generator;   
  std::normal_distribution<double> distribution(7.5,2.75); //played around with these a bit. 
  int ct[13]{}; //result tracking
  generator.seed(time(0));
  int flips{1000000};  //this started life as a coin tosser
  for(int i{}; i<flips; i++)
	  {	  
	    ct[(int)distribution(generator)]++;	
	  }   
   for(int i = 2; i < 13; i++)
   cout << i << " " << ct[i]<<" " << ct[i]/1000000.0 << endl;
}



C:\c>a
2 28227 0.028227
3 50616 0.050616
4 80444 0.080444
5 110783 0.110783
6 134874 0.134874
7 144278 0.144278
8 135857 0.135857
9 110959 0.110959
10 79885 0.079885
11 50848 0.050848
12 27911 0.027911

C:\c>a
2 28155 0.028155
3 50455 0.050455
4 80223 0.080223
5 111385 0.111385
6 135407 0.135407
7 143818 0.143818
8 135773 0.135773
9 110919 0.110919
10 80019 0.080019
11 50236 0.050236
12 28159 0.028159

C:\c>a
2 28075 0.028075
3 50412 0.050412
4 80392 0.080392
5 111289 0.111289
6 134863 0.134863
7 144368 0.144368
8 134991 0.134991
9 111067 0.111067
10 80245 0.080245
11 50392 0.050392
12 28416 0.028416




and all that to say rolling 2 6 is "correct" not only for the exact probability but because of the doubles being important in this specific game, it matters if 6 is 3&3 instead of 4 & 2.
Last edited on
Pages: 123