Homework 3
Caution
  • You are expected to work individually.
  • Due: Friday, February 21st at 11pm EST (Baltimore time).
  • This assignment is worth 60 points.

Learning Objectives

Objectives

To practice with:

  • 2D arrays
  • strings
  • file I/O
  • command-line arguments
  • Makefiles
  • program organization
  • testing with assert
Individual Assignment

This is an individual assignment. This means you must NOT show your working code to another student, and should discuss with each other only the assignment requirements and expectations. See course staff for coding help.

Project Scaffolding (“Starter Code”)

In the class public git repository, the folder cs220-sp25-public/homework/hw3/ contains several files you should use as a starting point for this assignment. From within the public repo, type git status to confirm your local copy is in good shape, then type git pull to copy this folder. Then copy these files to your personal cs220 repository to begin your work. If you compile the files as posted, you’ll see a number of unused parameter errors reported, because the files contain incomplete function definitions. As you work on filling in the function definitions, you’ll eventually eliminate these warnings. By the time you submit your code for grading, none of these warnings (or any others) should be reported.

Write a program to solve a word search puzzle. Your program will accept a single command-line argument which is the name of a text file containing the word-grid. After reading the grid of letters from that file, you will read words for which to search from stdin. For each given word, you will print to stdout the location of all copies of that word in the grid. We have specific requirements detailed below regarding the structure of your solution code (see Implementation Requirements), test code that must be submitted (see Testing) and use of a makefile (see Makefile). Please read through the entire homework description (including Hints) before you begin.

Required Program Functionality

Unless otherwise noted, your program should exit with return value 0. The program expects a filename as a command-line argument. If none is supplied, the program should output “Please enter a command line argument.” and exit with return value 1. If a filename is supplied but cannot be opened successfully, output “Grid file failed to open.” and exit with return value -1. A proper grid file contains an N x M grid of characters where both N and M are at most 10 and all characters in the grid are alphabet letters. Note that N and M can be different values but are always less than or equal to 10 (MAX_SIZE). There are N rows and M columns of letters (all lower case), followed by an empty line at the end of each row, with no spaces between the letters. Anything in the grid file after the empty line can be ignored. There are only 3 types of invalid grids that your program must detect and report. In any of the following cases, your program should output “Invalid grid.” and exit with return value -2:

Search words read from stdin are separated by whitespace (any amount). The program should continue processing search words until it reaches the end-of-input (ctrl-d if stdin is not redirected). When the program is run first, it should print (to stdout) the number of rows and number of columns in the grid and then print the entire grid before waiting for the user to input the search word(s). See sample runs below.

Words may appear in the grid 1-diagonally down, 2-diagonally up, 3-anti-diagonally down, and 4-anti-diagonally up with the characters in order in adjacent cells. These four search directions are defined as follows:

1-Diagonally Down (DD): this means the word is found on some diagonal pattern within the grid where the first letter of the searched word is at the top-left position of the diagonal and the last letter is at the down-right position.

In the following example, the word “bee” is found, on a diagonal down pattern, in two places:

drawing

2-Diagonally Up (DU): this is the opposite of DD, which means the word is found on some diagonal pattern within the grid where the first letter of the searched word is at the down-right position of the diagonal and the last letter is at the top-left position.

In the following example, the word “key” is found once on a diagonal up pattern:

drawing

3-Anti-Diagonally Down (AD): this means the word is found on some anti-diagonal pattern within the grid where the first letter of the searched word is at the top-right position of the anti-diagonal and the last letter is at the down-left position.

In the following example, the word “jay” is found once on an anti-diagonal down pattern:

drawing

4-Anti-Diagonally Up (AU): this is the opposite of AD, which means the word is found on some anti-diagonal pattern within the grid where the first letter of the searched word is at the down-left position of the anti-diagonal and the last letter is at the top-right position.

In the following example, the word “see” is found, on an anti-diagonal up pattern, in two places:

drawing

