rules
UnWordle is a word guessing game in which it shows you a n*n color grid and a base word. The goal of the player is to fill words into the grid such that it follows these rules:
- a green brick must contain a letter from the base word with correct positioning.
- a yellow brick must contain a letter from the base word but in an incorrect position
- a gray brick must NOT contain ANY letter from the base word
- a level beneath another level must use all the information from the level above it:
- so: if a yellow or green letter is used on an higher level they must be used in a lower level
- if a gray letter is used on an higher level they must NOT be used on the lower level
- a yellow letter can not be used on the same index multiple times
- if it is known that there's only M amount of letter k, you can not use more than that amount in you building
example:
image on the left is the starting position.
image on the right is the winning position.
they follow all the rules mentioned above.
problem I wrote code to solve the game, and it can solve 80% of the puzzle with no sweat, but it gets really slow as the question becomes more difficult. I am not sure if it was due to the algorithm or the flow of the program. The code was provided below.
link to the dictionary file I use
unwordle.py
def help_() -> None:
"""message for help/-help flags."""
s = """unwordle is a popular word game!
learn more about them here:https://unwordle.org/?daily=1
this program is a game solver.
first check out the unwordle game board.
then in command line type in `python unwordle.py `
if the first row is *green* *yellow* *gray* *gray* *gray*,
you would add `GYXXX` at the end of that command
you repeat this for the next two row.(all the hints)
and lastly typed in the answer word (letter only)
a sample output would look something like this:
`python unwordle.py YXXXX XYXYX XGYXG ralph\n`"""
print(s)
exit()
return
def check_sys_arg(sys_argv: list) -> list:
"""
validating the input of the user.
<br>including checking if word is the right len
<br>and if contains disallowed characters.
"""
ERROR_MSG_2 = ("include disallowed character in hint color:"+
"`G for green, Y for yellow, and X for gray`")
ERROR_MSG_3 = "include disallowed character in answer word`"
ERROR_MSG_4 = "inconsistent word length in input"
ALLOWED_COLOR_S: set = set("GYX")
ALLOWED_CHR_S: set = set("abcdefghijklmnopqrstuvwxyz")
output = []
wrd_ln = len(sys_argv[1])
for x in range(1, len(sys_argv) - 1):
x = sys_argv[x].upper()
if set(x) - ALLOWED_COLOR_S:
print("use `unwordle.py -help` to get help")
raise Exception(ERROR_MSG_2)
if len(x) != wrd_ln:
print("use `unwordle.py -help` to get help")
raise Exception(ERROR_MSG_4)
output.append(x)
x = sys_argv[-1].lower()
if set(x) - ALLOWED_CHR_S:
print("use `unwordle.py -help` to get help")
raise Exception(ERROR_MSG_3)
if len(x) != wrd_ln:
print("use `unwordle.py -help` to get help")
raise Exception(ERROR_MSG_4)
output.append(x)
return output
def import_dictionary(file: str = "dictionary.txt",
target_len_int: int = 5) -> list:
"""
reading and filtering words from txt files.
<br>file: the directory of which txt file is located
<br>target_len_int : target word length
"""
output = []
with open(file, "r+") as f: #reading list of words
temp = f.readlines()
for word in temp:
word = word.strip()
if len(word) == target_len_int:
output.append(word)
return output
def color_word(answer_word: str, guess_word: str) -> str:
"""
take two words and color accordingly.
an example output would be "XXYYG"
"""
color_l = ["X"] * len(guess_word)
ans_wrd_l = list(answer_word)
for counter, (chr_1, chr_2) in enumerate(zip(answer_word, guess_word)):
if chr_1 == chr_2:
color_l[counter] = "G"
ans_wrd_l.remove(chr_1)
for counter, chr_1 in enumerate(guess_word):
if color_l[counter] == "G":
continue
if chr_1 in ans_wrd_l:
color_l[counter] = "Y"
ans_wrd_l.remove(chr_1)
return "".join(color_l)
def find_candidate(wrd_l: list, final_ans: str, search: str) -> dict:
"""
loop through a word list and search for words that fit the criteria.
<br>the result to the function is a dict with:
<br>key being the word, val being a tuple of two set.:
<br>explain below:
<br>the first set contain sucess guesses(Y/G)
<br>the second set contain failed guesses:
<br>X or Y that is out of position
"""
def multi_apperance(chr: str,
succes: set,
append_to: set,
break_: bool = True):
for x in range(1, len(o) + 1):
x = str(x) + chr
if x not in success:
append_to.add(x)
if break_:
break
output = {}
for o in all_wrd_l:
if color_word(final_ans, o) != search:
continue
fail = set()
success = set()
for counter, (chr, color) in enumerate(zip(o, search)):
if color == "X":
continue
if chr in success:
multi_apperance(chr, success, success)
else:
success.add(chr)
if color == "Y":
fail.add(counter * "X" + chr)
for counter, (chr, color) in enumerate(zip(o, search)):
if color != "X":
continue
if chr not in success:
fail.add(chr)
else:
fail.add(counter * "X" + chr)
multi_apperance(chr, success, fail, False)
output[o] = (success, fail)
return output
def main(cand_list: list, answer: str):
"""
collect a list of possible candidates and render result. always return 0.
<br>required args:
<br>cand_list -> the candidate list. for reference only.
<br>include a list of all possible candidate dict here.
<br>starting from clue 1 ...clue 3
<br> answer -> final answer
"""
INSTRUCTION_MSG = """want a different answer?
enter 1 for a different answer or enter 0 to exit the program.
tip: you can enter any number and the word at that index will be changed
the left most word has a index of 1.
"""
def askinput():
print(INSTRUCTION_MSG)
while True:
ask_input = input("your respond:")
print()
if not ask_input:
ask_input = "1"
ask_input = ask_input.strip()
if set(ask_input) - set("1234567890"):
print("invalid input, let's try again.")
else:
return ask_input
def inner_main(depth: int,
cand_list: list,
answer: str,
display: list = [],
success_j: set = {},
fail_j: set = {}) -> 0:
"""The worker function for main."""
i_cands = cand_list[depth]
for cand in i_cands:
success_i, fail_i = i_cands[cand]
if display:
if success_i - success_j:
continue
if fail_i.intersection(fail_j):
continue
display.insert(0, cand)
if depth:
_ = inner_main(depth - 1, cand_list, answer, display,
success_i, fail_i.union(fail_j))
else:
print("answer suggestion:")
display.append(answer)
print(display)
_ = askinput()
_ = int(_) - 1
del display[-1]
del display[0]
if _:
return _ - 1
return 0
inner_main(len(cand_list) - 1, cand_list, answer)
return 0
if __name__ == "__main__":
import sys
print() # print an extra line to avoid any confusion
if len(sys.argv) == 1:
print("use `unwordle.py -help` to get help")
print()
exit()
help_flags = ["h", "help", "-help", "-h", "--help", "--h"]
if sys.argv[1].lower() in help_flags:
help_()
print()
if len(sys.argv) == 2:
print("use `unwordle.py -help` to get help")
print()
exit()
user_input_l: list = check_sys_arg(sys.argv)
WRD_LN = len(user_input_l[0])
ANSWER = user_input_l.pop()
GUESSES = len(user_input_l)
all_wrd_l: list = import_dictionary(target_len_int=WRD_LN)
cand = []
for clues in user_input_l:
temp: dict = find_candidate(all_wrd_l, ANSWER, clues)
cand.append(temp)
main(cand, ANSWER)