class State {
constructor({ inclusions, exclusions }) {
this.inclusions = inclusions;
this.exclusions = exclusions;
}
static empty() {
return new State({ inclusions: [], exclusions: [] });
}
isEmpty() {
return !this.inclusions.length && !this.exclusions.length;
}
static fromGuessesAndResults({ guesses, results }) {
let s = State.empty();
_.zip(guesses, results).forEach(([guess, result]) => {
if (!guess || !result) {
return;
}
s = s.addGuessResult({ guess, result });
});
return s;
}
addGuessResult({ guess, result }) {
result = [...result];
let positionalInclusions = "";
let positionalExclusions = [];
const globalInclusions = [];
let globalExclusions = [];
for (let i = 0; i < guess.length; ++i) {
const letter = guess[i];
const color = result[i];
switch (color) {
case "🟩":
case "g":
positionalInclusions += letter;
break;
case "🟨":
case "y":
positionalInclusions += ".";
positionalExclusions.push([i, letter]);
globalInclusions.push(letter);
break;
case "⬛":
case "b":
positionalInclusions += ".";
positionalExclusions.push([i, letter]);
globalExclusions.push(letter);
break;
}
}
// global exclusions should never override inclusions
// strip out global exclusions that are marked as needed to be included
globalExclusions = globalExclusions.filter(
(exclusion) =>
!globalInclusions.includes(exclusion) &&
!positionalInclusions.includes(exclusion)
);
const newInclusions = [...this.inclusions];
const newExclusions = [...this.exclusions];
// positional inclusions
newInclusions.unshift(new RegExp(`^${positionalInclusions}$`));
// PERFORMANCE: We're using unshift here because new Regexes will be used first
// by the _.every and the _.some that we use later on.
// positional exclusions
for (const [i, letter] of positionalExclusions) {
// split on every character
const base = ".....".split("");
base[i] = letter;
newExclusions.unshift(new RegExp(`^${_.sum(base)}$`));
}
// global inclusions
if (globalInclusions.length !== 0) {
// https://stackoverflow.com/a/15341118
// match at least the characters anywhere in the string
newInclusions.unshift(
new RegExp(
`(.*[${_.sum(globalInclusions)}]){${globalInclusions.length}}`
)
);
}
// global exclusions
if (globalExclusions.length !== 0) {
newExclusions.unshift(new RegExp(`[${_.sum(globalExclusions)}]`));
}
return new State({ inclusions: newInclusions, exclusions: newExclusions });
}
getPossibleQuarries(oldQuarryPool) {
// PERFORMANCE: don't traverse the dictionary every time when we can use the old candidate pool
const pool = oldQuarryPool ? oldQuarryPool : dictionary;
return pool.filter(
(word) =>
// PERFORMANCE: _.some will fail fast, _.every will not
// Do not reorder
!_.some(this.exclusions, (regex) => regex.test(word)) &&
_.every(this.inclusions, (regex) => regex.test(word))
);
}
}