I am trying to create a tennis tournament simulator. There are 128 players in the tournament, and 32 seeds. I have a vector filled with 'Player' objects named 'Players'. Each 'Player' object has three member variables, Strength, Ranking, and ATPPoints. When the vector 'Players' is first declared, the default constructor for each 'Player' object is used, which initialises each of the 'Player' objects to zero. I then generate a Strength value in the range of 0-10 for each of these 'Player' objects, and sort them descendingly according to their strength.

I now want to create the draw for a tournament. I declare a vector 'Draw' with 128 'slots'. Each slot initially contains a blank 'Player' object, with its member variables set to 0. I first place the top 32 players throughout the draw such that they do not meet until later rounds as they would be in real life. For the remaining players, I position them in the draw by generating a random number between 0 and 127. Some of the slots 0-127 are occupied by seeded Players. These seeded Players have non zero Strength and Rank values, so to avoid overwriting these slots, I check the Strength value of the 'Player' object in the slot in question. A return value of 0 would indicate an empty slot.

At the beginning of the function, I output the Strength and Rank values of the 'Player' objects already in the draw. This outputs correctly.

    for (Player& p : Draw)
    {// outputz the draw in it's current state, with the already placed seeds as expected. 
        cout << p.GetRanking() << " " << p.GetStrength() << endl;
    }

For reasons I don't understand though, later in the function when I read the values as below, 0 is returned regardless of which slot is selected.

    double CurrentSlotStrength = Draw.at(Slot).GetStrength();
    double CurrentSlotRank = Draw.at(Slot).GetRanking();

I have included the rest of my code below. The function is question is 'SelectRandomSlot'. Please could someone explain why I am seeing different values.

int SelectRandomSlot(vector<Player> &Draw)
{
    for (Player& p : Draw)
    {// outputz the draw in it's current state, with the already placed seeds as expected. 
        cout << p.GetRanking() << " " << p.GetStrength() << endl;
    }
    //minstd_rand is a simple multiplicative congruential pseudo-random number generator
    //minstd_rand Gen;
    mt19937 Gen;  // Now using mersenne_twister_engine
    int LowerLimit = 0;
    int UpperLimit = Draw.size()-1;
    uniform_int_distribution<> Dist(LowerLimit, UpperLimit);

    bool Found = false;
    int Slot = 0;
    while(!Found)
    {
        Slot = Dist(Gen);// generates a number pseudo randomly between 0 and 127.
        double CurrentSlotStrength = Draw.at(Slot).GetStrength();
        double CurrentSlotRank = Draw.at(Slot).GetRanking();
        // Draw is initialized with blank instantiations of Player, with the member variables
        // of Strength, Rank, and ATPPoints set to 0.0 (double), 0(int) and 0(int) respectively.
        // Since the seeds have already been placed, the Strength and Ranking values of these
        // should not equal 0. 0 indicates an empty Player.
        if(CurrentSlotStrength==0.0)
        {

            Found = true;
        }
    }

    return(Slot);
}

void CreateTournament(vector<Player> &Players, vector<Player> &Draw)
{
    //-- Place the appropriate number of seeds in the draw.
    int DrawPosition;
    int NoOfParticipants = Draw.size();
    int NoOfSeeds = NoOfParticipants/4;
    for(int i=0; i<NoOfSeeds; i++)
    {
        DrawPosition = SeedPlayer(Players.at(i).GetRanking(),NoOfParticipants);
        Draw.at(DrawPosition) = Players.at(i); // Assumes Players have been sorted descendingly according to rank...
        // ...This happens initially, but must be repeated after each tournament.
    }

    int Remaining = NoOfParticipants-NoOfSeeds;
    for(int j=NoOfSeeds; j<NoOfSeeds+Remaining; j++)
    {
        DrawPosition = SelectRandomSlot(Draw);
        Draw.at(DrawPosition) = Players.at(j);
    }
}

