mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-27 17:27:32 +08:00
refactor: word generation (#5344)
This commit is contained in:
parent
e632df7442
commit
6579f68955
4 changed files with 165 additions and 170 deletions
|
|
@ -56,7 +56,6 @@ import * as FunboxList from "./funbox/funbox-list";
|
|||
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
|
||||
import * as KeymapEvent from "../observables/keymap-event";
|
||||
import * as LayoutfluidFunboxTimer from "../test/funbox/layoutfluid-funbox-timer";
|
||||
import * as Wordset from "./wordset";
|
||||
import * as ArabicLazyMode from "../states/arabic-lazy-mode";
|
||||
import Format from "../utils/format";
|
||||
|
||||
|
|
@ -526,7 +525,7 @@ export async function init(): Promise<void> {
|
|||
|
||||
//add word during the test
|
||||
export async function addWord(): Promise<void> {
|
||||
let bound = 100;
|
||||
let bound = 100; // how many extra words to aim for AFTER the current word
|
||||
const funboxToPush = FunboxList.get(Config.funbox)
|
||||
.find((f) => f.properties?.find((fp) => fp.startsWith("toPush")))
|
||||
?.properties?.find((fp) => fp.startsWith("toPush:"));
|
||||
|
|
@ -587,27 +586,26 @@ export async function addWord(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
const language: MonkeyTypes.LanguageObject =
|
||||
Config.mode !== "custom"
|
||||
? await JSONData.getCurrentLanguage(Config.language)
|
||||
: {
|
||||
//borrow the direction of the current language
|
||||
...(await JSONData.getCurrentLanguage(Config.language)),
|
||||
words: CustomText.getText(),
|
||||
};
|
||||
const wordset = await Wordset.withWords(language.words);
|
||||
try {
|
||||
const randomWord = await WordsGenerator.getNextWord(
|
||||
TestWords.words.length,
|
||||
bound,
|
||||
TestWords.words.get(TestWords.words.length - 1),
|
||||
TestWords.words.get(TestWords.words.length - 2)
|
||||
);
|
||||
|
||||
const randomWord = await WordsGenerator.getNextWord(
|
||||
wordset,
|
||||
TestWords.words.length,
|
||||
language,
|
||||
bound,
|
||||
TestWords.words.get(TestWords.words.length - 1),
|
||||
TestWords.words.get(TestWords.words.length - 2)
|
||||
);
|
||||
|
||||
TestWords.words.push(randomWord.word, randomWord.sectionIndex);
|
||||
TestUI.addWord(randomWord.word);
|
||||
TestWords.words.push(randomWord.word, randomWord.sectionIndex);
|
||||
TestUI.addWord(randomWord.word);
|
||||
} catch (e) {
|
||||
TimerEvent.dispatch("fail", "word generation error");
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(
|
||||
e,
|
||||
"Error while getting next word. Please try again later"
|
||||
),
|
||||
-1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type RetrySaving = {
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ function applyLazyModeToWord(
|
|||
return word;
|
||||
}
|
||||
|
||||
export function getQuoteOrCustomModeWordOrder(): MonkeyTypes.FunboxWordOrder {
|
||||
export function getWordOrder(): MonkeyTypes.FunboxWordOrder {
|
||||
const wordOrder =
|
||||
FunboxList.get(Config.funbox)
|
||||
.find((f) => f.properties?.find((fp) => fp.startsWith("wordOrder")))
|
||||
|
|
@ -397,6 +397,9 @@ export function getWordsLimit(): number {
|
|||
if (Config.mode === "words") {
|
||||
limit = Config.words;
|
||||
}
|
||||
if (Config.mode === "quote") {
|
||||
limit = currentQuote.length;
|
||||
}
|
||||
}
|
||||
|
||||
//infinite words
|
||||
|
|
@ -428,6 +431,10 @@ export function getWordsLimit(): number {
|
|||
limit = Config.words;
|
||||
}
|
||||
|
||||
if (Config.mode === "quote" && currentQuote.length < limit) {
|
||||
limit = currentQuote.length;
|
||||
}
|
||||
|
||||
if (
|
||||
Config.mode === "custom" &&
|
||||
CustomText.getLimitMode() === "word" &&
|
||||
|
|
@ -448,132 +455,24 @@ export class WordGenError extends Error {
|
|||
|
||||
let currentQuote: string[] = [];
|
||||
|
||||
let isCurrentlyUsingFunboxSection = false;
|
||||
|
||||
export async function generateWords(
|
||||
language: MonkeyTypes.LanguageObject
|
||||
): Promise<{
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
}> {
|
||||
if (!TestState.isRepeated) {
|
||||
previousGetNextWordReturns = [];
|
||||
}
|
||||
currentQuote = [];
|
||||
currentSection = [];
|
||||
sectionIndex = 0;
|
||||
sectionHistory = [];
|
||||
const ret: {
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
} = {
|
||||
words: [],
|
||||
sectionIndexes: [],
|
||||
};
|
||||
|
||||
const sectionFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.pullSection
|
||||
);
|
||||
isCurrentlyUsingFunboxSection =
|
||||
sectionFunbox?.functions?.pullSection !== undefined;
|
||||
|
||||
const limit = getWordsLimit();
|
||||
console.debug("Words limit", limit);
|
||||
|
||||
const wordOrder = getQuoteOrCustomModeWordOrder();
|
||||
console.debug("Word order", wordOrder);
|
||||
|
||||
let wordList = language.words;
|
||||
if (Config.mode === "custom") {
|
||||
if (wordOrder === "reverse") {
|
||||
wordList = CustomText.getText().reverse();
|
||||
} else {
|
||||
wordList = CustomText.getText();
|
||||
}
|
||||
}
|
||||
const wordset = await Wordset.withWords(wordList);
|
||||
|
||||
if (Config.mode === "quote") {
|
||||
const quoteWords = await generateQuoteWords(language, wordset, limit);
|
||||
if (wordOrder === "reverse") {
|
||||
quoteWords.words.reverse();
|
||||
quoteWords.sectionIndexes.reverse();
|
||||
}
|
||||
return quoteWords;
|
||||
}
|
||||
|
||||
if (
|
||||
Config.mode === "time" ||
|
||||
Config.mode === "words" ||
|
||||
Config.mode === "custom"
|
||||
) {
|
||||
let stop = false;
|
||||
let i = 0;
|
||||
while (!stop) {
|
||||
const nextWord = await getNextWord(
|
||||
wordset,
|
||||
i,
|
||||
language,
|
||||
limit,
|
||||
Arrays.nthElementFromArray(ret.words, -1) ?? "",
|
||||
Arrays.nthElementFromArray(ret.words, -2) ?? ""
|
||||
);
|
||||
ret.words.push(nextWord.word);
|
||||
ret.sectionIndexes.push(nextWord.sectionIndex);
|
||||
|
||||
const randomSectionStop =
|
||||
CustomText.getLimitMode() === "section" &&
|
||||
CustomText.getLimitValue() !== 0 &&
|
||||
sectionIndex >= CustomText.getLimitValue();
|
||||
|
||||
const customModeStop =
|
||||
Config.mode === "custom" &&
|
||||
currentSection.length === 0 &&
|
||||
randomSectionStop;
|
||||
|
||||
if (customModeStop || ret.words.length >= limit) {
|
||||
stop = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
sectionHistory = []; //free up a bit of memory? is that even a thing?
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function generateQuoteWords(
|
||||
async function getQuoteWordList(
|
||||
language: MonkeyTypes.LanguageObject,
|
||||
wordset: Wordset.Wordset,
|
||||
limit: number
|
||||
): Promise<{
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
}> {
|
||||
const ret: {
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
} = {
|
||||
words: [],
|
||||
sectionIndexes: [],
|
||||
};
|
||||
wordOrder?: MonkeyTypes.FunboxWordOrder
|
||||
): Promise<string[]> {
|
||||
if (TestState.isRepeated) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < Math.min(limit, previousGetNextWordReturns.length);
|
||||
i++
|
||||
) {
|
||||
const repeated = previousGetNextWordReturns[i];
|
||||
|
||||
if (repeated === undefined) {
|
||||
throw new WordGenError("Repeated word is undefined");
|
||||
}
|
||||
|
||||
ret.words.push(repeated.word);
|
||||
ret.sectionIndexes.push(repeated.sectionIndex);
|
||||
if (currentWordset === null) {
|
||||
throw new WordGenError("Current wordset is null");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
currentQuote = currentWordset.words;
|
||||
|
||||
// need to re-reverse the words if the test is repeated
|
||||
// because it will be reversed again in the generateWords function
|
||||
if (wordOrder === "reverse") {
|
||||
return currentWordset.words.reverse();
|
||||
} else {
|
||||
return currentWordset.words;
|
||||
}
|
||||
}
|
||||
const languageToGet = language.name.startsWith("swiss_german")
|
||||
? "german"
|
||||
: language.name;
|
||||
|
|
@ -650,18 +549,85 @@ async function generateQuoteWords(
|
|||
|
||||
currentQuote = TestWords.randomQuote.textSplit;
|
||||
|
||||
for (let i = 0; i < Math.min(limit, currentQuote.length); i++) {
|
||||
return currentQuote;
|
||||
}
|
||||
|
||||
let currentWordset: Wordset.Wordset | null = null;
|
||||
let currentLanguage: MonkeyTypes.LanguageObject | null = null;
|
||||
let isCurrentlyUsingFunboxSection = false;
|
||||
|
||||
export async function generateWords(
|
||||
language: MonkeyTypes.LanguageObject
|
||||
): Promise<{
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
}> {
|
||||
if (!TestState.isRepeated) {
|
||||
previousGetNextWordReturns = [];
|
||||
}
|
||||
currentQuote = [];
|
||||
currentSection = [];
|
||||
sectionIndex = 0;
|
||||
sectionHistory = [];
|
||||
currentLanguage = language;
|
||||
const ret: {
|
||||
words: string[];
|
||||
sectionIndexes: number[];
|
||||
} = {
|
||||
words: [],
|
||||
sectionIndexes: [],
|
||||
};
|
||||
|
||||
const sectionFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.pullSection
|
||||
);
|
||||
isCurrentlyUsingFunboxSection =
|
||||
sectionFunbox?.functions?.pullSection !== undefined;
|
||||
|
||||
const wordOrder = getWordOrder();
|
||||
console.debug("Word order", wordOrder);
|
||||
|
||||
let wordList = language.words;
|
||||
if (Config.mode === "custom") {
|
||||
wordList = CustomText.getText();
|
||||
} else if (Config.mode === "quote") {
|
||||
wordList = await getQuoteWordList(language, wordOrder);
|
||||
}
|
||||
|
||||
const limit = getWordsLimit();
|
||||
console.debug("Words limit", limit);
|
||||
|
||||
if (wordOrder === "reverse") {
|
||||
wordList = wordList.reverse();
|
||||
}
|
||||
|
||||
currentWordset = await Wordset.withWords(wordList);
|
||||
|
||||
let stop = false;
|
||||
let i = 0;
|
||||
while (!stop) {
|
||||
const nextWord = await getNextWord(
|
||||
wordset,
|
||||
i,
|
||||
language,
|
||||
limit,
|
||||
Arrays.nthElementFromArray(ret.words, -1) ?? "",
|
||||
Arrays.nthElementFromArray(ret.words, -2) ?? ""
|
||||
);
|
||||
ret.words.push(nextWord.word);
|
||||
ret.sectionIndexes.push(i);
|
||||
ret.sectionIndexes.push(nextWord.sectionIndex);
|
||||
|
||||
if (Config.mode === "custom" && CustomText.getPipeDelimiter()) {
|
||||
const sectionFinishedAndOverLimit =
|
||||
currentSection.length === 0 &&
|
||||
sectionIndex >= CustomText.getLimitValue();
|
||||
if (sectionFinishedAndOverLimit) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (ret.words.length >= limit) {
|
||||
stop = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
sectionHistory = []; //free up a bit of memory? is that even a thing?
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -678,24 +644,33 @@ type GetNextWordReturn = {
|
|||
|
||||
//generate next word
|
||||
export async function getNextWord(
|
||||
wordset: Wordset.Wordset,
|
||||
wordIndex: number,
|
||||
language: MonkeyTypes.LanguageObject,
|
||||
wordsBound: number,
|
||||
previousWord: string,
|
||||
previousWord2: string
|
||||
): Promise<GetNextWordReturn> {
|
||||
console.debug("Getting next word", {
|
||||
isRepeated: TestState.isRepeated,
|
||||
wordset,
|
||||
currentWordset,
|
||||
wordIndex,
|
||||
language,
|
||||
language: currentLanguage,
|
||||
wordsBound,
|
||||
previousWord,
|
||||
previousWord2,
|
||||
});
|
||||
|
||||
if (TestState.isRepeated) {
|
||||
if (currentWordset === null) {
|
||||
throw new WordGenError("Current wordset is null");
|
||||
}
|
||||
|
||||
if (currentLanguage === null) {
|
||||
throw new WordGenError("Current language is null");
|
||||
}
|
||||
|
||||
//because quote test can be repeated in the middle of a test
|
||||
//we cant rely on data inside previousGetNextWordReturns
|
||||
//because it might not include the full quote
|
||||
if (TestState.isRepeated && Config.mode !== "quote") {
|
||||
const repeated = previousGetNextWordReturns[wordIndex];
|
||||
|
||||
if (repeated === undefined) {
|
||||
|
|
@ -707,7 +682,7 @@ export async function getNextWord(
|
|||
}
|
||||
|
||||
const funboxFrequency = getFunboxWordsFrequency() ?? "normal";
|
||||
let randomWord = wordset.randomWord(funboxFrequency);
|
||||
let randomWord = currentWordset.randomWord(funboxFrequency);
|
||||
const previousWordRaw = previousWord.replace(/[.?!":\-,]/g, "").toLowerCase();
|
||||
const previousWord2Raw = previousWord2
|
||||
.replace(/[.?!":\-,']/g, "")
|
||||
|
|
@ -717,23 +692,22 @@ export async function getNextWord(
|
|||
const funboxSection = await getFunboxSection();
|
||||
|
||||
if (Config.mode === "quote") {
|
||||
randomWord = currentQuote[wordIndex] as string;
|
||||
randomWord = currentWordset.nextWord();
|
||||
} else if (Config.mode === "custom" && CustomText.getMode() === "repeat") {
|
||||
const customText = CustomText.getText();
|
||||
randomWord = customText[sectionIndex % customText.length] as string;
|
||||
randomWord = currentWordset.nextWord();
|
||||
} else if (
|
||||
Config.mode === "custom" &&
|
||||
CustomText.getMode() === "random" &&
|
||||
(wordset.length < 4 || PractiseWords.before.mode !== null)
|
||||
(currentWordset.length < 4 || PractiseWords.before.mode !== null)
|
||||
) {
|
||||
randomWord = wordset.randomWord(funboxFrequency);
|
||||
randomWord = currentWordset.randomWord(funboxFrequency);
|
||||
} else if (Config.mode === "custom" && CustomText.getMode() === "shuffle") {
|
||||
randomWord = wordset.shuffledWord();
|
||||
randomWord = currentWordset.shuffledWord();
|
||||
} else if (
|
||||
Config.mode === "custom" &&
|
||||
CustomText.getLimitMode() === "section"
|
||||
) {
|
||||
randomWord = wordset.randomWord(funboxFrequency);
|
||||
randomWord = currentWordset.randomWord(funboxFrequency);
|
||||
|
||||
const previousSection = Arrays.nthElementFromArray(sectionHistory, -1);
|
||||
const previousSection2 = Arrays.nthElementFromArray(sectionHistory, -2);
|
||||
|
|
@ -744,14 +718,17 @@ export async function getNextWord(
|
|||
(previousSection === randomWord || previousSection2 === randomWord)
|
||||
) {
|
||||
regenerationCount++;
|
||||
randomWord = wordset.randomWord(funboxFrequency);
|
||||
randomWord = currentWordset.randomWord(funboxFrequency);
|
||||
}
|
||||
} else if (isCurrentlyUsingFunboxSection) {
|
||||
randomWord = funboxSection.join(" ");
|
||||
} else {
|
||||
let regenarationCount = 0; //infinite loop emergency stop button
|
||||
let firstAfterSplit = (randomWord.split(" ")[0] as string).toLowerCase();
|
||||
let firstAfterSplitLazy = applyLazyModeToWord(firstAfterSplit, language);
|
||||
let firstAfterSplitLazy = applyLazyModeToWord(
|
||||
firstAfterSplit,
|
||||
currentLanguage
|
||||
);
|
||||
while (
|
||||
regenarationCount < 100 &&
|
||||
(previousWordRaw === firstAfterSplitLazy ||
|
||||
|
|
@ -768,15 +745,18 @@ export async function getNextWord(
|
|||
/[0-9]/i.test(randomWord)))
|
||||
) {
|
||||
regenarationCount++;
|
||||
randomWord = wordset.randomWord(funboxFrequency);
|
||||
randomWord = currentWordset.randomWord(funboxFrequency);
|
||||
firstAfterSplit = randomWord.split(" ")[0] as string;
|
||||
firstAfterSplitLazy = applyLazyModeToWord(firstAfterSplit, language);
|
||||
firstAfterSplitLazy = applyLazyModeToWord(
|
||||
firstAfterSplit,
|
||||
currentLanguage
|
||||
);
|
||||
}
|
||||
}
|
||||
randomWord = randomWord.replace(/ +/g, " ");
|
||||
randomWord = randomWord.replace(/(^ )|( $)/g, "");
|
||||
|
||||
randomWord = getFunboxWord(randomWord, wordIndex, wordset);
|
||||
randomWord = getFunboxWord(randomWord, wordIndex, currentWordset);
|
||||
|
||||
currentSection = [...randomWord.split(" ")];
|
||||
sectionHistory.push(randomWord);
|
||||
|
|
@ -814,7 +794,7 @@ export async function getNextWord(
|
|||
|
||||
randomWord = randomWord.replace(/ +/gm, " ");
|
||||
randomWord = randomWord.replace(/(^ )|( $)/gm, "");
|
||||
randomWord = applyLazyModeToWord(randomWord, language);
|
||||
randomWord = applyLazyModeToWord(randomWord, currentLanguage);
|
||||
randomWord = await applyBritishEnglishToWord(randomWord, previousWordRaw);
|
||||
|
||||
if (Config.language.startsWith("swiss_german")) {
|
||||
|
|
@ -823,7 +803,7 @@ export async function getNextWord(
|
|||
|
||||
if (
|
||||
Config.punctuation &&
|
||||
!language.originalPunctuation &&
|
||||
!currentLanguage.originalPunctuation &&
|
||||
!isCurrentlyUsingFunboxSection
|
||||
) {
|
||||
randomWord = await punctuateWord(
|
||||
|
|
|
|||
|
|
@ -8,13 +8,19 @@ let currentWordset: MonkeyTypes.Wordset | null = null;
|
|||
export class Wordset implements MonkeyTypes.Wordset {
|
||||
words: string[];
|
||||
length: number;
|
||||
|
||||
orderedIndex: number;
|
||||
shuffledIndexes: number[];
|
||||
|
||||
constructor(words: string[]) {
|
||||
this.words = words;
|
||||
this.length = this.words.length;
|
||||
this.shuffledIndexes = [];
|
||||
this.orderedIndex = 0;
|
||||
}
|
||||
|
||||
resetIndexes(): void {
|
||||
this.orderedIndex = 0;
|
||||
this.shuffledIndexes = [];
|
||||
}
|
||||
|
||||
randomWord(mode: MonkeyTypes.FunboxWordsFrequency): string {
|
||||
|
|
@ -39,6 +45,13 @@ export class Wordset implements MonkeyTypes.Wordset {
|
|||
}
|
||||
shuffle(this.shuffledIndexes);
|
||||
}
|
||||
|
||||
nextWord(): string {
|
||||
if (this.orderedIndex >= this.length) {
|
||||
this.orderedIndex = 0;
|
||||
}
|
||||
return this.words[this.orderedIndex++] as string;
|
||||
}
|
||||
}
|
||||
|
||||
export async function withWords(words: string[]): Promise<MonkeyTypes.Wordset> {
|
||||
|
|
@ -51,5 +64,6 @@ export async function withWords(words: string[]): Promise<MonkeyTypes.Wordset> {
|
|||
if (currentWordset === null || words !== currentWordset.words) {
|
||||
currentWordset = new Wordset(words);
|
||||
}
|
||||
currentWordset.resetIndexes();
|
||||
return currentWordset;
|
||||
}
|
||||
|
|
|
|||
3
frontend/src/ts/types/types.d.ts
vendored
3
frontend/src/ts/types/types.d.ts
vendored
|
|
@ -111,11 +111,14 @@ declare namespace MonkeyTypes {
|
|||
class Wordset {
|
||||
words: string[];
|
||||
length: number;
|
||||
orderedIndex: number;
|
||||
shuffledIndexes: number[];
|
||||
constructor(words: string[]);
|
||||
resetIndexes(): void;
|
||||
randomWord(mode: MonkeyTypes.FunboxWordsFrequency): string;
|
||||
shuffledWord(): string;
|
||||
generateShuffledIndexes(): void;
|
||||
nextWord(): string;
|
||||
}
|
||||
|
||||
class Section {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue