summaryrefslogtreecommitdiffstats
path: root/src/quiz.c
diff options
context:
space:
mode:
authorFilip Wandzio <contact@philw.dev>2026-03-01 17:45:00 +0100
committerFilip Wandzio <contact@philw.dev>2026-03-01 17:45:00 +0100
commit9e9c1b21569faeabd33716e4153a881e2eed7134 (patch)
treef3a7ad21aed4b1c4f51c3ee308ffef88430deafc /src/quiz.c
parent57b077a4788b7fb5ed6add1df4ba3f15c9e6349b (diff)
downloadysnp-master.tar.gz
ysnp-master.zip
Separate quiz logic from main function fo dedicated moduleHEADmaster
Signed-off-by: Filip Wandzio <contact@philw.dev>
Diffstat (limited to 'src/quiz.c')
-rw-r--r--src/quiz.c203
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
21static const char* START_PROMPT = "Press ENTER to start...";
22static const char* DONE_PROMPT = "Press ENTER when done...";
23static const char* TIME_LIMIT_PROMPT = "Time limit (sec, 0 = unlimited): ";
24static const char* CORRECT_PROMPT = "Correct? (y/n): ";
25static const char* NEXT_QUESTION_PROMPT = "Next? (y/n): ";
26static const char* TIME_EXCEEDED_MESSAGE = "Time exceeded!\n";
27static const char* CSV_OPEN_ERROR_MESSAGE = "CSV open";
28static const char* QUESTIONS_LOAD_ERROR = "Error: cannot load questions file\n";
29static const char* DATA_DIR_ERROR = "Error: cannot create data directory\n";
30
31static 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
45static 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
67static 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
84void 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
93void 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// ------------------------
107bool 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
151void cleanup_session(QuizSession* session)
152{
153 fclose(session->csv);
154 free_questions(&session->questions);
155}
156
157// ------------------------
158// Single Quiz Iteration
159// ------------------------
160bool 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}