int main()
{
    vector<Player> Players(1000);
    // Player strengths are generated from a normal distribution with a mean of 5 and standard deviation of 2, with values outside the range of 0-10 discarded.
    GeneratePlayerStrengths(Players);
    // end() will return a iterator(pointer) to one past the last element in the array we pass it.
    // Remember the sort function sorts up to but not including what we pass in as the second parameter.
    sort(Players.begin(), Players.end(), SortMethod); // sort descending order

    int i=1;
    for (Player& a : Players)
    {
        // initialise the rank of the players according to their strength
        a.SetRanking(i++);
    }

    vector<Player> Draw(128);
    CreateTournament(Players, Draw);
    //TODO: Create list of tournaments with their participants and wildcards. Then loop through to create all of these tournaments.

    cin.ignore(); // keep the command window open for debugging purposes.
    return 0;
}

class Player
{
public:
    Player()
    {// initialize to 0.0 to indicate 'empty'
    Strength = 0.0;Ranking = 0.0;ATPPoints = 0.0;}

    Player(double val){Strength = val;Ranking = 0.0;ATPPoints = 0.0;}

    virtual ~Player() {}

    double GetStrength() const{return Strength;}

    void SetStrength(double val){Strength = val;}

    int GetRanking() const{return Ranking;}

    void SetRanking(int val){Ranking = val;}

    int GetATPPoints() const {return ATPPoints;}

    void SetATPPoints(int val){ATPPoints = val;}

protected:
private:
    double Strength; //!< Member variable "Strength"
    int Ranking; //!< Member variable "Ranking"
    int ATPPoints; //!< Member variable "ATPPoints"
};

I have a questions for you. Why does Players have a size of 1000 when you only need 128?. If you post the full code I can step through it with my debugger and see what is going on.

Edited 2 Years Ago by NathanOliver

In the future I hope to simulate not only a single tournament, but the entire ATP Tour, in which there are approximately 1000 players. Which tournaments players decide to enter depends on many factors, some of which I hope to model. The project consists of main.cpp and player.h. player./h is just the Player class above. main.cpp is made up of the functions above, plus the few I will post below.

Any help you can give will be greatly appreciated.

// standard headers
#include <iostream>
#include <string>
#include <random>
#include <algorithm>
#include <functional>
#include <vector>
// classes
#include <Player.h>

using namespace std;

// function prototypes
void GeneratePlayerStrengths(vector<Player> &Players);
bool SortMethod(const Player &i, const Player &j);
int SeedPlayer(int rank, int partSize);
int SelectRandomSlot(vector<Player> &Draw);
void CreateTournament(vector<Player> &Players, std::vector<Player> &Draw);

void GeneratePlayerStrengths(vector<Player> &Players)
{
    const int     StarsToDistribute=100;    // maximum number of stars to distribute
    const double  Mean = 5.0;
    const double  StDev = 2.0;
    const double  StrengthLowerLimit = 0.0;
    const double  StrengthUpperLimit = 10.0;
    /*   This is a random number engine class that generates pseudo-random numbers.
     *   It is the library implementation's selection of a generator that provides at
     *   least acceptable engine behaviour for relatively casual, inexpert, and/or lightweight use.
     */
    default_random_engine generator;
    normal_distribution<double> distribution(Mean,StDev);

    int p[(int)StrengthUpperLimit]= {};

    int i = 0;
    while(i<Players.size())
    {
//  operator() generates the next random number in the distribution
        double number = distribution(generator);
        if ( (number>=StrengthLowerLimit)&&(number<StrengthUpperLimit) )
        {
            // int(number) takes the integer part of the number
            // ++p increments the value in p[int(number)]
            //Formula to round to 2DP: d = floor((d*100)+0.5)/100.0;
            Players[i].SetStrength(number);
            i++;
            ++p[int(number)];
        }
    }

//  cout << "normal_distribution (" << Mean << "," << StDev << "):" << endl;

    // for each of the bins
//    for (int i=0; i<(int)StrengthUpperLimit; ++i)
//    {
//        cout << i << "-" << (i+1) << ": ";
//        cout << string(p[i]*StarsToDistribute/Players,'*') << endl;
//    }

//  for(int j=0;j<Players;j++)   {
//        cout << PlayerStrengths[j] << endl;
//  }

}

