1
0
anagram-games/www/bagels/bagels.js

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));
}
}
}
}