typing-game-cli 6.0.0 → 7.0.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 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
- state.userText = '';
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 = random(state.suite.sentences);
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.userText.length, state.startTime, now);
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: "98%",
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.charCount++;
329
- state.cps = calculateCPS(value.length, snap.startTime, now);
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(value.length, snap.startTime, now);
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))), /*#__PURE__*/React.createElement(Box, {
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: "98%",
413
+ width: "97%",
372
414
  borderStyle: "single",
373
415
  borderColor: getRobotBorderColor(snap.status),
374
416
  flexDirection: "column"
package/dist/cli.js CHANGED
@@ -22,6 +22,8 @@ const cli = meow(`
22
22
  --extra-fast Start a round with a robot having extra high typing speed.
23
23
  --medium Start a round with a robot having medium typing speed.
24
24
  --low Start a round with a robot having low typing speed.
25
+ --handicap Start a round with handicap given by opponent to you by specified count of chars
26
+ --handicap-count How many chars you wish to ask from opponent
25
27
  --display-results Show cpm and wpm results
26
28
  --sort-by Sort results by specified value (-cpm, cpm, -wpm, wpm, -date, date), Starting "-" indicates descending order, default is "-date"
27
29
  --all-history Show all history when displaying results (otherwise (default) display last 10 results respecting sorting parameter)
@@ -38,8 +40,10 @@ const cli = meow(`
38
40
  --sort-by -s
39
41
  --show-all-history: -a, --all, --all-history
40
42
  --clear-results: -c, --clear
41
- --topic: --author
43
+ --topic: -t, --author, --tpc
42
44
  --top-n: --top
45
+ --handicap --ha, --hndcp, --hdc, --han
46
+ --handicap-count --hanco, --hanCo, --hndco, --haco
43
47
 
44
48
 
45
49
  Examples
@@ -58,7 +62,7 @@ const cli = meow(`
58
62
  $ typing-game-cli -r -s="-wpm" --all-history
59
63
  $ typing-game-cli -r -s="-wpm" -a
60
64
  $ typing-game-cli --topic mark-twain
61
- $ typing-game-cli --author ambrose-bierce
65
+ $ typing-game-cli --topic ambrose-bierce
62
66
  `, {
63
67
  importMeta: import.meta,
64
68
  flags: {
@@ -79,6 +83,14 @@ const cli = meow(`
79
83
  type: 'boolean',
80
84
  shortFlag: 'l'
81
85
  },
86
+ handicap: {
87
+ type: 'boolean',
88
+ aliases: ['hndcp', 'hdc', 'han', 'ha']
89
+ },
90
+ handicapCount: {
91
+ type: 'number',
92
+ aliases: ['hanco', 'hanCo', 'hndco', 'haco']
93
+ },
82
94
  displayResults: {
83
95
  type: 'boolean',
84
96
  shortFlag: 'r',
@@ -118,7 +130,8 @@ const cli = meow(`
118
130
  },
119
131
  topic: {
120
132
  type: 'string',
121
- aliases: ['author']
133
+ shortFlag: 't',
134
+ aliases: ['author', 'tpc']
122
135
  }
123
136
  }
124
137
  });
@@ -130,6 +143,8 @@ if (cli.flags.clearResults) {
130
143
  }
131
144
  const robotLevel = compose(flags => pick(flags, ['extraFast', 'fast', 'medium', 'low']), flags => filter(flags, (_, value) => value), flags => Object.keys(flags)[0] || 'medium')(cli.flags);
132
145
  const {
146
+ handicap,
147
+ handicapCount,
133
148
  displayResults,
134
149
  sortBy,
135
150
  showAllHistory,
@@ -138,8 +153,11 @@ const {
138
153
  topN,
139
154
  topic
140
155
  } = cli.flags;
156
+ console.clear();
141
157
  render(/*#__PURE__*/React.createElement(App, {
142
158
  robotLevel: robotLevel,
159
+ handicap: handicap,
160
+ handicapCount: handicapCount,
143
161
  displayResults: displayResults,
144
162
  sortBy: sortBy,
145
163
  isShowAllHistory: showAllHistory,
package/dist/constants.js CHANGED
@@ -1,2 +1,5 @@
1
1
  const optionKeyColor = 'cyan';
2
- export { optionKeyColor };
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;
@@ -219,4 +291,4 @@ const registerBestFrames = (config, frames) => {
219
291
  bestFrames: frames
220
292
  });
221
293
  };
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 };
294
+ export { getDefaultSuite, getStatusVariant, getSentencesSkippingWords, getHaLeft, getHaCount, getWpmTextColor, getScoreTextColor, getMessage, getMessageOrPlaceholder, getBorderColor, getRobotBorderColor, getIntervalMs, isFinished, isWordTyped, isLastNumber as isLastNum, isAboutToFinish, calculateWPM, calculateCPS, calculateCPM, getCompetitionResult, getTypingWord, getRemainingPart, getResults, getBestResult, getBestResultCompactString, getBestFrames, getOpponentFrames, getResultByWordCount, getBestWpmResult, getSortedByString, getWordCount, getSuiteByTopic, registerResult, registerBestFrames };
@@ -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
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "sentences": [
3
+ "It is certain that the gulch was at one time pretty thoroughly prospected by miners, who must have had some means of getting in with at least pack animals carrying tools and supplies; their profits, apparently, were not such as would have justified any considerable outlay to connect Macarger's Gulch with any centre of civilization enjoying the distinction of a sawmill. The house, however, was there, most of it.",
4
+ "It lacked a door and a window frame, and the chimney of mud and stones had fallen into an unlovely heap, overgrown with rank weeds. Such humble furniture as there may once have been and much of the lower weather-boarding, had served as fuel in the camp fires of hunters; as had also, probably, the kerbing of an old well, which at the time I write of existed in the form of a rather wide but not very deep depression near by.",
5
+ "After rather carelessly inspecting the ruin I resumed my sport, and having fairly good success prolonged it until near sunset, when it occurred to me that I was a long way from any human habitation - too far to reach one by nightfall. But in my game bag was food, and the old house would afford shelter, if shelter were needed on a warm and dewless night in the foothills of the Sierra Nevada, where one may sleep in comfort on the pine needles, without covering.",
6
+ "The smoke escaped out of the ruined chimney, the light illuminated the room with a kindly glow, and as I ate my simple meal of plain bird and drank the remains of a bottle of red wine which had served me all the afternoon in place of the water, which the region did not supply, I experienced a sense of comfort which better fare and accommodations do not always give.",
7
+ "Outside these apertures all was black, and I was unable to repress a certain feeling of apprehension as my fancy pictured the outer world and filled it with unfriendly entities, natural and supernatural - chief among which, in their respective classes were the grizzly bear, which I knew was occasionally still seen in that region, and the ghost, which I had reason to think was not.",
8
+ "Unfortunately, our feelings do not always respect the law of probabilities, and to me that evening, the possible and the impossible were equally disquieting. Every one who has had experience in the matter must have observed that one confronts the actual and imaginary perils of the night with far less apprehension in the open air than in a house with an open doorway.",
9
+ "I felt this now as I lay on my leafy couch in a corner of the room next to the chimney and permitted my fire to die out. So strong became my sense of the presence of something malign and menacing in the place, that I found myself almost unable to withdraw my eyes from the opening, as in the deepening darkness it became more and more indistinct.",
10
+ "And when the last little flame flickered and went out I grasped the shotgun which I had laid at my side and actually turned the muzzle in the direction of the now invisible entrance, my thumb on one of the hammers, ready to cock the piece, my breath suspended, my muscles rigid and tense. But later I laid down the weapon with a sense of shame and mortification. What did I fear, and why? - I, to whom the night had been a more familiar face than that of man."
11
+ ],
12
+ "tags": ["ambrose-bierce", "the-secret-of-macargers-gulch"]
13
+ }
@@ -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 gate leading 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.",
4
- "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. 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.",
5
- "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. 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.",
6
- "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. They crwoded, 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.",
7
- "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. 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.",
8
- "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. 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.",
9
- "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. 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."
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,15 @@
1
+ {
2
+ "sentences": [
3
+ "He paused a moment at the door, and seemed struggling to command his feelings sufficiently to enable him to speak, and then, nodding his head toward his manuscript, ejaculated in a broken voice, \"Friend of mine-oh! how sad!\" and burst into tears. We were so moved at his distress that we did not think to call him back and endeavor to comfort him until he was gone, and it was too late.",
4
+ "The paper had already gone to press, but knowing that our friend would consider the publication of this item important, and cherishing the hope that to print it would afford a melancholy satisfaction to his sorrowing heart, we stopped, the press at once and inserted it in our columns: But such is life. Let us all take warning by this solemn occurrence, and let us endeavor so to conduct ourselves that when we come to die we can do it.",
5
+ "Let us place our hands upon our heart, and say with earnestness and sincerity that from this day forth we will beware of the intoxicating bowl.-'First Edition of the Californian.' The head editor has been in here raising the mischief, and tearing his hair and kicking the furniture about, and abusing me like a pickpocket. He says that every time he leaves me in charge of the paper for half an hour I get imposed upon by the first infant or the first idiot that comes along.",
6
+ "If I had been as unaccommodating and unsympathetic as some people, I would have told Mr. Bloke that I wouldn't receive his communication at such a late hour; but no, his snuffling distress touched my heart, and I jumped at the chance of doing something to modify his misery. I never read his item to see whether there was anything wrong about it, but hastily wrote the few lines which preceded it, and sent it to the printers. And what has my kindness done for me?",
7
+ "It has done nothing but bring down upon me a storm of abuse and ornamental blasphemy. Now I will read that item myself, and see if there is any foundation for all this fuss. And if there is, the author of it shall hear from me. I have read it, and I am bound to admit that it seems a little mixed at a first glance. However, I will peruse it once more. I have read it again, and it does really seem a good deal more mixed than ever.",
8
+ "Who is William Schuyler, anyhow, and what part of South Park did he live in, and if he started down-town at six o'clock, did he ever get there, and if he did, did anything happen to him? Is he the individual that met with the \"distressing accident\"? Considering the elaborate circumstantiality of detail observable in the item, it seems to me that it ought to contain more information than it does. On the contrary, it is obscure and not only obscure, but utterly incomprehensible.",
9
+ "Was the breaking of Mr. Schuyler's leg, fifteen years ago, the \"distressing accident\" that plunged Mr. Bloke into unspeakable grief, and caused him to come up here at dead of night and stop our press to acquaint the world with the circumstance? Or did the \"distressing accident\" consist in the destruction of Schuyler's mother-in-law's property in early times?",
10
+ "Or did it consist in the death of that person herself three years ago (albeit it does not appear that she died by accident)? In a word, what did that \"distressing accident\" consist in? What did that driveling ass of a Schuyler stand in the wake of a runaway horse for, with his shouting and gesticulating, if he wanted to stop him? And how the mischief could he get run over by a horse that had already passed beyond him? And what are we to take \"warning\" by?",
11
+ "And how is this extraordinary chapter of incomprehensibilities going to be a \"lesson\" to us? And, above all, what has the intoxicating \"bowl\" got to do with it, anyhow? It is not stated that Schuyler drank, or that his wife drank, or that his mother-in-law drank, or that the horse drank wherefore, then, the reference to the intoxicating bowl?",
12
+ "There certainly seems to have been an accident of some kind or other, but it is impossible to determine what the nature of it was, or who was the sufferer by it. I do not like to do it, but I feel compelled to request that the next time anything happens to one of Mr. Bloke's friends, he will append such explanatory notes to his account of it as will enable me to find out what sort of an accident it was and whom it happened to."
13
+ ],
14
+ "tags": ["marktwain", "mrblokesitem"]
15
+ }
@@ -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": "6.0.0",
4
+ "version": "7.0.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>",
@@ -25,7 +25,9 @@
25
25
  "go": "node dist/cli.js",
26
26
  "prettify": "prettier . --write",
27
27
  "xofix": "xo --fix",
28
- "pretcheck": "prettier --check ."
28
+ "pretcheck": "prettier --check .",
29
+ "telint": "textlint README.md docs/demos.md",
30
+ "bumpp": "bumpp"
29
31
  },
30
32
  "files": [
31
33
  "dist"
@@ -52,6 +54,7 @@
52
54
  "@babel/cli": "^7.22.5",
53
55
  "@babel/preset-react": "^7.25.9",
54
56
  "ava": "^6.1.2",
57
+ "bumpp": "^10.2.0",
55
58
  "eslint-config-xo-react": "^0.27.0",
56
59
  "eslint-plugin-react": "^7.34.1",
57
60
  "eslint-plugin-react-hooks": "^4.6.0",
@@ -59,6 +62,9 @@
59
62
  "ink-testing-library": "^3.0.0",
60
63
  "loadr": "^0.1.1",
61
64
  "prettier": "^3.2.5",
65
+ "textlint": "^15.2.0",
66
+ "textlint-rule-doubled-spaces": "^1.0.2",
67
+ "textlint-rule-no-dead-link": "^5.2.0",
62
68
  "xo": "^0.58.0"
63
69
  },
64
70
  "ava": {
package/readme.md CHANGED
@@ -27,6 +27,8 @@ $ 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)
@@ -72,6 +74,8 @@ $ npm install --global typing-game-cli
72
74
 
73
75
  ![](media/demo.gif)
74
76
 
77
+ [More demos](docs/demos.md)
78
+
75
79
  ## Screenshots
76
80
 
77
81
  ### Competition against fast robot