diff options
Diffstat (limited to '')
| -rw-r--r-- | src/quiz.c | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/src/quiz.c b/src/quiz.c new file mode 100644 index 0000000..7bc31b2 --- /dev/null +++ b/src/quiz.c | |||
| @@ -0,0 +1,203 @@ | |||
| 1 | #include "../include/quiz.h" | ||
| 2 | #include "../include/utils.h" | ||
| 3 | #include <errno.h> | ||
| 4 | #include <limits.h> | ||
| 5 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | #include <sys/stat.h> | ||
| 8 | #include <sys/types.h> | ||
| 9 | #include <time.h> | ||
| 10 | |||
| 11 | #define INPUT_BUFFER_SIZE 64 | ||
| 12 | #define QUIZ_DATA_DIRECTORY "data" | ||
| 13 | #define QUESTIONS_FILE_NAME "questions.txt" | ||
| 14 | #define RESULTS_FILE_NAME "results.csv" | ||
| 15 | #define CSV_TIMESTAMP_BUFFER 32 | ||
| 16 | #define FILE_PATH_BUFFER 256 | ||
| 17 | #define PERMISSIONS_DATA_DIRECTORY 0755 | ||
| 18 | #define TIME_UNLIMITED 0 | ||
| 19 | #define BASE_VALUE 10 | ||
| 20 | |||
| 21 | static const char* START_PROMPT = "Press ENTER to start..."; | ||
| 22 | static const char* DONE_PROMPT = "Press ENTER when done..."; | ||
| 23 | static const char* TIME_LIMIT_PROMPT = "Time limit (sec, 0 = unlimited): "; | ||
| 24 | static const char* CORRECT_PROMPT = "Correct? (y/n): "; | ||
| 25 | static const char* NEXT_QUESTION_PROMPT = "Next? (y/n): "; | ||
| 26 | static const char* TIME_EXCEEDED_MESSAGE = "Time exceeded!\n"; | ||
| 27 | static const char* CSV_OPEN_ERROR_MESSAGE = "CSV open"; | ||
| 28 | static const char* QUESTIONS_LOAD_ERROR = "Error: cannot load questions file\n"; | ||
| 29 | static const char* DATA_DIR_ERROR = "Error: cannot create data directory\n"; | ||
| 30 | |||
| 31 | static bool ensure_data_directory_exists(void) | ||
| 32 | { | ||
| 33 | struct stat st = {0}; | ||
| 34 | if (stat(QUIZ_DATA_DIRECTORY, &st) == -1) { | ||
| 35 | #ifdef _WIN32 | ||
| 36 | return _mkdir(QUIZ_DATA_DIRECTORY) == 0; | ||
| 37 | #else | ||
| 38 | return mkdir(QUIZ_DATA_DIRECTORY, PERMISSIONS_DATA_DIRECTORY) | ||
| 39 | == 0; | ||
| 40 | #endif | ||
| 41 | } | ||
| 42 | return true; | ||
| 43 | } | ||
| 44 | |||
| 45 | static int read_time_limit(void) | ||
| 46 | { | ||
| 47 | char buffer[INPUT_BUFFER_SIZE]; | ||
| 48 | printf("%s", TIME_LIMIT_PROMPT); | ||
| 49 | |||
| 50 | if (!fgets(buffer, sizeof buffer, stdin)) | ||
| 51 | return TIME_UNLIMITED; | ||
| 52 | |||
| 53 | errno = 0; | ||
| 54 | char* end = NULL; | ||
| 55 | long value = strtol(buffer, &end, BASE_VALUE); | ||
| 56 | |||
| 57 | if (errno == ERANGE || end == buffer) | ||
| 58 | return TIME_UNLIMITED; | ||
| 59 | if (value < 0) | ||
| 60 | value = 0; | ||
| 61 | if (value > INT_MAX) | ||
| 62 | value = INT_MAX; | ||
| 63 | |||
| 64 | return (int)value; | ||
| 65 | } | ||
| 66 | |||
| 67 | static void write_csv_result(FILE* csv, | ||
| 68 | const char* timestamp, | ||
| 69 | const char* general_q, | ||
| 70 | const char* major_q, | ||
| 71 | const bool correct_answer, | ||
| 72 | const double duration) | ||
| 73 | { | ||
| 74 | static const char* CSV_FORMAT = "\"%s\",\"%s\",\"%s\",%c,%.0f\n"; | ||
| 75 | fprintf(csv, | ||
| 76 | CSV_FORMAT, | ||
| 77 | timestamp, | ||
| 78 | general_q, | ||
| 79 | major_q, | ||
| 80 | correct_answer ? 'y' : 'n', | ||
| 81 | duration); | ||
| 82 | } | ||
| 83 | |||
| 84 | void print_score(const size_t correct, size_t total) | ||
| 85 | { | ||
| 86 | double percentage = 0.0; | ||
| 87 | if (total != 0) | ||
| 88 | percentage = (double)correct / total * PERCENT_MULTIPLIER; | ||
| 89 | |||
| 90 | printf(SCORE_FORMAT, correct, total, percentage); | ||
| 91 | } | ||
| 92 | |||
| 93 | void print_final_score(const QuizSession* session) | ||
| 94 | { | ||
| 95 | double final_percentage = 0.0; | ||
| 96 | if (session->total_answered > 0) | ||
| 97 | final_percentage = (double)session->total_correct | ||
| 98 | / session->total_answered | ||
| 99 | * PERCENT_MULTIPLIER; | ||
| 100 | |||
| 101 | printf("\nSession score: %.1f%%\n", final_percentage); | ||
| 102 | } | ||
| 103 | |||
| 104 | // ------------------------ | ||
| 105 | // Initialization & Cleanup | ||
| 106 | // ------------------------ | ||
| 107 | bool initialize_quiz_session(QuizSession* session) | ||
| 108 | { | ||
| 109 | session->seed = (unsigned int)time(NULL); | ||
| 110 | srand(session->seed); | ||
| 111 | |||
| 112 | if (!ensure_data_directory_exists()) { | ||
| 113 | fprintf(stderr, "%s", DATA_DIR_ERROR); | ||
| 114 | return false; | ||
| 115 | } | ||
| 116 | |||
| 117 | char questions_path[FILE_PATH_BUFFER]; | ||
| 118 | snprintf(questions_path, | ||
| 119 | sizeof questions_path, | ||
| 120 | "%s/%s", | ||
| 121 | QUIZ_DATA_DIRECTORY, | ||
| 122 | QUESTIONS_FILE_NAME); | ||
| 123 | |||
| 124 | char results_path[FILE_PATH_BUFFER]; | ||
| 125 | snprintf(results_path, | ||
| 126 | sizeof results_path, | ||
| 127 | "%s/%s", | ||
| 128 | QUIZ_DATA_DIRECTORY, | ||
| 129 | RESULTS_FILE_NAME); | ||
| 130 | |||
| 131 | if (load_questions(questions_path, &session->questions) | ||
| 132 | != EXIT_SUCCESS) { | ||
| 133 | fprintf(stderr, "%s", QUESTIONS_LOAD_ERROR); | ||
| 134 | return false; | ||
| 135 | } | ||
| 136 | |||
| 137 | session->csv = fopen(results_path, "a"); | ||
| 138 | if (!session->csv) { | ||
| 139 | perror(CSV_OPEN_ERROR_MESSAGE); | ||
| 140 | free_questions(&session->questions); | ||
| 141 | return false; | ||
| 142 | } | ||
| 143 | |||
| 144 | session->total_answered = 0; | ||
| 145 | session->total_correct = 0; | ||
| 146 | session->time_limit = read_time_limit(); | ||
| 147 | |||
| 148 | return true; | ||
| 149 | } | ||
| 150 | |||
| 151 | void cleanup_session(QuizSession* session) | ||
| 152 | { | ||
| 153 | fclose(session->csv); | ||
| 154 | free_questions(&session->questions); | ||
| 155 | } | ||
| 156 | |||
| 157 | // ------------------------ | ||
| 158 | // Single Quiz Iteration | ||
| 159 | // ------------------------ | ||
| 160 | bool quiz_iteration(QuizSession* session) | ||
| 161 | { | ||
| 162 | const size_t general_index = get_random_question_index( | ||
| 163 | &session->seed, session->questions.general_count); | ||
| 164 | const size_t major_index = get_random_question_index( | ||
| 165 | &session->seed, session->questions.major_count); | ||
| 166 | |||
| 167 | const char* general_question = | ||
| 168 | session->questions.general[general_index]; | ||
| 169 | const char* major_question = session->questions.major[major_index]; | ||
| 170 | |||
| 171 | print_line(); | ||
| 172 | printf( | ||
| 173 | "GENERAL:\n%s\n\nMAJOR:\n%s\n", general_question, major_question); | ||
| 174 | print_line(); | ||
| 175 | |||
| 176 | wait_for_enter(START_PROMPT); | ||
| 177 | const time_t start_time = time(NULL); | ||
| 178 | |||
| 179 | wait_for_enter(DONE_PROMPT); | ||
| 180 | const double duration_seconds = difftime(time(NULL), start_time); | ||
| 181 | |||
| 182 | if (session->time_limit > 0 && duration_seconds > session->time_limit) | ||
| 183 | printf("%s", TIME_EXCEEDED_MESSAGE); | ||
| 184 | |||
| 185 | const bool correct = ask_yes_no(CORRECT_PROMPT); | ||
| 186 | if (correct) | ||
| 187 | session->total_correct++; | ||
| 188 | session->total_answered++; | ||
| 189 | |||
| 190 | char timestamp[CSV_TIMESTAMP_BUFFER]; | ||
| 191 | get_answer_timestamp(timestamp, sizeof timestamp); | ||
| 192 | |||
| 193 | write_csv_result(session->csv, | ||
| 194 | timestamp, | ||
| 195 | general_question, | ||
| 196 | major_question, | ||
| 197 | correct, | ||
| 198 | duration_seconds); | ||
| 199 | |||
| 200 | print_score(session->total_correct, session->total_answered); | ||
| 201 | |||
| 202 | return ask_yes_no(NEXT_QUESTION_PROMPT); | ||
| 203 | } | ||
