This commit is contained in:
JISAUAY 2025-06-24 09:59:51 -05:00
commit 15094850f3
4 changed files with 15086 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
__pycache__
.vscode
word_scores.txt

180
WordleSolver.py Normal file
View File

@ -0,0 +1,180 @@
import math
import re
with open('wordle-words.txt', 'r') as file:
WORD_LIST = [ word.strip() for word in file.readlines() ]
class Letter:
char: str # character
pos: int = -1 # -1 is unknown
guessed: list[int] = [] # positions that were yellow
found: bool = False
def __init__(self, char: str, pos: int, found: bool = False):
self.char = char
self.guessed = []
if found:
self.pos = pos
else:
self.guessed.append(pos)
self.found = found
def __str__(self) -> str:
return (f"Letter(char='{self.char}', pos={self.pos}, "
f"guessed={self.guessed}, found={self.found})")
class WordleSolver:
known_letters: list[str] = []
word: str = '?????'
contains: list[Letter] = []
doesnt_contain: set[str] = set()
possible_words: list[str] = WORD_LIST
scores: list[tuple[str, int]] = []
def __init__(self):
self.known_letters = []
self.word = '?????'
self.contains = []
self.doesnt_contain = set()
self.possible_words = WORD_LIST
self.scores = []
# How many words does this guess rule out
def score_word(self, guess: str) -> int:
letters = set(guess)
ruled_out = 0
for word in self.possible_words:
letters_in_word = False
for letter in letters:
if letter in word:
# print(f'{letter} in {word}')
letters_in_word = True
break
if letters_in_word:
ruled_out += 1
return ruled_out
def score_words(self) -> None:
self.scores = []
for word in self.possible_words:
self.scores.append((word, self.score_word(word)))
# Sort scores by the score value (descending)
self.scores.sort(key=lambda x: x[1], reverse=True)
def add_guess(self, guess: str, colors: str) -> None:
# Pair each letter/color with its original index
zipped = list(zip(guess, colors, range(len(guess))))
order = {'g': 0, 'y': 1, 'b': 2}
zipped_sorted = sorted(zipped, key=lambda x: order[x[1]])
guess_sorted = ''.join([x[0] for x in zipped_sorted])
colors_sorted = ''.join([x[1] for x in zipped_sorted])
original_indices = [x[2] for x in zipped_sorted]
# Now use guess_sorted, colors_sorted, and original_indices for processing
for i, color in enumerate(colors_sorted):
letter = guess_sorted[i]
orig_idx = original_indices[i]
match color:
case 'y':
editted_existing = False
for containie in self.contains:
if containie.char == letter and not containie.found:
containie.guessed.append(orig_idx)
editted_existing = True
if not editted_existing:
self.contains.append(Letter(letter, orig_idx))
case 'b':
self.doesnt_contain.add(letter)
case 'g':
editted_existing = False
for containie in self.contains:
if containie.char == letter:
if containie.found and containie.pos == orig_idx:
editted_existing = True
break
containie.pos = orig_idx
containie.found = True
editted_existing = True
if not editted_existing:
self.contains.append(Letter(letter, orig_idx, found=True))
self.prune_words()
def prune_words(self) -> None:
# Build regex pattern for green and yellow constraints
pattern = ''
for i in range(5):
letter_at_pos = None
for letter in self.contains:
if letter.found and letter.pos == i:
letter_at_pos = letter.char
break
if letter_at_pos:
pattern += letter_at_pos
else:
# Exclude yellow letters at this position
forbidden = [l.char for l in self.contains if i in l.guessed]
if forbidden:
pattern += f"[^{''.join(forbidden)}]"
else:
pattern += '.'
regex = re.compile(f"^{pattern}$")
filtered = []
for word in self.possible_words:
# Exclude words with black letters
if any(b in word for b in self.doesnt_contain):
continue
# Must match green/yellow regex
if not regex.match(word):
continue
# Each yellow letter must be present somewhere (not just at forbidden positions)
valid = True
for letter in self.contains:
if letter.guessed:
# Count how many times this letter should appear (yellow + green)
required_count = len(letter.guessed)
if letter.found:
required_count += 1
if word.count(letter.char) < required_count:
valid = False
break
# Not at forbidden positions
for pos in letter.guessed:
if word[pos] == letter.char:
valid = False
break
if valid:
filtered.append(word)
self.possible_words = filtered
ws = WordleSolver()
ws.add_guess('naieo', 'bbgyb')
ws.add_guess('beige', 'bygbg')
# # ws.add_guess('blowy', 'byybg')
for letter in ws.contains:
print(letter)
ws.score_words()
with open('word_scores.txt', 'w') as file:
for word, score in ws.scores:
file.write(f'{word},{score}\n')

47
test.py Normal file
View File

@ -0,0 +1,47 @@
from WordleSolver import WordleSolver
with open('wordle-words.txt', 'r') as file:
WORD_LIST = [ word.strip() for word in file.readlines() ]
def score_word(correct_word: str, guess: str) -> str:
r = ''
for i, letter in enumerate(guess):
if letter == correct_word[i]:
r += 'g'
elif letter in correct_word:
r += 'y'
else:
r += 'b'
return r
guesses: list[int] = []
for correct_word in WORD_LIST:
ws = WordleSolver()
guess_count: int = 0
while True:
guess_count += 1
ws.score_words()
print(ws.scores)
word_to_guess: str = ws.scores[0][0]
print(f'Guessing word {word_to_guess}')
score = score_word(correct_word, word_to_guess)
print(f'{score=}')
if score == 'ggggg':
print(f'Got word "{correct_word}" in {guess_count} guesses!')
break
elif guess_count > 6:
break
ws.add_guess(word_to_guess, score)
average = sum(guesses) / len(guesses)
print(f'Average Guesses: {average}')

14855
wordle-words.txt Normal file

File diff suppressed because it is too large Load Diff