Programming Games in C - Tutorial 1 Star Empires

01
of 05

Introduction to the Games Programming Tutorials

This is the first of several games programming Tutorials in C for complete beginners. Instead of concentrating on teaching C then showing example programs they teach C by providing you with complete programs (ie games) in C

Keeping It Simple

The first game in the series is a console (i.e. text based game called Star Empires). Star Empires is a simple game where you have to capture all 10 systems in the Galaxy while stopping your AI opponent doing the same.

You start owning System 0, while your enemy own system 9. The remaining eight systems (1-8) all start neutral. All systems start within a 5 parsec x 5 parsec square so no system is more than 6 parsecs apart. The furthest two points are (0,0) and (4,4). By Pythagoras theorem, the furthest distance apart of any two systems is the square root ((4)2 + (4)2) which is the square root of 32 which is about 5.657.

Please note, this is not the final version and will be amended. Last change: August 21, 2011.

Turn Based & Real-Time

The game is turn based and each turn you give orders to move any number of fleets from any system you own to any other system. If you own more than one system you can order fleets to move from all your systems to the target system. This is done pro rata rounded up so if you own three systems (1,2,3) with 20, 10 and 5 fleets present and you order 10 Fleets to go to system 4 then 6 will go from system 1, 3 from system 2 and 1 from system 3. Each fleet moves 1 parsec per turn.

Each turn lasts 5 seconds though you can alter the speed to speed it up or slow it down by changing the 5 in this line of code to 3 or 7 or whatever you choose. Look for this line of code:

 

onesec = clock()+(5*CLOCKS_PER_SEC);

 

C Programming Tutorial

This game has been programmed and assumes that you don't know any C programming. I'll introduce C programming features in this and the next two or three tutorials as they progress. First though you'll need a compiler for Windows. Here are two free ones:

The CC386 article walks you through creating a project. If you install that compiler then all you have to do is load the Hello World program as described, copy and paste the source code over the example, save it and then hit F7 to compile it and run it. Likewise the Visual C++ 2010 article creates a hello world program. Overwrite it and press F7 to build Star Empires., F5 to run it.

 

On the next page - Making Star Empires Work

 

02
of 05

Making Star Empires Work

Making Star Empires Work

We need to store infomation on fleets and systems in the game. A fleet is one or more ships with an order to move from one sytem to another. A star system is a number of planets but is more of an abstract entity in this game. We need to hold the following information for a fleet.

  • Origin System (1-10).
  • Destination System (1-10)
  • How Many Ships (1-Many)
  • Turns to Arrive
  • Whose Fleet is it? 0= Player, 9 = Enemy

We will use a struct in C to hold this:

struct fleet {
int fromsystem;
int tosystem;
int turns;
int fleetsize;
int owner;
};

A struct is a collection of data, in this case 5 numbers that we manipulate as one. Each number has a name, eg fromsystem, tosystem. These names are variable names in C and can have underscores like_this but not spaces. In C, numbers are either integer; whole numbers like 2 or 7 these are called ints, or numbers with decimal parts like 2.5 or 7.3333 and these are called floats. In the whole of Star Empires, we only use floats once. In a chunk of code calculating the distance between two places. Every other number is an int.

So fleet is the name for a data structure holding five int variables. Now that's for one Fleet. We don't know how many fleets we'll need to hold so we'll allocate generous room for 100 using an array. Think of a struct as like a dinner table with room for five people (ints). An array is like a long row of dinner tables. 100 tables means it can hold 100 x 5 people.

If we were actually serving those 100 dinner tables, we'd need to know which table was which and we do this by numbering. In C, we always numbers elements of arrays starting at 0. The first dinner table (fleet) is number 0, the next one is 1 and the last one is 99. I always remember it as being how many dinner tables is this table from the start? The first one is at the start so is 0 along.

This is how we declare the fleets (ie our dinner tables).

struct fleet fleets[100];

