Today’s lab will build on your work from the previous lab. We will add support for loading and saving mazes to the maze editor you’ve already build. That means you’ll need a working editor before you can complete today’s lab work, so if you haven’t finished the previous lab you should start there first.
Before we jump into the implementation, here is a quick overview of what we’ll be adding to the maze editor:
$ ./maze sample.maze
If the file passed in on the command line doesn’t exist, the maze editor will initialize a default maze with just an outside border.
If the file passed in does exist and can be loaded successfully, the maze editor will load it and display that maze instead.
s key, the maze editor will save the current state of the maze to a file.
The program will always write to the filename passed in as a command line argument.We’ll go through these four features in order. There are some additional convenience features you might choose to add in the optional final part of the lab.
The first step we’ll need to take to load and save files is to modify the maze editor to accept a file name as a command line argument.
Modify your maze editor to check command line arguments as the very first step in the main function, before initializing ncurses.
Your program should expect exactly one argument, and should report an error if the arguments don’t match this expectation:
$ ./maze
Usage: ./maze <filename>
$ ./maze sample.maze extra_arg another_extra_arg
Usage: ./maze <filename>
Once you’ve checked the command line arguments, save the given filename in a local variable in main with a descriptive name like filename.
Commit and push your changes to git before moving on.
Next, we’re going to set our program up to start with a default, empty maze. The final version of the program will create a default maze any time it is run with a filename that does not exist yet. We’ll also fall back on the default maze if we run into errors when trying to load a maze from a file.
Add this function to your maze.c, which you’ll implement shortly:
void load_default_maze(int maze[MAZE_SIZE * MAZE_SIZE]) {
// TODO: implement me
}
This function should fill in the maze array with values to represent a maze that is entirely empty space except for a border around the edge of the maze.
Here’s what that maze should look like:
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒__ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒ ▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
Your implementation used a hard-coded maze before, but you can remove that now.
Keep the maze variable, but do not assign it a value.
Instead, call the load_default_maze function and pass in maze to initialize it.
Your editor should work just as it did at the end of day 1.
Commit and push your changes to git before moving on.
Next, we’re going to try to load a maze from the filename passed to our maze program.
We’re going to use a very simple format to store the maze:
every cell in the maze will be represented by a '0' or '1' character in the file.
All 529 cells (for a 23x23 maze) will appear consecutively in row-major order.
Add the following function to maze.c so you can start your implementation:
void load_maze(char* filename, int maze[MAZE_SIZE * MAZE_SIZE]) {
// TODO: Implement this function
}
Before you start to implement load_maze, replace the call to load_default_maze in main with a call to this new function.
If the given file doesn’t exist, load_maze will just call load_default_maze.
Using the material from today’s reading, you should have all the information you need to read in the maze file. Here are a few important requirements or hints:
Always check for errors.
If anything goes wrong when loading the maze you can call load_default_maze and then return.
Once you’ve opened a maze file with fopen you should always make sure to close it with fclose.
The load_maze function should take care of this (there’s no need to keep the file open while the editor is running).
That means there shouldn’t be any way to return from load_maze without closing the file unless it failed to open in the first place.
You can read in the maze file using fgets, although there are other good choices if you prefer another function.
Your load_maze function should make sure the maze file contains exactly the right number of cells, and must reject any file that has a character other than '0' or '1' in the maze file.
Any time you encounter an invalid maze file, just load the default maze instead.
Test your implementation thoroughly to make sure it can successfully load a valid maze, and sets up a default maze when something goes wrong.
You can download the sample.maze file to test loading a valid file.
Make sure your program can detect invalid maze files (e.g. too short, too long, or invalid characters) and just silently loads a default maze in these cases.
If you choose to complete part E below you can add some useful error reporting in these cases.
Commit and push your changes to git before moving on.
Finally, add a function to save the maze any time the user types s. Implement your saving code in this function:
void save_maze(char* filename, int maze[MAZE_SIZE * MAZE_SIZE]) {
// TODO: Implement this function
}
As with loading, your implementation will need to check for errors and should always close the maze file before returning from save_maze.
You are welcome to use any file functions you prefer for this implementation, but fputc is one choice that could work well.
Test your implementation and make sure it can save and load mazes. If you have extra time, move on to the next part of the lab to add some additional features to the editor.
Make sure you commit and push your changes to git.
You’ve likely noticed by now that the editor doesn’t give any informative feedback to the user when something goes wrong with loading a maze file.
One way to handle this is to display a status message for the user, but we can’t do that with printf when we’re in ncurses mode.
Instead, you can reserve an area just below or above the maze to show messages for some amount of time.
You can do this by creating a global variable char* message (declared outside of the main function).
Any time this message is set to a non-NULL string, the game loop should display the message to the user.
Your load_maze and save_maze functions can set this global variable to a descriptive message any time something goes wrong, which gives the user a little more of an indication that something unusual has happened.
Messages about loading or saving need to be visible for a while, but they probably shouldn’t stay on screen indefinitely. Instead, you should set them up to display for a while (a few seconds), and then disappear. Figuring out how to do this is part of the exercise, but here’s a hint: you can take advantage of the fact that our maze editor should be running at a predictable frame rate (e.g. 60 frames a second) to keep track of how long a message has been on screen.
As a final optional improvement, you might want to add some sort of visual indication to tell the user that the maze has been edited since it was last saved. It’s up to you to decide how to indicate this. Saving the maze should hide the indicator, and any edit made to the maze should cause it to reappear. Moving the cursor should not show the indicator, since cursor location is not stored in the maze file.