1// User
2let lineMaxCharacterLength = 40;
3let lineCount = 4;
4
5// System
6let threeKListLength = top_threek_english_words.length;
7let lineData = [];
8let userCursorPosition = 0;
9let isShiftPressed = false;
10let runStartTime = new Date();
11let runStarted = false;
12let runErrors = [];
13let writtenLetters = [];
14
15function pickRandomWordFromList() {
16 return top_threek_english_words[
17 Math.round(Math.random() * (threeKListLength - 1))
18 ];
19}
20
21function generateLines() {
22 let generatedLineData = [];
23 for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
24 let currentLineData = "";
25 let maxCharacterLengthReached = false;
26 while (!maxCharacterLengthReached) {
27 let newCurrentLineData = currentLineData.concat(
28 pickRandomWordFromList() + " "
29 );
30 if (newCurrentLineData.length > lineMaxCharacterLength) {
31 maxCharacterLengthReached = true;
32 } else {
33 currentLineData = newCurrentLineData;
34 }
35 }
36 generatedLineData.push(currentLineData);
37 }
38 return generatedLineData;
39}
40
41function getCharacterFromIndex(lineData, index) {
42 lineData;
43 let currentCharacterId = 0;
44 for (let lineIndex in lineData) {
45 let currentLineString = lineData[lineIndex];
46 for (let currentCharacterIndex in currentLineString) {
47 if (index == currentCharacterId) {
48 return currentLineString[currentCharacterIndex];
49 }
50 currentCharacterId++;
51 }
52 }
53 return "Not found.";
54}
55
56function getCharacterCount(lineData) {
57 let currentCharacterId = 0;
58 for (let lineIndex in lineData) {
59 let currentLineString = lineData[lineIndex];
60 for (let currentCharacterIndex in currentLineString) {
61 currentCharacterId++;
62 }
63 }
64 return currentCharacterId;
65}
66
67function getWordCount(lineData) {
68 let wordCount = 0;
69 for (let lineIndex in lineData) {
70 let currentLineString = lineData[lineIndex];
71 wordCount += currentLineString.split(" ").length;
72 }
73 return wordCount;
74}
75
76function formatForHTML(stringToFormat) {
77 return stringToFormat.replaceAll(" ", " ");
78}
79
80function renderLineDataToScreen() {
81 let linesContainerDiv = document.getElementById("lines-container");
82 linesContainerDiv.innerHTML = "";
83 let currentCharacterId = 0;
84 for (let lineIndex in lineData) {
85 let lineDiv = document.createElement("div");
86 lineDiv.className = "line-div";
87 let currentLineString = lineData[lineIndex];
88 for (let currentCharacterIndex in currentLineString) {
89 let characterDiv = document.createElement("div");
90 characterDiv.id = "character-div-" + currentCharacterId;
91 characterDiv.className = "character-div";
92 if (runErrors.includes(currentCharacterId)) {
93 characterDiv.className += " character-div-error";
94 }
95 if (writtenLetters.includes(currentCharacterId)) {
96 characterDiv.className += " character-div-written";
97 }
98 characterDiv.innerHTML = formatForHTML(
99 currentLineString[currentCharacterIndex]
100 );
101 lineDiv.appendChild(characterDiv);
102 currentCharacterId++;
103 }
104 linesContainerDiv.appendChild(lineDiv);
105 }
106}
107
108function updateCursorPosition() {
109 let cursorElement = document.getElementById("cursor-div");
110 cursorElement.innerHTML = formatForHTML(
111 getCharacterFromIndex(lineData, userCursorPosition)
112 );
113 let targetBoudingRect = {
114 left: "-50",
115 top: "50",
116 right: "-50",
117 bottom: "50",
118 };
119 try {
120 targetBoudingRect = document
121 .getElementById("character-div-" + userCursorPosition)
122 .getBoundingClientRect();
123 } catch {
124 console.log("unsafe behavior but I don't care");
125 }
126 cursorElement.style.top = targetBoudingRect.top + "px";
127 cursorElement.style.left = targetBoudingRect.left + "px";
128 cursorElement.style.width =
129 targetBoudingRect.right - targetBoudingRect.left + "px";
130 cursorElement.style.height =
131 targetBoudingRect.bottom - targetBoudingRect.top + "px";
132}
133
134function updateStats() {
135 let statusContainer = document.getElementById("status-container");
136 let characterCount = getCharacterCount(lineData);
137 let wordCount = getWordCount(lineData);
138 let avgTypedWords = wordCount * (userCursorPosition / characterCount);
139 let timeSinceStart = new Date() - runStartTime;
140 if (runStarted) {
141 statusContainer.innerHTML = `
142 <div id="status-wpm">WPM: ${Math.round(
143 (avgTypedWords / (timeSinceStart / 1000)) * 60
144 )}</div>
145 <div id="status-errors">Errors: ${runErrors.length}</div>
146 <div id="status-accuracy">Accuracy: ${Math.round(
147 ((characterCount - runErrors.length) / characterCount) * 100
148 )}%</div>
149 <div id="status-time">Time: ${String(
150 Math.floor(Math.floor(timeSinceStart / 1000) / 60)
151 ).padStart(2, "0")}:${String(
152 Math.floor(timeSinceStart / 1000) % 60
153 ).padStart(2, "0")}.${Math.floor((timeSinceStart % 1000) / 100)}</div>
154 `;
155 }
156}
157
158function initialiseTest() {
159 lineData = generateLines();
160 renderLineDataToScreen();
161}
162
163document.addEventListener("keyup", function (event) {
164 if (event.keyCode == 16) {
165 isShiftPressed = false;
166 }
167});
168
169function advanceCursor() {
170 if (userCursorPosition == 0) {
171 runStartTime = new Date();
172 runStarted = true;
173 }
174 userCursorPosition++;
175}
176
177document.addEventListener("keydown", function (event) {
178 let characterTyped = String.fromCharCode(event.keyCode);
179 let isCapsLockOn = event.getModifierState("CapsLock");
180 if (event.keyCode == 8) {
181 if (userCursorPosition != 0) {
182 userCursorPosition--;
183 var index = runErrors.indexOf(userCursorPosition);
184 if (index !== -1) {
185 runErrors.splice(index, 1);
186 }
187 index = writtenLetters.indexOf(userCursorPosition);
188 if (index !== -1) {
189 writtenLetters.splice(index, 1);
190 }
191 }
192 }
193 if (event.keyCode == 27) {
194 initialiseTest();
195 runStartTime = new Date();
196 runStarted = false;
197 runErrors = [];
198 writtenLetters = [];
199 userCursorPosition = 0;
200 }
201 if (event.keyCode == 109 || event.keyCode == 54) {
202 characterTyped = "-";
203 }
204 if (event.keyCode == 16) {
205 isShiftPressed = true;
206 }
207 if (
208 !((!isCapsLockOn && isShiftPressed) || (isCapsLockOn && !isShiftPressed))
209 ) {
210 characterTyped = characterTyped.toLowerCase();
211 }
212 let pendingCharacter = getCharacterFromIndex(lineData, userCursorPosition);
213 if (pendingCharacter == characterTyped) {
214 writtenLetters.push(userCursorPosition);
215 advanceCursor();
216 } else {
217 if (![20, 16, 8, 27].includes(event.keyCode)) {
218 runErrors.push(userCursorPosition);
219 advanceCursor();
220 }
221 }
222 if (userCursorPosition == getCharacterCount(lineData)) {
223 runStarted = false;
224 }
225 updateCursorPosition();
226 renderLineDataToScreen();
227});
228
229initialiseTest();
230setTimeout(updateCursorPosition, 100);
231setInterval(updateStats, 100);