Separate code into separate JavaScript file, chunk out the match precomputation so that it doesn't cause the page to be as unresponsive
This commit is contained in:
parent
f313a9e854
commit
16d108c530
203
wordle.htm
203
wordle.htm
|
@ -18,182 +18,18 @@
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
padding: 1em;
|
||||||
|
border: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="wordle.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var words = [];
|
|
||||||
|
|
||||||
// encodes the result of the guess as a number in base 3
|
var words = []
|
||||||
// where
|
|
||||||
// 0 means no match
|
|
||||||
// 1 means an exact match, and
|
|
||||||
// 2 means it's somewhere in the word
|
|
||||||
function compute_match(guess, word) {
|
|
||||||
var ret = 0
|
|
||||||
var matched = 0
|
|
||||||
const notmatched = []
|
|
||||||
|
|
||||||
const last = Math.min(guess.length, word.length)-1
|
|
||||||
for (var i = last; i >= 0; i--) {
|
|
||||||
ret *= 3
|
|
||||||
matched *= 2
|
|
||||||
if (guess[i] == word[i]) {
|
|
||||||
//console.log("match ", i)
|
|
||||||
ret += 1
|
|
||||||
matched += 1
|
|
||||||
} else {
|
|
||||||
notmatched.push(word[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var mul = 1
|
|
||||||
for (var i = 0; i <= last; i++) {
|
|
||||||
if ((matched % 2) == 0) {
|
|
||||||
// check against the notmatched array
|
|
||||||
for (var j = 0; j < notmatched.length; j++) {
|
|
||||||
if (notmatched[j] == guess[i]) {
|
|
||||||
//console.log("exists", i, j, guess, notmatched)
|
|
||||||
notmatched[j] = '-'
|
|
||||||
ret += 2*mul
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mul *= 3
|
|
||||||
matched = Math.floor(matched/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// converts the base 3 number into something that's readable
|
|
||||||
function dump_match(v) {
|
|
||||||
var ret = []
|
|
||||||
for (var i = 0; i < 5; i++) {
|
|
||||||
ret.push(v % 3)
|
|
||||||
v = Math.floor(v/3)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
function undump_match(l) {
|
|
||||||
var mul = 1
|
|
||||||
var ret = 0
|
|
||||||
for (var i = 0; i < 5; i++) {
|
|
||||||
ret += l[i]*mul
|
|
||||||
mul *= 3
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
var all_matches_buffer = null
|
|
||||||
var all_matches = null
|
|
||||||
const precompute_all = (() => {
|
|
||||||
var idx = 0
|
|
||||||
return () => {
|
|
||||||
const l = words.length
|
|
||||||
if (idx == 0) {
|
|
||||||
all_matches_buffer = new ArrayBuffer(l*l)
|
|
||||||
all_matches = new Uint8Array(all_matches_buffer)
|
|
||||||
}
|
|
||||||
const start = Date.now()
|
|
||||||
for (; (Date.now() - start) < 50 && idx < l; idx++) {
|
|
||||||
for (var j = 0; j < l; j++) {
|
|
||||||
all_matches[idx*l + j] = compute_match(words[idx], words[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx < l) {
|
|
||||||
status("computing word " + idx + "/" + words.length + " " + words[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx >= l) {
|
|
||||||
inited = true
|
|
||||||
console.log("precompute done")
|
|
||||||
status("precompute done")
|
|
||||||
} else {
|
|
||||||
setTimeout(precompute_all, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const LIST_END = (1<<16) - 1
|
|
||||||
function calc_entropy_with_cache(valid_words, guess_idx, cache) {
|
|
||||||
var entropy = 0
|
|
||||||
cache.fill(0)
|
|
||||||
|
|
||||||
const l = words.length
|
|
||||||
var view = new Uint8Array(all_matches_buffer, guess_idx*l)
|
|
||||||
|
|
||||||
var num_words = 0
|
|
||||||
for (var i = 0; i < valid_words.length; i++) {
|
|
||||||
if (valid_words[i] == LIST_END) break
|
|
||||||
cache[view[valid_words[i]]] += 1
|
|
||||||
num_words++
|
|
||||||
}
|
|
||||||
|
|
||||||
const log2 = Math.log(2)
|
|
||||||
for (var i = 0; i < 5*5*5*5*5; i++) {
|
|
||||||
if (cache[i] == 0) continue
|
|
||||||
var prob = cache[i]/num_words
|
|
||||||
//console.log("prob ", i, prob)
|
|
||||||
entropy += -Math.log(prob)/log2*prob
|
|
||||||
}
|
|
||||||
return entropy
|
|
||||||
}
|
|
||||||
|
|
||||||
function calc_entropy(valid_words, guess_idx) {
|
|
||||||
return calc_entropy_with_cache(valid_words, guess_idx, new Uint16Array(5*5*5*5*5))
|
|
||||||
}
|
|
||||||
|
|
||||||
function calc_best_word(valid_words) {
|
|
||||||
var cur_best = -1
|
|
||||||
var best_entropy = -1
|
|
||||||
|
|
||||||
var cache = new Uint16Array(5*5*5*5*5)
|
|
||||||
|
|
||||||
/*
|
|
||||||
for (var i = 0; i < valid_words.length; i++) {
|
|
||||||
if (valid_words[i] == LIST_END) break
|
|
||||||
var entropy = calc_entropy_with_cache(valid_words, valid_words[i], cache)
|
|
||||||
if (entropy > best_entropy) {
|
|
||||||
cur_best = valid_words[i]
|
|
||||||
best_entropy = entropy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
for (var i = 0; i < words.length; i++) {
|
|
||||||
var entropy = calc_entropy_with_cache(valid_words, i, cache)
|
|
||||||
if (entropy > best_entropy) {
|
|
||||||
cur_best = i
|
|
||||||
best_entropy = entropy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [cur_best, best_entropy]
|
|
||||||
}
|
|
||||||
|
|
||||||
function invalidate_words(valid_words, guess, match_result) {
|
|
||||||
const l = words.length
|
|
||||||
var view = new Uint8Array(all_matches_buffer, guess*l)
|
|
||||||
|
|
||||||
var idx = 0
|
|
||||||
for (var i = 0; i < valid_words.length; i++) {
|
|
||||||
if (valid_words[i] == LIST_END) break
|
|
||||||
if (view[valid_words[i]] == match_result) {
|
|
||||||
valid_words[idx] = valid_words[i]
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx != valid_words.length) valid_words[idx] = LIST_END
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
function gen_all_words_list() {
|
|
||||||
var ret = new Uint16Array(words.length)
|
|
||||||
for (var i = 0; i < words.length; i++) {
|
|
||||||
ret[i] = i
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
function status(s) {
|
function status(s) {
|
||||||
document.getElementById('status').innerText = s
|
document.getElementById('status').innerText = s
|
||||||
|
@ -205,6 +41,8 @@ function log(s) {
|
||||||
document.getElementById('log').appendChild(elem)
|
document.getElementById('log').appendChild(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valid_words = null;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
var req = new XMLHttpRequest()
|
var req = new XMLHttpRequest()
|
||||||
req.open('GET', 'valid-wordle-words.txt')
|
req.open('GET', 'valid-wordle-words.txt')
|
||||||
|
@ -212,6 +50,8 @@ function log(s) {
|
||||||
const text = req.responseText
|
const text = req.responseText
|
||||||
words = text.split('\n').map(line => line.trim())
|
words = text.split('\n').map(line => line.trim())
|
||||||
console.log("found " + words.length+ " words")
|
console.log("found " + words.length+ " words")
|
||||||
|
status("found " + words.length + " words")
|
||||||
|
valid_words = gen_all_words_list()
|
||||||
})
|
})
|
||||||
req.send()
|
req.send()
|
||||||
|
|
||||||
|
@ -225,6 +65,12 @@ function log(s) {
|
||||||
precompute_all()
|
precompute_all()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.getElementById('find').addEventListener('click', e => {
|
||||||
|
const [idx, entropy] = calc_best_word(valid_words)
|
||||||
|
status("best word is " + words[idx] + " , " + entropy)
|
||||||
|
log("best word " + words[idx] + " , " + entropy)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
@ -245,14 +91,15 @@ function log(s) {
|
||||||
<input minlength=5 maxlength=5 size=5></input>
|
<input minlength=5 maxlength=5 size=5></input>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<input type=button id="init" value="Init search"></button>
|
<input type=button id="init" value="Init search"></input>
|
||||||
<button id="clear">Clear history</button>
|
<input type=button id="clear" value="Clear history"></input>
|
||||||
<button id="submit">Submit word</button>
|
<input type=button id="submit" value="Submit word"></input>
|
||||||
<button id="confirm">Confirm word</button>
|
<input type=button id="confirm" value="Confirm word"></input>
|
||||||
<button id="find">Find best</button>
|
<input type=button id="find" value="Find best"></input>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
<div id=status></div>
|
<div id=status></div>
|
||||||
<div id=log></div>
|
log:
|
||||||
|
<div id=log class=log></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
// encodes the result of the guess as a number in base 3
|
||||||
|
// where
|
||||||
|
// 0 means no match
|
||||||
|
// 1 means an exact match, and
|
||||||
|
// 2 means it's somewhere in the word
|
||||||
|
function compute_match(guess, word) {
|
||||||
|
var ret = 0
|
||||||
|
var matched = 0
|
||||||
|
const notmatched = []
|
||||||
|
|
||||||
|
const last = Math.min(guess.length, word.length)-1
|
||||||
|
for (var i = last; i >= 0; i--) {
|
||||||
|
ret *= 3
|
||||||
|
matched *= 2
|
||||||
|
if (guess[i] == word[i]) {
|
||||||
|
//console.log("match ", i)
|
||||||
|
ret += 1
|
||||||
|
matched += 1
|
||||||
|
} else {
|
||||||
|
notmatched.push(word[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mul = 1
|
||||||
|
for (var i = 0; i <= last; i++) {
|
||||||
|
if ((matched % 2) == 0) {
|
||||||
|
// check against the notmatched array
|
||||||
|
for (var j = 0; j < notmatched.length; j++) {
|
||||||
|
if (notmatched[j] == guess[i]) {
|
||||||
|
//console.log("exists", i, j, guess, notmatched)
|
||||||
|
notmatched[j] = '-'
|
||||||
|
ret += 2*mul
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mul *= 3
|
||||||
|
matched = Math.floor(matched/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts the base 3 number into something that's readable
|
||||||
|
function dump_match(v) {
|
||||||
|
var ret = []
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
ret.push(v % 3)
|
||||||
|
v = Math.floor(v/3)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
function undump_match(l) {
|
||||||
|
var mul = 1
|
||||||
|
var ret = 0
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
ret += l[i]*mul
|
||||||
|
mul *= 3
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
var all_matches_buffer = null
|
||||||
|
var all_matches = null
|
||||||
|
const precompute_all = (() => {
|
||||||
|
var idx = 0
|
||||||
|
return () => {
|
||||||
|
const l = words.length
|
||||||
|
if (idx == 0) {
|
||||||
|
all_matches_buffer = new ArrayBuffer(l*l)
|
||||||
|
all_matches = new Uint8Array(all_matches_buffer)
|
||||||
|
}
|
||||||
|
const start = Date.now()
|
||||||
|
for (; (Date.now() - start) < 50 && idx < l; idx++) {
|
||||||
|
for (var j = 0; j < l; j++) {
|
||||||
|
all_matches[idx*l + j] = compute_match(words[idx], words[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx < l) {
|
||||||
|
status("computing word " + idx + "/" + words.length + " " + words[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx >= l) {
|
||||||
|
inited = true
|
||||||
|
console.log("precompute done, mem = " + all_matches.length/1024/1024 + " MB")
|
||||||
|
log("precompute done, mem = " + all_matches.length/1024/1024 + " MB")
|
||||||
|
status("precompute done, mem = " + all_matches.length/1024/1024 + " MB")
|
||||||
|
} else {
|
||||||
|
setTimeout(precompute_all, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
const LIST_END = (1<<16) - 1
|
||||||
|
function calc_entropy_with_cache(valid_words, guess_idx, cache) {
|
||||||
|
var entropy = 0
|
||||||
|
cache.fill(0)
|
||||||
|
|
||||||
|
const l = words.length
|
||||||
|
var view = new Uint8Array(all_matches_buffer, guess_idx*l)
|
||||||
|
|
||||||
|
var num_words = 0
|
||||||
|
for (var i = 0; i < valid_words.length; i++) {
|
||||||
|
if (valid_words[i] == LIST_END) break
|
||||||
|
cache[view[valid_words[i]]] += 1
|
||||||
|
num_words++
|
||||||
|
}
|
||||||
|
|
||||||
|
const log2 = Math.log(2)
|
||||||
|
for (var i = 0; i < 5*5*5*5*5; i++) {
|
||||||
|
if (cache[i] == 0) continue
|
||||||
|
var prob = cache[i]/num_words
|
||||||
|
//console.log("prob ", i, prob)
|
||||||
|
entropy += -Math.log(prob)/log2*prob
|
||||||
|
}
|
||||||
|
return entropy
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc_entropy(valid_words, guess_idx) {
|
||||||
|
return calc_entropy_with_cache(valid_words, guess_idx, new Uint16Array(5*5*5*5*5))
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc_best_word(valid_words) {
|
||||||
|
var cur_best = -1
|
||||||
|
var best_entropy = -1
|
||||||
|
|
||||||
|
var cache = new Uint16Array(5*5*5*5*5)
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (var i = 0; i < valid_words.length; i++) {
|
||||||
|
if (valid_words[i] == LIST_END) break
|
||||||
|
var entropy = calc_entropy_with_cache(valid_words, valid_words[i], cache)
|
||||||
|
if (entropy > best_entropy) {
|
||||||
|
cur_best = valid_words[i]
|
||||||
|
best_entropy = entropy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
for (var i = 0; i < words.length; i++) {
|
||||||
|
var entropy = calc_entropy_with_cache(valid_words, i, cache)
|
||||||
|
if (entropy > best_entropy) {
|
||||||
|
cur_best = i
|
||||||
|
best_entropy = entropy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [cur_best, best_entropy]
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidate_words(valid_words, guess, match_result) {
|
||||||
|
const l = words.length
|
||||||
|
var view = new Uint8Array(all_matches_buffer, guess*l)
|
||||||
|
|
||||||
|
var idx = 0
|
||||||
|
for (var i = 0; i < valid_words.length; i++) {
|
||||||
|
if (valid_words[i] == LIST_END) break
|
||||||
|
if (view[valid_words[i]] == match_result) {
|
||||||
|
valid_words[idx] = valid_words[i]
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx != valid_words.length) valid_words[idx] = LIST_END
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
function gen_all_words_list() {
|
||||||
|
var ret = new Uint16Array(words.length)
|
||||||
|
for (var i = 0; i < words.length; i++) {
|
||||||
|
ret[i] = i
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
function words_len(v) {
|
||||||
|
}
|
Loading…
Reference in New Issue