Last active
June 5, 2023 13:42
-
-
Save Starwarsfan2099/600899c8ebbe75e5b3e0bf581c38c5fa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# Quick and dirty wordle solver | |
# b is blank square, y is yellow square, g is green square. | |
from sys import argv | |
from time import time | |
# Wordle Solver class | |
class WordleSolve: | |
def __init__(self, file_name, start_word): | |
# Alphabet letters sorted by frequency | |
self.letter_freq = ["s", "e", "a", "r", "o", "i", "l", "t", "n", "u", "d", "c", "y", "p", "m", "h", "g", "b", "k", "f", "w", "v", "z", "x", "j", "q"] | |
self.letter_freq_value = [46.1, 44.8, 40.5, 30.9, 29.5, 28.1, 25, 24, 21.4, 18.6, 18.1, 15.7, 15.4, 14.6, 14.2, 13.3, 11.8, 11.5, 10.2, 7.9, 7.7, 5.2, 2.5, 2.4, 2.1, 0.9] | |
self.letter_pairs = ["th", "he", "an", "in", "er", "nd", "re", "ed", "es", "ou", "to", "ha", "en", "ea", "st", "nt", "on", "at", "hi", "as", "it", "ng", "is", "or", "et", "of", "ti", "ar", "te", "se", "me", "sa", "ne", "wa", "ve", "le", "no", "ta", "al", "de", "ot", "so", "dt", "ll", "tt", "el", "ro", "ad", "di", "ew", "ra", "ri", "sh"] | |
self.letter_pairs_freq = [33, 30.2, 18.1, 17.9, 16.9, 14.6, 13.3, 12.6, 11.5, 11.5, 11.5, 11.4, 11.1, 11, 10.9, 10.6, 10.6, 10.4, 9.7, 9.5, 9.3, 9.2, 8.6, 8.4, 8.3, 8, 7.6, 7.5, 7.5, 7.4, 6.8, 6.7, 6.6, 6.6, 6.5, 6.4, 6, 5.9, 5.7, 5.7, 5.7, 5.7, 5.6, 5.6, 5.6, 5.5, 5.5, 5.2, 5, 5, 5, 5, 5] | |
self.target_word = ["_", "_", "_", "_", "_"] | |
self.yellow_green_list = [] | |
self.not_list = [] | |
self.word_history = [] | |
self.score_list = [] | |
self.start_word = start_word | |
# Open a list of 5 letter words and save in memory | |
word_list_file = open(file_name, "r") | |
self.word_list = word_list_file.read().split() | |
word_list_file.close() | |
self.word_list_total = len(self.word_list) | |
# Print a list of words in a box like Wordle does | |
def box(self, words): | |
# Box top and bottom | |
box_top = " ___________________ " | |
box_spacer = " ------------------- " | |
output = box_top | |
for word in words: | |
output += "\n" + ' | ' + ' | '.join(list(word)) + ' | ' + "\n" + box_spacer | |
return output | |
# Get user input | |
def get_input(self): | |
text = input("Enter results: ") | |
input_list = [] | |
for letter in text: | |
input_list.append(letter) | |
return input_list | |
# Print iterations progress | |
def printProgressBar (self, iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '#', printEnd = "\r"): | |
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) | |
filledLength = int(length * iteration // total) | |
bar = fill * filledLength + '-' * (length - filledLength) | |
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd) | |
# Print New Line on Complete | |
if iteration == total: | |
print() | |
# If we know a letter is or isn't in a word, adjust the word list apropriatly | |
def letter_modify(self, letter, remove): | |
i = 0 | |
while i < len(self.word_list): | |
if remove: | |
if letter in self.word_list[i]: | |
if letter not in self.yellow_green_list: | |
del self.word_list[i] | |
del self.score_list[i] | |
continue | |
if not remove: | |
if letter not in self.word_list[i]: | |
del self.word_list[i] | |
del self.score_list[i] | |
continue | |
i += 1 | |
# If we know what place a letter is or isn't, adjust the word list apropriatly | |
def letter_place(self, place, letter, remove): | |
i = 0 | |
while i < len(self.word_list): | |
if remove: | |
if letter in self.word_list[i][place]: | |
del self.word_list[i] | |
del self.score_list[i] | |
continue | |
if not remove: | |
if letter not in self.word_list[i][place]: | |
del self.word_list[i] | |
del self.score_list[i] | |
continue | |
i += 1 | |
# Add known letters in the word to a list | |
def add_known_letters(self, letter): | |
if letter not in self.yellow_green_list: | |
self.yellow_green_list.append(letter) | |
# Add known letter not in the word to a list | |
def add_known_not_letters(self, letter): | |
if letter not in self.not_list: | |
self.not_list.append(letter) | |
# Print a few stats | |
def stats(self): | |
print("\nWordle: %s" % ' '.join(self.target_word)) | |
print("Known letters in word: %s" % ' '.join(self.yellow_green_list)) | |
print("Known letters not in word: %s" % ' '.join(self.not_list)) | |
print("{0} words remaining, down to {1:.2%} of words.\n".format(len(self.word_list), (len(self.word_list)/self.word_list_total))) | |
# Sort word list based on a letter frequency score | |
def sort_by_freq(self): | |
self.score_list = [] | |
for word in self.word_list: | |
i = 1 | |
score = 0 | |
letters = [] | |
for letter in word: | |
# Add to score for top signifigant letters by position | |
if i == 1 and letter == "s": score += 11.5 | |
if i == 2 and letter == "a": score += 12.5 | |
if i == 3 and letter == "r": score += 11.7 | |
if i == 4 and letter == "e": score += 12.5 | |
if i == 5 and letter == "s": score += 13.1 | |
if letter not in letters: | |
# Add the frequency score of a letter to the score. | |
score += float(self.letter_freq_value[self.letter_freq.index(letter)]) | |
else: | |
# If a letter is in a word more than once, reduce value of any other occurences. Magic number seems freq/5. | |
score += float(self.letter_freq_value[self.letter_freq.index(letter)])/5 | |
letters.append(letter) | |
i += 1 | |
# Now, we add a value based upon frequency of common letter pairs. | |
for pair in self.letter_pairs: | |
if pair in word: | |
score += float(self.letter_pairs_freq[self.letter_pairs.index(pair)]) | |
self.score_list.append(score) | |
# Sort the list of scores. | |
zipped_list = sorted(zip(self.score_list, self.word_list), reverse=True) | |
self.word_list = [self.word_list for score_list, self.word_list in zipped_list] | |
self.score_list = sorted(self.score_list, reverse=True) | |
return zipped_list | |
# Process input and adjust the word list starting with known letters in the target word | |
def process_guess(self, input_list, iteration): | |
# Known letter and position [Green square] | |
i = 0 | |
for letter in input_list: | |
if letter == "g": | |
self.add_known_letters(self.word_history[iteration][i]) | |
self.letter_place(i, self.word_history[iteration][i], False) | |
self.target_word[i] = self.word_history[iteration][i] | |
i +=1 | |
# Known letter but not position [Yellow square] | |
i = 0 | |
for letter in input_list: | |
if letter == "y": | |
self.add_known_letters(self.word_history[iteration][i]) | |
self.letter_place(i, self.word_history[iteration][i], True) | |
self.letter_modify(self.word_history[iteration][i], False) | |
i +=1 | |
# Known not a letter in the target word [Blank square] | |
i = 0 | |
for letter in input_list: | |
if letter == "b": | |
self.letter_modify(self.word_history[iteration][i], True) | |
self.add_known_not_letters(self.word_history[iteration][i]) | |
i += 1 | |
# Wordle logic for running tests | |
def wordle_logic(self, guess, wordle): | |
i = 0 | |
output = [] | |
for letter in guess: | |
if letter == wordle[i]: | |
output.append("g") | |
elif letter in wordle: | |
output.append("y") | |
elif letter not in wordle: | |
output.append("b") | |
else: | |
print("Something bad happened.") | |
i += 1 | |
if len(output) != 5: | |
print("Something bad happened x2.") | |
return output | |
# Automatically play Wordle for testing code, start words, etc. | |
def play_bot(solver, target_list, start_word, verbose): | |
succsess = [] | |
solver.word_list = [] | |
word_list_sorted = [] | |
score_list_sorted = [] | |
fails = 0 | |
for word in target_list: | |
solver.word_list.append(word) | |
# Sort by frequency and backup those values | |
scores = solver.sort_by_freq() | |
for word in solver.word_list: | |
word_list_sorted.append(word) | |
for score in solver.score_list: | |
score_list_sorted.append(score) | |
j = 0 | |
for wordle in target_list: | |
if verbose: solver.printProgressBar(j, len(target_list) - 1, prefix = start_word, suffix = "Done", length = 50) | |
j += 1 | |
solver.word_list = [] | |
solver.word_history = [] | |
solver.yellow_green_list = [] | |
solver.score_list = [] | |
# Copy the saved frequency values instead of calculating them again | |
for word in word_list_sorted: | |
solver.word_list.append(word) | |
for score in score_list_sorted: | |
solver.score_list.append(score) | |
i = 0 | |
for i in range(0, 6): | |
if i == 0: | |
solver.word_history.append(start_word) | |
guess = solver.wordle_logic(start_word, wordle) | |
else: | |
solver.word_history.append(solver.word_list[0]) | |
guess = solver.wordle_logic(solver.word_list[0], wordle) | |
solver.process_guess(guess, i) | |
if ''.join(guess) == "ggggg": | |
succsess.append(i + 1) | |
break | |
if len(solver.word_list) == 1: | |
succsess.append(i + 1) | |
break | |
if i == 5: | |
fails += 1 | |
if solver.word_list[0] in solver.word_history: | |
solver.word_list.pop(0) | |
count = 0 | |
for i in succsess: | |
count += i | |
return count, succsess, fails | |
# Using each word in the word list as a Wordle, try to solve it and see what the average number of guesses is | |
def test(): | |
start_word = "least" | |
successes = [] | |
fails = 0 | |
target_list = [] | |
solver = WordleSolve("5_letter_word_list.txt", start_word) | |
for word in solver.word_list: | |
target_list.append(word) | |
count, successes, fails = play_bot(solver, target_list, start_word, True) | |
print("Number of wordles: %s" % len(target_list)) | |
print("Number of succsess: %s" % len(successes)) | |
print("Average guesses for successes: %s" % (count/len(successes))) | |
print("Fails: %s" % fails) | |
# Using each word in the word list as a Wordle, try to solve it and see what the average number of guesses is | |
def test_start_words(): | |
averages = [] | |
target_list = [] | |
start_word_list =[] | |
start_word = "least" | |
solver = WordleSolve("5_letter_word_list.txt", start_word) | |
for word in solver.word_list: | |
start_word_list.append(word) | |
for word in solver.word_list: | |
target_list.append(word) | |
for start_word in start_word_list: | |
successes = [] | |
fails = 0 | |
count, successes, fails = play_bot(solver, target_list, start_word, True) | |
averages.append((count/len(successes), start_word)) | |
result = sorted(averages) | |
print("The best start word is %s with a average of %f Wordle guesses." % (result[0][1], result[0][0])) | |
# Solve a Wordle with a user | |
def play(): | |
start_word = "least" | |
solver = WordleSolve("5_letter_word_list.txt", start_word) | |
solver.stats() | |
print("Begin with:") | |
scores = solver.sort_by_freq() | |
print(solver.box([start_word])) | |
solver.word_history.append(start_word) | |
i = 0 | |
for i in range(0, 6): | |
user_input = solver.get_input() | |
solver.process_guess(user_input[:5], i) | |
solver.stats() | |
print("Top words:") | |
try: | |
for x in range(0, 4): | |
print("""%s (Freq score: %.2f)""" % (solver.word_list[x], round(solver.score_list[x], 2))) | |
except Exception as e: | |
pass | |
if len(solver.word_list) == 1: | |
if ''.join(user_input[:5]) == "ggggg": | |
print(solver.box(solver.word_history)) | |
else: | |
print(solver.box(solver.word_history + [solver.word_list[0]])) | |
print("Done! Hooray!!") | |
exit() | |
if solver.word_list[0] in solver.word_history: | |
solver.word_list.pop(0) | |
print(solver.box(solver.word_history + [solver.word_list[0]])) | |
solver.word_history.append(solver.word_list[0]) | |
print("Oof. Good luck picking final word!") | |
# Main | |
if __name__ == "__main__": | |
startTime = time() | |
if len(argv) == 1: | |
print("Try again.") | |
elif argv[1] == "test": | |
test() | |
elif argv[1] == "play": | |
play() | |
elif argv[1] == "test_start_words": | |
test_start_words() | |
else: | |
print("Try again.") | |
executionTime = (time() - startTime) | |
print('Time: ' + str(executionTime)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment