@testgorilla/tgo-typing-test 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/fesm2022/testgorilla-tgo-typing-test.mjs +4691 -0
  2. package/fesm2022/testgorilla-tgo-typing-test.mjs.map +1 -0
  3. package/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.d.ts +14 -0
  4. package/lib/components/tgo-typing-test/tgo-typing-test.component.d.ts +54 -0
  5. package/lib/helpers/config.d.ts +98 -0
  6. package/lib/helpers/constants/default-config.d.ts +3 -0
  7. package/lib/helpers/controllers/input-controller.d.ts +16 -0
  8. package/lib/helpers/controllers/quotes-controller.d.ts +20 -0
  9. package/lib/helpers/observables/config-event.d.ts +5 -0
  10. package/lib/helpers/observables/timer-event.d.ts +4 -0
  11. package/lib/helpers/states/active-page.d.ts +2 -0
  12. package/lib/helpers/states/composition.d.ts +10 -0
  13. package/lib/helpers/states/page-transition.d.ts +2 -0
  14. package/lib/helpers/states/slow-timer.d.ts +3 -0
  15. package/lib/helpers/states/test-active.d.ts +2 -0
  16. package/lib/helpers/states/time.d.ts +3 -0
  17. package/lib/helpers/test/caps-warning.d.ts +5 -0
  18. package/lib/helpers/test/caret.d.ts +11 -0
  19. package/lib/helpers/test/custom-text.d.ts +16 -0
  20. package/lib/helpers/test/english-punctuation.d.ts +3 -0
  21. package/lib/helpers/test/focus.d.ts +7 -0
  22. package/lib/helpers/test/manual-restart-tracker.d.ts +3 -0
  23. package/lib/helpers/test/out-of-focus.d.ts +4 -0
  24. package/lib/helpers/test/replay.d.ts +20 -0
  25. package/lib/helpers/test/test-input.d.ts +86 -0
  26. package/lib/helpers/test/test-logic.d.ts +25 -0
  27. package/lib/helpers/test/test-state.d.ts +7 -0
  28. package/lib/helpers/test/test-stats.d.ts +92 -0
  29. package/lib/helpers/test/test-timer.d.ts +6 -0
  30. package/lib/helpers/test/test-ui.d.ts +27 -0
  31. package/lib/helpers/test/test-words.d.ts +23 -0
  32. package/lib/helpers/test/timer-progress.d.ts +3 -0
  33. package/lib/helpers/test/weak-spot.d.ts +3 -0
  34. package/lib/helpers/test/wordset.d.ts +7 -0
  35. package/lib/utils/misc.d.ts +80 -0
  36. package/package.json +17 -4
  37. package/.eslintrc.json +0 -46
  38. package/jest.config.ts +0 -21
  39. package/ng-package.json +0 -15
  40. package/project.json +0 -36
  41. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.html +0 -30
  42. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.spec.ts +0 -250
  43. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.ts +0 -47
  44. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.html +0 -72
  45. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.spec.ts +0 -699
  46. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.ts +0 -287
  47. package/src/lib/helpers/config.ts +0 -28
  48. package/src/lib/helpers/constants/default-config.ts +0 -103
  49. package/src/lib/helpers/controllers/input-controller.ts +0 -710
  50. package/src/lib/helpers/controllers/quotes-controller.ts +0 -183
  51. package/src/lib/helpers/observables/banner-event.ts +0 -18
  52. package/src/lib/helpers/observables/config-event.ts +0 -31
  53. package/src/lib/helpers/observables/timer-event.ts +0 -18
  54. package/src/lib/helpers/states/active-page.ts +0 -9
  55. package/src/lib/helpers/states/composition.ts +0 -29
  56. package/src/lib/helpers/states/page-transition.ts +0 -9
  57. package/src/lib/helpers/states/slow-timer.ts +0 -16
  58. package/src/lib/helpers/states/test-active.ts +0 -9
  59. package/src/lib/helpers/states/time.ts +0 -13
  60. package/src/lib/helpers/test/caps-warning.ts +0 -50
  61. package/src/lib/helpers/test/caret.ts +0 -92
  62. package/src/lib/helpers/test/custom-text.ts +0 -73
  63. package/src/lib/helpers/test/english-punctuation.ts +0 -38
  64. package/src/lib/helpers/test/focus.ts +0 -39
  65. package/src/lib/helpers/test/manual-restart-tracker.ts +0 -13
  66. package/src/lib/helpers/test/out-of-focus.ts +0 -19
  67. package/src/lib/helpers/test/replay.ts +0 -265
  68. package/src/lib/helpers/test/test-input.ts +0 -320
  69. package/src/lib/helpers/test/test-logic.ts +0 -1039
  70. package/src/lib/helpers/test/test-state.ts +0 -17
  71. package/src/lib/helpers/test/test-stats.ts +0 -442
  72. package/src/lib/helpers/test/test-timer.ts +0 -209
  73. package/src/lib/helpers/test/test-ui.ts +0 -370
  74. package/src/lib/helpers/test/test-words.ts +0 -72
  75. package/src/lib/helpers/test/timer-progress.ts +0 -16
  76. package/src/lib/helpers/test/tts.ts +0 -42
  77. package/src/lib/helpers/test/weak-spot.ts +0 -74
  78. package/src/lib/helpers/test/wordset.ts +0 -109
  79. package/src/lib/styles/animations.scss +0 -101
  80. package/src/lib/styles/caret.scss +0 -108
  81. package/src/lib/styles/core.scss +0 -498
  82. package/src/lib/styles/index.scss +0 -19
  83. package/src/lib/styles/inputs.scss +0 -290
  84. package/src/lib/styles/popups.scss +0 -1311
  85. package/src/lib/styles/test.scss +0 -1008
  86. package/src/lib/styles/z_media-queries.scss +0 -848
  87. package/src/lib/types/types.d.ts +0 -731
  88. package/src/lib/utils/misc.ts +0 -776
  89. package/src/test-setup.ts +0 -20
  90. package/tsconfig.json +0 -16
  91. package/tsconfig.lib.json +0 -14
  92. package/tsconfig.lib.prod.json +0 -9
  93. package/tsconfig.spec.json +0 -11
  94. /package/{src/assets → assets}/typing-test-languages/english.json +0 -0
  95. /package/{src/assets → assets}/typing-test-languages/english_punctuation.json +0 -0
  96. /package/{src/assets → assets}/typing-test-languages/quotes/english_version_1.json +0 -0
  97. /package/{src/assets → assets}/typing-test-languages/quotes/english_version_2.json +0 -0
  98. /package/{src/assets → assets}/typing-test-languages/quotes/filtered_sources.json +0 -0
  99. /package/{src/index.ts → index.d.ts} +0 -0
