-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e56ee2c
Showing
14 changed files
with
13,259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__/ | ||
.vscode/ | ||
.pytest_cache/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Wordle Solver | ||
|
||
Run with `python wordle_beater.py`. | ||
|
||
This hardmode wordle solver takes the following approach: | ||
|
||
1. Figure out, for the target list of words, what the frequency of letters per positions 1-5 is. | ||
2. Pick the word that is most likely to get new green letters based on the frequency of letters in each slot. | ||
3. Use the results from the new guess to prune the target list to only words that are possible given all previous guesses. | ||
4. Repeat | ||
|
||
# For devs | ||
|
||
`./lib/words.py` is a python formatted set of all target words plus all extra possible guesses. | ||
|
||
Testing is minimal, run pytest. Tested with python 3.9. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .words import targetlist, guesslist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .frequency.frequency import FrequencyGame |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from ..game import Game | ||
from ..prune import prune_wordlist | ||
from ...words import targetlist, guesslist | ||
import string | ||
|
||
zero_alpha = {k: 0 for k in string.ascii_lowercase} | ||
|
||
|
||
def make_indexes() -> dict: | ||
return {k + 1: zero_alpha.copy() for k in range(5)} | ||
|
||
|
||
class FrequencyGame(Game): | ||
def __init__(self, **kwargs): | ||
expanded_list_guesses = kwargs.get("expanded_list_guesses") | ||
if not expanded_list_guesses and not expanded_list_guesses == 0: | ||
expanded_list_guesses = 2 | ||
kwargs["expanded_list_guesses"] = expanded_list_guesses | ||
self.expanded_list_guesses = expanded_list_guesses | ||
super().__init__(**kwargs) | ||
|
||
def __str__(self): | ||
return super().__str__() | ||
|
||
def letter_frequency_in_position(self, wordlist) -> dict: | ||
indexes = make_indexes() | ||
|
||
for word in wordlist: | ||
for idx, letter in enumerate(word): | ||
indexes[idx + 1][letter] += 1 | ||
|
||
return indexes | ||
|
||
def get_candidate(self, possible_guesses) -> str: | ||
""" | ||
Return best candidate for next guess. | ||
""" | ||
|
||
indexes = self.letter_frequency_in_position(self.targetlist) | ||
|
||
max_word = "" | ||
max_word_score = 0 | ||
|
||
for word in possible_guesses: | ||
word_score = 0 | ||
for idx, letter in enumerate(word): | ||
word_score += indexes[idx + 1][letter] | ||
if word_score > max_word_score: | ||
max_word_score = word_score | ||
max_word = word | ||
return max_word, max_word_score | ||
|
||
def run(self, target): | ||
super().run() | ||
self.targetlist = targetlist | ||
self.guesslist = set.union(targetlist, guesslist) | ||
for _ in range(self.expanded_list_guesses): | ||
self.targetlist = prune_wordlist(self.guesses, self.targetlist) | ||
self.guesslist = prune_wordlist(self.guesses, self.guesslist) | ||
candidate, score = self.get_candidate(self.guesslist) | ||
guess = self.guess(candidate, target) | ||
if guess.won: | ||
return | ||
self.targetlist = targetlist | ||
for _ in range(6 - self.expanded_list_guesses): | ||
self.targetlist = prune_wordlist(self.guesses, self.targetlist) | ||
candidate, score = self.get_candidate(self.targetlist) | ||
guess = self.guess(candidate, target) | ||
if guess.won: | ||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import wordle | ||
|
||
|
||
class Game: | ||
def __init__(self, **kwargs): | ||
self.kwargs = kwargs | ||
self.guess_func = wordle.play | ||
|
||
def run(self): | ||
self.guesses = [] | ||
|
||
def guess(self, word, target): | ||
guess = self.guess_func(word, target) | ||
self.guesses.append(guess) | ||
return guess | ||
|
||
def __str__(self) -> str: | ||
return f"{self.__name__}(**{self.kwargs})" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from .validate import validate | ||
|
||
|
||
def prune_wordlist(guesses, wordlist) -> set: | ||
if len(guesses) == 0: | ||
return wordlist | ||
guess_words = [guess.word for guess in guesses] | ||
|
||
possibles = set() | ||
|
||
for word in wordlist: | ||
if word in guess_words: | ||
continue | ||
if validate(word, guesses): | ||
possibles.add(word) | ||
|
||
return possibles |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import string | ||
|
||
zero_alpha = {k: 0 for k in string.ascii_lowercase} | ||
five_alpha = {k: 5 for k in string.ascii_lowercase} | ||
|
||
def validate(word, guesses): | ||
# Get a dict of what letters exist in the word | ||
word_alphabet = zero_alpha.copy() | ||
for char in word: | ||
word_alphabet[char] += 1 | ||
|
||
for guess in guesses: | ||
# Set the minimum and max amount of letters for each character | ||
# That could exist in the word | ||
alphabet_min = zero_alpha.copy() | ||
for idx, char in enumerate(guess.pattern): | ||
validated_word_char = word[idx] | ||
guess_word_char = guess.word[idx] | ||
# if the char is green and the word doesn't match, not valid | ||
if char == 2 and validated_word_char != guess_word_char: | ||
return False | ||
|
||
# if the char is yellow and the word does match, not valid | ||
if char == 1 and validated_word_char == guess_word_char: | ||
return False | ||
|
||
# if green or yellow, the alphabet minimum is one more than current | ||
if char in [2,1]: | ||
alphabet_min[guess_word_char] += 1 | ||
|
||
alphabet_max = five_alpha.copy() | ||
for idx, char in enumerate(guess.pattern): | ||
guess_word_char = guess.word[idx] | ||
if char == 0: | ||
alphabet_min_for_guess_char = alphabet_min[guess_word_char] | ||
if alphabet_min_for_guess_char > 0: | ||
alphabet_max[guess_word_char] = alphabet_min_for_guess_char | ||
else: | ||
alphabet_max[guess_word_char] = 0 | ||
for char, amount in word_alphabet.items(): | ||
if amount > alphabet_max[char]: | ||
return False | ||
if amount < alphabet_min[char]: | ||
return False | ||
return True |
Oops, something went wrong.