Skip to content
Snippets Groups Projects
Commit fc9696d3 authored by John Shawger's avatar John Shawger
Browse files

initial commit

parents
Branches main
No related tags found
No related merge requests found
README.md 0 → 100644
---
title: CS 537 Project 1
layout: default
---
# CS537 Fall 2024, Project 1
## Updates
* TBD
## Administrivia
- **Due Date** by September 13, 2024 at 11:59 PM
- Questions: We will be using Piazza for all questions.
- Collaboration: The assignment has to be done by yourself. Copying code (from others) is considered cheating. [Read this](http://pages.cs.wisc.edu/~remzi/Classes/537/Spring2018/dontcheat.html) for more info on what is OK and what is not. Please help us all have a good semester by not doing this.
- This project is to be done on the [lab machines](https://csl.cs.wisc.edu/docs/csl/2012-08-16-instructional-facilities/), so you can learn more about programming in C on a typical UNIX-based platform (Linux).
- A few sample tests are provided in the project repository. To run them, execute `run-tests.sh` in the `tests/` directory. Try `run-tests.sh -h` to learn more about the testing script. Note these test cases are not complete, and you are encouraged to create more on your own.
- Handing it in: Copy the whole project, including solution and tests folder, to `~cs537-1/handin/login/p1` where login is your CS login.
## Letter Boxed
In this assignment, you will write a program which checks solutions to the word game Letter Boxed.
Learning Objectives:
* Re-familiarize yourself with the C programming language
* Familiarize yourself with a shell / terminal / command-line of Linux
* Learn about file I/O, string processing, and simple data structures in C
Summary of what gets turned in:
* One `.c` file: `letter-boxed.c`
* It is mandatory to compile your code with following flags `-std=c17 -O2 -Wall -Wextra -Werror -pedantic -o letter-boxed`.
* Check `man gcc` and search for these flags to understand what they do and why you will like them.
* It is a good idea to create a `Makefile` so you just type `make` for the compilation.
* We are trying to use the most recent version of the C language. However, the compiler (`gcc` on lab machines) does not support final C23 specification, which is the most recent one. So we are using the second most recent (C17).
* It should (hopefully) pass the tests we supply.
* Include a single `README.md` describing the implementation. This file should include your name, your cs login, you wisc ID and email, and the status of your implementation. If it all works then just say that. If there are things you know doesn't work let me know.
__Before beginning__: Read this [lab tutorial](http://pages.cs.wisc.edu/~remzi/OSTEP/lab-tutorial.pdf); it has some useful tips for programming in the C environment.
## letter-boxed
The program you will build is called `letter-boxed`. This program plays the New York Times puzzle [Letter Boxed](https://www.nytimes.com/puzzles/letter-boxed). It takes two command line arguments -- a board file and a dictionary file. Then, it reads words from standard input (`STDIN`) until the board is solved, or until an invalid solution is attempted.
Here is what it looks like to run the program:
```bash
$ ./letter-boxed board1.txt dict.txt
```
## Rules of the game
The rules of letter boxed are simple. Given a letter boxed board, you must use each letter in the board at least once to form words found in the dictionary. You can start on any letter, but the first character of each subsequent word must be the same as the last character of the previous word. Letters on the same side of the board may not be used consecutively. Letters can be used more than once, even in a single word. Letters not present on the board must not be used. If a letter is present on one side of the board, it cannot be present on other sides, i.e. just a single occurrence of a letter on the board.
For simplicity, we will only use lower-case ASCII characters `a-z`. Thus, there are 26 characters in the entire alphabet in use for this project. This is true for the boards, dictionary, and solution inputs.
Let us look at an example:
```
r o k
+---------+
| |
w| |e
| |
f| |d
| |
a| |n
| |
+---------+
l c i
```
A solution to this board could be
```
flan
now
wreck
kid
```
However, `wrecked` would be an invalid word because the `e` and `d` are on the same side of the board. The same solution with the words in a different order would also be invalid, because that would break the rule of consecutive words sharing their last/first letters.
Since we are writing a programmatic solver, we can be more flexible with the game. Boards might have more than four sides (although never less than three), and sides need not always have three letters.
## File formats
The picture above is suitable for humans playing the game. Our board files are a little simpler -- a board file has one line of text per side of the board.
```bash
$ cat board1.txt
rok
edn
lci
wfa
```
The dictionary file will have one word per line.
You can expect the solution to be input through standard input (`STDIN`) as one word per line. The program should run until an invalid board is detected, an input error is found, the board is solved, or end of file (`EOF`) is reached.
## Possible outputs
- If the board is invalid (less than 3 sides), print `Invalid board\n` and exit with return code 1.
- If a board contains a letter more than once, print `Invalid board\n` and exit with return code 1.
- If the solution uses a letter not present on the board, print `Used a letter not present on the board\n` and exit with return code 0.
- If the solution does not use all the letters on the board, print `Not all letters used\n` and exit with return code 0.
- If the solution illegally uses letters on the same side of the board consecutively, print `Same-side letter used consecutively\n` and exit with return code 0.
- If the solution uses a word not found in the dictionary, print `Word not found in dictionary\n`, and exit with return code 0.
- If the first character of a word in the solution is not the same as the last character in the preceding word, print `First letter of word does not match last letter of previous word\n` and exit with return code 0.
- If the solution is correct, print `Correct\n`, and exit with return code 0.
- All other errors (wrong number of arguments, open file failed, etc.) should exit with code 1.
## Tips
- When working with any C library functions, check their manual page (`man`) for a description and proper usage, e.g. `man 3 fopen`.
- To work with files, look at `man 3 fopen` and `man 3 fclose`.
- To read data from files and STDIN, consider using `getline(3)`, `fgets(3)`, or maybe `scanf(3)`.
- Printing to the terminal can be done with `printf(3)`.
- You don't know the size of boards, words, or the dictionary ahead of time, so you will need to dynamically allocate memory.
- You will need to compare words against the dictionary. We aren't evaluating your program based on performance, so you could use something as simple as a linked list and linear search, but feel free to explore faster solutions like a dynamically-sized array with binary search, or tries.
## Acknowledgments
Assignment written by John Shawger, Letter Boxed may be found at www.nytimes.com
This diff is collapsed.
letter-boxed
letter-boxed-dbg
CC = gcc
CFLAGS-common = -std=c17 -Wall -Wextra -Werror -pedantic
CFLAGS = $(CFLAGS-common) -O2
CFLAGS-dbg = $(CFLAGS-common) -Og -g
TARGET = letter-boxed
SRC = $(TARGET).c
all: $(TARGET) $(TARGET)-dbg
$(TARGET): $(SRC)
$(CC) $(CFLAGS) $< -o $@
$(TARGET)-dbg: $(SRC)
$(CC) $(CFLAGS-dbg) $< -o $@
clean:
rm -f $(TARGET) $(TARGET)-dbg
The `run-tests.sh` script is called by various testers to do the work of
testing. Each test is actually fairly simple: it is a comparison of standard
output and standard error, as per the program specification.
In any given program specification directory, there exists a specific `tests/`
directory which holds the expected return code, standard output, and standard
error in files called `n.rc`, `n.out`, and `n.err` (respectively) for each
test `n`. The testing framework just starts at `1` and keeps incrementing
tests until it can't find any more or encounters a failure. Thus, adding new
tests is easy; just add the relevant files to the tests directory at the
lowest available number.
The files needed to describe a test number `n` are:
- `n.rc`: The return code the program should return (usually 0 or 1)
- `n.out`: The standard output expected from the test
- `n.err`: The standard error expected from the test
- `n.run`: How to run the test (which arguments it needs, etc.)
- `n.desc`: A short text description of the test
- `n.pre` (optional): Code to run before the test, to set something up
- `n.post` (optional): Code to run after the test, to clean something up
There is also a single file called `pre` which gets run once at the
beginning of testing; this is often used to do a more complex build
of a code base, for example. To prevent repeated time-wasting pre-test
activity, suppress this with the `-s` flag (as described below).
In most cases, a wrapper script is used to call `run-tests.sh` to do the
necessary work.
The options for `run-tests.sh` include:
* `-h` (the help message)
* `-v` (verbose: print what each test is doing)
* `-t n` (run only test `n`)
* `-c` (continue even after a test fails)
* `-d` (run tests not from `tests/` directory but from this directory instead)
* `-s` (suppress running the one-time set of commands in `pre` file)
There is also another script used in testing of `xv6` projects, called
`run-xv6-command.exp`. This is an
[`expect`](https://en.wikipedia.org/wiki/Expect) script which launches the
qemu emulator and runs the relevant testing command in the xv6 environment
before automatically terminating the test. It is used by the `run-tests.sh`
script as described above and thus not generally called by users directly.
#! /usr/bin/env bash
GREEN='\033[0;32m'
RED='\033[0;31m'
NONE='\033[0m'
# run_test testdir testnumber
run_test () {
local testdir=$1
local testnum=$2
local verbose=$3
# pre: execute this after before the test is done, to set up
local prefile=$testdir/$testnum.pre
if [[ -f $prefile ]]; then
eval $(cat $prefile)
if (( $verbose == 1 )); then
echo -n "pre-test: "
cat $prefile
fi
fi
local testfile=$testdir/$testnum.run
if (( $verbose == 1 )); then
echo -n "test: "
cat $testfile
fi
eval $(cat $testfile) > tests-out/$testnum.out 2> tests-out/$testnum.err
echo $? > tests-out/$testnum.rc
# post: execute this after the test is done, to clean up
local postfile=$testdir/$testnum.post
if [[ -f $postfile ]]; then
eval $(cat $postfile)
if (( $verbose == 1 )); then
echo -n "post-test: "
cat $postfile
fi
fi
return
}
print_error_message () {
local testnum=$1
local contrunning=$2
local filetype=$3
builtin echo -e "test $testnum: ${RED}$testnum.$filetype incorrect${NONE}"
echo " what results should be found in file: $testdir/$testnum.$filetype"
echo " what results produced by your program: tests-out/$testnum.$filetype"
echo " compare the two using diff, cmp, or related tools to debug, e.g.:"
echo " prompt> diff $testdir/$testnum.$filetype tests-out/$testnum.$filetype"
echo " See tests/$testnum.run for what is being run"
if (( $contrunning == 0 )); then
exit 1
fi
}
# check_test testdir testnumber contrunning out/err
check_test () {
local testdir=$1
local testnum=$2
local contrunning=$3
local filetype=$4
# option to use cmp instead?
returnval=$(diff $testdir/$testnum.$filetype tests-out/$testnum.$filetype)
if (( $? == 0 )); then
echo 0
else
echo 1
fi
}
# run_and_check testdir testnumber contrunning verbose printerror
# testnumber: the test to run and check
# printerrer: if 1, print an error if test does not exist
run_and_check () {
local testdir=$1
local testnum=$2
local contrunning=$3
local verbose=$4
local failmode=$5
if [[ ! -f $testdir/$testnum.run ]]; then
if (( $failmode == 1 )); then
echo "test $testnum does not exist" >&2; exit 1
fi
exit 0
fi
if (( $verbose == 1 )); then
echo -n -e "running test $testnum: "
cat $testdir/$testnum.desc
fi
run_test $testdir $testnum $verbose
rccheck=$(check_test $testdir $testnum $contrunning rc)
outcheck=$(check_test $testdir $testnum $contrunning out)
errcheck=$(check_test $testdir $testnum $contrunning err)
othercheck=0
if [[ -f $testdir/$testnum.other ]]; then
othercheck=$(check_test $testdir $testnum $contrunning other)
fi
# echo "results: outcheck:$outcheck errcheck:$errcheck"
if (( $rccheck == 0 )) && (( $outcheck == 0 )) && (( $errcheck == 0 )) && (( $othercheck == 0 )); then
echo -e "test $testnum: ${GREEN}passed${NONE}"
if (( $verbose == 1 )); then
echo ""
fi
else
if (( $rccheck == 1 )); then
print_error_message $testnum $contrunning rc
fi
if (( $outcheck == 1 )); then
print_error_message $testnum $contrunning out
fi
if (( $errcheck == 1 )); then
print_error_message $testnum $contrunning err
fi
if (( $othercheck == 1 )); then
print_error_message $testnum $contrunning other
fi
fi
}
# usage: call when args not parsed, or when help needed
usage () {
echo "usage: run-tests.sh [-h] [-v] [-t test] [-c] [-s] [-d testdir]"
echo " -h help message"
echo " -v verbose"
echo " -t n run only test n"
echo " -c continue even after failure"
echo " -s skip pre-test initialization"
echo " -d testdir run tests from testdir"
return 0
}
#
# main program
#
verbose=0
testdir="tests"
contrunning=0
skippre=0
specific=""
args=`getopt hvsct:d: $*`
if [[ $? != 0 ]]; then
usage; exit 1
fi
set -- $args
for i; do
case "$i" in
-h)
usage
exit 0
shift;;
-v)
verbose=1
shift;;
-c)
contrunning=1
shift;;
-s)
skippre=1
shift;;
-t)
specific=$2
shift
number='^[0-9]+$'
if ! [[ $specific =~ $number ]]; then
usage
echo "-t must be followed by a number" >&2; exit 1
fi
shift;;
-d)
testdir=$2
shift
shift;;
--)
shift; break;;
esac
done
# need a test directory; must be named "tests-out"
if [[ ! -d tests-out ]]; then
mkdir tests-out
fi
# do a one-time setup step
if (( $skippre == 0 )); then
if [[ -f tests/pre ]]; then
echo -e "doing one-time pre-test (use -s to suppress)"
source tests/pre
if (( $? != 0 )); then
echo "pre-test: failed"
exit 1
fi
echo ""
fi
fi
# run just one test
if [[ $specific != "" ]]; then
run_and_check $testdir $specific $contrunning $verbose 1
exit 0
fi
# run all tests
(( testnum = 1 ))
while true; do
run_and_check $testdir $testnum $contrunning $verbose 0
(( testnum = $testnum + 1 ))
done
exit 0
rok
edn
lci
wfa
flan
now
wreck
kid
Correct
make clean -C ../solution
make -C ../solution
0
../solution/letter-boxed tests/1.board ../dict.txt < tests/1.in
eun
roz
imp
fsy
frieze
eponymous
Correct
make clean -C ../solution
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment