[30%] hangman.c

Hangman is a word-guessing game. In Hangman, a word is chosen. You are shown a sequence of blanks, one for each character, so you know how long the word is. You must guess the word by guessing which letters are in it.

Every time you guess a letter correctly, all copies of that letter are revealed. If you guess all the letters, or if you guess the word, you win.

Every time you guess a letter or the word incorrectly, you get a strike. When you get five strikes, the game is over and you lose.

Here is a sample game of the player guessing the word “apple” successfully:

$ ./hangman
Welcome to hangman! Your word has 5 letters:
_ _ _ _ _ Guess a letter or type the whole word: s
Strike 1!
_ _ _ _ _ Guess a letter or type the whole word: e
_ _ _ _ e Guess a letter or type the whole word: a
a _ _ _ e Guess a letter or type the whole word: p
a p p _ e Guess a letter or type the whole word: appie
Strike 2!
a p p _ e Guess a letter or type the whole word: apple
You got it! The word was 'apple'.

Notice when they guessed 'p', it showed all the p’s in the word.

And here they are failing to guess the word “apple”:

$ ./hangman
Welcome to hangman! Your word has 5 letters:
_ _ _ _ _ Guess a letter or type the whole word: s
Strike 1!
_ _ _ _ _ Guess a letter or type the whole word: after
Strike 2!
_ _ _ _ _ Guess a letter or type the whole word: x
Strike 3!
_ _ _ _ _ Guess a letter or type the whole word: q
Strike 4!
_ _ _ _ _ Guess a letter or type the whole word: s
Strike 5!
Sorry, you lost! The word was 'apple'.

Notice they guessed the letter 's' twice. You don’t have to remember “wrong” letters. They can just look at their previous guesses 😜

Last, to help with testing and grading, your program should accept one command-line argument as the word to guess:

$ ./hangman scrumbus
Welcome to hangman! Your word has 8 letters:
_ _ _ _ _ _ _ _ Guess a letter or type the whole word: s
s _ _ _ _ _ _ s Guess a letter or type the whole word: u
s _ _ u _ _ u s Guess a letter or type the whole word: scrumbus
You got it! The word was 'scrumbus'.

That way, you can run it with a known word and see if it behaves the way you expect.


The Dictionary

I am providing you with a text file containing a dictionary of words to use. Here it is. Right-click and save the link. You’ll have to upload it to your AFS space with your SFTP client.

If you open this file, you will see a number on the first line, and then a bunch of words.

The first line says how many words there are in the dictionary. Then, each line after that is one word.

So, to read the dictionary:

  1. Read the first line with fgets and use atoi (from stdlib.h) or sscanf (from stdio.h) to parse the number out of it.
  2. Create an array of strings with that length.
    • You can use the syntax char words[length][20] to make the array.
    • This will limit each word to 19 characters, but none of the words are nearly that long.
  3. Read each subsequent line of the file into the array.
    • You can use words[i] and sizeof(words[i]) the way you’re used to.

Tips:


The game

Once you’re sure you’re reading the dictionary in properly, you can move onto the game.

It works like this:

  1. Pick a random word from the dictionary (see below).
    • Alternatively, if argc > 1, use argv[1] as the word instead.
  2. Tell the player how many letters there are in the word.
  3. In a loop:
    1. Show the current state of the word (see the interactions above to see how to print it out).
    2. Ask the user for a letter or word.
    3. If they didn’t type anything, go back to the previous step and ask them again.
    4. If they typed a single letter,
      • If it’s in the word at least once, mark all instances of that letter as “revealed”.
        • If all the letters are revealed, they win, and the program is over.
      • If it’s not in the word, they get a strike. Show how many strikes they have.
    5. If they typed a word (more than 1 letter),
      • If it’s correct (equal to the word you picked), they win, and the program is over.
      • If it’s not correct, they get a strike. Show how many strikes they have.
    6. If they have 5 strikes, they lose, and the program is over.
      • Show them the correct word.

Notes:

Random numbers

To generate random numbers:

