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()