{"id":16528365,"url":"https://github.com/danvk/quordlebot","last_synced_at":"2025-08-21T14:10:05.687Z","repository":{"id":66069060,"uuid":"514270245","full_name":"danvk/quordlebot","owner":"danvk","description":"Like WordleBot, but for Quordle","archived":false,"fork":false,"pushed_at":"2023-01-25T19:15:29.000Z","size":374,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-04T04:28:00.017Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danvk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-15T12:57:35.000Z","updated_at":"2023-01-03T23:59:38.000Z","dependencies_parsed_at":"2023-03-17T09:45:13.943Z","dependency_job_id":null,"html_url":"https://github.com/danvk/quordlebot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danvk/quordlebot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fquordlebot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fquordlebot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fquordlebot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fquordlebot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danvk","download_url":"https://codeload.github.com/danvk/quordlebot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Fquordlebot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271493232,"owners_count":24769117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T17:40:02.317Z","updated_at":"2025-08-21T14:10:05.610Z","avatar_url":"https://github.com/danvk.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# QuordleBot\n\nLike WordleBot, but for Quordle. How can I get it in six?\n\n## Usage\n\nInstall / generate lookup tables:\n\n    python3 -m venv venv\n    pip install -r requirements.txt\n\n    ./generate_lookup_table.py  # takes ~4 minutes\n    ./generate_array.py\n    ls -lh words/map.pickle words/array.pickle\n\nFind the best next play:\n\n    ./quordlebot.py NYLON,SHELF,VIRAL,BUDGE ROAST\n    ROAST ['.y...', '...y.', 'y.y..', '.....'] -\u003e [76, 94, 73, 266] = 138722192 +17.66 bits\n    Best next plays based on expected information gain:\n    CLINE -\u003e +20.00 bits\n    INCLE -\u003e +19.36 bits\n    CLIPE -\u003e +19.29 bits\n    CLIME -\u003e +19.26 bits\n\nFind the best plays absent any feedback:\n\n    ./priors.py\n    # (takes many hours to run!)\n\n## Notes\n\nThe word list is inlined into the Quordle JS:\n\n```json\n{\n  \"blacklist\": \"GYPSY GIPSY MAMMY AGORA SLAVE HUSSY JUNTA JUNTO WELCH MORON\",\n  \"wordBank\": \"ABACK ABASE ABATE...\",\n  \"allowed\": \"AAHED AALII AARGH...\"\n}\n```\n\n- blacklist: 10 words\n- wordBank: 2,315 words\n- allowed: 10,657 words\n\nI assume \"wordBank\" is the words that can be answers and \"allowed\" is the words you're allowed to guess. Unclear to me exactly how \"blacklist\" works, these words appear in \"wordBank\" but not \"allowed\".\n\nAssuming there are never dupes, that means there are\n\n    2314 * 2314 * 2313 * 2312 = 28634537426976 solutions\n    log2(28634537426976) = 44.7 bits of entropy\n\nSo maybe a good start is to look at which guesses maximize information gain for any given state.\n\nBest and worst first words according to pure information gain:\n\n```\n[(5.885960110378853, 'SOARE'), (5.882779324291968, 'ROATE'), (5.865709709951878, 'RAILE'), (5.865457142861989, 'REAST'), (5.834581525865644, 'SALET'), (5.817161175806047, 'ORATE'), (5.792668103557574, 'CARTE'), (5.786709827456228, 'RAINE'), (5.775166510376183, 'ARIEL'), (5.774382034176954, 'CARET')]\n[(2.2851367360614, 'GYPPY'), (2.267954317843193, 'KUDZU'), (2.2436395107418843, 'FUFFY'), (2.2341699553948438, 'JAFFA'), (2.2141711564276, 'PZAZZ'), (2.206893724562269, 'YUKKY'), (2.192000567958077, 'XYLYL'), (2.0525999694165993, 'IMMIX'), (2.0383091074888515, 'JUJUS'), (1.8918361327315534, 'QAJAQ')]\n```\n\nI remember `ROATE`!\n\nI see no reason to use a different first word for Quordle. What about as a second word? Presumably it depends on the patterns you get out.\n\nWith optimal information gain play, my quordlebot solves today's in seven, which is the same number of guesses that I used.\n\nHere's its sequence:\n\n    ./quordlebot.py SOARE,y..y.,..y..,..g..,g.yy. CLIPT,....y,.....,.g...,...yy STRAP,yyy..,...y.,...y.,ggggg DUNGY,.y...,...y.,.....,.....\n    SOARE [16, 138, 40, 8]\n    CLIPT [2, 20, 6, 1]\n    STRAP [2, 17, 6, 1]\n    DUNGY [1, 2, 1, 1]\n    Quad 0 must be TRUSS\n    Quad 1 is one of ['GAMMA', 'MAGMA']\n    Quad 2 must be LLAMA\n\nIf your goal were to get a six, I wonder if there's a better strategy than maximizing information gain?\n\nThe lists are actually disjoint! So I need to add the wordbank words to the allowed guess list.\n\n    $ two-stats words/wordbank.txt words/allowed.txt\n    A: words/wordbank.txt\n    B: words/allowed.txt\n    |A|: 2315\n    |B|: 10657\n    |A ∪ B|: 12972\n    |A ∩ B|: 0\n    |A - B|: 2315\n    |B - A|: 10657\n\nSo 12,972 guessable words total.\n\nNext steps:\n\n- [x] Rerun initial play analysis with larger list of guessable words\n- [x] Look for the best static pair of initial plays\n  - [x] Remove first word from list of seconds after examing\n  - [x] Parallelize the search\n  - [ ] Is sorting the list and then doing counts faster?\n  - [ ] Does factoring out the `result[guess1]` lookup matter?\n- [x] See if using pickle is faster/more compact than JSON (it is: 7.468s-\u003e5.060s for quordlebot.py)\n- [x] Print how many bits of information were gained by your actual guesses\n- [ ] Explore the game tree to find strategies to minimize expected remaining guesses\n  - [x] Expected number of remaining guesses doesn't seem right?\n  - [x] Sort un-forced guesses by information gain\n  - [ ] Prune by \"best so far\"; but when can you bail out if you're looking for expected number of plays?\n  - [x] Only keep track of optimal number in the cases where that's what we care about\n- [x] Ignore subsequent guesses after you've gotten a word\n- [ ] Switch `quordlebot.py` to use the array format\n- [x] Add a mode that takes the four words and your guesses, rather than .yg\n- [ ] Factor out a Quordle class\n- [x] ROAST / CLINE seems to always result in far more bits of information gain than `priors.py` suggests should be expected (~35 bits vs. 9.6 bits). What's going on? (It's four boards vs. one!)\n- [ ] How frequently does ROAST / CLINE / HUMID or ROAST / CLINE / DUMPY give you a guaranteed seven?\n- [ ] How often is CLINE the best second guess after ROAST?\n- [ ] Report the expected vs. actual information gain of each of your guesses\n- [ ] What are the odds of getting a six if you go for it?\n- [ ] Can Python distinguish guessable/wordbank indices via nominal types? (Sorta https://docs.python.org/3/library/typing.html#newtype)\n- [ ] Why do I have to import ResultDict to unpickle `array.pickle`?\n- [ ] Measure optimal performance\n  - [ ] What's quordlebot's expected number of guesses / distribution?\n  - [ ] How frequently does \"go for 5\" work? What does it cost you?\n- [x] Add a `--today` (or default) option that mimics Quorlde's Mersenne Twister;\n- [x] When's the next time that TRAIN / CLOSE / FILET / SONAR will be an answer?\n- [x] Add a `--no-spoilers` option\n- [ ] Improve the startup time for `quordle.py`\n- [x] Change progress printout to use a single line / progress bar\n- [ ] Print current word / performance on progress bar\n- [ ] Change all displayed numbers to be total plays (not additional plays)\n- [ ] Show expected number of remaining plays in `--no-spoilers` mode\n- [ ] Show other possibilities after you get a correct guess, even in `--no-spoilers` mode.\n\nUpdated best and worst first guesses:\n\n[(5.885960110378853, 'SOARE'), (5.882779324291968, 'ROATE'), (5.877909690821479, 'RAISE'), (5.865709709951878, 'RAILE'), (5.865457142861989, 'REAST'), (5.855775376955964, 'SLATE'), (5.834581525865644, 'SALET'), (5.832589698902821, 'CRATE'), (5.831396980440786, 'IRATE'), (5.829323821643724, 'TRACE')]\n[(2.2851367360614, 'GYPPY'), (2.267954317843193, 'KUDZU'), (2.2436395107418843, 'FUFFY'), (2.2341699553948438, 'JAFFA'), (2.2141711564276, 'PZAZZ'), (2.206893724562269, 'YUKKY'), (2.192000567958077, 'XYLYL'), (2.0525999694165993, 'IMMIX'), (2.0383091074888515, 'JUJUS'), (1.8918361327315534, 'QAJAQ')]\n\nBest first two guesses:\n\n    SOARE CLINT -\u003e +9.63 bits\n    ROAST CLINE -\u003e +9.61 bits *\n    SOCLE RIANT -\u003e +9.61 bits\n    RIANT CLOSE -\u003e +9.61 bits\n    LOAST CRINE -\u003e +9.60 bits\n    SAINT CEORL -\u003e +9.60 bits\n    TRAIL SONCE -\u003e +9.59 bits\n    ROIST LANCE -\u003e +9.58 bits\n    SONCE LIART -\u003e +9.58 bits\n    TRICE SALON -\u003e +9.58 bits\n    SARIN CLOTE -\u003e +9.58 bits\n    LIANE CROST -\u003e +9.58 bits\n    TORAN SLICE -\u003e +9.58 bits\n    TOILE CARNS -\u003e +9.57 bits\n    NOISE CLART -\u003e +9.57 bits\n    TRAIN SOCLE -\u003e +9.57 bits\n    TRINE COALS -\u003e +9.57 bits\n    TOILS CRANE -\u003e +9.57 bits\n    TOILE CRANS -\u003e +9.57 bits\n    CRANE TOILS -\u003e +9.57 bits *\n    DOILT CARSE -\u003e +9.56 bits\n\nROAST/CLINE feels somewhat not made up; CRANE/TOILS are both clearly real words.\n\nAfter playing ROAST/CLINE, there's a fully-determined word ~70% of the time.\n\nIf you only look at pairs of words from the wordbank, here are the top ones:\n\n    TRICE SALON -\u003e +9.58 bits\n    STOLE CAIRN -\u003e +9.56 bits\n    TRAIN CLOSE -\u003e +9.56 bits\n    LINER COAST -\u003e +9.55 bits\n    SLANT CRIED -\u003e +9.52 bits\n    STAIR CLONE -\u003e +9.52 bits\n    TRAIL SCONE -\u003e +9.52 bits\n    SPILT CRANE -\u003e +9.52 bits\n    SLANT PRICE -\u003e +9.51 bits\n\n\"TRICE\" feels made-up, but TRAIN / CLOSE is great.\n\nHere's a very slow run (~7h) with mixed solution / non-solution plays:\n\n    ./quordlebot.py CHAFF,LOWLY,SWORE,PAPER TRAIN CLOSE  14292.83s user 38.97s system 58% cpu 6:45:06.09 total\n    Best play by expected number of steps to complete:\n    1. 7.535 PUDGY (+5.535 plays, +6.73 bits)\n    2. 7.537 CHAMP (is solution, +5.537 plays, +5.72 bits)\n    3. 7.558 DOGMA (+5.558 plays, +5.73 bits)\n    4. 7.595 MOLDY (is solution, +5.595 plays, +5.76 bits)\n    5. 7.619 PADDY (+5.619 plays, +6.59 bits)\n    6. 7.619 GOLEM (+5.619 plays, +5.90 bits)\n    7. 7.624 DOPEY (+5.624 plays, +6.45 bits)\n    8. 7.639 GODLY (is solution, +5.639 plays, +5.06 bits)\n    9. 7.645 PYGMY (+5.645 plays, +6.64 bits)\n    10. 7.651 MADLY (+5.651 plays, +6.11 bits)\n    11. 7.654 DUMPY (+5.654 plays, +6.60 bits)\n    12. 7.663 MOGUL (is solution, +5.663 plays, +4.91 bits)\n\nAnother slow one:\n\n    ./quordlebot.py HAREM,GAILY,TRITE,KNEAD TRAIN CLOSE\n    Best play by expected number of steps to complete:\n    1. 7.537 EMPTY (+5.537 plays, +7.26 bits)\n    2. 7.600 PYGMY (+5.600 plays, +7.35 bits)\n    3. 7.631 DEPTH (+5.631 plays, +6.53 bits)\n    4. 7.641 DUMPY (+5.641 plays, +7.33 bits)\n    5. 7.706 PARTY (+5.706 plays, +6.28 bits)\n    6. 7.713 BLIMP (+5.713 plays, +6.49 bits)\n    7. 7.725 APHID (+5.725 plays, +6.77 bits)\n    8. 7.740 EMBED (+5.740 plays, +6.54 bits)\n    9. 7.744 AMPLY (+5.744 plays, +7.18 bits)\n    10. 7.749 GIPSY (+5.749 plays, +6.39 bits)\n\nIf you want to go for five, what's the best first word to play? For some of our standard openers, here's how many words are fully-determined by the opening play, or a 50/50:\n\n- ROAST: 35 / 26\n- CLINE: 30 / 28\n- TRAIN: 32 / 32\n- CLOSE: 21 / 26\n\nSo play ROAST before CLINE and TRAIN before CLOSE.\n\nWhat if you want to go for five? Some variations:\n\n- FILET / SONAR: 37 / 46, 9.276 information gain\n- FLIRT / CANOE: 32 / 52, 9.286 IG\n- SATIN / CRUEL: 35 / 38, 9.429 IG\n- TRIED / SALON: 35 / 36, 9.444 IG\n- ROAST / CLINE: 35 / 26, 9.61 IG\n- TRAIN / CLOSE: 32 / 32, 9.558 IG\n\n(Note: this only considered wordbank words for the second guess for IG.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Fquordlebot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanvk%2Fquordlebot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Fquordlebot/lists"}