typing-game-cli 6.1.0 → 7.1.0
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.
- package/dist/app.js +59 -17
- package/dist/cli.js +55 -3
- package/dist/config.js +20 -2
- package/dist/constants.js +4 -1
- package/dist/helpers.js +89 -1
- package/dist/questionPrompt.js +11 -0
- package/dist/sentences/daphne-du-maurier/rebecca.json +11 -7
- package/dist/sentences/o-henry/holding-up-a-train.json +11 -0
- package/package.json +4 -3
- package/readme.md +10 -0
package/dist/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useLayoutEffect } from 'react';
|
|
2
|
-
import { Text, Box, useInput, useStdout, useApp } from 'ink';
|
|
2
|
+
import { Text, Box, useInput, useStdout, useApp, Newline } from 'ink';
|
|
3
3
|
import TextInput from 'ink-text-input-2';
|
|
4
4
|
import { Spinner, Alert } from '@inkjs/ui';
|
|
5
5
|
import chalk from 'chalk';
|
|
@@ -7,8 +7,8 @@ import Gradient from 'ink-gradient';
|
|
|
7
7
|
import { proxy, useSnapshot } from 'valtio';
|
|
8
8
|
import random from 'just-random';
|
|
9
9
|
import { formatISO } from 'date-fns';
|
|
10
|
-
import { getBorderColor, getDefaultSuite, getRobotBorderColor, getStatusVariant, getMessageOrPlaceholder, isFinished, isWordTyped, calculateWPM, getTypingWord, getRemainingPart, getWpmTextColor, calculateCPS, calculateCPM, getBestResult, getScoreTextColor, getCompetitionResult, registerResult, registerBestFrames, getBestFrames, getOpponentFrames, getSuiteByTopic } from './helpers.js';
|
|
11
|
-
import { optionKeyColor } from './constants.js';
|
|
10
|
+
import { getBorderColor, getDefaultSuite, getHaLeft, getRobotBorderColor, getStatusVariant, getMessageOrPlaceholder, isFinished, isWordTyped, calculateWPM, getTypingWord, getRemainingPart, getWpmTextColor, calculateCPS, calculateCPM, getBestResult, getScoreTextColor, getCompetitionResult, registerResult, registerBestFrames, getBestFrames, getOpponentFrames, getSuiteByTopic, getHaCount, isLastNum } from './helpers.js';
|
|
11
|
+
import { maxHandicapCount, numerics, optionKeyColor } from './constants.js';
|
|
12
12
|
import Results from './Results.js';
|
|
13
13
|
import { config } from './config.js';
|
|
14
14
|
import Menu from './Menu.js';
|
|
@@ -34,6 +34,10 @@ const state = proxy({
|
|
|
34
34
|
robotCharCount: 0,
|
|
35
35
|
robotText: '',
|
|
36
36
|
userText: '',
|
|
37
|
+
printedByUserText: '',
|
|
38
|
+
handicap: false,
|
|
39
|
+
handicapCount: 0,
|
|
40
|
+
inputHandicapCount: '',
|
|
37
41
|
usingBestResult: false,
|
|
38
42
|
isAgainstMyselft: false,
|
|
39
43
|
nextAndRemaining: '',
|
|
@@ -43,6 +47,7 @@ const state = proxy({
|
|
|
43
47
|
cpm: 0,
|
|
44
48
|
robotWpm: 0,
|
|
45
49
|
robotCpm: 0,
|
|
50
|
+
handicappedWpm: 0,
|
|
46
51
|
cps: 0,
|
|
47
52
|
robotCps: 0,
|
|
48
53
|
bestRslt: null,
|
|
@@ -60,6 +65,8 @@ const state = proxy({
|
|
|
60
65
|
export default function App({
|
|
61
66
|
robotLevel,
|
|
62
67
|
topic,
|
|
68
|
+
handicap,
|
|
69
|
+
handicapCount,
|
|
63
70
|
displayResults = false,
|
|
64
71
|
sortBy,
|
|
65
72
|
isShowAllHistory,
|
|
@@ -77,6 +84,8 @@ export default function App({
|
|
|
77
84
|
useLayoutEffect(() => {
|
|
78
85
|
if (displayResults) {
|
|
79
86
|
state.status = 'RESULTS';
|
|
87
|
+
} else if (handicap && handicapCount > maxHandicapCount) {
|
|
88
|
+
state.status = 'QUESTION';
|
|
80
89
|
}
|
|
81
90
|
if (isCompetingAgainstBestResult) {
|
|
82
91
|
state.isAgainstMyselft = true;
|
|
@@ -88,8 +97,20 @@ export default function App({
|
|
|
88
97
|
state.topic = topic;
|
|
89
98
|
state.suite = getSuiteByTopic(topic);
|
|
90
99
|
}
|
|
91
|
-
}, [displayResults, isCompetingAgainstBestResult, topN, topic]);
|
|
100
|
+
}, [displayResults, isCompetingAgainstBestResult, topN, topic, handicap, handicapCount]);
|
|
92
101
|
useInput((input, key) => {
|
|
102
|
+
if (state.status === 'QUESTION') {
|
|
103
|
+
if (numerics.includes(input)) {
|
|
104
|
+
state.inputHandicapCount += input;
|
|
105
|
+
} else if (key.backspace) {
|
|
106
|
+
if (state.inputHandicapCount.length > 0) {
|
|
107
|
+
state.inputHandicapCount = state.inputHandicapCount.slice(0, -1);
|
|
108
|
+
}
|
|
109
|
+
} else if (key.return) {
|
|
110
|
+
state.status = 'PAUSED';
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
93
114
|
if (input === 'y') {
|
|
94
115
|
const foundBestResult = getBestResult();
|
|
95
116
|
if (foundBestResult) {
|
|
@@ -111,10 +132,16 @@ export default function App({
|
|
|
111
132
|
});
|
|
112
133
|
state.usingBestResult = false;
|
|
113
134
|
}
|
|
114
|
-
|
|
135
|
+
const currentRoundSentences = random(state.suite.sentences);
|
|
136
|
+
const currentHaCount = getHaCount(handicapCount, state.inputHandicapCount);
|
|
137
|
+
const initFirstPart = handicap ? getHaLeft(currentRoundSentences, currentHaCount) : '';
|
|
138
|
+
state.userText = initFirstPart;
|
|
139
|
+
state.printedByUserText = '';
|
|
140
|
+
state.handicap = handicap;
|
|
141
|
+
state.handicapCount = currentHaCount;
|
|
115
142
|
state.robotText = '';
|
|
116
|
-
state.source =
|
|
117
|
-
state.firstPart =
|
|
143
|
+
state.source = currentRoundSentences;
|
|
144
|
+
state.firstPart = initFirstPart;
|
|
118
145
|
state.highlightedPart = '';
|
|
119
146
|
state.erroredPart = '';
|
|
120
147
|
state.startTime = currentTime();
|
|
@@ -123,6 +150,7 @@ export default function App({
|
|
|
123
150
|
state.robotWordCount = 0;
|
|
124
151
|
state.wpm = 0;
|
|
125
152
|
state.cpm = 0;
|
|
153
|
+
state.charCount = 0;
|
|
126
154
|
state.robotWpm = 0;
|
|
127
155
|
state.robotCpm = 0;
|
|
128
156
|
state.frms = [];
|
|
@@ -141,7 +169,7 @@ export default function App({
|
|
|
141
169
|
const incrTimes = 1;
|
|
142
170
|
const passedMs = now - state.startTime;
|
|
143
171
|
if (!isFinished) {
|
|
144
|
-
state.cpm = calculateCPM(state.
|
|
172
|
+
state.cpm = calculateCPM(state.charCount, state.startTime, now);
|
|
145
173
|
state.robotWpm = calculateWPM(state.robotWordCount, state.startTime, now, isFinished);
|
|
146
174
|
state.robotCps = calculateCPS(state.robotCharCount, state.startTime, now);
|
|
147
175
|
const isRobotLastChar = state.source === state.robotText;
|
|
@@ -191,6 +219,7 @@ export default function App({
|
|
|
191
219
|
}
|
|
192
220
|
state.isAnimatingEnd = true;
|
|
193
221
|
state.gameOver = true;
|
|
222
|
+
state.showGameResult = true;
|
|
194
223
|
clearInterval(interval);
|
|
195
224
|
const animatingInterval = setInterval(() => {
|
|
196
225
|
state.showGameResult = !state.showGameResult;
|
|
@@ -223,6 +252,17 @@ export default function App({
|
|
|
223
252
|
}, {
|
|
224
253
|
isActive: state.status !== 'RESULTS' && state.status !== 'RUNNING'
|
|
225
254
|
});
|
|
255
|
+
if (snap.status === 'QUESTION') {
|
|
256
|
+
return /*#__PURE__*/React.createElement(Text, null, "Handicap count was exceeded max value (", maxHandicapCount, "), please specify another one.", /*#__PURE__*/React.createElement(Newline, null), /*#__PURE__*/React.createElement(TextInput, {
|
|
257
|
+
value: snap.inputHandicapCount,
|
|
258
|
+
onChange: value => {
|
|
259
|
+
if (isLastNum(value)) {
|
|
260
|
+
state.inputHandicapCount = value;
|
|
261
|
+
state.handicapCount = Number(value);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
226
266
|
if (snap.status === 'RESULTS') {
|
|
227
267
|
return /*#__PURE__*/React.createElement(Results, {
|
|
228
268
|
sortBy: sortBy,
|
|
@@ -311,7 +351,7 @@ export default function App({
|
|
|
311
351
|
}, /*#__PURE__*/React.createElement(Box, {
|
|
312
352
|
marginTop: 1
|
|
313
353
|
}, /*#__PURE__*/React.createElement(Text, null, "You")), /*#__PURE__*/React.createElement(Box, {
|
|
314
|
-
width: "
|
|
354
|
+
width: "97%",
|
|
315
355
|
borderStyle: "single",
|
|
316
356
|
borderColor: getBorderColor(snap.status)
|
|
317
357
|
}, isFinished(snap.status) ? /*#__PURE__*/React.createElement(Text, {
|
|
@@ -322,15 +362,18 @@ export default function App({
|
|
|
322
362
|
if (snap.source.indexOf(value) === 0) {
|
|
323
363
|
if (snap.userText.length < value.length) {
|
|
324
364
|
const now = currentTime();
|
|
365
|
+
const actualPrinted = state.handicap ? value.slice(getHaLeft(state.source, state.handicapCount).length) : value;
|
|
325
366
|
state.firstPart = value;
|
|
326
367
|
state.erroredPart = '';
|
|
327
368
|
state.userText = value;
|
|
328
|
-
state.
|
|
329
|
-
state.
|
|
369
|
+
state.printedByUserText = actualPrinted;
|
|
370
|
+
state.charCount = actualPrinted.length;
|
|
371
|
+
state.cps = calculateCPS(actualPrinted.length, snap.startTime, now);
|
|
330
372
|
const newWordCount = state.wordCount + 1;
|
|
331
373
|
if (isWordTyped(snap.source, value)) {
|
|
332
374
|
state.wordCount = newWordCount;
|
|
333
375
|
state.wpm = calculateWPM(newWordCount, snap.startTime, now, snap.source === value);
|
|
376
|
+
state.handicappedWpm = calculateWPM(newWordCount + snap.handicapCount, snap.startTime, now, snap.source === value);
|
|
334
377
|
}
|
|
335
378
|
state.frms.push(now - state.startTime);
|
|
336
379
|
if (snap.source === value) {
|
|
@@ -339,10 +382,7 @@ export default function App({
|
|
|
339
382
|
}
|
|
340
383
|
state.status = 'WON';
|
|
341
384
|
state.finishTime = now;
|
|
342
|
-
state.cpm = calculateCPM(
|
|
343
|
-
|
|
344
|
-
// RegisterBestFrames(config, state.frms);
|
|
345
|
-
|
|
385
|
+
state.cpm = calculateCPM(actualPrinted.length, snap.startTime, now);
|
|
346
386
|
registerResult(config, new Date(), {
|
|
347
387
|
wpm: state.wpm,
|
|
348
388
|
cps: state.cps,
|
|
@@ -362,13 +402,15 @@ export default function App({
|
|
|
362
402
|
color: getScoreTextColor(snap.cpm, snap.robotCpm)
|
|
363
403
|
}, "CPM: ", snap.cpm), /*#__PURE__*/React.createElement(Text, null, " "), /*#__PURE__*/React.createElement(Text, {
|
|
364
404
|
color: getWpmTextColor(snap.wpm, snap.robotWpm)
|
|
365
|
-
}, "WPM: ", snap.wpm))
|
|
405
|
+
}, "WPM: ", snap.wpm), snap.handicap && /*#__PURE__*/React.createElement(Text, null, " (W. handicap "), snap.handicap && /*#__PURE__*/React.createElement(Text, {
|
|
406
|
+
color: getWpmTextColor(snap.handicappedWpm, snap.robotWpm)
|
|
407
|
+
}, "WPM: ", snap.handicappedWpm), snap.handicap && /*#__PURE__*/React.createElement(Text, null, ")"))), /*#__PURE__*/React.createElement(Box, {
|
|
366
408
|
width: stdout.columns / 2.05,
|
|
367
409
|
alignItems: "center",
|
|
368
410
|
flexDirection: "column",
|
|
369
411
|
marginTop: 1
|
|
370
412
|
}, /*#__PURE__*/React.createElement(Box, null, /*#__PURE__*/React.createElement(Text, null, snap.usingBestResult ? 'Your best result' : 'Robot')), /*#__PURE__*/React.createElement(Box, {
|
|
371
|
-
width: "
|
|
413
|
+
width: "97%",
|
|
372
414
|
borderStyle: "single",
|
|
373
415
|
borderColor: getRobotBorderColor(snap.status),
|
|
374
416
|
flexDirection: "column"
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import compose from 'just-compose';
|
|
|
7
7
|
import filter from 'just-filter-object';
|
|
8
8
|
import App from './app.js';
|
|
9
9
|
import { config } from './config.js';
|
|
10
|
+
import { getBestWpmResult, getGoal, wasGoalAchieved } from './helpers.js';
|
|
10
11
|
const cli = meow(`
|
|
11
12
|
Usage
|
|
12
13
|
$ typing-game-cli
|
|
@@ -22,11 +23,15 @@ const cli = meow(`
|
|
|
22
23
|
--extra-fast Start a round with a robot having extra high typing speed.
|
|
23
24
|
--medium Start a round with a robot having medium typing speed.
|
|
24
25
|
--low Start a round with a robot having low typing speed.
|
|
26
|
+
--handicap Start a round with handicap given by opponent to you by specified count of chars
|
|
27
|
+
--handicap-count How many chars you wish to ask from opponent
|
|
25
28
|
--display-results Show cpm and wpm results
|
|
26
29
|
--sort-by Sort results by specified value (-cpm, cpm, -wpm, wpm, -date, date), Starting "-" indicates descending order, default is "-date"
|
|
27
30
|
--all-history Show all history when displaying results (otherwise (default) display last 10 results respecting sorting parameter)
|
|
28
31
|
--topic Use sentences from works written by specified author
|
|
29
32
|
--top-n Display top n results in displaying results mode
|
|
33
|
+
--set-goal Set a goal (in wpm) that you wish to achieve
|
|
34
|
+
--goal-achieved Let you know whether a goal was achieved
|
|
30
35
|
|
|
31
36
|
Short flags and aliases for options:
|
|
32
37
|
--against-my-best: -b, --best, --my-best, --myself, --against-my-best-result
|
|
@@ -38,8 +43,12 @@ const cli = meow(`
|
|
|
38
43
|
--sort-by -s
|
|
39
44
|
--show-all-history: -a, --all, --all-history
|
|
40
45
|
--clear-results: -c, --clear
|
|
41
|
-
--topic: --author
|
|
46
|
+
--topic: -t, --author, --tpc
|
|
42
47
|
--top-n: --top
|
|
48
|
+
--handicap --ha, --hndcp, --hdc, --han
|
|
49
|
+
--handicap-count --hanco, --hanCo, --hndco, --haco
|
|
50
|
+
--set-goal --sgoal, --goal
|
|
51
|
+
--goal-achieved --goalach, --coolprogress, --progone, --goaldone, --aic
|
|
43
52
|
|
|
44
53
|
|
|
45
54
|
Examples
|
|
@@ -58,7 +67,10 @@ const cli = meow(`
|
|
|
58
67
|
$ typing-game-cli -r -s="-wpm" --all-history
|
|
59
68
|
$ typing-game-cli -r -s="-wpm" -a
|
|
60
69
|
$ typing-game-cli --topic mark-twain
|
|
61
|
-
$ typing-game-cli --
|
|
70
|
+
$ typing-game-cli --topic ambrose-bierce
|
|
71
|
+
$ typing-game-cli --set-goal 60
|
|
72
|
+
$ typing-game-cli --goal 60
|
|
73
|
+
$ typing-game-cli --goal-achieved
|
|
62
74
|
`, {
|
|
63
75
|
importMeta: import.meta,
|
|
64
76
|
flags: {
|
|
@@ -79,6 +91,22 @@ const cli = meow(`
|
|
|
79
91
|
type: 'boolean',
|
|
80
92
|
shortFlag: 'l'
|
|
81
93
|
},
|
|
94
|
+
handicap: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
aliases: ['hndcp', 'hdc', 'han', 'ha']
|
|
97
|
+
},
|
|
98
|
+
handicapCount: {
|
|
99
|
+
type: 'number',
|
|
100
|
+
aliases: ['hanco', 'hanCo', 'hndco', 'haco']
|
|
101
|
+
},
|
|
102
|
+
setGoal: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
aliases: ['sgoal', 'goal']
|
|
105
|
+
},
|
|
106
|
+
goalAchieved: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
aliases: ['goalach', 'coolprogress', 'progrone', 'progone', 'goaldone', 'aic']
|
|
109
|
+
},
|
|
82
110
|
displayResults: {
|
|
83
111
|
type: 'boolean',
|
|
84
112
|
shortFlag: 'r',
|
|
@@ -118,7 +146,8 @@ const cli = meow(`
|
|
|
118
146
|
},
|
|
119
147
|
topic: {
|
|
120
148
|
type: 'string',
|
|
121
|
-
|
|
149
|
+
shortFlag: 't',
|
|
150
|
+
aliases: ['author', 'tpc']
|
|
122
151
|
}
|
|
123
152
|
}
|
|
124
153
|
});
|
|
@@ -128,8 +157,28 @@ if (cli.flags.clearResults) {
|
|
|
128
157
|
config.clearAll();
|
|
129
158
|
exitNow();
|
|
130
159
|
}
|
|
160
|
+
if (cli.flags.setGoal) {
|
|
161
|
+
config.appendGoal(cli.flags.setGoal);
|
|
162
|
+
exitNow();
|
|
163
|
+
}
|
|
164
|
+
if (cli.flags.goalAchieved) {
|
|
165
|
+
const achiemenceResult = wasGoalAchieved();
|
|
166
|
+
if (achiemenceResult === null) {
|
|
167
|
+
console.log(`There are no goal yet. First set a goal by --set-goal [wpm] command`);
|
|
168
|
+
} else {
|
|
169
|
+
let messageToSay = achiemenceResult ? `Congrats, you have achieved your goal.` : `You goal is not achieved yet.`;
|
|
170
|
+
if (!achiemenceResult) {
|
|
171
|
+
const bestResult = getBestWpmResult();
|
|
172
|
+
messageToSay += ` Best result - ${bestResult.value.wpm}, goal - ${getGoal()}. Keep trying, don't give up!`;
|
|
173
|
+
}
|
|
174
|
+
console.log(messageToSay);
|
|
175
|
+
}
|
|
176
|
+
exitNow();
|
|
177
|
+
}
|
|
131
178
|
const robotLevel = compose(flags => pick(flags, ['extraFast', 'fast', 'medium', 'low']), flags => filter(flags, (_, value) => value), flags => Object.keys(flags)[0] || 'medium')(cli.flags);
|
|
132
179
|
const {
|
|
180
|
+
handicap,
|
|
181
|
+
handicapCount,
|
|
133
182
|
displayResults,
|
|
134
183
|
sortBy,
|
|
135
184
|
showAllHistory,
|
|
@@ -138,8 +187,11 @@ const {
|
|
|
138
187
|
topN,
|
|
139
188
|
topic
|
|
140
189
|
} = cli.flags;
|
|
190
|
+
console.clear();
|
|
141
191
|
render(/*#__PURE__*/React.createElement(App, {
|
|
142
192
|
robotLevel: robotLevel,
|
|
193
|
+
handicap: handicap,
|
|
194
|
+
handicapCount: handicapCount,
|
|
143
195
|
displayResults: displayResults,
|
|
144
196
|
sortBy: sortBy,
|
|
145
197
|
isShowAllHistory: showAllHistory,
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { formatISO } from 'date-fns';
|
|
4
5
|
const defaultConfig = {};
|
|
5
6
|
export default class Config {
|
|
6
7
|
constructor() {
|
|
@@ -29,10 +30,27 @@ export default class Config {
|
|
|
29
30
|
...previous,
|
|
30
31
|
...entry
|
|
31
32
|
}, null, 4);
|
|
32
|
-
|
|
33
|
+
this.persist(data);
|
|
34
|
+
}
|
|
35
|
+
appendGoal(wpm) {
|
|
36
|
+
const previous = this.get();
|
|
37
|
+
const currentGoals = previous.goals || [];
|
|
38
|
+
currentGoals.push({
|
|
39
|
+
wpm,
|
|
40
|
+
date: formatISO(new Date())
|
|
41
|
+
});
|
|
42
|
+
const data = JSON.stringify({
|
|
43
|
+
...previous,
|
|
44
|
+
goals: currentGoals,
|
|
45
|
+
goal: wpm
|
|
46
|
+
}, null, 4);
|
|
47
|
+
this.persist(data);
|
|
33
48
|
}
|
|
34
49
|
clearAll() {
|
|
35
|
-
|
|
50
|
+
this.persist('{}');
|
|
51
|
+
}
|
|
52
|
+
persist(data) {
|
|
53
|
+
fs.writeFileSync(this._configFile, data, 'utf8');
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
export const config = new Config();
|
package/dist/constants.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
const optionKeyColor = 'cyan';
|
|
2
|
-
|
|
2
|
+
const defaultHandicapCount = 18;
|
|
3
|
+
const maxHandicapCount = 12;
|
|
4
|
+
const numerics = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
5
|
+
export { optionKeyColor, defaultHandicapCount, maxHandicapCount, numerics };
|
package/dist/helpers.js
CHANGED
|
@@ -8,6 +8,7 @@ import sortBy from 'just-sort-by';
|
|
|
8
8
|
import { format, formatISO, isValid, parseISO } from 'date-fns';
|
|
9
9
|
import Config from './config.js';
|
|
10
10
|
import { frames } from './robotFrames.js';
|
|
11
|
+
import { defaultHandicapCount, numerics } from './constants.js';
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = path.dirname(__filename);
|
|
13
14
|
const won = 'WON';
|
|
@@ -31,6 +32,71 @@ const getStatusVariant = status => {
|
|
|
31
32
|
if (isLost(status)) return 'error';
|
|
32
33
|
return '';
|
|
33
34
|
};
|
|
35
|
+
const getSentencesSkippingWords = (sentences, wordCountToSkip) => {
|
|
36
|
+
if (wordCountToSkip > 0) {
|
|
37
|
+
let previousSpace = false;
|
|
38
|
+
let sliceStart = 0;
|
|
39
|
+
let counter = 0;
|
|
40
|
+
let i = 0;
|
|
41
|
+
for (const ch of sentences) {
|
|
42
|
+
if (ch === ' ') {
|
|
43
|
+
if (previousSpace) {
|
|
44
|
+
sliceStart = i;
|
|
45
|
+
} else {
|
|
46
|
+
sliceStart = i;
|
|
47
|
+
previousSpace = true;
|
|
48
|
+
counter++;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
previousSpace &&= false;
|
|
52
|
+
if (wordCountToSkip === counter) {
|
|
53
|
+
sliceStart = i;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
i++;
|
|
58
|
+
}
|
|
59
|
+
return sentences.slice(sliceStart);
|
|
60
|
+
}
|
|
61
|
+
return sentences;
|
|
62
|
+
};
|
|
63
|
+
const getHaLeft = (sentences, wordCountToSkip) => {
|
|
64
|
+
if (wordCountToSkip > 0) {
|
|
65
|
+
let previousSpace = false;
|
|
66
|
+
let sliceStart = 0;
|
|
67
|
+
let counter = 0;
|
|
68
|
+
let i = 0;
|
|
69
|
+
for (const ch of sentences) {
|
|
70
|
+
if (ch === ' ') {
|
|
71
|
+
if (previousSpace) {
|
|
72
|
+
sliceStart = i;
|
|
73
|
+
} else {
|
|
74
|
+
sliceStart = i;
|
|
75
|
+
previousSpace = true;
|
|
76
|
+
counter++;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
previousSpace &&= false;
|
|
80
|
+
if (wordCountToSkip === counter) {
|
|
81
|
+
sliceStart = i;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
return sentences.slice(0, sliceStart > 0 ? sliceStart : 56);
|
|
88
|
+
}
|
|
89
|
+
return '';
|
|
90
|
+
};
|
|
91
|
+
const getHaCount = (value, inputValue) => {
|
|
92
|
+
if (inputValue !== undefined && inputValue !== '') {
|
|
93
|
+
return Number(inputValue);
|
|
94
|
+
}
|
|
95
|
+
if (value === undefined || value === null) {
|
|
96
|
+
return defaultHandicapCount;
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
};
|
|
34
100
|
const getWpmTextColor = (wpm, otherWpm) => {
|
|
35
101
|
if (wpm > otherWpm) return 'green';
|
|
36
102
|
if (otherWpm > wpm) return 'red';
|
|
@@ -83,6 +149,12 @@ const getWordCount = sentence => {
|
|
|
83
149
|
};
|
|
84
150
|
const isFinished = status => [won, lost].includes(status);
|
|
85
151
|
const isWordTyped = (source, outgoing) => source.length === outgoing.length || source.slice(outgoing.length, outgoing.length + 1) === ' ';
|
|
152
|
+
const isLastNumber = value => {
|
|
153
|
+
return numerics.includes(value.slice(-1));
|
|
154
|
+
};
|
|
155
|
+
const isAboutToFinish = (source, value) => {
|
|
156
|
+
return source === value;
|
|
157
|
+
};
|
|
86
158
|
const calculateWPM = (wordCount, startTime, finishTime, finished = false) => {
|
|
87
159
|
const durInMinutes = (finishTime - startTime) / 60_000;
|
|
88
160
|
return finished || durInMinutes > 1 ? Math.round(wordCount / durInMinutes) : wordCount;
|
|
@@ -153,6 +225,22 @@ const getResults = ({
|
|
|
153
225
|
}
|
|
154
226
|
return result;
|
|
155
227
|
};
|
|
228
|
+
const getGoal = () => {
|
|
229
|
+
const config = new Config();
|
|
230
|
+
const data = config.get();
|
|
231
|
+
return data.goal;
|
|
232
|
+
};
|
|
233
|
+
const wasGoalAchieved = () => {
|
|
234
|
+
const goal = getGoal();
|
|
235
|
+
if (goal) {
|
|
236
|
+
const bestResult = getBestWpmResult();
|
|
237
|
+
if (bestResult === undefined) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return goal <= bestResult.value.wpm;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
156
244
|
const getBestResult = () => {
|
|
157
245
|
const config = new Config();
|
|
158
246
|
const data = config.get();
|
|
@@ -219,4 +307,4 @@ const registerBestFrames = (config, frames) => {
|
|
|
219
307
|
bestFrames: frames
|
|
220
308
|
});
|
|
221
309
|
};
|
|
222
|
-
export { getDefaultSuite, getStatusVariant, getWpmTextColor, getScoreTextColor, getMessage, getMessageOrPlaceholder, getBorderColor, getRobotBorderColor, getIntervalMs, isFinished, isWordTyped, calculateWPM, calculateCPS, calculateCPM, getCompetitionResult, getTypingWord, getRemainingPart, getResults, getBestResult, getBestResultCompactString, getBestFrames, getOpponentFrames, getResultByWordCount, getBestWpmResult, getSortedByString, getWordCount, getSuiteByTopic, registerResult, registerBestFrames };
|
|
310
|
+
export { getDefaultSuite, getStatusVariant, getSentencesSkippingWords, getHaLeft, getHaCount, getWpmTextColor, getScoreTextColor, getMessage, getMessageOrPlaceholder, getBorderColor, getRobotBorderColor, getIntervalMs, getGoal, isFinished, isWordTyped, isLastNumber as isLastNum, isAboutToFinish, calculateWPM, calculateCPS, calculateCPM, getCompetitionResult, getTypingWord, getRemainingPart, getResults, getBestResult, getBestResultCompactString, getBestFrames, getOpponentFrames, getResultByWordCount, getBestWpmResult, getSortedByString, getWordCount, getSuiteByTopic, registerResult, registerBestFrames, wasGoalAchieved };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Text, useInput } from 'ink';
|
|
3
|
+
export default function QuestionPrompt() {
|
|
4
|
+
const [showApp, setShowApp] = useState(false);
|
|
5
|
+
useInput((input, _key) => {
|
|
6
|
+
if (input === 'y') {
|
|
7
|
+
setShowApp(true);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
return showApp ? /*#__PURE__*/React.createElement(Text, null, "UIE") : /*#__PURE__*/React.createElement(Text, null, "OTHE");
|
|
11
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sentences": [
|
|
3
|
-
"Last night I dreamt I went to Manderley again. It seemed to me I stood by the iron
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
3
|
+
"Last night I dreamt I went to Manderley again. It seemed to me I stood by the iron gateading to the drive, and for a while I could not enter, for the way was barred to me. There was a padlock and a chain upon the gate. I called in my dream to the lodge-keeper, and had no answer, and peering closer through the rusted spokes of the gate I saw that the lodge was uninhabited. There was a padlock and a chain upon the gate.",
|
|
4
|
+
"I called in my dream to the lodge-keeper, and had no answer, and peering closer through the rusted spokes of the gate I saw that the lodge was uninhabited. No smoke came from the chimney, and the little lattice windows gaped forlorn. Then, like all dreamers, I was possessed of a sudden with supernatural powers and passed like a spirit through the barrier before me. The drive wound away in front of me, twisting and turning as it had always done, but as I advanced I was aware that a change had come upon it; it was narrow and unkept, not the drive that we had known.",
|
|
5
|
+
"At first I was puzzled and did not understand, and it was only when I bent my head to avoid the low swinging branch of a tree that I realised what had happened. Nature had come into her own again and, little by, little, in her stealthy, insidious way had encroached upon the drive with long tenacious fingers. The woods, always a menace even in the past, had triumphed in the end.",
|
|
6
|
+
"They crowded, dark and uncrontrolled, to the borders of the drive. The beeches with white. naked limbs leant close to one another, their branches intermingled in a strange embrace, making a vault above my head like the archway of a church. And there were other trees as well, trees that I did not recognise, squat oaks and tortured elms that straggled cheek by jowl with the beeches, and had trust themselves out of the quiet earth, along with monster shrubs amd plants, none of which I remembered.",
|
|
7
|
+
"The drive was a ribbon now, a thread of its former self, with gravel surface gone, and choked with grass and moss. The trees had thrown out low branches, making an impediment to progress; the gnarled roots looked like skeleton claws. Scattered here and again amongs his jungle growth I would recognise shrubs that had been land-marks in our time, things of culture and of gracem hydrangeas whose blue heads had been famous.",
|
|
8
|
+
"No hand had checked their progress, and they had gone native now, rearing to monster height without a bloom, back and ugly as the nameless parasites that grew beside them. On and on, now east, now west, would the poor thread that once had been out drive. Sometimes I thought it lost, but it appeared again, beneath a fallen tree perhaps or struggling on the other side of a muddied ditch created by the winter rains.",
|
|
9
|
+
"I had not thought the way so long. Surely the miles had multiplied, even as the trees had done, and this path led but to a labirynth, some choked wilderness, and not to the house at all. I came upom it suddenly; the approach masked by the unnatural growth of a vast shrub that spread in all directions, and I stood, my heart thumping in my breast, the strange prick of tears behind my eyes.",
|
|
10
|
+
"There was Mandarley, our Manderley, secretive and silent as it had always been, the grey stone shining in the moonlight of my dream, the grey stone shining in the monnloight of my dream, the mullioned windows reflecting the green lawns and the terrace. Time could not wreck the perfect symmetry of those walls, not the site itself, a jewel in the hollow of a hand.",
|
|
11
|
+
"The terrace sloped to the lawns, and the lawans streched to the sea, and turning I could see the sheet of silver, placid under the moon, like a lake undistributed by wind or storm. No waves would come to ruffle this dream water, and no bulk of cloud, wind-drown from the west, obscure the claruty of this pale sky. I turned again to the house, and thought it stood inviolate, untouched, as though we ourselves had left but yesterday, I saw that the garden had obeyed the jungle law, even as the woods had done.",
|
|
12
|
+
"The rhododendroons stood fifty feet high, twisted and entwined with bracken, and they had entered into alien marriage with a host of nameless shrubs, poor, bastard things that clung about their roots as though conscious of their spurious origin. Ivy held prior place in this lost garden, the long strands crept across the lawns, and soon would encroach upon the house itself.",
|
|
13
|
+
"There was another plant too, some halfbreed from the woods, whose seed had been scattered long ago beneath the trees and then forgotten, and now, marching in unison with the iby, thrust its ugly form like a giant rhubarb towards the soft grass where the daffodils had been blown. Nettles were everywhere, the van guard of the army. They choked the terrace, they sprawled about the paths, they leant, vulgar and lanky, against the very windows of the house."
|
|
10
14
|
],
|
|
11
15
|
"tags": ["daphne-du-maurier", "maurier"]
|
|
12
16
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sentences": [
|
|
3
|
+
"Most people would say, if their opinion was asked for, that holding up a train would be a hard job. Well, it isn't; it's easy. I have contributed some to the uneasiness of railroads and the insomnia of express companies, and the most trouble I ever had about a hold-up was in being swindled by unscrupulous people while spending the money I got. The danger wasn't anything to speak of, and we didn't mind the trouble.",
|
|
4
|
+
"One man has come pretty near robbing a train by himself; two have succeeded a few times; three can do it if they are hustlers, but five is about the right number. The time to do it and the place depend upon several things. The first 'stick-up' I was ever in happened in 1890. Maybe the way I got into it will explain how most train robbers start in the business.",
|
|
5
|
+
"Five out of six Western outlaws are just cowboys out of a job and gone wrong. The sixth is a tough from the East who dresses up like a bad man and plays some low-down trick that gives the boys a bad name. Wire fences and 'nesters' made five of them; a bad heart made the sixth. Jim S-- and I were working on the 101 Ranch in Colorado.",
|
|
6
|
+
"The nesters had the cowman on t he go. They had taken up the land and elected officers who were hard to get along with. Jim and I rode into La Junta one day, going south from a round-up. We were having a little fun without malice toward any-body when a farmer administration cut in and tried to harvest us. Jim shot a deputy marshal, and I kind of corroborated his side of the argument.",
|
|
7
|
+
"We skirmished up and down the main street, the boomers having bad luck all the time. After a while we leaned forward and shoved for the ranch down on the Ceriso. We were riding a couple of horses that couldn't fly, but they could catch birds. A few days after that, a gang of the La Junta boomers came to the ranch and wanted us to go back with them. Naturally, we declined.",
|
|
8
|
+
"We had the house on them, and before we were done refusing, that old 'dobe was plumb full of lead. When dark came we fagged 'em a batch of bullets and shoved out the back door for the rocks. They sure smoked us as we went. We had to drift, which we did, and rounded up down in Oklahoma. Well, there wasn't anything we could get there, and, being mighty hard up, we decided to transact a little business with the railroads."
|
|
9
|
+
],
|
|
10
|
+
"tags": ["ohenry", "henry", "holdingupatrain", "holdinguptrain"]
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typing-game-cli",
|
|
3
3
|
"description": "Command line game to practice your typing speed by competing against typer-robot or against your best result",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "7.1.0",
|
|
5
5
|
"homepage": "https://github.com/akgondber/typing-game-cli",
|
|
6
6
|
"repository": "akgondber/typing-game-cli",
|
|
7
7
|
"author": "Rushan Alyautdinov <akgondber@gmail.com>",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"prettify": "prettier . --write",
|
|
27
27
|
"xofix": "xo --fix",
|
|
28
28
|
"pretcheck": "prettier --check .",
|
|
29
|
-
"telint": "textlint README.md",
|
|
29
|
+
"telint": "textlint README.md docs/demos.md",
|
|
30
30
|
"bumpp": "bumpp"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"typingpracrice",
|
|
115
115
|
"bestresult",
|
|
116
116
|
"terminalgame",
|
|
117
|
-
"cligame"
|
|
117
|
+
"cligame",
|
|
118
|
+
"typingspeed"
|
|
118
119
|
]
|
|
119
120
|
}
|
package/readme.md
CHANGED
|
@@ -27,12 +27,16 @@ $ npm install --global typing-game-cli
|
|
|
27
27
|
--extra-fast Start a round with a robot having extra high typing speed.
|
|
28
28
|
--medium Start a round with a robot having medium typing speed.
|
|
29
29
|
--low Start a round with a robot having low typing speed.
|
|
30
|
+
--handicap Start a round with handicap given by opponent to you by specified count of chars
|
|
31
|
+
--handicap-count How many chars you wish to ask from opponent
|
|
30
32
|
--display-results Show cpm and wpm results
|
|
31
33
|
--sort-by Sort results by specified value (-cpm, cpm, -wpm, wpm, -date, date), Starting "-" indicates descending order, default is "-date"
|
|
32
34
|
--all-hostory Show all history when displaying results (otherwise (default) display last 10 results respecting sorting parameter)
|
|
33
35
|
--compact-result Display top result in compact format
|
|
34
36
|
--topic Use sentences from works written by specified author
|
|
35
37
|
--top-n Display top n results when `--display-results` is being used
|
|
38
|
+
--set-goal Set a goal (in wpm) that you wish to achieve
|
|
39
|
+
--goal-achieved Let you know whether a goal was achieved
|
|
36
40
|
|
|
37
41
|
Short flags and aliases for options:
|
|
38
42
|
--against-my-best: -b, --best, --my-best, --myself, --against-my-best-result
|
|
@@ -47,6 +51,10 @@ $ npm install --global typing-game-cli
|
|
|
47
51
|
--compact-result --cmpc
|
|
48
52
|
--topic --author
|
|
49
53
|
--top-n --top
|
|
54
|
+
--handicap --ha, --hndcp, --hdc, --han
|
|
55
|
+
--handicap-count --hanco, --hanCo, --hndco, --haco
|
|
56
|
+
--set-goal --sgoal, --goal
|
|
57
|
+
--goal-achieved --goalach, --coolprogress, --progone, --goaldone, --aic
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
Examples
|
|
@@ -72,6 +80,8 @@ $ npm install --global typing-game-cli
|
|
|
72
80
|
|
|
73
81
|

|
|
74
82
|
|
|
83
|
+
[More demos](docs/demos.md)
|
|
84
|
+
|
|
75
85
|
## Screenshots
|
|
76
86
|
|
|
77
87
|
### Competition against fast robot
|