3
\$\begingroup\$

Just looking for more experienced programmers opinions on this code. It's a framework for making simple multiple-choice quizzes. I am aware that there is the potential to make the arrays dynamic, but at small sizes this shouldn't matter much.
The formatting of the .txt files used to compose quizzes is simple- start a line with q for question, a for incorrect answer and r for the right answer, meaning almost anyone can easily compose quizzes. As noted in the code, the current form also means that you have to start the quiz with an endline ("enter"), and that long questions should have spaces on all subsequent lines.

#include<stdio.h>
#include<stdlib.h>
///IMPORTANT! Always start the quiz with a new-line, or won't be saved; alternatively I could just add separate code for start of file.
///long questions are problematic; at moment, Get around by insisting on space? could choose to go down the special-character route. 
///This version a-b-c labeling has to be done by the quiz author
char text[10000];
int answers[300]; 
int ansamount[300];
int startpoint[300];
int end;
int convert(char a)
{
    switch (a)
    {
    case 'a':
    case 'A':
        return 1;
    case'b':
    case 'B':
        return 2;
    case 'c':
    case 'C':
        return 3;
    case 'd':
    case 'D':
        return 4;
    case 'e':
    case 'E':
        return 5;///up to five
    default:
        return 100;
    }
}
int quiz()
{
    int print=0,run=1,asked=1,wrong=0;///asked will be adapted if randomisation is added as feature
    char choice='e';
    while (run==1)
    {
        while (print<startpoint[asked+1])///+1 as using new questions start point. so question one uses 2's start-point as end
        {
            printf("%c",text[print]);
            print++;
        }
        choice=getchar();
        while (convert(choice)>ansamount[asked])///make sure right
        {
            choice=getchar();///get rid of endline
            printf("Invalid choice. Please choose again\n");
            choice=getchar();
        }
        if (convert(choice)!=answers[asked])///currently no mechanism to stop invalid choice from being 'wrong'. latter add an answer-telling facility
        {
            printf("You got the answer wrong!\n");
            wrong++;
        }
        choice=getchar();///remove the end-line.
        printf("You have got %d out of %d questions right.\n\n",asked-wrong,asked);
        asked++;
        if (asked==end)
        {
            return 0;
        }
    }

}
int load()
{
    char filename [25];///can do dynamic, just prefer this for ease-of code for now
    int temp=0,position=0,run=1,question=1, answer=1;///starting from question 1, as that's how I feel like doing it.
    FILE *fp;
    printf("Welcome to Console . Please choose the quiz you wish to take.\n\n");///eventually, add list
    while (temp<19)///why stop at new line?
    {
        filename[temp]=getchar();
        if (filename[temp]=='\n')
        {
            filename[temp]='.';///all files have to be .txt
            filename[temp+1]='t';
            filename[temp+2]='x';
            filename[temp+3]='t';
            filename[temp+4]='\0';
            break;
        }
        temp++;
    }

    fp=fopen(filename,"r");///alternative using %s? check st.ov. again. Add error check
    if (fp==NULL)
    {
        printf("Disaster! The file doesn't open! Please try again, or choose a different file \n");
        return 0;
    }
    while (run==1)
    {
        text[position]=getc(fp);
        if (text[position]==EOF)///loading finished
        {
            ansamount[question-1]=answer;///for last question
            text[position]='\n';///to make sure at least on new line for input, regardless of what quiz-writer did.
            end=question;///so that quiz()knows to end.
            startpoint[question]=position;///so that can priint till there
            return 1;///sucessful load, breaks do-while loop;
        }
        if (text[position]=='\n')
        {
            while (text[position]=='\n')///solves multiple spaces.
            {
                position++;
                text[position]=getc(fp);
                {
                    if (text[position]=='Q'||text[position]=='q')///could do switch, choose a 3 way if
                    {
                        startpoint[question]=position;///as start from after the 'q'
                        ansamount[question-1]=answer;///-1 as answer is what last question was up to, as this just start now. also had to add to eof, since only does at start of new question
                        question++;///so next question is saved in right place. thats it. answer saved should before q-1.
                        answer=1;///resets for next question
                    }
                    else if (text[position]=='a'||text[position]=='A')
                    {
                        answer++;///so that we know what number the correct answer is
                    }
                    else if (text[position]=='r'||text[position]=='R')/// else to prevent the position -- causing probs, even though end line does that.
                    {
                        answers [question-1]=answer;///answer is saved for question before, as there was++ at Q
                    }

                }

            }
        }
        else
        {
            position++;///if A Q or R, is overwritten by loop, so doesn't appear in final .
        }
    }
}
main()
{
    int run=1;
    while (run==1)
    {
        int success=0;
        do
        {
            success=load();
        }
        while (success==0);///do while to keep on re-running load
          ///debug
        if (quiz()==0)
        {
            printf("To exit, enter 'e'. To take another quiz, choose any other letter\n\n");
            int temp=getchar();
            while (temp=='\n')
            {
                temp=getchar();
            }
            if (temp=='e'||temp=='E')///has to be here, or else eaten. change on other
            {
                return 0;

            }
            temp=getchar(); /// newline clearup for new quiz


        }

    }


}
\$\endgroup\$
1
\$\begingroup\$

