CMSC216 Project 1: C Programming
- Due: 11:59pm Mon Mon 24-Feb-2025
- Approximately 4.0% of total grade
- Submit to Gradescope
- Projects are individual work: no collaboration with other students is allowed. Seek help from course staff if you get stuck for too long.
CODE DISTRIBUTION: p1-code.zip
VIDEO OVERVIEW: https://youtu.be/B-dCO2Xt79c
CHANGELOG:
- Thu Feb 20 12:59:36 PM EST 2025
The doc comments for Problem 2's
maze_print_state()
incorrectly placed the column axis labels above the maze when all test cases expect them below. The maze should print in the following fashion:################: 0 #0123456789a123#: 1 #1###5######2#4#: 2 #2###6##E #34 #: 3 #3###7####4## #: 4 #456789a1234 #: 5 ################: 6 0123456789012345 <<< Column 1's digit, below maze 0 1 <<< Column 10's digit, below maze queue count: 4 NN ROW COL 0 4 10 1 5 11 2 3 13 3 2 14
Docs have been updated to remove this inconsistency. No provided code changes for this. The comply with tests, implementations just need to move the column label printing after the loops that print the tiles. Apologies for the inconvenience.
- Wed Feb 19 04:20:12 PM EST 2025
- Updated documentation comments to
clarify that
maze_bfs_process_neighbor()
returns 1 when a newly FOUND tile has its path extended and is added to the search queue; this contrasts Blocked and out-of-bounds tiles which cause no changes to any data and yield a 0 return value. - Mon Feb 17 08:48:14 AM EST 2025
Clarified the required error message on
maze_from_file()
when the file cannot be opened as reported in Post 123.Post 112 reported a discrepancy in the documentation for
maze_bfs_step()
which specified an incorrect log level for some log printing. The correct logging portion is// LOGGING: // If LOG_LEVEL >= LOG_BFS_STEPS, print a message like // LOG: processing neighbors of (5,1) // at the start of the function. // // If LOG_LEVEL >= LOG_BFS_STATES, prints a message and uses // maze_print_state() at the end of the function to show the maze // after processing finishes as in: // LOG: maze state after BFS step // ################: 0 // #01234 #: 1 // ... int maze_bfs_step(maze_t *maze){
Printing all messages when the log level is
LOG_BFS_STEPS
will cause a test failure on Problem 4 Test 7 where there is a distinction between two log levels.- Fri Feb 14 10:46:33 AM EST 2025
- Post 104 reported a minor bug
that might cause folks working to solve Problem 3 test 6 a bit of
trouble. That test has been updated and students can get the updated
version via a
make update
. - Wed Feb 12 09:05:59 PM EST 2025
Typos were found in the testing file for Problem 3 (
test_mazesolve3.org
). The file can be updated by running the command>> make update
which will create backups of the project, then download up-to-date copies of all provided files. Students who already downloaded the code pack should do this while those who download the codepack after this announcement will already have the changes.
- Wed Feb 12 08:31:27 PM EST 2025
A video overview of project 1 has been posted here: https://youtu.be/B-dCO2Xt79c. Students wishing for additional discussion and demonstration of steps on how to get started with the function may benefit from watching the video.
The missing images illustrating the process of submitting a completed project have been fixed.
1 Introduction
Breadth First Search (BFS) is a fundamental algorithm in computing that allows data modeled with mathematical graphs to be processed and analyzed for varying effects. A classic usage of BFS is to determine the Shortest Path between a Start and End point in grid-like graphs such as in 2D mazes, sometimes referred to a "the dungeon problem" early computer games used text and characters to on the screen to draw dungeons that player would navigate. This project will process simple 2D maze data stored in text files and determine the shortest path from a Start position to all other reachable positions in the maze including an End tile.
2 Download Code and Setup
Download the code pack linked at the top of the page. Unzip this which will create a project folder. Create new files in this folder. Ultimately you will re-zip this folder to submit it.
File | State | Notes |
---|---|---|
Makefile |
Provided | Build file to compile all programs |
mazesolve_funcs.c |
CREATE | Problems 1-3 functions to write, outline provided below |
mazesolve_main.c |
CREATE | Problem 3 main function |
mazesolve.h |
Provided | Project Header file |
cmdline_args.c |
Provided | Demo of how to access command line arguments |
data/maze-little.txt |
Data | Tiny maze for testing |
data/maze-room1.txt |
Data | Modest maze for testing |
data/maze-big-mult1.txt |
Data | Large maze with multiple paths from Start to End |
… | ||
testy |
Testing | Test running script |
test_mazesolve_funcs.c |
Testing | Testing file for required C functions |
test-results/ |
Testing | Directory in which temporary testing files are written |
test_mazesolve1.org |
Testing | Problem 1 tests |
test_mazesolve2.org |
Testing | Problem 2 tests |
test_mazesolve3.org |
Testing | Problem 3 tests |
test_mazesolve4.org |
Testing | Problem 4 tests |
2.1 Makefile
A Makefile
is provided as part of this project. Building programs in
C is a bit tedious and most folks use build systems of which make
is a venerable entry. The instructions and dependencies to create programs
are written in a Makefile
which is then interpreted by the make
program which will run gcc
and other commands to create programs.
Use this Makefile
by issuing commands like make prob1
>> make help Typical usage is: > make # build all programs > make clean # remove all compiled items > make zip # create a zip file for submission > make prob1 # built targets associated with problem 1 > make test # run all tests > make test-prob2 # run test for problem 2 > make test-prob2 testnum=5 # run problem 2 test #5 only > make update # download and install any updates to project files >> make prob2 # build problem 2 demo program make prob2 gcc -Wall -g -Wno-unused-variable -c mazesolve_funcs.c gcc -Wall -g -Wno-unused-variable -o test_mazesolve_funcs test_mazesolve_funcs.c mazesolve_funcs.o > make clean # remove all programs/binary object files rm -f mazesolve_main test_mazesolve_funcs *.o >> make prob4 # build problem 4 main program gcc -Wall -g -Wno-unused-variable -c mazesolve_main.c gcc -Wall -g -Wno-unused-variable -c mazesolve_funcs.c gcc -Wall -g -Wno-unused-variable -o mazesolve_main mazesolve_main.o mazesolve_funcs.o gcc -Wall -g -Wno-unused-variable -o test_mazesolve_funcs test_mazesolve_funcs.c mazesolve_funcs.o > make clean # remove all programs/binary object files rm -f mazesolve_main test_mazesolve_funcs *.o >> make # build all programs/objects for the assignment gcc -Wall -g -Wno-unused-variable -c mazesolve_main.c gcc -Wall -g -Wno-unused-variable -c mazesolve_funcs.c gcc -Wall -g -Wno-unused-variable -o mazesolve_main mazesolve_main.o mazesolve_funcs.o gcc -Wall -g -Wno-unused-variable -o test_mazesolve_funcs test_mazesolve_funcs.c mazesolve_funcs.o
You are not required to understand all that is in the Makefile
(yet)
but it is a very useful tool well worth your time to learn.
2.2 Automated Tests
Automated tests are included with the code distribution. These tests are known to work on grace.umd.edu only but in most cases they should run identically in Linux environments. They may work on the Windows Subsystem for Linux but no guarantees are made. They very unlikely to run on MacOS natively as Linux-specific tools are used.
The provided Makefile
allows automated tests to be run via calls
like make test-prob1
to test Problem 1 and make test-prob2
to test
Problem 2. See the transcript below.
>> make test-prob1 # run tests for problem 1, compiles required code first gcc -Wall -g -Wno-unused-variable -c mazesolve_funcs.c gcc -Wall -g -Wno-unused-variable -o test_mazesolve_funcs test_mazesolve_funcs.c mazesolve_funcs.o ./testy -o md test_mazesolve1.org =============================================================== == test_mazesolve1.org : Problem 1 Row/Col Queue Function Tests == Running 10 / 10 tests 1) rcqueue_allocate_free1 : ok 2) rcqueue_add_rear1 : ok 3) rcqueue_add_rear2 : ok 4) rcqueue_get_front1 : ok 5) rcqueue_remove_front1 : ok 6) rcqueue_remove_front2 : ok 7) rcqueue_remove_front3 : ok 8) rcqueue_print1 : ok 9) rcqueue_print1 : ok 10) rcqueue_print2 : ok =============================================================== RESULTS: 10 / 10 tests passed >> make test-prob2 # run tests for problem 2 ./testy -o md test_mazesolve2.org ================================================================= == test_mazesolve2.org : Problem 2 Basic Tile/Maze Function Tests == Running 15 / 15 tests 1) tile_print_path1 : ok 2) tile_print_path2 : ok 3) tile_extend_path1 : ok 4) tile_extend_path2 : ok 5) maze_allocate_free1 : ok 6) maze_allocate_free2 : ok ... > make test # run tests for all problems ...
Each problem describes specifically how tests can be run and how credit will be assigned.
Note that one can run a single test with the following make
invocation which sets testnum
.
> make test-prob2 testnum=5
This is useful when debugging to limit the output and time it takes to check program results.
2.3 Multipart Tests
Some automated tests are reported as MULTIPART
. These involve several
steps such as repeated runs of a program or multiple criteria that are
checked in sequence. Multipart tests will be run in Segments and if
an early segment fails, the remaining segments will not run. The
Segment that fails will appear at the end of the results file and show
the usual diagnostic information (side-by-side diff, Valgrind report,
etc.). All Segments of a MULTIPART tests must pass for the test to succeed.
3 Maze Solutions via BFS
Mazes will be encoded in simple text files several of which are
present in the project codepack in the data/
directory. An example
is:
>> cat data/maze-simple2.txt rows: 7 cols: 16 tiles: ################ #S # # ### ###### # # # ### ##E # # # ### #### ## # # # ################
S
marks the Start Tile where the search beginsE
marks the End Tile which is the goal to be reached#
marks Wall tiles which are blocked and cannot be crossed- Spaces are Open tiles which can be traversed
The goal of the project is to create a program that will read this text file, construct and internal representation of the maze, use a BFS to determine paths to all other tiles, and then print out the shortest path from the Start to End tile. It is well summarized by a sample run.
>> ./mazesolve_main data/maze-simple2.txt maze: 7 rows 16 cols (1,1) start (3,8) end maze tiles: ################ #S # # ### ###### # # # ### ##E # # # ### #### ## # # # ################ SOLUTION: maze: 7 rows 16 cols (1,1) start (3,8) end maze tiles: ################ #S # #.### ###### # # #.### ##E..# # #.### ####.## # #.......... # ################ path length: 17 0: SOUTH 1: SOUTH 2: SOUTH 3: SOUTH 4: EAST 5: EAST 6: EAST 7: EAST 8: EAST 9: EAST 10: EAST 11: EAST 12: EAST 13: NORTH 14: NORTH 15: WEST 16: WEST
This demonstration shows several features of the program.
- It reads a text file with the maze in it and dynamically allocates data to store the maze
- After conducting the BFS and locating a path from Start to End, the program draws the path and prints out the sequence of movement directions to go from the Start to End tile, the 17 steps shown last
Much happens between the two steps indicated above that will be discussed further.
3.1 Breadth First Search Principles
The essence of a BFS is to maintain a Queue of positions to explore with the queue ordered on the "distance" from the Start tile. In this situation, only North / South / East / West movements are allowed and the regular grid means adjacent Tiles are 1 "move" apart (general graphs use edges to represented connected nodes possibly weighting the edges which leads to more complex approaches like Dijkstra's algorithm). A nice feature of queuing Tiles in this fashion is that Tiles nearer to the Start Tile will be explored first allowing the algorithm to determine the shortest path from the Start tile to any other. Below is adapted pseudocode for the type of BFS that will be implemented in the project.
1: def maze_bfs(maze): 2: start_tile = maze.tiles[maze.start_row][maze.start_col] 3: start_tile.state = FOUND 4: start_tile.path = [] # empty array 5: queue = make_empty_queue() # for row/col coordinates 6: queue.add_rear(maze.start_row, maze.start_col) 7: while queue is not empty: 8: (row,col) = queue.get_front() 9: cur_tile = maze.tiles[row][col] 10: for dir in [North,South,West,East]: 11: next_tile = tile in direction dir from (row,col) 12: if tile out of bounds or tile.type==Wall or tile.state==FOUND: 13: continue to next direction 14: else: 15: next_tile.state = FOUND 16: next_tile.path = copy of cur_tile.path w/ dir appended 17: queue.add_rear(row of next_tile, col of next_tile) 18: queue.remove_front() 19: # At the end of execution, all tiles have the shortest path from the 20: # Start tile to them stored in their path[] array 21: # 22: # Note: this variation calculates paths from Start to all other 23: # tiles but a common variant terminates the algorithm when a path to 24: # the End Tile is found
There are quite a few details associated with the pseudocode above that must be handled in a code implementation. The project will break this algorithm into smaller chunks that can be written and tested somewhat independently during development.
3.2 Demo of BFS in a Maze
An example of how BFS proceeds is instructive. Below is the execution
of the program the project implements with logging enabled so that
intermediate information is reported as the algorithm makes
progress. The additional output is enabled by using the command line
flag -log <num>
and causes output to be printed affixed with
LOG:
. The program will be required to handle this command line
argument AND function will examine a global LOG_LEVEL
variable and
print additional output for some of its values.
The program demo is shown in chunks.
Initialization
>> ./mazesolve_main -log 5 data/maze-small-threepath1.txt maze: 6 rows 9 cols (2,4) start (3,7) end maze tiles: ######### # # # # ##S ## # # ##E# # # ######### LOG: BFS initialization complete #########: 0 # # #: 1 # ##0 ##: 2 # # ##E#: 3 # #: 4 #########: 5 012345678 0 queue count: 1 NN ROW COL 0 2 4
In this first portion, the file is loaded and the BFS data is
initialized. The maze is drawn after the LOG: BFS initialization
complete
message with the S
replaced with a 0 as the Start tile has
a path of length 0 to itself. Below this drawing of the maze is the
state of the queue which has only the Start tile coordinates (2,4) in
it. This is the state of affairs after the first 5 lines of Pseudocode
for maze_bfs()
and before the main loop proceeds. The drawing
includes Axis Labels to make it easier to see the (row,col) positions
of various tiles.
BFS Step 1
LOG: BFS STEP 1 LOG: processing neighbors of (2,4) LOG: Found tile at (1,4) with len 1 path: N LOG: Found tile at (3,4) with len 1 path: S LOG: Skipping BLOCKED tile at (2,3) LOG: Found tile at (2,5) with len 1 path: E LOG: maze state after BFS step #########: 0 # 1# #: 1 # ##01 ##: 2 # #1##E#: 3 # #: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 1 4 1 3 4 2 2 5
During the firs iteration, the single element from the search Queue is
removed. It is the Start tile at (2,4) as indicated by the LOG:
message. Each of the neighbors of this tile are searched
- (1,4) which is North, an Open tile
- (3,4) which is South, an Open tile
- (2,3) which is North, a Wall
- (2,5) which is North, an Open tile
The Wall tile is ignored. Each of the Open tiles is marked as FOUND
and the empty path for (2,4) has the direction to go to reach neighbor
appended and copied to the neighbors. Each of these Found tiles is
added to the search Queue. Completing the first iteration of the
algorithm's while()
loop.
The search Queue is not empty so the outer loop will iterate again.
BFS Step 2
LOG: BFS STEP 2 LOG: processing neighbors of (1,4) LOG: Skipping BLOCKED tile at (0,4) LOG: Skipping FOUND tile at (2,4) LOG: Found tile at (1,3) with len 2 path: NW LOG: Skipping BLOCKED tile at (1,5) LOG: maze state after BFS step #########: 0 # 21# #: 1 # ##01 ##: 2 # #1##E#: 3 # #: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 3 4 1 2 5 2 1 3
At the front of the queue is (1,4) so it is removed from the queue and
its neighbors are processed. These are Walls (North, East) and Open
tiles (South, West). However, the Start tile is to the South and it
has already been marked as FOUND so is skipped from further
processing. Rather, only (1,3) is new and added at the end of the
queue with the path {North, West} as printed in the LOG:
message. That tile is now printed with a 2 in it as it is 2 moves away
from the Start tile.
BFS Step 3
LOG: BFS STEP 3 LOG: processing neighbors of (3,4) LOG: Skipping FOUND tile at (2,4) LOG: Found tile at (4,4) with len 2 path: SS LOG: Skipping BLOCKED tile at (3,3) LOG: Skipping BLOCKED tile at (3,5) LOG: maze state after BFS step #########: 0 # 21# #: 1 # ##01 ##: 2 # #1##E#: 3 # 2 #: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 2 5 1 1 3 2 4 4
Like the preceding step, (3,4) which is processed in this step, has only one new neighbor (4,4) with (3,3) and (3,5) being blocked by Walls and (2,4) being previously Found.
Remaining BFS Steps
The remaining output is shown below which eventually visits all tiles in the maze. It follows a similar pattern of processing the first queue element to add its unblocked/new neighbors to the search Queue. If in doubt, study these examples for additional insight on how BFS proceeds and output expected for the program when logging is enabled.
LOG: BFS STEP 4 LOG: processing neighbors of (2,5) LOG: Skipping BLOCKED tile at (1,5) LOG: Skipping BLOCKED tile at (3,5) LOG: Skipping FOUND tile at (2,4) LOG: Found tile at (2,6) with len 2 path: EE LOG: maze state after BFS step #########: 0 # 21# #: 1 # ##012##: 2 # #1##E#: 3 # 2 #: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 1 3 1 4 4 2 2 6 LOG: BFS STEP 5 LOG: processing neighbors of (1,3) LOG: Skipping BLOCKED tile at (0,3) LOG: Skipping BLOCKED tile at (2,3) LOG: Found tile at (1,2) with len 3 path: NWW LOG: Skipping FOUND tile at (1,4) LOG: maze state after BFS step #########: 0 # 321# #: 1 # ##012##: 2 # #1##E#: 3 # 2 #: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 4 4 1 2 6 2 1 2 LOG: BFS STEP 6 LOG: processing neighbors of (4,4) LOG: Skipping FOUND tile at (3,4) LOG: Skipping BLOCKED tile at (5,4) LOG: Found tile at (4,3) with len 3 path: SSW LOG: Found tile at (4,5) with len 3 path: SSE LOG: maze state after BFS step #########: 0 # 321# #: 1 # ##012##: 2 # #1##E#: 3 # 323 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 2 6 1 1 2 2 4 3 3 4 5 LOG: BFS STEP 7 LOG: processing neighbors of (2,6) LOG: Found tile at (1,6) with len 3 path: EEN LOG: Skipping BLOCKED tile at (3,6) LOG: Skipping FOUND tile at (2,5) LOG: Skipping BLOCKED tile at (2,7) LOG: maze state after BFS step #########: 0 # 321#3 #: 1 # ##012##: 2 # #1##E#: 3 # 323 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 1 2 1 4 3 2 4 5 3 1 6 LOG: BFS STEP 8 LOG: processing neighbors of (1,2) LOG: Skipping BLOCKED tile at (0,2) LOG: Skipping BLOCKED tile at (2,2) LOG: Found tile at (1,1) with len 4 path: NWWW LOG: Skipping FOUND tile at (1,3) LOG: maze state after BFS step #########: 0 #4321#3 #: 1 # ##012##: 2 # #1##E#: 3 # 323 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 4 3 1 4 5 2 1 6 3 1 1 LOG: BFS STEP 9 LOG: processing neighbors of (4,3) LOG: Skipping BLOCKED tile at (3,3) LOG: Skipping BLOCKED tile at (5,3) LOG: Found tile at (4,2) with len 4 path: SSWW LOG: Skipping FOUND tile at (4,4) LOG: maze state after BFS step #########: 0 #4321#3 #: 1 # ##012##: 2 # #1##E#: 3 # 4323 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 4 5 1 1 6 2 1 1 3 4 2 LOG: BFS STEP 10 LOG: processing neighbors of (4,5) LOG: Skipping BLOCKED tile at (3,5) LOG: Skipping BLOCKED tile at (5,5) LOG: Skipping FOUND tile at (4,4) LOG: Found tile at (4,6) with len 4 path: SSEE LOG: maze state after BFS step #########: 0 #4321#3 #: 1 # ##012##: 2 # #1##E#: 3 # 43234 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 1 6 1 1 1 2 4 2 3 4 6 LOG: BFS STEP 11 LOG: processing neighbors of (1,6) LOG: Skipping BLOCKED tile at (0,6) LOG: Skipping FOUND tile at (2,6) LOG: Skipping BLOCKED tile at (1,5) LOG: Found tile at (1,7) with len 4 path: EENE LOG: maze state after BFS step #########: 0 #4321#34#: 1 # ##012##: 2 # #1##E#: 3 # 43234 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 1 1 1 4 2 2 4 6 3 1 7 LOG: BFS STEP 12 LOG: processing neighbors of (1,1) LOG: Skipping BLOCKED tile at (0,1) LOG: Found tile at (2,1) with len 5 path: NWWWS LOG: Skipping BLOCKED tile at (1,0) LOG: Skipping FOUND tile at (1,2) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 # #1##E#: 3 # 43234 #: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 4 2 1 4 6 2 1 7 3 2 1 LOG: BFS STEP 13 LOG: processing neighbors of (4,2) LOG: Found tile at (3,2) with len 5 path: SSWWN LOG: Skipping BLOCKED tile at (5,2) LOG: Found tile at (4,1) with len 5 path: SSWWW LOG: Skipping FOUND tile at (4,3) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 # 5#1##E#: 3 #543234 #: 4 #########: 5 012345678 0 queue count: 5 NN ROW COL 0 4 6 1 1 7 2 2 1 3 3 2 4 4 1 LOG: BFS STEP 14 LOG: processing neighbors of (4,6) LOG: Skipping BLOCKED tile at (3,6) LOG: Skipping BLOCKED tile at (5,6) LOG: Skipping FOUND tile at (4,5) LOG: Found tile at (4,7) with len 5 path: SSEEE LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 # 5#1##E#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 5 NN ROW COL 0 1 7 1 2 1 2 3 2 3 4 1 4 4 7 LOG: BFS STEP 15 LOG: processing neighbors of (1,7) LOG: Skipping BLOCKED tile at (0,7) LOG: Skipping BLOCKED tile at (2,7) LOG: Skipping FOUND tile at (1,6) LOG: Skipping BLOCKED tile at (1,8) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 # 5#1##E#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 2 1 1 3 2 2 4 1 3 4 7 LOG: BFS STEP 16 LOG: processing neighbors of (2,1) LOG: Skipping FOUND tile at (1,1) LOG: Found tile at (3,1) with len 6 path: NWWWSS LOG: Skipping BLOCKED tile at (2,0) LOG: Skipping BLOCKED tile at (2,2) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##E#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 4 NN ROW COL 0 3 2 1 4 1 2 4 7 3 3 1 LOG: BFS STEP 17 LOG: processing neighbors of (3,2) LOG: Skipping BLOCKED tile at (2,2) LOG: Skipping FOUND tile at (4,2) LOG: Skipping FOUND tile at (3,1) LOG: Skipping BLOCKED tile at (3,3) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##E#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 3 NN ROW COL 0 4 1 1 4 7 2 3 1 LOG: BFS STEP 18 LOG: processing neighbors of (4,1) LOG: Skipping FOUND tile at (3,1) LOG: Skipping BLOCKED tile at (5,1) LOG: Skipping BLOCKED tile at (4,0) LOG: Skipping FOUND tile at (4,2) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##E#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 2 NN ROW COL 0 4 7 1 3 1 LOG: BFS STEP 19 LOG: processing neighbors of (4,7) LOG: Found tile at (3,7) with len 6 path: SSEEEN LOG: Skipping BLOCKED tile at (5,7) LOG: Skipping FOUND tile at (4,6) LOG: Skipping BLOCKED tile at (4,8) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##6#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 2 NN ROW COL 0 3 1 1 3 7 LOG: BFS STEP 20 LOG: processing neighbors of (3,1) LOG: Skipping FOUND tile at (2,1) LOG: Skipping FOUND tile at (4,1) LOG: Skipping BLOCKED tile at (3,0) LOG: Skipping FOUND tile at (3,2) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##6#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 1 NN ROW COL 0 3 7 LOG: BFS STEP 21 LOG: processing neighbors of (3,7) LOG: Skipping BLOCKED tile at (2,7) LOG: Skipping FOUND tile at (4,7) LOG: Skipping BLOCKED tile at (3,6) LOG: Skipping BLOCKED tile at (3,8) LOG: maze state after BFS step #########: 0 #4321#34#: 1 #5##012##: 2 #65#1##6#: 3 #5432345#: 4 #########: 5 012345678 0 queue count: 0 NN ROW COL LOG: solution START at (2,4) LOG: solution path[0] is SOUTH, set (3,4) to ONPATH LOG: solution path[1] is SOUTH, set (4,4) to ONPATH LOG: solution path[2] is EAST, set (4,5) to ONPATH LOG: solution path[3] is EAST, set (4,6) to ONPATH LOG: solution path[4] is EAST, set (4,7) to ONPATH LOG: solution path[5] is NORTH, set (3,7) to ONPATH LOG: solution END at (3,7) SOLUTION: maze: 6 rows 9 cols (2,4) start (3,7) end maze tiles: ######### # # # # ##S ## # #.##E# # ....# ######### path length: 6 0: SOUTH 1: SOUTH 2: EAST 3: EAST 4: EAST 5: NORTH
3.3 BFS Reference Material
For those who want more details on BFS associated with our case and the more general case, the following references are concise and useful.
- Wikipedia: Breadth First Search : Covers basics of the algorithm including simplified pseudocode, some good visuals, and a variety of properties of the algorithm like its time and space complexity.
- Breadth First Search grid shortest path | Graph Theory by WilliamFiset : Good Overview video which focuses on a Python implementation to search a 2D maze / grid as is our situation. Connections to Graph Theory and general BFS on graphs are made though without code.
4 Outline of Code
The file mazesolve_funcs.c
will contain most of the support
functions for the Maze Solving program. An outline of these functions
are presented below. Note that each function has the Problem # to
which it belongs. The final main()
function should be written in the
mazesolve_main.c
file.
You may add additional functions to those indicated ("helper" or "utility" functions) but these will not graded and the design of the project is oriented towards not needing additional functions.
#include "mazesolve.h" //////////////////////////////////////////////////////////////////////////////// // PROVIDED DATA // // The data below should not be altered as it should be used as-is in functions /////////////////////////////////////////////////////////////////////////////// // Global variable controlling how much info should be printed; it is // assigned values like LOG_ALL (defined in the header as 10) to // trigger additional output to be printed during certain // functions. This output is useful to monitor and audit how program // execution proceeds. int LOG_LEVEL = 0; // Pre-specified order in which neighbor tiles shoudl be checked for // compatibility with tests. direction_t dir_delta[5] = {NONE, NORTH, SOUTH, WEST, EAST}; int row_delta[5] = {+0, -1, +1, +0, +0}; int col_delta[5] = {+0, +0, +0, -1, +1}; #define DELTA_START 1 #define DELTA_COUNT 5 // strings to print for compact directions char *direction_compact_strs[5] = { "?", // NONE "N", // NORTH "S", // SOUTH "W", // WEST "E", // EAST }; // strings to print for verbose directions char *direction_verbose_strs[5] = { "NONE", // NONE "NORTH", // NORTH "SOUTH", // SOUTH "WEST", // WEST "EAST", // EAST }; #define TILETYPE_COUNT 6 // strings to print for each tile type char tiletype_chars[TILETYPE_COUNT] = { '?', // NOTSET = 0, '#', // WALL, ' ', // OPEN, '.', // ONPATH, 'S', // START, 'E', // END, }; // characters to print in the visual rendering of the BFS in the maze // when the distance is evently divisible by 10: a is 10, b is 20, c // is 30, etc. char digit10_chars[] = "0abcdefghijklmnopqrstuvwxyzABCDE%GHIJKLMNOPQR$TUVWXYZ"; //////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR PROBLEM 1: Row/Col Queue //////////////////////////////////////////////////////////////////////////////// rcqueue_t *rcqueue_allocate(); // PROBLEM 1: Create a new queue of row/col coordinates that is // empty. Heap-allocate the queue and return a pointer to // it. Initialize all fields of the queue struct to indicate its // emptiness. // // CONSTRAINTS: When allocating heap memory, assume calls to malloc() // succeed and avoid checks to determine if memory allocation failed. void rcqueue_add_rear(rcqueue_t *queue, int row, int col); // PROBLEM 1: Add the given row/col coordinates at the end of the // queue. Allocates a new node for the coordinates and links this in // at the end of the queue. // // NOTES: The first addition to an empty queue usually requires // special handling that is different from than subsequent // additions. Make sure to check for this case. If you see // Segmentation Faults when running tests, examine the line numbers // closely, check what the test was evaluating, and trace the // execution mentally or via debug printing to find the problem. void rcqueue_free(rcqueue_t *queue); // PROBLEM 1: De-allocate the memory associated with `queue`. If it is // non-empty, remove all elements from it de-allocating their nodes // before free()'ing the queue struct itself. int rcqueue_get_front(rcqueue_t *queue, int *rowp, int *colp); // PROBLEM 1: Retrieve the front element of the queue but do not // otherwise change the queue. If the queue is empty, immediately // returns 0. Otherwise, the pointers rowp and colp are dereferenced // and set to be the row/col in the front node in the queue and 1 is // returned to indicate that the rowp/colp have been set. int rcqueue_remove_front(rcqueue_t *queue); // PROBLEM 1: Removes the front node of the queue. If the queue is // empty, immediately returns 0 to indicate the queue is // unchanged. Otherwise, advances the front pointer of the queue to // the next node in the queue and the de-allocates the node which was // previously at the front. Adjusts the count of nodes in the // queue. Returns 1 to indicate the queue has changed. // // CONSTRAINT: This should be a constant time O(1) operation. // CONSTRAINT: When the queue is empty, BOTH front/rear should be NULL. void rcqueue_print(rcqueue_t *queue); // PROBLEM 1: Prints a table of the current queue contents to standard // out using printf(). The output should look like the following. // // queue count: 3 // NN ROW COL // 0 4 5 // 1 1 8 // 2 5 5 // // The first line is the plain text "queue count: " followed by the // count of nodes in the queue. The next line is a header with // Node Number (NN), Row, and Col for columns with remaining lines // showing data for each node. Each line of this output is printed // with numbers right-aligned with a field width of 2 characters for // node numbers and 3 characters for row/col coordinates. // // If `queue` is NULL, print the string "null queue\n" instead. // // NOTES: Research the capabilities of printf() to print data in a // specific field width. A usage like printf("%3d",number) will be // useful in a situation like this to line up table entries. //////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR PROBLEM 2: Tile and Maze Utility Functions //////////////////////////////////////////////////////////////////////////////// void tile_print_path(tile_t *tile, int format); // PROBLEM 2: Print the path field of `tile` in one of two formats. If // the path field is NULL, print "No path found\n". Otherwise, if the // `format` parameter is PATH_FORMAT_COMPACT, iterate over the path // array and print each element as a string that comes from the global // direction_compact_strs[] array without printing any newlines. If // the `format` parameter is PATH_FORMAT_VERBOSE, print the length of // the path on its own line then each element of the path with its // index on its on line using strings from teh // direction_verbose_strs[] global array. If the format is neither of // COMPACT or VERBOSE, print an error message (not tested). // // CONSTRAINT: This function must use the correspondence of // enumeration values like NORTH to integers to access elements from // the global arrays direction_compact_strs[] and // direction_verbose_strs[]. Code that utilizes long-winded // conditional cases here will lose large quantities of style points. // // EXAMPLE: // tile_t src = {.path_len = 4, .path = {NORTH, EAST, EAST, SOUTH} }; // tile_print_path(&tile, PATH_FORMAT_COMPACT); // printf("\n"); // NEES // tile_print_path(&tile, PATH_FORMAT_VERBOSE); // path length: 4 // 0: NORTH // 1: EAST // 2: EAST // 3: SOUTH // // NOTES: This function will be short, less than 30 lines. It should // not have conditional structures that decide between NORTH, SOUTH, // etc. Rather, use an access pattern like the following to "look up" // the string that is needed in a given case. // // direction_t d = path[i]; // char *dirstr = direction_verbose_strs[d]; // printf("%s\n",dirstr); // // Or more compactly // // printf("%s\n", direction_verbose_strs[path[i]]); // // Handling a multipart conditional using an array of data like this // is an elegant technique that proficient codes seek to use as often // as possible. void tile_extend_path(tile_t *src, tile_t *dst, direction_t dir); // PROBLEM 2: Fills in the path/path_len fields of `dst` based on // arriving at it from the `src` tile via `dir`. Heap-allocates a // direction_t array for the path in dst that is one longer than the // path in src. Copies all elements from the `src` path to `dst`. Adds // `dir` as the final element of the path in `dst`. This function is // used when a new tile is found during BFS to store the path used to // locate it from the starting point. // // EXAMPLE: // tile_t src = {.path_len = 4, .path = {NORTH, EAST, EAST, SOUTH} }; // tile_t dst = {.path_len = -1, .path = NULL }; // tile_extend_path(&src, &dst, EAST); // dst is now {.path_len = 5, .path = {NORTH, EAST, EAST, SOUTH, EAST} }; // // NOTES: This function will need to access fields of the // tiles. Review syntax to do so. maze_t *maze_allocate(int rows, int cols); // PROBLEM 2: Allocate on the heap a maze with the given rows/cols. // Allocates space for the maze struct itself and an array of row // pointers for its tiles. Then uses a nested set of loops to allocate // each row of tiles for the maze and initialize the fields of each // tile to be NOTSET, NOTFOUND, NULL, and -1 appropriately. Sets // start/end row/col fields to be -1 and the queue to be NULL // initially. Returns the resulting maze. // // CONSTRAINT: Assumes malloc() succeeds and does not include checks // for its failure. Does not bother with checking rows/cols for // inappropriate values such as 0 or negatives. // // NOTES: Consult recent lab work for examples of how to allocate a 2D // array using repeated malloc() calls and adapt that approach // hear. Ensure that the allocation is being done via rows of tile_t // structs. Common errors are to neglect initializing all fields of // the maze and all fields of every tile. Valgrind errors that data // is uninitialized are usually resolved by adding code to explicitly // initialize everything. void maze_free(maze_t *maze); // PROBLEM 2: De-allocates the memory associated with a maze and its // tiles. Uses a doubly nested loop to iterate over all tiles and // de-allocate any non-NULL paths that are part of the tiles. // Iterates to free each row of the tiles row and frees then frees the // array of row tile row pointers. If the queue is non-null, frees it // and finally frees the maze struct itself. int maze_tile_blocked(maze_t *maze, int row, int col); // PROBLEM 2: Return 1 if the indicated coordinate is blocked and // could not be traversed as part of a path solving a maze. A // coordinate is blocked if it is out of bounds for the maze (row/col // below zero or above maze row/col boundaries) or the tile at that // coordinate has type WALL. If the coordinate is not blocked, returns // 0. This function will be used later during the // Breadth-First-Search to determine if a coordinate should be ignored // due to being blocked. // // NOTES: This function will require accessing fields of the maze so // review syntax on struct field acces and 2D indexing into the maze's // tiles table. The tiletype_t enumeration in mazesolve.h establishes // global symbols for tile types like OPEN, ONPATH, START, and so // forth. Use one of these in this function. void maze_print_tiles(maze_t *maze); // PROBLEM 2: Prints `maze` showing the solution path from Start to // End tiles. First prints maze information including the size in rows // and columns and the coordinates of Start/End tiles. The prints out // the maze tiles with each tile printed based on its type. The character // to print for each tile is based on its type and corresponds to // elements in the global tiletyp_chars[] array: if the type is WALL, // prints the character at tiletype_char[WALL]. This function is used // to print a maze loaded from a file and print the solution path // after doing a BFS. // // EXAMPLE 1: Output for maze which does not yet have a solution so no // tiles have state ONPATH // // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // # ### ###### # # // # ### ##E # # // # ### #### ## # // # # // ################ // // EXAMPLE 2: The same maze above where some tiles have been marked // with ONPATH as a solution has been calcualted via BFS. The ONPATH // tiles are printed with . characters specified in the // tiletype_chars[] global variable. // // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // #.### ###### # # // #.### ##E..# # // #.### ####.## # // #.......... # // ################ void maze_print_state(maze_t *maze); // PROBLEM 2: Prints the `maze` in a format that shows its state and // progress during the BFS search from Start to End positions. The // 2D grid of tiles is printed with tile with state FOUND printing a // single character representaion of its distance from the Start tile // and all other tiles with state NOTFOUND printing a single character // that is based on their type (e.g. spaces for OPEN tiles, # for WALL // tiles, etc). The `maze` queue is also printed using the previously // written rcqueue_print() function for output. // // FOUND tiles will print their distance as a single character per one // of two cases so that compact be reasonably complete information // about its distance from the Start tile is shown. // // - If the tile's path_len field is NOT divsible by 10, then print // the last digit of the path_len (e.g. divide path_len by 10 and // take remainder // // - If the tiles' path_len IS divisible by 10, print the single // character at index (path_len)/10 in the global string // digit10_chars[]. These will show 'a' for 10, 'b' for 20, 'c' for // 30, and so on. // // To the right of each maze row print a ": <row#>". Below the maze, // print two lines: the first a sequence of "01234..890123..." up to // the number of columns and below that a "tens" digits (0, 1, 2, etc // printed every 10 characters). The combin combination of row and // column axis label numbers makes it easier to find a tile at a // specific coordinate. // // This function is used during BFS to show progress of the algorithm // in a step-by-step manner when logging of BFS steps is enabled. // // CONSTRAINT 1: You must provide comments in this function that guide // a reader on what different blocks and in some cases specific lines // do. Comments like "print the bottom axis labels" or "tile not // found, print its normal character" are informative. Comments like // "print" or "assign tile" are not informative. // // CONSTRAINT 2: This function must avoid "many-case" conditionals // such as if/else an switches based on the tile type or path_len. Use // the provided global arrays digit10_chars[] and tiletype_chars[] to // avoid such many-case conditionals. // // EXAMPLE: A 7x16 maze printed after a series of BFS steps. // // ################: 0 // #0123456789a123#: 1 // #1###5######2#4#: 2 // #2###6##E #34 #: 3 // #3###7####4## #: 4 // #456789a1234 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 4 // NN ROW COL // 0 4 10 // 1 5 11 // 2 3 13 // 3 2 14 // // EXAMPLE NOTES: The End tile E at (3,8) has not been found yet so is // drawn with an E while the Start tile at (1,1) is FOUND so is drawn // with a 0 (path_len 0 from the Start). Some tiles like the WALL // tiles marked with # are never FOUND as they are blocked and some // OPEN tiles have not been FOUND yet so are draw with a space. Some // FOUND tiles have 'a' in them indicating their path is 10 long from // the Start tile. The 'a' is taken from the digits10_chars[] array. //////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR PROBLEM 3: Breadth First Search of the Maze //////////////////////////////////////////////////////////////////////////////// void maze_bfs_init(maze_t *maze); // PROBLEM 3: Initializes the maze for a BFS search. Adjusts the start // tile: allocates it a length 0 path, sets its path_len to 0, and // sets its state to FOUND. Allocates an empty rcqueue for the queue // in the maze using an appropriate function and then adds the Start // tile to it. // // LOGGING: If LOG_LEVEL >= LOG_BFS_STATES, after initialization is // complete. prints "BFS initialization compelte" and calls // maze_print_state(). // // NOTES: This function is called within maze_bfs_iterate() to set up // the queue and Start tile before the search proceeds. It should not // be called outside of that context except during some testing of its // functionality and that of the BFS step function. // // EXAMPLE: The queue is initially empty (prints as null). After // calling bfs_init(), the Start tile is set to FOUND so // prints as its path_len of 0 and appears in the now // allocated queue. // // print_maze_state(maze); // ################: 0 // #S #: 1 // # ### ###### # #: 2 // # ### ##E # #: 3 // # ### #### ## #: 4 // # #: 5 // ################: 6 // 0123456789012345 // 0 1 // null queue // LOG_LEVEL = LOG_ALL; // maze_bfs_init(maze); // print_maze_state(maze); // LOG: BFS initialization complete // ################: 0 // #0 #: 1 // # ### ###### # #: 2 // # ### ##E # #: 3 // # ### #### ## #: 4 // # #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 1 // NN ROW COL // 0 1 1 int maze_bfs_process_neighbor(maze_t *maze, int cur_row, int cur_col, direction_t dir); // PROBLEM 3: Process the neighbor in direction `dir` from coordinates // `cur_row/cur_col`. Calculates the adjacent tiles row/col // coordinates using the row_delta[]/col_delta[] global array and // `dir`. If the neighbor tile is blocked according to the // maze_tile_blocked() function, makes no changes and returns 0 as the // position cannot be reached. If the neighbor tile has state FOUND, // makes no changes and returns 0 as the tile has already been // processed in the BFS. Otherwise changes the neighbor tile to be a // Found tile: extends the current tile's path to the neighbor tile in // the given direction, changes the neighbor tile's state to FOUND and // adds the neighbor tile to the maze search queue. Ends by returning // 1 as the tile/maze data has changed. This function is used in BFS // to propogate paths to all non-blocked neighbor tiles and extend the // search frontier. // // LOGGING: // 1. If LOG_LEVEL >= LOG_BFS_PATHS and the neighor tile's state // changes from NOTFOUND to FOUND, print a message like: // LOG: Found tile at (4,10) with len 14 path: SSSSEEEEEEEEEN // with the coordinates, path_len, and COMPACT path for the newly // found tile. // 2. If LOG_LEVEL >= LOG_SKIPPED_TILES and the neighbor tile is // skipped as it is blocked or already found, print a message like // one of // LOG: Skipping BLOCKED tile at (6,13) // LOG: Skipping FOUND tile at (5,12) // // EXAMPLE: // maze_print_state(maze); // ################: 0 // #0123 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #4 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 4 // 1 5 1 // LOG_LEVEL = LOG_ALL; // above both LOG_BFS_PATHS and LOG_SKIPPED_TILES // maze_bfs_process_neighbor(maze, 1, 4, NORTH); // LOG: Skipping BLOCKED tile at (0,4) // maze_bfs_process_neighbor(maze, 1, 4, SOUTH); // LOG: Skipping BLOCKED tile at (2,4) // maze_bfs_process_neighbor(maze, 1, 4, WEST); // LOG: Skipping FOUND tile at (1,3) // maze_bfs_process_neighbor(maze, 1, 4, EAST); // LOG: Found tile at (1,5) with len 4 path: EEEE // maze_print_state(maze); // ################: 0 // #01234 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #4 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 4 // 1 5 1 // 2 1 5 int maze_bfs_step(maze_t *maze); // PROBLEM 3: Processes the tile in BFS which is at the front of the // maze search queue. For the front tile, iterates over the directions // in the global dir_delta[] array from index DELTA_START to less than // DELTA_COUNT which will be NORTH, SOUTH, WEST, EAST. Processes the // neighbors in each of these directions with an appropriate // function. Removes the front element of the search queue and // returns 1. Note: if this function is called when the maze queue is // empty, return 0 and print an error message though this case will // not be tested and should not arise if other parts of the program // are correct. // // LOGGING: // If LOG_LEVEL >= LOG_BFS_STEPS, print a message like // LOG: processing neighbors of (5,1) // at the start of the function. // // If LOG_LEVEL >= LOG_BFS_STATES, prints a message and uses // maze_print_state() at the end of the function to show the maze // after processing finishes as in: // LOG: maze state after BFS step // ################: 0 // #01234 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #45 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 5 // 1 5 2 // // EXAMPLE: // maze_print_state(maze); // ################: 0 // #0123456789a1 #: 1 // #1###5###### # #: 2 // #2###6##E # #: 3 // #3###7#### ## #: 4 // #456789a12 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 12 // 1 5 9 // LOG_LEVEL = LOG_ALL; // maze_bfs_step(maze); // LOG: processing neighbors of (1,12) // LOG: maze state after BFS step // ################: 0 // #0123456789a12 #: 1 // #1###5######2# #: 2 // #2###6##E # #: 3 // #3###7#### ## #: 4 // #456789a12 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 3 // NN ROW COL // 0 5 9 // 1 2 12 // 2 1 13 void maze_bfs_iterate(maze_t *maze); // PROBLEM 3: Initializes a BFS on the maze and iterates BFS steps // until the queue for the maze is empty and the BFS is complete. Each // iteration of the algorithm loop will process all neighbors of a // FOUND tile. This will calculate paths from the Start tile to all // other tiles including the End tile. // // LOGGING: If LOG_LEVEL >= LOG_BFS_STEPS prints the step number for // each iteration of the loop as // LOG: BFS STEP 45 // where the number starts at 1 and increases each loop iteration. // // See EXAMPLES for main() to get an idea of output for iteration. // // NOTES: This function will call several of the preceding functions // to initialize and proceed with the BFS. int maze_set_solution(maze_t *maze); // PROBLEM 3: Uses the path stored in the End tile to visit each tile // on the solution path from Star to End and make them as ONPATH to // show the solution path. If the End tile has a NULL path, returns 0 // and makes no changes to the maze. Otherwise, visits each direction // in the End tile's path and, beginning at the Start tile, "moves" in // the direction indicated and chcnges the nextf tile's state to // ONPATH. Returns 1 on changing the state of tiles to show the // solution path. This function is used to set up printing the // solution path later. // // CONSTRAINT: Makes use of the row_delta[] / col_delta[] global // arrays when "moving" rather than using multi-way conditional. For // example, if the current coordinates are (3,5) and the path[i] // element is WEST, uses row_delta[WEST] which is -1 and // col_delta[WEST] which is 0 to update the coordinates to (3,4) which // is one tile to the WEST. Solutions that use a multi-way conditional // with cases for NORTH, SOUTH, etc. will be penalized. // // NOTE: This function may temporarily change the state of the END // tile to ONPATH as indicated in the log messages below. Before // finishing, the state of the END tile is changed from ONPATH to END. // // LOGGING: If the LOG_LEVEL >= LOG_SET_SOLUTION, prints out // // 1. Once at the beginning of the solution construction // LOG: solution START at (1,1) // with the coordinates substituted for the actual START coordinates. // // 2. For every element of the End tile path[] array // LOG: solution path[5] is EAST, set (5,3) to ONPATH // with items like the index, direction, and coordinates replaced with // appropriate data via printf(). Use the direction_verbose_strs[] // to print strings of the directions. // // 3. Once at the end of solution construction: // LOG: solution END at (3,8) // with the coordinates substituted for the actual END coordinates. // // EXAMPLE: // maze_print_tiles(maze); // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // # ### ###### # # // # ### ##E # # // # ### #### ## # // # # // ################ // // LOG_LEVEL = LOG_SET_SOLUTION; // maze_set_solution(maze); // LOG: solution START at (1,1) // LOG: solution path[0] is SOUTH, set (2,1) to ONPATH // LOG: solution path[1] is SOUTH, set (3,1) to ONPATH // LOG: solution path[2] is SOUTH, set (4,1) to ONPATH // LOG: solution path[3] is SOUTH, set (5,1) to ONPATH // LOG: solution path[4] is EAST, set (5,2) to ONPATH // LOG: solution path[5] is EAST, set (5,3) to ONPATH // LOG: solution path[6] is EAST, set (5,4) to ONPATH // LOG: solution path[7] is EAST, set (5,5) to ONPATH // LOG: solution path[8] is EAST, set (5,6) to ONPATH // LOG: solution path[9] is EAST, set (5,7) to ONPATH // LOG: solution path[10] is EAST, set (5,8) to ONPATH // LOG: solution path[11] is EAST, set (5,9) to ONPATH // LOG: solution path[12] is EAST, set (5,10) to ONPATH // LOG: solution path[13] is NORTH, set (4,10) to ONPATH // LOG: solution path[14] is NORTH, set (3,10) to ONPATH // LOG: solution path[15] is WEST, set (3,9) to ONPATH // LOG: solution path[16] is WEST, set (3,8) to ONPATH // LOG: solution END at (3,8) // // maze_print_tiles(maze); // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // #.### ###### # # // #.### ##E..# # // #.### ####.## # // #.......... # // ################ //////////////////////////////////////////////////////////////////////////////// // FUNCTIONS FOR PROBLEM 4: Maze Memory Allocation and File Input //////////////////////////////////////////////////////////////////////////////// maze_t *maze_from_file(char *fname); // PROBLEM 4: Read a maze from a text file. The text file format // starts with the size of the maze and then contains its tiles. An // example is: // // rows: 7 cols: 19 // tiles: // ################### // # # # # // # ### ## ## # # // # ## # S # # # # // ## # ##### # # # // #E # # # // ################### // // If `fname` cannot be opened as a file, prints the error message // ERROR: could not open file <FILENAME> // and returns NULL. Otherwise proceeds to read row/col numbers, // allocate a maze of appropriate size, and read characters into // it. Each tile read has its state changed per the character it is // shown as. The global array tiletype_chars[] should be searched to // find the character read for a tile and the index at which it is // found set to be the tile state; e.g. the character 'S' was read // which appears at index 4 of tiletype_chars[] so the tile.type = 4 // which is START in the tiletype enumeration. // // CONSTRAINT: You must use fscanf() for this function. // // CONSTRAINT: You MUST comment your code to describe the intent of // blocks of code. Full credit comments will balance some details with // broad descriptions of blocks of code. Missing comments or pedantic // comments which describe every code line with unneeded detail will // not receive full credit. Aim to make it easy for a human reader to // scan your function to find a specific point of interest such as // where a tile type is determined or when a row is finished // processing. Further guidance on good/bad comments is in the C // Coding Style Guide. // // LOGGING: If LOG_LEVEL >= LOG_FILE_LOAD prints messages to help // track parsing progress. There are many log messages required as // they will be useful for debugging parsing problems that always // arise when reading such data. // // LOG: expecting 6 rows and 9 columns // Printed after reading the first line indicating rows/cols in the // maze. // // LOG: beginning to read tiles // Printed after reading the "tiles\n" line when the main loop is // about to start reading character/tiles. // // LOG: (2,1) has character ' ' type 2 // Printed with the coordinates of each character/tile that is // read. The coordinates, character read, and an integer indicating // the type decided upon for the tile is shown. // // LOG: (2,4) has character 'S' type 4 // LOG: setting START at (2,4) // Printed when the Start tile, marked with an S, is found // // LOG: (3,7) has character 'E' type 5 // LOG: setting END at (3,7) // Printed when the End tile, marked with an E, is found // // LOG: finished reading row 4 of tiles // Printed after reading each row of the maze file to help track // progress. // // NOTES: The fscanf() function is essential for this function. A call // like fscanf(fin,"age: %d name: %s\n",&num, str) will read the literal // word "age:" then a number, then the literal word "name:" and a // string. Use this facility to parse the simple format. It is fine // to read a literal string only as in fscanf(fin,"skip this stuff\n") // which will bypass that exact line in a file. Use the format // specifier %c to read single characters as you process the tiles as // this is the easiest mechanism to work on the character parsing of // the maze tiles. Keep in mind that all lines of the maze will end // with the character '\n' which must be read past in order to get to // the next row. int main(int argc, char *argv[]); // PROBLEM 4: main() in mazesolve_main.c // // The program must support two command line forms. // 1. ./mazesolve_main <mazefile> // 2. ./mazesolve_main -log <N> <mazefile> // Both forms have a maze data file as the last command line // argument. The 2nd form sets the global variable LOG_LEVEL to the // value N which enables additional output. // // NOTES // - It is a good idea to check that the number of command line arguments // is either 2 (Form 1) and 4 (Form 2) and if not, bail out from the // program. This won't be tested but it simplifies the rest of the // program. // - All command line arguments come into C programs as strings // (char*). That means the number after the -log option will also // be a string of characters and needs to be converted to an int to // be used in the program. The atoi() function is useful for this: // search for documentation on it and use it for the conversion. // - Make sure to check that loading a maze from a file succeeds. If // not, print the following error message: // Could not load maze file. Exiting with error code 1 // This message is in addition to the error message that is printed // byt he maze_from_file() function. Return 1 from main() to indicate // that the program was not successful. // - If a maze is successfully loaded, perform a BFS on it using // appropriate functions. Don't forget to free its memory before // ending the program.
4.1 Getting Started
Create the file mazesolve_funcs.c
. Copy the code outline above into
the C file to serve as a starting point for the project. Filling in
dummy bodies (e.g. return 0
or return NULL
etc.) will allow the
Automated Tests to run after which one can begin filling in
definitions for the functions according to the provided specification.
For the most part, try to solve functions in the order that they appear in the code outline as later functions will use earlier functions. If you do get stuck on a function, don't be afraid to move on momentarily to keep your momentum up.
4.2 The Dev Cycle
To complete the project
- Read documentation on a required function
- Write part of the implementation
- Run test cases associated with function
- Analyze the results
- Consult docs on the function for clarification
- Add
printf()
statements to show debug information - Repeat the above until all tests for the function pass
- Consult the MANUAL INSPECTION criteria to ensure your style and functionality meet their requirements
- Repeat for the next function
When stuck on a bug for an Extended period (1+ hour)
- Search for related examples from Labs / HWs / Lectures
- Ask for help on Piazza or visit office hours
- Take a break and come back after recovering some mental energy
- Stay Determined!
5 Problem 1: Row/Col Queue
This problem is a "warm-up" that will implement a standard Queue data structure using linked nodes. The data tracked in the queue are row/column coordinates that should be processed later during a BFS. Students are expected to have seen and implemented queues like this before in other programming environments and are expected to have gained familiarity with linked nodes from previous labs/HWs in this course so the problem should not be difficult. It is a chance to practice the basic "Dev Cycle" on familiar ground before moving to the more complex tasks later.
5.1 Queue Data Types
The central data types associated with the queue appear in
mazesolve.h
near the top.
// rcqueue_t data //////////////////////////////////////////////////////////////////////////////// typedef struct rcnode { // node type for row/col queues int row, col; // row/col coordinates for the node struct rcnode *next; // pointer to next node } rcnode_t; typedef struct { // queue type for row/col coordinates rcnode_t *front, *rear; // pointers to ends of queue int count; // number of nodes in queue } rcqueue_t; ////////////////////////////////////////////////////////////////////////////////
These data structures will be manipulated by the required functions.
5.2 Required Queue Functions
These functions all start with rcqueue_
and preform the basic
operations expected for queues: adding elements to the rear of the
queue, removing elements from the front, and accessing the front
element. Some queue implementations merge the access/remove front
element however in this situation the operations are distinct:
rcqueue_get_front()
sets a pair of row/col integers based on the row/col at the front of the queue; if the queue is empty, the function does nothing.rcqueue_remove_front()
removes the front queue element for non-empty queues and does nothing to empty queues.
The queue completed in this problem will be used later to facilitate the Breadth First Search of the maze (Problem 3).
// FUNCTIONS FOR PROBLEM 1: Row/Col Queue //////////////////////////////////////////////////////////////////////////////// rcqueue_t *rcqueue_allocate(); // PROBLEM 1: Create a new queue of row/col coordinates that is // empty. Heap-allocate the queue and return a pointer to // it. Initialize all fields of the queue struct to indicate its // emptiness. // // CONSTRAINTS: When allocating heap memory, assume calls to malloc() // succeed and avoid checks to determine if memory allocation failed. void rcqueue_add_rear(rcqueue_t *queue, int row, int col); // PROBLEM 1: Add the given row/col coordinates at the end of the // queue. Allocates a new node for the coordinates and links this in // at the end of the queue. // // NOTES: The first addition to an empty queue usually requires // special handling that is different from than subsequent // additions. Make sure to check for this case. If you see // Segmentation Faults when running tests, examine the line numbers // closely, check what the test was evaluating, and trace the // execution mentally or via debug printing to find the problem. void rcqueue_free(rcqueue_t *queue); // PROBLEM 1: De-allocate the memory associated with `queue`. If it is // non-empty, remove all elements from it de-allocating their nodes // before free()'ing the queue struct itself. int rcqueue_get_front(rcqueue_t *queue, int *rowp, int *colp); // PROBLEM 1: Retrieve the front element of the queue but do not // otherwise change the queue. If the queue is empty, immediately // returns 0. Otherwise, the pointers rowp and colp are dereferenced // and set to be the row/col in the front node in the queue and 1 is // returned to indicate that the rowp/colp have been set. int rcqueue_remove_front(rcqueue_t *queue); // PROBLEM 1: Removes the front node of the queue. If the queue is // empty, immediately returns 0 to indicate the queue is // unchanged. Otherwise, advances the front pointer of the queue to // the next node in the queue and the de-allocates the node which was // previously at the front. Adjusts the count of nodes in the // queue. Returns 1 to indicate the queue has changed. // // CONSTRAINT: This should be a constant time O(1) operation. // CONSTRAINT: When the queue is empty, BOTH front/rear should be NULL. void rcqueue_print(rcqueue_t *queue); // PROBLEM 1: Prints a table of the current queue contents to standard // out using printf(). The output should look like the following. // // queue count: 3 // NN ROW COL // 0 4 5 // 1 1 8 // 2 5 5 // // The first line is the plain text "queue count: " followed by the // count of nodes in the queue. The next line is a header with // Node Number (NN), Row, and Col for columns with remaining lines // showing data for each node. Each line of this output is printed // with numbers right-aligned with a field width of 2 characters for // node numbers and 3 characters for row/col coordinates. // // If `queue` is NULL, print the string "null queue\n" instead. // // NOTES: Research the capabilities of printf() to print data in a // specific field width. A usage like printf("%3d",number) will be // useful in a situation like this to line up table entries. ////////////////////////////////////////////////////////////////////////////////
5.3 Grading Criteria for Problem 1 grading 20
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob1 |
|
10 | Runs tests in test_mazesolve1.org / test_mazesolve_funcs.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
2 | rcqueue_allocate() / rcqueue_add_rear() |
Correct use of malloc() / sizeof() to allocate space for queue / nodes |
|
Use of "arrow" syntax when accessing fields of pointer to a struct | |
Correct initialization / update of fields when allocating | |
2 | rcqueue_get_front() / rcqueue_remove_front() |
Use of pointer dereference operation when setting pointers to "settable" arguments | |
Correct de-allocation using free() when unlinking nodes |
|
1 | rcqueue_print() |
Checks present of cases for NULL vs non-NULL queue | |
Use of printf() with width in format specifiers like %3d to format output |
|
Clean code that iterates through the queue printing each element |
6 Problem 2: Tile/Maze Functions
This problem introduces the Tile and Maze data types used in the project and implements some basic functionality for them.
6.1 Tile/Maze Data Types
The two central structs for representing program are
- tilet which store information about a single coordinate in the maze, whether it is a Wall, Open, Start etc. and whether it has been processed during the BFS
- mazet which maintains a 2D array of tiles along with global information such as the location of the start/end tiles and the queue of row/col coordinates to search.
These two structs are defined in the project header.
// tile and maze data //////////////////////////////////////////////////////////////////////////////// typedef struct { // tile data for positions in a 2D maze tiletype_t type; // One of NOTSET, OPEN, WALL, ONPATH, START, END searchstate_t state; // One of NOT_FOUND, QUEUED, DONE direction_t *path; // array of directions from start to this position int path_len; // length of path array } tile_t; typedef struct { // maze data tracking shape of maze and state of BFS search tile_t **tiles; // 2D array of tiles int rows, cols; // number of rows/cols in the 2D tile array int start_row, start_col; // starting position in the maze int end_row, end_col; // ending position in the maze rcqueue_t *queue; // queue of coordinates to search } maze_t; ////////////////////////////////////////////////////////////////////////////////
Note the tiles
field of maze_t
is a doubly indirected pointer: it
will be used to access tiles via 2 array indices like
maze->tiles[5][3]
for row 5 column 3.
Some of the fields in of tile_t
have their own types which are
discussed in the next section on Enumerations.
6.2 Enumerations in C
The C language provides Enumerations which are fixed set of symbolically named values usually encoded as integers. Enumerations are used when a programmer wants to give human-readable names to finite set of items and get efficient storage/operations on those items. Below are enumerations that appear the project header that are used to represent a limited set of value appropriate to 3 situations.
- Directions: the 2D grid allows for only 4 movement directions
which are labeled in the
direction_t
enumeration - Tile Types: tiles in the maze come in a limited set of types which such as Walls and Open positions.
- Tile Search State: tiles are "found" during the BFS which begins at the Start tile. At any time during BFS, some tiles are Found while others are Notfound.
// tile enumerations //////////////////////////////////////////////////////////////////////////////// typedef enum { // allowable directions for neighbors NONE = 0, // NONE should not be used NORTH, // Same as integer 1 SOUTH, // Same as integer 2 WEST, // Same as integer 3 EAST, // Same as integer 4 } direction_t; // EXAMPLE USE: // direction_t dirs[5] = {NORTH, WEST, WEST, SOUTH, SOUTH}; // char *str = direction_verbose_strs[WEST]; typedef enum { // types of supported tiles NOTSET = 0, // NOTSET should not be used WALL, // cannot be traversed, display as '#' OPEN, // can be traversed, display as a space ' ' ONPATH, // part of shortest path from Start to End, '.' START, // starting tile, display as 'S' END, // ending tile, display as 'E' } tiletype_t; // EXAMPLE USE: // tile_t tile; // tile.type = OPEN; // char display = tiletype_chars[WALL]; typedef enum { // type used during BFS to track found iles UNKNOWN = 0, // UNKNOWN should not be used NOTFOUND, // tile has not yet be found during BFS FOUND // tile has been found during BFS and has its path set } searchstate_t; // EXAMPLE USE: // tile_t tile; // tile.state = NOTFOUND; ////////////////////////////////////////////////////////////////////////////////
As the syntax indicates, enumerations can be set up in a similar
fashion to a struct definitions via the typedef
C construct.
Creating enumerations establishes a global symbol for each value in
the enumeration. For example, the global symbol NORTH
is now
available to assign to variables of type direction_t
as the examples
show.
Beneath each enumeration is an example of using the enumerations. C does not disguise the fact that enumerations are usually encoded as an integral type so have a number associated with them. This appears in the enumeration definitions where the initial element is dictated as having equivalent value 0. It is also exploited in several of the examples which show that the enumeration value can be used as an index into arrays, a useful trick which coders will employ in some parts of this problem.
Enumerations are useful as they allow the compiler to do a little bit
of type checking. Assigning an enumeration value to another type of
variable may cause the compiler to issue a warning or error. This is
often more useful than equivalent alternative mechanisms to setting up
global symbols for numbers such as can be done with the #define
mechanism also shown in the header.
6.3 Provided Data
There is some provided data at the top of outline of
mazesolve_funcs.c
which includes some provided data at the top which
can and should be used in the completion of the project. For example,
the first function in this problem will print "paths", arrays of
direction_t
elements. The make this function flexible, two arrays
are provided which specify different formats for how direction_t
elements can be printed.
// strings to print for compact directions char *direction_compact_strs[5] = { "?", // NONE "N", // NORTH "S", // SOUTH "W", // WEST "E", // EAST }; // strings to print for verbose directions char *direction_verbose_strs[5] = { "NONE", // NONE "NORTH", // NORTH "SOUTH", // SOUTH "WEST", // WEST "EAST", // EAST };
The encoding of the enumeration value NORTH
is integer 1. That
allows easy printing of a string corresponding to that value.
{ printf("NORTH integer: %d\n",NORTH); printf("NORTH compact string: %s\n", direction_compact_strs[NORTH]); printf("NORTH verbose string: %s\n", direction_verbose_strs[NORTH]); } // OUTPUT: // NORTH integer: 1 // NORTH compact string: N // NORTH verbose string: NORTH
6.4 Required Tile/Maze Functions
Tile functions are implemented first followed by maze functions.
- There are now tile allocation/de-allcoation functions. Rather, when a maze is created, a 2D array of tiles is allocated as part of it.
- Slightly more complex memory allocation is required in this
problem. Draw inspiration from lab work, especially for the 2D array
allocation in
maze_allocate()
- The final function that prints the state of a maze during BFS is somewhat involved and getting the output correct may take some time. Stay determined!
// FUNCTIONS FOR PROBLEM 2: Tile and Maze Utility Functions //////////////////////////////////////////////////////////////////////////////// void tile_print_path(tile_t *tile, int format); // PROBLEM 2: Print the path field of `tile` in one of two formats. If // the path field is NULL, print "No path found\n". Otherwise, if the // `format` parameter is PATH_FORMAT_COMPACT, iterate over the path // array and print each element as a string that comes from the global // direction_compact_strs[] array without printing any newlines. If // the `format` parameter is PATH_FORMAT_VERBOSE, print the length of // the path on its own line then each element of the path with its // index on its on line using strings from teh // direction_verbose_strs[] global array. If the format is neither of // COMPACT or VERBOSE, print an error message (not tested). // // CONSTRAINT: This function must use the correspondence of // enumeration values like NORTH to integers to access elements from // the global arrays direction_compact_strs[] and // direction_verbose_strs[]. Code that utilizes long-winded // conditional cases here will lose large quantities of style points. // // EXAMPLE: // tile_t src = {.path_len = 4, .path = {NORTH, EAST, EAST, SOUTH} }; // tile_print_path(&tile, PATH_FORMAT_COMPACT); // printf("\n"); // NEES // tile_print_path(&tile, PATH_FORMAT_VERBOSE); // path length: 4 // 0: NORTH // 1: EAST // 2: EAST // 3: SOUTH // // NOTES: This function will be short, less than 30 lines. It should // not have conditional structures that decide between NORTH, SOUTH, // etc. Rather, use an access pattern like the following to "look up" // the string that is needed in a given case. // // direction_t d = path[i]; // char *dirstr = direction_verbose_strs[d]; // printf("%s\n",dirstr); // // Or more compactly // // printf("%s\n", direction_verbose_strs[path[i]]); // // Handling a multipart conditional using an array of data like this // is an elegant technique that proficient codes seek to use as often // as possible. void tile_extend_path(tile_t *src, tile_t *dst, direction_t dir); // PROBLEM 2: Fills in the path/path_len fields of `dst` based on // arriving at it from the `src` tile via `dir`. Heap-allocates a // direction_t array for the path in dst that is one longer than the // path in src. Copies all elements from the `src` path to `dst`. Adds // `dir` as the final element of the path in `dst`. This function is // used when a new tile is found during BFS to store the path used to // locate it from the starting point. // // EXAMPLE: // tile_t src = {.path_len = 4, .path = {NORTH, EAST, EAST, SOUTH} }; // tile_t dst = {.path_len = -1, .path = NULL }; // tile_extend_path(&src, &dst, EAST); // dst is now {.path_len = 5, .path = {NORTH, EAST, EAST, SOUTH, EAST} }; // // NOTES: This function will need to access fields of the // tiles. Review syntax to do so. maze_t *maze_allocate(int rows, int cols); // PROBLEM 2: Allocate on the heap a maze with the given rows/cols. // Allocates space for the maze struct itself and an array of row // pointers for its tiles. Then uses a nested set of loops to allocate // each row of tiles for the maze and initialize the fields of each // tile to be NOTSET, NOTFOUND, NULL, and -1 appropriately. Sets // start/end row/col fields to be -1 and the queue to be NULL // initially. Returns the resulting maze. // // CONSTRAINT: Assumes malloc() succeeds and does not include checks // for its failure. Does not bother with checking rows/cols for // inappropriate values such as 0 or negatives. // // NOTES: Consult recent lab work for examples of how to allocate a 2D // array using repeated malloc() calls and adapt that approach // hear. Ensure that the allocation is being done via rows of tile_t // structs. Common errors are to neglect initializing all fields of // the maze and all fields of every tile. Valgrind errors that data // is uninitialized are usually resolved by adding code to explicitly // initialize everything. void maze_free(maze_t *maze); // PROBLEM 2: De-allocates the memory associated with a maze and its // tiles. Uses a doubly nested loop to iterate over all tiles and // de-allocate any non-NULL paths that are part of the tiles. // Iterates to free each row of the tiles row and frees then frees the // array of row tile row pointers. If the queue is non-null, frees it // and finally frees the maze struct itself. int maze_tile_blocked(maze_t *maze, int row, int col); // PROBLEM 2: Return 1 if the indicated coordinate is blocked and // could not be traversed as part of a path solving a maze. A // coordinate is blocked if it is out of bounds for the maze (row/col // below zero or above maze row/col boundaries) or the tile at that // coordinate has type WALL. If the coordinate is not blocked, returns // 0. This function will be used later during the // Breadth-First-Search to determine if a coordinate should be ignored // due to being blocked. // // NOTES: This function will require accessing fields of the maze so // review syntax on struct field acces and 2D indexing into the maze's // tiles table. The tiletype_t enumeration in mazesolve.h establishes // global symbols for tile types like OPEN, ONPATH, START, and so // forth. Use one of these in this function. void maze_print_tiles(maze_t *maze); // PROBLEM 2: Prints `maze` showing the solution path from Start to // End tiles. First prints maze information including the size in rows // and columns and the coordinates of Start/End tiles. The prints out // the maze tiles with each tile printed based on its type. The character // to print for each tile is based on its type and corresponds to // elements in the global tiletyp_chars[] array: if the type is WALL, // prints the character at tiletype_char[WALL]. This function is used // to print a maze loaded from a file and print the solution path // after doing a BFS. // // EXAMPLE 1: Output for maze which does not yet have a solution so no // tiles have state ONPATH // // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // # ### ###### # # // # ### ##E # # // # ### #### ## # // # # // ################ // // EXAMPLE 2: The same maze above where some tiles have been marked // with ONPATH as a solution has been calcualted via BFS. The ONPATH // tiles are printed with . characters specified in the // tiletype_chars[] global variable. // // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // #.### ###### # # // #.### ##E..# # // #.### ####.## # // #.......... # // ################ void maze_print_state(maze_t *maze); // PROBLEM 2: Prints the `maze` in a format that shows its state and // progress during the BFS search from Start to End positions. The // 2D grid of tiles is printed with tile with state FOUND printing a // single character representaion of its distance from the Start tile // and all other tiles with state NOTFOUND printing a single character // that is based on their type (e.g. spaces for OPEN tiles, # for WALL // tiles, etc). The `maze` queue is also printed using the previously // written rcqueue_print() function for output. // // FOUND tiles will print their distance as a single character per one // of two cases so that compact be reasonably complete information // about its distance from the Start tile is shown. // // - If the tile's path_len field is NOT divsible by 10, then print // the last digit of the path_len (e.g. divide path_len by 10 and // take remainder // // - If the tiles' path_len IS divisible by 10, print the single // character at index (path_len)/10 in the global string // digit10_chars[]. These will show 'a' for 10, 'b' for 20, 'c' for // 30, and so on. // // To the right of each maze row print a ": <row#>". Below the maze, // print two lines: the first a sequence of "01234..890123..." up to // the number of columns and below that a "tens" digits (0, 1, 2, etc // printed every 10 characters). The combin combination of row and // column axis label numbers makes it easier to find a tile at a // specific coordinate. // // This function is used during BFS to show progress of the algorithm // in a step-by-step manner when logging of BFS steps is enabled. // // CONSTRAINT 1: You must provide comments in this function that guide // a reader on what different blocks and in some cases specific lines // do. Comments like "print the bottom axis labels" or "tile not // found, print its normal character" are informative. Comments like // "print" or "assign tile" are not informative. // // CONSTRAINT 2: This function must avoid "many-case" conditionals // such as if/else an switches based on the tile type or path_len. Use // the provided global arrays digit10_chars[] and tiletype_chars[] to // avoid such many-case conditionals. // // EXAMPLE: A 7x16 maze printed after a series of BFS steps. // // ################: 0 // #0123456789a123#: 1 // #1###5######2#4#: 2 // #2###6##E #34 #: 3 // #3###7####4## #: 4 // #456789a1234 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 4 // NN ROW COL // 0 4 10 // 1 5 11 // 2 3 13 // 3 2 14 // // EXAMPLE NOTES: The End tile E at (3,8) has not been found yet so is // drawn with an E while the Start tile at (1,1) is FOUND so is drawn // with a 0 (path_len 0 from the Start). Some tiles like the WALL // tiles marked with # are never FOUND as they are blocked and some // OPEN tiles have not been FOUND yet so are draw with a space. Some // FOUND tiles have 'a' in them indicating their path is 10 long from // the Start tile. The 'a' is taken from the digits10_chars[] array. ////////////////////////////////////////////////////////////////////////////////
6.5 Grading Criteria for Problem 2 grading 30
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob2 |
|
15 | Runs tests in test_mazesolve5.org / test_mazesolve_funcs.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
2 | tile_print_path |
Clear case analysis for NULL path, compact, and verbose formats | |
Uses the provided arrays of compact/verbose strings when printing directions | |
Use of printf() format specifiers and width the nicely format output |
|
1 | tile_extend_path() |
Proper use of malloc() to create a new memory space for the longer path |
|
Use of a loop or library function to copy memory from the old to the new space | |
1 | maze_allocate() |
Initialization of fields of maze to default values like -1 and NULL | |
Allocation of tile row pointers and a loop to iterate and all rows of the maze | |
Clear iteration pattern to initialize all tiles in the maze | |
1 | maze_free() |
Clear iteration pattern to visit all tiles in the 2D grid and free() any non-null paths in them |
|
Iteration to free all rows and the tiles array | |
Checks and frees a non-null queue | |
1 | maze_tile_blocked() |
Performs bounds checks to ensure tile coordinates are within the maze | |
Correct use of 2D indexing into the 2D tiles array to access the type of a tile | |
1 | maze_print_tiles() |
Use of printf() and format specifiers to print integer and character data |
|
Clear iteration pattern visiting all tiles in the 2D grid of the maze | |
3 | maze_print_state() |
Nested loops to print a character for each tile | |
Case analysis so that tiles with pathlen evenly divisible by 10 are printed using letters | |
Avoids many-case conditions and uses global arrays like digit10_chars[] / tiletype_chars[] |
|
Clear code that prints the axis labels for row/column | |
Comments included to guide a reader through the complexities of the code |
7 Problem 3: BFS Functions
This problem implements Breadth First Search on the maze. The implementation is broken up into several functions with some specific features.
7.1 Step Functions
S iterates manipulating data at each step. To make it possible to
check that steps are taken correctly, the specific changes to process
a neighbor tile are condensed into the function
maze_bfs_process_neighbor()
and the sequence of operations to expand
beyond a tile are also in a function, maze_bfs_step()
. This allows
for easier testing to give some confidence that these operations are
correct before repeating them. The general hierarchy of the algorithm
is
maze_bfs_iterate()
repeatedly callsmaze_bfs_step()
until the queue of search tiles is emptymaze_bfs_step()
repeatedly callsmaze_bfs_process_neighbor()
for each direction of movement (North, South, etc.) from that tile.maze_bfs_process_neighbor()
will process a single tile, adding a path to when it is Found and ignoring out-of-bounds or previously found tiles
7.2 Logging
The functions here perform potentially many operations
that manipulate the internal state of Tiles and Mazes. To make
debugging them easier, some functions are required to print "LOG"
messages when the global variable LOG_LEVEL
is set to certain
values. Logging and debug messages are a common feature of many
programs as they enable diagnosis of problems more easily by
providing more detail information about how code proceeds. A simple
way to do conditional logging is with conditionals, as in
if(LOG_LEVEL >= LOG_BFS_STATES){ // print something here }
The documentation comments for each function note what log messages to print in their LOGGING sections.
Most of the tests set LOG_LEVEL=LOG_ALL
which will print
everything. This is meant to make debugging easier.
7.3 Required BFS Functions
// FUNCTIONS FOR PROBLEM 3: Breadth First Search of the Maze //////////////////////////////////////////////////////////////////////////////// void maze_bfs_init(maze_t *maze); // PROBLEM 3: Initializes the maze for a BFS search. Adjusts the start // tile: allocates it a length 0 path, sets its path_len to 0, and // sets its state to FOUND. Allocates an empty rcqueue for the queue // in the maze using an appropriate function and then adds the Start // tile to it. // // LOGGING: If LOG_LEVEL >= LOG_BFS_STATES, after initialization is // complete. prints "BFS initialization compelte" and calls // maze_print_state(). // // NOTES: This function is called within maze_bfs_iterate() to set up // the queue and Start tile before the search proceeds. It should not // be called outside of that context except during some testing of its // functionality and that of the BFS step function. // // EXAMPLE: The queue is initially empty (prints as null). After // calling bfs_init(), the Start tile is set to FOUND so // prints as its path_len of 0 and appears in the now // allocated queue. // // print_maze_state(maze); // ################: 0 // #S #: 1 // # ### ###### # #: 2 // # ### ##E # #: 3 // # ### #### ## #: 4 // # #: 5 // ################: 6 // 0123456789012345 // 0 1 // null queue // LOG_LEVEL = LOG_ALL; // maze_bfs_init(maze); // print_maze_state(maze); // LOG: BFS initialization complete // ################: 0 // #0 #: 1 // # ### ###### # #: 2 // # ### ##E # #: 3 // # ### #### ## #: 4 // # #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 1 // NN ROW COL // 0 1 1 int maze_bfs_process_neighbor(maze_t *maze, int cur_row, int cur_col, direction_t dir); // PROBLEM 3: Process the neighbor in direction `dir` from coordinates // `cur_row/cur_col`. Calculates the adjacent tiles row/col // coordinates using the row_delta[]/col_delta[] global array and // `dir`. If the neighbor tile is blocked according to the // maze_tile_blocked() function, makes no changes and returns 0 as the // position cannot be reached. If the neighbor tile has state FOUND, // makes no changes and returns 0 as the tile has already been // processed in the BFS. Otherwise changes the neighbor tile to be a // Found tile: extends the current tile's path to the neighbor tile in // the given direction, changes the neighbor tile's state to FOUND and // adds the neighbor tile to the maze search queue. Ends by returning // 1 as the tile/maze data has changed. This function is used in BFS // to propogate paths to all non-blocked neighbor tiles and extend the // search frontier. // // LOGGING: // 1. If LOG_LEVEL >= LOG_BFS_PATHS and the neighor tile's state // changes from NOTFOUND to FOUND, print a message like: // LOG: Found tile at (4,10) with len 14 path: SSSSEEEEEEEEEN // with the coordinates, path_len, and COMPACT path for the newly // found tile. // 2. If LOG_LEVEL >= LOG_SKIPPED_TILES and the neighbor tile is // skipped as it is blocked or already found, print a message like // one of // LOG: Skipping BLOCKED tile at (6,13) // LOG: Skipping FOUND tile at (5,12) // // EXAMPLE: // maze_print_state(maze); // ################: 0 // #0123 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #4 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 4 // 1 5 1 // LOG_LEVEL = LOG_ALL; // above both LOG_BFS_PATHS and LOG_SKIPPED_TILES // maze_bfs_process_neighbor(maze, 1, 4, NORTH); // LOG: Skipping BLOCKED tile at (0,4) // maze_bfs_process_neighbor(maze, 1, 4, SOUTH); // LOG: Skipping BLOCKED tile at (2,4) // maze_bfs_process_neighbor(maze, 1, 4, WEST); // LOG: Skipping FOUND tile at (1,3) // maze_bfs_process_neighbor(maze, 1, 4, EAST); // LOG: Found tile at (1,5) with len 4 path: EEEE // maze_print_state(maze); // ################: 0 // #01234 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #4 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 4 // 1 5 1 // 2 1 5 int maze_bfs_step(maze_t *maze); // PROBLEM 3: Processes the tile in BFS which is at the front of the // maze search queue. For the front tile, iterates over the directions // in the global dir_delta[] array from index DELTA_START to less than // DELTA_COUNT which will be NORTH, SOUTH, WEST, EAST. Processes the // neighbors in each of these directions with an appropriate // function. Removes the front element of the search queue and // returns 1. Note: if this function is called when the maze queue is // empty, return 0 and print an error message though this case will // not be tested and should not arise if other parts of the program // are correct. // // LOGGING: // If LOG_LEVEL >= LOG_BFS_STEPS, print a message like // LOG: processing neighbors of (5,1) // at the start of the function. // // If LOG_LEVEL >= LOG_BFS_STATES, prints a message and uses // maze_print_state() at the end of the function to show the maze // after processing finishes as in: // LOG: maze state after BFS step // ################: 0 // #01234 #: 1 // #1### ###### # #: 2 // #2### ##E # #: 3 // #3### #### ## #: 4 // #45 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 5 // 1 5 2 // // EXAMPLE: // maze_print_state(maze); // ################: 0 // #0123456789a1 #: 1 // #1###5###### # #: 2 // #2###6##E # #: 3 // #3###7#### ## #: 4 // #456789a12 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 2 // NN ROW COL // 0 1 12 // 1 5 9 // LOG_LEVEL = LOG_ALL; // maze_bfs_step(maze); // LOG: processing neighbors of (1,12) // LOG: maze state after BFS step // ################: 0 // #0123456789a12 #: 1 // #1###5######2# #: 2 // #2###6##E # #: 3 // #3###7#### ## #: 4 // #456789a12 #: 5 // ################: 6 // 0123456789012345 // 0 1 // queue count: 3 // NN ROW COL // 0 5 9 // 1 2 12 // 2 1 13 void maze_bfs_iterate(maze_t *maze); // PROBLEM 3: Initializes a BFS on the maze and iterates BFS steps // until the queue for the maze is empty and the BFS is complete. Each // iteration of the algorithm loop will process all neighbors of a // FOUND tile. This will calculate paths from the Start tile to all // other tiles including the End tile. // // LOGGING: If LOG_LEVEL >= LOG_BFS_STEPS prints the step number for // each iteration of the loop as // LOG: BFS STEP 45 // where the number starts at 1 and increases each loop iteration. // // See EXAMPLES for main() to get an idea of output for iteration. // // NOTES: This function will call several of the preceding functions // to initialize and proceed with the BFS. int maze_set_solution(maze_t *maze); // PROBLEM 3: Uses the path stored in the End tile to visit each tile // on the solution path from Star to End and make them as ONPATH to // show the solution path. If the End tile has a NULL path, returns 0 // and makes no changes to the maze. Otherwise, visits each direction // in the End tile's path and, beginning at the Start tile, "moves" in // the direction indicated and chcnges the nextf tile's state to // ONPATH. Returns 1 on changing the state of tiles to show the // solution path. This function is used to set up printing the // solution path later. // // CONSTRAINT: Makes use of the row_delta[] / col_delta[] global // arrays when "moving" rather than using multi-way conditional. For // example, if the current coordinates are (3,5) and the path[i] // element is WEST, uses row_delta[WEST] which is -1 and // col_delta[WEST] which is 0 to update the coordinates to (3,4) which // is one tile to the WEST. Solutions that use a multi-way conditional // with cases for NORTH, SOUTH, etc. will be penalized. // // NOTE: This function may temporarily change the state of the END // tile to ONPATH as indicated in the log messages below. Before // finishing, the state of the END tile is changed from ONPATH to END. // // LOGGING: If the LOG_LEVEL >= LOG_SET_SOLUTION, prints out // // 1. Once at the beginning of the solution construction // LOG: solution START at (1,1) // with the coordinates substituted for the actual START coordinates. // // 2. For every element of the End tile path[] array // LOG: solution path[5] is EAST, set (5,3) to ONPATH // with items like the index, direction, and coordinates replaced with // appropriate data via printf(). Use the direction_verbose_strs[] // to print strings of the directions. // // 3. Once at the end of solution construction: // LOG: solution END at (3,8) // with the coordinates substituted for the actual END coordinates. // // EXAMPLE: // maze_print_tiles(maze); // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // # ### ###### # # // # ### ##E # # // # ### #### ## # // # # // ################ // // LOG_LEVEL = LOG_SET_SOLUTION; // maze_set_solution(maze); // LOG: solution START at (1,1) // LOG: solution path[0] is SOUTH, set (2,1) to ONPATH // LOG: solution path[1] is SOUTH, set (3,1) to ONPATH // LOG: solution path[2] is SOUTH, set (4,1) to ONPATH // LOG: solution path[3] is SOUTH, set (5,1) to ONPATH // LOG: solution path[4] is EAST, set (5,2) to ONPATH // LOG: solution path[5] is EAST, set (5,3) to ONPATH // LOG: solution path[6] is EAST, set (5,4) to ONPATH // LOG: solution path[7] is EAST, set (5,5) to ONPATH // LOG: solution path[8] is EAST, set (5,6) to ONPATH // LOG: solution path[9] is EAST, set (5,7) to ONPATH // LOG: solution path[10] is EAST, set (5,8) to ONPATH // LOG: solution path[11] is EAST, set (5,9) to ONPATH // LOG: solution path[12] is EAST, set (5,10) to ONPATH // LOG: solution path[13] is NORTH, set (4,10) to ONPATH // LOG: solution path[14] is NORTH, set (3,10) to ONPATH // LOG: solution path[15] is WEST, set (3,9) to ONPATH // LOG: solution path[16] is WEST, set (3,8) to ONPATH // LOG: solution END at (3,8) // // maze_print_tiles(maze); // maze: 7 rows 16 cols // (1,1) start // (3,8) end // maze tiles: // ################ // #S # // #.### ###### # # // #.### ##E..# # // #.### ####.## # // #.......... # // ################ ////////////////////////////////////////////////////////////////////////////////
7.4 Grading Criteria for Problem 3 grading 30
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob3 |
|
15 | Runs tests in test_mazesolve3.org / test_mazesolve_funcs.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
3 | maze_bfs_init() |
Initializes the queue and start tile in preparation for BFS | |
Appropriate log message(s) printed | |
3 | maze_bfs_process_neighbor() |
Utilizes the global row_deltas[] array to determine row/col change appropriate to neighbor direction |
|
Makes use of previously written functions for queue manipulation, blocked tile checks, etc. | |
Appropriate log message(s) printed | |
3 | maze_bfs_step() |
Makes use of previously written functions for queue manipulation, neigbhbor processing etc. | |
Clear iteration pattern over directions in the maze_dir_delta[] array form DELTA_START to DELTA_COUNT |
|
Appropriate log message(s) printed | |
3 | maze_bfs_iterate() |
Clear iteration pattern while the maze search queue is not empty | |
Makes use of previously written functions for search data initialization, etc. | |
Appropriate log message(s) printed | |
3 | maze_set_solution() |
Checks for NULL path in which case no solution is present | |
Clear iteration pattern over the path to End tile | |
Use of the row_delta[] / col_delta[] arrays to avoid many-case conditionals while setting path |
|
Appropriate log message(s) printed |
8 Problem 4: Data Files and Main
8.1 Maze Data Files
Maze data files follow a simple format which specifies the rows and
columns in the maze in the first line, follows with a fixed indication
of the start of the tiles section, and all subsequent lines are rows
of the maze. This format is chosen as it allows easy processing of the
data: reading the first line gives the amount of memory required for
all tiles which an be allocated and the remainder of the file read
into the allocated space. Start/End tiles are marked with S/E
with
Open tiles as spaces and wall tiles as #
.
Below are a few examples of files.
EXAMPLE: maze-room1.txt
rows: 7 cols: 19 tiles: ################### # # # # # ### ## ## # # # ## # S # # # # ## # ##### # # # #E # # # ###################
A fairly standard maze. To make it easier to visualize the boundaries,
many mazes include a border of Wall #
tiles around the edge as is
done here.
EXAMPLE: maze-edge1.txt
rows: 7 cols: 16 tiles: ############## S # # ### ###### # # # ### ##E # # # ### #### ## # # # ################
This maze is similar but does not have explicit wall tiles all the way
around it. Rather, the S
start tile is located at the edge. Going
East or North will be out of bounds and a dead end to the search.
EXAMPLE: maze-big-mult1.txt
rows: 21 cols: 51 tiles: ################################################### #S # # # ### ########### ################# ##### # # ##### # # # # # # # # # # # # # # ##### # ### # ##### # ##### # ### # # ##### ### # # # # # # # # # # # # # # # # # ######### # ####### ### # ##### ### ### ### # # # # # # # # # # # # # ##### ### # ##### ####### ######### ### # ##### # # # # # # # # # # # # # # # ### # ####### # # ### ####### ### ####### # # # # # # # # # # # # # # # # # ##### # ### # ##### # # ######### ######### ### # # # # # # # # # # # # ########## # # # ##### ########### # # ##### # # # # # # # # # # # # # # # ### ### ######### # # # # ##### ### # # ##### # # # # # # # # # # # # # # # # # # # # # ####### # # ### ### # ### # # # ##### # # ##### # # # # # # # E ###################################################
This is a large maze that would take a human reader some time to complete and cause some second-guesses on whether the solution path found is indeed the shortest path from Start to End. That's why we write programs, to provide relief for tedious tasks and give more confidence in correct answers.
Other Examples
There are a variety of other examples of data files in the data/
directory for the project. Many of these files are used in testing so
DON'T DELETE THE data/ DIRECTORY.
8.2 Loading Maze Data
Data is loaded from files into a Maze via the following function. This is a fairly complex function so expect to take some time to get it right. Also be conscious of the constraint that you must comment your code for full credit on this function.
maze_t *maze_from_file(char *fname); // PROBLEM 4: Read a maze from a text file. The text file format // starts with the size of the maze and then contains its tiles. An // example is: // // rows: 7 cols: 19 // tiles: // ################### // # # # # // # ### ## ## # # // # ## # S # # # # // ## # ##### # # # // #E # # # // ################### // // If `fname` cannot be opened as a file, returns NULL. Otherwise // proceeds to read row/col numbers, allocate a maze of appropriate // size, and read characters into it. Each tile read has its state // changed per the character it is shown as. The global array // tiletype_chars[] should be searched to find the character read for // a tile and the index at which it is found set to be the tile state; // e.g. the character 'S' was read which appears at index 4 of // tiletype_chars[] so the tile.type = 4 which is START in the // tiletype enumeration. // // CONSTRAINT: You must use fscanf() for this function. // // CONSTRAINT: You MUST comment your code to describe the intent of // blocks of code. Full credit comments will balance some details with // broad descriptions of blocks of code. Missing comments or pedantic // comments which describe every code line with unneeded detail will // not receive full credit. Aim to make it easy for a human reader to // scan your function to find a specific point of interest such as // where a tile type is determined or when a row is finished // processing. Further guidance on good/bad comments is in the C // Coding Style Guide. // // LOGGING: If LOG_LEVEL >= LOG_FILE_LOAD prints messages to help // track parsing progress. There are many log messages required as // they will be useful for debugging parsing problems that always // arise when reading such data. // // LOG: expecting 6 rows and 9 columns // Printed after reading the first line indicating rows/cols in the // maze. // // LOG: beginning to read tiles // Printed after reading the "tiles\n" line when the main loop is // about to start reading character/tiles. // // LOG: (2,1) has character ' ' type 2 // Printed with the coordinates of each character/tile that is // read. The coordinates, character read, and an integer indicating // the type decided upon for the tile is shown. // // LOG: (2,4) has character 'S' type 4 // LOG: setting START at (2,4) // Printed when the Start tile, marked with an S, is found // // LOG: (3,7) has character 'E' type 5 // LOG: setting END at (3,7) // Printed when the End tile, marked with an E, is found // // LOG: finished reading row 4 of tiles // Printed after reading each row of the maze file to help track // progress. // // NOTES: The fscanf() function is essential for this function. A call // like fscanf(fin,"age: %d name: %s\n",&num, str) will read the literal // word "age:" then a number, then the literal word "name:" and a // string. Use this facility to parse the simple format. It is fine // to read a literal string only as in fscanf(fin,"skip this stuff\n") // which will bypass that exact line in a file. Use the format // specifier %c to read single characters as you process the tiles as // this is the easiest mechanism to work on the character parsing of // the maze tiles. Keep in mind that all lines of the maze will end // with the character '\n' which must be read past in order to get to // the next row.
8.3 The Main Function
The final part of the Maze Solving application is to code a main()
function. Unlike the "service" or "helper" functions, main()
should
be in its own file, mazesolve_main.c
. Create this file, and a main()
prototype that allows for command line arguments to be passed
in. Don't forget to include the project header as otherwise this
separate file will not be able to use the various functions in
mazesolve_funcs.c
.
#include "mazesolve.h" int main(int argc, char *argv[]){ ... }
The mazesolve_main.c
file will be compiled to mazesolve_main
using
the included Makefile
and this program has two required command line
forms it must support.
- Only the data file with the maze is passed in
- A
-log N
option appears preceding the data file. This sets the global variableLOG_LEVEL
which will trigger additional output to be printed.
Examples of these two command line forms are below.
>> ./mazesolve_main data/maze-small-twopath1.txt # Form 1: argc=2, only data file maze: 5 rows 5 cols # on command line (1,2) start (3,2) end maze tiles: ##### # S # # # # # E # ##### SOLUTION: maze: 5 rows 5 cols (1,2) start (3,2) end maze tiles: ##### #.S # #.# # #.E # ##### path length: 4 0: WEST 1: SOUTH 2: SOUTH 3: EAST >> ./mazesolve_main -log 1 data/maze-small-twopath1.txt # form 2: argc=4, -log option passed maze: 5 rows 5 cols # to specify log level, then data file (1,2) start (3,2) end maze tiles: ##### # S # # # # # E # ##### LOG: BFS STEP 1 # Log messages for BFS steps printed LOG: processing neighbors of (1,2) LOG: BFS STEP 2 LOG: processing neighbors of (1,1) LOG: BFS STEP 3 LOG: processing neighbors of (1,3) LOG: BFS STEP 4 LOG: processing neighbors of (2,1) LOG: BFS STEP 5 LOG: processing neighbors of (2,3) LOG: BFS STEP 6 LOG: processing neighbors of (3,1) LOG: BFS STEP 7 LOG: processing neighbors of (3,3) LOG: BFS STEP 8 LOG: processing neighbors of (3,2) SOLUTION: maze: 5 rows 5 cols (1,2) start (3,2) end maze tiles: ##### #.S # #.# # #.E # ##### path length: 4 0: WEST 1: SOUTH 2: SOUTH 3: EAST
To get insight into how command line arguments are received in the
argc / argv[]
parameters to main()
, study the provided
cmdline_args.c
program which prints command line arguments passed to
its executable.
>> gcc cmdline_args.c >> ./a.out There are 1 command line arguments arg 0: './a.out' >> ./a.out apple banana peach There are 4 command line arguments arg 0: './a.out' arg 1: 'apple' arg 2: 'banana' arg 3: 'peach' ...
8.4 Grading Criteria for Problem 4 grading 20
The following criteria will be checked.
Weight | Criteria |
---|---|
AUTOMATED TESTS via make test-prob4 |
|
10 | Runs tests in test_mazesolve4.org / test_mazesolve_funcs.c |
Runs all code under Valgrind to ensure that no memory errors are present. | |
1 point per test passed | |
MANUAL INSPECTION | |
5 | Code Style: Functions adhere to CMSC 216 C Coding Style Guide which dictates |
reasonable indentation, commenting, consistency of curly usage, etc. | |
3 | maze_from_file() |
Includes clear comments indicating stages of file processing / what is being read or done | |
Checks for failure to open a file and returns | |
Clearly marked code sections to read header information for rows/cols and "tiles:\n" | |
Clearly marked code sections to process rows of the maze and tiles | |
Use of the tiletype_chars[] array or equivalent data array to set tile types from characters |
|
2 | main() in mazesolve_main.c |
Case analysis of arguments to detect 2 or 4 arguments | |
Use of atoi() to convert strings to ints when needed |
|
Checks for failure to load data file |
9 Optional MAKEUP Credit (10pts)
An optional MAKEUP credit problem may be added after the initial project release. This will be announced and added to this section for those interested.
10 Project Submission
10.1 Submit to Gradescope
Some of the pictures below are dated and contain out of sync info like:
- 'Assignment' rather than'Project'
- Class code 'CSCI 2021' rather than 'CMSC216'
- Mention some files that are not part of the current project
- May indicate some other zip name when
p1-complete.zip
is that default name of the zip for completed projects.
However, most students should have no trouble adapting these
instructions which amount to: make zip
then upload the zip to
Gradescope.
In a terminal, change to your project code directory and type make zip which will create a zip file of your code. A session should look like this:
>> cd 216-sync/p1-code # location of assignment code >> ls Makefile mazesolve_main.c mazesolve_funcs.c data/ ... >> make zip # create a zip file using Makefile target rm -f p1-complete.zip cd .. && zip "p1-code/p1-complete.zip" -r "p1-code" adding: p1-code/ (stored 0%) adding: p1-code/Makefile (deflated 68%) adding: p1-code/test_prob1.org (deflated 69%) ... Zip created in p1-complete.zip >> ls p1-complete.zip p1-complete.zip
Log into Gradescope and locate and click 'Project 1' which will open up submission
Click on the 'Drag and Drop' text which will open a file selection dialog; locate and choose your
p1-complete.zip
file (The pictures below may use a different zip name likea1-code.zip
)This will show the contents of the Zip file and should include your C source files along with testing files and directories.
Click 'Upload' which will show progress uploading files. It may take a few seconds before this dialog closes to indicate that the upload is successful. Note: there is a limit of 256 files per upload; normal submissions are not likely to have problems with this but you may want to make sure that nothing has gone wrong such as infinite loops creating many files or incredibly large files.
WARNING: There is a limit of 256 files per zip. Doing
make zip
will warn if this limit is exceeded but uploading to Gradescope will fail without any helpful messages if you upload more the 256 files in a zip.Once files have successfully uploaded, the Autograder will begin running the command line tests and recording results. These are the same tests that are run via
make test
.When the tests have completed, results will be displayed summarizing scores along with output for each batch of tests.
10.2 Late Project Submission Policies
You may wish to review the policy on late project submission which will cost 1 Engagement Point per day late. No projects will be accepted more than 48 hours after the deadline.
https://www.cs.umd.edu/~profk/216/syllabus.html#late-submission