After many years away from writing in Python, I am getting back into it. Specifically, I am trying to teach myself dataclasses. As an exercise I wrote a Wordle-solving program. I downloaded the list of possible Wordle solutions and began writing.
There are two functions. The first simply asks for a five-letter string from the user that indicates what the result of our last guess was. "b" for a bad letter, "y" for a yellow letter, "g" for a green letter. It then updates our list of "bad" and "yellow" letters accordingly. If we get a 'ggggg' result indicating we've guessed the word successfully, we simply stop.
The second function loops through the list of Wordle solutions and tests them against the results we've gathered so far and produces a new guess.
I struggled a long time with this second function. Does the current word contain all "yellow" letters but not in all the positions those individual letters have been tried? Does the current word have all the right "green" letters? Is it absent of all letters that have already been deemed "bad"?
I eventually came up with the idea of raising an exception every time our guess fails a test. If we get through the loop and no exceptions have been raised, we have a new guess.
I'm not entirely happy with this and would welcome suggestions on how to make a better testing loop. Also, any other comments you have on my code, constructive or otherwise, I'd love to hear.
#!/usr/bin/python3
"""Generages guesses for the daily Wordle puzzle."""
import sys
from dataclasses import dataclass
from re import match
def get_result(last_results):
"""Find out from the user what the result of our last guess was."""
# Keep asking for a result until we get a response that is five characters long
# and consists of only the letters b, g, and y
last_results.result = input("Result: ")
while not (
match("^[ygb]+$", last_results.result) and len(last_results.result) == 5
):
last_results.result = input("Result: ")
# Five g's means we've solved it
if last_results.result == "ggggg":
sys.exit()
# Iterate over the result. Every letter that got a "b" is added to the bad
# letters string. A letter that got a "y" gets added to the yellow letters
# dict, along with what position it was tried in.
for position in range(5):
if last_results.result[position] == "b":
last_results.bad += last_results.guess[position]
elif last_results.result[position] == "y":
last_results.yellow.setdefault(last_results.guess[position], []).append(
position
)
return last_results
def new_guess(last_guess):
"""Generate a new guess based on the results of our previous guesses."""
# Create a class that allows us to cleanly break out of the loop if
# we discover this word doesn't meet the criteria we're looking for.
class NextWord(Exception):
"""Do-nothing class that simply catches an exception"""
try_next_word = NextWord()
while True:
word = words.pop(0)
try:
for position in range(5):
# Is this letter in our bad letters list?
bad = word[position] in last_guess.bad
# Is this letter in our list of yellow letters?
yellow = word[position] in last_guess.yellow.keys()
# Have we already tried this yellow letter in this position?
tried_here = yellow and position in last_guess.yellow.get(
word[position]
)
# Was the last result for this position "g"?
green = last_guess.result[position] == "g"
# Does the letter in this position match what was in this position
# in our last guess?
matching = word[position] == last_guess.guess[position]
if bad and not green and not yellow:
raise try_next_word
if green and not matching:
raise try_next_word
if yellow and tried_here:
raise try_next_word
# Make certain all our yellow letters are in this word
for yellow_letter in last_guess.yellow.keys():
if yellow_letter not in word:
raise try_next_word
# If this word hasn't failed any of the above tests, we have a new guess
last_guess.guess = word
break
# If the "try_next_word" exception was raised during the above tests,
# continue on to the next word
except NextWord:
continue
return last_guess
with open("/home/localr/coding/5lw.txt", encoding="utf-8") as word_source:
word_list = word_source.read()
words = word_list.split()
@dataclass
class ResultsOfGuesses:
"""Class for keeping track of our current guess, the last result
of our guess, and which letters we've used in our guesses."""
guess: str
result: str
bad: str
yellow: dict
# First guess is always 'dealt'
results = ResultsOfGuesses("dealt", "", "", {})
# Get the result from testing our guess and generate a new guess
while True:
print(results.guess)
results = get_result(results)
results = new_guess(results)