Last active
February 10, 2022 15:39
-
-
Save PJensen/24af8a21cb9de78c89c24b6a74d7dc24 to your computer and use it in GitHub Desktop.
Solves Wordle Puzzles with Input from User
This file contains hidden or 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
from functools import reduce | |
import os, sys, time, string | |
from turtle import down | |
import urllib.request | |
import urllib | |
class WordleSolver: | |
""" | |
Solves Wordle Puzzles with feedback from the user | |
https://www.powerlanguage.co.uk/wordle/ | |
""" | |
def __init__(self, wordLength) -> None: | |
"""Initializes the solver targeting a specific word length""" | |
self._wordLength = wordLength | |
self._allWords = self._initializeWordList(wordLength) | |
self._allWordCount = len(self._allWords) | |
print('loaded ' + str(len(self._allWords)) + ' ' + str(wordLength) + ' letter words') | |
self._initializeState() | |
def _initializeState(self): | |
"""Initializes or re-initializes to the starting state""" | |
self._words = set(self._allWords) | |
self._correct = {} | |
self._incorrect = list() | |
self._badLetters = set() | |
self._goodLetters = set() | |
assert self._allWordCount == len(self._words) | |
def _initializeWordList(self, length): | |
"""Initializes the word list by fetching all n length words""" | |
downloadUrl = "https://gist.githubusercontent.com/cfreshman/a03ef2cba789d8cf00c08f767e0fad7b/raw/5d752e5f0702da315298a6bb5a771586d6ff445c/wordle-answers-alphabetical.txt" | |
urllib.request.urlretrieve(downloadUrl, "words_alpha.txt") | |
with open("words_alpha.txt", "r") as fp: | |
return set([word for word in list(fp.read().split()) if len(word) == length]) | |
@property | |
def _correct_string(self): | |
"""Returns the correction string in a more readable way""" | |
tmpReturn = '' | |
for i in range(0, self._wordLength): | |
if i in self._correct: | |
tmpReturn += self._correct[i] | |
else: | |
tmpReturn += '_' | |
return tmpReturn | |
@property | |
def _remainingWordCount(self): | |
"""The number of words that are remaining in the word list""" | |
return len(self._words) | |
@property | |
def _percentWordsRuledOut(self): | |
"""The percentage of words that have been ruled out""" | |
return 1 - self._remainingWordCount / self._allWordCount | |
def _applyCorrection(self, originalWord, positionallyCorrect, positionallyIncorrect): | |
"""Applies a correction to the internal representation of the solver the | |
first argument is the word, the 2nd and 3rd arguments are the correction | |
data. Correction data should be entered as _ to represent misses.""" | |
if positionallyCorrect == '': | |
positionallyCorrect = '_' * self._wordLength | |
if positionallyIncorrect == '': | |
positionallyIncorrect = '_' * self._wordLength | |
if len(positionallyCorrect) > self._wordLength: | |
raise Exception('correction too long') | |
if len(positionallyCorrect) != len(positionallyIncorrect): | |
raise Exception('invalid correction') | |
for i in range(0, self._wordLength): | |
wordLetter = originalWord[i] | |
correctLetter = positionallyCorrect[i] | |
incorrectLetter = positionallyIncorrect[i] | |
# a letter that is in the right spot | |
if correctLetter != '_': | |
self._correct[i] = correctLetter | |
self._goodLetters.add(correctLetter) | |
# the right letter in the wrong spot, record it | |
# both by position and by letter generally | |
if incorrectLetter != '_': | |
self._incorrect.append((i, incorrectLetter)) | |
self._goodLetters.add(incorrectLetter) | |
# it wasn't positionnally wrong and it wasn't right | |
# it's a bad letter and cannot show up in the word | |
if correctLetter == incorrectLetter and correctLetter == '_': | |
self._badLetters.add(wordLetter) | |
def _applyProximalMatches(self): | |
"""Once the correction data has been applied, the next step is to | |
use the correction data to remove words that have been ruled out""" | |
removeWords = set() | |
for word in self._words: | |
# handle positionally incorrect letters | |
# this is the case where we know there is a letter | |
# but the positio is wrong | |
for pair in self._incorrect: | |
position = pair[0] | |
letter = pair[1] | |
# the letter is NOT in the right position | |
if word[position] == letter: | |
removeWords.add(word) | |
break | |
# the letter is NOT in the word at all | |
elif letter not in word: | |
removeWords.add(word) | |
break | |
# handle positionally correct letters | |
# this is the case where we know a letter | |
# is in a specific position | |
for position in self._correct.keys(): | |
if self._correct[position] != word[position]: | |
removeWords.add(word) | |
break | |
# remove words that do not have REQUIRED letters | |
for requiredLetter in self._correct.values(): | |
if requiredLetter not in word: | |
removeWords.add(word) | |
break | |
# remove ALL words with ANY bad letter | |
for badLetter in self._badLetters: | |
if badLetter in word: | |
removeWords.add(word) | |
break | |
# remove words and clear out the working set | |
self._words.difference_update(removeWords) | |
def _input_guess(self): | |
"""Prompts the user to enter their guess""" | |
print('enter guess:') | |
return input() | |
def _input_correction(self): | |
"""Enter the letters that are in the right position""" | |
print('positionally correct:') | |
return input() | |
def _input_incorrection(self): | |
"""Enter the letters that are in the wrong position""" | |
print('positionally incorrect:') | |
return input() | |
def _noMatchWordMask(self): | |
return '_' * self._wordLength | |
def getWordScore(self, word): | |
noMatchWordMask = self._noMatchWordMask() | |
self._applyCorrection(word, noMatchWordMask, noMatchWordMask) | |
self._applyProximalMatches() | |
tmpReturn = self._percentWordsRuledOut | |
self._initializeState() | |
return tmpReturn | |
def findBestAndWorstStartingWords(self): | |
bestWord = '' | |
bestWordPercent = 0 | |
worstWord = '' | |
worstWordPercent = 1 | |
for currentWord in self._allWords: | |
wordScore = self.getWordScore(currentWord) | |
# searching for worst possible words | |
if wordScore <= worstWordPercent: | |
worstWord = currentWord | |
worstWordPercent = wordScore | |
print('Worst: "' + worstWord + '", ' + str(worstWordPercent)) | |
# searching for best possible words | |
if wordScore >= bestWordPercent: | |
bestWord = currentWord | |
bestWordPercent = wordScore | |
print('Best: "' + bestWord + '", ' + str(bestWordPercent)) | |
self._initializeState() | |
return ((worstWord, worstWordPercent), (bestWord, bestWordPercent)) | |
def run(self): | |
"""Runs the Wordle Solver""" | |
self.showHelp() | |
while True: | |
self._applyCorrection(self._input_guess(), self._input_correction(), self._input_incorrection()) | |
self._applyProximalMatches() | |
print(self) | |
def __repr__(self) -> str: | |
"""Internal state representation of the Solver""" | |
tmpReturn = '' | |
tmpReturn = tmpReturn + 'words: ' + str(self._words) + '\r\n' | |
tmpReturn = tmpReturn + 'correct positions: ' + str(self._correct) + '\r\n' | |
tmpReturn = tmpReturn + 'correct: ' + str(self._correct_string) + '\r\n' | |
tmpReturn = tmpReturn + 'incorrect positions:' + str(self._incorrect) + '\r\n' | |
tmpReturn = tmpReturn + 'bad letters: ' + str(self._badLetters) + '\r\n' | |
tmpReturn = tmpReturn + 'good letters: ' + str(self._goodLetters) + '\r\n' | |
tmpReturn = tmpReturn + 'alphabet ruled out percent: ' + str(len(self._badLetters) / len(string.ascii_lowercase)) + '\r\n' | |
tmpReturn = tmpReturn + 'words ruled out percent: ' + str(self._percentWordsRuledOut) + '\r\n' | |
tmpReturn = tmpReturn + 'words remaining: ' + str(self._remainingWordCount) + '\r\n' | |
return tmpReturn | |
def showHelp(self): | |
"""Shows how to use WordleSolver""" | |
print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=') | |
print('WordleSolver') | |
print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=') | |
print('') | |
print('Each round you will be asked three questions') | |
print('') | |
print('1. The guess') | |
print('2. The correct letters and their positions') | |
print('3. The incorrect but present letters and their positions') | |
print('') | |
print('Enter corrections using underlines for missed letters _____ ') | |
print('') | |
print('Examples of corrections:') | |
print('') | |
print('// The "e" is green in wordle') | |
print('correction: ____e') | |
print('') | |
print('// The "s" is yellow in wordle') | |
print('incorrect: s____') | |
print('') | |
print('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=') | |
print('') | |
wordleSolver = WordleSolver(5) | |
wordleSolver.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment