Transferred from GitHub

This commit is contained in:
Maddox Werts 2024-05-08 13:59:08 -04:00
parent 2b1c46cc68
commit c3794c8a09
25 changed files with 26245 additions and 0 deletions

33
CMakeLists.txt Normal file
View file

@ -0,0 +1,33 @@
# Version Info
cmake_minimum_required(VERSION 3.5.0)
project(TestDesk VERSION 0.1.0 LANGUAGES C CXX)
include(CTest)
enable_testing()
# Files
set(SOURCES
src/tester/question.cpp
src/tester/quiz.cpp
src/interface/cli.cpp
src/interface/gui_fltk.cpp
src/main.cpp
)
# Executable
add_executable(TestDesk ${SOURCES})
# Libraries
target_include_directories(TestDesk PRIVATE ${PROJECT_SOURCE_DIR}/inc)
target_link_directories(TestDesk PRIVATE ${PROJECT_SOURCE_DIR}/lib)
target_link_libraries(TestDesk fltk)
# Directory
set_target_properties(TestDesk PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/build)
# Compile info
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

0
bin/.empty Normal file
View file

0
data/exams/.empty Normal file
View file

15
inc/base.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef H_BASE
#define H_BASE
// Includes
#include <filesystem>
#include <iostream>
#include <fstream>
#include <vector>
#include "tools/json.hpp"
// Namespaces
using namespace nlohmann;
#endif

30
inc/interface/cli.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef H_TCLI
#define H_TCLI
// Includes
#include <base.h>
#include <tester/question.h>
#include <tester/quiz.h>
// Classes
class Tester_CLI{
private:
// Variables
// Functions
public:
// Variables
// Constructor
Tester_CLI();
// Functions
void AskQuestions(Quiz quiz, int num);
static int Letter2Number(const char* letter);
void DisplayQuestion(int index, int qmax, std::string title, Question* question);
void DisplayScore(Score score, std::string title);
};
#endif

116
inc/interface/gui_fltk.h Normal file
View file

@ -0,0 +1,116 @@
#ifndef H_GUI
#define H_GUI
// Includes
#include <base.h>
#include <tester/question.h>
#include <tester/quiz.h>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Radio_Button.H>
#include <FL/Fl_Radio_Round_Button.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Scroll.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Value_Input.H>
#include <FL/Fl_Input_Choice.H>
// Structures
typedef struct MissedQuestion{
// Variables
char* title;
char* prompt;
char* user;
char* correct;
} MissedQuestion;
typedef struct HubData{
Fl_Input_Choice* name;
Fl_Value_Input* questions;
} HubData;
// Classes
class Window_GUI{
private:
// Variables
Fl_Window* window;
public:
// Variables
static Window_GUI* instance;
Fl_Window* getWindow();
// Constructors
Window_GUI(const char* title, int w, int h);
// Functions
void Update();
void Show(int argc, char* argv[]);
};
class Hub_GUI{
private:
// Variables
Fl_Box* title;
Fl_Box* subtitle;
Fl_Box* credits;
Fl_Input_Choice* quizName;
Fl_Value_Input* numQuestions;
Fl_Button* start;
// Functions
std::vector<std::string> getQuizzes();
public:
// Variables
static Hub_GUI* instance;
// Constructors
Hub_GUI();
// Functions
void Clean();
};
class Tester_GUI{
private:
// Variables
Fl_Button* g_exam_next;
Fl_Button* g_exam_back;
Fl_Box* g_exam_progress;
Fl_Box* g_exam_title;
Fl_Box* g_exam_question;
std::vector<Fl_Radio_Round_Button*> questionButtons;
std::vector<MissedQuestion> mQuestions;
Fl_Group* g_grade_header;
Fl_Box* g_grade_prompt;
Fl_Box* g_grade_user;
Fl_Box* g_grade_correct;
int gCurrent, gMax;
// Functions
public:
// Variables
static Tester_GUI* instance;
Quiz* quiz;
int sResponse;
// Constructors
Tester_GUI(Quiz* quiz);
// Functions
void Instantiate();
void Populate();
void gPopulate();
void gNav(int dir);
void LastQuestion();
void EndTest();
};
#endif

21
inc/tester/question.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef H_QUESTION
#define H_QUESTION
// Includes
#include "base.h"
// Classes
class Question{
public:
// Variables
std::string prompt;
std::vector<std::string> responses;
int correct;
// Constructor
Question(std::string prompt, std::vector<std::string> responses, int correct);
// Functions
};
#endif

47
inc/tester/quiz.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef H_QUIZ
#define H_QUIZ
// Includes
#include <base.h>
#include "question.h"
// Classes
class Score{
public:
// Variables
float percentage;
int incorrect;
int correct;
std::vector<Question*> qIncorrect;
std::vector<int> aIncorrect;
std::vector<int> iIncorrect;
// Constructor
Score();
};
class Quiz{
private:
// Variables
std::vector<Question*> questions;
std::vector<int> answers;
public:
// Variables
std::string name;
std::string description;
int qCurrent;
// Constructors
Quiz(const char* quizPath, int wantedQuestions);
// Functions
void qAnswer(int answer);
Question* qGet();
int qSize();
Score qScore();
};
#endif

