Text Adventure: Lesson 2

Replacing scanf with fgets, write a 'remove newline character' function & start creating rooms

Resources:

The code for this episode.
More on the fgets function

We start the episode by replacing the scanf function with the fgets function. The reason for this is, with scanf, we can't specify the length of the array we're passing in. That means the function has no idea how long our array is, and if the user types more characters then the array holds, it will write over memory that wasn't the array. This kind of bug is called buffer overflow & can be quite nasty when we can't track down what's causing it.

char response[2];

Here we've put an array of char types on the stack. This means there are two spots in this array to store two characters.

But we can quite easily do this in C code.

response[3] = 'a';

Here were assigning spot 4 (remember it includes 0) to equal the character type 'a'. But our array can only store 2 spots: index 0 & 1. This is an array index out of bounds or a buffer overflow. So without scanf knowing the size of our array, it has no idea when it would be writing past the size of the array, which could easily happen if the user types a lot of text. This is the reason we want to use fgets.

char bufferToFillOut[64]; fgets(bufferToFillOut, 64, stdin);

Here you see were now passing the buffer to be filled out (like with scanf), but also the size of that buffer.

One quirk of fgets is that it adds a newline character at the end of the array. So our string in memory instead of looking like this: (where each [a] square bracket defines a seperate element in our array)

[h][e][l][l][o][\0]

It will look like this:

[h][e][l][l][o][\n][\0]

First you'll notice the null terminator at the end of the string that defines the end of the string. But the other thing you'll notice there is an extra '\n' character, the newline character at the end. This wouldn't be a problem if it didn't break our string match function.

Why the string match function broke.

In order to quit our program we try & match the string "quit" with the respose the user types. Previously with scanf our function matched correctly, however now it no longer works. The reason is this pesky newline character fgets has put at the end of user's response.

string trying to match against: [q][u][i][t][\0]
user string they typed in:    [q][u][i][t][\n][\0]

So when our function tries and match the string, it will say the string is equal up until we test the length of the strings are equal. And we find the user's reponse is longer, so rightly so, our function says the strings aren't equal. So our first mission is to remove the pesky newline character from the response string.

To do this we're going to loop through and find the newline character we're looking for i.e. the one at the end of the string. We can identify it by seeing if it is followed by the null terminator character. So our function will look something like this:

void removeNewlineCharacter(char *ourString) {
    //NOTE: Loop through our whole string
    while(*ourString != '\0') {
        //NOTE: Look for the newline character at the end of the string
        if(ourString[0] == '\n' && ourString[1] == '\0') {
        //NOTE: When we find the newline character, replace it with the null terminating character
            ourString[0] = '\0';
        }
        //NOTE: Advance our string each time in our while loop
        ourString++;
    }
}

We loop through our string looking for the newline character next to the null terminating character. When we find it, we replace the newline character with another null terminating character. So our sting will now look like this:

[q][u][i][t][\0][\0]

Adding a Room data structure

Now that we've got the basic structure of our program working, we're going to start adding some rooms to our game. The basic idea is that a player finds herself in one room at a time. In each room there will be an assortment of items, humans or monsters. We can then move to a different room that is attached to the room we're in.

To get started we'll add a room struct to store all the data we need for each room.

typedef struct {
  char *name;
  char *description;

  int entityCount;
  Entity entities[256];
} Room;

A room will have a string (char *) called name, a string for it's description & an array of entities. Here we're just describing an entity as either a human, monster or item. Each entity will have some data that describes it. So lets do that next and create our entity type.

typedef struct {
  char *name;
  char *description;

  EntityType type;
} Entity;

In our next structure we've created our entity type. For now it'll just have a name, description & type. Where the type will be a MONSTER, HUMAN or ITEM. This allows us to right game logic that will handle the different types. For example, you might only be able to 'talk' to humans, 'attack' monsters & 'take' items. The final thing to make this compile now is to add our 'EntityType' type. So let's do that.

typedef enum {
  MONSTER,
  HUMAN,
  ITEM
} EntityType;

Here we've create an enumurator that just lists the different types of entities. This is the main way of using enum's, list different types, then when you are in the code you can test which type you have, & run the correct game logic for it. For example:

if(entity.type == MONSTER) {
  runMonsterLogic();
}
if(entity.type == HUMAN) {
  runHumanLogic();
}
if(entity.type == ITEM) {
  runItemLogic();
}

It's important to know in C and C++, when you use something like a function or data structure, it has to be declared above where your trying to use it. For example to use the type Entity in the Room struct, the Entity struct has to be declared above the Room struct. This is also the reason why there are header files (.h) and code files (.c or .cpp), which helps arrange your code so all code can reference all other code in your program.

That comes to the end of this lesson. In the next lesson we'll keep going with our room creation, adding entities to them, add the logic to be able to interact with the entities, and be able to move between rooms. See you then!

Quick Quiz:

Why did we have to swap the scanf function to the fgets function?Click to see

The fgets function is more memory safe since it knows how long the array is.

To Previous Lesson
To Next Lesson