Different words might share characters. The program must search for each word in all 4 directions: DD, DU, AD, and AU (as described above.)

Note

You may assume the content of the input grid file as well as all user inputs are in lower case!

For each appearance of a search word found in the grid, your program should output to stdout a line of text containing the matched word, the row number of the start of the word, the column number of the start of the word, and the direction of the match: DD for Diagonal Down, DU for Diagonal Up, AD for Anti-Diagonal Down, AU for Anti-Diagonal Up (as shown in the examples below). Rows and columns shown in the output are to be numbered starting with 0.

If multiple copies of a single search word are present in the grid, the program must locate and report each of them. The required order of the output lines for multiple appearances of a single search word w is as follows:

Multiple appearances within each directional category (DD, DU, AD, AU) should be reported in order of occurrence of the first letter in the search word, when the grid is read row-by-row from left to right, starting with row 0. For example, if word w appears in the grid directed DU starting at row 4, column 3 and again directed DU starting at row 1, column 6, then the appearance starting at row 1, column 6 should be listed before the other DU appearance, and both DU appearances should be after any DD appearances are reported, but before any AD and AU directions are reported.

Further Assumptions
  • If no matches for a word are found, print the search word followed by " - Not Found" , as shown below.
  • You may assume the input word is never longer than `MAX_SIZE` characters.
  • You may assume there is no intervening whitespace among characters in `grid.txt`.
  • You may assume there are NO null terminators anywhere in `grid.txt`.

Sample Runs

grid.txt, example input file with empty line at the end

pitka
olpeb
pkeyc
toped

Sample run #1, interactive user input shown (in bold) interleaved with program output

$ ./word_search grid.txt
num_rows = 4 and num_cols = 5
pitka
olpeb
pkeyc
toped
dyp
dyp 3 4 DU
ae
ae 0 4 AD
pit
pit - Not Found
ee
ee 2 2 DD
ee 3 3 DU
ee 1 3 AD
ee 2 2 AU
abcd
abcd - Not Found
[...user types Control-D...]

Sample run #2, interactive user input shown in bold:

$ ./word_search grid.txt
num_rows = 4 and num_cols = 5
pitka
olpeb
pkeyc
toped
lee pi e
lee 1 1 DD
pi 1 2 DU
e 1 3 DD
e 2 2 DD
e 3 3 DD
e 1 3 DU
e 2 2 DU
e 3 3 DU
e 1 3 AD
e 2 2 AD
e 3 3 AD
e 1 3 AU
e 2 2 AU
e 3 3 AU
[...user types Control-D...]

Sample run #3, redirected input using Unix echo and pipe

$ echo "po key nope" | ./word_search grid.txt
num_rows = 4 and num_cols = 5
pitka
olpeb
pkeyc
toped
po 2 0 DD
key - Not Found
nope - Not Found

Implementation Requirements

/*
 * Given a filename and a MAX_SIZExMAX_SIZE grid to fill, this function 
 * populates the grid and returns n, the actual grid dimension. 
 * If filename_to_read_from can't be opened, this function returns -1. 
 * If the file contains an invalid grid, this function returns -2.
 */
int populate_grid(int * m, char grid[][MAX_SIZE], char filename_to_read_from[]); 


/*
 * Each of these 4 functions returns the number of times the given 
 * word string was found in the grid facing the direction indicated
 * in the function name. Parameter n and m indicate num of rows and cols
 * respectively. The function sends corresponding output to the specified 
 * file pointer, which already points to an open stream. Output lines 
 * must appear in order of the first character's appearance in a 
 * left-to-right scan of each row beginning with row 0.
 */
int find_dd (char grid[][MAX_SIZE], int n, int m, char word[], FILE *write_to); 
int find_du (char grid[][MAX_SIZE], int n, int m, char word[], FILE *write_to); 
int find_ad (char grid[][MAX_SIZE], int n, int m, char word[], FILE *write_to); 
int find_au (char grid[][MAX_SIZE], int n, int m, char word[], FILE *write_to); 


/*
 * This function is similar to the other 4 find_ functions above, 
 * but reports ALL appearances of the given word, in the required 
 * DD, DU, AD, AU order.
 */
int find_all  (char grid[][MAX_SIZE], int n, int m, char word[], FILE *write_to); 

Testing

In addition to the source files listed above, you must write and submit a C file named test_search_functions.c that will contain a tester main function and #include search_functions.h. The file will also contain at least one helper test function per each of the functions declared in search_functions.h. For example, if the declaration for a function named fun1 appears in search_functions.h, then test_search_functions.c should include a corresponding function named test_fun1. The job of each of the helper functions in test_search_functions.c is to test, using assert statements, that the corresponding function declared in search_functions.h works properly. The main function in test_search_functions.c will call each helper function in turn, and, if all of the tests pass, should output Passed search_functions tests!!!\n to indicate success.

Makefile

You must submit a working Makefile with at least a word_search target that builds your program to create an executable named word_search and a test target that builds and runs your testing executable. To be considered fully functional, your Makefile should correctly build the executables with the appropriate flags when (and only when) one of the relevant source files has changed. The Makefile should be named Makefile (spelling and case are important).

Git Log

You are expected to use git to backup your progress on this project. Include in your submission a copy of the output of git log showing at least five commits to the repository of code from this assignment, with meaningful commit messages. (You should commit early and often, likely every time you have a version that compiles, so you should probably have many more than five of these). Just before submitting your work, save the output into a file called gitlog.txt by executing:

git log > gitlog.txt

Submission Checklist

Your Gradescope submission should contain at least the following files:

* word_search.c
* search_functions.h
* search_functions.c
* test_search_functions.c
* any data files created by you which your tester functions may require
* Makefile
* gitlog.txt

Hints and Tips

Here are some further hints and tips:

  • Make the DD search as clean and efficient as possible before attempting to write searches in other directions.
  • Instead of writing a new search function to search in reverse for a word, reverse the word itself and apply your original searches (DD, AD) to it.
  • Make use of gdb to help debug your program.
  • Write your function tester functions early, rather than waiting until the last minute. Their purpose is to help you catch bugs in the functions early on, so the bugs don't cause problems when used by main. Using this approach as intended will help you track down bugs more easily.
  • If in testing your program's output, you want to determine whether two files are identical (e.g. when one file contains your program's actual output and the other contains the expected output), the completed function named file_eq provided for you in the test_search_functions.c file will be useful!
  • In addition to writing tests for individual helper functions that are included in your submission, you'll need to create and utilize "end-to-end" tests for your complete word search as well. By "end-to-end" testing, we simply mean running the entire program and checking if it behaves as desired on a number of different inputs. Your end-to-end tests do not need to be handed in.

Submission

Create a .zip file named hw3.zip which contains all the requested files mentioned above. (Do not zip your entire hw3 folder - only the files.) Copy the hw3.zip file to your local machine and submit it as Homework 3 on Gradescope.

Caution

When you submit, Gradescope conducts a series of automatic tests. These tests do basic checks like making sure that you submitted the right files and that your `.c` file compiles properly. If you see error messages here (look for red), address them and resubmit.
No-compile policy

Remember that if your final submitted code does not compile, you will earn a zero score for the assignment.
Tips and Hints
  • You may re-submit any number of times prior to the deadline; only your latest submission will be graded.
  • Review the course syllabus for late submission policies (grace period and late days). You will want to save your late days for the future assignments as they will be more involved.
Code Styling - Style Matters!

You should also make sure that your code has good style. You can look at the coding style guidelines here: https://jhucsf.github.io/fall2024/resources/style.html. It is from a course you will take later that also applies to this course. In brief, you should make sure that your submission is well formed:
  • it is not overcommented or undercommented
  • there are no ambiguous variable names
  • there is proper/consistent bracket placements and indentation
  • there are no global variables
  • line and functions are a reasonable length

Two notes regarding automatic checks for programming assignments: