diff options
| author | Filip Wandzio <contact@philw.dev> | 2026-03-01 17:45:00 +0100 |
|---|---|---|
| committer | Filip Wandzio <contact@philw.dev> | 2026-03-01 17:45:00 +0100 |
| commit | 9e9c1b21569faeabd33716e4153a881e2eed7134 (patch) | |
| tree | f3a7ad21aed4b1c4f51c3ee308ffef88430deafc | |
| parent | 57b077a4788b7fb5ed6add1df4ba3f15c9e6349b (diff) | |
| download | ysnp-9e9c1b21569faeabd33716e4153a881e2eed7134.tar.gz ysnp-9e9c1b21569faeabd33716e4153a881e2eed7134.zip | |
Signed-off-by: Filip Wandzio <contact@philw.dev>
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | .idea/codeStyles/Project.xml | 106 | ||||
| -rw-r--r-- | .idea/codeStyles/codeStyleConfig.xml | 5 | ||||
| -rwxr-xr-x | build/exam | bin | 34952 -> 35720 bytes | |||
| -rwxr-xr-x | build/test_questions | bin | 34688 -> 35008 bytes | |||
| -rw-r--r-- | include/questions.h | 12 | ||||
| -rw-r--r-- | include/quiz.h | 34 | ||||
| -rw-r--r-- | include/utils.h | 30 | ||||
| -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 | ||||
| -rw-r--r-- | tests/test_questions.c | 34 |
13 files changed, 599 insertions, 206 deletions
| @@ -4,7 +4,7 @@ | |||
| 4 | ### C++ ### | 4 | ### C++ ### |
| 5 | # Prerequisites | 5 | # Prerequisites |
| 6 | *.d | 6 | *.d |
| 7 | 7 | build | |
| 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 | ||
| 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 | ||
| 4 | typedef struct { | 4 | typedef 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 | */ |
| 15 | int load_questions(const char *filename, QuestionSet *qs); | 15 | int 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 | */ |
| 20 | void free_questions(QuestionSet *qs); | 20 | void 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 | // ------------------------ | ||
| 18 | typedef 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 | // ------------------------ | ||
| 30 | bool initialize_quiz_session(QuizSession* session); | ||
| 31 | bool quiz_iteration(QuizSession* session); | ||
| 32 | void print_score(size_t correct, size_t total); | ||
| 33 | void print_final_score(const QuizSession* session); | ||
| 34 | void 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 */ | ||
| 4 | void print_line(void); | 7 | void print_line(void); |
| 5 | void wait_enter(const char *msg); | 8 | |
| 6 | char ask_yes_no(const char *msg); | 9 | /** Pause until the user presses ENTER */ |
| 7 | void now_str(char *buf, size_t size); | 10 | void wait_for_enter(const char* prompt); |
| 8 | size_t rand_index(size_t max); | 11 | |
| 12 | /** Ask a yes/no question and return boolean */ | ||
| 13 | bool 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 | */ | ||
| 21 | void 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 | */ | ||
| 30 | size_t get_random_question_index(unsigned int* seed, size_t total_questions); | ||
| @@ -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 | } | ||
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 | ||
| 5 | int main(void) { | 5 | int 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 | } |