bool SortMethod(const Player &i, const Player &j)
{
    return (i.GetStrength() > j.GetStrength());
}

/**
 * @rank - rank of the player, best player == 1, second best player == 2
 * @partSize - number of total free positions. Has to be a power of 2 (e.g.2,4,8,16,32)
 * @return the start position of the player, zero based
 */
int SeedPlayer(int rank, int partSize)
{
    // base case, if rank == 1, return position 0
    if (rank <= 1)
    {
        return 0;
    }
    // if our rank is even we need to put the player into the bottom part of the draw
    // so we add half the part size to his position
    // and make a recursive call with half the rank and half the part size
    if (rank % 2 == 0)
    {
        return partSize / 2 + SeedPlayer(rank / 2, partSize / 2);
    }

    // if the rank is uneven, we put the player in the left part
    // since rank is uneven we need to add + 1 so that it stays uneven
    return SeedPlayer(rank / 2 + 1, partSize / 2);
}

I have stepped through your code and it is working and putting new players in open slots. One thing with it is that every time you enter the function SelectRandomSlot(vector<Player> &Draw) the seed is the same for the random number generator and it is generating the same sequnce every time. this could cause some very long delays as you get father down the list of players. You might want to look at creating enough players to fill the open spots in the vector and then using random shuffle to mix them up and then just insert them into the first blank spot you get to. The why I have your code when i tested it is as follows:

#include <iostream>
#include <string>
#include <random>
#include <algorithm>
#include <functional>
#include <vector>

using namespace std;

class Player
{
public:
    Player()
    {// initialize to 0.0 to indicate 'empty'
    Strength = 0.0;Ranking = 0.0;ATPPoints = 0.0;}

    Player(double val){Strength = val;Ranking = 0.0;ATPPoints = 0.0;}

    virtual ~Player() {}

    double GetStrength() const{return Strength;}

    void SetStrength(double val){Strength = val;}

    int GetRanking() const{return Ranking;}

    void SetRanking(int val){Ranking = val;}

    int GetATPPoints() const {return ATPPoints;}

    void SetATPPoints(int val){ATPPoints = val;}

protected:
private:
    double Strength; //!< Member variable "Strength"
    int Ranking; //!< Member variable "Ranking"
    int ATPPoints; //!< Member variable "ATPPoints"
};

// function prototypes
void GeneratePlayerStrengths(vector<Player> &Players);
bool SortMethod(const Player &i, const Player &j);
int SeedPlayer(int rank, int partSize);
int SelectRandomSlot(vector<Player> &Draw);
void CreateTournament(vector<Player> &Players, std::vector<Player> &Draw);

int SelectRandomSlot(vector<Player> &Draw)
{
    for (Player& p : Draw)
    {// outputz the draw in it's current state, with the already placed seeds as expected. 
        cout << p.GetRanking() << " " << p.GetStrength() << endl;
    }
    //minstd_rand is a simple multiplicative congruential pseudo-random number generator
    //minstd_rand Gen;
    mt19937 Gen;  // Now using mersenne_twister_engine
    int LowerLimit = 0;
    int UpperLimit = Draw.size()-1;
    uniform_int_distribution<> Dist(LowerLimit, UpperLimit);

    bool Found = false;
    int Slot = 0;
    while(!Found)
    {
        Slot = Dist(Gen);// generates a number pseudo randomly between 0 and 127.
        double CurrentSlotStrength = Draw.at(Slot).GetStrength();
        double CurrentSlotRank = Draw.at(Slot).GetRanking();
        // Draw is initialized with blank instantiations of Player, with the member variables
        // of Strength, Rank, and ATPPoints set to 0.0 (double), 0(int) and 0(int) respectively.
        // Since the seeds have already been placed, the Strength and Ranking values of these
        // should not equal 0. 0 indicates an empty Player.
        if(CurrentSlotStrength==0.0)
        {

            Found = true;
        }
    }

    return(Slot);
}

