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
package/LISCENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright © 2026 Arlo Filley
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/ReadMe.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# WORDULATOR
|
|
2
|
+
## An Automated Entropy Based Wordle Guessing Bot
|
|
3
|
+
|
|
4
|
+
**Do you average 6 guesses at Wordle like I do?**
|
|
5
|
+
**If so then this is the repository you've been looking for!**
|
|
6
|
+
|
|
7
|
+
This is a Node.js implementation of a Wordle guessing bot. Uses entropy and several
|
|
8
|
+
heuristics to average 3.83 guesses per answer with 99% accuracy! *Tested over
|
|
9
|
+
100 different cases, accuracy is defined as getting the answer within the 6 allowed
|
|
10
|
+
guesses*
|
|
11
|
+
|
|
12
|
+
This project was built over the course of 4 days so don't expect it to be fast
|
|
13
|
+
or optimal. It was however an interesting dive into entropy, bitwise optimisation,
|
|
14
|
+
and implementing the Wordle ruleset!
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
- Clone repo
|
|
18
|
+
- Navigate to `./` in terminal
|
|
19
|
+
- Run `npm run install-wordulator` - and wait to finish
|
|
20
|
+
- Installs base Wordle solutions and guesses
|
|
21
|
+
- Precomputes feedback matrix - This step might take a while
|
|
22
|
+
- Creates 5000 benchmark test cases from the solution list
|
|
23
|
+
- All Done
|
|
24
|
+
|
|
25
|
+
### Usage
|
|
26
|
+
- Run in **user** mode using `node .`
|
|
27
|
+
- Run a standard **benchmark** using `npm run benchmark`
|
|
28
|
+
- Run a custom **benchmark** using `node . combo bench (# of tests)`
|
|
29
|
+
- Enjoy
|
|
30
|
+
|
|
31
|
+
### Example Usage
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
### Example Benchmark
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
- Implements full Wordle ruleset - with automated feedback for efficient benchmarking
|
|
39
|
+
- Entropy guess scoring with several adjusting heuristics including
|
|
40
|
+
- Positional letter frequencies
|
|
41
|
+
- Non-overlapping letters
|
|
42
|
+
- Guess being a possible remaining answer
|
|
43
|
+
- Capable of using most\* 5 letter word lists (\*with A-Z charset)
|
|
44
|
+
- Precomputation of feedback matrix
|
|
45
|
+
- Memory mapping of feedback matrix allowing parallel program execution
|
|
46
|
+
- Automated benchmarking for running up to thousands of tests
|
|
47
|
+
|
|
48
|
+
## Known Issues
|
|
49
|
+
- Feedback matrix consumes significant disk and memory space
|
|
50
|
+
- Limited to 5-letter Wordle variants
|
|
51
|
+
- Missing many possible runtime performance optimisations
|
|
52
|
+
- Assumes only ASCII characters a-z in wordlist
|
|
53
|
+
|
|
54
|
+
## Ideas for Future Improvement
|
|
55
|
+
- Weighting heuristics scores based on possible words left
|
|
56
|
+
- Precomputed second guesses
|
|
57
|
+
- Massive Performance optimisations
|
|
58
|
+
- User-friendly web interface
|
|
59
|
+
- Rewrite in rust?
|
|
60
|
+
|
|
61
|
+
# How it Works
|
|
62
|
+
## Feedback Modeling
|
|
63
|
+
This program models Wordle feedback per letter as green, yellow or grey and encodes
|
|
64
|
+
that into an 8 bit integer, using ~2 bits per letter position. Because Wordle has
|
|
65
|
+
5 positions which each have 3 possibilities this means there are 243 possible feedbacks.
|
|
66
|
+
Each guess and answer pair is mapped into a deterministic feedback pattern represented
|
|
67
|
+
by the feedback matrix. The feedback matrix is precomputed before running the problem
|
|
68
|
+
as it significantly speeds up runtime performance at the cost of greater disk and
|
|
69
|
+
memory usage. The matrix allows simulating potential outcomes from each guess-answer
|
|
70
|
+
pair to model expected information gain
|
|
71
|
+
|
|
72
|
+
## Scoring
|
|
73
|
+
Each possible guess in the total word list is evaluate based on its entropy. This
|
|
74
|
+
entropy in turn is based on simulating how the remaining possible answers would
|
|
75
|
+
be partitioned be a given feedback. Guesses with higher entropy eliminate more potential
|
|
76
|
+
answers than guesses with lower entropy\* (\*In the vast majority of cases). Each
|
|
77
|
+
score is then adjusted based on the positional frequency of each letter in the guess
|
|
78
|
+
based on the positional frequencies of remaining possible answers. The score is
|
|
79
|
+
also adjusted based on whether letters in the guess have already been seen before
|
|
80
|
+
in a previous guess. And finally the score is higher for guesses that are also contained
|
|
81
|
+
in the possible answer set. These heuristics are great for picking more optimal
|
|
82
|
+
guesses that still have high entropy.
|
|
83
|
+
|
|
84
|
+
## Solving
|
|
85
|
+
To solve a given Wordle problem, the program:
|
|
86
|
+
1. Makes a first predetermined guess (currently 'dares')
|
|
87
|
+
2. Receives user or simulated feedback
|
|
88
|
+
3. Create conditions the solution must fulfil
|
|
89
|
+
4. Filters possible answers based on currently known solution conditions
|
|
90
|
+
5. Makes another guess
|
|
91
|
+
|
|
92
|
+
This loop repeats steps 2-5 until:
|
|
93
|
+
- a) There is only one possible answer, the solution
|
|
94
|
+
- b) The program has used up its 6 guesses
|
|
95
|
+
- c) There is no possible answer that meets the solution conditions
|
|
Binary file
|
|
Binary file
|
package/main.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
let { solve : combinedSolver } = require('./src/solvers/combined.js')
|
|
4
|
+
let { randomInt } = require('./src/lib/lib.js')
|
|
5
|
+
|
|
6
|
+
let words = require('./data/filter/words.json');
|
|
7
|
+
let test_data = require('./data/test/tests.json')
|
|
8
|
+
|
|
9
|
+
main();
|
|
10
|
+
async function main() {
|
|
11
|
+
try {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const type = typeof(args[0]) === 'string' ? args[0] : 'user';
|
|
14
|
+
const num = args[1] > 0 ? Number.parseInt(args[1]) : 100;
|
|
15
|
+
|
|
16
|
+
let solve = combinedSolver;
|
|
17
|
+
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'bench' : await benchmark(solve, num, test_data, console.log); break;
|
|
20
|
+
case 'benchmark' : await benchmark(solve, num, test_data, console.log); break; break;
|
|
21
|
+
case 'user' : await solve({ type: "user", rand: true, log: console.log }); break;
|
|
22
|
+
default: throw `Invalid Mode Selected ${type}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(err);
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {function} solve
|
|
35
|
+
* @param {num} benchmark_num
|
|
36
|
+
* @param {object} weights
|
|
37
|
+
* @param {string[]} tests
|
|
38
|
+
* @param {function} log
|
|
39
|
+
*/
|
|
40
|
+
async function benchmark(solve, benchmark_num = 100, tests, log) {
|
|
41
|
+
const results = [];
|
|
42
|
+
let total_guesses = 0;
|
|
43
|
+
let correct = 0;
|
|
44
|
+
let no_of_guesses = [0, 0, 0, 0, 0, 0];
|
|
45
|
+
let avg = 0;
|
|
46
|
+
|
|
47
|
+
for (let i=0; i < benchmark_num; i++) {
|
|
48
|
+
let ans = words[randomInt(words.length)];
|
|
49
|
+
let result;
|
|
50
|
+
|
|
51
|
+
if (tests !== undefined) ans = tests[i];
|
|
52
|
+
result = await solve({ type: 'benchmark', answer: ans, log: () => {} });
|
|
53
|
+
|
|
54
|
+
if (result.solved === true) {
|
|
55
|
+
total_guesses += result.guesses;
|
|
56
|
+
correct++;
|
|
57
|
+
no_of_guesses[result.guesses-1]++;
|
|
58
|
+
avg = total_guesses/(i+1);
|
|
59
|
+
} else {
|
|
60
|
+
avg = total_guesses/(i+1);
|
|
61
|
+
total_guesses += 8;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (/*i % 20 === 0 &&*/ result.solved) {
|
|
65
|
+
log(`Solved ${correct}/${i+1} | guesses: ${result.guesses} | accuracy ${(correct / (i+1) * 100).toFixed(1)} | running avg: ${avg.toFixed(2)}`);
|
|
66
|
+
} else /*if (i % 20 === 0)*/ {
|
|
67
|
+
log(`Solved ${correct}/${i+1} | guesses: >6 | accuracy ${(correct / (i+1) * 100).toFixed(1)} | running avg: ${avg.toFixed(2)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
results.push(result);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log(`Correct - ${correct}/${benchmark_num} - ${correct / benchmark_num * 100}%`);
|
|
74
|
+
log(`Avg Guess - ${avg.toFixed(3)}`);
|
|
75
|
+
log('Guess Breakdown:')
|
|
76
|
+
for (let i=0; i<no_of_guesses.length; i++) {
|
|
77
|
+
if (i === 0) {
|
|
78
|
+
log(`\t${i+1} guess - ${no_of_guesses[0]}`);
|
|
79
|
+
} else {
|
|
80
|
+
log(`\t${i+1} guesses - ${no_of_guesses[i]}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wordulator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An Entropy Based Wordle Solver",
|
|
5
|
+
"keywords": ["wordle", "entropy"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Arlo Filley",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/ArloFilley/Wordulator"
|
|
11
|
+
},
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"main": "main.js",
|
|
14
|
+
"bin": {
|
|
15
|
+
"wordulator": "node ./main.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "echo \"What the hell is a test???? YALL TESTING UR CODE??? GETTOUTAHEER!!!\" && exit 1",
|
|
19
|
+
"install-wordulator": "chmod u+x run/install.bash && run/install.bash && chmod u+x run/benchmark.bash",
|
|
20
|
+
"benchmark": "run/benchmark.bash",
|
|
21
|
+
"bench": "run/benchmark.bash"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@luminati-io/mmap-io": "^1.1.7-lum.6",
|
|
25
|
+
"i": "^0.3.7",
|
|
26
|
+
"npm": "^11.8.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/run/install.bash
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
echo "installing node modules"
|
|
3
|
+
npm i
|
|
4
|
+
wait
|
|
5
|
+
sleep 5
|
|
6
|
+
|
|
7
|
+
echo "Creating Directory Structure"
|
|
8
|
+
sleep 0.2
|
|
9
|
+
echo "Created ./data"
|
|
10
|
+
sleep 0.2
|
|
11
|
+
mkdir data
|
|
12
|
+
echo "Created ./data/filter"
|
|
13
|
+
mkdir data/filter
|
|
14
|
+
sleep 0.2
|
|
15
|
+
echo "Created ./data/proc"
|
|
16
|
+
mkdir data/proc
|
|
17
|
+
sleep 0.2
|
|
18
|
+
echo "Created ./data/raw"
|
|
19
|
+
mkdir data/raw
|
|
20
|
+
sleep 0.2
|
|
21
|
+
echo "Created ./data/test"
|
|
22
|
+
mkdir data/test
|
|
23
|
+
sleep 0.2
|
|
24
|
+
echo "Created ./bench"
|
|
25
|
+
mkdir bench
|
|
26
|
+
|
|
27
|
+
wait
|
|
28
|
+
sleep 5
|
|
29
|
+
|
|
30
|
+
echo "Copying Files Into Correct Places"
|
|
31
|
+
cp run/wordle.txt data/raw/wordle.txt
|
|
32
|
+
sleep 0.2
|
|
33
|
+
echo "Copied ./run/wordle.txt -> ./data/raw/wordle.txt"
|
|
34
|
+
cp run/solutions.txt data/raw/solutions.txt
|
|
35
|
+
sleep 0.2
|
|
36
|
+
echo "Copied ./run/solutions.txt -> ./data/raw/solutions.txt"
|
|
37
|
+
|
|
38
|
+
wait
|
|
39
|
+
sleep 1
|
|
40
|
+
|
|
41
|
+
echo "Filtering List For Valid Words"
|
|
42
|
+
node src/pre/filter_words.js data/raw/wordle.txt data/filter/words.json 5
|
|
43
|
+
node src/pre/filter_words.js data/raw/solutions.txt data/filter/solutions.json 5
|
|
44
|
+
|
|
45
|
+
wait
|
|
46
|
+
sleep 1
|
|
47
|
+
|
|
48
|
+
echo "Generating Feedback Matrix"
|
|
49
|
+
echo "This Step Might Take a While"
|
|
50
|
+
echo "The Feedback Matrix Can Be Found at data/proc/feedback_matrix.bin It Takes Up Roughly 200mb For 12k Words"
|
|
51
|
+
node ./src/pre/feedback_matrix.js data/filter/words.json data/proc/feedback_matrix.bin
|
|
52
|
+
|
|
53
|
+
wait
|
|
54
|
+
sleep 1
|
|
55
|
+
|
|
56
|
+
echo "Generating Benchmark Tests"
|
|
57
|
+
node src/pre/test.js data/filter/solutions.json data/test/tests.json 5000
|
|
58
|
+
|
|
59
|
+
wait
|
|
60
|
+
sleep 1
|
|
61
|
+
echo "Everything installed correctly :>"
|