wordlesolver / app.py
hansaka1's picture
Update app.py
62e140b verified
import gradio as gr
import re
from collections import defaultdict
# --- Core Wordle Solver Logic ---
def load_words():
"""Load 5-letter words from the words.txt file."""
words = []
try:
with open('words.txt', 'r') as f:
for line in f:
word = line.strip().lower()
if len(word) == 5 and word.isalpha():
words.append(word)
except FileNotFoundError:
print("FATAL ERROR: words.txt not found. Please create it in the same directory.")
return None
return words
WORD_LIST = load_words()
# --- Advanced Parsing Logic (to handle flexible input) ---
def parse_guess(line):
"""Parse a single line of a guess, like 'πŸŸ₯ 🟨 πŸŸ₯ πŸŸ₯ 🟩 π—›π—œπ—‘π—šπ—˜' or 'IRATE 🟨πŸŸ₯🟨πŸŸ₯🟩'."""
# Mapping for special bold characters to regular ASCII
math_bold_to_regular = {
'𝗔': 'A', '𝗕': 'B', '𝗖': 'C', '𝗗': 'D', 'π—˜': 'E', '𝗙': 'F', 'π—š': 'G', '𝗛': 'H',
'π—œ': 'I', '𝗝': 'J', 'π—ž': 'K', 'π—Ÿ': 'L', '𝗠': 'M', '𝗑': 'N', '𝗒': 'O', '𝗣': 'P',
'𝗀': 'Q', 'π—₯': 'R', '𝗦': 'S', '𝗧': 'T', '𝗨': 'U', '𝗩': 'V', 'π—ͺ': 'W', '𝗫': 'X',
'𝗬': 'Y', '𝗭': 'Z'
}
def convert_math_bold_to_regular(text):
return ''.join(math_bold_to_regular.get(char, char) for char in text)
line = convert_math_bold_to_regular(line.upper())
# Pattern 1: Emojis first, then word (e.g., πŸŸ₯ 🟨 ... 🟩 HINGE)
pattern1 = r'([\s]*[πŸŸ₯πŸŸ¨πŸŸ©β¬›][\s]*){5}([\s]*[A-Z]{5})'
match1 = re.search(pattern1, line)
if match1:
emojis = re.sub(r'\s+', '', match1.group(0).replace(match1.group(2).strip(), ''))
word = match1.group(2).strip().lower()
return word, emojis
# Pattern 2: Word first, then emojis (e.g., HINGE πŸŸ₯ 🟨 ... 🟩)
pattern2 = r'([A-Z]{5}[\s]*)'
match2 = re.search(pattern2, line)
if match2:
word = match2.group(1).strip().lower()
emojis = re.sub(r'\s+', '', line.replace(match2.group(1), ''))
if len(emojis) == 5:
return word, emojis
return None, None
def parse_multiline_input(raw_text):
"""Parses a multi-line string of guesses into a list of clues."""
clues = []
errors = []
for i, line in enumerate(raw_text.strip().split('\n'), 1):
if not line.strip():
continue
word, emojis = parse_guess(line)
if word and emojis and len(word) == 5 and len(emojis) == 5:
clues.append((word, emojis))
else:
errors.append(f"Line {i}: Could not parse -> '{line}'")
return clues, errors
# --- Core Filtering and Suggestion Logic (Unchanged) ---
def word_matches_clue(word, guess_word, emoji_result):
for i, (guess_char, emoji) in enumerate(zip(guess_word, emoji_result)):
if emoji == '🟩' and word[i] != guess_char: return False
for i, (guess_char, emoji) in enumerate(zip(guess_word, emoji_result)):
if emoji == '🟨' and (guess_char not in word or word[i] == guess_char): return False
for i, (guess_char, emoji) in enumerate(zip(guess_word, emoji_result)):
if emoji in ['πŸŸ₯', '⬛']:
clue_count = sum(1 for j, gc in enumerate(guess_word) if gc == guess_char and emoji_result[j] in ['🟩', '🟨'])
if word.count(guess_char) > clue_count: return False
return True
def filter_words_by_clues(words, clues):
if not clues: return words
remaining = list(words)
for guess, emoji in clues:
remaining = [w for w in remaining if word_matches_clue(w, guess, emoji)]
return remaining
def get_letter_frequency(words):
freq = defaultdict(int)
for word in words:
for char in set(word): freq[char] += 1
return freq
def score_word(word, letter_freq):
return sum(letter_freq[char] for char in set(word))
def get_best_guess(words, all_words):
if not words: return None, "No words match the clues."
if len(words) <= 2: return words[0], f"{len(words)} possible word(s) remain."
letter_freq = get_letter_frequency(words)
best_word = max(all_words, key=lambda w: score_word(w, letter_freq))
return best_word, f"{len(words)} possible words remain."
# --- Gradio Interface Functions ---
def process_guesses(raw_text_input):
"""Handles the 'Submit Guesses' button click."""
parsed_clues, errors = parse_multiline_input(raw_text_input)
if errors:
error_msg = "\n".join(errors)
history_md = format_clues_history([])
return [], gr.update(visible=True, value=error_msg), "", "Parsing Error", history_md
if not parsed_clues:
error_msg = "Input is empty or invalid. Please check the format."
history_md = format_clues_history([])
return [], gr.update(visible=True, value=error_msg), "", "Invalid Input", history_md
remaining_words = filter_words_by_clues(WORD_LIST, parsed_clues)
best_guess, status_text = get_best_guess(remaining_words, WORD_LIST)
history_md = format_clues_history(parsed_clues)
if not remaining_words:
best_guess_str = "πŸ˜•"
status_text = "No words match your clues. Check for typos and try again."
elif len(remaining_words) == 1:
best_guess_str = remaining_words[0].upper()
status_text = "πŸŽ‰ Found it! This is the only possible word."
else:
best_guess_str = best_guess.upper()
return parsed_clues, gr.update(visible=False, value=""), best_guess_str, status_text, history_md
def get_other_suggestions(current_clues_list):
"""Provides alternative word suggestions based on the current parsed clues."""
if not current_clues_list:
return "Enter at least one guess to get suggestions."
remaining_words = filter_words_by_clues(WORD_LIST, current_clues_list)
if not remaining_words: return "No words match the current clues."
if len(remaining_words) == 1: return f"Only one word remains: **{remaining_words[0].upper()}**"
letter_freq = get_letter_frequency(remaining_words)
scored_words = sorted([(word, score_word(word, letter_freq)) for word in remaining_words], key=lambda x: x[1], reverse=True)
suggestions_md = f"### Top Suggestions ({len(remaining_words)} words remain)\n"
suggestions_md += "\n".join([f"- **{word.upper()}**" for word, _ in scored_words[:10]])
if len(scored_words) > 10:
suggestions_md += f"\n\n...and {len(scored_words) - 10} more."
return suggestions_md
def reset_session():
"""Clears all inputs, outputs, and the session state."""
return [], "", "", "", "No guesses yet. Enter your game history above.", "", gr.update(visible=False, value="")
def format_clues_history(clues_list):
"""Converts the list of clues into a markdown string for display."""
if not clues_list:
return "No guesses yet. Enter your game history above."
md = "### Your Parsed Guesses\n"
for word, emojis in clues_list:
md += f"- `{word.upper()}` -> {emojis}\n"
return md
# --- Gradio UI Layout ---
with gr.Blocks(theme=gr.themes.Soft(), title="Wordle Solver Bot") as demo:
session_clues = gr.State([])
gr.Markdown("# 🧩 Wordle Solver Bot (Single Input)")
gr.Markdown("Paste your entire game history into the text box below. Each line should contain one guess and its results.")
#[Image of a Wordle game grid]
with gr.Row():
with gr.Column(scale=2):
guesses_input = gr.Textbox(
label="Enter Guesses (One Per Line)",
lines=5,
placeholder="Example:\nπŸŸ₯ 🟨 πŸŸ₯ πŸŸ₯ 🟩 HINGE\n🟨 πŸŸ₯ 🟨 πŸŸ₯ 🟩 IRATE\n..."
)
error_box = gr.Textbox(label="Error", visible=False, interactive=False, lines=3)
with gr.Row():
submit_button = gr.Button("Find Best Guess", variant="primary")
other_button = gr.Button("Get Other Suggestions")
reset_button = gr.Button("Reset Session", variant="stop")
with gr.Column(scale=3):
best_guess_output = gr.Textbox(label="πŸ’‘ Best Next Guess", interactive=False)
status_output = gr.Textbox(label="πŸ“Š Status", interactive=False)
with gr.Row():
history_output = gr.Markdown("No guesses yet. Enter your game history above.")
other_suggestions_output = gr.Markdown("")
# --- Event Handlers ---
submit_button.click(
fn=process_guesses,
inputs=[guesses_input],
outputs=[session_clues, error_box, best_guess_output, status_output, history_output]
)
other_button.click(
fn=get_other_suggestions,
inputs=[session_clues],
outputs=[other_suggestions_output]
)
reset_button.click(
fn=reset_session,
inputs=[],
outputs=[session_clues, guesses_input, best_guess_output, status_output, history_output, other_suggestions_output, error_box]
)
if __name__ == "__main__":
if not WORD_LIST:
print("Could not start the application because 'words.txt' is missing or empty.")
else:
print("Starting Gradio app... Access it at the URL provided below.")
demo.launch()