Skip to content

Commit 3a0a40e

Browse files
authored
Merge pull request #937 from Adrien227/develop
Wordle_solver
2 parents 3569348 + fdc4658 commit 3a0a40e

File tree

4 files changed

+2546
-0
lines changed

4 files changed

+2546
-0
lines changed

wordle_solver/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Script to solve Wordle automatically
2+
3+
## Setup and activate virtual environment :
4+
For Unix based systems please execute the following command to install requirements.
5+
6+
```
7+
pip install -r requirements.txt
8+
```
9+
10+
## How to use:
11+
1. Launch the python script using
12+
13+
```
14+
python wordle_solver.py
15+
```
16+
17+
2. When you are on the Wordle web page, close all the pop-ups and press "ESC" to start the script.
18+
19+
3. The Wordle will solve automatically !

wordle_solver/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
selenium
2+
webdriver_manager
3+
pynput

wordle_solver/wordle_solver.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# https://www.wordunscrambler.net/word-list/wordle-word-list
2+
# for the list of words
3+
4+
from selenium import webdriver
5+
from selenium.webdriver.common.by import By
6+
from selenium.webdriver.firefox.service import Service as FirefoxService
7+
from webdriver_manager.firefox import GeckoDriverManager
8+
from pynput import keyboard
9+
import time
10+
11+
12+
# This class is used to store data about the wordle such as :
13+
# - the list of possible words
14+
# - the letters that are present but not in the right position
15+
# - the letters that are absent
16+
# - the letters that are correct and their position in a list
17+
# - the word that is currently being tested
18+
19+
class Finder:
20+
def __init__(self):
21+
self.possible_words = get_list_of_words()
22+
self.present_letters = set([])
23+
self.absent_letters = set([])
24+
self.word = [''] * 5
25+
26+
# Creators recommend “Slate” as starting word
27+
self.word_to_try = "slate"
28+
29+
30+
# Function that is called by the KeyboardListener
31+
def on_release(key):
32+
# Start button
33+
if key == keyboard.Key.esc:
34+
return False # stop listener
35+
36+
37+
# Get the status of the letters in the wordle
38+
def get_row_results(game_row):
39+
tiles = game_row.find_elements(
40+
By.XPATH, ".//*[contains(@class, 'Tile-module_tile__')]")
41+
row_results = []
42+
res_to_int = {
43+
"correct": 1,
44+
"present": 0,
45+
"absent": -1,
46+
"empty": -2,
47+
"tbd": -3
48+
}
49+
for tile in tiles:
50+
row_results.append(res_to_int[tile.get_attribute("data-state")])
51+
print(f"Row results : {row_results}")
52+
53+
return tuple(row_results)
54+
55+
56+
# Enter the word in the wordle
57+
def enter_word(word):
58+
keyboard_controller = keyboard.Controller()
59+
keyboard_controller.type(word)
60+
keyboard_controller.tap(keyboard.Key.enter)
61+
time.sleep(2)
62+
63+
64+
# Check word length, used in get_list_of_words()
65+
# if the source list contains words with different length
66+
def check_word_length(word):
67+
if len(word) != 5:
68+
return False
69+
else:
70+
return True
71+
72+
73+
# Check if a word contains a specific letter
74+
def check_letter_in_word(letter, word):
75+
if letter in word:
76+
return True
77+
else:
78+
return False
79+
80+
81+
# Check if the letter in the finder object
82+
# is the same as the letter in the possible answer
83+
def check_match(finder_word_letter, possible_word_letter):
84+
if finder_word_letter == possible_word_letter:
85+
return True
86+
else:
87+
return False
88+
89+
90+
# From the wordle words list, return all the words
91+
def get_list_of_words():
92+
list_of_words = open("words_alpha.txt", "r").read().strip().splitlines()
93+
94+
# *** Use this if the source list contains words with different length ***
95+
# list_of_words = list(filter(check_word_length, list_of_words))
96+
97+
return list_of_words
98+
99+
100+
# Algorithm that solve the wordle
101+
def solving_algorithm(res, finder):
102+
print("Starting solving algorithm")
103+
word = finder.word_to_try
104+
105+
# Compare the word with the results of the wordle
106+
for letter in range(len(word)):
107+
# Case when the status of the letter is "correct"
108+
if res[letter] == 1:
109+
print(f"Letter {word[letter]} is correct")
110+
finder.word[letter] = word[letter]
111+
print(finder.word)
112+
if word[letter] in finder.absent_letters:
113+
finder.absent_letters.remove(word[letter])
114+
115+
# Case when the status of the letter is "present"
116+
# (present but at the wrong position)
117+
elif res[letter] == 0:
118+
print(f"Letter {word[letter]} is present")
119+
finder.present_letters.add(word[letter])
120+
# We keep all the words that don't match
121+
# the pattern of the word entered
122+
finder.possible_words = list(
123+
filter(lambda x_word:
124+
not check_match(word[letter], x_word[letter]),
125+
finder.possible_words))
126+
127+
else: # Case when the status of the letter is "absent"
128+
print(f"Letter {word[letter]} is absent")
129+
if word[letter] not in finder.present_letters:
130+
finder.absent_letters.add(word[letter])
131+
132+
# We keep all the words that don't match
133+
# the pattern of the word entered
134+
finder.possible_words = list(
135+
filter(lambda x_word:
136+
not check_match(word[letter], x_word[letter]),
137+
finder.possible_words))
138+
139+
print("\n")
140+
print("Updating list of possible words ...")
141+
142+
# Update list of words
143+
for absent in finder.absent_letters:
144+
finder.possible_words = list(
145+
filter(lambda x_word:
146+
not check_letter_in_word(absent, x_word),
147+
finder.possible_words))
148+
for present in finder.present_letters:
149+
finder.possible_words = list(
150+
filter(lambda x_word:
151+
check_letter_in_word(present, x_word),
152+
finder.possible_words))
153+
for i in range(len(finder.word)):
154+
if finder.word[i] != "":
155+
finder.possible_words = list(
156+
filter(lambda x_word:
157+
check_match(x_word[i], finder.word[i]),
158+
finder.possible_words))
159+
160+
# Update the next word to try
161+
finder.word_to_try = finder.possible_words[0]
162+
163+
print("List of possible words updated !\n")
164+
165+
print("Letter not in the right position : ", finder.present_letters)
166+
print("Letters with absent status : ", finder.absent_letters)
167+
print("List of words : ", finder.possible_words)
168+
print("Length of list", len(finder.possible_words))
169+
170+
171+
def main():
172+
# Start the browser
173+
browser = webdriver.Firefox(
174+
service=FirefoxService(GeckoDriverManager().install()))
175+
browser.get("https://www.nytimes.com/games/wordle/index.html")
176+
177+
# Create the finder object (cf. class Finder)
178+
finder = Finder()
179+
180+
guesses_left = 6
181+
182+
# Wait for start
183+
with keyboard.Listener(on_release=on_release, suppress=True) as listener:
184+
print("Starting program\n")
185+
listener.join()
186+
187+
# With "suppress=True", duplicate key presses are not sent
188+
# to the application but for some reason I need to add a delay
189+
# for the first input to be sent.
190+
time.sleep(1)
191+
192+
# Get the game rows
193+
game_rows = browser.find_elements(
194+
By.XPATH, "//*[contains(@class, 'Row-module_row__')]")
195+
196+
# Enter words until the game is over or the wordle is solved
197+
for i in range(guesses_left, 0, -1):
198+
enter_word(finder.word_to_try)
199+
res = get_row_results(game_rows[guesses_left - i])
200+
solving_algorithm(res, finder)
201+
if len(finder.possible_words) == 1:
202+
enter_word(finder.word_to_try)
203+
print(f"The word is : {finder.word_to_try}\n")
204+
break
205+
time.sleep(1)
206+
207+
208+
if __name__ == "__main__":
209+
main()

0 commit comments

Comments
 (0)