Skip to content

Commit b2a7879

Browse files
committed
worker: same value-net tiebreak as the browser bot; refresh readme
1 parent e495049 commit b2a7879

2 files changed

Lines changed: 66 additions & 28 deletions

File tree

README.md

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,61 @@
11
# DuotrigordleBot
22

3-
Solver bot for [duotrigordle.com](https://duotrigordle.com)
3+
A solver for [duotrigordle.com](https://duotrigordle.com) — 32 Wordle boards at
4+
once, 37 guesses. It scores every word against all the boards together and plays
5+
the best one.
46

5-
## Layout
7+
The extension and the web demo run the whole thing **in your browser** — no
8+
server, nothing to install past the extension itself.
69

7-
```
8-
data/ wordlists scraped from the live bundle
9-
core/ C++ solver — greedy / beam / endgame strategies
10-
server/ Python HTTP wrappers around the C++ worker
11-
extension/ Chrome MV3 extension that overlays suggestions on duotrigordle.com
12-
ml/ experiments: value net + turn-2 specialist training
13-
```
10+
- **Web demo + install steps:** https://shruichan.github.io/DuotrigordleBot/
11+
- **Extension:** load `extension/` unpacked (see below)
1412

15-
## Build
13+
## How it plays
1614

17-
```
18-
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
19-
ninja -C build
20-
```
15+
Entropy across all unsolved boards + a bonus for words that are likely to solve
16+
a board outright. Near-tied candidates get re-ranked by a small value net that
17+
predicts how many guesses are left, which keeps the worst games short. Plus the
18+
usual endgame guards (distinct answers, a rhyme-trap override).
2119

22-
Needs: cmake, ninja, OpenMP, GoogleTest, nlohmann_json. On Debian/Ubuntu:
20+
2000-game bench on the daily distribution:
2321

24-
```
25-
sudo apt install cmake ninja-build libgtest-dev nlohmann-json3-dev
26-
```
22+
| | mean | max | 36s |
23+
|---|---|---|---|
24+
| tail-averse (default) | 33.76 | 35 | 0 |
25+
| lowest-mean greedy | 33.62 | 36 | 3 |
26+
27+
100% solved, every game under the 37-guess daily limit. See `EXPERIMENTS.md` for
28+
the value-net / MCTS investigation and why ~33.6 is the floor.
2729

28-
## Run
30+
## Layout
2931

3032
```
31-
python3 server/serve.py # solver on http://127.0.0.1:8765
33+
web/ the JS engine (source of truth) + node tests
34+
extension/ self-contained Chrome MV3 extension (bundles the engine)
35+
docs/ GitHub Pages site — landing + in-browser solve demo
36+
data/ word lists + the trained value net
37+
core/ original C++ solver (greedy / endgame / mcts) + bench
38+
server/ optional local HTTP wrapper around the C++ worker
39+
ml/ training scripts (value net, turn-2 specialist)
3240
```
3341

34-
Load `extension/` as an unpacked Chrome extension, open duotrigordle.com.
42+
## Use the extension
3543

36-
## Numbers
44+
1. Grab this repo and open `chrome://extensions`.
45+
2. Turn on Developer mode → "Load unpacked" → pick the `extension/` folder.
46+
3. Open duotrigordle.com. The overlay shows the next guess (first one takes a
47+
couple seconds while the solver warms up).
3748

38-
Greedy with answer-bonus, 500-game realistic (distinct-answer) bench:
49+
## Build the C++ core (optional)
3950

40-
- 100% solve rate (under the Daily 37-guess threshold)
41-
- Mean: ~33.8 guesses
42-
- Range: 33–35
51+
Only needed for benching / the native worker:
4352

53+
```
54+
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
55+
ninja -C build
56+
./build/dt_bench -n 2000 -S greedy --opener LITRE -a 300 --la-k 12 --la-exact \
57+
--value-net data/value_net_20260527.bin
58+
```
4459

45-
Headroom over greedy at turn 2 is ~0.35 guesses, the turn-2 specialist (ml/train_turn2.py) captures a small slice of
46-
that. Late game is essentially optimal under greedy.
60+
Needs cmake, ninja, OpenMP, GoogleTest, nlohmann_json
61+
(`sudo apt install cmake ninja-build libgtest-dev nlohmann-json3-dev`).

core/server/worker_main.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "feedback.hpp"
22
#include "game_state.hpp"
33
#include "strategy.hpp"
4+
#include "value_net.hpp"
45
#include "wordlists.hpp"
56

67
#include <nlohmann/json.hpp>
@@ -165,6 +166,18 @@ json process_request(const dt::Wordlists& w, const json& req) {
165166

166167
dt::GreedyStrategy strat(w, alpha);
167168

169+
// Tail-averse value-net tie-break for auto mode (matches the browser bot):
170+
// re-rank the near-tied top candidates by predicted remaining turns, which
171+
// caps games at 35. Loaded once; skipped silently if the file is absent or
172+
// in perfect mode (which must stay restricted to answer-only guesses).
173+
static dt::ValueNet g_value_net;
174+
static bool g_net_ready = g_value_net.load(std::string(DT_DATA_DIR) + "/value_net_20260527.bin");
175+
if (g_net_ready && mode != "perfect") {
176+
strat.set_value_net(&g_value_net);
177+
strat.set_lookahead(12, 5);
178+
strat.set_lookahead_exact(true);
179+
}
180+
168181
std::vector<int> active_idx;
169182
for (int b = 0; b < dt::NUM_BOARDS; ++b) {
170183
if (!state.boards[b].solved && !state.boards[b].candidates.empty()) {
@@ -199,6 +212,16 @@ json process_request(const dt::Wordlists& w, const json& req) {
199212
}
200213
}
201214

215+
// In auto mode, the headline pick is the strategy's actual decision (which
216+
// applies the exact-V tie-break + endgame guards); promote it to the front.
217+
if (g_net_ready && mode != "perfect" && !active_idx.empty() && state.guesses_used > 0) {
218+
dt::WordIdx best = strat.choose_guess(state);
219+
auto it = std::find(top.begin(), top.end(), best);
220+
if (it != top.end()) top.erase(it);
221+
top.insert(top.begin(), best);
222+
if (static_cast<int>(top.size()) > top_k) top.resize(top_k);
223+
}
224+
202225
json suggestions = json::array();
203226
for (dt::WordIdx g : top) {
204227
json sug;

0 commit comments

Comments
 (0)