'use strict'; const LetterGuessResult = Object.freeze({ MATCH: Symbol("match"), UNUSED: Symbol("unused"), ELSEWHERE: Symbol("elsewhere") }); function getLetterGuessResultClass(result) { return result.toString().slice(7, -1); } class GuessResult { constructor(guess, correct, letters) { this.guess = guess; this.correct = correct; this.letters = letters; } } class LetterInfo { constructor(letter, wordLength) { this.letter = letter; this.min = 0; this.max = wordLength; this.knownMatches = new Set(); this.knownElsewheres = new Set(); this.knownNotHere = new Set(); } } class Bagels { constructor(word, availableLetters) { this.word = word; this.numLetters = word.length; this.availableLetters = word.split(''); for (var i = 0; i < availableLetters.length; i++) { this.availableLetters = this.availableLetters .concat(availableLetters[i].split('')); } this.availableLetters = [...new Set(this.availableLetters)].sort(); this.guesses = new Array(); this.knownInformation = {}; this.availableLetters.forEach(letter => { this.knownInformation[letter] = new LetterInfo(letter, word.length); }); this.knownMatches = new Set(); } static generateRandom(settings) { if (settings === null) return null; let maxIters = 100; let iters = 0; let regExp = new RegExp('^[a-z]{' + settings.min_word_length + ',' + settings.max_word_length + '}$'); let word; do { word = topWords[1000+getRandomInt(30000)].word; if (iters++ > maxIters) return null; } while (!regExp.test(word) || !isValidWord(word)); let numLetters = Math.min(26, word.length + settings.num_random_letters); let availableLetters; let availableLettersValid = false; do { availableLetters = new Set(word.split('')); for (let i = availableLetters.size; i < numLetters; i++) { let newLetter; do { newLetter = getRandomLetter(); } while (availableLetters.has(newLetter)); availableLetters.add(newLetter); } availableLetters = [...availableLetters]; if (settings.min_matches <= 1 && settings.max_matches >= topWords.length) { availableLettersValid = true; } else { let preciseRegExp = new RegExp('^[' + availableLetters.join('') + ']{' + word.length + '}$'); let matchingTopWords = topWords.filter(w => preciseRegExp.test(w.word)).length; availableLettersValid = matchingTopWords >= settings.min_matches && matchingTopWords <= settings.max_matches; } if (!availableLettersValid && iters++ > maxIters) return null; } while (!availableLettersValid); return new Bagels(word, availableLetters); } static fromFragment(fragment) { try { if (!fragment.includes(',')) { return null; } var parts = fragment.split(','); var word = parts[0]; var availableLetters = parts[1]; // TODO more checks? if (!isValidWord(word)) return null; if (word.length < 2) return null; return new Bagels(word, availableLetters); } catch(e) { return null; } } get fragment() { return this.word + ',' + this.availableLetters.sort().join(''); } isValidGuess(guess) { var availableLetters = this.availableLetters; return guess.length == this.numLetters && guess.split('') .every(letter => availableLetters.includes(letter)) && isValidWord(guess); } computeGuessResult(guess) { if (!this.isValidGuess(guess)) return null; var guessArr = guess.split(''); // Don't compute ELSEWHERE results yet. var unmatchedLetters = this.word.split(''); // From https://blog.mariusschulz.com/2016/07/16/removing-elements-from-javascript-arrays function remove(array, element) { const index = array.indexOf(element); if (index !== -1) { array.splice(index, 1); } } var word = this.word; var result = guessArr.map(function(guessedLetter, idx) { if (word[idx] == guessedLetter) { remove(unmatchedLetters, guessedLetter); return LetterGuessResult.MATCH; } return LetterGuessResult.UNUSED; }); if (guess == word) { return new GuessResult(guess, true, result); } for (var i = 0; i < this.numLetters; i++) { if (result[i] == LetterGuessResult.UNUSED) { if (unmatchedLetters.includes(guessArr[i])) { remove(unmatchedLetters, guessArr[i]); result[i] = LetterGuessResult.ELSEWHERE; } } } return new GuessResult(guess, false, result); } makeGuess(guess) { var result = this.computeGuessResult(guess); if (result !== null) { this.guesses.push(result); this.updateKnownInformation(result); } return result; } updateKnownInformation(guess) { let minTotal = 0; for (var letter in this.knownInformation) { let info = this.knownInformation[letter]; let min = 0; let timesGuessed = 0; let anyUnused = false; for (let i=0; i < this.numLetters; i++) { if (guess.guess[i] != letter) continue; timesGuessed++; let res = guess.letters[i]; if (res == LetterGuessResult.MATCH) { min++; info.knownMatches.add(i); this.knownMatches.add(i); } else if (res == LetterGuessResult.ELSEWHERE) { min++; info.knownElsewheres.add(i); } else if (res == LetterGuessResult.UNUSED) { info.knownNotHere.add(i); anyUnused = true; } } if (anyUnused) { info.max = min; } info.min = Math.max(info.min, min); minTotal += info.min; } if (minTotal > 0) { for (let letter in this.knownInformation) { let info = this.knownInformation[letter]; info.max = Math.min(info.max, this.numLetters - (minTotal - info.min)); } } } }