spacing with correct cooldowns and updates

This commit is contained in:
jorenchik
2024-10-19 17:21:19 +03:00
parent 2f2818b44b
commit 2d1589c94d
5 changed files with 234 additions and 117 deletions

14
src/include/main.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include "parser.h"
void update();
void saveMdem();
struct MdemBuffer {
std::vector<Question*> questions = std::vector<Question*>();
time_t trainedAt = 0;
};

View File

@@ -2,6 +2,7 @@
#include <QMainWindow>
#include "main.h"
#include "parser.h"
extern QMainWindow *trainWindow;
@@ -15,7 +16,6 @@ enum PracticeAlgorithm {
void initTrainWindow();
void initiatePractice(
std::vector<Question*> questions,
PracticeAlgorithm algorithm,
time_t *trainedAt
MdemBuffer *mdemBuffer,
PracticeAlgorithm algorithm
);

View File

@@ -58,6 +58,7 @@
#include <QPushButton>
#include <QStandardPaths>
#include "main.h"
#include "api.h"
#include "parser.h"
#include "qscilexer.h"
@@ -86,11 +87,6 @@ struct Mdem {
Question *question;
};
struct MdemBuffer {
std::vector<Question*> questions = std::vector<Question*>();
time_t trainedAt = 0;
};
struct ErrorView {
QWidget box;
QVBoxLayout layout;
@@ -164,6 +160,8 @@ std::string outputMdem(std::vector<Question*> 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<MultiElementQuestion*>(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<Question*>& 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<PracticeAlgorithm>(cbAlgorithm->currentData().toInt()),
&mdemBuffer->trainedAt
mdemBuffer,
static_cast<PracticeAlgorithm>(cbAlgorithm->currentData().toInt())
);
}
);

View File

@@ -2,14 +2,14 @@
#include <QWidget>
#include <QToolButton>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <ctime>
#include <format>
#include <cstdlib>
#include <exception>
#include <format>
#include <qabstractitemview.h>
#include <qboxlayout.h>
#include <qchar.h>
#include <qcoreevent.h>
#include <qlabel.h>
#include <qlayoutitem.h>
@@ -17,6 +17,7 @@
#include <qnamespace.h>
#include <qscrollarea.h>
#include <qstandarditemmodel.h>
#include <qstringalgorithms.h>
#include <qstringlistmodel.h>
#include <qtoolbutton.h>
#include <Qt>
@@ -29,6 +30,7 @@
#include <QStyledItemDelegate>
#include <QPainter>
#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<QStandardItemModel*> groupModels;
// Questions & State
std::vector<Question*> trainQuestions = std::vector<Question*>();
MdemBuffer *currentBuffer;
int32_t currentQuestionIndex = -1;
std::vector<GroupView*> groupViews;
PracticeAlgorithm practiceAlgoritm;
time_t lastTrainedAt;
std::default_random_engine rng;
std::vector<QStandardItem*> 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<MultiElementQuestion*>(trainQuestions[currentQuestionIndex])) {
btnNotRemembered->hide();
btnHard->hide();
btnMedium->hide();
btnEasy->hide();
switch (question->type) {
if (auto *q = dynamic_cast<MultiElementQuestion*>(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<GroupQuestion*>(trainQuestions[currentQuestionIndex])) {
} else if (auto *question = dynamic_cast<GroupQuestion*>(
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<Question*> questions,
PracticeAlgorithm algorithm,
time_t *trainedAt
) {
template <typename T>
int randomIndex(std::vector<T> *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<std::chrono::seconds>(
return std::chrono::duration_cast<std::chrono::seconds>(
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<Question*>();
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(

View File

@@ -141,6 +141,18 @@ Result<NoneType> ValidateGrammar(const std::vector<Token>& 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<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
auto questions = std::vector<Question*>();
time_t time = 0;
@@ -178,16 +190,14 @@ Result<ParseInfo> parseQuestions(const std::vector<Token>& 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++;
}