File size: 9,271 Bytes
c5bcf4c
 
 
 
28f1794
c5bcf4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28f1794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5bcf4c
 
28f1794
c5bcf4c
28f1794
c5bcf4c
28f1794
 
 
c5bcf4c
 
 
28f1794
 
 
 
 
c5bcf4c
 
 
 
28f1794
c5bcf4c
 
 
28f1794
c5bcf4c
 
28f1794
 
c5bcf4c
 
 
 
 
 
28f1794
 
 
 
 
 
 
 
c5bcf4c
28f1794
 
 
 
 
 
 
 
c5bcf4c
 
 
28f1794
c5bcf4c
28f1794
 
c5bcf4c
 
 
28f1794
c5bcf4c
 
28f1794
c5bcf4c
 
 
28f1794
c5bcf4c
28f1794
 
c5bcf4c
 
 
 
 
28f1794
c5bcf4c
28f1794
c5bcf4c
 
 
 
 
28f1794
c5bcf4c
 
 
 
28f1794
c5bcf4c
28f1794
c5bcf4c
 
 
 
 
 
 
 
 
28f1794
 
 
62e140b
c5bcf4c
 
 
28f1794
 
 
 
 
 
c5bcf4c
28f1794
c5bcf4c
 
 
 
 
 
 
 
28f1794
 
c5bcf4c
 
 
28f1794
 
c5bcf4c
 
 
 
 
 
 
 
 
 
 
 
28f1794
c5bcf4c
 
 
 
 
 
 
 
28f1794
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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()