Spaces:
Sleeping
Sleeping
| from flask import Flask, render_template, request, jsonify # type: ignore | |
| from math import log2 | |
| import numpy as np # type: ignore | |
| import os | |
| import time | |
| POSSIBLE_WORDS = set() | |
| with open("wordle_words.txt", "r") as f: | |
| for line in f: | |
| POSSIBLE_WORDS.add(line.strip()) | |
| # There are {len(POSSIBLE_WORDS)} possible words loaded | |
| def getFeedback(guess: str, answer: str) -> int: | |
| """ | |
| This function returns the feedback for a given guess and answer. | |
| The feedback is a list of 5 integers, where 0 means grey, 1 means yellow, and 2 means green. | |
| """ | |
| chars = list(answer) | |
| feedback = [2 if guess[i] == chars[i] else 0 for i in range(5)] | |
| for i in range(5): | |
| if feedback[i] == 0 and guess[i] in chars: | |
| feedback[i] = 1 # yellow | |
| chars[chars.index(guess[i])] = None # consume letter | |
| optimized = 0 | |
| for f in feedback: | |
| optimized = 3 * optimized + f | |
| return optimized # optimized encoding of feedback stores guesses as decimal numbers | |
| def filterCandidates(candidates: set, guess: str, feedback: int) -> set: | |
| """ | |
| This function filters the current candidates based on our guess and feedback. | |
| """ | |
| filtered = set(word for word in candidates if getFeedback(guess, word) == feedback) | |
| return filtered | |
| def bestGuessVectorized(candidates: set, allWords: set): | |
| """ | |
| This function returns the best guess for the next round. | |
| It uses a vectorized approach to compute the best guess. | |
| """ | |
| bestWord = None | |
| bestEntropy = -np.inf | |
| L = len(candidates) | |
| for guess in allWords: | |
| counts = np.zeros(3**5) | |
| for answer in candidates: | |
| counts[getFeedback(guess, answer)] += 1 | |
| # Filter out zero counts to avoid log(0) | |
| nonzero_counts = counts[counts > 0] | |
| probabilities = nonzero_counts / L | |
| entropy = -np.sum(probabilities * np.log2(probabilities)) | |
| if entropy > bestEntropy: | |
| bestEntropy = entropy | |
| bestWord = guess | |
| return bestWord | |
| def solveWordle(candidates: set[str], answer: str, maxGuesses: int = 6, allWords: set[str] = None): | |
| """ | |
| Solve Wordle by always starting with 'arise' and then using entropy-based scoring. | |
| This has an average solve of 3.89 guesses. | |
| """ | |
| guesses = [] | |
| # --- First guess: fixed word --- | |
| firstGuess = "arise" | |
| guesses.append(firstGuess) | |
| feedback = getFeedback(firstGuess, answer) | |
| print(f"Round 1: guess = {firstGuess}, feedback = {feedback}") | |
| if feedback == 242: | |
| print("Solved in 1 guess!") | |
| return guesses | |
| candidates = filterCandidates(candidates, firstGuess, feedback) | |
| print(f"After first guess, {len(candidates)} candidates remain") | |
| # --- Remaining guesses --- | |
| for i in range(2, maxGuesses + 1): | |
| if len(candidates) == 0: | |
| print("No candidates remaining!") | |
| break | |
| if len(candidates) == 1: | |
| # If only one candidate remains, just guess it | |
| nextGuess = list(candidates)[0] | |
| else: | |
| nextGuess = bestGuessVectorized(candidates, allWords if allWords else candidates) | |
| guesses.append(nextGuess) | |
| feedback = getFeedback(nextGuess, answer) | |
| print(f"Round {i}: guess = {nextGuess}, feedback = {feedback}") | |
| if feedback == 242: | |
| print(f"Solved in {i} guesses!") | |
| return guesses | |
| candidates = filterCandidates(candidates, nextGuess, feedback) | |
| print(f"After round {i}, {len(candidates)} candidates remain") | |
| print(f"Failed to solve Wordle.") | |
| return guesses | |
| def playWordle(allWords: set[str], maxGuesses: int = 6): | |
| """ | |
| Interactive Wordle solver for real gameplay. | |
| """ | |
| candidates = allWords.copy() | |
| guesses = [] | |
| print("Welcome to the Wordle Solver!") | |
| print("Enter feedback as 5 characters: 'g' for green, 'y' for yellow, 'b' for black/grey") | |
| print("Example: 'gybbb' means first letter is green, second is yellow, rest are black") | |
| print() | |
| # First guess: always "arise" | |
| firstGuess = "arise" | |
| print(f"Suggested guess 1: {firstGuess.upper()}") | |
| while True: | |
| feedback_str = input("Enter feedback for this guess (or 'q' to quit): ").strip().lower() | |
| if feedback_str == 'q': | |
| return | |
| if len(feedback_str) == 5 and all(c in 'gyb' for c in feedback_str): | |
| break | |
| print("Invalid input. Please enter exactly 5 characters using only 'g', 'y', 'b'") | |
| # Convert feedback to our integer format | |
| feedback_list = [] | |
| for c in feedback_str: | |
| if c == 'g': feedback_list.append(2) | |
| elif c == 'y': feedback_list.append(1) | |
| else: feedback_list.append(0) | |
| feedback = 0 | |
| for f in feedback_list: | |
| feedback = 3 * feedback + f | |
| guesses.append(firstGuess) | |
| if feedback == 242: # All green | |
| print("Congratulations! Solved in 1 guess!") | |
| return | |
| candidates = filterCandidates(candidates, firstGuess, feedback) | |
| print(f"Remaining candidates: {len(candidates)}") | |
| # Remaining guesses | |
| for round_num in range(2, maxGuesses + 1): | |
| if len(candidates) == 0: | |
| print("No valid words remain. There might be an error in the feedback.") | |
| return | |
| if len(candidates) == 1: | |
| nextGuess = list(candidates)[0] | |
| else: | |
| nextGuess = bestGuessVectorized(candidates, allWords) | |
| print(f"\nSuggested guess {round_num}: {nextGuess.upper()}") | |
| while True: | |
| feedback_str = input("Enter feedback for this guess (or 'q' to quit): ").strip().lower() | |
| if feedback_str == 'q': | |
| return | |
| if len(feedback_str) == 5 and all(c in 'gyb' for c in feedback_str): | |
| break | |
| print("Invalid input. Please enter exactly 5 characters using only 'g', 'y', 'b'") | |
| # Convert feedback to our integer format | |
| feedback_list = [] | |
| for c in feedback_str: | |
| if c == 'g': feedback_list.append(2) | |
| elif c == 'y': feedback_list.append(1) | |
| else: feedback_list.append(0) | |
| feedback = 0 | |
| for f in feedback_list: | |
| feedback = 3 * feedback + f | |
| guesses.append(nextGuess) | |
| if feedback == 242: # All green | |
| print(f"Congratulations! Solved in {round_num} guesses!") | |
| return | |
| candidates = filterCandidates(candidates, nextGuess, feedback) | |
| print(f"Remaining candidates: {len(candidates)}") | |
| print("Reached maximum guesses. Better luck next time!") | |
| def testSolverOnHistoricalWordles(allWords: set[str], testWords: list[str], maxGuesses: int = 6): | |
| """ | |
| Test the solver against a list of historical Wordle answers to measure success rate. | |
| """ | |
| results = [] | |
| total_guesses = 0 | |
| successes = 0 | |
| print(f"Testing solver on {len(testWords)} historical Wordle answers...") | |
| print("=" * 60) | |
| for i, word in enumerate(testWords, 1): | |
| print(f"Test {i}/{len(testWords)}: {word.upper()}") | |
| # Use the existing solveWordle function but capture results | |
| candidates = allWords.copy() | |
| guesses = [] | |
| solved = False | |
| # First guess: always "arise" | |
| firstGuess = "arise" | |
| guesses.append(firstGuess) | |
| feedback = getFeedback(firstGuess, word) | |
| if feedback == 242: # All green | |
| solved = True | |
| num_guesses = 1 | |
| else: | |
| candidates = filterCandidates(candidates, firstGuess, feedback) | |
| # Remaining guesses | |
| for round_num in range(2, maxGuesses + 1): | |
| if len(candidates) == 0: | |
| break | |
| if len(candidates) == 1: | |
| nextGuess = list(candidates)[0] | |
| else: | |
| nextGuess = bestGuessVectorized(candidates, allWords) | |
| guesses.append(nextGuess) | |
| feedback = getFeedback(nextGuess, word) | |
| if feedback == 242: # All green | |
| solved = True | |
| num_guesses = round_num | |
| break | |
| candidates = filterCandidates(candidates, nextGuess, feedback) | |
| else: | |
| num_guesses = maxGuesses | |
| # Record results | |
| result = { | |
| 'word': word, | |
| 'solved': solved, | |
| 'guesses': num_guesses, | |
| 'guess_sequence': guesses[:num_guesses] if solved else guesses | |
| } | |
| results.append(result) | |
| if solved: | |
| successes += 1 | |
| total_guesses += num_guesses | |
| print(f" ✓ Solved in {num_guesses} guesses: {' → '.join(g.upper() for g in result['guess_sequence'])}") | |
| else: | |
| print(f" ✗ Failed to solve: {' → '.join(g.upper() for g in result['guess_sequence'])}") | |
| # Calculate and display statistics | |
| success_rate = (successes / len(testWords)) * 100 | |
| avg_guesses = total_guesses / successes if successes > 0 else 0 | |
| print("\n" + "=" * 60) | |
| print("RESULTS SUMMARY") | |
| print("=" * 60) | |
| print(f"Total tests: {len(testWords)}") | |
| print(f"Successes: {successes}") | |
| print(f"Failures: {len(testWords) - successes}") | |
| print(f"Success rate: {success_rate:.1f}%") | |
| print(f"Average guesses (for successful solves): {avg_guesses:.2f}") | |
| # Guess distribution | |
| guess_distribution = {i: 0 for i in range(1, maxGuesses + 1)} | |
| for result in results: | |
| if result['solved']: | |
| guess_distribution[result['guesses']] += 1 | |
| print(f"\nGuess distribution:") | |
| for guesses in range(1, maxGuesses + 1): | |
| count = guess_distribution[guesses] | |
| percentage = (count / successes) * 100 if successes > 0 else 0 | |
| print(f" {guesses} guesses: {count:3d} ({percentage:4.1f}%)") | |
| return results | |
| def loadHistoricalWordles(filename: str = "all_historical_wordles.txt"): | |
| """ | |
| Load historical Wordle answers from a file. | |
| """ | |
| try: | |
| with open(filename, 'r') as f: | |
| words = [line.strip().lower() for line in f if line.strip()] | |
| print(f"Loaded {len(words)} historical Wordle answers from {filename}") | |
| return words | |
| except FileNotFoundError: | |
| print(f"File {filename} not found. You can create it with historical Wordle answers.") | |
| return [] | |
| app = Flask(__name__) | |
| # Global state to track the game | |
| game_state = { | |
| 'candidates': POSSIBLE_WORDS.copy(), | |
| 'current_word': 'arise', | |
| 'guesses': [], | |
| 'round': 0 | |
| } | |
| def home(): | |
| return render_template('index.html') | |
| def get_next_word(): | |
| global game_state | |
| data = request.json | |
| feedback_string = data.get('feedback', '') | |
| # Convert feedback string (byg) to our integer format | |
| feedback_int = 0 | |
| for char in feedback_string: | |
| if char == 'b': | |
| feedback_int = feedback_int * 3 + 0 # absent | |
| elif char == 'y': | |
| feedback_int = feedback_int * 3 + 1 # present | |
| elif char == 'g': | |
| feedback_int = feedback_int * 3 + 2 # correct | |
| # Filter candidates based on feedback | |
| current_word = game_state['current_word'] | |
| game_state['candidates'] = filterCandidates(game_state['candidates'], current_word, feedback_int) | |
| game_state['guesses'].append(current_word) | |
| game_state['round'] += 1 | |
| # Get next best word | |
| if len(game_state['candidates']) == 0: | |
| return jsonify({'word': 'ERROR', 'candidates_remaining': 0}) | |
| elif len(game_state['candidates']) == 1: | |
| next_word = list(game_state['candidates'])[0] | |
| else: | |
| next_word = bestGuessVectorized(game_state['candidates'], POSSIBLE_WORDS) | |
| game_state['current_word'] = next_word | |
| return jsonify({ | |
| 'word': next_word.upper(), | |
| 'candidates_remaining': len(game_state['candidates']) | |
| }) | |
| def reset_game(): | |
| global game_state | |
| game_state = { | |
| 'candidates': POSSIBLE_WORDS.copy(), | |
| 'current_word': 'arise', | |
| 'guesses': [], | |
| 'round': 0 | |
| } | |
| return jsonify({'word': 'ARISE'}) | |
| if __name__ == '__main__': | |
| port = int(os.environ.get("PORT", 7860)) | |
| app.run(debug=True, host='0.0.0.0', port=port) | |