Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlockwood committed Jan 23, 2022
0 parents commit e56ee2c
Show file tree
Hide file tree
Showing 14 changed files with 13,259 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__pycache__/
.vscode/
.pytest_cache/
16 changes: 16 additions & 0 deletions README.md
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 added conftest.py
Empty file.
1 change: 1 addition & 0 deletions lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .words import targetlist, guesslist
1 change: 1 addition & 0 deletions lib/games/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .frequency.frequency import FrequencyGame
Empty file added lib/games/frequency/__init__.py
Empty file.
70 changes: 70 additions & 0 deletions lib/games/frequency/frequency.py
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
18 changes: 18 additions & 0 deletions lib/games/game.py
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})"
17 changes: 17 additions & 0 deletions lib/games/prune.py
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
45 changes: 45 additions & 0 deletions lib/games/validate.py
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
Loading

0 comments on commit e56ee2c

Please sign in to comment.