Read this from left to right. Struct fleet refers to our structure to hold one fleet. The name fleets is the name we give to all the fleets and [100] tells us there are 100 x struct fleet in the fleets variable. Each int occupies 4 locations in memory (called bytes) so one fleet occupies 20 bytes and 100 fleets is 2000 bytes. It's always a good idea to know how much memory our program needs to hold its data.

In the struct fleet, each of the ints holds an integer number. This number is stored in 4 bytes and the range of this is from -2,147,483,647 to 2,147,483,648. Most of the time we'll use smaller values. There are ten systems so both fromsystem and tosystem will hold values 0 to 9.


On the next page: Systems and Random Numbers

03
of 05

About Systems and Random Numbers

Each of the neutral systems (1-8) starts with 15 ships (a number I picked out of the air!) to start with and the other two (yours: system 0 and your computer opponent at system 9) have 50 ships each. Each turn the number of ships at a system is increased by 10% rounded down. So after one turn if you don't move them, your 50 will become 55 and each of the neutral systems will have 16 (15 + 1.5 rounded down). Note that fleets moving to another system don't increase in numbers.

Increasing the number of ships this way may seem a little odd, but I've done it to keep the game moving along. Rather than clutter this tutorial with too much on design decisions, I wrote a separate article about the design decisions of Star Empires.

Implementing Systems

At the start we need to generate all the systems and put them on the map, with a maximum of one system in each location, As there are 25 locations on our 5 x 5 grid, we will have ten systems and 15 empty locations. We generate them using the function GenMapSystems() which we'll look at on the next page.

A system is stored in a struct, with the following 4 fields that are all int.

struct system {
    int x,y;
    int numfleets;
    int owner;
};

The galaxy (all 10 systems) is stored in another array just like with fleets except we have 10 systems.

struct system galaxy[10];

Random Numbers

All games need random numbers. C has a built in function rand() that returns a random int. We can force this into a range by passing the maximum number in and using the % operator. (Modulus). This is like clock arithemetic except instead of 12 or 24 we pass in an int number called max.

/* returns a number between 1 and max */
int Random(int max) {
 return (rand() % max)+1;
}

This is an example of a function which is a piece of code wrapped up inside a container. The first line here that starts /* and end */ is a comment. It says what the code does but is ignored by the compiler which reads the C instructions and converts them into instructions that the computer understands and can execute very fast.

A function is like a mathematical function such as Sin(x). There are three parts to this function:

int Random(int max)

The int says what type of number it returns (usually int or float). Random is the name of the function and (int max) says that we're passing in an int number. We might use it like this:

int dice;
dice = Random(6); /* returns a random number between 1 and 6 */

The line:

return (rand() % max)+1;
This calls that built in function rand() which returns a large number. % max does clock arithmetic reducing it to the range 0 to max-1. Then the +1 adds 1 making it return a value in the range 1 to max.

On the next page: Generating a Random Start Map

04
of 05

Generating a Random Start Map

Star Empires Map

This code below generates the start map. That's it shown above.

