216 lines
6.8 KiB
JavaScript
216 lines
6.8 KiB
JavaScript
'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 = settings.num_random_letters;
|
|
let availableLetters;
|
|
let availableLettersValid = false;
|
|
do {
|
|
availableLetters = new Set(word.split(''));
|
|
for (let i = 0; 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));
|
|
}
|
|
}
|
|
}
|
|
}
|