# [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: • Remember to check if fopen returned NULL! • If so, you can return from main or use exit from stdlib.h to exit the program. • Feel free to use any example code I have given you. • Things like get_line, streq_nocase etc will be helpful. • You’ll probably want to make a version of get_line to read from an arbitrary FILE* instead of only stdin. • Test your code before you make the game. • Print out the dictionary after reading it into the array. • If you’re getting blank lines between words, you probably didn’t chop the \n off the lines when you read them. ## 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: • If they guess a letter they have guessed before, don’t handle it specially. • If they guess a correct letter twice, nothing bad will happen. • If they guess an incorrect letter twice, that’s the user’s fault 😜 • Try to split things into functions. • Do not add extra prompts/questions, and do not be too picky about user input. • Accept lower- AND upper-case letters for their guesses, using streq_nocase from lab2. • User experience is an important part of program design! • And it will make it faster and easier to grade your projects ;) ### Random numbers To generate random numbers: • #include <stdlib.h> and #include <time.h> • ONCE at the beginning of the program, “seed” the random number generator like so:  srand((unsigned int)time(NULL));  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. • When you need an exclusive range of random numbers such as [0, 2) do this:  rand() % (high_value - low_value) + low_value  • Write a function to do this. random_range(1, 10) makes more sense and is more readable than rand() % 9 + 1. # [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: • The four _id members are char arrays. • Look at the size to see how long they should be. • All the other members are unsigned integers. • The size will decide which type they should be. • Look back at your exploration from lab 2 to see which types are which size. • This whole struct should be 44 bytes. • You can print sizeof(WAVHeader) or whatever you’ve named it to check. • If you like, there is a header file called stdint.h which contains shorter, more precise type names for integers. • uint8_t, uint16_t, uint32_t, and uint64_t are unsigned 8-, 16-, 32-, and 64-bit integer types. • You use them like any other type, e.g. uint8_t x; makes an unsigned 8-bit integer variable x. ## Your program OK, enough talk, let’s program! You will make a utility that can: • Check if a file really is a WAV file • If it is, read and display useful information about it • Change its speed • Reverse the sound 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: • The number of bits per sample • The sample rate (samples per second), as Hz • Whether it is mono (1 channel) or stereo (2 channels) • The length of the data in samples (data_size / block_alignment) • The length of the data in seconds (length in samples, divided by samples_per_second) • You’re gonna get a fraction here, so you must cast one of the values to a float! • Display this value with 3 digits after the decimal point. 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: • If bits_per_sample is 8 and number_of_channels is 1: • treat it as an array of 8-bit integers. • If bits_per_sample is 16 and number_of_channels is 1; OR • If bits_per_sample is 8 and number_of_channels is 2: • treat it as an array of 16-bit integers. • If bits_per_sample is 16 and number_of_channels is 2: • treat it as an array of 32-bit integers. 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: • sine440_16.wav: 16-bit, 44100Hz, mono, 99225 samples, 2.250 seconds • sine440_8.wav: 8-bit, 44100Hz, mono, 99225 samples, 2.250 seconds • speech_16.wav: 16-bit, 44100Hz, mono, 103936 samples, 2.357 seconds • speech_8.wav: 8-bit, 44100Hz, mono, 103936 samples, 2.357 seconds • stereo_16.wav: 16-bit, 44100Hz, stereo, 112128 samples, 2.543 seconds • stereo_8.wav: 8-bit, 44100Hz, stereo, 112128 samples, 2.543 seconds • woosh_16.wav: 16-bit, 44100Hz, stereo, 220500 samples, 5.000 seconds • woosh_8.wav: 8-bit, 44100Hz, stereo, 220500 samples, 5.000 seconds ## 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

• [5] Submitted properly
• Follow the instructions….. please………………
• You’ll lose all 5 points if not submitted correctly.
• [30] hangman.c
• [5] Compiles and runs
• [5] Properly reads dictionary
• [5] Picks a random word from the dictionary
• [5] Implements rules correctly
• [5] Proper input and output (PLEASE follow the examples I gave)
• [5] Style (TRY to split your code into functions.)
• [65] wavedit.c
• [5] Compiles and runs
• [5] Struct matches WAV header structure
• [5] Uses command-line arguments to do its work (argc/argv)
• [5] Shows a “usage” message if run without command-line arguments
• [5] Checks that file exists (exit if fopen returns NULL)
• [5] Checks for a valid WAV header
• [10] Correctly displays file information in a readable way
• [10] Can change the sample rate of WAV files
• [10] Can reverse WAV files
• [5] Style (again, TRY to split your code into functions.)

# Submission

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

Follow this checklist:

• Did you make sure to put both .c files in the same proj1 directory?
• Did you make a copy of that directory just in case?
• Did you name them hangman.c and wavedit.c?
• Did you put your full name and username at the top of each file in comments?
• Do they compile using the command lines I gave in the testing section above?

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.