24766
inc/tools/json.hpp Normal file

File diff suppressed because it is too large Load diff

0
lib/.empty Normal file
View file

BIN
old/fltk_version.zip Normal file

Binary file not shown.

94
src/interface/cli.cpp Normal file
View file

@ -0,0 +1,94 @@
// Header
#include <interface/cli.h>
// Constants
std::vector<std::string> indexes = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q"};
// Constructors
Tester_CLI::Tester_CLI(){
/*
INIT CLI (Command Line Interface)
*/
// Clearing the screen
system("clear");
}
// Functions
int Tester_CLI::Letter2Number(const char* letter){
for(unsigned int i = 0; i < indexes.size(); i++){
if(indexes[i] == letter){
return i;
}
}
return -1;
}
void Tester_CLI::AskQuestions(Quiz quiz, int num){
// Displaying questions
quiz.qCurrent = 0;
for(unsigned int i = 0; i < quiz.qSize(); i++){
DisplayQuestion(quiz.qCurrent, quiz.qSize(), quiz.name, quiz.qGet());
std::string typed;
std::cout << "Answer: ";
std::cin >> typed;
quiz.qAnswer(Tester_CLI::Letter2Number(typed.c_str()));
quiz.qCurrent++;
}
// Showing results
DisplayScore(quiz.qScore(), quiz.name);
}
void Tester_CLI::DisplayQuestion(int index, int qmax, std::string title, Question* question){
// Clear screen
system("clear");
// Test title
std::cout << "-- " << title << " --" << "\n";
std::cout << "\n";
// Giving the question's prompt
std::cout << "(" << index+1 << " / " << qmax << ") " << question->prompt << "\n";
std::cout << "\n";
// Spitting out responses
for(unsigned int i = 0; i < question->responses.size(); i++){
std::cout << indexes[i] << ") " << question->responses[i] << "\n";
}
std::cout << "\n";
}
void Tester_CLI::DisplayScore(Score score, std::string title){
// Clear screen
system("clear");
// Test title
std::cout << "-- " << title << " --" << "\n";
// Quick Stats
std::cout << "\n";
std::cout << "- Summary -" << "\n";
std::cout << "\tGrade: " << score.percentage << "%\n";
std::cout << "\tCorrect: " << score.correct << " / " << (score.correct + score.incorrect) << "\n";
if(score.incorrect) {std::cout << "\t" << "You missed " << score.incorrect << " questions.\n";}
std::cout << "\n";
// Did we miss any?
if(score.incorrect <= 0){
std::cout << "\n";
return;
}
// Showing wrong answers
std::cout << "\n";
std::cout << "- Missed Questions -" << "\n";
for(unsigned int i = 0; i < score.incorrect; i++){
std::cout << score.iIncorrect[i] + 1 << ") " << score.qIncorrect[i]->prompt << "\n";
std::cout << "\n";
std::cout << "You said: \t\t" << score.qIncorrect[i]->responses[score.aIncorrect[i]] << "\n";
std::cout << "Correct answer: \t" << score.qIncorrect[i]->responses[score.qIncorrect[i]->correct] << "\n";
std::cout << "\n";
}
}

462
src/interface/gui_fltk.cpp Normal file
View file