void CreateTournament(vector<Player> &Players, vector<Player> &Draw)
{
    //-- Place the appropriate number of seeds in the draw.
    int DrawPosition;
    int NoOfParticipants = Draw.size();
    int NoOfSeeds = NoOfParticipants/4;
    for(int i=0; i<NoOfSeeds; i++)
    {
        DrawPosition = SeedPlayer(Players.at(i).GetRanking(),NoOfParticipants);
        Draw.at(DrawPosition) = Players.at(i); // Assumes Players have been sorted descendingly according to rank...
        // ...This happens initially, but must be repeated after each tournament.
    }

    int Remaining = NoOfParticipants-NoOfSeeds;
    for(int j=NoOfSeeds; j<NoOfSeeds+Remaining; j++)
    {
        DrawPosition = SelectRandomSlot(Draw);
        Draw.at(DrawPosition) = Players.at(j);
    }
}

void GeneratePlayerStrengths(vector<Player> &Players)
{
    const int     StarsToDistribute=100;    // maximum number of stars to distribute
    const double  Mean = 5.0;
    const double  StDev = 2.0;
    const double  StrengthLowerLimit = 0.0;
    const double  StrengthUpperLimit = 10.0;
    /*   This is a random number engine class that generates pseudo-random numbers.
     *   It is the library implementation's selection of a generator that provides at
     *   least acceptable engine behaviour for relatively casual, inexpert, and/or lightweight use.
     */
    default_random_engine generator;
    normal_distribution<double> distribution(Mean,StDev);

    int p[10]= {};

    int i = 0;
    while(i<Players.size())
    {
//  operator() generates the next random number in the distribution
        double number = distribution(generator);
        if ( (number>=StrengthLowerLimit)&&(number<StrengthUpperLimit) )
        {
            // int(number) takes the integer part of the number
            // ++p increments the value in p[int(number)]
            //Formula to round to 2DP: d = floor((d*100)+0.5)/100.0;
            Players[i].SetStrength(number);
            i++;
            ++p[int(number)];
        }
    }

//  cout << "normal_distribution (" << Mean << "," << StDev << "):" << endl;

    // for each of the bins
//    for (int i=0; i<(int)StrengthUpperLimit; ++i)
//    {
//        cout << i << "-" << (i+1) << ": ";
//        cout << string(p[i]*StarsToDistribute/Players,'*') << endl;
//    }

//  for(int j=0;j<Players;j++)   {
//        cout << PlayerStrengths[j] << endl;
//  }

}

bool SortMethod(const Player &i, const Player &j)
{
    return (i.GetStrength() > j.GetStrength());
}

/**
 * @rank - rank of the player, best player == 1, second best player == 2
 * @partSize - number of total free positions. Has to be a power of 2 (e.g.2,4,8,16,32)
 * @return the start position of the player, zero based
 */
int SeedPlayer(int rank, int partSize)
{
    // base case, if rank == 1, return position 0
    if (rank <= 1)
    {
        return 0;
    }
    // if our rank is even we need to put the player into the bottom part of the draw
    // so we add half the part size to his position
    // and make a recursive call with half the rank and half the part size
    if (rank % 2 == 0)
    {
        return partSize / 2 + SeedPlayer(rank / 2, partSize / 2);
    }

    // if the rank is uneven, we put the player in the left part
    // since rank is uneven we need to add + 1 so that it stays uneven
    return SeedPlayer(rank / 2 + 1, partSize / 2);
}

int main()
{
    vector<Player> Players(1000);
    // Player strengths are generated from a normal distribution with a mean of 5 and standard deviation of 2, with values outside the range of 0-10 discarded.
    GeneratePlayerStrengths(Players);
    // end() will return a iterator(pointer) to one past the last element in the array we pass it.
    // Remember the sort function sorts up to but not including what we pass in as the second parameter.
    sort(Players.begin(), Players.end(), SortMethod); // sort descending order

    int i=1;
    for (Player& a : Players)
    {
        // initialise the rank of the players according to their strength
        a.SetRanking(i++);
    }

    vector<Player> Draw(128);
    CreateTournament(Players, Draw);
    //TODO: Create list of tournaments with their participants and wildcards. Then loop through to create all of these tournaments.

    cin.ignore(); // keep the command window open for debugging purposes.
    return 0;
}

i did have to change line 110 from int p[(int)StrengthUpperLimit]= {}; to int p[10]= {}; since StrengthUpperLimit is not const

Edited 2 Years Ago by NathanOliver

It places the players yes, but it overwrites the previously placed seeded players. Lines 65 and 66 (in yours above) return 0 every time, regardless of whether a seed is there or not. Line 51 correctly shows the value has been set though - can you see why?

I did notice the generator seem being the same every time, I am not very confident in how the generation works though, how can I make the seed different each time?

For reasons I don't understand, its working as expected now. Thank you for your help.

This, (to me), seems to be a much simpler 'first go' ... and a possibly more efficent re-design ....

(that yields sets of Playoff Players all ready initialed in a random simulation) ...

and thus it may be of interest to you, (and easier for you to understand and re-work?)

Take a look:

// test_classPlayoff.cpp //

#include <iostream>
#include <iomanip>
#include <vector>
#include <cstdlib> // re. rand, srand
#include <ctime>
#include <algorithm> // re. vector sort


using namespace std;

const int PLAYOFF_SIZE = 128;

struct Player
{
    int id;
    int grade; // 1..10
    Player( int grade=0 ) { ++ count; id = count; this->grade = grade; }

    static int count;
};

// ok to set inital value here ... //
int Player::count = 0;

bool myCmp( const Player& a, const Player& b )
{
    return b.grade < a.grade; // re. desending sort .... //
}

ostream& operator << ( ostream& os, const Player& pl )
{
    return os << " id<" << setw(3) << pl.id
              << ">:grade <" << setw(2) << pl.grade << '>';
}



class Playoff
{
public:
    Playoff()
    {
        players.reserve(PLAYOFF_SIZE);
        srand( time(0) );
        for( int i = 0; i < PLAYOFF_SIZE; ++i )
        {
            Player tmp( rand() % 10 + 1 );
            players.push_back( tmp );
        }
        sort( players.begin(), players.end(), myCmp );
    }
    typedef vector< Player >::iterator iter;
    typedef vector< Player >::const_iterator const_iter;

    iter begin() { return players.begin(); }
    iter end() { return players.end(); }

    const_iter begin() const { return players.begin(); }
    const_iter end() const { return players.end(); }

private:
    vector< Player > players;
    friend ostream& operator << ( ostream& os, const Playoff& poff )
    {
        for( const_iter it = poff.begin(); it != poff.end(); ++it )
             os << *it << ' ';
        return os;
    }
} ;



int main()
{
    Playoff year1; // get initial values/state ...

    cout << year1 << endl; // show all ...


    // now check frequency distribution of 'grades' among id's

    int grade_freqs[11] = {0}; // get empty array all set to 0

    Playoff::const_iter it;
    for(  it = year1.begin(); it != year1.end(); ++it )
    {
        ++grade_freqs[ it->grade ];
    }

    cout << "Showing frequency distribution of grades ... \n";
    for( int i = 1; i <= 10; ++ i ) // start at 1 ... go to 10 //
         cout << setw(5) << i;
    cout << endl;

    int tot = 0;
    for( int i = 1; i <= 10; ++ i )
    {
        tot += grade_freqs[i];
        cout << setw(5) << grade_freqs[i];
    }
    cout << '\n' << fixed << setprecision(2);
    for( int i = 1; i <= 10; ++ i )
    {
        cout << setw(5) << float(grade_freqs[i])/tot;
    }
    cout << "\n\nTotal is " << tot; // X-check that it matches //

    cout << "\n\nPress 'Enter' to continue/exit ... " << flush;
    cin.get();
}

Edited 2 Years Ago by David W

This article has been dead for over six months. Start a new discussion instead.