diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 94 | ||||
| -rw-r--r-- | src/questions.c | 160 | ||||
| -rw-r--r-- | src/quiz.c | 203 | ||||
| -rw-r--r-- | src/utils.c | 125 |
4 files changed, 403 insertions, 179 deletions
| @@ -1,83 +1,15 @@ | |||
| 1 | #include <stdio.h> | 1 | #include "../include/quiz.h" // QuizSession and functions |
| 2 | #include <stdlib.h> | ||
| 3 | #include <time.h> | ||
| 4 | #include <sys/stat.h> | ||
| 5 | #include <sys/types.h> | ||
| 6 | |||
| 7 | #include "questions.h" | ||
| 8 | #include "utils.h" | ||
| 9 | |||
| 10 | int main(void) { | ||
| 11 | srand((unsigned)time(NULL)); | ||
| 12 | |||
| 13 | // utwórz katalog data/ jeśli nie istnieje | ||
| 14 | struct stat st = {0}; | ||
| 15 | if (stat("data", &st) == -1) { | ||
| 16 | #ifdef _WIN32 | ||
| 17 | _mkdir("data"); | ||
| 18 | #else | ||
| 19 | mkdir("data", 0755); | ||
| 20 | #endif | ||
| 21 | } | ||
| 22 | |||
| 23 | QuestionSet qs; | ||
| 24 | if (load_questions("data/questions.txt", &qs) != EXIT_SUCCESS) { | ||
| 25 | fprintf(stderr, "Error: cannot load questions file\n"); | ||
| 26 | return EXIT_FAILURE; | ||
| 27 | } | ||
| 28 | |||
| 29 | FILE *csv = fopen("data/results.csv", "a"); | ||
| 30 | if (!csv) { | ||
| 31 | perror("CSV open"); | ||
| 32 | free_questions(&qs); | ||
| 33 | return EXIT_FAILURE; | ||
| 34 | } | ||
| 35 | |||
| 36 | int limit = 0; | ||
| 37 | printf("Time limit (sec, 0 = unlimited): "); | ||
| 38 | if (scanf("%d", &limit) != 1) limit = 0; | ||
| 39 | wait_enter(NULL); | ||
| 40 | |||
| 41 | size_t total = 0, correct = 0; | ||
| 42 | char cont = 'y'; | ||
| 43 | |||
| 44 | while (cont == 'y' || cont == 'Y') { | ||
| 45 | const char *g = qs.general[rand_index(qs.general_count)]; | ||
| 46 | const char *m = qs.major[rand_index(qs.major_count)]; | ||
| 47 | |||
| 48 | print_line(); | ||
| 49 | printf("GENERAL:\n%s\n\nMAJOR:\n%s\n", g, m); | ||
| 50 | print_line(); | ||
| 51 | 2 | ||
| 52 | wait_enter("Press ENTER to start..."); | 3 | #include <stdlib.h> |
| 53 | time_t start = time(NULL); | ||
| 54 | |||
| 55 | wait_enter("Press ENTER when done..."); | ||
| 56 | double duration = difftime(time(NULL), start); | ||
| 57 | |||
| 58 | if (limit > 0 && duration > limit) | ||
| 59 | printf("Time exceeded!\n"); | ||
| 60 | |||
| 61 | char ans = ask_yes_no("Correct? (y/n): "); | ||
| 62 | if (ans == 'y' || ans == 'Y') correct++; | ||
| 63 | total++; | ||
| 64 | |||
| 65 | char ts[32]; | ||
| 66 | now_str(ts, sizeof(ts)); | ||
| 67 | fprintf(csv, "\"%s\",\"%s\",\"%s\",%c,%.0f\n", ts, g, m, ans, duration); | ||
| 68 | |||
| 69 | printf("Score: %zu/%zu (%.1f%%)\n", | ||
| 70 | correct, total, | ||
| 71 | total ? (double)correct / total * 100 : 0.0); | ||
| 72 | |||
| 73 | cont = ask_yes_no("Next? (y/n): "); | ||
| 74 | } | ||
| 75 | |||
| 76 | printf("\nSession score: %.1f%%\n", | ||
| 77 | total ? (double)correct / total * 100 : 0.0); | ||
| 78 | |||
| 79 | fclose(csv); | ||
| 80 | free_questions(&qs); | ||
| 81 | 4 | ||
| 82 | return EXIT_SUCCESS; | 5 | int main(void) |
| 83 | } | 6 | { |
| 7 | QuizSession session; | ||
| 8 | if (!initialize_quiz_session(&session)) | ||
| 9 | return EXIT_FAILURE; | ||
| 10 | while (quiz_iteration(&session)) { | ||
| 11 | } | ||
| 12 | print_final_score(&session); | ||
| 13 | cleanup_session(&session); | ||
| 14 | return EXIT_SUCCESS; | ||
| 15 | } \ No newline at end of file | ||
diff --git a/src/questions.c b/src/questions.c index 88671a7..491c106 100644 --- a/src/questions.c +++ b/src/questions.c | |||
| @@ -5,88 +5,94 @@ | |||
| 5 | 5 | ||
| 6 | #define MAX_LINE 512 | 6 | #define MAX_LINE 512 |
| 7 | 7 | ||
| 8 | static int add_question(char ***arr, size_t *count, const char *text) { | 8 | static int add_question(char*** arr, size_t* count, const char* text) |
| 9 | char **tmp = realloc(*arr, (*count + 1) * sizeof(char *)); | 9 | { |
| 10 | if (!tmp) | 10 | char** tmp = realloc(*arr, (*count + 1) * sizeof(char*)); |
| 11 | return EXIT_FAILURE; | 11 | if (!tmp) |
| 12 | *arr = tmp; | 12 | return EXIT_FAILURE; |
| 13 | 13 | *arr = tmp; | |
| 14 | (*arr)[*count] = malloc(strlen(text) + 1); | 14 | |
| 15 | if (!(*arr)[*count]) | 15 | (*arr)[*count] = malloc(strlen(text) + 1); |
| 16 | return EXIT_FAILURE; | 16 | if (!(*arr)[*count]) |
| 17 | 17 | return EXIT_FAILURE; | |
| 18 | strcpy((*arr)[*count], text); | 18 | |
| 19 | (*count)++; | 19 | strcpy((*arr)[*count], text); |
| 20 | return EXIT_SUCCESS; | 20 | (*count)++; |
| 21 | return EXIT_SUCCESS; | ||
| 21 | } | 22 | } |
| 22 | 23 | ||
| 23 | int load_questions(const char *filename, QuestionSet *qs) { | 24 | int load_questions(const char* filename, QuestionSet* qs) |
| 24 | if (!filename || !qs) | 25 | { |
| 25 | return EXIT_FAILURE; | 26 | if (!filename || !qs) |
| 26 | 27 | return EXIT_FAILURE; | |
| 27 | FILE *f = fopen(filename, "r"); | 28 | |
| 28 | if (!f) | 29 | FILE* f = fopen(filename, "r"); |
| 29 | return EXIT_FAILURE; | 30 | if (!f) |
| 30 | 31 | return EXIT_FAILURE; | |
| 31 | qs->general = NULL; | 32 | |
| 32 | qs->major = NULL; | 33 | qs->general = NULL; |
| 33 | qs->general_count = 0; | 34 | qs->major = NULL; |
| 34 | qs->major_count = 0; | 35 | qs->general_count = 0; |
| 35 | 36 | qs->major_count = 0; | |
| 36 | char line[MAX_LINE]; | 37 | |
| 37 | enum { NONE, GENERAL, MAJOR } mode = NONE; | 38 | char line[MAX_LINE]; |
| 38 | 39 | enum { NONE, GENERAL, MAJOR } mode = NONE; | |
| 39 | while (fgets(line, sizeof(line), f)) { | 40 | |
| 40 | line[strcspn(line, "\n")] = '\0'; | 41 | while (fgets(line, sizeof(line), f)) { |
| 41 | if (line[0] == '\0') | 42 | line[strcspn(line, "\n")] = '\0'; |
| 42 | continue; | 43 | if (line[0] == '\0') |
| 43 | 44 | continue; | |
| 44 | if (strcmp(line, "#GENERAL") == 0) { | 45 | |
| 45 | mode = GENERAL; | 46 | if (strcmp(line, "#GENERAL") == 0) { |
| 46 | continue; | 47 | mode = GENERAL; |
| 47 | } | 48 | continue; |
| 48 | if (strcmp(line, "#MAJOR") == 0) { | 49 | } |
| 49 | mode = MAJOR; | 50 | if (strcmp(line, "#MAJOR") == 0) { |
| 50 | continue; | 51 | mode = MAJOR; |
| 51 | } | 52 | continue; |
| 52 | 53 | } | |
| 53 | int res = EXIT_FAILURE; | 54 | |
| 54 | switch (mode) { | 55 | int res = EXIT_FAILURE; |
| 55 | case GENERAL: | 56 | switch (mode) { |
| 56 | res = add_question(&qs->general, &qs->general_count, line); | 57 | case GENERAL: |
| 57 | break; | 58 | res = add_question( |
| 58 | case MAJOR: | 59 | &qs->general, &qs->general_count, line); |
| 59 | res = add_question(&qs->major, &qs->major_count, line); | 60 | break; |
| 60 | break; | 61 | case MAJOR: |
| 61 | default: | 62 | res = add_question( |
| 62 | continue; // ignore lines before section | 63 | &qs->major, &qs->major_count, line); |
| 63 | } | 64 | break; |
| 64 | 65 | default: | |
| 65 | if (res != EXIT_SUCCESS) { | 66 | continue; |
| 66 | fclose(f); | 67 | } |
| 67 | free_questions(qs); | 68 | |
| 68 | return EXIT_FAILURE; | 69 | if (res != EXIT_SUCCESS) { |
| 69 | } | 70 | fclose(f); |
| 70 | } | 71 | free_questions(qs); |
| 71 | 72 | return EXIT_FAILURE; | |
| 72 | fclose(f); | 73 | } |
| 73 | return (qs->general_count && qs->major_count) ? EXIT_SUCCESS : EXIT_FAILURE; | 74 | } |
| 75 | |||
| 76 | fclose(f); | ||
| 77 | return (qs->general_count && qs->major_count) ? EXIT_SUCCESS | ||
| 78 | : EXIT_FAILURE; | ||
| 74 | } | 79 | } |
| 75 | 80 | ||
| 76 | void free_questions(QuestionSet *qs) { | 81 | void free_questions(QuestionSet* qs) |
| 77 | if (!qs) | 82 | { |
| 78 | return; | 83 | if (!qs) |
| 84 | return; | ||
| 79 | 85 | ||
| 80 | for (size_t i = 0; i < qs->general_count; i++) | 86 | for (size_t i = 0; i < qs->general_count; i++) |
| 81 | free(qs->general[i]); | 87 | free(qs->general[i]); |
| 82 | for (size_t i = 0; i < qs->major_count; i++) | 88 | for (size_t i = 0; i < qs->major_count; i++) |
| 83 | free(qs->major[i]); | 89 | free(qs->major[i]); |
| 84 | 90 | ||
| 85 | free(qs->general); | 91 | free(qs->general); |
| 86 | free(qs->major); | 92 | free(qs->major); |
| 87 | 93 | ||
| 88 | qs->general = NULL; | 94 | qs->general = NULL; |
| 89 | qs->major = NULL; | 95 | qs->major = NULL; |
| 90 | qs->general_count = 0; | 96 | qs->general_count = 0; |
| 91 | qs->major_count = 0; | 97 | qs->major_count = 0; |
| 92 | } | 98 | } |
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 | } | ||
diff --git a/src/utils.c b/src/utils.c index c139929..2e26e8e 100644 --- a/src/utils.c +++ b/src/utils.c | |||
| @@ -1,34 +1,117 @@ | |||
| 1 | #include "utils.h" | 1 | #include "utils.h" |
| 2 | #include <stdbool.h> | ||
| 2 | #include <stdio.h> | 3 | #include <stdio.h> |
| 3 | #include <stdlib.h> | 4 | #include <stdlib.h> |
| 4 | #include <time.h> | 5 | #include <time.h> |
| 5 | 6 | ||
| 6 | void print_line(void) { | 7 | #define SEPARATOR_CHAR '-' |
| 7 | printf("--------------------------------------------------\n"); | 8 | #define SEPARATOR_WIDTH 50 |
| 9 | #define NEWLINE_CHAR '\n' | ||
| 10 | #define INPUT_BUFFER_SIZE 16 | ||
| 11 | #define TIME_FORMAT_STRING "%Y-%m-%d %H:%M:%S" | ||
| 12 | #define ASK_YES_NO_PROMPT "Please enter 'y' or 'n': " | ||
| 13 | |||
| 14 | /** Clear leftover input from stdin */ | ||
| 15 | static void flush_stdin(void) | ||
| 16 | { | ||
| 17 | int input_char; | ||
| 18 | do | ||
| 19 | input_char = getchar(); | ||
| 20 | while (input_char != NEWLINE_CHAR && input_char != EOF); | ||
| 21 | } | ||
| 22 | |||
| 23 | /** Print a horizontal separator line for the quiz UI */ | ||
| 24 | static void print_separator_line(void) | ||
| 25 | { | ||
| 26 | for (size_t current_column = 0; current_column < SEPARATOR_WIDTH; | ||
| 27 | ++current_column) | ||
| 28 | putchar(SEPARATOR_CHAR); | ||
| 29 | |||
| 30 | putchar(NEWLINE_CHAR); | ||
| 8 | } | 31 | } |
| 9 | 32 | ||
| 10 | void wait_enter(const char *msg) { | 33 | /** Print a horizontal separator line */ |
| 11 | if (msg) | 34 | void print_line(void) |
| 12 | printf("%s", msg); | 35 | { |
| 13 | int c; | 36 | print_separator_line(); |
| 14 | while ((c = getchar()) != '\n' && c != EOF) | ||
| 15 | ; | ||
| 16 | } | 37 | } |
| 17 | 38 | ||
| 18 | char ask_yes_no(const char *msg) { | 39 | /** |
| 19 | char c = 'n'; | 40 | * Pause until the user presses ENTER |
| 20 | printf("%s", msg); | 41 | * |
| 21 | if (scanf(" %c", &c) != 1) | 42 | * @param prompt Optional message to display before waiting |
| 22 | c = 'n'; | 43 | */ |
| 23 | int flush; | 44 | void wait_for_enter(const char* prompt) |
| 24 | while ((flush = getchar()) != '\n' && flush != EOF) | 45 | { |
| 25 | ; | 46 | if (prompt != NULL) |
| 26 | return c; | 47 | fputs(prompt, stdout); |
| 48 | |||
| 49 | flush_stdin(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Ask a yes/no question and return a boolean answer | ||
| 54 | * | ||
| 55 | * @param prompt Question message to display | ||
| 56 | * @return true if user answered 'y' or 'Y', false if 'n', default false | ||
| 57 | */ | ||
| 58 | bool ask_yes_no(const char* prompt) | ||
| 59 | { | ||
| 60 | char input_line[INPUT_BUFFER_SIZE]; | ||
| 61 | |||
| 62 | while (1) { | ||
| 63 | if (prompt != NULL) | ||
| 64 | fputs(prompt, stdout); | ||
| 65 | |||
| 66 | if (fgets(input_line, sizeof(input_line), stdin) == NULL) | ||
| 67 | return false; | ||
| 68 | |||
| 69 | const char first_char = input_line[0]; | ||
| 70 | |||
| 71 | if (first_char == 'y' || first_char == 'Y') | ||
| 72 | return true; | ||
| 73 | |||
| 74 | if (first_char == 'n' || first_char == 'N') | ||
| 75 | return false; | ||
| 76 | |||
| 77 | fputs(ASK_YES_NO_PROMPT, stdout); | ||
| 78 | } | ||
| 27 | } | 79 | } |
| 28 | 80 | ||
| 29 | void now_str(char *buf, size_t size) { | 81 | /** |
| 30 | time_t t = time(NULL); | 82 | * Get the current timestamp for recording when a question was answered |
| 31 | strftime(buf, size, "%Y-%m-%d %H:%M:%S", localtime(&t)); | 83 | * |
| 84 | * @param answer_time_buffer Buffer to store the formatted timestamp | ||
| 85 | * @param buffer_capacity Size of the buffer in bytes | ||
| 86 | */ | ||
| 87 | void get_answer_timestamp(char* answer_time_buffer, | ||
| 88 | const size_t buffer_capacity) | ||
| 89 | { | ||
| 90 | if (answer_time_buffer == NULL || buffer_capacity == 0) | ||
| 91 | return; | ||
| 92 | |||
| 93 | const time_t current_time = time(NULL); | ||
| 94 | const struct tm* local_time = localtime(¤t_time); | ||
| 95 | if (local_time != NULL) | ||
| 96 | strftime(answer_time_buffer, | ||
| 97 | buffer_capacity, | ||
| 98 | TIME_FORMAT_STRING, | ||
| 99 | local_time); | ||
| 32 | } | 100 | } |
| 33 | 101 | ||
| 34 | size_t rand_index(size_t max) { return max ? (size_t)(rand() % max) : 0; } | 102 | /** |
| 103 | * Get a random question index in the quiz (thread-safe) | ||
| 104 | * | ||
| 105 | * @param seed Pointer to an unsigned int seed (per-thread) | ||
| 106 | * @param total_questions Number of questions in the current quiz session | ||
| 107 | * @return Random index in the range [0, total_questions-1] | ||
| 108 | */ | ||
| 109 | size_t get_random_question_index(unsigned int* seed, | ||
| 110 | const size_t total_questions) | ||
| 111 | { | ||
| 112 | if (total_questions == 0 || seed == NULL) | ||
| 113 | return 0; | ||
| 114 | |||
| 115 | const unsigned long random_value = (unsigned long)rand_r(seed); | ||
| 116 | return random_value % total_questions; | ||
| 117 | } | ||