@ -0,0 +1,462 @@
// Header
#include <interface/gui_fltk.h>
// Functions
void btnNext(Fl_Widget*, void* data){
// Getting info from data
std::vector<Fl_Radio_Round_Button*> *buttons = (std::vector<Fl_Radio_Round_Button*> *)data;
// Going through buttons
for(unsigned int i = 0; i < buttons->size(); i++){
if(buttons->at(i)->value()){
// Printing that we answered
printf("Answered: %i\n", i);
// Answering the question
Tester_GUI::instance->quiz->qAnswer(i);
Tester_GUI::instance->quiz->qCurrent++;
if(Tester_GUI::instance->quiz->qCurrent == Tester_GUI::instance->quiz->qSize() - 1){
Tester_GUI::instance->LastQuestion();
}
// Do we have more questions?
if(Tester_GUI::instance->quiz->qCurrent < Tester_GUI::instance->quiz->qSize()){
// Updating the question board
Tester_GUI::instance->Populate();
}
else{
// Ending exam
Tester_GUI::instance->EndTest();
}
}
}
}
void btnQuit(Fl_Widget*, void* data){
exit(EXIT_SUCCESS);
}
void btnGNext(Fl_Widget*, void* data){
Tester_GUI::instance->gNav(1);
Tester_GUI::instance->gPopulate();
}
void btnGBack(Fl_Widget*, void* data){
Tester_GUI::instance->gNav(-1);
Tester_GUI::instance->gPopulate();
}
void btnHStart(Fl_Widget*, void* data){
// Cast
HubData* hData = (HubData*)data;
// Getting values
const char* quizName = hData->name->value();
int quizAmount = (int)(floor(hData->questions->value()));
if(quizAmount > 0){
// Loading a quiz
Quiz* quiz = new Quiz(("data/exams/" + std::string(quizName) + ".json").c_str(), quizAmount);
// Cleanup
Hub_GUI::instance->Clean();
// Creating the GUI
Tester_GUI::instance = new Tester_GUI(quiz);
// Redrawing the window
Window_GUI::instance->getWindow()->redraw();
}
}
std::vector<std::string> split(std::string line, char delimeter){
std::vector<std::string> splits;
std::string temp;
for(unsigned int i = 0; i < line.size(); i++){
if(line[i] == delimeter){
splits.push_back(temp);
temp = "";
}
else{
temp += line[i];
}
}
splits.push_back(temp);
return splits;
}
// Statics
Window_GUI* Window_GUI::instance;
Hub_GUI* Hub_GUI::instance;
Tester_GUI* Tester_GUI::instance;
// Variables
Fl_Window* Window_GUI::getWindow(){
return window;
}
// Constructors
Window_GUI::Window_GUI(const char* title, int w, int h){
// Creating a window
this->window = new Fl_Window(w, h, title);
}
Hub_GUI::Hub_GUI(){
// Title
this->title = new Fl_Box(205, 35, 335, 50, "TestDesk");
title->labelsize(42);
// Subtitle
this->subtitle = new Fl_Box(275, 80, 200, 25, "The simple study assistant.");
subtitle->labelsize(14);
// Credits
this->credits = new Fl_Box(0, 505, 255, 20, "Created by OBJNULL (Maddox Werts)");
credits->labelsize(14);
// Quiz Name
this->quizName = new Fl_Input_Choice(285, 227, 180, 28, "Quiz Name");
// Getting quiz questions
std::vector<std::string> quizzes = getQuizzes();
for(unsigned int i = 0; i < quizzes.size(); i++){
char* bytes = new char[quizzes[i].length() + 1];
strcpy(bytes, quizzes[i].c_str());
quizName->add(bytes);
}
// Question amounts
this->numQuestions = new Fl_Value_Input(285, 257, 180, 28, "Questions");
// Start button
this->start = new Fl_Button(310, 290, 120, 30, "Start");
HubData* data = new HubData{quizName, numQuestions};
start->callback(btnHStart, (void*)data);
}
Tester_GUI::Tester_GUI(Quiz* quiz){
// Setting Quiz pointer
this->quiz = quiz;
this->quiz->qCurrent = 0;
// Update to the current question
Instantiate();
}
// Functions
void Window_GUI::Update(){
this->window->end();
}
void Window_GUI::Show(int argc, char* argv[]){
this->window->show(argc, argv);
}
std::vector<std::string> Hub_GUI::getQuizzes(){
// Result object
std::vector<std::string> result;
// Going through exams
for (const auto& entry : std::filesystem::recursive_directory_iterator("data/exams/")) {
if (std::filesystem::is_regular_file(entry)) {
// Getting the file name
std::string line = std::string(entry.path().string());
std::string strippedName = split(split(line, '/')[2], '.')[0];
if(strippedName != ""){
std::cout << strippedName << "\n";
result.push_back(strippedName);
}
}
}
// Return Result
return result;
}
void Hub_GUI::Clean(){
Window_GUI::instance->getWindow()->remove(this->title);
Window_GUI::instance->getWindow()->remove(this->subtitle);
Window_GUI::instance->getWindow()->remove(this->credits);
Window_GUI::instance->getWindow()->remove(this->quizName);
Window_GUI::instance->getWindow()->remove(this->numQuestions);
Window_GUI::instance->getWindow()->remove(this->start);
Window_GUI::instance->getWindow()->redraw();
}
void Tester_GUI::Instantiate(){
// Progress
this->g_exam_progress = new Fl_Box(760, 5, 13, 20);
this->g_exam_progress->align(FL_ALIGN_LEFT);
std::string progressText = "Question " + std::to_string(this->quiz->qCurrent+1) + " / " + std::to_string(this->quiz->qSize());
char* progressFinal = new char[progressText.length() + 1];
strcpy(progressFinal, progressText.c_str());
this->g_exam_progress->label(progressFinal);
Window_GUI::instance->getWindow()->add(this->g_exam_progress);
// Title
std::string promptText = this->quiz->qGet()->prompt;
char* promptFinal = new char[promptText.length() + 1];
strcpy(promptFinal, promptText.c_str());
// Adding a exam question
this->g_exam_question = new Fl_Box(20, 45, 730, 85, promptFinal);
this->g_exam_question->align(FL_ALIGN_LEFT|FL_ALIGN_CLIP|FL_ALIGN_WRAP|FL_ALIGN_INSIDE);
this->g_exam_question->labelsize(14);
Window_GUI::instance->getWindow()->add(this->g_exam_question);
// Adding a exam title
this->g_exam_title = new Fl_Box(10, 10, 5, 30, this->quiz->name.c_str());
this->g_exam_title->align(FL_ALIGN_RIGHT);
this->g_exam_title->labelsize(24);
Window_GUI::instance->getWindow()->add(this->g_exam_title);
// Adding next button
this->g_exam_next = new Fl_Button(700, 485, 60, 30);
this->g_exam_next->labeltype(FL_NORMAL_LABEL);
this->g_exam_next->label("Next");
this->g_exam_next->box(FL_UP_BOX);
Window_GUI::instance->getWindow()->add(this->g_exam_next);
// Adding buttons for each question response
for(unsigned int i = 0; i < this->quiz->qGet()->responses.size(); i++){
std::string qResponse = this->quiz->qGet()->responses[i];
Fl_Radio_Round_Button* nRB = new Fl_Radio_Round_Button(45, 145+(i*40), 700, 25);
nRB->labeltype(FL_NORMAL_LABEL);
nRB->label(this->quiz->qGet()->responses[i].c_str());
nRB->box(FL_NO_BOX);
nRB->down_box(FL_ROUND_DOWN_BOX);
nRB->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP|FL_ALIGN_CLIP);
this->questionButtons.push_back(nRB);
Window_GUI::instance->getWindow()->add(nRB);
}
// Next Callback
this->g_exam_next->callback(btnNext, (void*)&this->questionButtons);
}
void Tester_GUI::Populate(){
// Progress
std::string progressText = "Question " + std::to_string(this->quiz->qCurrent+1) + " / " + std::to_string(this->quiz->qSize());
char* progressFinal = new char[progressText.length() + 1];
strcpy(progressFinal, progressText.c_str());
this->g_exam_progress->label(progressFinal);
// Title
std::string promptText = std::to_string(this->quiz->qCurrent+1) +") " + this->quiz->qGet()->prompt;
char* promptFinal = new char[promptText.length() + 1];
strcpy(promptFinal, promptText.c_str());
// Setting the question title
this->g_exam_question->label(promptFinal);
// Clearing the existing answers
for(unsigned int i = 0; i < questionButtons.size(); i++){
Window_GUI::instance->getWindow()->remove(questionButtons[i]);
delete questionButtons[i];
}
// Clearing array
this->questionButtons.clear();
// Adding new question buttons
for(unsigned int i = 0; i < this->quiz->qGet()->responses.size(); i++){
std::string qResponse = this->quiz->qGet()->responses[i];
Fl_Radio_Round_Button* nRB = new Fl_Radio_Round_Button(45, 145+(i*40), 700, 40);
nRB->labeltype(FL_NORMAL_LABEL);
nRB->label(this->quiz->qGet()->responses[i].c_str());
nRB->box(FL_NO_BOX);
nRB->down_box(FL_ROUND_DOWN_BOX);
nRB->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP|FL_ALIGN_CLIP);
this->questionButtons.push_back(nRB);
Window_GUI::instance->getWindow()->add(nRB);
}
// After Adding more elements
Window_GUI::instance->getWindow()->redraw();
}
void Tester_GUI::gPopulate(){
this->g_grade_header->label(mQuestions[gCurrent].title);
this->g_grade_prompt->label(mQuestions[gCurrent].prompt);
this->g_grade_user->label(mQuestions[gCurrent].user);
this->g_grade_correct->label(mQuestions[gCurrent].correct);
}
void Tester_GUI::gNav(int dir){
this->gCurrent += dir;
if(this->gCurrent > gMax - 1){
this->gCurrent = 0;
}
else if(this->gCurrent < 0){
this->gCurrent = gMax - 1;
}
}
void Tester_GUI::LastQuestion(){
printf("LAST QUESTION\n");
this->g_exam_next->label("Submit");
}
void Tester_GUI::EndTest(){
// DEBUG
printf("Your exam has ended.\n");
// Clearing current screen
Window_GUI::instance->getWindow()->remove(g_exam_question);
Window_GUI::instance->getWindow()->remove(g_exam_next);
Window_GUI::instance->getWindow()->remove(g_exam_back);
for(unsigned int i = 0; i < this->questionButtons.size(); i++){
Window_GUI::instance->getWindow()->remove(questionButtons[i]);
}
// Scoring the test
Score score = Tester_GUI::instance->quiz->qScore();
// Adding the top section
if(true){
// Adding new elements
Fl_Box* title = new Fl_Box(270, 100, 215, 30);
title->labeltype(FL_NORMAL_LABEL);
title->labelsize(24);
// Adding percentage in a weird way
std::string qTitleText = "";
if(score.percentage < 75){
qTitleText += "Failed";
}
else{
qTitleText += "Passed";
}
qTitleText += " (";
std::string percent_raw = std::to_string(score.percentage);
qTitleText += percent_raw.substr(0, percent_raw.length() - 4);
qTitleText += "%)";
char* qTitleFinal = new char[qTitleText.length() + 1];
strcpy(qTitleFinal, qTitleText.c_str());
title->label(qTitleFinal);
Window_GUI::instance->getWindow()->add(title);
// The questions you got right and wrong
Fl_Box* qRatio = new Fl_Box(270, 130, 215, 30);
qRatio->labeltype(FL_NORMAL_LABEL);
qRatio->labelsize(16);
std::string qRatioText = "";
qRatioText += std::string(std::to_string(score.correct));
qRatioText += " / ";
qRatioText += std::to_string(score.correct + score.incorrect);
char* qRatioFinal = new char[qRatioText.length() + 1];
strcpy(qRatioFinal, qRatioText.c_str());
qRatio->label(qRatioFinal);
Window_GUI::instance->getWindow()->add(qRatio);
}
// Adding the missed answers section
if(true){
// FUNCTION
gMax = score.qIncorrect.size();
mQuestions.clear();
for(unsigned int i = 0; i < gMax; i++){
// Title
std::string titleText = "Question ";
titleText += std::to_string(score.iIncorrect[i]+1);
char* titleFinal = new char[titleText.length() + 1];
strcpy(titleFinal, titleText.c_str());
// Prompt
std::string promptText = score.qIncorrect[i]->prompt;
char* promptFinal = new char[promptText.length() + 1];
strcpy(promptFinal, promptText.c_str());
// You Said
std::string mYouSaidText = "You Said: ";
mYouSaidText += score.qIncorrect[i]->responses[score.aIncorrect[i]];
char* mYouSaidFinal = new char[mYouSaidText.length() + 1];
strcpy(mYouSaidFinal, mYouSaidText.c_str());
// Correct
std::string mCorrectText = "Correct: ";
mCorrectText += score.qIncorrect[i]->responses[score.qIncorrect[i]->correct];
char* mCorrectFinal = new char[mCorrectText.length() + 1];
strcpy(mCorrectFinal, mCorrectText.c_str());
// New missed question
MissedQuestion mq = MissedQuestion();
mq.title = titleFinal;
mq.prompt = promptFinal;
mq.user = mYouSaidFinal;
mq.correct = mCorrectFinal;
// Pushing back final result
mQuestions.push_back(mq);
}
// UI
if(mQuestions.size() > 0){
Fl_Group* missedQuestions = new Fl_Group(10, 200, 750, 280);
missedQuestions->labelsize(19);
missedQuestions->label("Missed Questions");
Window_GUI::instance->getWindow()->add(missedQuestions);
// Going through all missed questions
this->g_grade_header = new Fl_Group(20, 225, 730, 150);
this->g_grade_header->labelsize(14);
this->g_grade_header->label(mQuestions[0].title);
missedQuestions->add(this->g_grade_header);
// Question prompt
this->g_grade_prompt = new Fl_Box(25, 230, 710, 100);
this->g_grade_prompt->align(FL_ALIGN_CLIP|FL_ALIGN_WRAP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
this->g_grade_prompt->labelsize(14);
this->g_grade_prompt->label(mQuestions[0].prompt);
this->g_grade_header->add(this->g_grade_prompt);
// You said
this->g_grade_user = new Fl_Box(40, 330, 5, 20);
this->g_grade_user->align(FL_ALIGN_RIGHT);
this->g_grade_user->labelsize(14);
this->g_grade_user->label(mQuestions[0].user);
this->g_grade_header->add(this->g_grade_user);
// Correct
this->g_grade_correct = new Fl_Box(40, 350, 5, 20);
this->g_grade_correct->align(FL_ALIGN_RIGHT);
this->g_grade_correct->labelsize(14);
this->g_grade_correct->label(mQuestions[0].correct);
this->g_grade_header->add(this->g_grade_correct);
// Next
Fl_Button* next = new Fl_Button(680, 485, 80, 30, "Next");
next->callback(btnGNext);
Window_GUI::instance->getWindow()->add(next);
// Next
Fl_Button* back = new Fl_Button(595, 485, 80, 30, "Back");
back->callback(btnGBack);
Window_GUI::instance->getWindow()->add(back);
}
}
// Adding quit button
Fl_Button* quit = new Fl_Button(10, 485, 80, 30, "Quit");
quit->callback(btnQuit);
Window_GUI::instance->getWindow()->add(quit);
// Redraw
Window_GUI::instance->getWindow()->redraw();
}

92
src/main.cpp Normal file
View file

@ -0,0 +1,92 @@
// Includes
#include <base.h>
#include <tester/quiz.h>
#include <interface/cli.h>
#include <interface/gui_fltk.h>
// Variables
std::string quizPath = "data/exams/";
int numOfQuestions = 15;
int flagsSet = 0;
// Functions
void cli_version(){
// Loading a new test
Quiz quiz = Quiz(quizPath.c_str(), numOfQuestions);
// Init application
Tester_CLI cli;
// Asking the user questions
cli.AskQuestions(quiz, numOfQuestions);
}
int gui_version(int argc, char* argv[]){
// Creating the window app
Window_GUI::instance = new Window_GUI("TestDesk", 770, 525);
if(flagsSet >= 2){
// Loading a quiz
Quiz* quiz = new Quiz(quizPath.c_str(), numOfQuestions);
// Creating the GUI
Tester_GUI::instance = new Tester_GUI(quiz);
}
else{
Hub_GUI::instance = new Hub_GUI();
}
// Finalizing and showing the window after creating GUI elements
Window_GUI::instance->Update();
Window_GUI::instance->Show(argc, argv);
// Running FLTK
return Fl::run();
}
// Entry point
int main(int argc, char* argv[]){
// Getting argument(s)
std::vector<std::string> args;
for(unsigned int i = 0; i < argc; i++){
args.push_back(std::string(argv[i]));
}
// Checking the arguments
if(argc > 1){
if(args[1] == "--help"){
printf("Usage: TestDesk [OPTIONS]\n");
printf("OPTIONS: \n");
printf("\t-t: Sets the test you want to use (ex: CompTIA 1101)\n");
printf("\t-q: Sets the amount of questions to be quizzed on (random)\n");
printf("\t--help: Displays how to use the program\n");
printf("\t--cli: Starts program in a Command Line Interface.\n");
return 0;
}
}
// Getting typed arguments
for(unsigned i = 0; i < argc; i++){
// Converting the char array into a string
std::string arg_string = std::string(argv[i]);
// What do we want?
if(arg_string == "--cli"){
cli_version();
return 0;
}
if(arg_string == "-q"){
numOfQuestions = std::stoi(argv[i+1]);
i++;
flagsSet++;
}
if(arg_string == "-t"){
quizPath += argv[i+1];
quizPath += ".json";
i++;
flagsSet++;
}
}
// Starting the GUI Version
return gui_version(argc, argv);
}

9
src/tester/question.cpp Normal file
View file

@ -0,0 +1,9 @@
// Header
#include <tester/question.h>
Question::Question(std::string prompt, std::vector<std::string> responses, int correct){
// Getting the JSON stuff
this->prompt = prompt;
this->responses = responses;
this->correct = correct;
}

117
src/tester/quiz.cpp Normal file
View file

@ -0,0 +1,117 @@
// Includes
#include <tester/quiz.h>
// Constructors
Score::Score(){
percentage = 0;
incorrect = 0;
correct = 0;
}
Quiz::Quiz(const char* quizPath, int wantedQuestions){
// Load the quiz from path
std::ifstream file(quizPath);
// Parsing JSON
json qData = json::parse(file);
// Did it work?
if(qData == nullptr){
// Exit
printf("ERROR: Failed to parse JSON.\n");
exit(EXIT_FAILURE);
}
// Getting important data
this->name = qData["name"];
this->description = qData["description"];
// Loading questions
std::vector<Question*> tquestions;
for(unsigned int i = 0; i < qData["questions"].size(); i++){
// Current questions
json lq = qData["questions"][i];
// Loading responses
std::vector<std::string> responses;
for(unsigned int i = 0; i < lq["responses"].size(); i++){
responses.push_back(lq["responses"][i]);
}
// Creating a new question
Question* nq = new Question(lq["prompt"], responses, lq["correct"]);
// Adding it into the list
tquestions.push_back(nq);
}
// Making sure it is 100% random
srand(time(NULL));
// Adding 15 random questions from that list
int i = 0;
std::vector<int> usedQuestions;
while(i < wantedQuestions){
// Wanted question index
int qIndex = std::rand() % (tquestions.size());
// Did we already call it?
bool alreadyUsed = false;
for(int j = 0; j < usedQuestions.size(); j++){
if(usedQuestions[j] == qIndex){
alreadyUsed = true;
break;
}
}
// Adding question to index
if(!alreadyUsed
&& tquestions[qIndex] != nullptr){
this->questions.push_back(tquestions[qIndex]);
i++;
}
// Already got our questions?
if(i >= wantedQuestions){
break;
}
}
}
// Functions
void Quiz::qAnswer(int answer){
answers.push_back(answer);
}
Question* Quiz::qGet(){
return this->questions[this->qCurrent];
}
int Quiz::qSize(){
return this->questions.size();
}
Score Quiz::qScore(){
// Result
Score result;
// Going through all questions
for(unsigned int i = 0; i < this->questions.size(); i++){
if(this->questions[i]->correct == this->answers[i]){
result.correct++;
}
else{
result.incorrect++;
result.qIncorrect.push_back(this->questions[i]);
result.aIncorrect.push_back(this->answers[i]);
result.iIncorrect.push_back(i);
}
}
// Calculate percentage
float fC = result.correct;
float fI = result.correct + result.incorrect;
float fP = fC / fI;
float rP = (float)( (int)(fP * 10000) );
result.percentage = rP / 100;
// Return result
return result;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

92
tools/providers/messer.py Normal file
View file

@ -0,0 +1,92 @@
def Letter2Number(letter):
if("A" in letter):
return 0
if("B" in letter):
return 1
if("C" in letter):
return 2
if("D" in letter):
return 3
if("E" in letter):
return 4
if("F" in letter):
return 5
if("G" in letter):
return 6
if("H" in letter):
return 7
if("I" in letter):
return 8
if("J" in letter):
return 9
if("K" in letter):
return 10
def ParseMesser(lines):
result = []
# Pre-Stuff
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 0
# Going through lines
for line in lines:
match(state):
case 0: # Awaiting question
if("Practice Exam A - Answers" in line
or "Practice Exam B - Answers" in line
or "Practice Exam C - Answers" in line):
state = 1
case 1: # Writing into prompt
if("*" in line):
if("PICK TWO" in line):
state = 0
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 2
else:
for l in line:
if(l == '\n'):
tempQuestion["prompt"] += " "
else:
tempQuestion["prompt"] += l
case 2: # Awaiting for answers
if(line == "" or line == " " or line == "\n" or line == None):
state = 3
case 3: # Adding responses
if(line == "" or line == " " or line == "\n" or line == None):
state = 4
else:
if(len(line.split(".")) <= 1):
print("SKIPED QUESTION: " + tempQuestion["prompt"])
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 0
else:
tempQuestion["responses"].append(line.split(".")[1])
case 4: # Awaiting for Correct Answer
if("The Answer" in line):
tempQuestion["correct"] = Letter2Number(line.split(":")[1].split(".")[0])
result.append(tempQuestion)
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 0
return result

View file

@ -0,0 +1,72 @@
def Letter2Number(letter):
if("A" in letter):
return 0
if("B" in letter):
return 1
if("C" in letter):
return 2
if("D" in letter):
return 3
if("E" in letter):
return 4
if("F" in letter):
return 5
if("G" in letter):
return 6
if("H" in letter):
return 7
if("I" in letter):
return 8
if("J" in letter):
return 9
if("K" in letter):
return 10
def ParseVCE(lines):
# Result object
result = []
# Pre-Stuff
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 0
# Going through lines
for line in lines:
match(state):
case 0: # Awaiting question
if("QUESTION" in line):
state = 1
case 1: # Writing to prompt
if("A." in line):
state = 2
else:
tempQuestion["prompt"] += line
case 2: # Awaiting for answers
if(line == "" or line == " " or line == "\n" or line == None):
state = 3
case 3: #Adding responses
if(line == "" or line == " " or line == "\n" or line == None):
state = 4
else:
tempQuestion["responses"].append(line)
case 4: # Awaiting for correct answer
if("Correct" in line):
print(line)
tempQuestion["correct"] = Letter2Number(line.split(": ")[1])
elif("Section: " in line):
result.append(tempQuestion)
tempQuestion = {
"type": 0,
"prompt": "",
"responses": [],
"correct": 0
}
state = 0
# Return result
return result

128
tools/testConverter.py Normal file
View file

@ -0,0 +1,128 @@
####################
## Test Converter ##
####################
## A free script to convert a text file into a TestDesk compatible JSON file.
# Imports
import json
import sys
import os
import providers.messer as MSR
import providers.vceplus as VCE
# Variables
result = {
"name": None,
"description": None,
"questions": []
}
commands = [
{
"token": "--name"
},
{
"token": "--desc"
},
{
"token": "--in"
},
{
"token": "--out"
}
]
args = []
quizProvider = 0
quizPath = ""
quizOut = ""
# Functions
def readArgs():
global commands, args
for arg in range(len(sys.argv)):
for cmd in commands:
if(cmd["token"] == sys.argv[arg]):
args.append({
"key": cmd["token"],
"value": sys.argv[arg+1]
})
arg += 1
def argExists(key):
global args
for arg in args:
if(arg["key"] == key):
return True
return False
def argValue(key):
global args
for arg in args:
if(arg["key"] == key):
return arg["value"]
return None
def getMetadata():
global result, quizProvider, quizPath, quizOut
# Asking for a test name and description
if(argExists("--name")):
result["name"] = argValue("--name")
else:
result["name"] = input("Give your test a name.\n[Test Name]: ")
if(argExists("--desc")):
result["description"] = argValue("--desc")
else:
result["description"] = input("Describe what your test is for.\n[Description]: ")
# Asking for quiz provider
print("Select the provider that has given you the PDF file.")
print("1. VCEplus")
print("2. Professor Messer")
quizProvider = int(input("[Provider]: "))
# Asking for quiz info
if(argExists("--in")):
quizPath = argValue("--in")
else:
quizPath = input("Drag and Drop your text file into this window\n(Use https://pdftotext.com/ to convert your PDF file into a text file.)\n[In Location]: ")
if(argExists("--out")):
quizOut = argValue("--out")
else:
quizOut = input("Write a path to where you want to save your JSON file (ex: data/exams/test.json)\nYour Working Directory: " + os.getcwd() + "\n[Out Location]: ")
def readFile():
global quizPath, quizProvider, result
# Opening quiz
quizFile = open(quizPath, 'r')
# Did the user type it in wrong?
if(quizFile == None):
print("ERROR: Invalid path for Quiz!")
exit()
# Based on provider, LOAD
match(quizProvider):
case 1:
questions = VCE.ParseVCE(quizFile.readlines())
case 2:
questions = MSR.ParseMesser(quizFile.readlines())
# Result
result["questions"] = questions
def writeFile():
global quizOut, result
quizFile = open(quizOut, 'w')
quizFile.write(json.dumps(result))
quizFile.close()
def main():
readArgs()
getMetadata()
readFile()
writeFile()
# Entry Point
main()

58
ui/FLTK/TestDesk-Grade.fl Normal file
View file

@ -0,0 +1,58 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0309
header_name {.h}
code_name {.cxx}
Function {make_window()} {open
} {
Fl_Window {} {
label TestDesk open
xywh {613 329 770 525} type Double visible
} {
Fl_Box {} {
label {CompTIA A+ (1101)}
xywh {10 10 5 30} labelsize 24 align 8
}
Fl_Box {} {
label {Passed (75%)}
xywh {270 100 215 30} labelsize 24 align 16
}
Fl_Box {} {
label {30/40}
xywh {270 130 215 30} labelsize 16
}
Fl_Scroll {} {
label {Missed Questions} open
xywh {5 200 760 315} labelsize 19
} {
Fl_Group {} {
label {Question 13} open
xywh {5 225 760 290}
} {
Fl_Box {} {
label {A technician installed a new router at a small office. After the Installation, the technician notices that all devices have a I69.254.x.x IP address. Printers and fileshares are still working, but PCs cannot access the Internet. Which of the following should the technician configure on the router to enable devices to connect to the internet?}
xywh {25 230 710 100} align 212
}
Fl_Box {} {
label {You Said: APIPA}
xywh {40 330 5 20} align 8
}
Fl_Box {} {
label {Correct: DNS}
xywh {40 350 5 20} align 8
}
Fl_Button {} {
label Quit
xywh {5 485 80 30}
}
Fl_Button {} {
label Next
xywh {680 485 80 30}
}
Fl_Button {} {
label Back
xywh {595 485 80 30}
}
}
}
}
}

45
ui/FLTK/TestDesk-Hub.fl Normal file
View file

@ -0,0 +1,45 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0309
header_name {.h}
code_name {.cxx}
Function {make_window()} {open
} {
Fl_Window {} {
label TestDesk open
xywh {594 365 770 525} type Double visible
} {
Fl_Box {} {
label TestDesk
xywh {205 35 335 50} labelsize 42
}
Fl_Box {} {
label {The simple study assistant.}
xywh {275 80 200 25}
}
Fl_Button {} {
label Start
xywh {285 290 180 30}
}
Fl_Value_Input {} {
label Questions
xywh {285 257 180 28}
}
Fl_Box {} {
label {Created by OBJNULL (Maddox Werts)}
xywh {0 505 255 20}
}
Fl_Input_Choice {} {
label {Quiz Name} open
xywh {285 227 180 28}
} {
MenuItem {} {
label {Quiz A}
xywh {0 0 34 24}
}
MenuItem {} {
label {Quiz B}
xywh {0 0 34 24}
}
}
}
}

View file

@ -0,0 +1,48 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0309
header_name {.h}
code_name {.cxx}
Function {make_window()} {open
} {
Fl_Window {} {
label TestDesk open
xywh {549 227 770 525} type Double color 48 labelcolor 48 visible
} {
Fl_Box {} {
label {CompTIA A+ (1101)}
xywh {10 10 5 30} labelsize 24 align 8
}
Fl_Box {} {
label {Which of the following would an administrator use to migrate virtual machines from on premises to the cloud?}
xywh {20 45 730 85} align 212
}
Fl_Round_Button {} {
label laaS
xywh {45 145 250 25} down_box ROUND_DOWN_BOX
}
Fl_Round_Button {} {
label SaaS
xywh {45 175 250 25} down_box ROUND_DOWN_BOX
}
Fl_Round_Button {} {
label DBaaS
xywh {45 205 250 25} down_box ROUND_DOWN_BOX
}
Fl_Round_Button {} {
label DRaaS
xywh {45 235 250 25} down_box ROUND_DOWN_BOX
}
Fl_Button {} {
label Next
xywh {700 485 60 30}
}
Fl_Button {} {
label Back
xywh {635 485 60 30}
}
Fl_Box {} {
label {Question 1/2}
xywh {760 5 13 20} align 4
}
}
}