This method will generate slightly biased results and should not be used to generate a range of random numbers in general. For something this simple, it’s fine.




[65%] wavedit.c

Now you’ll write a program to read and do simple operations on a common sound file format called WAV.

The way we record sound is by recording how the air pressure changes over time. In digital sound, we take thousands of measurements (“samples”) each second, and store each measurement as a number.

Here’s a diagram of a simple sound wave, represented by the red line; and the digital version of it, represented by the individual blue dots. Each dot is one sample, and each sample can be one of 16 different values.

Thanks, Wikipedia. Thikipedia.


The WAV file format

If you’re curious, chunk-based file formats are very common and pretty simple and flexible. Python even has a library for them. Cause, like, of course it does.

WAV files use a binary file format called RIFF, which is a “chunk-based” file format. However, the file format is simple enough that we can kind of treat it as a fixed format.

At the very beginning of the file, this structure appears (offsets and sizes are measured in bytes). What these fields should contain is explained a little further down.

Offset Size Name Description
0 4 riff_id chunk ID
4 4 file_size size of rest of file, in bytes
8 4 wave_id WAV file ID
12 4 fmt_id chunk ID
16 4 fmt_size size of the format info, in bytes
20 2 data_format how the data is encoded
22 2 number_of_channels how many channels (mono, stereo, surround etc)
24 4 samples_per_second how many times per second the pressure was measured
28 4 bytes_per_second how many bytes per second of sound
32 2 block_alignment how many bytes per sample of all channels
34 2 bits_per_sample how many bits each pressure measurement is
36 4 data_id chunk ID
40 4 data_size size of the sound data, in bytes

You must make a struct to represent this file header. (You could name it WAVHeader or something.)

When converting this table to a struct, keep these things in mind when deciding what types to use:


Your program

OK, enough talk, let’s program!

You will make a utility that can:

Your program will work as a command-line program taking command-line arguments – NOT asking for input with fgets!

You will be able to run it four different ways:

./wavedit
./wavedit somefile.wav
./wavedit somefile.wav -rate 22050
./wavedit somefile.wav -reverse

No arguments: ./wavedit

When run like this, your program should show a help message telling how to use it.

Try running cat --help on thoth to see what this kind of help or “usage” information usually looks like. Then make yours look kind of similar. :)


One argument: ./wavedit FILENAME

When run like this, the first argument (argv[1]) will be the name of a file.

This should:

  1. Check that the file exists (by using fopen)
    • Which mode should you use to read a binary file?
  2. Check that it really is a WAV file (see below)
  3. Display the format information and some other information. (see below)
  4. Don’t forget to close the file.

Make a function for each of these steps. Then it’ll be easy to reuse them for the other modes.

Checking that a file really is a WAV file

Remember from class that a file extension is just part of its name, and doesn’t mean that the file really is what it says it is. So, you must check the data in the file to ensure that it’s valid.

If any of these checks fail, display a message saying it’s an invalid WAV file and exit. Here’s how to check:

  1. Make a variable of your WAVHeader struct.
  2. Use a single fread call to read the struct from the file.
  3. Ensure that these fields have these values:
    • riff_id should contain the characters "RIFF"
    • wave_id should contain the characters "WAVE"
    • fmt_id should contain the characters "fmt " (NOTE: there is a space in there!)
    • data_id should contain the characters "data"
    • fmt_size should be 16
    • data_format should be 1
    • number_of_channels should be 1 or 2
    • samples_per_second should be greater than 0 and less than or equal to 192000
    • bits_per_sample should be 8 or 16
    • bytes_per_second should be samples_per_second * (bits_per_sample)/8 * number_of_channels
    • block_alignment should be (bits_per_sample)/8 * number_of_channels

To check the four _id fields, you CANNOT use strcmp/streq, because these are not C-style strings. They have no 0 terminator. Instead, use strncmp. Look it up!

Displaying the information

You should display the following information:

As an example, here is what my program outputs on the sine440_16.wav test file:

