From 9e9c1b21569faeabd33716e4153a881e2eed7134 Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Sun, 1 Mar 2026 17:45:00 +0100 Subject: Separate quiz logic from main function fo dedicated module Signed-off-by: Filip Wandzio --- src/quiz.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/quiz.c (limited to 'src/quiz.c') 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 @@ +#include "../include/quiz.h" +#include "../include/utils.h" +#include +#include +#include +#include +#include +#include +#include + +#define INPUT_BUFFER_SIZE 64 +#define QUIZ_DATA_DIRECTORY "data" +#define QUESTIONS_FILE_NAME "questions.txt" +#define RESULTS_FILE_NAME "results.csv" +#define CSV_TIMESTAMP_BUFFER 32 +#define FILE_PATH_BUFFER 256 +#define PERMISSIONS_DATA_DIRECTORY 0755 +#define TIME_UNLIMITED 0 +#define BASE_VALUE 10 + +static const char* START_PROMPT = "Press ENTER to start..."; +static const char* DONE_PROMPT = "Press ENTER when done..."; +static const char* TIME_LIMIT_PROMPT = "Time limit (sec, 0 = unlimited): "; +static const char* CORRECT_PROMPT = "Correct? (y/n): "; +static const char* NEXT_QUESTION_PROMPT = "Next? (y/n): "; +static const char* TIME_EXCEEDED_MESSAGE = "Time exceeded!\n"; +static const char* CSV_OPEN_ERROR_MESSAGE = "CSV open"; +static const char* QUESTIONS_LOAD_ERROR = "Error: cannot load questions file\n"; +static const char* DATA_DIR_ERROR = "Error: cannot create data directory\n"; + +static bool ensure_data_directory_exists(void) +{ + struct stat st = {0}; + if (stat(QUIZ_DATA_DIRECTORY, &st) == -1) { +#ifdef _WIN32 + return _mkdir(QUIZ_DATA_DIRECTORY) == 0; +#else + return mkdir(QUIZ_DATA_DIRECTORY, PERMISSIONS_DATA_DIRECTORY) + == 0; +#endif + } + return true; +} + +static int read_time_limit(void) +{ + char buffer[INPUT_BUFFER_SIZE]; + printf("%s", TIME_LIMIT_PROMPT); + + if (!fgets(buffer, sizeof buffer, stdin)) + return TIME_UNLIMITED; + + errno = 0; + char* end = NULL; + long value = strtol(buffer, &end, BASE_VALUE); + + if (errno == ERANGE || end == buffer) + return TIME_UNLIMITED; + if (value < 0) + value = 0; + if (value > INT_MAX) + value = INT_MAX; + + return (int)value; +} + +static void write_csv_result(FILE* csv, + const char* timestamp, + const char* general_q, + const char* major_q, + const bool correct_answer, + const double duration) +{ + static const char* CSV_FORMAT = "\"%s\",\"%s\",\"%s\",%c,%.0f\n"; + fprintf(csv, + CSV_FORMAT, + timestamp, + general_q, + major_q, + correct_answer ? 'y' : 'n', + duration); +} + +void print_score(const size_t correct, size_t total) +{ + double percentage = 0.0; + if (total != 0) + percentage = (double)correct / total * PERCENT_MULTIPLIER; + + printf(SCORE_FORMAT, correct, total, percentage); +} + +void print_final_score(const QuizSession* session) +{ + double final_percentage = 0.0; + if (session->total_answered > 0) + final_percentage = (double)session->total_correct + / session->total_answered + * PERCENT_MULTIPLIER; + + printf("\nSession score: %.1f%%\n", final_percentage); +} + +// ------------------------ +// Initialization & Cleanup +// ------------------------ +bool initialize_quiz_session(QuizSession* session) +{ + session->seed = (unsigned int)time(NULL); + srand(session->seed); + + if (!ensure_data_directory_exists()) { + fprintf(stderr, "%s", DATA_DIR_ERROR); + return false; + } + + char questions_path[FILE_PATH_BUFFER]; + snprintf(questions_path, + sizeof questions_path, + "%s/%s", + QUIZ_DATA_DIRECTORY, + QUESTIONS_FILE_NAME); + + char results_path[FILE_PATH_BUFFER]; + snprintf(results_path, + sizeof results_path, + "%s/%s", + QUIZ_DATA_DIRECTORY, + RESULTS_FILE_NAME); + + if (load_questions(questions_path, &session->questions) + != EXIT_SUCCESS) { + fprintf(stderr, "%s", QUESTIONS_LOAD_ERROR); + return false; + } + + session->csv = fopen(results_path, "a"); + if (!session->csv) { + perror(CSV_OPEN_ERROR_MESSAGE); + free_questions(&session->questions); + return false; + } + + session->total_answered = 0; + session->total_correct = 0; + session->time_limit = read_time_limit(); + + return true; +} + +void cleanup_session(QuizSession* session) +{ + fclose(session->csv); + free_questions(&session->questions); +} + +// ------------------------ +// Single Quiz Iteration +// ------------------------ +bool quiz_iteration(QuizSession* session) +{ + const size_t general_index = get_random_question_index( + &session->seed, session->questions.general_count); + const size_t major_index = get_random_question_index( + &session->seed, session->questions.major_count); + + const char* general_question = + session->questions.general[general_index]; + const char* major_question = session->questions.major[major_index]; + + print_line(); + printf( + "GENERAL:\n%s\n\nMAJOR:\n%s\n", general_question, major_question); + print_line(); + + wait_for_enter(START_PROMPT); + const time_t start_time = time(NULL); + + wait_for_enter(DONE_PROMPT); + const double duration_seconds = difftime(time(NULL), start_time); + + if (session->time_limit > 0 && duration_seconds > session->time_limit) + printf("%s", TIME_EXCEEDED_MESSAGE); + + const bool correct = ask_yes_no(CORRECT_PROMPT); + if (correct) + session->total_correct++; + session->total_answered++; + + char timestamp[CSV_TIMESTAMP_BUFFER]; + get_answer_timestamp(timestamp, sizeof timestamp); + + write_csv_result(session->csv, + timestamp, + general_question, + major_question, + correct, + duration_seconds); + + print_score(session->total_correct, session->total_answered); + + return ask_yes_no(NEXT_QUESTION_PROMPT); +} -- cgit v1.2.3