Last Updated: 2025-02-20 Thu 13:07

CMSC216 Project 1: C Programming

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 begins
  • E 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.

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.

  1. Directions: the 2D grid allows for only 4 movement directions which are labeled in the direction_t enumeration
  2. Tile Types: tiles in the maze come in a limited set of types which such as Walls and Open positions.
  3. 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 calls maze_bfs_step() until the queue of search tiles is empty
  • maze_bfs_step() repeatedly calls maze_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.

  1. Only the data file with the maze is passed in
  2. A -log N option appears preceding the data file. This sets the global variable LOG_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.

  1. 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
    
  2. Log into Gradescope and locate and click 'Project 1' which will open up submission

    gradescope01.png

  3. 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 like a1-code.zip)

    gradescope02.png

  4. This will show the contents of the Zip file and should include your C source files along with testing files and directories.

    gradescope03.png

  5. 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.

    gradescope04.png

  6. 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.

    gradescope05.png

  7. When the tests have completed, results will be displayed summarizing scores along with output for each batch of tests.

    gradescope06.png

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


Web Accessibility
Author: Chris Kauffman (profk@umd.edu)
Date: 2025-02-20 Thu 13:07