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