init
This commit is contained in:
commit
15094850f3
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
.vscode
|
||||
|
||||
word_scores.txt
|
||||
180
WordleSolver.py
Normal file
180
WordleSolver.py
Normal 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
47
test.py
Normal 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
14855
wordle-words.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user