Compare commits
4 Commits
febec0cf10
...
d7c2c6249a
Author | SHA1 | Date |
---|---|---|
|
d7c2c6249a | |
|
1e31f023bd | |
|
93c5821248 | |
|
74812a6a01 |
|
@ -0,0 +1 @@
|
|||
wordle_opt.wasm
|
4
Makefile
4
Makefile
|
@ -1,8 +1,8 @@
|
|||
all: wordle_opt.wasm
|
||||
|
||||
wordle_opt.wasm: wordle_opt/target/wasm32-unknown-unknown/release/wordle_opt.wasm
|
||||
#wasm-snip $< -o $@
|
||||
cp $< $@
|
||||
wasm-snip $< -o $@ --snip-rust-fmt-code --snip-rust-panicking-code
|
||||
#cp $< $@
|
||||
|
||||
wordle_opt/target/wasm32-unknown-unknown/release/wordle_opt.wasm: wordle_opt/src/*.rs
|
||||
cd wordle_opt; cargo build --target wasm32-unknown-unknown --release
|
||||
|
|
|
@ -0,0 +1,296 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.letter {
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
.match {
|
||||
background: lightgreen;
|
||||
}
|
||||
|
||||
.exists {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
.notincluded {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.log {
|
||||
padding: 1em;
|
||||
border: solid;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" src="wordle_shim.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
function status(s) {
|
||||
document.getElementById('status').innerText = s
|
||||
}
|
||||
|
||||
function log(s) {
|
||||
const elem = document.createElement('p')
|
||||
elem.innerText = s
|
||||
document.getElementById('log').appendChild(elem)
|
||||
}
|
||||
|
||||
var valid_words = null;
|
||||
|
||||
(() => {
|
||||
var req = new XMLHttpRequest()
|
||||
req.open('GET', 'valid-wordle-words.txt')
|
||||
req.addEventListener('load', e => {
|
||||
const text = req.responseText
|
||||
init_wasm_solver(text)
|
||||
status("words loaded")
|
||||
})
|
||||
req.send()
|
||||
|
||||
})()
|
||||
|
||||
var solver = null
|
||||
const cur_status = [0, 0, 0, 0, 0]
|
||||
var cur_guess = null
|
||||
var num_guess = 0
|
||||
var chosen_words = [];
|
||||
|
||||
(() => {
|
||||
window.addEventListener('load', () => {
|
||||
document.getElementById('init').addEventListener('click', e => {
|
||||
if (solver == null) {
|
||||
solver = init_solver()
|
||||
var num_loops = 100
|
||||
const precalc_start = Date.now()
|
||||
const precompute_loop = () => {
|
||||
if (solver.precalc_done()) return
|
||||
const start = Date.now()
|
||||
solver.precalc(num_loops)
|
||||
const end = Date.now()
|
||||
const millis = end - start
|
||||
num_loops = Math.floor(num_loops - 0.5*(millis - 50))
|
||||
num_loops = Math.max(num_loops, 1)
|
||||
|
||||
status("computing word " + solver.precalc_idx() + "/" + word_count + " " + solver.lookup_word(solver.precalc_idx()))
|
||||
if (!solver.precalc_done()) {
|
||||
setTimeout(precompute_loop, 0)
|
||||
} else {
|
||||
const precalc_end = Date.now()
|
||||
log("took " + (precalc_end - precalc_start) + " ms")
|
||||
console.log("took " + (precalc_end - precalc_start) + " ms")
|
||||
}
|
||||
}
|
||||
precompute_loop(solver)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('reset').addEventListener('click', e => {
|
||||
cur_guess = null
|
||||
num_guess = 0
|
||||
chosen_words.length = 0
|
||||
|
||||
if (solver != null) {
|
||||
solver.reset()
|
||||
}
|
||||
const history_elem = document.getElementById('history')
|
||||
while (history_elem.firstChild) {
|
||||
history_elem.removeChild(history_elem.lastChild)
|
||||
}
|
||||
|
||||
const cur_word_elem = document.getElementById('cur-word')
|
||||
while (cur_word_elem.firstChild) {
|
||||
cur_word_elem.removeChild(cur_word_elem.lastChild)
|
||||
}
|
||||
|
||||
const log_elem = document.getElementById('log')
|
||||
while (log_elem.firstChild) {
|
||||
log_elem.removeChild(log_elem.lastChild)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('find').addEventListener('click', e => {
|
||||
if (solver == null) {
|
||||
status("please initialize the solver first")
|
||||
return
|
||||
}
|
||||
if (solver.words_left() == 1) {
|
||||
const word = solver.lookup_valid_word(0)
|
||||
status("the word is " + word)
|
||||
log("the word is " + word)
|
||||
return
|
||||
}
|
||||
const [idx, entropy, word] = solver.best_word()
|
||||
status("best word is " + word + " , " + entropy + " bits")
|
||||
log("best word " + word + " , " + entropy + " bits")
|
||||
})
|
||||
|
||||
document.getElementById('submit').addEventListener('click', e => {
|
||||
if (solver == null) {
|
||||
status("please initialize the solver first")
|
||||
return
|
||||
}
|
||||
const input_elem = document.getElementById('guess')
|
||||
const input = input_elem.value.trim()
|
||||
|
||||
if (input.length != 5) {
|
||||
status(input + " is not a valid word")
|
||||
return
|
||||
}
|
||||
const idx = solver.find_word(input)
|
||||
|
||||
if (idx == -1) {
|
||||
status(input + " is not a valid word")
|
||||
return
|
||||
}
|
||||
|
||||
cur_guess = input
|
||||
num_guess++
|
||||
const my_num_guess = num_guess
|
||||
for (var i = 0; i < 5; i++) {
|
||||
cur_status[i] = 0
|
||||
}
|
||||
|
||||
const cur_word = document.getElementById('cur-word')
|
||||
while (cur_word.firstChild) {
|
||||
cur_word.removeChild(cur_word.lastChild)
|
||||
}
|
||||
for (var i = 0; i < 5; i++) {
|
||||
const elem = document.createElement('span')
|
||||
elem.textContent = input[i].toUpperCase()
|
||||
elem.classList.add('letter')
|
||||
elem.classList.add('notincluded')
|
||||
|
||||
const char_idx = i
|
||||
elem.addEventListener('click', e => {
|
||||
if (num_guess != my_num_guess) return
|
||||
|
||||
cur_status[char_idx]++
|
||||
if (cur_status[char_idx] >= 3) {
|
||||
cur_status[char_idx] = 0
|
||||
}
|
||||
elem.classList.remove('match')
|
||||
elem.classList.remove('exists')
|
||||
elem.classList.remove('notincluded')
|
||||
switch (cur_status[char_idx]) {
|
||||
case 0:
|
||||
elem.classList.add('notincluded')
|
||||
break
|
||||
case 1:
|
||||
elem.classList.add('match')
|
||||
break
|
||||
case 2:
|
||||
elem.classList.add('exists')
|
||||
}
|
||||
})
|
||||
cur_word.appendChild(elem)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('confirm').addEventListener('click', e => {
|
||||
if (solver == null) {
|
||||
status("please initialize the solver first")
|
||||
return
|
||||
}
|
||||
if (cur_guess != null) {
|
||||
var nodes = []
|
||||
const cur_word = document.getElementById('cur-word')
|
||||
while (cur_word.firstChild) {
|
||||
nodes.push(cur_word.firstChild)
|
||||
cur_word.removeChild(cur_word.firstChild)
|
||||
}
|
||||
|
||||
const div_wrapper = document.createElement('div')
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
div_wrapper.appendChild(nodes[i])
|
||||
}
|
||||
document.getElementById('history').appendChild(div_wrapper)
|
||||
|
||||
const guess_idx = solver.find_word(cur_guess)
|
||||
if (guess_idx == -1) return
|
||||
chosen_words.push([guess_idx, [...cur_status]])
|
||||
solver.eliminate_words(guess_idx, undump_match(cur_status))
|
||||
log("guessed " + cur_guess + " " + solver.words_left() + " words left")
|
||||
status("guessed " + cur_guess + " " + solver.words_left() + " words left")
|
||||
cur_guess = null
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('back').addEventListener('click', e => {
|
||||
if (chosen_words.length == 0) return
|
||||
if (solver == null) {
|
||||
status("please initialize the solver first")
|
||||
return
|
||||
}
|
||||
chosen_words.pop()
|
||||
const history = document.getElementById('history')
|
||||
history.removeChild(history.lastChild)
|
||||
|
||||
// replay the game up to this point
|
||||
solver.reset()
|
||||
for (var i = 0; i < chosen_words.length; i++) {
|
||||
solver.eliminate_words(
|
||||
chosen_words[i][0],
|
||||
undump_match(chosen_words[i][1]))
|
||||
}
|
||||
const num_valid = solver.words_left()
|
||||
log("removed last added word, " + num_valid + " words left")
|
||||
status("removed last added word, " + num_valid + " words left")
|
||||
})
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="history" id="history"></div>
|
||||
<div id="cur-word"></div>
|
||||
<form>
|
||||
<p>
|
||||
<input id="guess" minlength=5 maxlength=5 size=5></input>
|
||||
</p>
|
||||
<p>
|
||||
<input type=button id="init" value="Initialize"></input>
|
||||
<input type=button id="reset" value="Reset game"></input>
|
||||
<input type=button id="submit" value="Submit word"></input>
|
||||
<input type=button id="confirm" value="Confirm word"></input>
|
||||
<input type=button id="back" value="Back"></input>
|
||||
<input type=button id="find" value="Find best"></input>
|
||||
</p>
|
||||
</form>
|
||||
<div id=status>loading words...</div>
|
||||
log:
|
||||
<!-- TODO show a sorted list of all the valid words or all the best words to guess with -->
|
||||
<div id=log class=log></div>
|
||||
|
||||
<h1>How to use</h1>
|
||||
<p>1. Initialize the solver by pressing the Initialize button. This causes it to precompute this large (~220 MB) array of values</p>
|
||||
<p>2. Use "Find best" to choose a word for you or enter in your own choice of word, and press Submit word. Submit this word in Wordle as well</p>
|
||||
<p>3. Click on the letters of the word to match the results of what was matching according to Wordle. Hit confirm when it matches the colors of the word in Wordle. This will cause the solver to eliminate words that are no longer possible. If there was an error in the color pattern you can hit Back to remove the last confirmed word, and reenter it. </p>
|
||||
<p>4. Optimally win Wordle. </p>
|
||||
</body>
|
||||
</html>
|
|
@ -27,6 +27,7 @@
|
|||
</style>
|
||||
|
||||
<script type="text/javascript" src="wordle.js"></script>
|
||||
<script type="text/javascript" src="wordle_shim.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var words = []
|
||||
|
|
|
@ -65,6 +65,7 @@ var all_matches = null
|
|||
const precompute_all = (() => {
|
||||
var idx = 0
|
||||
var init_start = null
|
||||
var num_loops = 10
|
||||
return () => {
|
||||
const l = words.length
|
||||
const start = Date.now()
|
||||
|
@ -73,7 +74,7 @@ const precompute_all = (() => {
|
|||
all_matches = new Uint8Array(all_matches_buffer)
|
||||
init_start = start
|
||||
}
|
||||
for (; (Date.now() - start) < 40 && idx < l; idx++) {
|
||||
for (var i = 0; i < num_loops && idx < l; i++, idx++) {
|
||||
for (var j = 0; j < l; j++) {
|
||||
all_matches[idx*l + j] = compute_match(words[idx], words[j])
|
||||
}
|
||||
|
@ -81,9 +82,12 @@ const precompute_all = (() => {
|
|||
if (idx < l) {
|
||||
status("computing word " + idx + "/" + words.length + " " + words[idx])
|
||||
}
|
||||
const end = Date.now()
|
||||
const millis = end - start
|
||||
num_loops = Math.floor(num_loops - 0.5*(millis - 50))
|
||||
num_loops = Math.max(num_loops, 1)
|
||||
|
||||
if (idx >= l) {
|
||||
const end = Date.now()
|
||||
inited = true
|
||||
console.log("precompute done, mem = " + all_matches.length + " bytes")
|
||||
log("precompute done, mem = " + all_matches.length + " bytes")
|
||||
|
|
|
@ -1,79 +1,21 @@
|
|||
use std::alloc::{alloc, dealloc, Layout};
|
||||
|
||||
extern crate wee_alloc;
|
||||
|
||||
extern {
|
||||
fn fill_string(p: *mut u8);
|
||||
fn log_num_idxs(i: usize);
|
||||
}
|
||||
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
const WORD_SZ: usize = 5;
|
||||
|
||||
// webassembly version of all the wordle logic
|
||||
// because the javascript version is slow as hell on firefox
|
||||
use std::os::raw::c_char;
|
||||
|
||||
static mut LOOKUP: Option<&'static mut [u8]> = None;
|
||||
static mut STRINGS: Option<&'static mut [u8]> = None;
|
||||
static mut STR_IDXS: Option<&'static mut [*const c_char]> = None;
|
||||
|
||||
fn unwrap_or_abort<T>(v: Option<T>) -> T {
|
||||
match v {
|
||||
Some(v) => v,
|
||||
None => std::process::abort(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn alloc_ary<T>(sz: usize) -> &'static mut [T] {
|
||||
let layout = unwrap_or_abort(Layout::from_size_align(
|
||||
sz*std::mem::size_of::<T>(),
|
||||
std::mem::align_of::<T>()
|
||||
).ok());
|
||||
let ptr = alloc(layout) as *mut T;
|
||||
std::slice::from_raw_parts_mut(ptr, sz)
|
||||
}
|
||||
|
||||
unsafe fn dealloc_ary<T>(v: &'static mut [T]) {
|
||||
let layout = unwrap_or_abort(Layout::from_size_align(
|
||||
v.len()*std::mem::size_of::<T>(),
|
||||
std::mem::align_of::<T>()
|
||||
).ok());
|
||||
dealloc(v.as_ptr() as *mut u8, layout);
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn init_strings(sz: usize) -> *mut c_char {
|
||||
unsafe {
|
||||
if let Some(v) = &mut STRINGS {
|
||||
dealloc_ary(v);
|
||||
STRINGS = None;
|
||||
}
|
||||
STRINGS = Some(alloc_ary(sz));
|
||||
unwrap_or_abort(STRINGS.as_deref_mut().map(|v| v.as_mut_ptr() as *mut c_char))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn init_idx() -> usize {
|
||||
// TODO
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn init_lookup() -> *mut c_char {
|
||||
unsafe {
|
||||
let num_strings = STR_IDXS.as_ref().map(|idxs| idxs.len()).unwrap_or(0);
|
||||
if let Some(v) = &mut LOOKUP {
|
||||
dealloc_ary(v);
|
||||
STRINGS = None;
|
||||
}
|
||||
|
||||
LOOKUP = Some(alloc_ary(num_strings*num_strings));
|
||||
unwrap_or_abort(
|
||||
LOOKUP.as_deref_mut().map(|v| v.as_mut_ptr() as *mut c_char))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn calc_match(guess: *const c_char, reference: *const c_char) -> u8 {
|
||||
let mut unmatched = vec![0u8; 26];
|
||||
pub extern fn calc_match(guess: *const u8, reference: *const u8, unmatched: &mut [u8; 26]) -> u8 {
|
||||
unmatched.fill(0);
|
||||
let mut ret = 0;
|
||||
let mut matched = 0;
|
||||
|
||||
|
@ -105,6 +47,261 @@ pub extern fn calc_match(guess: *const c_char, reference: *const c_char) -> u8 {
|
|||
ret
|
||||
}
|
||||
|
||||
|
||||
pub struct Solver {
|
||||
strings: Vec<u8>,
|
||||
idxs: Vec<*const u8>,
|
||||
valid_words: Vec<u16>,
|
||||
lookup: Vec<u8>,
|
||||
|
||||
precalc_idx: usize,
|
||||
entropy_scratch: [u16; 3*3*3*3*3],
|
||||
}
|
||||
|
||||
impl Solver {
|
||||
fn new() -> Solver {
|
||||
Solver {
|
||||
strings: Vec::new(),
|
||||
idxs: Vec::new(),
|
||||
valid_words: Vec::new(),
|
||||
|
||||
lookup: Vec::new(),
|
||||
precalc_idx: 0,
|
||||
entropy_scratch: [0u16; 3*3*3*3*3],
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: calls fill_string to get the string from Javascript
|
||||
fn init(&mut self, str_sz: usize) {
|
||||
self.strings.resize(str_sz, 0u8);
|
||||
unsafe { fill_string(self.strings.as_mut_ptr()); }
|
||||
self.init_index();
|
||||
let num_words = self.idxs.len();
|
||||
unsafe { log_num_idxs(num_words); }
|
||||
self.reset();
|
||||
self.lookup.resize(num_words*num_words, 0);
|
||||
}
|
||||
|
||||
fn init_index(&mut self) {
|
||||
let mut last_alpha = false;
|
||||
let mut num_words = 0;
|
||||
for v in self.strings.iter() {
|
||||
let cur_alpha = *v >= ('a' as u8) && *v <= ('z' as u8);
|
||||
if cur_alpha && !last_alpha {
|
||||
num_words += 1;
|
||||
}
|
||||
last_alpha = cur_alpha;
|
||||
}
|
||||
|
||||
self.idxs.resize(num_words, 0 as *const u8);
|
||||
|
||||
let mut idx = 0;
|
||||
for (i, v) in self.strings.iter().enumerate() {
|
||||
let cur_alpha = *v >= ('a' as u8) && *v <= ('z' as u8);
|
||||
if cur_alpha && !last_alpha {
|
||||
self.idxs[idx] = &self.strings[i] as *const u8;
|
||||
idx += 1;
|
||||
}
|
||||
last_alpha = cur_alpha;
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.valid_words.resize(self.idxs.len(), 65535);
|
||||
for (i, v) in self.valid_words.iter_mut().enumerate() {
|
||||
*v = i as u16;
|
||||
}
|
||||
}
|
||||
|
||||
fn eliminate_words(&mut self, guess_idx: usize, guess_result: u8) {
|
||||
let num_valid_words = self.valid_words.len();
|
||||
let mut idx = 0;
|
||||
for i in 0..num_valid_words {
|
||||
if self.lookup[guess_idx * self.idxs.len() + self.valid_words[i] as usize] == guess_result {
|
||||
self.valid_words[idx] = self.valid_words[i];
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
self.valid_words.resize(idx, 65535);
|
||||
}
|
||||
|
||||
fn precalc(&mut self, num_words: usize) -> bool {
|
||||
let mut tmp = [0u8; 26];
|
||||
let len = self.idxs.len();
|
||||
for _ in 0..num_words {
|
||||
if self.precalc_idx >= len {
|
||||
break;
|
||||
}
|
||||
|
||||
let i = self.precalc_idx;
|
||||
for (j, v) in self.idxs.iter().enumerate() {
|
||||
self.lookup[i * len + j] = calc_match(self.idxs[i], *v, &mut tmp);
|
||||
}
|
||||
self.precalc_idx += 1;
|
||||
}
|
||||
self.precalc_idx >= len
|
||||
}
|
||||
|
||||
fn calc_entropy(&mut self, guess_idx: usize) -> f32 {
|
||||
self.entropy_scratch.fill(0);
|
||||
let l = self.idxs.len();
|
||||
for idx in &self.valid_words {
|
||||
self.entropy_scratch[self.lookup[guess_idx*l + (*idx as usize)] as usize] += 1;
|
||||
}
|
||||
let mut entropy = 0f32;
|
||||
let recip = 1.0f32/(self.valid_words.len() as f32);
|
||||
for v in &self.entropy_scratch {
|
||||
if *v != 0 {
|
||||
let prob = (*v as f32)*recip;
|
||||
entropy -= prob.log2() * prob;
|
||||
}
|
||||
}
|
||||
entropy
|
||||
}
|
||||
|
||||
fn best_word(&mut self) -> (isize, f32) {
|
||||
if self.valid_words.len() == 1 {
|
||||
return (self.valid_words[0] as isize, 0f32);
|
||||
}
|
||||
let mut cur_best = -1;
|
||||
let mut cur_entropy = 0f32;
|
||||
for i in 0..self.idxs.len() {
|
||||
let entropy = self.calc_entropy(i);
|
||||
if entropy > cur_entropy {
|
||||
cur_entropy = entropy;
|
||||
cur_best = i as isize;
|
||||
}
|
||||
}
|
||||
(cur_best, cur_entropy)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[no_mangle]
|
||||
pub fn idxs_offset(s: &Solver) -> *const *const u8 {
|
||||
(&s.idxs[0]).as_ptr()
|
||||
}
|
||||
*/
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn init(str_sz: usize) -> *mut Solver {
|
||||
let mut s = Box::new(Solver::new());
|
||||
s.init(str_sz);
|
||||
Box::into_raw(s)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn reset(s: *mut Solver) {
|
||||
let solver: &mut Solver = unsafe { &mut *s };
|
||||
solver.reset();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn eliminate_words(s: *mut Solver, guess_idx: usize, guess_result: u8) {
|
||||
let solver: &mut Solver = unsafe { &mut *s };
|
||||
solver.eliminate_words(guess_idx, guess_result);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn words_left(s: *const Solver) -> usize {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
solver.valid_words.len()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn calc_entropy(s: *mut Solver, idx: usize) -> f32{
|
||||
let solver: &mut Solver = unsafe { &mut *s };
|
||||
solver.calc_entropy(idx)
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn precalc(s: *mut Solver, num_words: usize) -> bool {
|
||||
let solver: &mut Solver = unsafe { &mut *s };
|
||||
solver.precalc(num_words)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn get_precalc(s: *const Solver) -> usize {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
solver.precalc_idx
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn precalc_done(s: *const Solver) -> bool {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
solver.precalc_idx >= solver.idxs.len()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn lookup_word(s: *const Solver, idx: usize) -> *const u8 {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
if idx < solver.idxs.len() {
|
||||
solver.idxs[idx]
|
||||
} else {
|
||||
0 as *const u8
|
||||
}
|
||||
}
|
||||
|
||||
static mut WORD_BUF: [u8; 5] = [0u8; 5];
|
||||
#[no_mangle]
|
||||
pub extern fn find_word_load(_s: *const Solver) -> *mut u8 {
|
||||
unsafe {&mut *(&mut WORD_BUF[0])}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn find_word(s: *const Solver) -> isize {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
let mut idx = -1;
|
||||
for (i, ptr) in solver.idxs.iter().enumerate() {
|
||||
let word: &[u8] = unsafe { std::slice::from_raw_parts(*ptr, 5) };
|
||||
unsafe {
|
||||
if word == WORD_BUF {
|
||||
idx = i as isize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
idx
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn lookup_match(s: *const Solver, guess_idx: usize, ref_idx: usize) -> u8 {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
let idx = guess_idx * solver.idxs.len() + ref_idx;
|
||||
if idx < solver.lookup.len() {
|
||||
solver.lookup[idx]
|
||||
} else {
|
||||
0xff
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn get_valid_word(s: *const Solver, idx: usize) -> isize {
|
||||
let solver: &Solver = unsafe { &*s };
|
||||
if idx < solver.valid_words.len() {
|
||||
solver.valid_words[idx] as isize
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static mut LAST_ENTROPY: f32 = 0f32;
|
||||
#[no_mangle]
|
||||
pub extern fn best_word(s: *mut Solver) -> isize {
|
||||
let solver: &mut Solver = unsafe { &mut *s };
|
||||
let (idx, entropy) = solver.best_word();
|
||||
unsafe { LAST_ENTROPY = entropy };
|
||||
idx
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn best_word_entropy(_s: *const Solver) -> f32 {
|
||||
unsafe { LAST_ENTROPY }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
var wasm_solver = null
|
||||
var word_count = 0
|
||||
var words_str_len = 0
|
||||
function init_wasm_solver(words_str) {
|
||||
words_str_len = words_str.length
|
||||
const fill_string = (offset) => {
|
||||
console.log("fill strings called " + offset)
|
||||
const str_buf = new Uint8Array(wasm_solver.exports.memory.buffer, offset)
|
||||
const buf = new TextEncoder().encode(words_str, "utf8")
|
||||
str_buf.set(buf, 0)
|
||||
}
|
||||
const log_num_idxs = i => {
|
||||
word_count = i
|
||||
console.log("wasm: found " + i + " words")
|
||||
}
|
||||
WebAssembly.instantiateStreaming(fetch("wordle_opt.wasm"), {
|
||||
env: {
|
||||
fill_string: fill_string,
|
||||
log_num_idxs: log_num_idxs,
|
||||
}
|
||||
}).then(wm => {
|
||||
wasm_solver = wm.instance
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function init_solver() {
|
||||
const exports = wasm_solver.exports
|
||||
console.log("wsl = ", words_str_len)
|
||||
const solver_ptr = wasm_solver.exports.init(words_str_len)
|
||||
return {
|
||||
ptr: solver_ptr,
|
||||
precalc: (nw) => exports.precalc(solver_ptr, nw),
|
||||
precalc_idx: () => exports.get_precalc(solver_ptr),
|
||||
precalc_done: () => exports.precalc_done(solver_ptr),
|
||||
|
||||
reset: () => exports.reset(solver_ptr),
|
||||
eliminate_words: (guess_idx, guess_result) =>
|
||||
exports.eliminate_words(solver_ptr, guess_idx, guess_result),
|
||||
words_left: () => exports.words_left(solver_ptr),
|
||||
calc_entropy: (idx) => exports.calc_entropy(solver_ptr, idx),
|
||||
find_word: (w) => {
|
||||
const ary = new TextEncoder().encode(w, "utf8")
|
||||
const offset = exports.find_word_load(solver_ptr)
|
||||
const dst = new Uint8Array(exports.memory.buffer, offset, 5)
|
||||
dst.set(ary)
|
||||
return exports.find_word(solver_ptr)
|
||||
},
|
||||
best_word: () => {
|
||||
const idx = exports.best_word(solver_ptr)
|
||||
const entropy = exports.best_word_entropy(solver_ptr)
|
||||
console.log(idx)
|
||||
var word = null
|
||||
if (idx != -1) {
|
||||
const offset = exports.lookup_word(solver_ptr, idx)
|
||||
word_ary = new Uint8Array(exports.memory.buffer, offset, 5)
|
||||
word = new TextDecoder().decode(word_ary)
|
||||
}
|
||||
return [idx, entropy, word]
|
||||
},
|
||||
|
||||
lookup_word: (idx) => {
|
||||
const offset = exports.lookup_word(solver_ptr, idx)
|
||||
if (offset == -1) return null
|
||||
word_ary = new Uint8Array(exports.memory.buffer, offset, 5)
|
||||
return new TextDecoder().decode(word_ary)
|
||||
},
|
||||
|
||||
lookup_valid_word: (i) => {
|
||||
const idx = exports.get_valid_word(solver_ptr, i)
|
||||
if (idx != -1) {
|
||||
const offset = exports.lookup_word(solver_ptr, idx)
|
||||
word_ary = new Uint8Array(exports.memory.buffer, offset, 5)
|
||||
return new TextDecoder().decode(word_ary)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue