Page 1 of 1

Data Modeling for Games in C Part II Part II: Pointers Rate Topic: -----

#1 NickDMax  Icon User is offline

  • Can grep dead trees!
  • member icon

Reputation: 2250
  • View blog
  • Posts: 9,245
  • Joined: 18-February 07

Posted 22 April 2007 - 08:06 PM

Data Modeling for Games in C

Part II: Pointers
By NickDMax

Previous: Enumerations

Pointers

Although this topic is not directly in our path, I think a little side trip is in order since we will use pointers from here on out. Pointers are wonderful, powerful, and yet misunderstood they are a disaster waiting to happen. Many languages don’t have pointers since they allow the programmer to get into all kinds of trouble. However, in most of these languages there are “workarounds” that have been developed to allow the adventurous to perform many of the techniques that use pointers.

So what are pointers? They are integer data types that hold an address to a location in memory. More than that, to the compiler they are associated with another data type (of a known size). The programmer can manipulate the pointer itself, for example incrementing it so that it points to the next element (not necessarily the next byte), or the programmer can manipulate the data that the pointer addresses.

Let’s look at a specific example. The notation int *ptr; declares a pointer to an integer. The variable ptr is nothing more than an integer itself, but it is a special integer to the compiler. To the compiler the value of ptr is a memory address to a 4 byte (assuming a 32 bit integer) block of memory. When we tell the compiler ptr++ the compiler will move to the NEXT integer, which means that the value in ptr is increased by 4. When we tell the compiler ptr-- the value of ptr is decreased by 4 because an integer is 4 bytes and the compiler wants to move ptr to address adjacent integers (not overlapping integers).

To access the data that the pointer addresses we would use the syntax *ptr. To increase the value that the pointer addresses, we may use (*ptr)++, which will increase *ptr by 1, but will leave the value of ptr unaffected. Should we code something like ptr += 8 this would increment ptr by 4*8= 16 bytes (or 8 integers).

Pointers come with three special operators (* , [ ], and ->), and there are two associated operators we will want to discuss ( &, and sizeof( )). The first operator (*) is usually called the dereference operator as it “dereferences” the pointer and returns the value or object that the pointer addresses. The next operator ([ ]) is the Array Index operator. This operator is a hybrid of the discussion in the last two paragraphs. That is to say that (ptr[i]==*(ptr+i)), it chooses an offset for ptr, and then dereferences that value. The next operator (->) is also a dereference operator that is used to dereference an element of a structure. Since we have not discussed such things yet, I will reserve the discussion of this operator until after I have introduced structures.

Not exactly pointer operators, but important to the use of pointers, there is the (&) operator. This operator is known as the reference operator or, as I like to think of it, the “Address of” operator. It gives the address of a variable. The last operator I wish to discuss is the sizeof() operator. This operator acts more like a function and it returns the number of bytes needed to represent a type or variable.

---------- Pointer101.C: Demo Program ----------
#include <stdlib.h>
#include <stdio.h>

//Utility function to pause output until user presses enter.
void pause();

//My crash course in pointers.
int main()
{
int *ptrToInt;
int Array[] = {0, 10, 20, 30, 40, 50, 60};
//C uses pointers to access arrays. The variable Array is a pointer
// to a block of memory containing the integers |0|10|20|30|...|60|
// All array variable are pointers, and all pointer can be array vaiables!!!

int counter;

//This assigns ptrToInt to the address of the block of memory that is Array[]
ptrToInt = Array;

puts("ptrToInt = Array, Same as ptrToInt = &Array[0]\n");

//First lets use ptrToInt as though it were ptrToInt[]
for (counter=0; counter<7; ++counter)
{
printf("Array[%d]==%d\tptrToInt[%d]==%d\n", counter, Array[counter], counter, ptrToInt[counter]);
} //Now lets see if it is true that ptrToInt[i]==*(ptrToInt + i), What is *(ptrToInt)+i doing?
for (counter=0; counter<7; ++counter)
{
printf("*(ptrToInt + %d)==%d\t*(ptrToInt)+%d==%d\n", counter, *(ptrToInt+ counter), counter, (*(ptrToInt)+counter));
}

//Take a min or two...
pause();

// Lets see what happens when we do ptrToInt++.
puts("\n*ptrToInt += counter");
for (counter=0; counter<7; ++counter)
{
*ptrToInt += counter;
printf("Array[%d]==%d\tptrToInt==%d\t*ptrToInt==%d\n", counter, Array[counter], ptrToInt, *ptrToInt);
ptrToInt++; //Note that ptrToInt goes up by either 2 or 4.

}

puts("\nptrToInt=&counter\n");

ptrToInt = &counter; //make ptrToInt point to our counter
for (counter=0; counter<7; ++(*ptrToInt))
{
printf("counter==%d\tptrToInt==%d\t*ptrToInt==%d\n", counter, ptrToInt, *ptrToInt);
if (*ptrToInt==3) { *ptrToInt=7; } //*ptrToInt can affect the loop
}

return 0;
}

void pause()
{
puts("Press ENTER to continue...");
while (getchar()!='\n');
return;
}
---------- END Pointer101.C ----------


The above program, although very dry, points out that pointers and arrays are intertwined in C/C++. Each array variable is actually a pointer, and each pointer can be used to access and array. In this tutorial it is the latter feature which will be of the most use, as pointers allow us to use dynamically allocated memory.

In all my examples so far, the various variables and data structures that I have defined have all been “static” data that is either allocated when the program loads, or is made out of chucks of our stack space. This means that so far our data has essentially been very limited in size. This is fine for small amounts of data, but when our games have many large tables of data we risk running out of available stack space and thus “out of memory” or worse putting tight limits on what our game can do.

There is another way. Rather using our data-segment and stack to store our data we can ask to allocate memory in the heap during program execution (called dynamically allocating memory) and this allows us access to larger data structures, data structures that may have different sizes, data structures that are not continuous blocks of memory. What makes this all possible? Pointers.

As an example this next program (too long to do in color) loads room descriptions into dynamically allocated memory. The room descriptions are stored in a file that attached to this tutorial.
//In the Enum2.C example from the last section I used an array of 
//strings to hold the descriptions of the rooms. In this example
//I would like to load these descriptions from file. That way I
//can update the room descriptions without having to update any
//any code. This is also handy as it makes it easer to use a
//word processors (with spell check) to edit the text. This also
//means that non-programmer game designer can write/edit the text.


//To do this I will need to sketch out a file-format. That is I 
//need rules to how the file will look so that my program knows
//how to read it in.

//Rule #1 All rooms are entered in the order defined by the enum.
//Rule #2 All room descriptions end with an <END> tag on a line by itself.
//Rule #3 Each line should be no more than 80 characters wide
//Rule #4 All line starting with a # will be ignored (allows you to
//		  add comments to the file.)

//Example:
//# This line would be a comment
//This is a description of the Forest!
//<END>
//# Yet another comment
//This is a description of the Entrance!
//<END>
//etc.
//<END>

//There are FAR better file formats. This one has many failings
//  but it will work for an example.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Enum of our rooms.
enum Room_Names {
	FOREST=0,
	ENTRANCE, //=1
	PARLOR, //=2
	KITCHEN, //=3
	MAINHALL, //=4
	GRANDSTAIRCASE, //=5
	HIDDENPASSAGE1, //=6
	NUM_OF_1ST_FLOOR_ROOMS, //=7
	BEDROOM1 = NUM_OF_1ST_FLOOR_ROOMS, //=7
	BEDROOM2, //=8
	GRANDCLOSET, //=9
	ALCHEMYLAB, //=10
	NUMBER_OF_ROOMS, //=11
	NUM_OF_2ND_FLOOR_ROOMS = NUMBER_OF_ROOMS - NUM_OF_1ST_FLOOR_ROOMS //= 11- 7 = 4
};