void GenMapSystems() {
int i,x,y;

    for (x=0;x      for (y=0;y         layout[x][y]=' ';
    }

    InitSystem(0,0,0,50,0) ;
    InitSystem(9,4,4,50,1) ;

    /* Find an empty space for remaining 8 systems*/
    for (i=1;i      do {
        x= Random(5)-1;
        y= Random(5)-1;
      }
      while (layout[x][y] !=' ') ;
      InitSystem(i,x,y,15,-1) ;
    }
}

Generating Systems is a matter of adding the player and the opponents systems (at 0,0) and (4,4) and then randomly adding 8 systems in the remaining 23 empty locations.

The code uses three int variables defined by the line

int i,x,y;

A variable is a location in memory that holds an int value. The variables x and y holds the coordinates of the systems and will hold a value in the range 0-4. The variable i is used for counting in loops.

To place the 8 random systems in the 5x5 grid we need to know if a location has a system already and prevent another one being put in the same location. For this we use a simple two dimensional array of characters. The type char is another type of variable in C and holds a single character like 'B' or 'x'.

Primer on Datatypes in C

The fundamental type of variables in C are int (integers like 46), char (a single character like 'A'), and float (for holding numbers with floating point like 3.567). Arrays [] are for holding lists of the same element. So char[5][5] defines a list of lists; a two dimensional array of chars. Think of it like 25 Scrabble pieces arranged in a 5 x 5 grid.

Now We Loop!

Each char is initially set to a space in a double loop using two for statements. A for statement has three parts. An initialization, a comparison part and a change part.

 for (x=0;x    for (y=0;y        layout[x][y]=' ';
}
  • x=0; This is the initialization part.
  • x
  • x++. This is the change part. It adds 1 to x.

So (for (x=0;x

Inside the for (x loop is a for y loop that does the same for y. This y loop happens for each value of X. When X is 0, Y will loop from 0 to 4, when X is 1, Y will loop and so on. This means that every one of the 25 locations in the layout array is initialized to a space.

After the for loop the function InitSystem is called with five int parameters. A function has to be defined before it is called or the compiler won't know how many parameters it should have. InitSystem has these five parameters.


On the next page: Generating a Random Start Map Continues...

05
of 05

Generating a Random Start Map Continues

These are the parameters to InitSystem.

  • systemindex - a value from 0 -9.
  • x and y - coordinates of the system (0-4).
  • numships - how many ships there are at this system.
  • owner. Who owns a system. 0 means the player, 9 means the enemy.

So the line InitSystem(0,0,0,50,0) initializes system 0 at locations x=-0,y=0 with 50 ships to owner 0.

C has three types of loop, while loops, for loops and do loops and we use for and do in the function GenMapSystems. Here we have to place the remaining 8 systems somewhere in the galaxy.

for (i=1;i    do {
        x= Random(5)-1;
        y= Random(5)-1;
    }
   while (layout[x][y] !=' ') ;
   InitSystem(i,x,y,15,0) ;
}

There are two nested loops in this code. The outside loop is a for statement that counts up the i variable from an initial value of 1 to a final value of 8. We'll use i to refer to the system. Remember we've already initialzed system 0 and 9, so now we're initialising systems 1-8.

Everything from the do { to the while (layout[x][y] is the second loop. It's syntax is do {something} while (condition is true); So we assign random values to x and y, each value in the range 0-4. Random(5) returns a value in the range 1 to 5, subtracting 1 gets the range 0-4.

We don't want to put two systems at the same coordinates so this loop is looking for a random location that has a space in it. If there is a system there, the layout[x][y] will not be a space. When we call InitSystem it puts a different value there. BTW != means not equal to and == means equal to.

When the code reaches the InitSystem after while (layout[x][y] != ' '), x and y definitely refer to a place in layout that has a space in it. So we can call InitSystem and then go round the for loop to find a random location for the next system until all 8 systems have been placed.

The first call to InitSystem sets up system 0 at location 0,0 (the top left of the grid) with 50 fleets and woned by me. The second call initialises system 9 at location 4,4 (bottom right) with 50 fleets and it's owned by player 1. We'll look closely at what InitSystem actually does in the next tutorial.

#define

These lines declare literal values. It's customary to put them in upper case. Everywhere the compiler sees MAXFLEETS, it uses the value 100. Change them here and it applies everywhere:

  • #define WIDTH 80
  • #define HEIGHT 50
  • #define MAXLEN 4
  • #define MAXFLEETS 100
  • #define MAXSYSTEMS 10
  • #define FIGHTMARKER 999

Conclusion

In this tutorial, We've covered variables and the use of int, char and struct to group them plus array to create a list. Then simple looping using for and do. If you examine the source code, the same structures are seen time after time.

  • for (i=0;i
  • for (i=0;i

Tutorial Twowill look at aspects of C mentioned in this tutorial.