From 2d1589c94de90715c3771c921020f55e99a90110 Mon Sep 17 00:00:00 2001 From: jorenchik Date: Sat, 19 Oct 2024 17:21:19 +0300 Subject: [PATCH] spacing with correct cooldowns and updates --- src/include/main.h | 14 +++ src/include/trainWindow.h | 6 +- src/qtapp/main.cpp | 95 ++++++++-------- src/qtapp/trainWindow.cpp | 220 ++++++++++++++++++++++++++------------ src/transpiler/parser.cpp | 16 ++- 5 files changed, 234 insertions(+), 117 deletions(-) create mode 100644 src/include/main.h diff --git a/src/include/main.h b/src/include/main.h new file mode 100644 index 0000000..315fde9 --- /dev/null +++ b/src/include/main.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "parser.h" + +void update(); +void saveMdem(); + +struct MdemBuffer { + std::vector questions = std::vector(); + time_t trainedAt = 0; +}; + diff --git a/src/include/trainWindow.h b/src/include/trainWindow.h index 2bebad8..199930b 100644 --- a/src/include/trainWindow.h +++ b/src/include/trainWindow.h @@ -2,6 +2,7 @@ #include +#include "main.h" #include "parser.h" extern QMainWindow *trainWindow; @@ -15,7 +16,6 @@ enum PracticeAlgorithm { void initTrainWindow(); void initiatePractice( - std::vector questions, - PracticeAlgorithm algorithm, - time_t *trainedAt + MdemBuffer *mdemBuffer, + PracticeAlgorithm algorithm ); diff --git a/src/qtapp/main.cpp b/src/qtapp/main.cpp index d90d671..4b5cb39 100644 --- a/src/qtapp/main.cpp +++ b/src/qtapp/main.cpp @@ -58,6 +58,7 @@ #include #include +#include "main.h" #include "api.h" #include "parser.h" #include "qscilexer.h" @@ -86,11 +87,6 @@ struct Mdem { Question *question; }; -struct MdemBuffer { - std::vector questions = std::vector(); - time_t trainedAt = 0; -}; - struct ErrorView { QWidget box; QVBoxLayout layout; @@ -164,6 +160,8 @@ std::string outputMdem(std::vector questions, time_t time = 0) { std::stringstream ss; if (time > 0) { + auto timezoneOffset = settings->value("timezone").toInt(); + /*time = time + 3600 * timezoneOffset;*/ std::tm* tm = std::localtime(&time); char buffer[100]; std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H\\:%M", tm); @@ -242,8 +240,13 @@ void makePages() { void setupMdem(Mdem *mdem, Question *question) { if (MultiElementQuestion* mw = dynamic_cast(question)) { + std::stringstream ss; + if (mw->Cooldown > 0) { + ss << std::format("[{:.2f}] ", question->Cooldown); + } + ss << mw->QuestionText; mdem->wFrontText.setText( - QString::fromStdString(mw->QuestionText) + QString::fromStdString(ss.str()) ); auto choices = mw->Choices; for (size_t k = 0; k < choices.size(); ++k) { @@ -444,7 +447,35 @@ void CreateMdems(std::vector& questions) { hMdemScroll->addItem(mdemSpacer); } +void updateMdemInfo(std::string filename, bool isChanged = false) { + if (filename.length() > 0) { + std::stringstream ss; + ss << std::format("mdem: {}", filename); + if (isChanged) { + ss << "*"; + } + if (mdemBuffer->trainedAt > 0) { + std::tm* tm = std::localtime(&mdemBuffer->trainedAt); + char buffer[100]; + std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H:%M", tm); + ss << std::endl << "Last practiced: " << std::string(buffer); + } + deckListLabel->setText(QString::fromStdString(ss.str())); + } else { + deckListLabel->setText(""); + } +} +std::string getFilename(std::string path) { + std::smatch matches; + auto filenameMatched = std::regex_search(path, matches, lastPathElementExp); + return matches[2].str(); +} + +void update() { + SwitchPage(currentPage); + updateMdemInfo(getFilename(currentMdem)); +} void SwitchPage(int pageIdx) { currentPage = pageIdx; @@ -560,29 +591,6 @@ void releaseError(ErrorView** item) { std::cout << std::format("Released, current pool size: {}\n", errorPool.size()); } -std::string getFilename(std::string path) { - std::smatch matches; - auto filenameMatched = std::regex_search(path, matches, lastPathElementExp); - return matches[2].str(); -} - - -void updateMdemInfo(std::string filename) { - if (filename.length() > 0) { - std::stringstream ss; - ss << std::format("mdem: {}", filename); - if (mdemBuffer->trainedAt > 0) { - std::tm* tm = std::localtime(&mdemBuffer->trainedAt); - char buffer[100]; - std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H:%M", tm); - ss << std::endl << "Last practiced: " << std::string(buffer); - } - deckListLabel->setText(QString::fromStdString(ss.str())); - } else { - deckListLabel->setText(""); - } -} - void reloadMdem() { auto file = std::ifstream(currentMdem); std::string content; @@ -616,7 +624,7 @@ void reloadMdem() { mdemBuffer->trainedAt = 0; } else { auto timezoneOffset = settings->value("timezone").toInt(); - mdemBuffer->trainedAt = res.value.lastTrainedAt + 3600 * timezoneOffset; + mdemBuffer->trainedAt = res.value.lastTrainedAt - 3600 * timezoneOffset; } std::cout << std::format("Last trained at: {}", mdemBuffer->trainedAt) << std::endl; @@ -705,13 +713,13 @@ void setupEditorSave(bool checked) { for (int i = 0; i < mdemBuffer->questions.size(); ++i) { if (mdemBuffer->questions[i] == oldQuestion) { mdemBuffer->questions.erase(mdemBuffer->questions.begin() + i); - delete mdemBuffer->questions[i]; - mdemBuffer->questions[i] = editMdem->question; + delete oldQuestion; + mdemBuffer->questions.insert(mdemBuffer->questions.begin() + i, res.value.questions[0]); break; } } - /*setupMdem(editMdem, res.value.questions[0]);*/ - /*showBacklabels(editMdem);*/ + setupMdem(editMdem, res.value.questions[0]); + showBacklabels(editMdem); editorWindow->hide(); } else { QMessageBox::information( @@ -741,6 +749,12 @@ void setupEditorSave(bool checked) { } }; +void saveMdem() { + auto filename = getFilename(currentMdem); + std::ofstream out(currentMdem); + out << outputMdem(mdemBuffer->questions, mdemBuffer->trainedAt); +} + int main(int argc, char *argv[]) { mdemBuffer = new MdemBuffer; QApplication app(argc, argv); @@ -952,9 +966,9 @@ int main(int argc, char *argv[]) { hlButtonsTop->addWidget(add); hlButtonsTop->addWidget(save); hlButtonsTop->addWidget(load); - cbAlgorithm->addItem("Primary", PRIMARY); - cbAlgorithm->addItem("Random", RANDOM); cbAlgorithm->addItem("Spaced", SPACED); + cbAlgorithm->addItem("Random", RANDOM); + cbAlgorithm->addItem("Primary", PRIMARY); practice->setText("Practice"); hlButtonsBottom->addWidget(cbAlgorithm); hlButtonsBottom->addWidget(practice); @@ -972,9 +986,7 @@ int main(int argc, char *argv[]) { }); QObject::connect(load, &QToolButton::clicked, &reloadMdem); QObject::connect(save, &QToolButton::clicked, []() { - auto filename = getFilename(currentMdem); - std::ofstream out(currentMdem); - out << outputMdem(mdemBuffer->questions, mdemBuffer->trainedAt); + saveMdem(); }); QObject::connect( practice, @@ -983,9 +995,8 @@ int main(int argc, char *argv[]) { trainWindow->show(); trainWindow->resize(600, 300); initiatePractice( - mdemBuffer->questions, - static_cast(cbAlgorithm->currentData().toInt()), - &mdemBuffer->trainedAt + mdemBuffer, + static_cast(cbAlgorithm->currentData().toInt()) ); } ); diff --git a/src/qtapp/trainWindow.cpp b/src/qtapp/trainWindow.cpp index c6cc9ef..ab824ce 100644 --- a/src/qtapp/trainWindow.cpp +++ b/src/qtapp/trainWindow.cpp @@ -2,14 +2,14 @@ #include #include #include -#include #include +#include #include #include -#include #include #include #include +#include #include #include #include @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include +#include "main.h" #include "trainWindow.h" #include "parser.h" @@ -204,7 +206,6 @@ QLabel *lQuestionText; QVBoxLayout *vButtonBox; QWidget *actionButtons; QHBoxLayout *hButtons; -QToolButton *btnPrev; QSpacerItem *leftSpacer; QToolButton *btnTriggerAnswer; QSpacerItem *rightSpacer; @@ -229,10 +230,12 @@ QVBoxLayout *vGroups; std::vector groupModels; // Questions & State -std::vector trainQuestions = std::vector(); +MdemBuffer *currentBuffer; + int32_t currentQuestionIndex = -1; std::vector groupViews; PracticeAlgorithm practiceAlgoritm; +time_t lastTrainedAt; std::default_random_engine rng; std::vector itemPool; @@ -285,6 +288,11 @@ void hideQuestionElements() { orderList->hide(); multiChoiceList->hide(); wGroupQuestion->hide(); + btnNotRemembered->hide(); + btnHard->hide(); + btnMedium->hide(); + btnEasy->hide(); + btnTriggerAnswer->hide(); } void setupAnswerQuestion(MultiElementQuestion *question) { @@ -308,6 +316,10 @@ void setupAnswerQuestion(MultiElementQuestion *question) { [](bool checked) { answerText->show(); btnTriggerAnswer->hide(); + btnNotRemembered->show(); + btnHard->show(); + btnMedium->show(); + btnEasy->show(); } ); btnTriggerAnswer->show(); @@ -491,63 +503,132 @@ void setupQuestion(Question *question) { hideQuestionElements(); releaseAllItems(); QObject::disconnect(btnTriggerAnswer, 0, 0, 0); - if (auto *question = dynamic_cast(trainQuestions[currentQuestionIndex])) { - btnNotRemembered->hide(); - btnHard->hide(); - btnMedium->hide(); - btnEasy->hide(); - switch (question->type) { + if (auto *q = dynamic_cast(question)) { + switch (q->type) { case MultiElementType::Order: - setupOrderQuestion(question); + setupOrderQuestion(q); break; case MultiElementType::MultiChoice: - setupMultiChoiceQuestion(question); + setupMultiChoiceQuestion(q); break; case MultiElementType::Regular: - setupAnswerQuestion(question); + setupAnswerQuestion(q); break; } - } else if (auto *question = dynamic_cast(trainQuestions[currentQuestionIndex])) { + } else if (auto *question = dynamic_cast( + currentBuffer->questions[currentQuestionIndex]) + ) { setupGroupQuestion(question); } } void updatePaginationVisibility() { - if (currentQuestionIndex == 0) { - btnPrev->hide(); - } else { - btnPrev->show(); - } - if (currentQuestionIndex == trainQuestions.size() - 1) { + if (currentQuestionIndex == currentBuffer->questions.size() - 1) { btnNext->hide(); } else { btnNext->show(); } } -void initiatePractice( - std::vector questions, - PracticeAlgorithm algorithm, - time_t *trainedAt -) { +template +int randomIndex(std::vector *vec) { + srand(time(NULL)); + return rand() % vec->size(); +} + +time_t getTime() { auto now = std::chrono::system_clock::now(); - time_t unix_timestamp = std::chrono::duration_cast( + return std::chrono::duration_cast( now.time_since_epoch() ).count(); +} - trainQuestions = questions; - if (questions.size() <= 0) { +void setupNextQuestion() { + if (currentBuffer->questions.size() <= 0) { return; } - currentQuestionIndex = 0; - updatePaginationVisibility(); - setupQuestion(trainQuestions[currentQuestionIndex]); - practiceAlgoritm = algorithm; + switch (practiceAlgoritm) { + case PRIMARY: { + currentQuestionIndex++; + if (currentQuestionIndex < currentBuffer->questions.size()) { + setupQuestion(currentBuffer->questions[currentQuestionIndex]); + } + updatePaginationVisibility(); + } break; + case RANDOM: { + auto questionCandidates = currentBuffer->questions; + if (currentQuestionIndex > -1) { + questionCandidates.erase(questionCandidates.begin() + currentQuestionIndex); + } + if (questionCandidates.size() > 0) { + auto i = randomIndex(&questionCandidates); + setupQuestion(questionCandidates[i]); + for (int k = 0; k < currentBuffer->questions.size(); ++k) { + if (currentBuffer->questions[k] == questionCandidates[i]) { + currentQuestionIndex = k; + break; + } + } + } + } break; + case SPACED: { + auto questionCandidates = std::vector(); + time_t time = getTime(); + auto lastTrainedAt = currentBuffer->trainedAt; + currentBuffer->trainedAt = time; + for (int i = 0; i < currentBuffer->questions.size(); ++i) { + auto cooldownSeconds = currentBuffer->questions[i]->Cooldown * 3600; + auto cooldownEndsAt = lastTrainedAt + cooldownSeconds; + if (i != currentQuestionIndex && cooldownEndsAt <= time) { + questionCandidates.push_back(currentBuffer->questions[i]); + } + auto newCooldown = cooldownEndsAt - time; + if (newCooldown < 0) { + newCooldown = 0; + } + currentBuffer->questions[i]->Cooldown = (double)newCooldown / 3600; + } + if (questionCandidates.size() > 0) { + auto i = randomIndex(&questionCandidates); + setupQuestion(questionCandidates[i]); + setupQuestion( + questionCandidates[randomIndex(&questionCandidates)] + ); + for (int k = 0; k < currentBuffer->questions.size(); ++k) { + if (currentBuffer->questions[k] == questionCandidates[i]) { + currentQuestionIndex = k; + break; + } + } + } + update(); + } break; + } +} - btnNotRemembered->hide(); - btnHard->hide(); - btnMedium->hide(); - btnEasy->hide(); +void initiatePractice( + MdemBuffer *mdemBuffer, + PracticeAlgorithm algorithm +) { + practiceAlgoritm = algorithm; + currentBuffer = mdemBuffer; + + if (currentBuffer->questions.size() <= 0) { + return; + } + + currentQuestionIndex = -1; + hideQuestionElements(); + updatePaginationVisibility(); + setupNextQuestion(); +} + +void setCooldownHours(int cooldown) { + time_t time = getTime(); + currentBuffer->trainedAt = time; + auto question = currentBuffer->questions[currentQuestionIndex]; + question->Cooldown = cooldown; + update(); } void initTrainWindow() { @@ -566,6 +647,25 @@ void initTrainWindow() { itemDelegate = new CustomItemDelegate(multiChoiceList); + { // Top button menu. + auto hTopButtons = new QHBoxLayout(); + auto topButtons = new QWidget(); + topButtons->setLayout(hTopButtons); + + auto topLeftSpacer = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum); + auto btnSaveProgress = new QToolButton(); + btnSaveProgress->setText("Save progress"); + + hTopButtons->addItem(topLeftSpacer); + hTopButtons->addWidget(btnSaveProgress); + vTrainWidget->addWidget(topButtons); + + QObject::connect(btnSaveProgress, &QToolButton::clicked, []() { + saveMdem(); + }); + } + + { // Make the question box. questionBox = new QWidget(); vQuestionBox = new QVBoxLayout(); @@ -639,16 +739,26 @@ void initTrainWindow() { vButtonBox = new QVBoxLayout(); actionButtons = new QWidget(); hButtons = new QHBoxLayout(); - btnPrev = new QToolButton(); + leftSpacer = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum); btnTriggerAnswer = new QToolButton(); - btnNotRemembered = new QToolButton(); + QObject::connect(btnNotRemembered, &QToolButton::clicked, []() { + setCooldownHours(0); + }); btnHard = new QToolButton(); + QObject::connect(btnHard, &QToolButton::clicked, []() { + setCooldownHours(12); + }); btnMedium = new QToolButton(); + QObject::connect(btnMedium, &QToolButton::clicked, []() { + setCooldownHours(48); + }); btnEasy = new QToolButton(); - + QObject::connect(btnEasy, &QToolButton::clicked, []() { + setCooldownHours(72); + }); btnNotRemembered->setText("Not remembered"); btnHard->setText("Hard"); btnMedium->setText("Medium"); @@ -659,17 +769,10 @@ void initTrainWindow() { rightSpacer = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum); btnNext = new QToolButton(); - hButtons->addWidget(btnPrev); hButtons->addItem(leftSpacer); hButtons->addWidget(btnTriggerAnswer); - hButtons->addWidget(btnNotRemembered); hButtons->addWidget(btnHard); - QObject::connect(btnHard, &QToolButton::clicked, []() { - const double hourCooldown = 25.0; - auto question = trainQuestions[currentQuestionIndex]; - question->Cooldown = hourCooldown; - }); hButtons->addWidget(btnMedium); hButtons->addWidget(btnEasy); @@ -677,34 +780,13 @@ void initTrainWindow() { hButtons->addWidget(btnNext); vButtonBox->addWidget(actionButtons); actionButtons->setLayout(hButtons); - btnPrev->setText("Previous"); - QObject::connect(btnPrev, &QToolButton::clicked, []() { - currentQuestionIndex--; - if (currentQuestionIndex > -1) { - setupQuestion(trainQuestions[currentQuestionIndex]); - } - updatePaginationVisibility(); - }); btnTriggerAnswer->setText("Show answer"); + btnTriggerAnswer->hide(); btnNext->setText("Next"); QObject::connect(btnNext, &QToolButton::clicked, []() { - switch (practiceAlgoritm) { - case PRIMARY: - currentQuestionIndex++; - if (currentQuestionIndex < trainQuestions.size()) { - setupQuestion(trainQuestions[currentQuestionIndex]); - } - updatePaginationVisibility(); - break; - case RANDOM: - // TODO: implement - break; - case SPACED: - // TODO: implement - break; - } + setupNextQuestion(); }); questionBox->setObjectName("question-box"); actionButtons->setStyleSheet(QString( diff --git a/src/transpiler/parser.cpp b/src/transpiler/parser.cpp index de1be11..6a0d697 100644 --- a/src/transpiler/parser.cpp +++ b/src/transpiler/parser.cpp @@ -141,6 +141,18 @@ Result ValidateGrammar(const std::vector& tokens) { return {}; } +time_t parseToUTCTime(const std::string datetime, std::string format) { + std::tm tm = {}; + std::istringstream ss(datetime); + ss >> std::get_time(&tm, format.c_str()); + if (ss.fail()) { + throw std::runtime_error("Failed to parse datetime string"); + } + std::time_t time = timegm(&tm); + return time; +} + + Result parseQuestions(const std::vector& tokens) { auto questions = std::vector(); time_t time = 0; @@ -178,16 +190,14 @@ Result parseQuestions(const std::vector& tokens) { }; if (isInBounds(i) && tokens[i].tokenType == TokenType::TextFragment) { - std::tm tm = {}; try { - strptime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M", &tm); + time = parseToUTCTime(tokens[i].content.c_str(), "%d.%m.%Y %H:%M"); } catch (std::exception e) { return makeResult( std::format("cannot parse the time - {}", e.what()), tokens[i] ); } - time = mktime(&tm); i++; }