Skip to content

Instantly share code, notes, and snippets.

@jakelevi1996
Last active February 22, 2022 10:25
Show Gist options
  • Select an option

  • Save jakelevi1996/2fa6b080b2eb569fe9e737b24dd0d09f to your computer and use it in GitHub Desktop.

Select an option

Save jakelevi1996/2fa6b080b2eb569fe9e737b24dd0d09f to your computer and use it in GitHub Desktop.
Programs for making good word choices in Wordle

Programs for making good word choices in Wordle

This Gist contains a couple of Python scripts for making good word choices in the word-game Wordle.

The first script is dumb_wordle.py. It suggests what words to guess without taking into account the information returned by Wordle after each guess. According to this script, the 4 best initial guesses are "raise" (or alternatively "arise"), "clout", "nymph", and "badge". The first 2 guesses are good because they hit all 5 vowels, and the top 4 most common letters to appear in 5 letter words. This script uses the text file "words_alpha.txt" which can be downloaded from this GitHub repository, takes up 4.03 MB of disk space, and contains 370,103 words between 1 and 31 letters long (the only word in this list which is 31 letters long is "dichlorodiphenyltrichloroethane")

The second script is wordle_guesser.py. It suggests what words to guess, but also takes into account the information returned by Wordle after each guess, which has to be manually entered in the correct format in the main function. This script can guess the word "abbey" in 3 guesses: "alert" -> "abode" -> "abbey". This script uses the text file "wordle-answers-alphabetical.txt" which can be downloaded from here, takes up 14 KB of disk space, and contains all 2315 of the 5 letter words that are allowed as answers in Wordle.

By replacing make_wordle_guess_any with make_wordle_guess_restricted in the main function, it is possible to restrict word choices to only possible solutions (IE words with correct letters in correct places and not incorrect places, and no incorrect letters).

This script also has a simulate_game function, which can be used to simulate games of Wordle starting from a given true word, EG calling simulate_game("tangy") outputs "arise" -> "count" -> "glyph" -> "tangy".

The scripts can also use the text file "wordle-allowed-guesses.txt" which can be downloaded from here, takes up 63 KB of disk space, and contains all 10,657 of the 5 letter words that are allowed as guesses in Wordle.

TODO: add a user interface to wordle_guesser.py.

