summaryrefslogtreecommitdiffstats
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
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 '')
-rw-r--r--.gitignore2
-rw-r--r--.idea/codeStyles/Project.xml106
-rw-r--r--.idea/codeStyles/codeStyleConfig.xml5
-rwxr-xr-xbuild/exambin34952 -> 35720 bytes
-rwxr-xr-xbuild/test_questionsbin34688 -> 35008 bytes
-rw-r--r--include/questions.h12
-rw-r--r--include/quiz.h34
-rw-r--r--include/utils.h30
-rw-r--r--src/main.c94
-rw-r--r--src/questions.c160
-rw-r--r--src/quiz.c203
-rw-r--r--src/utils.c125
-rw-r--r--tests/test_questions.c34
13 files changed, 599 insertions, 206 deletions
diff --git a/.gitignore b/.gitignore
index d483172..8e37b1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@
4### C++ ### 4### C++ ###
5# Prerequisites 5# Prerequisites
6*.d 6*.d
7 7build
8*.csv 8*.csv
9# Compiled Object files 9# Compiled Object files
10*.slo 10*.slo
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..54584fe
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,106 @@
1<component name="ProjectCodeStyleConfiguration">
2 <code_scheme name="Project" version="173">
3 <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
4 <RiderCodeStyleSettings>
5 <option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="true" type="bool" />
6 <option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
7 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
8 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
9 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
10 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
11 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
12 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
13 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
14 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
15 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
16 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
17 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
18 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
19 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
20 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
21 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
22 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
23 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
24 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
25 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
26 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
27 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
28 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
29 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
30 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
31 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
32 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
33 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
34 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
35 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
36 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
37 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
38 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
39 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
40 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
41 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
42 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
43 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
44 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
45 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
46 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
47 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
48 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
49 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
50 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
51 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
52 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
53 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
54 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
55 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
56 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
57 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
58 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
59 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
60 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
61 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
62 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
63 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
64 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
65 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
66 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
67 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
68 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
69 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
70 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
71 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
72 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
73 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
74 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
75 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
76 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
77 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
78 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
79 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
80 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
81 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
82 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
83 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
84 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
85 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
86 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
87 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
88 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
89 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
90 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
91 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
92 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
93 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
94 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
95 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
96 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
97 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
98 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
99 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
100 <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
101 </RiderCodeStyleSettings>
102 <clangFormatSettings>
103 <option name="ENABLED" value="true" />
104 </clangFormatSettings>
105 </code_scheme>
106</component> \ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
1<component name="ProjectCodeStyleConfiguration">
2 <state>
3 <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4 </state>
5</component> \ No newline at end of file
diff --git a/build/exam b/build/exam
index 988b751..77b3548 100755
--- a/build/exam
+++ b/build/exam
Binary files differ
diff --git a/build/test_questions b/build/test_questions
index 04dac40..bdf53be 100755
--- a/build/test_questions
+++ b/build/test_questions
Binary files differ
diff --git a/include/questions.h b/include/questions.h
index 75bc751..95ce286 100644
--- a/include/questions.h
+++ b/include/questions.h
@@ -2,19 +2,19 @@
2#include <stddef.h> 2#include <stddef.h>
3 3
4typedef struct { 4typedef struct {
5 char **general; 5 char** general;
6 char **major; 6 char** major;
7 size_t general_count; 7 size_t general_count;
8 size_t major_count; 8 size_t major_count;
9} QuestionSet; 9} QuestionSet;
10 10
11/** 11/**
12 * Load questions from file. 12 * Load questions from file.
13 * Returns EXIT_SUCCESS or EXIT_FAILURE. 13 * Returns EXIT_SUCCESS or EXIT_FAILURE.
14 */ 14 */
15int load_questions(const char *filename, QuestionSet *qs); 15int load_questions(const char* filename, QuestionSet* qs);
16 16
17/** 17/**
18 * Free all allocated memory in QuestionSet. 18 * Free all allocated memory in QuestionSet.
19 */ 19 */
20void free_questions(QuestionSet *qs); 20void free_questions(QuestionSet* qs);
diff --git a/include/quiz.h b/include/quiz.h
new file mode 100644
index 0000000..db69367
--- /dev/null
+++ b/include/quiz.h
@@ -0,0 +1,34 @@
1//
2// Created by philw on 01/03/2026.
3//
4
5#pragma once
6
7#include "../include/questions.h"
8#include <stdbool.h>
9#include <stddef.h>
10#include <stdio.h>
11
12#define PERCENT_MULTIPLIER 100.0
13#define SCORE_FORMAT "Score: %zu/%zu (%.1f%%)\n"
14
15// ------------------------
16// Quiz state
17// ------------------------
18typedef struct {
19 QuestionSet questions;
20 FILE* csv;
21 unsigned int seed;
22 size_t total_answered;
23 size_t total_correct;
24 int time_limit;
25} QuizSession;
26
27// ------------------------
28// Quiz functions
29// ------------------------
30bool initialize_quiz_session(QuizSession* session);
31bool quiz_iteration(QuizSession* session);
32void print_score(size_t correct, size_t total);
33void print_final_score(const QuizSession* session);
34void cleanup_session(QuizSession* session);
diff --git a/include/utils.h b/include/utils.h
index 74c0ca1..08895c0 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -1,8 +1,30 @@
1#pragma once 1#pragma once
2
3#include <stdbool.h>
2#include <stddef.h> 4#include <stddef.h>
3 5
6/** Print a horizontal separator line */
4void print_line(void); 7void print_line(void);
5void wait_enter(const char *msg); 8
6char ask_yes_no(const char *msg); 9/** Pause until the user presses ENTER */
7void now_str(char *buf, size_t size); 10void wait_for_enter(const char* prompt);
8size_t rand_index(size_t max); 11
12/** Ask a yes/no question and return boolean */
13bool ask_yes_no(const char* prompt);
14
15/**
16 * Get the current timestamp for recording when a question was answered
17 *
18 * @param answer_time_buffer Buffer to store the formatted timestamp
19 * @param buffer_capacity Size of the buffer in bytes
20 */
21void get_answer_timestamp(char* answer_time_buffer, size_t buffer_capacity);
22
23/**
24 * Get a random question index in the quiz (thread-safe)
25 *
26 * @param seed Pointer to an unsigned int seed (per-thread)
27 * @param total_questions Number of questions in the current quiz session
28 * @return Random index in the range [0, total_questions-1]
29 */
30size_t get_random_question_index(unsigned int* seed, size_t total_questions);
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}
diff --git a/tests/test_questions.c b/tests/test_questions.c
index c605c1a..334ab86 100644
--- a/tests/test_questions.c
+++ b/tests/test_questions.c
@@ -1,24 +1,26 @@
1#include "questions.h" 1#include "../include/questions.h"
2#include <stdio.h> 2#include <stdio.h>
3#include <stdlib.h> 3#include <stdlib.h>
4 4
5int main(void) { 5int main(void)
6 QuestionSet qs; 6{
7 QuestionSet qs;
7 8
8 if (load_questions("data/questions.txt", &qs) != EXIT_SUCCESS) { 9 if (load_questions("data/questions.txt", &qs) != EXIT_SUCCESS) {
9 fprintf(stderr, "TEST FAILED: cannot load file\n"); 10 fprintf(stderr, "TEST FAILED: cannot load file\n");
10 return EXIT_FAILURE; 11 return EXIT_FAILURE;
11 } 12 }
12 13
13 if (qs.general_count == 0 || qs.major_count == 0) { 14 if (qs.general_count == 0 || qs.major_count == 0) {
14 fprintf(stderr, "TEST FAILED: empty sets\n"); 15 fprintf(stderr, "TEST FAILED: empty sets\n");
15 free_questions(&qs); 16 free_questions(&qs);
16 return EXIT_FAILURE; 17 return EXIT_FAILURE;
17 } 18 }
18 19
19 printf("TEST OK: loaded %zu general, %zu major\n", qs.general_count, 20 printf("TEST OK: loaded %zu general, %zu major\n",
20 qs.major_count); 21 qs.general_count,
22 qs.major_count);
21 23
22 free_questions(&qs); 24 free_questions(&qs);
23 return EXIT_SUCCESS; 25 return EXIT_SUCCESS;
24} 26}