Documentation and Quiz Layout

The first issue I see with this is that there is no documentation for the program (users will not read your source code comments) and that the text file layout is a little unintuitive. For example, as a user, I would expect that a single quiz entry in a text file look something like this:

Q What is the name of the planet we're living on?
A Venus|Pluto|Mars|Earth
C Earth

where Q denotes the question, A is a pipe-separated list of possible answers, and C denotes the correct answer. The best way to provide documentation is through example (which is what I'll do now):

Q How many continents are there?
A 1|2|7|4
C 7
Q What is the name of the planet we're living on?
A Venus|Pluto|Mars|Earth
C Earth
Q What is the proper IUPAC name for table salt?
A KCl|NaCl|CO2|SO4
C NaCl

Remember that a little documentation and connecting with your audience can go a long way to making your program successful and much easier to understand. Note: I went with pipe separation here; comma separation or any other delimiter would have been fine. The reason I chose pipes is because they are typically not used in the English language so there'd be very little chance of us running into it in a user's question or answers. It would be great to document that your program also only supports 4 choices per question.

Now let's talk a little about the implementation.

Implementation

PLEASE spread the word. The only valid declaration of main is one of:

int main()
int main(int argc, char **argv)
int main(int argc, char *argv[])

Anything else is non-standard and simply WRONG. Not that that's off my chest...

We now have a well defined layout for each question and file, so we can think about the overall program design. We want to:

a) Figure out a way to accept user input (i.e. - the input file)
b) Check for any errors and report it
c) Parse the input file and store it in a sensible data structure
d) Perform some kind of work on the data structure
e) Output
f) Clean up any resources and gracefully exit.

That's the high level of what we want to do. Since we have a well-defined structure, we can define a quiz question like this:

#define NUMBER_OF_POSSIBLE_ANSWERS (4)

typedef struct QuizQuestion {
    char *answers[NUMBER_OF_POSSIBLE_ANSWERS]; /* The possible answers */
    char *qtext; /* The question itself */
    int right_answer; /* Index in member answers of the right answer. NOTE: must be >= 0 && < NUMBER_OF_POSSIBLE_ANSWERS */
} QuizQuestion ;

and the quiz itself becomes:

typedef struct Quiz {
    QuizQuestion *questions; /* Array of quiz questions */
} Quiz;

Now let's tackle IO. It's probably best for the user to pass in the file containing the quiz questions like this:

./quizprogram quiz_file.txt

So let's support that:

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "Wrong arguments! Please supply quiz file!\n");
        return EXIT_FAILURE;
    }

    FILE *file = fopen(argv[1], "r");
    if (!file) {
        fprintf(stderr, "Couldn't open %s for reading! Check the file!\n", argv[1]);
        return EXIT_FAILURE;
    }

    Quiz quiz = {.questions = NULL };

    int num_questions;
    // figure out the number of quiz questions from the file and
    // populate num_questions

    // get memory for all of the quiz questions
    quiz.questions = malloc(num_questions * sizeof(*(quiz.questions)));
    if (!quiz.questions) {
        fprintf(stderr, "Couldn't allocate memory for %d questions!\n", num_questions);
        fclose(file);
        return EXIT_FAILURE;
    }

    // read in the rest of the file and populate quiz data structure

    // ...

    // print the quiz to the screen and have user take it

    // ...

    // clean up
    free(quiz.questions);
    fclose(file);
}

Note that this isn't a complete implementation but hopefully I've given you enough food for thought and a good idea of how to lay out your program from this.

\$\endgroup\$
  • \$\begingroup\$ .Just to add, would calling on structures for the print-out be faster than an array? Also, to allow for flexible amounts of questions, I presume I'd want the quiz question structure to have something like 'answers_to_question', as currently my program will allow each question to have a different amount of options. \$\endgroup\$ – user101969 Apr 19 '16 at 20:42
  • \$\begingroup\$ I actually don't think there is any observable speed difference between the two (especially with a small input like yours). And you're correct. If you wanted to support different number of options for each question, the answers member would have to be declared as char **answers. You would then dynamically allocate room for the number of answers the question has. Either that or declare a maximum number of options a question is allowed to have and statically allocate the array: char *answers[MAX_OPTIONS_ALLOWED] where MAX_OPTIONS_ALLOWED is a macro. \$\endgroup\$ – Bizkit Apr 20 '16 at 13:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy