wordulator 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LISCENSE +7 -0
- package/ReadMe.md +95 -0
- package/docs/Example Benchmark.png +0 -0
- package/docs/Example Usage.png +0 -0
- package/main.js +84 -0
- package/package.json +28 -0
- package/run/benchmark.bash +4 -0
- package/run/install.bash +61 -0
- package/run/solutions.txt +2315 -0
- package/run/wordle.txt +12972 -0
- package/src/lib/entropy.js +123 -0
- package/src/lib/heuristic.js +104 -0
- package/src/lib/lib.js +65 -0
- package/src/lib/wordle.js +250 -0
- package/src/pre/feedback_matrix.js +35 -0
- package/src/pre/filter_words.js +36 -0
- package/src/pre/test.js +26 -0
- package/src/solvers/combined.js +136 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Lib Imports
|
|
2
|
+
const { calculatePosFreq, pfHeuristicScore: posFreqScore, createOverlap, overlapScore } = require('../lib/heuristic.js');
|
|
3
|
+
const { calculateGuessEntropy: entropy_score, genEntropyTable, loadFeedbackMatrix } = require('../lib/entropy.js');
|
|
4
|
+
const { ask, normalise } = require('../lib/lib.js');
|
|
5
|
+
const { Wordle, patternFromUserInput } = require('../lib/wordle.js')
|
|
6
|
+
|
|
7
|
+
// Load Required Data
|
|
8
|
+
const words = require('../../data/filter/words.json');
|
|
9
|
+
const answers = require('../../data/filter/solutions.json');
|
|
10
|
+
const feedback_matrix = loadFeedbackMatrix('./data/proc/feedback_matrix.bin');
|
|
11
|
+
const word_index = new Map();
|
|
12
|
+
words.forEach((w, i) => word_index.set(w, i));
|
|
13
|
+
|
|
14
|
+
async function solve(opt) {
|
|
15
|
+
let log = opt.log;
|
|
16
|
+
|
|
17
|
+
// evaluation and feedback
|
|
18
|
+
/** @type {Wordle} */
|
|
19
|
+
let wordle;
|
|
20
|
+
if (opt.answer !== undefined) {
|
|
21
|
+
wordle = new Wordle(true, opt.answer);
|
|
22
|
+
} else {
|
|
23
|
+
wordle = new Wordle(false);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Guessing and scoring
|
|
27
|
+
let possible_words = answers;
|
|
28
|
+
let pos_frequencies;
|
|
29
|
+
let previous_guess_feedback = null;
|
|
30
|
+
const scores = { entropy: new Array(words.length), pos_freq: new Array(words.length), overlap: new Array(words.length) }
|
|
31
|
+
let word_indecies = answers.map((v, i) => word_index.get(v));
|
|
32
|
+
let overlapBin = "";
|
|
33
|
+
|
|
34
|
+
// Stats and tracking
|
|
35
|
+
let guesses = 0;
|
|
36
|
+
const word_numbers = []
|
|
37
|
+
|
|
38
|
+
while (true) {
|
|
39
|
+
// Strategize & Guess
|
|
40
|
+
let progress = 1 - (words.length - possible_words.length / words.length)
|
|
41
|
+
let best_guess = '!@??*';
|
|
42
|
+
let best_score = -Infinity;
|
|
43
|
+
|
|
44
|
+
if (guesses === 0) {
|
|
45
|
+
best_guess = 'dares'; // First guess is predefined based on entropy
|
|
46
|
+
overlapBin = createOverlap('dares');
|
|
47
|
+
} else if (possible_words.length === 1) {
|
|
48
|
+
// Optimisation for When Only 1 Possible Guess is Left
|
|
49
|
+
best_guess = possible_words[0];
|
|
50
|
+
} else {
|
|
51
|
+
// Weights
|
|
52
|
+
const entropy_weight = 80;
|
|
53
|
+
const pos_frequency_weight = 8;
|
|
54
|
+
const possible_answer_weight = 400 * (progress ** 2);
|
|
55
|
+
const overlap_weight = 8 * -progress;
|
|
56
|
+
|
|
57
|
+
pos_frequencies = calculatePosFreq(possible_words);
|
|
58
|
+
word_indecies = word_indecies.filter(v => wordle.meetsConditions(words[v]));
|
|
59
|
+
const ent_table = genEntropyTable(possible_words.length);
|
|
60
|
+
|
|
61
|
+
for (let g = 0; g < words.length; g++) {
|
|
62
|
+
let guess = words[g];
|
|
63
|
+
scores.entropy[g] = entropy_score(g, words, feedback_matrix, word_indecies, ent_table);
|
|
64
|
+
scores.pos_freq[g] = posFreqScore(guess, pos_frequencies);
|
|
65
|
+
scores.overlap[g] = overlapScore(guess, overlapBin);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
scores.pos_freq = normalise(scores.pos_freq);
|
|
69
|
+
scores.overlap = normalise(scores.overlap);
|
|
70
|
+
|
|
71
|
+
for (let guess = 0; guess < words.length; guess++) {
|
|
72
|
+
let score = 0;
|
|
73
|
+
score += (1 - Math.pow(2, -scores.entropy[guess])) * entropy_weight;
|
|
74
|
+
score += scores.pos_freq[guess] * pos_frequency_weight;
|
|
75
|
+
score += scores.overlap[guess] * overlap_weight;
|
|
76
|
+
if (wordle.meetsConditions(words[guess])) {
|
|
77
|
+
score += 1 * possible_answer_weight;
|
|
78
|
+
}
|
|
79
|
+
if (opt.rand !== undefined && opt.rand === true) {
|
|
80
|
+
score += 0.001 * Math.random();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (score > best_score) {
|
|
84
|
+
best_score = score;
|
|
85
|
+
best_guess = words[guess];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
createOverlap(best_guess, overlapBin);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
word_numbers.push(possible_words.length);
|
|
93
|
+
guesses += 1;
|
|
94
|
+
|
|
95
|
+
log(`Possible Words Left: ${possible_words.length}`);
|
|
96
|
+
log(`Best Guess ${guesses}: ${best_guess}`);
|
|
97
|
+
if (possible_words.length < 50) {
|
|
98
|
+
log(`Other Guesses ${guesses}: ${possible_words}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Feedback & Conditions updating
|
|
102
|
+
if (opt.type === 'benchmark') {
|
|
103
|
+
const feedback = wordle.evaluateGuess(best_guess);
|
|
104
|
+
previous_guess_feedback = feedback;
|
|
105
|
+
wordle.updateConditions(best_guess, feedback);
|
|
106
|
+
} else if (opt.type === 'user') {
|
|
107
|
+
best_guess = await ask("What Word Did You Guess: ");
|
|
108
|
+
const green = await ask("Green Letters - Use '.' for any blanks: ");
|
|
109
|
+
const yellow = await ask("Yellow Letters - Use '.' for any blanks: ");
|
|
110
|
+
|
|
111
|
+
const feedback = patternFromUserInput(green, yellow);
|
|
112
|
+
previous_guess_feedback = feedback;
|
|
113
|
+
wordle.updateConditions(best_guess, feedback);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
possible_words = possible_words.filter((word) => {
|
|
117
|
+
return wordle.meetsConditions(word)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
if (guesses > 6) {
|
|
121
|
+
log("Couldn't Solve in 6 guesses");
|
|
122
|
+
return { solved: false };
|
|
123
|
+
} else if (previous_guess_feedback === 682) {
|
|
124
|
+
log(`Solution: ${best_guess}`);
|
|
125
|
+
log(`Guess ${guesses} - Yippeee!`);
|
|
126
|
+
log(`Word Numbers By Guess: ${word_numbers}`);
|
|
127
|
+
return { solved: true, answer: best_guess, guesses: guesses, word_count: word_numbers };
|
|
128
|
+
} else if (possible_words.length === 0) {
|
|
129
|
+
log("ERROR: No Correct Word Could Be Found");
|
|
130
|
+
return { solved: false };
|
|
131
|
+
}
|
|
132
|
+
log('\n---|---|---|---')
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { solve }
|