Maze Runner Program

++ Maze Runner Files
++ Makefile for Maze Runner
This commit is contained in:
Charles Descamps 2023-11-05 22:55:38 -07:00
parent 42fc64e613
commit bf83664da4
6 changed files with 324 additions and 0 deletions

39
MazeRunner/README.txt Normal file
View file

@ -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!

54
MazeRunner/maze_runner.c Normal file
View file

@ -0,0 +1,54 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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");
}

162
MazeRunner/mazelib.c Normal file
View file

@ -0,0 +1,162 @@
// PAY NO MIND TO ANYTHING FROM HERE...
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#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;
}
}

14
MazeRunner/mazelib.h Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
//-------------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);

52
MazeRunner/runner.c Normal file
View file

@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdbool.h>
// 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#
// ###########################
}

3
MazeRunner/runner.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void runner_solve(void);