@@ -1,1039 +0,0 @@
1
- import { BehaviorSubject, Subject } from 'rxjs';
2
- import Config from '../../helpers/config';
3
- import { MonkeyTypes } from '../../types/types';
4
- import * as Misc from '../../utils/misc';
5
- import QuotesController from '../controllers/quotes-controller';
6
- import * as TimerEvent from '../observables/timer-event';
7
- import * as ActivePage from '../states/active-page';
8
- import * as PageTransition from '../states/page-transition';
9
- import * as TestActive from '../states/test-active';
10
- import * as Caret from './caret';
11
- import * as CustomText from './custom-text';
12
- import * as EnglishPunctuation from './english-punctuation';
13
- import * as Focus from './focus';
14
- import * as ManualRestart from './manual-restart-tracker';
15
- import * as OutOfFocus from './out-of-focus';
16
- import * as Replay from './replay';
17
- import * as TestInput from './test-input';
18
- import * as TestState from './test-state';
19
- import * as TestStats from './test-stats';
20
- import * as TestTimer from './test-timer';
21
- import * as TestUI from './test-ui';
22
- import * as TestWords from './test-words';
23
- import * as TimerProgress from './timer-progress';
24
- import * as WeakSpot from './weak-spot';
25
- import * as Wordset from './wordset';
26
-
27
- let failReason = '';
28
-
29
- let wordsInput: HTMLInputElement;
30
-
31
- export const startTestSubject = new BehaviorSubject<boolean>(false);
32
- export const resultSub = new Subject<any>();
33
-
34
- export let notSignedInLastResult: MonkeyTypes.Result<MonkeyTypes.Mode> | null = null;
35
-
36
- export function setWordsInputElementTestLogic(wordsElement: HTMLInputElement) {
37
- wordsInput = wordsElement;
38
- }
39
-
40
- export function clearNotSignedInResult(): void {
41
- notSignedInLastResult = null;
42
- }
43
-
44
- function shouldCapitalize(lastChar: string): boolean {
45
- return /[?!.؟]/.test(lastChar);
46
- }
47
-
48
- function addDecimalPoint(word: string): string {
49
- if (Math.random() < 0.8) {
50
- const decimal = Misc.getNumbers(3, 0);
51
- if (decimal != '') {
52
- word = word + '.' + decimal;
53
- }
54
- }
55
- return word;
56
- }
57
-
58
- let spanishSentenceTracker = '';
59
- export async function punctuateWord(
60
- previousWord: string,
61
- currentWord: string,
62
- index: number,
63
- maxindex: number
64
- ): Promise<string> {
65
- let word = currentWord;
66
-
67
- const currentLanguage = Config.language.split('_')[0];
68
-
69
- const lastChar = Misc.getLastChar(previousWord);
70
-
71
- if (Config.funbox === '58008') {
72
- if (currentWord.length > 3) {
73
- if (Math.random() < 0.5) {
74
- word = Misc.setCharAt(word, Misc.randomIntFromRange(1, word.length - 2), '.');
75
- }
76
- if (Math.random() < 0.75) {
77
- const index = Misc.randomIntFromRange(1, word.length - 2);
78
- if (word[index - 1] !== '.' && word[index + 1] !== '.' && word[index + 1] !== '0') {
79
- const special = Misc.randomElementFromArray(['/', '*', '-', '+']);
80
- word = Misc.setCharAt(word, index, special);
81
- }
82
- }
83
- }
84
- } else if (Config.funbox === 'tenKeyMode') {
85
- word = addDecimalPoint(word);
86
- const special = Misc.randomElementFromArray(['/', '*', '-', '+']);
87
- word = word + special + Misc.getNumbers(4);
88
- // 2nd number of expression
89
- word = addDecimalPoint(word);
90
- } else {
91
- if (currentLanguage != 'code' && (index == 0 || shouldCapitalize(lastChar))) {
92
- //always capitalise the first word or if there was a dot unless using a code alphabet
93
-
94
- word = Misc.capitalizeFirstLetterOfEachWord(word);
95
-
96
- if (currentLanguage == 'turkish') {
97
- word = word.replace(/I/g, 'İ');
98
- }
99
-
100
- if (currentLanguage == 'spanish' || currentLanguage == 'catalan') {
101
- const rand = Math.random();
102
- if (rand > 0.9) {
103
- word = '¿' + word;
104
- spanishSentenceTracker = '?';
105
- } else if (rand > 0.8) {
106
- word = '¡' + word;
107
- spanishSentenceTracker = '!';
108
- }
109
- }
110
- } else if (
111
- (Math.random() < 0.1 && lastChar != '.' && lastChar != ',' && index != maxindex - 2) ||
112
- index == maxindex - 1
113
- ) {
114
- if (currentLanguage == 'spanish' || currentLanguage == 'catalan') {
115
- if (spanishSentenceTracker == '?' || spanishSentenceTracker == '!') {
116
- word += spanishSentenceTracker;
117
- spanishSentenceTracker = '';
118
- }
119
- } else {
120
- const rand = Math.random();
121
- if (rand <= 0.8) {
122
- if (currentLanguage == 'kurdish') {
123
- word += '.';
124
- } else {
125
- word += '.';
126
- }
127
- } else if (rand > 0.8 && rand < 0.9) {
128
- if (currentLanguage == 'french') {
129
- word = '?';
130
- } else if (
131
- currentLanguage == 'arabic' ||
132
- currentLanguage == 'persian' ||
133
- currentLanguage == 'urdu' ||
134
- currentLanguage == 'kurdish'
135
- ) {
136
- word += '؟';
137
- } else if (currentLanguage == 'greek') {
138
- word += ';';
139
- } else {
140
- word += '?';
141
- }
142
- } else {
143
- if (currentLanguage == 'french') {
144
- word = '!';
145
- } else {
146
- word += '!';
147
- }
148
- }
149
- }
150
- } else if (
151
- Math.random() < 0.01 &&
152
- lastChar != ',' &&
153
- lastChar != '.' &&
154
- currentLanguage !== 'russian'
155
- ) {
156
- word = `"${word}"`;
157
- } else if (
158
- Math.random() < 0.011 &&
159
- lastChar != ',' &&
160
- lastChar != '.' &&
161
- currentLanguage !== 'russian' &&
162
- currentLanguage !== 'ukrainian'
163
- ) {
164
- word = `'${word}'`;
165
- } else if (Math.random() < 0.012 && lastChar != ',' && lastChar != '.') {
166
- if (currentLanguage == 'code') {
167
- const r = Math.random();
168
- if (r < 0.25) {
169
- word = `(${word})`;
170
- } else if (r < 0.5) {
171
- word = `{${word}}`;
172
- } else if (r < 0.75) {
173
- word = `[${word}]`;
174
- } else {
175
- word = `<${word}>`;
176
- }
177
- } else {
178
- word = `(${word})`;
179
- }
180
- } else if (
181
- Math.random() < 0.013 &&
182
- lastChar != ',' &&
183
- lastChar != '.' &&
184
- lastChar != ';' &&
185
- lastChar != '؛' &&
186
- lastChar != ':'
187
- ) {
188
- if (currentLanguage == 'french') {
189
- word = ':';
190
- } else if (currentLanguage == 'greek') {
191
- word = '·';
192
- } else {
193
- word += ':';
194
- }
195
- } else if (Math.random() < 0.014 && lastChar != ',' && lastChar != '.' && previousWord != '-') {
196
- word = '-';
197
- } else if (
198
- Math.random() < 0.015 &&
199
- lastChar != ',' &&
200
- lastChar != '.' &&
201
- lastChar != ';' &&
202
- lastChar != '؛' &&
203
- lastChar != ':'
204
- ) {
205
- if (currentLanguage == 'french') {
206
- word = ';';
207
- } else if (currentLanguage == 'greek') {
208
- word = '·';
209
- } else if (currentLanguage == 'arabic' || currentLanguage == 'kurdish') {
210
- word += '؛';
211
- } else {
212
- word += ';';
213
- }
214
- } else if (Math.random() < 0.2 && lastChar != ',') {
215
- if (
216
- currentLanguage == 'arabic' ||
217
- currentLanguage == 'urdu' ||
218
- currentLanguage == 'persian' ||
219
- currentLanguage == 'kurdish'
220
- ) {
221
- word += '،';
222
- } else {
223
- word += ',';
224
- }
225
- } else if (Math.random() < 0.25 && currentLanguage == 'code') {
226
- const specials = ['{', '}', '[', ']', '(', ')', ';', '=', '+', '%', '/'];
227
-
228
- word = Misc.randomElementFromArray(specials);
229
- } else if (
230
- Math.random() < 0.5 &&
231
- currentLanguage === 'english' &&
232
- (await EnglishPunctuation.check(word))
233
- ) {
234
- word = await applyEnglishPunctuationToWord(word);
235
- }
236
- }
237
- return word;
238
- }
239
-
240
- async function applyEnglishPunctuationToWord(word: string): Promise<string> {
241
- return EnglishPunctuation.replace(word);
242
- }
243
-
244
- export function startTest(): boolean {
245
- if (PageTransition.get()) {
246
- return false;
247
- }
248
- startTestSubject.next(true);
249
- TestActive.set(true);
250
- Replay.startReplayRecording();
251
- Replay.replayGetWordsList(TestWords.words.list);
252
- TestInput.resetKeypressTimings();
253
- TimerProgress.update();
254
- TestTimer.clear();
255
-
256
- TestStats.setStart(performance.now());
257
- TestTimer.start();
258
- return true;
259
- }
260
-
261
- interface RestartOptions {
262
- withSameWordset?: boolean;
263
- nosave?: boolean;
264
- event?: { shiftKey: any };
265
- practiseMissed?: boolean;
266
- noAnim?: boolean;
267
- }
268
-
269
- export function restart(options = {} as RestartOptions): void {
270
- const defaultOptions = {
271
- withSameWordset: false,
272
- practiseMissed: false,
273
- noAnim: false,
274
- nosave: false,
275
- };
276
-
277
- options = { ...defaultOptions, ...options };
278
-
279
- if (TestUI.testRestarting || TestUI.resultCalculating) {
280
- event?.preventDefault();
281
- return;
282
- }
283
- if (ActivePage.get() == 'test' && !TestUI.resultVisible) {
284
- if (!ManualRestart.get()) {
285
- if (TestWords.hasTab && !options.event?.shiftKey && Config.quickRestart !== 'esc') {
286
- return;
287
- }
288
- if (Config.mode !== 'zen') event?.preventDefault();
289
- if (!Misc.canQuickRestart(Config.mode, Config.words, Config.time, CustomText)) {
290
- return;
291
- }
292
- }
293
- }
294
- if (TestActive.get()) {
295
- if (
296
- Config.repeatQuotes === 'typing' &&
297
- Config.mode === 'quote' &&
298
- Config.language.replace(/_\d*k$/g, '') === TestWords.randomQuote.language
299
- ) {
300
- options.withSameWordset = true;
301
- }
302
- if (TestState.isRepeated) {
303
- options.withSameWordset = true;
304
- }
305
-
306
- TestInput.pushKeypressesToHistory();
307
- const testSeconds = TestStats.calculateTestSeconds(performance.now());
308
- const afkseconds = TestStats.calculateAfkSeconds(testSeconds);
309
- let tt = testSeconds - afkseconds;
310
- if (tt < 0) tt = 0;
311
- TestStats.incrementIncompleteSeconds(tt);
312
- TestStats.incrementRestartCount();
313
- }
314
-
315
- let repeatWithPace = false;
316
- if (TestUI.resultVisible && Config.repeatedPace && options.withSameWordset) {
317
- repeatWithPace = true;
318
- }
319
-
320
- ManualRestart.reset();
321
- TestTimer.clear();
322
- TestStats.restart();
323
- TestInput.restart();
324
- TestInput.corrected.reset();
325
- startTestSubject.next(false);
326
- Caret.hide();
327
- TestActive.set(false);
328
- TestInput.setBailout(false);
329
-
330
- if (ActivePage.get() == 'test' && window.scrollY > 0) {
331
- window.scrollTo({ top: 0, behavior: 'smooth' });
332
- }
333
-
334
- wordsInput.value = ' ';
335
-
336
- TestUI.reset();
337
-
338
- TestUI.setResultVisible(false);
339
- PageTransition.set(true);
340
- TestUI.setTestRestarting(true);
341
- (async () => {
342
- if (ActivePage.get() == 'test') {
343
- Focus.set(false);
344
- }
345
- TestUI.focusWords();
346
- wordsInput.value = ' ';
347
- let shouldQuoteRepeat = false;
348
- if (Config.mode === 'quote' && Config.repeatQuotes === 'typing' && failReason !== '') {
349
- shouldQuoteRepeat = true;
350
- }
351
-
352
- if (!options.withSameWordset && !shouldQuoteRepeat) {
353
- TestState.setRepeated(false);
354
- TestState.setPaceRepeat(repeatWithPace);
355
- TestWords.setHasTab(false);
356
- try {
357
- await init();
358
- } catch {
359
- TestUI.setTestRestarting(false);
360
- return;
361
- }
362
- } else {
363
- TestState.setRepeated(true);
364
- TestState.setPaceRepeat(repeatWithPace);
365
- TestActive.set(false);
366
- TestWords.words.resetCurrentIndex();
367
- TestInput.input.reset();
368
- TestUI.showWords();
369
- }
370
- failReason = '';
371
-
372
- let fbtext = '';
373
- if (Config.funbox !== 'none') {
374
- fbtext = ' ' + Config.funbox;
375
- }
376
-
377
- TestUI.setTestRestarting(false);
378
- TestTimer.clear();
379
- PageTransition.set(false);
380
-
381
- // Added by me
382
- startTest();
383
- })();
384
- }
385
-
386
- function applyFunboxesToWord(word: string, wordset?: Wordset.Wordset): string {
387
- if (Config.funbox === 'rAnDoMcAsE') {
388
- let randomcaseword = '';
389
- for (let i = 0; i < word.length; i++) {
390
- if (i % 2 != 0) {
391
- randomcaseword += word[i].toUpperCase();
392
- } else {
393
- randomcaseword += word[i];
394
- }
395
- }
396
- word = randomcaseword;
397
- } else if (Config.funbox === 'capitals') {
398
- word = Misc.capitalizeFirstLetterOfEachWord(word);
399
- } else if (Config.funbox === 'gibberish') {
400
- word = Misc.getGibberish();
401
- } else if (Config.funbox === 'arrows') {
402
- word = Misc.getArrows();
403
- } else if (Config.funbox === 'tenKeyMode') {
404
- word = Misc.getNumbers(Config.punctuation ? 4 : 5, Config.punctuation ? 1 : 2);
405
- if (Config.language.split('_')[0] === 'kurdish') {
406
- word = Misc.convertNumberToArabicIndic(word);
407
- }
408
- } else if (Config.funbox === '58008') {
409
- word = Misc.getNumbers(7);
410
- if (Config.language.split('_')[0] === 'kurdish') {
411
- word = Misc.convertNumberToArabicIndic(word);
412
- }
413
- } else if (Config.funbox === 'specials') {
414
- word = Misc.getSpecials();
415
- } else if (Config.funbox === 'ascii') {
416
- word = Misc.getASCII();
417
- } else if (wordset !== undefined && Config.funbox === 'weakspot') {
418
- word = WeakSpot.getWord(wordset);
419
- }
420
- return word;
421
- }
422
-
423
- async function getNextWord(
424
- wordset: Wordset.Wordset,
425
- language: MonkeyTypes.LanguageObject,
426
- wordsBound: number
427
- ): Promise<string> {
428
- let randomWord = wordset.randomWord();
429
- const previousWord = TestWords.words.get(TestWords.words.length - 1, true);
430
- const previousWord2 = TestWords.words.get(TestWords.words.length - 2, true);
431
-
432
- // Added by TG
433
- if (Config.customTGQuotes) {
434
- if (TestWords.words.length - TestWords.words.currentIndex <= 40) {
435
- const randomQuote = QuotesController.getRandomQuote();
436
- let rq: MonkeyTypes.Quote | undefined | null = undefined;
437
- rq = randomQuote;
438
-
439
- if (rq !== undefined && rq !== null) {
440
- rq.text = rq.text.replace(/ +/gm, ' ');
441
- rq.text = rq.text.replace(/\\\\t/gm, '\t');
442
- rq.text = rq.text.replace(/\\\\n/gm, '\n');
443
- rq.text = rq.text.replace(/\\t/gm, '\t');
444
- rq.text = rq.text.replace(/\\n/gm, '\n');
445
- rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, '\n ');
446
- rq.text = rq.text.replace(/…/g, '...');
447
- rq.text = rq.text.trim();
448
- rq.textSplit = rq.text.split(' ');
449
- rq.language = Config.language.replace(/_\d*k$/g, '');
450
- TestWords.setRandomQuote(rq);
451
- }
452
-
453
- return TestWords.randomQuote.text;
454
- }
455
- } else if (Config.mode === 'quote') {
456
- randomWord = TestWords.randomQuote.textSplit?.[TestWords.words.length] ?? '';
457
- } else if (Config.mode == 'custom' && !CustomText.isWordRandom && !CustomText.isTimeRandom) {
458
- randomWord = CustomText.text[TestWords.words.length];
459
- } else {
460
- let regenarationCount = 0; //infinite loop emergency stop button
461
- while (
462
- regenarationCount < 100 &&
463
- (previousWord == randomWord ||
464
- previousWord2 == randomWord ||
465
- (Config.mode !== 'custom' && !Config.punctuation && randomWord == 'I') ||
466
- (Config.mode !== 'custom' &&
467
- !Config.punctuation &&
468
- /[-=_+[\]{};'\\:"|,./<>?]/i.test(randomWord)))
469
- ) {
470
- regenarationCount++;
471
- randomWord = wordset.randomWord();
472
- }
473
- }
474
-
475
- if (randomWord === undefined) {
476
- randomWord = wordset.randomWord();
477
- }
478
-
479
- randomWord = randomWord.replace(/ +/gm, ' ');
480
- randomWord = randomWord.replace(/^ | $/gm, '');
481
- randomWord = applyFunboxesToWord(randomWord, wordset);
482
-
483
- if (Config.punctuation) {
484
- randomWord = await punctuateWord(
485
- TestWords.words.get(TestWords.words.length - 1),
486
- randomWord,
487
- TestWords.words.length,
488
- wordsBound
489
- );
490
- }
491
- if (Config.numbers) {
492
- if (Math.random() < 0.1) {
493
- randomWord = Misc.getNumbers(4);
494
-
495
- if (Config.language.split('_')[0] === 'kurdish') {
496
- randomWord = Misc.convertNumberToArabicIndic(randomWord);
497
- }
498
- }
499
- }
500
-
501
- return randomWord;
502
- }
503
-
504
- export async function init(): Promise<void> {
505
- TestActive.set(false);
506
- TestWords.words.reset();
507
- TestUI.setCurrentWordElementIndex(0);
508
-
509
- TestInput.input.resetHistory();
510
- TestInput.input.resetCurrent();
511
-
512
- let language;
513
- try {
514
- language = await Misc.getLanguage(Config.language);
515
- } catch (e) {
516
- Misc.emitQuoteNetworkError();
517
- throw e;
518
- }
519
-
520
- let wordsBound = 100;
521
- if (Config.showAllLines) {
522
- if (Config.mode === 'quote') {
523
- wordsBound = 100;
524
- } else if (Config.mode === 'custom') {
525
- if (CustomText.isWordRandom) {
526
- wordsBound = CustomText.word;
527
- } else if (CustomText.isTimeRandom) {
528
- wordsBound = 100;
529
- } else {
530
- wordsBound = CustomText.text.length;
531
- }
532
- } else if (Config.mode != 'time') {
533
- wordsBound = Config.words;
534
- }
535
- } else {
536
- if (Config.mode === 'words' && Config.words < wordsBound) {
537
- wordsBound = Config.words;
538
- }
539
- if (Config.mode == 'custom' && CustomText.isWordRandom && CustomText.word < wordsBound) {
540
- wordsBound = CustomText.word;
541
- }
542
- if (Config.mode == 'custom' && CustomText.isTimeRandom) {
543
- wordsBound = 100;
544
- }
545
- if (
546
- Config.mode == 'custom' &&
547
- !CustomText.isWordRandom &&
548
- !CustomText.isTimeRandom &&
549
- CustomText.text.length < wordsBound
550
- ) {
551
- wordsBound = CustomText.text.length;
552
- }
553
- }
554
-
555
- if (
556
- (Config.mode === 'custom' && CustomText.isWordRandom && CustomText.word == 0) ||
557
- (Config.mode === 'custom' && CustomText.isTimeRandom && CustomText.time == 0)
558
- ) {
559
- wordsBound = 100;
560
- }
561
-
562
- if (Config.mode === 'words' && Config.words === 0) {
563
- wordsBound = 100;
564
- }
565
- if (Config.funbox === 'plus_one') {
566
- wordsBound = 2;
567
- if (Config.mode === 'words' && Config.words < wordsBound) {
568
- wordsBound = Config.words;
569
- }
570
- }
571
- if (Config.funbox === 'plus_two') {
572
- wordsBound = 3;
573
- if (Config.mode === 'words' && Config.words < wordsBound) {
574
- wordsBound = Config.words;
575
- }
576
- }
577
-
578
- if (
579
- (Config.mode == 'time' || Config.mode == 'words' || Config.mode == 'custom') &&
580
- !Config.customTGQuotes
581
- ) {
582
- let wordList = language.words;
583
- if (Config.mode == 'custom') {
584
- wordList = CustomText.text;
585
- }
586
- const wordset = Wordset.withWords(wordList, Config.funbox);
587
-
588
- if ((Config.funbox == 'wikipedia' || Config.funbox == 'poetry') && Config.mode != 'custom') {
589
- // Skip word generation for wikipedia/poetry funboxes in non-custom mode
590
- } else {
591
- for (let i = 0; i < wordsBound; i++) {
592
- const randomWord = await getNextWord(wordset, language, wordsBound);
593
-
594
- if (/\t/g.test(randomWord)) {
595
- TestWords.setHasTab(true);
596
- }
597
-
598
- const te = randomWord.replace('\n', '\n ').trim();
599
-
600
- if (/ +/.test(te)) {
601
- const randomList = te.split(' ');
602
- let id = 0;
603
- while (id < randomList.length) {
604
- TestWords.words.push(randomList[id]);
605
- id++;
606
-
607
- if (
608
- TestWords.words.length == wordsBound &&
609
- Config.mode == 'custom' &&
610
- CustomText.isWordRandom
611
- ) {
612
- break;
613
- }
614
- }
615
- if (Config.mode == 'custom' && !CustomText.isWordRandom && !CustomText.isTimeRandom) {
616
- // Custom mode with fixed text - no adjustment needed
617
- } else {
618
- i = TestWords.words.length - 1;
619
- }
620
- } else {
621
- TestWords.words.push(randomWord);
622
- }
623
- }
624
- }
625
- }
626
-
627
- // Added by TG
628
- if (Config.customTGQuotes) {
629
- let quotesCollection;
630
-
631
- try {
632
- quotesCollection = await QuotesController.getQuotes(
633
- Config.language,
634
- Config.testVersion,
635
- Config.quoteLength
636
- );
637
- } catch (e) {
638
- Misc.emitQuoteNetworkError();
639
- throw e;
640
- }
641
-
642
- let rq: MonkeyTypes.Quote | undefined | null = undefined;
643
- const randomQuote = QuotesController.getRandomQuote();
644
- rq = randomQuote;
645
-
646
- if (rq === undefined || rq === null) return;
647
-
648
- rq.text = rq.text.replace(/ +/gm, ' ');
649
- rq.text = rq.text.replace(/\\\\t/gm, '\t');
650
- rq.text = rq.text.replace(/\\\\n/gm, '\n');
651
- rq.text = rq.text.replace(/\\t/gm, '\t');
652
- rq.text = rq.text.replace(/\\n/gm, '\n');
653
- rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, '\n ');
654
- rq.text = rq.text.replace(/…/g, '...');
655
- rq.text = rq.text.trim();
656
- rq.textSplit = rq.text.split(' ');
657
- rq.language = Config.language.replace(/_\d*k$/g, '');
658
-
659
- TestWords.setRandomQuote(rq);
660
-
661
- const w = TestWords.randomQuote.textSplit;
662
-
663
- if (w === undefined) return;
664
-
665
- for (let i = 0; i < w.length; i++) {
666
- if (/\t/g.test(w[i])) {
667
- TestWords.setHasTab(true);
668
- }
669
-
670
- w[i] = applyFunboxesToWord(w[i]);
671
-
672
- TestWords.words.push(w[i]);
673
- }
674
- }
675
-
676
- TestUI.showWords();
677
- }
678
-
679
- export async function addWord(): Promise<void> {
680
- let bound = 100;
681
- if (Config.funbox === 'plus_one') bound = 1;
682
- if (Config.funbox === 'plus_two') bound = 2;
683
- if (
684
- TestWords.words.length - TestInput.input.history.length > bound ||
685
- (Config.mode === 'words' && TestWords.words.length >= Config.words && Config.words > 0) ||
686
- (Config.mode === 'custom' &&
687
- CustomText.isWordRandom &&
688
- TestWords.words.length >= CustomText.word &&
689
- CustomText.word != 0) ||
690
- (Config.mode === 'custom' &&
691
- !CustomText.isWordRandom &&
692
- !CustomText.isTimeRandom &&
693
- TestWords.words.length >= CustomText.text.length) ||
694
- (Config.mode === 'quote' &&
695
- TestWords.words.length >= (TestWords.randomQuote.textSplit?.length ?? 0))
696
- ) {
697
- return;
698
- }
699
-
700
- const language: MonkeyTypes.LanguageObject =
701
- Config.mode !== 'custom'
702
- ? await Misc.getCurrentLanguage(Config.language)
703
- : {
704
- //borrow the direction of the current language
705
- ...(await Misc.getCurrentLanguage(Config.language)),
706
- words: CustomText.text,
707
- };
708
- const wordset = Wordset.withWords(language.words, Config.funbox);
709
-
710
- const randomWord = await getNextWord(wordset, language, bound);
711
-
712
- const split = randomWord.split(' ');
713
-
714
- if (!Config.customTGQuotes) {
715
- if (split.length > 1) {
716
- split.forEach(word => {
717
- TestWords.words.push(word);
718
- TestUI.addWord(word);
719
- });
720
- } else {
721
- TestWords.words.push(randomWord);
722
- TestUI.addWord(randomWord);
723
- }
724
- } else {
725
- if (split.length > 1) {
726
- split.forEach(word => {
727
- TestWords.words.push(word);
728
- TestUI.addWord(word);
729
- });
730
- }
731
- }
732
- }
733
-
734
- interface CompletedEvent extends MonkeyTypes.Result<MonkeyTypes.Mode> {
735
- keySpacing: number[] | 'toolong';
736
- keyDuration: number[] | 'toolong';
737
- customText: MonkeyTypes.CustomText;
738
- smoothConsistency: number;
739
- wpmConsistency: number;
740
- lang: string;
741
- challenge?: string | null;
742
- }
743
-
744
- type PartialCompletedEvent = Omit<Partial<CompletedEvent>, 'chartData'> & {
745
- chartData: Partial<MonkeyTypes.ChartData>;
746
- };
747
-
748
- interface RetrySaving {
749
- completedEvent: CompletedEvent | null;
750
- canRetry: boolean;
751
- }
752
-
753
- const retrySaving: RetrySaving = {
754
- completedEvent: null,
755
- canRetry: false,
756
- };
757
-
758
- function buildCompletedEvent(difficultyFailed: boolean): CompletedEvent {
759
- //build completed event object
760
- const completedEvent: PartialCompletedEvent = {
761
- wpm: undefined,
762
- rawWpm: undefined,
763
- kph: -1,
764
- charStats: undefined,
765
- acc: undefined,
766
- mode: Config.mode,
767
- mode2: undefined as never,
768
- quoteLength: -1,
769
- punctuation: Config.punctuation,
770
- numbers: Config.numbers,
771
- lazyMode: Config.lazyMode,
772
- timestamp: Date.now(),
773
- language: Config.language,
774
- restartCount: TestStats.restartCount,
775
- incompleteTestSeconds:
776
- TestStats.incompleteSeconds < 0 ? 0 : Misc.roundTo2(TestStats.incompleteSeconds),
777
- difficulty: Config.difficulty,
778
- blindMode: Config.blindMode,
779
- tags: undefined,
780
- keySpacing: TestInput.keypressTimings.spacing.array,
781
- keyDuration: TestInput.keypressTimings.duration.array,
782
- consistency: undefined,
783
- keyConsistency: undefined,
784
- funbox: Config.funbox,
785
- bailedOut: TestInput.bailout,
786
- chartData: {
787
- wpm: TestInput.wpmHistory,
788
- raw: undefined,
789
- err: undefined,
790
- },
791
- customText: undefined,
792
- testDuration: undefined,
793
- afkDuration: undefined,
794
- };
795
-
796
- // stats
797
- const stats = TestStats.calculateStats();
798
- if (stats.time % 1 != 0 && Config.mode !== 'time') {
799
- TestStats.setLastSecondNotRound();
800
- }
801
- TestStats.setLastTestWpm(stats.wpm);
802
- completedEvent.wpm = stats.wpm;
803
- completedEvent.rawWpm = stats.wpmRaw;
804
- completedEvent.charStats = [
805
- stats.correctChars + stats.correctSpaces,
806
- stats.incorrectChars,
807
- stats.extraChars,
808
- stats.missedChars,
809
- ];
810
- completedEvent.acc = stats.acc;
811
- if (Config.funbox === 'tenKeyMode') {
812
- completedEvent.kph = stats.wpm * 5 * 60;
813
- }
814
-
815
- // if the last second was not rounded, add another data point to the history
816
- if (TestStats.lastSecondNotRound && !difficultyFailed) {
817
- const wpmAndRaw = TestStats.calculateWpmAndRaw();
818
- TestInput.pushToWpmHistory(wpmAndRaw.wpm);
819
- TestInput.pushToRawHistory(wpmAndRaw.raw);
820
- TestInput.pushKeypressesToHistory();
821
- }
822
-
823
- //consistency
824
- const rawPerSecond = TestInput.keypressPerSecond.map(f => Math.round((f.count / 5) * 60));
825
- const stddev = Misc.stdDev(rawPerSecond);
826
- const avg = Misc.mean(rawPerSecond);
827
- let consistency = Misc.roundTo2(Misc.kogasa(stddev / avg));
828
- let keyConsistencyArray =
829
- TestInput.keypressTimings.spacing.array === 'toolong'
830
- ? []
831
- : TestInput.keypressTimings.spacing.array.slice();
832
- if (keyConsistencyArray.length > 0) {
833
- keyConsistencyArray = keyConsistencyArray.slice(0, keyConsistencyArray.length - 1);
834
- }
835
- let keyConsistency = Misc.roundTo2(
836
- Misc.kogasa(Misc.stdDev(keyConsistencyArray) / Misc.mean(keyConsistencyArray))
837
- );
838
- if (!consistency || isNaN(consistency)) {
839
- consistency = 0;
840
- }
841
- if (!keyConsistency || isNaN(keyConsistency)) {
842
- keyConsistency = 0;
843
- }
844
- completedEvent.keyConsistency = keyConsistency;
845
- completedEvent.consistency = consistency;
846
- const smoothedraw = Misc.smooth(rawPerSecond, 1);
847
- completedEvent.chartData.raw = smoothedraw;
848
- completedEvent.chartData.unsmoothedRaw = rawPerSecond;
849
-
850
- //smoothed consistency
851
- const stddev2 = Misc.stdDev(smoothedraw);
852
- const avg2 = Misc.mean(smoothedraw);
853
- const smoothConsistency = Misc.roundTo2(Misc.kogasa(stddev2 / avg2));
854
- completedEvent.smoothConsistency = smoothConsistency;
855
-
856
- //wpm consistency
857
- const stddev3 = Misc.stdDev(completedEvent.chartData.wpm ?? []);
858
- const avg3 = Misc.mean(completedEvent.chartData.wpm ?? []);
859
- const wpmConsistency = Misc.roundTo2(Misc.kogasa(stddev3 / avg3));
860
- completedEvent.wpmConsistency = wpmConsistency;
861
-
862
- completedEvent.testDuration = parseFloat(stats.time.toString());
863
- completedEvent.afkDuration = TestStats.calculateAfkSeconds(completedEvent.testDuration);
864
-
865
- completedEvent.chartData.err = [];
866
- for (let i = 0; i < TestInput.keypressPerSecond.length; i++) {
867
- completedEvent.chartData.err.push(TestInput.keypressPerSecond[i].errors);
868
- }
869
-
870
- if (Config.mode === 'quote') {
871
- completedEvent.quoteLength = TestWords.randomQuote.group;
872
- completedEvent.language = Config.language.replace(/_\d*k$/g, '');
873
- } else {
874
- delete completedEvent.quoteLength;
875
- }
876
-
877
- // @ts-expect-error TODO fix this
878
- completedEvent.mode2 = Misc.getMode2(Config, TestWords.randomQuote);
879
-
880
- if (Config.mode === 'custom') {
881
- completedEvent.customText = <MonkeyTypes.CustomText>{};
882
- completedEvent.customText.textLen = CustomText.text.length;
883
- completedEvent.customText.isWordRandom = CustomText.isWordRandom;
884
- completedEvent.customText.isTimeRandom = CustomText.isTimeRandom;
885
- completedEvent.customText.word = CustomText.word;
886
- completedEvent.customText.time = CustomText.time;
887
- } else {
888
- delete completedEvent.customText;
889
- }
890
-
891
- //tags
892
- const activeTagsIds: string[] = [];
893
- completedEvent.tags = activeTagsIds;
894
-
895
- if (completedEvent.mode != 'custom') delete completedEvent.customText;
896
-
897
- return <CompletedEvent>completedEvent;
898
- }
899
-
900
- export function stopTest(): void {
901
- TestActive.set(false);
902
- Focus.set(false);
903
- Caret.hide();
904
- OutOfFocus.hide();
905
- TestTimer.clear();
906
- }
907
-
908
- export async function finish(difficultyFailed = false): Promise<void> {
909
- if (!TestActive.get()) return;
910
-
911
- startTestSubject.next(false);
912
-
913
- TestInput.recordKeypressSpacing(); //this is needed in case there is afk time at the end - to make sure test duration makes sense
914
-
915
- TestUI.setResultCalculating(true);
916
- TestUI.setResultVisible(true);
917
- TestStats.setEnd(performance.now());
918
- TestActive.set(false);
919
- Focus.set(false);
920
- Caret.hide();
921
- OutOfFocus.hide();
922
- TestTimer.clear();
923
-
924
- if (TestInput.burstHistory.length !== TestInput.input.getHistory().length) {
925
- const burst = TestStats.calculateBurst();
926
- TestInput.pushBurstToHistory(burst);
927
- }
928
-
929
- if (Config.mode == 'zen' || TestInput.bailout) {
930
- TestStats.removeAfkData();
931
- }
932
-
933
- const completedEvent = buildCompletedEvent(difficultyFailed);
934
-
935
- function countUndefined(input: unknown): number {
936
- if (typeof input === 'undefined') {
937
- return 1;
938
- } else if (typeof input === 'object' && input !== null) {
939
- return Object.values(input).reduce((a, b) => a + countUndefined(b), 0) as number;
940
- } else {
941
- return 0;
942
- }
943
- }
944
-
945
- if (countUndefined(completedEvent) > 0) {
946
- console.log(completedEvent);
947
- return;
948
- }
949
-
950
- const kps = TestInput.keypressPerSecond.slice(-5);
951
- let afkDetected = kps.every(second => second.afk);
952
- if (TestInput.bailout) afkDetected = false;
953
-
954
- let tooShort = false;
955
- let dontSave = false;
956
- if (difficultyFailed) {
957
- dontSave = true;
958
- } else if (afkDetected) {
959
- dontSave = true;
960
- } else if (TestState.isRepeated) {
961
- dontSave = true;
962
- } else if (
963
- (Config.mode === 'time' && completedEvent.mode2 < 15 && completedEvent.mode2 > 0) ||
964
- (Config.mode === 'time' && completedEvent.mode2 == 0 && completedEvent.testDuration < 15) ||
965
- (Config.mode === 'words' && completedEvent.mode2 < 10 && completedEvent.mode2 > 0) ||
966
- (Config.mode === 'words' && completedEvent.mode2 == 0 && completedEvent.testDuration < 15) ||
967
- (Config.mode === 'custom' &&
968
- !CustomText.isWordRandom &&
969
- !CustomText.isTimeRandom &&
970
- CustomText.text.length < 10) ||
971
- (Config.mode === 'custom' &&
972
- CustomText.isWordRandom &&
973
- !CustomText.isTimeRandom &&
974
- CustomText.word < 10) ||
975
- (Config.mode === 'custom' &&
976
- !CustomText.isWordRandom &&
977
- CustomText.isTimeRandom &&
978
- CustomText.time < 15) ||
979
- (Config.mode === 'zen' && completedEvent.testDuration < 15)
980
- ) {
981
- tooShort = true;
982
- dontSave = true;
983
- } else if (completedEvent.wpm < 0 || completedEvent.wpm > 350) {
984
- TestStats.setInvalid();
985
- dontSave = true;
986
- } else if (completedEvent.acc < 75 || completedEvent.acc > 100) {
987
- TestStats.setInvalid();
988
- dontSave = true;
989
- }
990
-
991
- TestStats.setLastResult(completedEvent);
992
-
993
- resultSub.next({
994
- completedEvent: completedEvent,
995
- difficultyFailed: difficultyFailed,
996
- failReason: failReason,
997
- afkDetected: afkDetected,
998
- isRepeated: TestState.isRepeated,
999
- tooShort: tooShort,
1000
- randomQuote: TestWords.randomQuote,
1001
- dontSave: dontSave,
1002
- wpmHistory: TestInput.wpmHistory,
1003
- });
1004
-
1005
- if (completedEvent.chartData !== 'toolong') {
1006
- delete completedEvent.chartData.unsmoothedRaw;
1007
- }
1008
-
1009
- if (completedEvent.testDuration > 122) {
1010
- completedEvent.chartData = 'toolong';
1011
- completedEvent.keySpacing = 'toolong';
1012
- completedEvent.keyDuration = 'toolong';
1013
- TestInput.setKeypressTimingsTooLong();
1014
- }
1015
-
1016
- if (
1017
- Config.difficulty == 'normal' ||
1018
- ((Config.difficulty == 'master' || Config.difficulty == 'expert') && !difficultyFailed)
1019
- ) {
1020
- TestStats.resetIncomplete();
1021
- }
1022
- }
1023
-
1024
- export function fail(reason: string): void {
1025
- failReason = reason;
1026
- TestInput.pushKeypressesToHistory();
1027
- finish(true);
1028
- const testSeconds = TestStats.calculateTestSeconds(performance.now());
1029
- const afkseconds = TestStats.calculateAfkSeconds(testSeconds);
1030
- let tt = testSeconds - afkseconds;
1031
- if (tt < 0) tt = 0;
1032
- TestStats.incrementIncompleteSeconds(tt);
1033
- TestStats.incrementRestartCount();
1034
- }
1035
-
1036
- TimerEvent.subscribe((eventKey, eventValue) => {
1037
- if (eventKey === 'fail' && eventValue !== undefined) fail(eventValue);
1038
- if (eventKey === 'finish') finish();
1039
- });