This is a 16-bit 44100Hz mono sound.
It is 99225 samples (2.250 seconds) long.

Setting the sound’s sample rate: ./wavedit somefile.wav -rate 22050

This will change the file, by setting its samples_per_second field to the given number. It should:

  1. Parse the argument after the -rate argument, and ensure that it is greater than 0 and less than or equal to 192000.
    • Give an error and exit if not.
  2. Open the file and read the header
    • You’ll have to open it for reading AND writing
  3. Check that it really is a WAV file, like before
  4. Set the samples_per_second in the struct to the rate given on the command line.
  5. Set the bytes_per_second in the struct to a new value calculated as:
    • samples_per_second * (bits_per_sample)/8 * number_of_channels
  6. Seek back to the beginning of the file
  7. fwrite the struct back out to the file
  8. Don’t forget to close the file!

Did I do it right?

If you set the rate too high, your computer might refuse to play it. If so, try to stay at 48000 Hz and below.

Now, if you read the file’s properties again, everything should be the same, except the rate should have changed. If it says that the header is invalid, maybe you forgot to update the bytes_per_second value in the header.

Also, you can download the file with your SFTP client, and play it, and it should sound faster or slower :)

Warning: iTunes likes to do stupid shit and will make copies of your files when you open them, and you’ll think your program isn’t working, when it is. This happened to me. Either delete the files from iTunes after listening, or use a different media player (VLC?).


Reversing a sound: ./wavedit somefile.wav -reverse

This will change the file, by reading all the samples, reversing them, and then writing them all back out again. It should work on 8- or 16-bit, mono or stereo sounds.

Here’s what it should do

  1. Open the file and read the header
    • You’ll have to open it for reading AND writing
  2. Check that it really is a WAV file, like before
  3. Calculate the data length in samples, like before
  4. Allocate an array of the right type and that length (see below)
  5. Use a single fread call to read the whole array from the file
    • After you read the header, the file is already in the right place to read the data
  6. Reverse the array’s values
    • That is, swap the first and last values; then the second and second-to-last; etc.
  7. Seek back to the beginning of the file plus sizeof(WAVHeader)
  8. Use a single fwrite call to write the whole array back to the file
  9. Don’t forget to close the file!

How samples are stored

For mono (1-channel) sounds, the samples are simply an array. If bits_per_sample is 8, it’s an array of 8-bit integers. If it’s 16, it’s an array of 16-bit integers.

For stereo (2-channel) sounds, each sample is stored as a pair of 8- or 16-bit integers. The first number in the pair is the left channel’s sample, and the second number is the right channel’s.

Since your reversing function doesn’t have to treat the stereo channels individually, you can treat the data array as follows:

Since C doesn’t have generics or function overloading, you’ll have to repeat this code three times, but with different types. Sorry ¯\_(ツ)_/¯

You can allocate the arrays like in hangman: sometype samples[sample_length]; where sometype is one of the types listed above.

When you fread, you can actually use sizeof() on that array, and it will give you the correct size of the array in bytes. At runtime. I know. I kind of lied. ;)

Did I reverse it correctly?

The best way to check is to use your ears. Use the stereo_ and speech_ test files given below. After you run your program, download it in your SFTP client and listen to it.


Test files

Here are some test WAV files to use, but your program will be graded with others. You can copy them to your directory like so:

cp ~jfb42/public/cs449/*.wav .

Don’t forget the space and period in the above command.

If you mess up or corrupt the files somehow, don’t worry, just run this command again to get clean copies of them.

The files should have the following properties by default:


Grading

We will be compiling your programs with the following options, so be sure to compile with them while you develop as well:

$ gcc --std=c99 -Wall -Werror -o hangman hangman.c
$ gcc --std=c99 -Wall -Werror -o wavedit wavedit.c

Submission

Name your file with proj1, like abc123_proj1.tar.gz.

Follow this checklist:

Never turn in a program that doesn’t compile.

If you answered yes to all those questions, then you can submit using the same directions as lab1, but replace lab1 with proj1.