summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main.c94
-rw-r--r--src/questions.c160
-rw-r--r--src/quiz.c203
-rw-r--r--src/utils.c125
4 files changed, 403 insertions, 179 deletions
diff --git a/src/main.c b/src/main.c
index 33d4b39..f69d734 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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
10int 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; 5int 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
8static int add_question(char ***arr, size_t *count, const char *text) { 8static 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
23int load_questions(const char *filename, QuestionSet *qs) { 24int 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
76void free_questions(QuestionSet *qs) { 81void 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
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}
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
6void 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 */
15static 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 */
24static 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
10void wait_enter(const char *msg) { 33/** Print a horizontal separator line */
11 if (msg) 34void 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
18char 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; 44void 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 */
58bool 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
29void 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 */
87void 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(&current_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
34size_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 */
109size_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}