Question 1. List some differences between text and binary data files.
Text files store lines of characters, intended to be human-readable if printed, displayed to the screen, read into a text editor, etc. Binary files contain arbitrary byte values which make little or no sense if interpreted as characters.
Binary files may be smaller, and if read and written in a simpleminded way, they may be more efficient to read and write, and the code to do so may be easier to write. Text files tend to be larger, to take longer (than binary files) to read and write, and to require more elaborate code. (However, if binary files are read in safer ways, or written in more portable ways, the code to do so is as complicated as for text files, and will probably be intermediate in efficiency between text files and simpleminded binary files.)
Text files are quite portable. Binary files will not necessarily work if they are moved between machines having different word sizes or data formats, or between compilers which lay out data structures differently in memory.
It's easy to create, inspect, and modify text files using standard tools. Binary file maintenance generally requires specially-written tools.
Binary files must be written using fopen mode "wb" and read using mode "rb"
Exercise 2. Devise and implement a way for long descriptions to be read from the data file.
There are many ways of doing this. All are a bit tricky, given certain constraints imposed by the existing code.
The most obvious way is to use lines of the form
desc You are in a dark and gloomy cave.in the data file. (Such lines could of course be used for objects as well.) The only problem with this approach is that in the existing data file reading code in io.c, each line read from the data file is immediately broken up into words by calling getwords, and this break-up happens before it's decided what keyword began the line and what's to be done with the rest of the information on the line. In the case of long descriptions, we want the rest of the text on the line (after the keyword desc) to be treated as a single string, not a series of individual words.
One way around this problem is to make a copy of the line read from the data file before breaking it up with getwords. I declared a second array
char line2[MAXLINE];at the top of parsedatafile in io.c, and changed the old line
ac = getwords(line, av, MAXARGS);to
strcpy(line2, line); ac = getwords(line2, av, MAXARGS);Now line2 is broken up by getwords (and av ends up containing pointers into line2), but the contents of line remain intact.
Then I added this case to the if/else chain that makes up the bulk of parsedatafile:
else if(strcmp(av[0], "desc") == 0) { char *p; for(p = line; *p != '\0' && !isspace(*p); p++) /* skip "desc" */ ; for(; isspace(*p); p++) /* skip whitespace */ ; if(currentobject != NULL) currentobject->desc = chkstrdup(p); else if(currentroom != NULL) currentroom->desc = chkstrdup(p); else fprintf(stderr, "desc not in object or room\n"); }The code has two more troublesome spots: first, since the line array hasn't been broken into words (which was the point, so as not to break up the descriptive text), we have to manually skip over the keyword ``desc'' and the whitespace to find the beginning of the description itself. Second, since I've declared the long descriptions in the object and room structures as char *, I have to allocate memory to hold a copy of the description. I do that by calling the function chkstrdup, which makes a duplicate copy of a string in newly-allocated memory, checking the return value of the memory allocation for me so that I don't have to. (On another sheet you'll find the code for chkstrdup and another function chkmalloc which it calls. chkstrdup is declared in chkmalloc.h; both chkmalloc.c and chkmalloc.h can be found in the week5 subdirectory of the source tree.) We'll have much more to say about dynamic memory allocation in upcoming weeks, so don't worry about the details of chkstrdup too much.
If you chose to use character arrays as the long description fields in the object and room structures, you don't have to worry about memory allocation, but can instead use something like
strcpy(currentobject->desc, p);to copy the long description to your array. Just make sure you understand why a single call to strcpy would not work if desc is declared as char *, and why the simple assignment
currentobject->desc = p;would not work if desc was an array or a pointer (for a different reason in each case).
In a real game, room descriptions are likely to be more like a paragraph in length, so cramming them all onto one desc line in the data file is quite cumbersome. How could we spread them across multiple lines in the data file? One way is by allowing multiple desc lines, and arranging that they all get concatenated together to form the complete description. Here's how I implemented that:
else if(strcmp(av[0], "desc") == 0) { char *p; char *newstr; for(p = line; *p != '\0' && !isspace(*p); p++) /* skip "desc" */ ; for(; isspace(*p); p++) /* skip whitespace */ ; if(currentobject != NULL) { if(currentobject->desc == NULL) currentobject->desc = chkstrdup(p); else { newstr = chkmalloc( strlen(currentobject->desc) + 1 + strlen(p) + 1); strcpy(newstr, currentobject->desc); strcat(newstr, "\n"); strcat(newstr, p); free(currentobject->desc); currentobject->desc = newstr; } } else if(currentroom != NULL) { if(currentroom->desc == NULL) currentroom->desc = chkstrdup(p); else { newstr = chkmalloc( strlen(currentroom->desc) + 1 + strlen(p) + 1); strcpy(newstr, currentroom->desc); strcat(newstr, "\n"); strcat(newstr, p); free(currentroom->desc); currentroom->desc = newstr; } } else fprintf(stderr, "desc not in object or room\n"); }Now the memory allocation is more complicated, because when we already have a (partial) long description string for a given room or object, we have to allocate a new, longer one and free the old one. (There's an easier way to do this, involving another standard library memory allocation function called realloc. We'll see it later.)
Another option would be to decide that long descriptions span many lines on the data file, followed by some terminator, without having each descriptive line preceded by any keyword. For example, we might have lines in the data file like this:
desc You are in a dark and gloomy cave. The air is stale and damp. In the shadows you can hear various squeaking and chattering noises. desc endHere, the word desc alone on a line indicates that a long description follows, up to the line containing desc end.
I implemented that option like this. Notice that the description-reading code therefore has to make more calls to fgetline (i.e. besides the main one at the top of the loop in parsedatafile). In this case, we don't have to worry about the allocation of line vs. line2, and we also don't have to skip over anything to find the start of each descriptive line.
else if(strcmp(av[0], "desc") == 0) { char *newstr; while(fgetline(fp, line, MAXLINE) != EOF) { if(strcmp(line, "desc end") == 0) break; if(currentobject != NULL) { if(currentobject->desc == NULL) currentobject->desc = chkstrdup(line); else { newstr = chkmalloc( strlen(currentobject->desc) + 1 + strlen(line) + 1); strcpy(newstr, currentobject->desc); strcat(newstr, "\n"); strcat(newstr, line); free(currentobject->desc); currentobject->desc = newstr; } } else if(currentroom != NULL) { if(currentroom->desc == NULL) currentroom->desc = chkstrdup(line); else { newstr = chkmalloc( strlen(currentroom->desc) + 1 + strlen(line) + 1); strcpy(newstr, currentroom->desc); strcat(newstr, "\n"); strcat(newstr, line); free(currentroom->desc); currentroom->desc = newstr; } } } }
Exercise 3. Expand the set of allowable exits from rooms.
I modified the room structure in game.h like this:
#define NEXITS 10 struct room { char name[MAXNAME]; struct object *contents; struct room *exits[NEXITS]; char *desc; /* long description */ }; /* direction indices in exits array: */ #define NORTH 0 #define SOUTH 1 #define EAST 2 #define WEST 3 #define NORTHEAST 4 #define SOUTHEAST 5 #define NORTHWEST 6 #define SOUTHWEST 7 #define UP 8 #define DOWN 9(By adding the new directions at the end of the array, rather than interspersing them with the original four, any array initializations such as we used to have in main.c would still work, since it's legal to supply fewer initializers for an array than the maximum number of elements in the array.)
I added this code to docommand in commands.c:
else if(strcmp(verb, "ne") == 0 || strcmp(verb, "northeast") == 0) dircommand(player, NORTHEAST); else if(strcmp(verb, "se") == 0 || strcmp(verb, "southeast") == 0) dircommand(player, SOUTHEAST); else if(strcmp(verb, "nw") == 0 || strcmp(verb, "northwest") == 0) dircommand(player, NORTHWEST); else if(strcmp(verb, "sw") == 0 || strcmp(verb, "southwest") == 0) dircommand(player, SOUTHWEST); else if(strcmp(verb, "up") == 0) dircommand(player, UP); else if(strcmp(verb, "down") == 0) dircommand(player, DOWN);
I added this code to the roomexits case in parsedatafile in io.c:
else if(strcmp(av[i], "ne") == 0) dir = NORTHEAST; else if(strcmp(av[i], "se") == 0) dir = SOUTHEAST; else if(strcmp(av[i], "nw") == 0) dir = NORTHWEST; else if(strcmp(av[i], "sw") == 0) dir = SOUTHWEST; else if(strcmp(av[i], "u") == 0) dir = UP; else if(strcmp(av[i], "d") == 0) dir = DOWN;
This page by Steve Summit // Copyright 1995-9 // mail feedback