//Names of all of the rooms based upon the order of enumeration.
const char Room_Names[][20] = {"Forest", "Enterance", "Parlor", "Kitchen", "Main Hall",
		"Grand Staircase", "North Passage","Master Bedroom", "Guest Bedroom", "Grand Closet",
		"Alchemy Lab" };

//Utility function to get input.
char Get_Char();

int main()
{
	//The variable we will declare is an array of pointers (that is right,
	//   RoomDescriptions is an array, of pointers (to arrays)).
	char *roomDescriptions[NUMBER_OF_ROOMS];
	//   This is no different that delaring char RoomDescriptions[NUMBER_OF_ROOMS][1024]
	//   Except that in this version the memory to store the information is unknown.
	//   We will be able to use this variable just as we did before.
	
	//Since we have no idea how long the room descriptions may be, we will need to create
	//  a temporary buffer to hold the data as it is loaded from the file.
	char *tempInputBuffer;
	int inputLength; //Will tell us how long our description is.
	int currentRoom; //Let us know which room we are working on.

	
	//Next we need a buffer to get each line.
	//   Since computer screens have a maximum length of 80 chars
	//   our text file should contain no more than about 80 or so
	//   characters per line. We need to add an extra byte for the
	//   zero to terminate the string. And 2 for EOL markers.
	char lineBuffer[83];
	char tempChar; //This will hold a character so we can shorten lineBuffer to 5 chars.

	//---- These variables are used in the "display descriptions" routine
	char cInput; //Used to get input from the user
	int iRoomNumber=0; //Used to get the room number the user wants to see


	//Next we need to make a pointer to a file stream.
	FILE *fp; //A pointer to a file buffer.

	

	//How big should this buffer be? Well The room descriptions should be short,
	//  the average word length in English is 5 letters (also used to determine
	//  typing rate). Each line can be up to 80 bytes, about 16 words and assuming
	//  an 80x25 screen, we don't want the user to have to look past about 12 lines.
	//  so 12 * 80 = 960, bump that up to 1024 (1k) and we have about 200 words/description maximum.
	tempInputBuffer = malloc(1024); //return a NULL (0) if it fails.

	//We should check to make sure that the memory did get allocated
	//If the memory is not allocated then tempInputBuffer == NULL (which equates to a false in C)
	if (tempInputBuffer) 
	{
		fp = fopen("rooms.txt", "r");
		//Next we can see if the file was opened, if it was not
		// opened then fp is a NULL pointer (NULL Pointers point to 0)
		if (fp != NULL) //same as (fp != 0) same as (fp==true) same as (fp)
		{
			//Our file was opened successfully!
			puts("File open: Reading in descriptions...\n");
			currentRoom=0; //This tells us which RoomDescriptions[] we are working on
			tempInputBuffer[0]= 0; //This sets tempInputBuffer = ""
			inputLength = 0;
			//Get inputs until either EOF or we have enough rooms...
			while (fgets(lineBuffer, 82, fp) != NULL && currentRoom < NUMBER_OF_ROOMS)
			{
				//We read a line in from the file.
				//  Most lines will end in a '\n' char, but the very last line of the file will not
				//  as it was terminated with a EOF marker. Originally I used strcmp(lineBuffer,"<END>\n")==0
				//  but this a little bug in it at the EOF. Not a big deal, just remember to press enter
				//  after finishing the last description. BUT, what if we get lazy?
				//  The next two lines ensure that we find just "<END>".
				tempChar=lineBuffer[5]; 
				lineBuffer[5] = 0; //This will set lineBuffer = "?????" so we can do our compairison
				if (strcmp(lineBuffer,"<END>")==0)
				{
					printf("Done reading room #%d: %s\n", currentRoom,Room_Names[currentRoom]);
					inputLength = strlen(tempInputBuffer);
					roomDescriptions[currentRoom] = malloc(inputLength + 1);
					if (roomDescriptions[currentRoom])
					{
						roomDescriptions[currentRoom][0] = 0;
						strcat(roomDescriptions[currentRoom], tempInputBuffer);
					} else
					{
						//We should never see this code run. But in case it does lets make sure
						//   we do everything correctly.
						printf ("ERROR: Memory Allocation Failure at room #%d: %s\n", currentRoom, Room_Names[currentRoom]);
						
						//We need to ensure we free any memory that we allocated.
						puts("\n!!!Freeing all allocated memory!!!");
						puts("*Freeing input buffer.");
						free(tempInputBuffer);
						puts("*Freeing room descriptions:\n--------------------------");
						while (currentRoom)
						{
							currentRoom--;
							printf("*Freeing room discription #%d: %s\n", currentRoom, Room_Names[currentRoom]);
							free(roomDescriptions[currentRoom]);
						} 
						puts("*Closing input file");
						fclose(fp);
						puts("*** EXITING WITH ERROR CODE -1 ***");
						exit(-1);
					}
					currentRoom++;
					tempInputBuffer[0]=0; //reset to null string ""
					inputLength = 0;
				} else if (lineBuffer[0]!='#') 
				{
					//First restore missing char.
					lineBuffer[5] = tempChar;
					//Check the length. tempInputBuffer can only hold 1024 bytes (and this must include the 0)
					inputLength += strlen(lineBuffer);
					if (inputLength < 1023)
					{
						strcat(tempInputBuffer, lineBuffer);
					} //else this line of input is ignored.

				} else
				{
					//First restore missing char.
					lineBuffer[5] = tempChar;
					//Print out the comment line.
					printf("%s",lineBuffer);
				}
			}
			//We have our inputs, lets close the file.
			puts("Closing input file");
			fclose(fp);
			printf("\nProgram read %d descriptions\n", currentRoom);

			//---- USER REVIEW ROUTINE -----
			// here we will let the user review the rooms to ensure they are loaded correctly.
			do
			{
				do
				{
					puts("Would you like to view the description of a room? (Y/N)");
					cInput=Get_Char();
					cInput=toupper(cInput); //Lets only deal with upper case.
				} while(cInput!='Y' && cInput!='N');
				if (cInput=='Y')
				{
					//User said they wanted to see, so let us print a menu...
					do
					{
						int i;
						for (i=0; i<currentRoom; ++i)
						{
							printf("%d) %s\n", i, Room_Names[i]);
						}
						puts("Enter number to see:");
						scanf("%d",&iRoomNumber);
					//We must ensure that the user enters a valid menu item...
					} while (iRoomNumber <0 || iRoomNumber >= currentRoom);
					printf("\nRoom %d: %s\n%s\n", iRoomNumber,Room_Names[iRoomNumber], roomDescriptions[iRoomNumber]);
				}
			//Continue to ask the user until they get the answer right!
			} while (cInput != 'N');
			
			//We must ALWAYS free the memory that we allocate. This little routine will do that.
			puts("\n!!!Freeing all allocated memory!!!");
			puts("Freeing input buffer.");
			free(tempInputBuffer); //free our buffer...
			puts("Freeing room descriptions:\n--------------------------");
			while (currentRoom)
			{
				currentRoom--;
				printf("*Freeing room description #%d: %s\n", currentRoom, Room_Names[currentRoom]);
				free(roomDescriptions[currentRoom]);
			} 

		} else //Goes with if (fp != NULL)
		{
			puts("ERROR: Could not open file!");
		}
	} else //Goes with if (tempInputBuffer)
	{
		puts("Could not allocate memory for a an input buffer!!!");
	}
	return 0;
}


//Utility function to eat '\n' characters from getchar()
char Get_Char()
{
	char cIn;
	//Will ignore any char less then ESC (most non printable ones).
	while((cIn=getchar())<27);
	return cIn;
}


As useful as dynamic memory is, pointers can do something even more useful: They allow us to pass data by reference to functions. Rather than passing an entire array back and forth on the stack (which might use all the stack space quickly), I can pass my function a pointer to the array then it can read and manipulate that array. By using pointers as function parameters I can make functions that, manipulate arrays, return large amounts of data, return multiple values.

---------- BEGIN NameGen.C ----------
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h> //Needed for strcat()
//Here I will create an array of prefixes to help generate names.
// I am banking on multiplication to ensure a large number of names
// by using 7 prefixes and 20 stems, and 16 suffixes I should be able to
// create about 7 * 20 * 16 = 2240 names out of 312 bytes of data (In my earlier
// example from the forum I used this code to generate male and female names,
// but here I combined them).
char NamePrefix[][5] = {
"", //who said we need to add a prefix?
"bel", //lets say that means "the good"
"nar", //"The not so good as Bel"
"xan", //"The evil"
"bell", //"the good"
"natr", //"the neutral/natral"
"ev", //Man am I original
};

char NameSuffix[][5] = {
"", "us", "ix", "ox", "ith",
"ath", "um", "ator", "or", "axia",
"imus", "ais", "itur", "orex", "o",
"y"
};


const char NameStems[][10] = {
"adur", "aes", "anim", "apoll", "imac",
"educ", "equis", "extr", "guius", "hann",
"equi", "amora", "hum", "iace", "ille",
"inept", "iuv", "obe", "ocul", "orbis"
};

//Declare the function up here so that we can use it
// note that it does not return a value, rather it
// edits the character passed to it by reference.
void NameGen(char *PlayerName);
char get_Char(); //little utility function to make getting char input easer...

int main()
{
char Player1Name[21]; //Used to hold our character's name
char cIn; //Used to get user answers to prompts.
do
{
NameGen(Player1Name);
printf("Generated Name: %s\n\n", Player1Name);
puts("Would you like to generate another (Y,N): ");
cIn = get_Char();
} while (cIn != 'n' && cIn != 'N');

return 0;
}

//Utility function for input
char get_Char()
{
char cIn;
while((cIn = getchar())<27); //ignore anything less then ESC
return cIn;
}

//The return type is void because we use a pointer to the array holding
// the characters of the name.
void NameGen(char* PlayerName)
{
srand((long)time(NULL)); //Seed the random number generator...
PlayerName[0]=0; //initialize the string to "" (zero length string).
//add the prefix...
strcat(PlayerName, NamePrefix[(rand() % 7)]);
//add the stem...
strcat(PlayerName, NameStems[(rand() % 20)]);
//add the suffix...
strcat(PlayerName, NameSuffix[(rand() % 16)]);
//Make the first letter capital...
PlayerName[0]=toupper(PlayerName[0]);
return;
}
---------- END NameGen.C ----------

I have to admit that some of the names sound like exotic psychological disorders (Natraduraxia, Xanineptaxia), or new drug names (Xanoculix, Narguiusum), or painful surgical procedures (Beloculaxia, Narapollus). In fact the addition of the suffix was probably a bad idea as my tests turned up only a few choices acceptable for character names; however, the process is a lot of fun. By choosing different prefixes, stems, and suffixes you can change the feel/sound of the names generated. These names are loosely based on Latin stems and endings, which resultes in very scientific sounding names.

There is just one last thing I want to mention about pointers before I move along. What happens if the pointer points to address 0? A pointer that points to 0 is considered a NULL pointer. Very often this is used to mean “unassigned” or “unused” but you should never ASSUME that a pointer is NULL just because it is unused/uninitialized as the compiler will not initialize the pointer to NULL for you.

Pointers are probably the one thing that sets C/C++ vastly apart from most other languages. Many languages share the same syntax, some even feel very much like C/C++, but it is this pointer that give C/C++ its low-level feel and untamed power. If you wish to master C/C++ you will need to master pointers.

Next Section: Structures.

References and Additional Information:

Pointers
[1] Banahan, Mike. Brady, Declan. Doran, Mark.The C Book, second edition Ch 5.
[2] Oualline, Steve. Practical C Programming, 3rd Edition Ch 13.
[3] Jensen, Ted. A TUTORIAL ON POINTERS AND ARRAYS IN C
[4] Hosey, Peter. Everything you need to know about pointers in C
[5] Wikipedia. Pointer (computing)

Attached File(s)

  • Attached File  rooms.txt (2.29K)
    Number of downloads: 270


Is This A Good Question/Topic? 0
  • +

Page 1 of 1