Hey folks --

DaniWeb has helped me a lot with various other programming projects, but I've run into a snag that I just can't get myself to understand. Hooray for vague compiler errors. Hopefully someone here can show me the error of my ways.

I'm making a little game, based on an external map file. You can traverse from room to room using various directional commands (i.e. north south east west). The "maps.c." file contains eight lines for each room, in this format:

room number
room name
room description
number of connections to other rooms (max: 2)
direction 1
connection1
direction 2
connection 2

In my program, I need to declare an array of structures, one structure for each room, each structure having a place for each piece of information about the room.

Then, I need a function to read the information for a particular room, print that information, and then wait for the user to input where to go next.

I've re-worked this code enough times that my brain is about to explode. The following is an (obviously) incomplete version of how I'm starting things off. I suspect some gross conceptual error, but I can't figure it out. At this point all I want to do is get the program to get the room name and room number, and print 'em.

Thanks a lot for any help.

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

void readRoom (rooms);

int main (void) {

typedef struct {
    int room_num;
    char room_name[80];
    char desc[1000];
    int num_links;
    char direction1[6];
    int dest1;
    char direction2[6];
    int dest2;
} roomStruct;

roomStruct rooms[100];

readRoom(rooms);

return 0;

}

void readRoom (rooms[]) {

    FILE *fin;
    int i = 0;

    fin = fopen("maps.c", "r");

    fscanf(fin, "%d", &rooms[i].room_num);

    fgets(rooms[i].room_name, 80, fin);

    printf("Room number: %d ... Room name: %s\n", rooms[i].room_num, rooms[i].room_name);

    return;

}

A quick re-org to help:

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

// typedefs don't belong inside functions (not usually, anyway...)
typedef struct {
    int room_num;
    char room_name[80];
    char desc[1000];
    int num_links;
    char direction1[6];
    int dest1;
    char direction2[6];
    int dest2;
} roomStruct;

// Functions need complete types
void readRoom ( roomStruct rooms[] );

// int main() doesn't take any void arguments.
int main () {
  roomStruct rooms[100];

  readRoom(rooms);

  return 0;
}

// And the type must be the same as the prototype's
void readRoom ( roomStruct rooms[] ) {

    FILE *fin;
    int i = 0;

    fin = fopen("maps.c", "r");

    fscanf(fin, "%d", &rooms[i].room_num);

    fgets(rooms[i].room_name, 80, fin);

    printf("Room number: %d ... Room name: %s\n", rooms[i].room_num, rooms[i].room_name);

    // no need to "return" from a void function
}

Hope this helps.

Thanks for the re-organization...I should probably learn to comment a little more.

I tried the code you gave me, and it compiled wonderfully! Yay.

Then I ran it. It read in the number correctly and displayed it. The name, however, didn't come up at all.

The output was:

Room number: 0 ... Room name:

What the heck is going on? Is fgets() not reading the next line in the map.c file?

I've re-worked this code enough times that my brain is about to explode. The following is an (obviously) incomplete version of how I'm starting things off. I suspect some gross conceptual error, but I can't figure it out. At this point all I want to do is get the program to get the room name and room number, and print 'em.

Figure what out? We know what you're trying to do, but you didn't tell us what's actually happening. Knowing that will help us understand he problem.

Kinda like "hey Mr. Mechanic, I want my car to start when I turn the key. Fix it.." What's likely to be his first question?

Also, knowing what's being read might be important...

Sorry; I'm a bit of a coding forum noobie. I'll be more specific.

// maps.c //

0
Logan and Eliseo's Room
You are in Logan and Eliseo's room. It's full of people and it's really really loud.
1
west
1
none
0
1
Jake and Garrett's Room
You are in Jake and Garrett's room. There is a big fancy TV, and Garrett and his buddies are playing Splinter Cell.
2
east
0
west
2
2
Will and Ryan's Room
You are in Will and Ryan's room. There is a piano, and there's junk strewn all over the place.
1
east
1
none
2

The idea is that you start in room 0. You read the description. If you type north, south, or east, it leaves you in room 0, but if you type west, it takes you to room 1, displays the description, and awaits input again. Zork flashbacks, I know.

The "none" is just the second link specifier if there is not a second room connection, so anything but the correct direction will just leave you in the same room.

The re-organized code Duoas gave me pleased the compiler, but didn't correctly print rooms.room_name. It didn't print anything, in fact.

Thanks for the help guys; I got really excited about this project and it was kind of a buzzkill to run into problems :)

void readRoom (rooms[]) {

    FILE *fin;
    int i = 0;

    fin = fopen("maps.c", "r");
    if ( fin == NULL )
    {
        /* some kind of error here */
    }
    while ( fscanf(fin, "%s%d", rooms[i].room_name,&rooms[i].room_num) > 1 )
    {
        printf("Room number: %d ... Room name: %s\n", rooms[i].room_num, rooms[i].room_name);
    }

    return;

}

I haven't played with scanf() in a long while, but what is probably happening is that you are reading the room number, but not the newline. Thereafter, you scan for a room name and get the newline immediately, which terminates the string.

Try getting the room number like this instead: fscanf( fin, "%d%*[ \t]\n", &rooms[i].room_number ); This gets an int, skips spaces and tabs (if any) and reads a newline, leaving the fp at the beginning of the line containing the room name. Then, when you read the room name it works.

I hope this is correct.

[EDIT] Aia, that completely ignores his input format requirements.

Okay, thanks.

This code is working 3/4 of the way:

void readRoom (roomStruct rooms[]) {

FILE *fin;
int i = 0;

fin = fopen("map.c", "r");

fscanf(fin, "%d*[ \t]\n", &rooms[i].room_num);
fscanf(fin, "%s", &rooms[i].room_name);

But it only prints out "Logan" as rooms.room_name

I tried fscanf() again, this time putting "%s" into rooms.desc, and then printing all three, and that prints

Room number: 0 ... Room name: Logan ... Room desc: and

So it's only getting the first word. Alas, I need the whole line.

Would fgets() be better?

[EDIT] Aia, that completely ignores his input format requirements.

Correct. Base in the information I had, I took some assumption.
Room_name 1
Room_name 2
etc...
It appeared to me at the time that the OP didn't know how to make use of fscanf and its parameters.

Heh, don't worry 'bout it.

deadswitch
Why did you change your fgets(rooms[i].room_name, 80, fin); (which worked fine) to fscanf(fin, "%s", &rooms[i].room_name); (which only reads until whitespace is encountered)?

Thanks so much for the help, everyone. Unfortunately, I'm still not understanding things.

I'm completely baffled as to how to read in each room from the map file and stick each room into its own struct and have each element of the room in its own little place in the struct.

I'd really like to just use the format

fgets(rooms[i].room_num, fin);
fgets(rooms[i].room_name, fin);
fgets(rooms[i].desc, fin);
fgets(rooms[i].num_links, fin);

etc...

and get just ONE room into ONE struct. I don't even know where to begin with finding out how many rooms are in the maps.c. file and reading them all into the structures array. Or maybe I don't even have to do that.

Sorry, I probably sound really confused (which I am). I think I must be really off-base. Once I can get the basic structure of this thing down, the "game" should be infinitely expandable just by adding more rooms to the maps.c file, and never messing with the code itself.

Thanks again for your help. Any more you can offer would be greatly appreciated. I just want it to work! :(

Yes, such abstraction is usually best. However, in C, you must build your own.

I've taken some liberties here.
1. First, I changed your direction type to an enumeration.
2. Second, I added a whole lot of error checking. (I am assuming this is not a school project. Please don't make me wrong.)

Some notes:
1. Don't name files other than C source code "anything.c". I renamed "maps.c" to "maps.txt".
2. Don't use C++ comments in a C program.
3. Don't forget to close files when you are done with them.
4. This code aborts on error, but does not inform you that an error actually occurred. Hence, if the nth record contains an error, you will only get n-1 records out of the file, and main() won't know any different. There are a number of ways to settle this error... the easiest being to check to see if any record has a dest1 or dest2 >= the number of records read. You'll have to do that.
5. Care is taken not to hardcode the length of any string in the readRooms() function. (Note the use of the sizeof operator.) Keep in mind this only works for char strings. (Unicode strings would need a bit more help.)

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

typedef enum {
  d_none, d_north, d_south, d_east, d_west,  d_error = -1
  } direction_t;

const char *directions[] = {
  "none", "north", "south", "east", "west"
  };

typedef struct {
    int         room_num;
    char        room_name[ 80 ];
    char        desc[ 1000 ];
    int         num_links;
    direction_t direction1;
    int         dest1;
    direction_t direction2;
    int         dest2;
} roomStruct;

direction_t str_to_direction( const char *d );
int readRooms( const char *filename, roomStruct rooms[], int max_rooms );

/* -----------------------------------------------------------------
  main
----------------------------------------------------------------- */
int main () {
  int        num_rooms;
  roomStruct rooms[ 100 ];

  num_rooms = readRooms( "maps.txt", rooms, 100 );

  printf( "number of rooms = %d", num_rooms );

  return 0;
}

/* -----------------------------------------------------------------
  fgetline
--------------------------------------------------------------------
  Get an entire line from file, filling 'max' characters with
  the result and tossing any extra, stripping the newline,
  guaranteeing the string is nul-terminated,
  and returning success or failure.
*/
int fgetline( FILE *f, char *s, int max ) {
  char c;
  int  i;

  /* Get the line from file */
  if (fgets( s, max, f ) == NULL) return 0;

  /* Make sure the entire line was read */
  i = strlen( s ) -1;
  if (s[ i ] != '\n') {
    while ((c = fgetc( f )) != '\n')
      if (c == EOF) break;
    }
  /* Strip the trailing newline */
  else s[ i ] = '\0';

  return 1;
  }

/* -----------------------------------------------------------------
  mstricmp
--------------------------------------------------------------------
  The POSIX stricmp function is deprecated and not always
  available anyway... so we roll our own here.
*/
int mstricmp( const char *s, const char *t ) {
  int i;
  char *ss, *tt;
  for (
    ss = (char *)s, tt = (char *)t;
    *ss || *tt;
    ss++, tt++
    ) {
    i = tolower( *ss ) - tolower( *tt );
    if (i != 0) return i;
    }
  return 0;
  }

/* -----------------------------------------------------------------
  str_to_direction
--------------------------------------------------------------------
  Convert a character string into a direction_t.
  Takes any string that is <= a direction name without
  case-sensitivity.
  Hence, "n" == "north", as do "no", "NOR", etc.
*/
direction_t str_to_direction( const char *d ) {
  direction_t i;

  for (i = d_none; i <= d_west; i++)
    if (mstricmp( d, directions[ i ] ) <= 0) return i;

  return d_error;
  }

/* -----------------------------------------------------------------
  readRooms
--------------------------------------------------------------------
  Read records from file. Returns the number of complete records read.
*/
int readRooms( const char *filename, roomStruct rooms[], int max_rooms ) {
  char  s[ 20 ];
  char *p;
  FILE *f;
  int   i = 0;

  if ((f = fopen( filename, "r" )) == NULL) return 0;

  do {
    /* get the room number */
    if (!fgetline( f, s, 20 )) break;
    rooms[ i ].room_num = strtol( s, &p, 10 );
    if (*p != '\0') break;

    /* get the room name */
    if (!fgetline( f, rooms[ i ].room_name, sizeof( rooms[ i ].room_name ) )) break;

    /* get the room description */
    if (!fgetline( f, rooms[ i ].desc, sizeof( rooms[ i ].desc ) )) break;

    /* get the number of links */
    if (!fgetline( f, s, 20 )) break;
    rooms[ i ].num_links = strtol( s, &p, 10 );
    if (*p != '\0') break;

    /* get direction 1 */
    if (!fgetline( f, s, 20 )) break;
    if ((rooms[ i ].direction1 = str_to_direction( s )) == d_error) break;

    /* get destination 1 */
    if (!fgetline( f, s, 20 )) break;
    rooms[ i ].dest1 = strtol( s, &p, 10 );
    if (*p != '\0') break;

    /* get direction 2 */
    if (!fgetline( f, s, 20 )) break;
    if ((rooms[ i ].direction2 = str_to_direction( s )) == d_error) break;

    /* get destination 2 */
    if (!fgetline( f, s, 20 )) break;
    rooms[ i ].dest2 = strtol( s, &p, 10 );
    if (*p != '\0') break;

    if (++i >= max_rooms) break;
  } while (!feof( f ));

  fclose( f );
  return i;
  }

The purpose for posting all this wasn't to make your life thought-free. It was to give you a template upon which to work your program further. It provides examples of:
1. Good I/O.
2. Use of enums and conversion. (enum to string: directions[ d_north ] )
3. Use of a loop.
4. Care when handling maxima (i.e. you can't read more records than you have room for --currently at 100).
5. capacity for good error checking.

Well, that's enough. Enjoy.

Oh yeah... (sorry for the double-post)

the error checking in the example is fairly simplistic. It glosses over errors like lines that are too long in the file. So, for example, if your description is longer than 999 characters, the player will see only the first 999 and the rest are ignored.

It would be better to dynamically allocate the space for description. That way it can be as large or small as you want...

Hope this helps.

I'm completely baffled as to how to read in each room from the map file and stick each room into its own struct and have each element of the room in its own little place in the struct.

I'd really like to just use the format

fgets(rooms[i].room_num, fin);
fgets(rooms[i].room_name, fin);
fgets(rooms[i].desc, fin);
fgets(rooms[i].num_links, fin);

etc...

and get just ONE room into ONE struct.

I assume the above did not work properly for some reason. So what happened? What didn't work?

fgets() is prototyped:

char *fgets(char *restrict s, int n, FILE *restrict stream);

meaning it returns a pointer to a string and accepts three arguments. 1st, a string, 2nd, size of the storage available to it and 3rd, a stream or where it reads from.

Let's look at your calls to fgets:

fgets(rooms[i].room_num, fin);

rooms.room_num is an integer and not an array of characters as is needed, futhermore is missing the second argument which should be an int for the size of the space available.


fgets(rooms[i].room_name, fin);

rooms.room_name is the correct argument, however is missing the second one.

fgets(rooms[i].desc, fin);
fgets(rooms[i].num_links, fin);

The last two I'll leave it to you to figure out where and why they are wrong.
The information in the file maps.c needs to be formatted in a neat and controlled way that
will allow a methodical reading by fgets or fscanf.

By the way, if you use fgets to obtain an integer, it is necessary to store the read string in an array of chars and then scan for the integer using sscanf().

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