Introducing wordle + its solver
This commit is contained in:
parent
96c158be79
commit
0bfe0cf4f3
7 changed files with 2643 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -5,4 +5,5 @@ __pycache__
|
||||||
maze/__pycache__
|
maze/__pycache__
|
||||||
maze/modules/__pycache__
|
maze/modules/__pycache__
|
||||||
saves
|
saves
|
||||||
tetris
|
tetris
|
||||||
|
wordle-curses/__pycache__
|
6
wordle-curses/README.md
Normal file
6
wordle-curses/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# wordle-curses
|
||||||
|
A simple TUI wordle game with curses
|
||||||
|
|
||||||
|
![ezgif-7-16d0fbabbd](https://user-images.githubusercontent.com/30610197/151707991-824e3c54-9b69-449c-bc65-e500345d7877.gif)
|
||||||
|
|
||||||
|
Run `main.py` to play.
|
58
wordle-curses/dictionary.py
Normal file
58
wordle-curses/dictionary.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import curses
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
app_id = "4a8fca56"
|
||||||
|
app_key = "85885b5929e5b14402e75fcb4898d7f5"
|
||||||
|
language = "en-us"
|
||||||
|
|
||||||
|
def defnsyn(w):
|
||||||
|
url = r"https://od-api.oxforddictionaries.com:443/api/v2/entries/" + language + "/" + w.lower()
|
||||||
|
r=requests.get(url, headers={"app_id": app_id, "app_key": app_key})
|
||||||
|
if r.status_code != 200:
|
||||||
|
return None, None
|
||||||
|
res = r.json()
|
||||||
|
s1 = res["results"][0]["lexicalEntries"]
|
||||||
|
lexicalCategories = []
|
||||||
|
synonyms = []
|
||||||
|
defn = ""
|
||||||
|
if len(s1) > 1:
|
||||||
|
for i in range(len(s1)):
|
||||||
|
lexicalCategories.append(s1[i]["lexicalCategory"]['id'])
|
||||||
|
if "verb" in lexicalCategories:
|
||||||
|
baseindex = s1[lexicalCategories.index("verb")]['entries'][0]['senses'][0]
|
||||||
|
defn = (baseindex['shortDefinitions'][0])
|
||||||
|
if "synonyms" in baseindex:
|
||||||
|
no = 3 if len(baseindex["synonyms"]) > 3 else len(baseindex["synonyms"])
|
||||||
|
while no:
|
||||||
|
synonyms.append(baseindex["synonyms"][no]["text"])
|
||||||
|
no -= 1
|
||||||
|
synonyms.reverse()
|
||||||
|
elif "noun" in lexicalCategories:
|
||||||
|
baseindex = s1[lexicalCategories.index("noun")]['entries'][0]['senses'][0]
|
||||||
|
defn = (baseindex['shortDefinitions'][0])
|
||||||
|
if "synonyms" in baseindex:
|
||||||
|
no = 3 if len(baseindex["synonyms"]) > 3 else len(baseindex["synonyms"])
|
||||||
|
while no:
|
||||||
|
synonyms.append(baseindex["synonyms"][no]["text"])
|
||||||
|
no -= 1
|
||||||
|
synonyms.reverse()
|
||||||
|
else:
|
||||||
|
baseindex = s1[0]['entries'][0]['senses'][0]
|
||||||
|
defn = (baseindex['shortDefinitions'][0])
|
||||||
|
if "synonyms" in baseindex:
|
||||||
|
no = 3 if len(baseindex["synonyms"]) > 3 else len(baseindex["synonyms"])
|
||||||
|
while no:
|
||||||
|
synonyms.append(baseindex["synonyms"][no]["text"])
|
||||||
|
no -= 1
|
||||||
|
synonyms.reverse()
|
||||||
|
else:
|
||||||
|
baseindex = s1[0]['entries'][0]['senses'][0]
|
||||||
|
defn = (baseindex['shortDefinitions'][0])
|
||||||
|
if "synonyms" in baseindex:
|
||||||
|
no = 3 if len(baseindex["synonyms"]) > 3 else len(baseindex["synonyms"])
|
||||||
|
while no:
|
||||||
|
synonyms.append(baseindex["synonyms"][no]["text"])
|
||||||
|
no -= 1
|
||||||
|
synonyms.reverse()
|
||||||
|
return defn, synonyms
|
147
wordle-curses/main_readable.py
Normal file
147
wordle-curses/main_readable.py
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
# A slightly more readable version of wordle-curses
|
||||||
|
|
||||||
|
import curses, random, time
|
||||||
|
from dictionary import defnsyn
|
||||||
|
|
||||||
|
words = open("words.txt", "r").read().split("\n")
|
||||||
|
colorPairBindings = {"c": 2, "w": 3, "n": 7, "u": 6}
|
||||||
|
completionMessages = [
|
||||||
|
"",
|
||||||
|
"Genius!",
|
||||||
|
"Unbelievable!",
|
||||||
|
"Splendid!",
|
||||||
|
"Amazing!",
|
||||||
|
"Great!",
|
||||||
|
"Good!"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Draw one row of the board
|
||||||
|
# Generates new row + colours alphabets in
|
||||||
|
def writeWord(s, word, remark, y):
|
||||||
|
s.addstr(y, 0, "│ │ │ │ │ │\n├─┼─┼─┼─┼─┤")
|
||||||
|
for i, (letter, color) in enumerate(zip(word, remark)):
|
||||||
|
s.addstr(
|
||||||
|
y,
|
||||||
|
i * 2 + 1,
|
||||||
|
letter.upper(),
|
||||||
|
curses.color_pair(colorPairBindings[color]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Score a word
|
||||||
|
def score(guess, word, alphabet):
|
||||||
|
res = [" "] * 5
|
||||||
|
counts = [0] * 26
|
||||||
|
|
||||||
|
# First process correct letters
|
||||||
|
for i, c in enumerate(guess):
|
||||||
|
if c == word[i]: #checking if guess letter corresponds to letter of the same index in chosen word
|
||||||
|
charIndex = ord(c) - 97 # 97 corresponds to a - gives alphabet number. (eg. h = 8)
|
||||||
|
counts[charIndex] += 1
|
||||||
|
res[i] = "c" # correct spot
|
||||||
|
alphabet[charIndex] = "c"
|
||||||
|
|
||||||
|
# Then handle wrong and nonpresent letters
|
||||||
|
for i, c in enumerate(guess):
|
||||||
|
if c != word[i]:
|
||||||
|
charIndex = ord(c) - 97
|
||||||
|
counts[charIndex] += 1
|
||||||
|
if c in word and word.count(c) >= counts[charIndex]: # if freq of letters in guess lesser than freq in word
|
||||||
|
res[i] = "w" # wrong spot
|
||||||
|
if alphabet[charIndex] != "c":
|
||||||
|
alphabet[charIndex] = "w"
|
||||||
|
else:
|
||||||
|
res[i] = "n" # not in word
|
||||||
|
alphabet[charIndex] = "n"
|
||||||
|
|
||||||
|
return "".join(res), alphabet
|
||||||
|
|
||||||
|
# Render current board
|
||||||
|
# Updates alphabet use state + renders colours of guesses
|
||||||
|
def render(s, guesses, alphabet):
|
||||||
|
s.addstr(0, 0, "=== WORDLE ===", curses.color_pair(2))
|
||||||
|
for i, c in enumerate(alphabet):
|
||||||
|
s.addstr(
|
||||||
|
1 + int(i // 7),
|
||||||
|
(i % 7) * 2,
|
||||||
|
chr(65 + i),
|
||||||
|
curses.color_pair(colorPairBindings[c]),
|
||||||
|
)
|
||||||
|
s.addstr(6, 0, "╭─┬─┬─┬─┬─╮")
|
||||||
|
for i, (w, r) in enumerate(guesses):
|
||||||
|
writeWord(s, w, r, i * 2 + 7)
|
||||||
|
s.addstr(len(guesses) * 2 + 10, 0, " ")
|
||||||
|
|
||||||
|
# Accept word from user input
|
||||||
|
def getWord(s, y):
|
||||||
|
word = ""
|
||||||
|
while True:
|
||||||
|
writeWord(s, word, "u" * len(word), y) # u = default blue colour
|
||||||
|
k = s.getch()
|
||||||
|
if k == 8: # backspace
|
||||||
|
word = word[:-1]
|
||||||
|
elif k == 27: # esc
|
||||||
|
exit()
|
||||||
|
elif chr(k) == "\n" and len(word) == 5:
|
||||||
|
return word
|
||||||
|
elif chr(k).isalpha() and len(word) < 5:
|
||||||
|
word += chr(k)
|
||||||
|
|
||||||
|
# Run one game of Wordle
|
||||||
|
def run(s):
|
||||||
|
s.clear()
|
||||||
|
word = random.choice(words) #chosen word
|
||||||
|
print("Chosen word: ", word)
|
||||||
|
defn, synonyms = defnsyn(word)
|
||||||
|
guesses = [] # stores each guess and its result
|
||||||
|
alphabet = ["u"] * 26 # current status of each letter whether used or not
|
||||||
|
# c = correct positon, w = correct letter but not position, n = wrong letter, u = not used
|
||||||
|
# "ccccc" means all letters are in correct spot
|
||||||
|
while not (len(guesses)) or (guesses[-1][1] != "ccccc" and len(guesses) < 6):
|
||||||
|
render(s, guesses, alphabet) # Update current state of board from start
|
||||||
|
guess = getWord(s, len(guesses) * 2 + 7).lower()
|
||||||
|
if not (guess in words): # Check if given word is valid
|
||||||
|
s.addstr(len(guesses) * 2 + 10, 0, "INVALID WORD", curses.color_pair(1))
|
||||||
|
s.refresh()
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
res, alphabet = score(guess, word, alphabet)
|
||||||
|
guesses.append([guess, res])
|
||||||
|
render(s, guesses, alphabet) #Renders final board
|
||||||
|
|
||||||
|
# Ending spiel
|
||||||
|
s.addstr(len(guesses) * 2 + 6, 0, "╰─┴─┴─┴─┴─╯")
|
||||||
|
if guesses[-1][1] != "ccccc":
|
||||||
|
s.addstr(len(guesses) * 2 + 8, 0, "No more tries - the word was " + word.upper())
|
||||||
|
else:
|
||||||
|
s.addstr(
|
||||||
|
len(guesses) * 2 + 8,
|
||||||
|
0,
|
||||||
|
completionMessages[len(guesses)]
|
||||||
|
)
|
||||||
|
if defn and synonyms:
|
||||||
|
s.addstr(len(guesses) * 2 + 9, 0, word+": ", curses.color_pair(2))
|
||||||
|
synonyms = ", ".join(synonyms)
|
||||||
|
s.addstr(len(guesses) * 2 + 9, 8, defn)
|
||||||
|
s.addstr(len(guesses) * 2 + 10, 0, "Some synonyms: ", curses.color_pair(2))
|
||||||
|
s.addstr(len(guesses) * 2 + 10, 16, synonyms)
|
||||||
|
s.addstr(len(guesses) * 2 + 11, 0, "[esc] to quit, [enter] to play again", curses.color_pair(3))
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
def main(s):
|
||||||
|
# Initialize colors
|
||||||
|
for p in [
|
||||||
|
(1, curses.COLOR_RED),
|
||||||
|
(2, curses.COLOR_GREEN),
|
||||||
|
(3, curses.COLOR_YELLOW),
|
||||||
|
(7, curses.COLOR_WHITE),
|
||||||
|
(6, curses.COLOR_CYAN),
|
||||||
|
]:
|
||||||
|
curses.init_pair(p[0], p[1], curses.COLOR_BLACK)
|
||||||
|
# Run game
|
||||||
|
while True:
|
||||||
|
run(s)
|
||||||
|
if s.getch() == 27: # esc
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
curses.wrapper(main)
|
49
wordle-curses/solver.py
Normal file
49
wordle-curses/solver.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
words = open("words.txt").read().split("\n")
|
||||||
|
|
||||||
|
def partition(word, words):
|
||||||
|
# Get how many words will remain for each possible response
|
||||||
|
partitions = []
|
||||||
|
for a in "MCW":
|
||||||
|
for b in "MCW":
|
||||||
|
for c in "MCW":
|
||||||
|
for d in "MCW":
|
||||||
|
for e in "MCW":
|
||||||
|
partitions.append(len(reduce(word, a+b+c+d+e, words)))
|
||||||
|
return partitions
|
||||||
|
|
||||||
|
def reduce(word, result, words):
|
||||||
|
# word: 5-letter word (lowercase)
|
||||||
|
# result: 5-letter str consisting of M, C, W (misplaced, correct, wrong)
|
||||||
|
res = words[:]
|
||||||
|
for i, s in enumerate(result):
|
||||||
|
nres = []
|
||||||
|
for w in res:
|
||||||
|
if s == "M":
|
||||||
|
if w[i] != word[i] and word[i] in w: nres.append(w)
|
||||||
|
if s == "C":
|
||||||
|
if w[i] == word[i]: nres.append(w)
|
||||||
|
if s == "W":
|
||||||
|
if w[i] != word[i]:
|
||||||
|
if not(word[i] in w) or word.count(word[i]) > 1:
|
||||||
|
nres.append(w)
|
||||||
|
res = nres
|
||||||
|
return res
|
||||||
|
|
||||||
|
print("WORDLE SOLVER")
|
||||||
|
print("=============")
|
||||||
|
# First guess is precomputed
|
||||||
|
opt = "crate"
|
||||||
|
result = ""
|
||||||
|
while result != "CCCCC":
|
||||||
|
print(opt)
|
||||||
|
result = input("> ").upper()
|
||||||
|
words = reduce(opt, result, words)
|
||||||
|
opt = ""
|
||||||
|
opt_size = float("inf")
|
||||||
|
for word in words:
|
||||||
|
p = partition(word, words)
|
||||||
|
avg_partition_size = sum(p)/len(p)
|
||||||
|
if opt_size > avg_partition_size:
|
||||||
|
opt_size = avg_partition_size
|
||||||
|
opt = word
|
||||||
|
# print(p)
|
51
wordle-curses/wordle-solver.py
Normal file
51
wordle-curses/wordle-solver.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
words = open("words.txt").read().split()
|
||||||
|
|
||||||
|
guesslist = []
|
||||||
|
maxguesses = 6
|
||||||
|
wantedletters = []
|
||||||
|
uselessletters = []
|
||||||
|
correctletters = ["-"] * 5
|
||||||
|
|
||||||
|
while maxguesses:
|
||||||
|
try:
|
||||||
|
correctguess = input("\nEnter correct letter positions (use - for other letters): ").lower()
|
||||||
|
uselessguess = input("Enter all incorrect letters without space: ").lower()
|
||||||
|
wantedguess = input("Enter all letters of incorrect postiions: ").lower()
|
||||||
|
|
||||||
|
correctletters = list(correctguess)
|
||||||
|
for i in uselessguess:
|
||||||
|
if i not in uselessletters:
|
||||||
|
uselessletters.append(i)
|
||||||
|
for i in wantedguess:
|
||||||
|
if i not in wantedletters:
|
||||||
|
wantedletters.append(i)
|
||||||
|
|
||||||
|
subsetwanted = []
|
||||||
|
for i in words:
|
||||||
|
if set(wantedletters).issubset(set(i)):
|
||||||
|
subsetwanted.append(i)
|
||||||
|
print("First filter", subsetwanted)
|
||||||
|
for i in uselessletters:
|
||||||
|
for j in subsetwanted:
|
||||||
|
if j.count(i):
|
||||||
|
subsetwanted.remove(j)
|
||||||
|
|
||||||
|
print("Second filter: ", subsetwanted)
|
||||||
|
|
||||||
|
print(correctletters)
|
||||||
|
temp = []
|
||||||
|
for i in range(len(correctletters)):
|
||||||
|
for j in range(len(subsetwanted)):
|
||||||
|
if correctletters[i] == "-":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(i, j, correctletters[i], subsetwanted[j][i])
|
||||||
|
if correctletters[i] == subsetwanted[j][i]:
|
||||||
|
temp.append(subsetwanted[j])
|
||||||
|
subsetwanted = temp.copy()
|
||||||
|
print("Third filter: ", subsetwanted)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nThe word was found!")
|
||||||
|
break
|
||||||
|
maxguesses -= 1
|
2330
wordle-curses/words.txt
Normal file
2330
wordle-curses/words.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue