Full Version: Generating Random Starmaps in C++
Dream.In.Code > Programming Tutorials > C++ Tutorials
dogboi
Generating Random Starmaps in C++ - Part I: The Basics

Strategy games set in space, especially 4X games, generally require a Starmap. You could create a Starmap in advance, but this limits playability. A better option is to generate Random Starmaps. This tutorial will give you a simple method of doing just that.

In order to generate a random Starmap, we first need to know what information we're going to need. For this example, it's not much. Because this is meant for a 4X game, we'll only need the information about a star that players can see, and later on, the information necessary to generate planetary systems. So we need the stars location, its size, and its stellar classification.

The location of a star is simply its x and y coordinates (or x, y, and z on a 3d map). Note that these x,y coordinates will be tile positions, not screen positions. (So 0,0 will be the top left tile, 0,1 the tile underneath that, etc.) The size of the star will just be a number from 1 to 3, or the number 5 for supergiants.

Stellar Classification takes a little explanation if you know little about astronomy. Astronomers classify stars based on their heat using a series of letters. The order, from hottest to coldest is O,B,A,F,G,K, and M. The mnemonic generally cited to remember this order is "Oh Be A Fine Girl, Kiss Me." Each classification is then further divided in 10 parts, designated from hottest to coldest with the numbers 0 to 9. So you could have for example, a hot star of classification O3, a temperate star of F5, and a very cold star classified M8. Earth's sun, Sol, is a G2. We're not going to get this detailed, though. We'll stick to the seven letters. Note that this classification ignores things like brown dwarfs, quasars, pulsars, black holes, etc. They could be added fairly easily and this is left as an exercise for the reader.

So our first code example is the class for an individual star.

CODE

class Star
{
    private:
        int x,y;   // The stars location on the starmap grid
        int classification; // The stellar classification
        int size; //The size of the star ranges from 1 to 3, and then 5 for super giants
        // char *name;      This will be taken care of in another tutorial
        // Planet *planets;   Again, in another tutorial
        Star *next;  // The next star

    public:
        Star(int, int); //constructor
        ~Star(void); //destructor
        void Add(Star *); // Add a star to the list
        int here(int, int); // Is there a star at the given coordinates?
        Star * GetStar(int, int); //return the star at the given location, otherwise return null
        int GetClassification(void); //Get this star's classification
        int GetSize(void); //Get this star's size
        Star *GetNext(void); // return the next star
        int GetX(void);  // Get this star's X coordinate
        int GetY(void);  // Get this star's Y coordinate
};


I implemented the Star class as a linked list. This was a personal choice. You could just use arrays or even vectors in the Starmap class.

So let's look at the methods of the Star Class, we'll start with the constructor and destructor

CODE

Star::Star(int x_a, int y_a)
{
    x=x_a;
    y=y_a;
    int temp;  // This variable will be used to determine if the star is a giant

    classification=rand() % 7;  // This will generate a random number from 0 to 6, for the OBAFGKM classification

    temp=(rand() %100);         // Generate a random number between 0 and 99

    if(temp<PERCENT_GIANTS)          // is this a supergiant
        size=5;                      // then it's size is 5
    else                             // otherwise
        size=(rand() % 3) + 1;       // This will generate a random size from 1 to 3
    next=NULL;    // There is no next star yet
}

Star::~Star(void)
{
    if(next)            // If there is a star after this one
        delete next;    // Delete it first
    

}


There's a variable used in the constructor that we haven't seen yet. That's PERCENT_GIANTS. It's defined in the header file for the classes. The definitions defined there are as follows:

CODE

// DEFINES

#define PERCENT_STARS .10    // The fraction of the map that has stars
#define PERCENT_GIANTS  3    // The chance that any given star is a super giant

#define STAR_O      0
#define STAR_B      1
#define STAR_A      2
#define STAR_F      3
#define STAR_G      4
#define STAR_K      5
#define STAR_M      6


I chose PERCENT_STARS and PERCENT_GIANTS arbitrarily. Feel free to change them.

To add a star to the list, use the Add(Star *) method.

CODE

void Star::Add(Star *n)
{
    if(!next)               // If this star doesn't point to another star
        next=n;             // Then add the passed star to this one
    else                    // Otherwise
        next->Add(n);       // Let the next star handle it
}


Okay, for those not familiar with linked lists and pointers, a little explanation is probably necessary. The *next pointer points to the next star object in the list. If there is no next star, it is simply NULL. So the if(!next) will be true if the pointer is NULL, ie, if there's no next star. Otherwise, if there is one, we let the next star decide what to do with the object. (ie, pass it on, or add it to itself).

The destructor works the same way. If there is a next star, it has to be deleted first, all the way down the list.

The next two methods work the same way as well. Using the recursive nature of lists, they pass information down the line, and also return information back up the line.

CODE

int Star::here(int x_a, int y_a)
{
    if((x==x_a) & (y==y_a))                 // Am I at the given location
        return 1;                           // Okay, then return true
    else                                    // Otherwise
        if (next)                           // Is there another star?
            return next->here(x_a, y_a);    // If so, let it handle the request
        else                                // No more stars and no match found
            return 0;                       // return false
}

Star * Star::GetStar(int x_a, int y_a)
{
    if((x==x_a) & (y==y_a))                 // Am I at the given location
        return(this);                       // Hey, it's me.  So return me
    else                                    // Otherwise
        if(next)                            // Is there another star?
            return next->GetStar(x_a, y_a);  // If so, let it handle the request
        else                                // No more stars and no match found
            return NULL;                    // Return NULL
}


The first method, here(int, int), just returns a boolean. It basically answers the question "Is there a star at this x and y coordinate position." This is necessary for the star map generation routine to make sure that no stars are created in the same place.

The second method, GetStar(int, int), works the same way, and just returns a different value. If there is a star at a given coordinate position, it returns a pointer to that star. Otherwise, it returns NULL.

The rest of the methods in the Star class are pretty self explanatory.

CODE

int Star::GetClassification(void)
{
    return classification;  // Duh
}

int Star::GetSize(void)
{
    return size;  // Duh
}

Star * Star::GetNext(void)
{
    return next; // Duh
}

int Star::GetX(void)
{
    return x;   // Duh
}

int Star::GetY(void)
{
    return y;   // Duh
}


Now that we know how to deal with stars, let's deal with the map

The Starmap

The Starmap class is simpler than the Star class. It contains the width and the height of the map (in tiles), and a pointer to the stars in the map.

CODE

class StarMap
{
    private:
        int width, height; // The size of the starmap in tiles
        Star * stars;  // Pointer the stars in the map
    public:
        StarMap(int, int);  // Constructor - passed width and height
        ~StarMap(void); // Destructor
        void Dump(void); // Dump the Starmap to an HTML file
};


Pretty self explanatory. The Dump() method will dump a copy of the starmap to an HTML file so we can look at it.

So let's take a look at the methods. We'll start with the Constructor, which actually creates a starmap and populates it with stars.

CODE

StarMap::StarMap(int w, int h)
{
    width=w;
    height=h;
    stars=NULL;  // There are no stars yet

    int numstars=int(width*height*PERCENT_STARS);  // The number of stars we're going to create
    
    int x,y;        // x,y coordinates of a created star, generated randomly
    
    int found;       // a boolean to determine if we found a good place for a new star

    for(int i=0; i<numstars; ++i)
    {
        found=0;            // set found to false
        while(!found)       // while we haven't found a good place
        {
            x=rand() % width;   // Determine x
            y=rand() % height;  // and y randomly
            Star *TempStar=new Star(x,y);  // create a temporary star
            if(!stars)  // If we have no stars yet, then we don't have to check to see if the location is okay.
            {
                stars=TempStar;   // So we simply point stars to it's first star
                found=1;          // And we set found to true so we can get out of the loop
            }
            else if(!stars->here(x,y)) // If there are stars in the list, we have to make sure the current random
                                                 //location is okay
            {
                stars->Add(TempStar);  // If it is, add the newly created star
                found=1;               // And set found to true
            }
            else                        // This location is not okay because a star already exists there
            {
                delete TempStar;        // Get rid of this so we don't have memory leaks.
            }

        }
    }
}


This creates a random starmap for us pretty easily. And because we're using linked lists, we can create a list of any size that memory can hold.

The destructor is as follows:

CODE

StarMap::~StarMap(void)
{
    delete stars;
    
}


This just makes sure we clean up all the pointers.

Finally, we have the Dump(), which writes this all out to an HTML file so we can look at it. Right now, this isn't too terribly useful, but when we create planetary systems in a future tutorial, it might be nice to be able to look at the data about them, to make sure it's all working correctly.

The colors come from a site called "What Colors Are the Stars" which is found here

Note that the Dump() method doesn't create well formed HTML. It loads up in Firefox. No guarantees, though. It's just a quick and dirty hack job for testing the classes.

CODE

void StarMap::Dump(void)
{
    Star *DumpMe;  // Just to get the color and size
    char * StarColors[7]; // The colors for all 7 classifications
    int classification, size;

    StarColors[STAR_O]="#9bb0ff";
    StarColors[STAR_B]="#aabfff";
    StarColors[STAR_A]="#cad7ff";
    StarColors[STAR_F]="#f8f7ff";
    StarColors[STAR_G]="#fff4ea";
    StarColors[STAR_K]="#ffd2a1";
    StarColors[STAR_M]="#ffcc6f";



    ofstream outfile("stars.html"); //create ofstream object

    // First, let's write out the HTML header
    outfile<<"<html><head><title>Starmap</title></head>"<<endl<<"<body bgcolor=#000000>";


    for(int j=0; j<height; ++j)
    {
        for(int i=0; i<width; ++i)
        {

            DumpMe=stars->GetStar(i,j);   // Is there a star at i,j.  If so, get it
            if(DumpMe)                    // So is this a valid star or a NULL
            {                             // If it's valid
                classification=DumpMe->GetClassification(); //Get its classification
                size=DumpMe->GetSize();  // And its size
                if(size==1)
                    outfile<<"<font color="<<StarColors[classification]<<">.</font>";   // Period for size 1
                else if(size==2)
                    outfile<<"<font color="<<StarColors[classification]<<">,</font>";   // Comma for size 2
                else if(size==3)
                    outfile<<"<font color="<<StarColors[classification]<<">+</font>";   // Plus Sign for size 3
                else if(size==5)    // Red Giant
                    outfile<<"<font color=#FF0000>*</font>";                            // Asterisk for supergiants
            }
            else
                outfile<<" "<<endl;                                                // Otherwise just put a space
        }
        outfile<<"<BR>"<<endl;                                                          // Put a break at the end of each line
    }
    outfile<<"</body></html>"<<endl;                                                    // close out the body and html tags
}


Note that I didn't seed the randomizer, which means that it will always create the same map. You can seed the randomizer with the time by including time.h and stdlib.h and using the following:

CODE

srand(time(NULL));


And that's it. The source code for the whole thing is attached as a zip file. In the next tutorial, we're going to look at generating random star names using Markov chains and a list of Latin words.

Comments welcome and appreciated.
NickDMax
QUOTE
"generating random star names using Markov chains and a list of Latin words."

Nice tutorial! I am really looking forward to part II!!!


This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Invision Power Board © 2001-2008 Invision Power Services, Inc.