Due Date: | 4/13/17 (note: Friday) |
---|
-- :Revision: 1
In this homework, we'll take the "monster" database we used from last time (possibly including your own additions) and use it to write a fun little application: a quiz that will test the user's knowledge of movie monsters.
In order to make the quiz interesting, it makes use of a "random number generator" that makes it highly unlikely that any two executions of the program give users ("quizees") the same quiz.
We'll use a random number generator available to C programs as a function random() which, when called, returns a long int that ranges from 0 to some very large value called RAND_MAX. To make that result r to lie between 0 and N-1, inclusive, we use the modulus operator:
r = random() % N
(There's a helper function chooseOneOf() in the code to do just this. Feel free to use it.)
Here are the steps:
Download the template monsterquiz_tplt.c from the course web link for this homework:
Rename it to monsterquiz.c:
$ mv monsterquiz_tplt.c monsterquiz.c
Here are its contents:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <time.h> // for time() #include "monster.h" /* * This is storage for the database. */ #define MAX_MONSTERS 1024 /* should be big enough */ struct Monster monsters[MAX_MONSTERS]; int nMonsters; /* set in loadDatabase() */ /* * This is the number of possible answers (including the correct one) * for each question. */ #define N_CHOICES 4 /* * We number and name the columns of the CSV file (which are monster * attributes) in order. */ #define NAME_COLUMN 0 #define FILM_COLUMN 1 #define YEAR_COLUMN 2 #define WEAKNESS_COLUMN 3 #define DEFEATED_BY_COLUMN 4 #define RATING_COLUMN 5 /* * We use this struct to hold questions for the quiz. Each question * has a `promptFormat` (suitable for printf()) that will include a * string ("%s") that, when presented to the quizee, contains the * contents of column `promptColumn` of the correct, randomly-chosen * monster database record. The column of the attribute the question * is asking about is the `answerColumn`. */ struct Question { char *promptFormat; int promptColumn; int answerColumn; }; struct Question questions[] = { { "What was the name of the monster in the film \"%s\"?", FILM_COLUMN, NAME_COLUMN, }, { "Who defeated the monster in the film \"%s\"?", FILM_COLUMN, DEFEATED_BY_COLUMN, }, { "What was \"%s\"'s weakness?", NAME_COLUMN, WEAKNESS_COLUMN, }, { "What year was the film \"%s\" released?", FILM_COLUMN, YEAR_COLUMN, }, { "What IMDB rating did the film \"%s\" receive?", FILM_COLUMN, RATING_COLUMN, }, { "In what film was %s victorious?", DEFEATED_BY_COLUMN, FILM_COLUMN, }, { /* Alhough "year" is an int, we'll turn it into a string for this. */ "Which of the following monsters first appeared on the screen in %s?", YEAR_COLUMN, NAME_COLUMN, }, /* add more questions here, if you like */ { /* * A question with a zero-length `promptFormat` ends the array * of `Questions`. */ "" } }; int foundColumnName(FILE *f_p, char columnName[], char followedBy) /* helper function used to read an expected column name */ { /* * ASSIGNMENT * * Modify your foundColumnName() code from the previous homework, * changing scanf() -> fscanf() and getchar() -> getc(), * incorporating `f_p` into both. */ } int scanCsvHeader(FILE *f_p) /* scans a CSV-compatible "monster" header for compatibility */ { /* * ASSIGNMENT * * Modify your scanCsvHeader() from the previous homework, * changing all calls to foundColumnName() to pass `f_p`. */ } int scanCsvRow(FILE *f_p, struct Monster *monster) /* reads a Monster row from a CSV file */ { /* * ASSIGNMENT * * Modify your scanCsvRow() code from the previous homework, * changing scanf() -> fscanf() and getchar() -> getc(), * incorporating `f_p`. */ } int loadDatabase(char fileName[]) /* reads a CSV monster database from a file, returns the number of members */ { /* * ASSIGNMENT * * Implement this pseudocode: * * call fopen() to open fileName for reading, let `f_p` be the FILE pointer * if `f_p` is NULL, * return 0 (the open did not succeed) * call svnCsvHeader() using `f_p` and return 0 if it returns 0 * set `count` to 0 * loop * call scanCsvRow() to read `monsters[count]` * if not successful, * break out of the loop * increment `count` * set the name of `monsters[count]` to "" * return `count` */ } void monster_stringAttribute(struct Monster monster, int column, char result[128]) /* converts the attribute in `column` to a string `result` */ { /* * This function takes the attribute specified by `column` and, if * it's a string attribute, copies it to `result[]`. If it's not * a string attribute, it converts it to one and copies it to * `result[]` (using sprintf()). */ switch (column) { case NAME_COLUMN: strcpy(result, monster.name); break; case FILM_COLUMN: strcpy(result, monster.film); break; case YEAR_COLUMN: sprintf(result, "%d", monster.year); break; case WEAKNESS_COLUMN: strcpy(result, monster.weakness); break; case DEFEATED_BY_COLUMN: strcpy(result, monster.defeatedBy); break; case RATING_COLUMN: sprintf(result, "%4.1f", monster.rating); break; default: assert(0); /* shouldn't be reached */ } } int chooseOneOf(int n) /* returns uniformly-chosen random value r such that 0 <= r < `n` */ { return rand() % n; /* MinGW doesn't have random(), so we must use rand() */ } void scramble(int a[], int n) /* randomly swap the `n` elements of `a[]` */ { int i, j, k, swap; for (i = 0; i < 20*n; i++) { /* ought to be enough */ j = chooseOneOf(n); k = chooseOneOf(n); swap = a[j]; a[j] = a[k]; a[k] = swap; } } void selectAnswers(int choices[], int iCorrect, int nPossible) /* fill in an array of unique `choices`, one of which is `iCorrect` */ { int iFound, nFound, iPossible; nFound = 0; choices[nFound++] = iCorrect; while (nFound < N_CHOICES) { iPossible = chooseOneOf(nPossible); for (iFound = 0; iFound < nFound; iFound++) { if (choices[iFound] == iPossible) break; } if (iFound == nFound) choices[nFound++] = iPossible; } /* * Mix up the choices */ scramble(choices, N_CHOICES); } char chr(int i) /* return the `i`th lower case letter in the alphabet, with 0 -> 'a' */ { return 'a' + i; } int ord(char c) /* return the order of the lower case letter `c` in the alphabet, with 'a' -> 0 */ { return c - 'a'; } void printPrompt(int iQuestion, int iMonster, int choices[N_CHOICES]) /* print the prompt based on `question` about monster `iMonster` with `choices[]` */ { /* * ASSIGNMENT * * Implement the following pseudocode: * * call monster_stringAttribute() to fill in a buffer with that * attribute of the monster (see the function for details) * call printf() to output the question number `iQuestion+1` * (The quizee will expect the questions to start at 1). * call printf() to output `question.promptFormat` with the buffer * you filled in earlier * call putchar() twice to output a coyple of newlines * for each choice `i`, * let `choice` be the ith element of `choices[]` * let `monster` be the`choice`th monster in `monsters[]` * call monster_stringAttribute() to fill in a buffer with * `monster`'s `question.answerColumn`'th attribute * call printf() to output `chr(i)` and the attribute value buffer * on one line */ } int askQuestion(int iQuestion) // ask question `iQuestion`, return true/false if answer is correct/incorrect { /* * ASSIGNMENT * * Implement the following pseudocode: * * call chooseOneOf() to set `iMonster` to a value between 0 and * `nMonsters` * let `monster` be the `iMonster`th monster * call selectAnswers() to fill in an array of N_CHOICES * choices[], including `iMonster` * call printPrompt() to print the prompt for `iQuestion` and * `iMonster` [* comment omitted here *] * loop forever: * call printf() to prompt for an answer * call fgets() to read a line into a buffer on standard input * let `answer` be ord() of the first character in the buffer * if `answer` is between 0 and N_CHOICES - 1, inclusive, * break out of the loop * call printf() to remind the user to enter a value in the * correct range * return true if `choices[answer]` is `iMonster`, false otherwise */ } void quiz(void) /* run the quiz based on the `monsters` array */ { int i; int score, count; count = 0; score = 0; for (i = 0; questions[i].promptFormat[0] != '\0'; i++) { if (askQuestion(i)) { printf("That is correct.\n"); score++; } else { printf("That is incorrect.\n"); } count++; printf("\n"); } printf("You scored %d out of %d possible.\n", score, count); } int main(void) { /* MinGW doesn't have srandom(), so we must use srand() */ srand(time(NULL)); nMonsters = loadDatabase("monsters.csv"); if (nMonsters > 0) quiz(); return 0; }
Implement the pseudocode described in the program comments. You will need code from Lab 9 (Structures) and Homework 6 (The Monster Database). Those parts of the code that require modification are marked "ASSIGNMENT".
Compile your monsterquiz as usual.
Download monsters.csv from the same course web link. (If you have your own version from Homework 6, that should work, too.)
Run the following:
$ monsterquiz
and make sure that the quiz is working:
Turn in monsterquiz.c via email, optionally with your own monsters.csv.