-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypingtest.cpp
More file actions
155 lines (143 loc) · 5.37 KB
/
Copy pathtypingtest.cpp
File metadata and controls
155 lines (143 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <random>
#include <QDebug>
#include <QStringView>
#include <QString>
#include <algorithm>
#include "typingtest.h"
TypingTest::TypingTest(const std::vector<QString>& wordDataset,
unsigned wordsPerSample, QObject *parent)
: QObject{parent}, m_wordsPerSample{wordsPerSample},
m_currentWordSample(wordsPerSample, ""), m_wordDataset{wordDataset}, m_rng{m_rd()},
m_randomWordIdx{std::uniform_int_distribution<int>(0, m_wordDataset.size()-1)}
{
reset();
}
void TypingTest::reset()
{
m_correctChars = 0;
m_totalTypedChars = 0;
m_totalAcceptedChars = 0;
sampleWordDataset();
updateTestPrompt(true);
}
void TypingTest::sampleWordDataset()
{
m_currentWordIdx = 0;
auto getRandomWord = [&] { return m_wordDataset[m_randomWordIdx(m_rng)]; };
std::generate(m_currentWordSample.begin(), m_currentWordSample.end(), getRandomWord);
}
bool TypingTest::newCharIsCorrect(const QString& currentWord, const QString& input) const
{
if (input.size() > currentWord.size()) {
return input.endsWith(" ");
}
return input.back() == currentWord[input.size()-1];
}
/*
* This function should be called every time the user presses a key.
*
* - Update character counts (corrent and total).
* - Update color of the current word depending on text input correctness.
* - Go to the next word if space was pressed.
* - Load new word sample if all words in the current sample were typed.
*/
void TypingTest::processKbInput(const QString& input, bool backspacePressed, bool spacePressed)
{
const QString& currentTestWord = m_currentWordSample[m_currentWordIdx];
QString wordColor;
bool resetTestPrompt = false;
/* Update character counts only if keypress was not backspace. */
if (!backspacePressed) {
if (newCharIsCorrect(currentTestWord, input)) {
m_correctChars++;
}
m_totalTypedChars++;
}
if (spacePressed) {
/* Update accepted character count, set color for typed word
* and change active word in test prompt. */
QStringView typedWord(input.constData(), input.size()-1);
if (typedWord == currentTestWord) {
m_totalAcceptedChars += input.size();
wordColor = "#fae1c3";
} else {
wordColor = "#bb1e10";
}
m_testPromptFinished.append(
QString("<font color='%1'>%2 </font>").arg(wordColor, currentTestWord));
m_currentWordIdx++;
if (m_currentWordIdx == m_wordsPerSample) {
sampleWordDataset();
resetTestPrompt = true;
} else {
const QString& nextTestWord = m_currentWordSample[m_currentWordIdx];
m_testPromptCurrentWord =
QString("<u>%1</u>").arg(nextTestWord);
m_testPromptUntyped.remove(0, nextTestWord.size()+1);
}
} else {
/* Set color for each typed letter of current word,
* depending on correctness. */
setCurrentWordColor(currentTestWord, input);
}
updateTestPrompt(resetTestPrompt);
}
/*
* Color each letter of the current word:
* - for typed letters choose color (white/red) depending on correctness
* - keep default color (gray, defined in TestInterface.qml) if the letter wasn't typed yet
*/
void TypingTest::setCurrentWordColor(const QString& currentWord, const QString& userInput)
{
unsigned numTypedChars = std::min(currentWord.size(), userInput.size());
unsigned numUntypedChars = currentWord.size() - numTypedChars;
m_testPromptCurrentWord.clear();
for (unsigned i = 0; i < numTypedChars; i++) {
QString color = "#fae1c3";
if (userInput[i] != currentWord[i]) {
color = "#bb1e10";
}
m_testPromptCurrentWord.append(
QString("<font color='%1'>%2</font>").arg(color, currentWord[i]));
}
/* append untyped part of word (with default color) */
m_testPromptCurrentWord.append(QStringView(currentWord).right(numUntypedChars));
m_testPromptCurrentWord = QString("<u>%1</u>").arg(m_testPromptCurrentWord);
}
/*
* testPrompt contains a sample of words that the user should type.
* The current word is underlined.
* Words are colored depending on input correctness.
*/
QString TypingTest::testPrompt() const {
return m_testPrompt;
}
void TypingTest::updateTestPrompt(bool initialize=false) {
if (initialize) {
m_testPromptFinished.clear();
m_testPromptCurrentWord = QString("<u>%1</u>").arg(m_currentWordSample[0]);
m_testPromptUntyped.clear();
m_testPromptUntyped = std::accumulate(
m_currentWordSample.begin()+1, m_currentWordSample.end(), m_testPromptUntyped,
[](QString& untypedWords, QString& word) { return untypedWords + " " + word; });
}
m_testPrompt.clear();
m_testPrompt.append(m_testPromptFinished);
m_testPrompt.append(m_testPromptCurrentWord);
m_testPrompt.append(m_testPromptUntyped);
emit testPromptChanged();
}
/*
* Calculate the number of correctly typed words per minute.
* Assume average word length is 5 like on most typing websites.
*/
unsigned TypingTest::calculateWPM(unsigned testTimeSec) const
{
float wpm = static_cast<float>(m_totalAcceptedChars) / 5 * 60. / testTimeSec;
return static_cast<unsigned>(wpm);
}
unsigned TypingTest::calculateAccuracy() const
{
float acc = static_cast<float>(m_correctChars) / m_totalTypedChars;
return static_cast<unsigned>(acc * 100);
}