From bf83664da44a7f3f6926c962dea83ab07f5978b6 Mon Sep 17 00:00:00 2001 From: cdez Date: Sun, 5 Nov 2023 22:55:38 -0700 Subject: [PATCH] Maze Runner Program ++ Maze Runner Files ++ Makefile for Maze Runner --- MazeRunner/README.txt | 39 ++++++++++ MazeRunner/maze_runner.c | 54 +++++++++++++ MazeRunner/mazelib.c | 162 +++++++++++++++++++++++++++++++++++++++ MazeRunner/mazelib.h | 14 ++++ MazeRunner/runner.c | 52 +++++++++++++ MazeRunner/runner.h | 3 + 6 files changed, 324 insertions(+) create mode 100644 MazeRunner/README.txt create mode 100644 MazeRunner/maze_runner.c create mode 100644 MazeRunner/mazelib.c create mode 100644 MazeRunner/mazelib.h create mode 100644 MazeRunner/runner.c create mode 100644 MazeRunner/runner.h diff --git a/MazeRunner/README.txt b/MazeRunner/README.txt new file mode 100644 index 0000000..9ba3e69 --- /dev/null +++ b/MazeRunner/README.txt @@ -0,0 +1,39 @@ +In this project, you will fill in the code necessary to solve a maze. I have +already written the code which is capable of generating and displaying the +maze, as well as a few function that will let you interact with the maze. All +you have to do is write the code that actually marks the path through the maze. + +I have outlined an algorithm in English by which you might accomplish this in +the file "runner.c" which is the *only* file that you should edit and submit +for this assignment. + +Since this program will consist of multiple files that all need to be connected +together to work, I have provided a Makefile to assist with compiling it. If +you are on Mac, you should already have the "make" program installed. If you +are on WSL, execute the command "sudo apt-get install build-essential" to gain +access to "make". + +To build the project, open up a shell, move to the directory containing the +project files, and simply issue the command 'make'. This will build the +project and show any compiler warnings and/or errors. If the build is +successful, you will be able to execute the program via the command +"./maze_runner". Do this once now to see the usage text which tells you how +the program ought to be invoked. + +Now, all that's left for you to do is to open up the various code files and +explore them and then to write your part of the code in runner.c. + +The point of this assignment is for you to get practice breaking a larger +problem down into subproblems via functions, so make sure that you try to do +that. For reference, in my solution to this problem, I created 7 other +functions that are called by runner_solve(). While it is possible to complete +this project with fewer functions, you should probably have at least 4. + +Again, don't edit any files except runner.c as I will be testing your +submission with the "stock" version of all the other project files. Should you +accidentally modify another file, you can always just unzip the archive they +came in again. But, ***BE CAREFUL*** that you don't accidentally override the +file you've done all your work in if you do that. + +When you are satisfied with your work, simply upload *only* your runner.c file +to the BBLearn assignment submission. Good luck! diff --git a/MazeRunner/maze_runner.c b/MazeRunner/maze_runner.c new file mode 100644 index 0000000..4d2b795 --- /dev/null +++ b/MazeRunner/maze_runner.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include "mazelib.h" +#include "runner.h" + +void usage(void); + +int main(int argc, char* argv[]) { + if (argc < 3) { + usage(); + return 1; + } + + int width = (int) strtol(argv[1], NULL, 10); + int height = (int) strtol(argv[2], NULL, 10); + + // The dimensions are valid when the width is between 9 and 79 (inclusive) and + // the height is between 9 and 25 (inclusive). Both the width and the height + // must also be odd. + bool dimensions_are_valid = ((9 <= width && width <= 79) && + (9 <= height && height <= 25) && + (width % 2 == 1) && (height % 2 == 1)); + if(!dimensions_are_valid) { + usage(); + return 1; + } + + // Oh, if you want to reuse the same maze for testing purposes, you can pass + // a secret 3rd argument to the program. Any integer will do, just pass the + // same one each time and it will be the same maze + unsigned int seed; + if (argc == 4) { + seed = (int) strtol(argv[3], NULL, 10); + } else { + seed = time(NULL); + } + + maze_init(width, height, seed); + + maze_display(); + printf("\n\n\n"); + // You must implement this function in runner.c and declare it in runner.h + runner_solve(); + maze_display(); + return 0; +} + +// This function displays the usage message if the parameters are missing or incorrect. +void usage(void) { + printf("Usage: mazerunner width height \n"); + printf("\t width must be an odd integer between 9 and 79 inclusive\n"); + printf("\t height must be an odd integer between 9 and 25 inclusive\n\n"); +} diff --git a/MazeRunner/mazelib.c b/MazeRunner/mazelib.c new file mode 100644 index 0000000..45dc874 --- /dev/null +++ b/MazeRunner/mazelib.c @@ -0,0 +1,162 @@ +// PAY NO MIND TO ANYTHING FROM HERE... +#include +#include +#include +#include +#include "mazelib.h" + +#define NORTH 0 +#define EAST 1 +#define SOUTH 2 +#define WEST 3 + +static void carve(int, int); +static void shuffle(int[4]); + +static int maze_width; +static int maze_height; +static char *maze; +static bool is_maze_being_initialized = false; +// ... TO HERE + + +//////////////////////////////////////////////////////////////////////////////// +// BEGIN: THINGS YOU SHOULD LOOK AT (mostly the comments) // +//////////////////////////////////////////////////////////////////////////////// + + +// Returns the character in the maze at the given row and column. First checks +// to ensure that the attempted access is valid and, if not, terminates the +// program. +char maze_get_char(int row, int col) { + bool is_in_bounds = ((0 <= row && row < maze_height) && + (0 <= col && col < maze_width)); + if (!is_in_bounds) { + printf("Illegal maze access at row %i and col %i\n", row, col); + exit(EXIT_FAILURE); + } + return maze[(row * maze_width) + col]; +} + + +// Sets the character at the given row and column to the replacement +// character. Replacement of the start and end signifiers and walls (#'s) +// will be prevented unless the maze is being initialized, in which case, it +// is allowed. +// +// Since it calls maze_get_char first, this function will also cause program +// termination if passed a row or column that is out of bounds +void maze_set_char(int row, int col, char replacement) { + char c = maze_get_char(row, col); + if (!is_maze_being_initialized && (c == '#' || c == 'S' || c == 'E')) { + return; + } + + maze[(row * maze_width) + col] = replacement; +} + + +void maze_display(void) { + for(int row = 0; row < maze_height; row++) { + for(int col = 0; col < maze_width; col++) { + printf("%c", maze_get_char(row, col)); + } + printf("\n"); + } + printf("\n"); +} + + +//////////////////////////////////////////////////////////////////////////////// +// BEGIN: STUFF YOU DON'T NEED TO LOOK AT OR UNDERSTAND FOR THE PROJECT // +//////////////////////////////////////////////////////////////////////////////// + + +// maze_init allocates the memory for the maze array based on width and height, +// fills every square of the maze with walls, and then calls carve() to +// remove walls in a way that creates valid maze paths +void maze_init(int width, int height, int seed) { + maze_width = width; + maze_height = height; + + maze = malloc(sizeof(*maze) * maze_width * maze_height); + + // Fill maze completely with walls + for(int i = 0; i < maze_width * maze_height; i++) { + maze[i] = '#'; + } + + is_maze_being_initialized = true; + // Seed the random number generator, since carve() uses of random numbers + srand(seed); + // We always start carving the maze from row 1 column 1 to ensure + // that we always have a valid start there and a valid end in the bottom + // right hand corner + carve(1, 1); + + maze_set_char(1, 1, 'S'); + maze_set_char(maze_height-2, maze_width-2, 'E'); + is_maze_being_initialized = false; +} + +/* + Starts at a given index and recursively carves toward every direction in a + randomized order. Recursion ends at a particular location when the blocks in + every possible direction have already been carved. +*/ +static void carve(int row, int col) { + maze_set_char(row, col, ' '); + + int directions[4] = {NORTH, EAST, SOUTH, WEST}; + shuffle(directions); + + for (int i=0; i < 4; i++) { + // Find the appropriate offsets for the row and column to move in the + // current direction + + int row_offset = 0; + int col_offset = 0; + int current_direction = directions[i]; + // Only one offset will be changed and the other will remain zero + if (current_direction == NORTH) { + row_offset = -1; + } + else if (current_direction == SOUTH) { + row_offset = +1; + } + else if (current_direction == WEST) { + col_offset = -1; + } + else { + col_offset = +1; + } + + // Find the row and col for the spot *two* squares forward in the current + // direction + int next_row = row + (2 * row_offset); + int next_col = col + (2 * col_offset); + + bool is_in_bounds = ((0 <= next_row && next_row < maze_height) && + (0 <= next_col && next_col < maze_width)); + + // If that spot is in bounds and hasn't already been carved out... + if (is_in_bounds && maze_get_char(next_row, next_col) == '#') { + // carve out the wall between the current spot from and the next spot... + maze_set_char(row + row_offset, col + col_offset, ' '); + // and then continue carving from the new spot + carve(next_row, next_col); + } + } +} + + +static void shuffle(int directions[4]) { + for(int i = 0; i < 4; i++) { + // Random number between 0 and 3 + int random_index = rand() % 4; + // Swap the thing at the random index and the current index (i) + int temp = directions[random_index]; + directions[random_index] = directions[i]; + directions[i] = temp; + } +} diff --git a/MazeRunner/mazelib.h b/MazeRunner/mazelib.h new file mode 100644 index 0000000..0324d7b --- /dev/null +++ b/MazeRunner/mazelib.h @@ -0,0 +1,14 @@ +#pragma once +#include + + +//-------------Functions you SHOULD use in your runner.c file------------------ +// Take a look in mazelib.c to see how they work and what they are for +char maze_get_char(int, int); +void maze_set_char(int, int, char); + +//----Functions you may use for debugging, but not in your final submission---- +void maze_display(void); + +//-----------------------Functions you should NOT use-------------------------- +void maze_init(int, int, int); diff --git a/MazeRunner/runner.c b/MazeRunner/runner.c new file mode 100644 index 0000000..9cdd972 --- /dev/null +++ b/MazeRunner/runner.c @@ -0,0 +1,52 @@ +#include +#include +// Including a header file lets your file refer to the functions *declared* in +// that header file. The functions are usually *defined* elsewhere though, in +// this case, mazelib.c +#include "mazelib.h" + +#define NORTH 0 +#define EAST 1 +#define SOUTH 2 +#define WEST 3 + + +void runner_solve(void) { + // Use a few variables to keep track of the current row and column position + // of the "runner". The runner should always start at the 'S' symbol in the + // maze. Luckily, the 'S' symbol is always in the same place + + // You should also keep track of which way the runner is facing. You can use + // the preprocessor defines at the top of this file to make your life easier + // instead of just having to remember "0 is North, 1 is East, etc." + // Basically, any time you use NORTH in your code, a zero will be substituted + // in for it at compile time. + + // Ok, algorithm time: + // + // 1. As long as you are not standing on the 'E', do the following: + // 2. Turn to your left + // 3. Can you go that way? I.E., is there not a wall in front of you? + // 4. If your path is blocked, turn to your right and go back to (3) + // 5. If your path is open, throw down the appropriate breadcrumb (see note) + // 6. Move forward and go back to (1) + // 7. You are standing on the 'E', STOP, you're done! + + // NOTE: By "appropriate breadcrumb" I mean this: + // If you are in an empty square, the breadcrumb should be '.' + // If you are in a square with '.', the breadcrumb should be 'o' + // If you are in a square with 'o', the breadcrumb should be 'O' + // If you are in a square with 'O', the breadcrumb should be '@' + // + // Here is an example of a solved maze: + // + // ########################### + // #S....#.oo..#.oooOoooooo..# + // #####.###.#.#####o#####.#.# + // # #.#...#.....#.#.....#.# + // # # #.#.#######.###.#####.# + // # # #...# #.#...#.....# + // # ####### # ###.#.###.##### + // # # ...# ....E# + // ########################### +} diff --git a/MazeRunner/runner.h b/MazeRunner/runner.h new file mode 100644 index 0000000..cdb5d27 --- /dev/null +++ b/MazeRunner/runner.h @@ -0,0 +1,3 @@ +#pragma once + +void runner_solve(void);