Skip to content

Instantly share code, notes, and snippets.

@PJensen
Last active February 10, 2022 15:39
Show Gist options
  • Save PJensen/24af8a21cb9de78c89c24b6a74d7dc24 to your computer and use it in GitHub Desktop.
Save PJensen/24af8a21cb9de78c89c24b6a74d7dc24 to your computer and use it in GitHub Desktop.
Solves Wordle Puzzles with Input from User
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