""" Calculate the approximately N best first guesses in Jewdle """
from word_list import JEWDLE_KNOWN_POSSIBILITES
valid_word_set = set(JEWDLE_KNOWN_POSSIBILITES)
char_set = set(char for word in valid_word_set for char in word)
char_score_dict = {
char: sum((1 if char in word else 0) for word in valid_word_set)
for char in char_set
}
for _ in range(5):
print(
"Character scores: %s"
% sorted(char_score_dict.items(), key=lambda x: x[1], reverse=True)
)
word_score_dict = {
word: sum(char_score_dict[char] for char in set(word))
for word in valid_word_set
}
best_score = max(word_score_dict.values())
best_word_list = [
word for word in valid_word_set
if word_score_dict[word] == best_score
]
print(
"Best word score: %i\nBest scoring words: %s"
% (best_score, best_word_list)
)
best_word = best_word_list[0]
for char in best_word:
char_score_dict[char] = 0
""" Calculate the approximately N best first guesses in Wordle """
from word_list import WORD_LIST, BAD_WORDS_LIST
word_length = 5
valid_word_set = set(x for x in WORD_LIST if len(x) == word_length)
print(
"Number of valid %i-letter words = %i"
% (word_length, len(valid_word_set))
)
char_set = set(char for word in valid_word_set for char in word)
char_score_dict = {
char: sum((1 if char in word else 0) for word in valid_word_set)
for char in char_set
}
for _ in range(5):
print(
"Character scores: %s"
% sorted(char_score_dict.items(), key=lambda x: x[1], reverse=True)
)
word_score_dict = {
word: sum(char_score_dict[char] for char in set(word))
for word in valid_word_set
}
for word in BAD_WORDS_LIST:
word_score_dict[word] = 0
best_score = max(word_score_dict.values())
best_word_list = [
word for word in valid_word_set
if word_score_dict[word] == best_score
]
print(
"Best word score: %i\nBest scoring words: %s"
% (best_score, best_word_list)
)
best_word = best_word_list[0]
for char in best_word:
char_score_dict[char] = 0
""" Make guesses for the Wordle-inspired game known as Jewdle, which can be
played at this URL: https://www.jewdle.app/ . Note that I only have a small
number of possible Jewdle words in the corresponding word list, so this script
might give good initial guesses for Jewdle, but it is unlikely to give the
ultimately correct answer on a given day. I will try to add more words to the
Jewdle-specific word list as time goes on. """
from word_list import JEWDLE_KNOWN_POSSIBILITES
from wordle_guesser import make_wordle_guess_any, str_to_letter_set
def main():
""" Main function for the script. Call make_wordle_guess with the specified
arguments. """
make_wordle_guess_any(
good_char_good_pos_set=str_to_letter_set("h5"),
good_char_bad_pos_set=str_to_letter_set(""),
bad_char_str="mensc",
word_length=6,
word_list=JEWDLE_KNOWN_POSSIBILITES,
)
if __name__ == "__main__":
main()
""" Load valid words from file and define invalid words """
import os
from urllib.request import urlretrieve
def open_or_download_file(filename, url):
""" Check if a file exists, download the file from the given URL if it
is missing, open the file, and return the file contents as a list of
strings, in which each element of the list corresponds to a line of the
text file (with newline characters removed) """
if not os.path.isfile(filename):
print("\"%s\" doesn't exist, downloading... " % filename, end="")
urlretrieve(url, filename)
print("Done")
with open(filename) as f:
file_contents = f.read().split("\n")
return file_contents
WORD_LIST = open_or_download_file(
"words_alpha.txt",
(
"https://raw.githubusercontent.com/dwyl/english-words/master/"
"words_alpha.txt"
),
)
WORDLE_ALLOWED_GUESSES = open_or_download_file(
"wordle-allowed-guesses.txt",
(
"https://gist.githubusercontent.com/cfreshman/"
"cdcdf777450c5b5301e439061d29694c/raw/"
"975d5100d91ddfe87d99dcee1f01170a43520ea0/wordle-allowed-guesses.txt"
),
)
WORDLE_POSSIBILITES = open_or_download_file(
"wordle-answers-alphabetical.txt",
(
"https://gist.githubusercontent.com/cfreshman/"
"a03ef2cba789d8cf00c08f767e0fad7b/raw/"
"a9e55d7e0c08100ce62133a1fa0d9c4f0f542f2c/"
"wordle-answers-alphabetical.txt"
),
)
JEWDLE_KNOWN_POSSIBILITES = (
"mensch", "talmud", "kippur", "kneidl", "gimmel", "aliyah", "amidah",
"shofar", "fleish", "shalom", "mikveh", "kishke", "kosher", "sheila",
"kippah", "yallah", "hashem", "kvetch", "tuches", "bracha", "scroll",
"temple",
)
BAD_WORDS_LIST = (
"aesir, aries, serai, potch, embay, pynot, louty, outly, pgntt, abeam, "
"mouly, contg, multo, thawy, compt, clamb, clomb, bawty, thewy, whity, "
"withy, fplot, dompt, punkt, topog, dargo, pargo, garon, nagor, pardo, "
"fardo, ozark, garbo, bardo, bothy, thymi, thyms, thymy, otary, cymol, "
"cytol, mycol, octyl, blanc, punct, punto, puton, unpot, untop, nould, "
"unold, crink, doylt, tould, outby, blype, comdg, compd, bacin, danic, "
"comdt, contd, bundt, ducat, mulct, clomp, clonk, chold, imcnt, fldxt, "
"bumph, phoby, abamp, pobby, verby, bewry, vefry, clong, glump, onlay, "
"noyau, dumby, gumby, cadgy, ireos, osier, serio, sotie, toise, thulr, "
"thurl, mewer, bever, oundy, refly, rebec, rehem, yuchi, mutic, budgy, "
"drony, gyron, byron, flory, drovy, frowy, grovy, eosin, orias"
).replace(" ", "").split(",")
""" This script recommends word choices for the word-game wordle, which can be
played at this URL: https://www.nytimes.com/games/wordle/index.html .
Edit the arguments to the make_wordle_guess function with the outputs from your
recent guesses on Wordle, to see your recommended guesses (NOTE: the
character-positions use zero-indexing).
If you don't like the suggested guesses, add them to BAD_WORDS_LIST, and they
will no longer be recommended. """
from word_list import (
WORD_LIST,
BAD_WORDS_LIST,
WORDLE_POSSIBILITES,
WORDLE_ALLOWED_GUESSES,
)
def main():
""" Main function for the script. Call make_wordle_guess with the specified
arguments. """
make_wordle_guess_any(
good_char_good_pos_set=str_to_letter_set("t0a1y4"),
good_char_bad_pos_set=str_to_letter_set("n3"),
bad_char_str="riseclouw",
)
def str_to_letter_set(s):
""" Convert an appropriate string to a set of Letter objects. The string
should alternate alphabetic characters and numeric characters. The number
following each character refers to the position that number is in. Note
that the position should use zero-indexing.
For example, str_to_letter_set("p0e1r2") == {Letter("p", 0), Letter("e",
1), Letter("r", 2), } """
char_str = s[0::2]
pos_str = s[1::2]
letter_set = {
Letter(char, int(pos))
for char, pos in zip(char_str, pos_str)
}
return letter_set
class Letter:
def __init__(self, char, pos):
self.char = char
self.pos = pos
def __repr__(self):
return "Letter(\"%s\", %i)" % (self.char, self.pos)
def __hash__(self):
return hash((self.char, self.pos))
def __eq__(self, other):
return (self.char == other.char) and (self.pos == other.pos)
def assess_guess(true_word, guess_word_list):
""" Given the true word that is trying to be guessed, and a list of guesses
for that word, return the set of correct characters in the correct
positions (as Letter objects), the set of correct characters in the
incorrect positions (as Letter objects), and a string of incorrect
characters """
good_char_good_pos_set = set(
Letter(char, i)
for guess_word in guess_word_list
for i, char in enumerate(guess_word)
if true_word[i] == char
)
good_char_bad_pos_set = set(
Letter(char, i)
for guess_word in guess_word_list
for i, char in enumerate(guess_word)
if (char in true_word)
and (true_word[i] != char)
)
bad_char_str = "".join(
char
for guess_word in guess_word_list
for char in guess_word
if char not in true_word
)
return good_char_good_pos_set, good_char_bad_pos_set, bad_char_str
def make_wordle_guess_restricted(
good_char_good_pos_set=None,
good_char_bad_pos_set=None,
bad_char_str=None,
word_length=5,
word_list=WORDLE_POSSIBILITES,
):
""" Calculate the approximately best guess to make in a game of Wordle,
given the information in the input arguments (see docstring to assess_guess
function).
This function only allows guessing words which are possible answers. """
# Find the sets of valid words and characters
n_length_word_set = set(
word
for word in word_list
if len(word) == word_length
and word not in BAD_WORDS_LIST
)
valid_word_set = get_valid_word_set(
n_length_word_set,
word_length,
good_char_good_pos_set,
good_char_bad_pos_set,
bad_char_str,
)
if len(valid_word_set) == 1:
return valid_word_set.pop()
elif len(valid_word_set) == 0:
print("No valid words, returning empty string")
return ""
char_set = set(char for word in valid_word_set for char in word)
# Find the dictionaries mapping characters and words to scores
char_score_dict = {
char: sum((1 if char in word else 0) for word in valid_word_set)
for char in char_set
}
word_score_dict = {
word: sum(char_score_dict[char] for char in set(word))
for word in valid_word_set
}
# Find the words with the best score
best_score = max(word_score_dict.values())
best_word_list = [
word for word in valid_word_set
if word_score_dict[word] == best_score
]
print("Best scoring words: %s\n" % ", ".join(sorted(best_word_list)))
return sorted(best_word_list)[0]
def make_wordle_guess_any(
good_char_good_pos_set=None,
good_char_bad_pos_set=None,
bad_char_str=None,
word_length=5,
known_char_score=0,
valid_word_bonus=10,
word_list=WORDLE_POSSIBILITES,
):
""" Calculate the approximately best guess to make in a game of Wordle,
given the information in the input arguments (see docstring to assess_guess
function).
This function allows guesses from any real word of the right length, even
if it is not a possible answer. """
# Find the sets of valid words and known and scoring characters
n_length_word_set = set(
word
for word in word_list
if len(word) == word_length
and word not in BAD_WORDS_LIST
)
valid_word_set = get_valid_word_set(
n_length_word_set,
word_length,
good_char_good_pos_set,
good_char_bad_pos_set,
bad_char_str,
)
if len(valid_word_set) == 1:
return valid_word_set.pop()
elif len(valid_word_set) == 0:
print("No valid words, returning empty string")
return ""
f = lambda x: x if x is not None else []
known_char_set = set(
letter.char
for s in [f(good_char_good_pos_set), f(good_char_bad_pos_set)]
for letter in s
)
scoring_char_set = set(
char
for word in valid_word_set
for char in word
)
# Find the dictionaries mapping characters and words to scores
char_score_dict = {
char: (
sum((1 if char in word else 0) for word in valid_word_set)
if char not in known_char_set
else known_char_score
)
for char in scoring_char_set
}
if valid_word_bonus is None:
valid_word_bonus = word_length
word_score_dict = {
word: (
sum(char_score_dict.get(char, 0) for char in set(word))
+ (valid_word_bonus if word in valid_word_set else 0)
)
for word in n_length_word_set
}
# Find the words with the best score
best_score = max(word_score_dict.values())
best_word_list = [
word for word in n_length_word_set
if word_score_dict[word] == best_score
]
print("Best scoring words: %s\n" % ", ".join(sorted(best_word_list)))
return sorted(best_word_list)[0]
def get_valid_word_set(
word_set,
word_length,
good_char_good_pos_set=None,
good_char_bad_pos_set=None,
bad_char_str=None,
):
""" Given a set of words, filter it such that it only contains words with
correct characters in the correct positions and not in incorrect positions,
and no words with incorrect characters """
valid_word_set = word_set
if good_char_good_pos_set is not None:
valid_word_set = set(
word for word in valid_word_set
if all(
word[letter.pos] == letter.char
for letter in good_char_good_pos_set
)
)
if good_char_bad_pos_set is not None:
valid_word_set = set(
word for word in valid_word_set
if all(
letter.char in word
and word[letter.pos] != letter.char
for letter in good_char_bad_pos_set
)
)
if bad_char_str is not None:
valid_word_set = set(
word for word in valid_word_set
if all(
char not in word
for char in bad_char_str
)
)
print(
"Number of valid %i-letter words = %i"
% (word_length, len(valid_word_set))
)
if len(valid_word_set) < 40:
print("Valid word list: %s" % ", ".join(sorted(valid_word_set)))
return valid_word_set
def simulate_game(
true_word,
initial_guess_list=None,
word_guesser=make_wordle_guess_any,
max_guesses=6,
):
""" Given a true word to guess, and a function which makes guesses (EG
make_wordle_guess_any or make_wordle_guess_restricted), simulate a game of
wordle, which ends when the correct word is guessed, or the same word is
guessed twice """
if initial_guess_list is not None:
guess_list = initial_guess_list
else:
guess_list = []
while len(guess_list) < max_guesses:
new_guess = word_guesser(*assess_guess(true_word, guess_list))
if new_guess in guess_list:
print_with_emphasis(
"Failure: a word has been guessed multiple times: %s"
% " -> ".join(guess_list)
)
return
guess_list.append(new_guess)
if true_word in guess_list:
print_with_emphasis(
"Success! True word %r guessed in %i guesses: %s"
% (true_word, len(guess_list), " -> ".join(guess_list))
)
return
print_with_emphasis(
"Failure: the word %r has not been guessed after %i attempts: %s"
% (true_word, len(guess_list), " -> ".join(guess_list))
)
def print_with_emphasis(s):
""" Print a string with extra emphasis (newlines, asterisks, etc) """
line_break = "*" * len(s)
print("\n%s\n%s\n%s\n" % (line_break, s, line_break))
if __name__ == "__main__":
# make_wordle_guess_any(*assess_guess("tangy", []))
# make_wordle_guess_any(*assess_guess("tangy", ["raise"]))
# make_wordle_guess_any(*assess_guess("tangy", ["raise", "monty"]))
# print(*assess_guess("tangy", ["raise", "monty"]), sep="\n")
# simulate_game("drink")
# simulate_game("abbey")
# simulate_game("tangy")
# simulate_game("favor")
# simulate_game("query")
# simulate_game("panic")
# simulate_game("break")
# simulate_game("chair")
# simulate_game("panic", ["raise"])
# simulate_game("favor", ["raise"])
# simulate_game("favor", ["raise", "clout"])
# simulate_game("favor", word_guesser=make_wordle_guess_restricted)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment