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.
@@ -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 }