// class constants
const WORDLE = 'Wordle';
const WOODLE = 'Woodle';
const PEAKS = 'W-Peaks';
const ANTI = 'Antiwordle';
const XORDLE = 'Xordle';
const THIRDLE = 'Thirdle';
const FIBBLE = 'Fibble';
const HARDLE = 'Hardle';
const DORDLE = 'Dordle';
const QUORDLE = 'Quordle';
const OCTORDLE = 'Octordle';
const WARMLE = 'Warmle';
class Bot {
constructor(type) {
this.type = type;
}
isFor(type) {
return this.type == type;
}
hasHardMode() {
// return this.type == WORDLE || this.type == ANTI;
return true;
}
hasMax() {
return this.type != ANTI;
}
guessesAllowed() {
if (this.type == ANTI) return INFINITY;
return parseInt(document.getElementById('max-guesses').value);
}
setChangeEvents(row) {
if (this.type == WOODLE) {
woodleDropdown(row);
} else {
tilesChangeColor(row);
}
}
getDifference(word1, word2) {
if (this.type == WOODLE) {
return differencesWithoutPositions(word1, word2);
}
if (this.type == PEAKS) {
return getAlphabeticDifferences(word1, word2);
}
if (typeof word2 == 'object') {
return getDoubleDifference(word1, word2);
}
if (this.type == WARMLE) {
return getWarmleDifferences(word1, word2);
}
return differencesWithPositions(word1, word2);
}
getRowColor(row_number) {
if (this.type == WOODLE) {
return rowDifferencesWithoutPositions(row_number);
} else if (this.getCount() > 1) {
return rowDifferencesWithPairs(row_number);
} else {
return rowDifferencesWithPositions(row_number);
}
}
setRowColor(difference, row) {
if (this.type == DORDLE && difference.length == word_length*2) {
return setDordleDifferences(difference, row);
} else if (this.type == WOODLE) {
return setRowDifferencesWithoutPositions(difference, row);
} else {
return setRowDifferencesWithPositions(difference, row);
}
}
getBestLetters(list) {
if (this.type == PEAKS) {
return lettersClosestToCenter(list);
}
if (this.type == WARMLE) {
return bestWarmleLetters(list);
}
return mostCommonLetters(list);
}
reducesListBest(answers, guesses, future_guess) {
if (this.type == ANTI) {
return reducesListLeast(answers, guesses);
} else {
return reducesListMost(answers, guesses, future_guess);
}
}
getAllDifferences(difference, guess, reduced_filter) {
if (reduced_filter) {
return getAntiWordleDiffs(difference, guess);
}
if (this.type == XORDLE) {
return getXordleDiffs(difference, 0, [difference]);
}
if (this.type == FIBBLE) {
return getFibbleDiffs(difference);
}
if (this.type == HARDLE) {
return getHardleDiffs(difference);
}
if (this.getCount() > 1) {
return difference;
}
return [difference];
}
isBetter(a, b) {
if (bot.isFor(ANTI)) {
return isHigher(a, b);
} else {
return isLower(a, b);
}
}
getCount() {
if (bot.isFor(DORDLE)) return 2;
if (bot.isFor(QUORDLE)) return 4;
if (bot.isFor(OCTORDLE)) return 8;
else return 1;
}
getAllPossibleAnswersFrom(list) {
list = filterList(list, 0, 0, bot.getCount() > 1);
if (bot.isFor(XORDLE)) {
list = xordleFilter(uniqueWordsFrom(list));
}
return list;
}
getAnswerListLength(answers) {
if (bot.getCount() > 1) {
return lengthOfAllLists(answers);
}
return answers.length;
}
isLikely(answer) {
if (this.type == XORDLE && typeof answer == 'object') {
return this.isLikely(answer.word1) && this.isLikely(answer.word2);
}
return common.includes(answer);
}
}
function isHigher(a, b) {
return a > b;
}
function isLower(a, b) {
return a < b;
}
// Wordle Specific Functions
function tilesChangeColor(row) {
let tiles = row.getElementsByClassName('tile');
Array.from(tiles).forEach(function(t) {
t.addEventListener('click', function() {
changeTileColor(t);
});
});
}
function changeTileColor(tile) {
let old_color = getTileColor(tile);
let new_color = nextColor(old_color);
tile.classList.replace(old_color, new_color);
}
function nextColor(color) {
return color == CORRECT ? WRONG_SPOT : (color == WRONG_SPOT ? INCORRECT : CORRECT)
}
function getTileColor(tile) {
return Array.from(tile.classList).filter(a => a == CORRECT || a == INCORRECT || a == WRONG_SPOT);
}
function differencesWithPositions(word1, word2) {
if (pairings[word1]) {
if (pairings[word1][word2]) return pairings[word1][word2];
} else pairings[word1] = [];
let temp1 = word1;
let temp2 = word2;
let diff = EMPTY.repeat(word_length);
let pos = 0;
for (let j = 0; j < temp1.length; j++) {
let word1_c = temp1.charAt(j);
let word2_c = temp2.charAt(j);
if (word1_c == word2_c) {
temp1 = temp1.slice(0, j) + temp1.slice(j+1);
temp2 = temp2.slice(0, j) + temp2.slice(j+1);
diff = replaceAt(diff, CORRECT, pos);
j--;
}
pos++;
}
pos = 0;
for (let j = 0; j < temp1.length; j++) {
if (diff.charAt(pos) != 'X') {
j--;
pos++;
continue;
}
let word1_c = temp1.charAt(j);
if (temp2.includes(word1_c)) {
diff = replaceAt(diff, WRONG_SPOT, pos);
let index = temp2.indexOf(word1_c);
temp2 = temp2.slice(0, index) + temp2.slice(index+1);
} else {
diff = replaceAt(diff, INCORRECT, pos);
}
pos++;
}
pairings[word1][word2] = diff;
return diff;
}
function getDoubleDifference(guess, answers) {
let diff1 = bot.getDifference(guess, answers.word1);
let diff2 = bot.getDifference(guess, answers.word2);
let new_diff = "";
for (let i = 0; i < word_length; i++) {
if (diff1.charAt(i) != INCORRECT) {
new_diff += diff1.charAt(i);
} else if (diff2.charAt(i) != INCORRECT) {
new_diff += diff2.charAt(i);
} else {
new_diff += INCORRECT;
}
}
return new_diff;
}
function dordleDifference(guess, answers) {
return [differencesWithPositions(guess, answers.word1),
differencesWithPositions(guess, answers.word2)];
}
function rowDifferencesWithPositions(row_number) {
let row = document.getElementsByClassName("row")[row_number];
let coloring = "";
for (let i = 0; i < word_length; i++) {
coloring += getTileColor(row.getElementsByClassName("tile")[i]);
}
return coloring;
}
function rowDifferencesWithPairs(row_number) {
let colors = [];
let grids = document.getElementsByClassName('grid');
for (let i = 0; i < grids.length; i++) {
let row = grids[i].getElementsByClassName('row')[row_number];
let coloring = "";
for (let j = 0; j < word_length; j++) {
coloring += getTileColor(row.getElementsByClassName("tile")[j]);
}
colors.push(coloring);
}
return colors;
}
function getAlphabeticDifferences(word1, word2) {
let diff = "";
for (let i = 0; i < word_length; i++) {
let a = word1.charAt(i), b = word2.charAt(i);
if (a == b) {
diff += CORRECT;
} else if (a > b) {
diff += INCORRECT;
} else if (a < b) {
diff += WRONG_SPOT;
}
}
return diff;
}
function getWarmleDifferences(word1, word2) {
let diff = "";
let distance = document.getElementsByClassName('warmle-selector')[0].value;
for (let i = 0; i < word_length; i++) {
let a = word1.charAt(i).charCodeAt(0), b = word2.charAt(i).charCodeAt(0);
if (a == b) {
diff += CORRECT;
} else if (Math.abs(a-b) <= distance ) {
diff += WRONG_SPOT;
} else {
diff += INCORRECT;
}
}
return diff;
}
function setDordleDifferences(colorings, row) {
setRowDifferencesWithPositions(colorings[0], row);
setRowDifferencesWithPositions(colorings[1], row.nextSibling);
}
function setRowDifferencesWithPositions(coloring, row) {
let tiles = row.getElementsByClassName('tile');
for (let i = 0; i < word_length; i++) {
tiles[i].classList.replace(INCORRECT, coloring[i]);
}
}
// Woodle Specific Functions
function woodleDropdown(row) {
let selector = row.getElementsByClassName('woodle-count');
for (let i = 0; i < selector.length; i++) {
if (selector[i].getElementsByTagName('option').length) {
continue;
}
let options = "";
for (let j = 0; j <= word_length; j++) {
options += ""
}
selector[i].innerHTML = options;
}
}
function rowDifferencesWithoutPositions(row) {
let num_correct = document.getElementsByClassName('woodle-count ' + CORRECT)[row].value;
let num_wrong_spots = document.getElementsByClassName('woodle-count ' + WRONG_SPOT)[row].value;
let num_wrong = word_length - num_correct - num_wrong_spots;
return CORRECT.repeat(num_correct) + WRONG_SPOT.repeat(num_wrong_spots) + INCORRECT.repeat(num_wrong);
}
function differencesWithoutPositions(word1, word2) {
let temp1 = word1;
let temp2 = word2;
if (pairings[word1]) {
if (pairings[word1][word2]) return pairings[word1][word2];
} else pairings[word1] = [];
let correct = "";
let wrong_spots = "";
let num_wrong = word_length;
for (let j = 0; j < temp1.length; j++) {
if (num_wrong == 0) break;
let word1_c = temp1.charAt(j);
let word2_c = temp2.charAt(j);
if (word1_c == word2_c) {
correct += CORRECT;
num_wrong--;
temp1 = temp1.slice(0, j) + temp1.slice(j+1);
temp2 = temp2.slice(0, j) + temp2.slice(j+1);
j--;
}
}
for (let j = 0; j < temp1.length && num_wrong > 0; j++) {
let word1_c = temp1.charAt(j);
if (temp2.includes(word1_c)) {
wrong_spots += WRONG_SPOT;
num_wrong--;
let index = temp2.indexOf(word1_c);
temp2 = temp2.slice(0, index) + temp2.slice(index+1);
}
}
let diff = correct + wrong_spots + INCORRECT.repeat(num_wrong);
pairings[word1][word2] = diff;
return diff;
}
function setRowDifferencesWithoutPositions(coloring, row) {
let selectors = row.getElementsByClassName('tracker')[0];
let num_correct = selectors.getElementsByClassName('woodle-count ' + CORRECT)[0];
let num_wrong_spots = selectors.getElementsByClassName('woodle-count ' + WRONG_SPOT)[0];
let correct = count(coloring, CORRECT);
let wrong_spots = count(coloring, WRONG_SPOT);
num_correct.innerHTML = "";
num_wrong_spots.innerHTML = "";
}
// calculates which letters appear most often throughout the remaining answers
// used to rough sort the list if the entire list is too large to check
// info is also prited underneath 'Most Common Letters' section
function mostCommonLetters(list) {
if (!list.length) return [];
let letters = makeAlphabetArray(parseInt(word_length)+1);
let checked;
for (let i = 0; i < list.length; i++) {
checked = [];
for (let j = 0; j < word_length; j++) {
c = list[i].charAt(j);
letters[c][j]++;
if (checked[c] != true) letters[c][word_length]++; // only counts letters once per word
checked[c] = true;
}
}
return letters;
}
function lettersClosestToCenter() {
let letters = [];
for (let c = 65; c <= 90; c++) {
let char = String.fromCharCode(c);
let val = 1/Math.abs(c - (90+65)/2);
letters[char] = [];
for (let i = 0; i < word_length+1; i++) {
letters[char].push(val);
}
}
return letters;
}
function bestWarmleLetters(list) {
let letters = makeAlphabetArray(parseInt(word_length)+1);
list.forEach(function(word) {
for (let i = 0; i < word_length; i++) {
let c = word.charAt(i);
letters[c][i]++;
letters[c][word.length]++;
}
});
let new_letters = makeAlphabetArray(parseInt(word_length)+1);
for (let i = 65; i <= 90; i++) {
let pos = intToChar(i);
for (let j = 0; j < word_length; j++) {
new_letters[pos][j] = letters[pos][j];
let distance = document.getElementsByClassName('warmle-selector')[0].value;
for (let k = 1; k <= distance; k++) {
let c = charToInt(pos)+k;
if (c <= 90) {
c = intToChar(c);
new_letters[pos][j] += letters[c][j];
}
c = charToInt(pos)-k;
if (c >= 65) {
c = intToChar(c);
new_letters[pos][j] += letters[c][j];
}
}
}
new_letters[pos][word_length] = letters[pos][word_length];
}
return new_letters;
}
function makeAlphabetArray(size) {
let letters = [];
for (let i = 65; i <= 90; i++) {
let c = String.fromCharCode(i);
letters[c] = [];
for (let i = 0; i < size; i++) {
letters[c].push(0);
}
}
return letters;
}
function reducesListMost(answers, guesses, future_guess) {
let best_words = [];
let min = answers.length;
for (let i = 0; i < guesses.length; i++) {
let data;
if (bot.getCount() > 1 && typeof answers[0] == 'object') {
let data_per_list = [];
for (let j = 0; j < answers.length; j++) {
min = answers[j].length;
data_per_list.push(calculateAverageBucketSize(guesses[i], answers[j], min, future_guess));
}
data = averageBucketSizeFromAllLists(data_per_list);
} else {
data = calculateAverageBucketSize(guesses[i], answers, min, future_guess);
}
if (!data) continue;
min = Math.min(min, data.adjusted);
best_words.push({word: guesses[i], average: data.adjusted, differences: data.differences, wrong: 0});
if (data.weighted < 1 && future_guess) break;
if (min == 0 && best_words.length >= answers.length && future_guess) break;
}
best_words = sortListByAverage(best_words);
return best_words;
}
function averageBucketSizeFromAllLists(data) {
let differences = {};
let average = 0;
for (let i = 0; i < data.length; i++) {
average += data[i].adjusted;
let keys = [...new Set([...Object.keys(differences),...Object.keys(data[i].differences)])]
let op = {};
differences = keys.forEach(key=>{
op[key] = {
...differences[key],
...data[i].differences[key]
}
});
differences = op;
}
average /= bot.getCount();
return {adjusted: average, differences: differences};
}
function reducesListLeast(answers, guesses) {
let best_words = [];
for (let i = 0; i < guesses.length; i++) {
let data = calculateAverageBucketSize(guesses[i], answers, 0, 0);
best_words.push({word: guesses[i], average: data.weighted, differences: data.differences});
}
best_words = sortListByAverage(best_words);
return best_words;
}
function calculateAverageBucketSize(guess, answers, min, future_guess) {
let differences = [];
let list_size = answers.length;
let weighted = adjusted = 0;
let threes = 1;
for (let i = 0; i < list_size; i++) {
let diff = bot.getDifference(guess, answers[i]);
if (differences[diff] == null) {
differences[diff] = [];
}
if (diff != CORRECT.repeat(word_length) || bot.isFor(XORDLE)) {
differences[diff].push(answers[i]);
}
let freq = differences[diff].length;
if (freq > 0) {
weighted += (freq/list_size)*freq - ((freq-1)/list_size)*(freq-1);
if (freq > 1) {
threes -= 1/list_size;
}
}
adjusted = (1-threes)*weighted;
if (!bot.isFor(ANTI) && (adjusted >= min && future_guess || adjusted > min*SIZE_FACTOR)) {
return;
}
}
let bucket_data = {word: guess, weighted: weighted, threes: threes, adjusted: adjusted, differences: differences};
return bucket_data;
}
function getXordleDiffs(difference, index, diff_list) {
if (index == difference.length) return [...new Set(diff_list)];
if (difference.charAt(index) != INCORRECT) {
let alt = replaceAt(difference, INCORRECT, index);
diff_list.push(alt);
getXordleDiffs(alt, index+1, diff_list);
}
return getXordleDiffs(difference, index+1, diff_list);
}
function getFibbleDiffs(diff) {
let differences = [];
for (let i = 0; i < diff.length; i++) {
if (diff.charAt(i) != INCORRECT) {
let new_diff = replaceAt(diff, INCORRECT, i);
differences.push(new_diff);
}
if (diff.charAt(i) != CORRECT) {
let new_diff = replaceAt(diff, CORRECT, i);
differences.push(new_diff);
}
if (diff.charAt(i) != WRONG_SPOT) {
let new_diff = replaceAt(diff, WRONG_SPOT, i);
differences.push(new_diff);
}
}
return differences;
}
function getHardleDiffs(diff) {
let differences = [diff];
let new_diff = "";
if (diff == WRONG_SPOT.repeat(word_length)) {
return differences;
}
for (let i = 0; i < diff.length; i++) {
if (diff.charAt(i) == CORRECT) {
new_diff += WRONG_SPOT;
} else if (diff.charAt(i) == WRONG_SPOT) {
new_diff += CORRECT;
} else new_diff += INCORRECT;
}
differences.push(new_diff);
return differences;
}
function getAntiWordleDiffs(diff, guess) {
let wrong_letters = findWrongSpotLetters(diff, guess);
let differences = antiRecursion(guess, diff, wrong_letters, [], 0);
includesAllWrongSpots(differences, wrong_letters, guess);
return differences;
}
function includesAllWrongSpots(differences, wrong_letters, word) {
if (!wrong_letters.length) return differences;
outer:
for (let i = 0; i < differences.length; i++) {
let check_list = [];
for (let j = 0; j < word_length; j++) {
if (differences[i].charAt(j) != INCORRECT) {
let c = word.charAt(j);
if (!check_list.includes(c)) {
check_list.push(c);
if (check_list.length == wrong_letters.length) {
continue outer;
}
}
}
}
differences.splice(i, 1);
i--;
}
}
function antiRecursion(word, difference, wrong_letters, diff_list, i) {
diff_list.push(difference);
if (i == word_length) {
return [...new Set(diff_list)];
}
if (wrong_letters.includes(word.charAt(i)) && difference.charAt(i) != CORRECT) {
antiRecursion(word, replaceAt(difference, CORRECT, i), wrong_letters, diff_list, i+1);
if (difference.charAt(i) != INCORRECT) {
antiRecursion(word, replaceAt(difference, INCORRECT, i), wrong_letters, diff_list, i+1);
}
if (difference.charAt(i) != WRONG_SPOT) {
antiRecursion(word, replaceAt(difference, WRONG_SPOT, i), wrong_letters, diff_list, i+1);
}
}
return antiRecursion(word, difference, wrong_letters, diff_list, i+1);
}
function findWrongSpotLetters(diff, guess) {
// find index of every Y character in the differences
let wrong_spots = allInstancesOf(WRONG_SPOT, diff);
let correct = allInstancesOf(CORRECT, diff);
let indices = combineLists(wrong_spots, correct);
let c = [];
// indentify all letters marked as Y
for (let i = 0; i < indices.length; i++) {
c.push(guess.charAt(indices[i]));
}
c = [...new Set(c)];
return c;
}
function lengthOfAllLists(lists) {
if (lists.length && typeof lists[0] == 'string') {
return lists.length;
}
let new_list = [];
lists.forEach(function(a) {
new_list = combineLists(new_list, a);
});
new_list = uniqueWordsFrom(new_list);
return new_list.length;
}