diff --git a/CHROME_EXTENSION.md b/CHROME_EXTENSION.md new file mode 100644 index 0000000..2c39c16 --- /dev/null +++ b/CHROME_EXTENSION.md @@ -0,0 +1,54 @@ +# Chrome Extension Installation Guide + +## Quick Start + +1. **Download the Extension** + - Clone this repository or download the `chrome-extension` folder + +2. **Install in Chrome** + - Open Chrome and go to `chrome://extensions/` + - Enable "Developer mode" (toggle in top right) + - Click "Load unpacked" + - Select the `chrome-extension` folder + - The extension will be added to Chrome + +3. **Use the Extension** + - Visit any Wordle game (NY Times, Wordle Unlimited, etc.) + - The solver will appear automatically + - Play normally and follow the suggestions + +## Supported Wordle Sites + +- NY Times Wordle (official): `nytimes.com/games/wordle` +- Wordle Unlimited: `wordleunlimited.com` +- Wordle Game: `wordlegame.org` +- Most other Wordle clone sites + +## Features + +- 🎯 **Real-time suggestions** while you play +- 📊 **Information theory** based optimal word selection +- ⚙️ **Hard mode support** with rule enforcement +- 🌐 **Universal compatibility** across Wordle sites +- 🔄 **Automatic game state detection** and tracking + +## Privacy & Security + +- ✅ No data collection or transmission +- ✅ Works entirely in your browser +- ✅ Only activates on Wordle game pages +- ✅ No account required + +## Troubleshooting + +**Extension not appearing?** +- Refresh the Wordle page +- Check that you're on a supported site +- Verify the extension is enabled in Chrome + +**Suggestions not updating?** +- Make sure you've completed each guess +- Check the browser console for errors +- Try refreshing the page + +For more help, see the full README in the `chrome-extension` folder. \ No newline at end of file diff --git a/chrome-extension/README.md b/chrome-extension/README.md new file mode 100644 index 0000000..40ced67 --- /dev/null +++ b/chrome-extension/README.md @@ -0,0 +1,119 @@ +# Wordle Solver Chrome Extension + +A Chrome extension version of the Wordle Solver that provides AI-powered word suggestions while you play Wordle on any website. + +## Features + +- 🎯 **AI-Powered Suggestions** - Uses information theory to suggest optimal words +- 📊 **Real-time Analysis** - Automatically tracks your game progress +- 🌐 **Universal Compatibility** - Works on NY Times Wordle, Wordle Unlimited, and other Wordle sites +- ⚙️ **Hard Mode Support** - Toggle hard mode for stricter word filtering +- 🔄 **Live Updates** - Suggestions update automatically as you play + +## Installation + +### From Chrome Web Store (Coming Soon) +The extension will be available on the Chrome Web Store. + +### Manual Installation (For Development) +1. Download or clone this repository +2. Open Chrome and go to `chrome://extensions/` +3. Enable "Developer mode" (toggle in top right) +4. Click "Load unpacked" +5. Select the `chrome-extension` folder +6. The extension will be added to Chrome + +## How to Use + +1. **Install the extension** using one of the methods above +2. **Visit any Wordle game** (NY Times, Wordle Unlimited, etc.) +3. **Play normally** - the solver appears automatically +4. **Use the suggestions** to make optimal guesses +5. **Toggle settings** like Hard Mode as needed + +## Understanding Suggestions + +- **🎯 Target words** - These are possible correct answers +- **🔍 Explore words** - These provide maximum information gain +- **Numbers** - Information entropy scores (higher = better for gathering info) + +## Strategy Tips + +1. **Start strong** - Use high-entropy words like SOARE, TARES, or RALES +2. **Explore early** - Use 🔍 explore words in your first 2-3 guesses +3. **Target late** - Switch to 🎯 target words when few options remain +4. **Use Hard Mode** - Enable if playing with Wordle's hard mode rules + +## Supported Sites + +- NY Times Wordle (official) +- Wordle Unlimited +- Wordle Game +- Most Wordle clone sites + +The extension automatically detects Wordle games and activates itself. + +## Privacy + +This extension: +- ✅ Only runs on Wordle game pages +- ✅ Processes game data locally in your browser +- ✅ Does not collect or transmit any personal data +- ✅ Does not require account creation or login + +## Algorithm + +Based on information theory principles from 3Blue1Brown's Wordle analysis. The algorithm: + +1. **Calculates entropy** for each possible guess +2. **Filters words** based on previous guesses and feedback +3. **Ranks suggestions** by information value +4. **Updates in real-time** as you play + +## Development + +### File Structure +``` +chrome-extension/ +├── manifest.json # Extension configuration +├── content-script.js # Main extension logic +├── solver-algorithm.js # Core Wordle solving algorithm +├── extension-styles.css # UI styling +├── popup.html # Extension popup interface +├── popup.js # Popup functionality +├── icons/ # Extension icons +└── README.md # This file +``` + +### Building + +No build process required - this is a vanilla JavaScript extension. + +### Contributing + +1. Fork the repository +2. Create your feature branch +3. Make your changes +4. Test thoroughly on different Wordle sites +5. Submit a pull request + +## License + +MIT License - see the main repository for details. + +## Related Projects + +- [Wordle Solver Web App](https://wordlesolver.github.io) - The original web application +- [Main Repository](https://github.com/WordleSolver/WordleSolver.github.io) - Source code and documentation + +## Support + +If you encounter issues: +1. Check that you're on a supported Wordle site +2. Refresh the page to restart the extension +3. Check the browser console for error messages +4. Report issues on the GitHub repository + +--- + +**Enjoy solving Wordle optimally! 🎯** \ No newline at end of file diff --git a/chrome-extension/content-script.js b/chrome-extension/content-script.js new file mode 100644 index 0000000..9fc5809 --- /dev/null +++ b/chrome-extension/content-script.js @@ -0,0 +1,354 @@ +// Wordle Solver Chrome Extension - Content Script +// Integrates with Wordle game pages to provide solving assistance + +(function() { + 'use strict'; + + let solver = null; + let extensionUI = null; + let isGameDetected = false; + + // Initialize the extension + function initialize() { + // Check if we're on a Wordle page + if (detectWordlePage()) { + console.log('Wordle Solver: Game detected!'); + isGameDetected = true; + solver = new window.WordleSolver(); + createExtensionUI(); + observeGameChanges(); + } + } + + // Detect if we're on a Wordle game page + function detectWordlePage() { + // Check for common Wordle game indicators + const indicators = [ + // NYTimes Wordle + '[data-testid="game-app"]', + '.Game-container', + '#wordle-app-game', + // Other Wordle sites + '.board', + '.game-board', + '#game', + '.wordle-board' + ]; + + for (const indicator of indicators) { + if (document.querySelector(indicator)) { + return true; + } + } + + // Check URL patterns + return window.location.href.includes('wordle') || + window.location.href.includes('/games/wordle') || + document.title.toLowerCase().includes('wordle'); + } + + // Create the extension UI overlay + function createExtensionUI() { + // Create container + extensionUI = document.createElement('div'); + extensionUI.id = 'wordle-solver-extension'; + extensionUI.innerHTML = ` +
+

🎯 Wordle Solver

+ +
+
+
+ 2315 words remaining + +
+
+

💡 Best Words:

+
    +
  1. Analyzing game state...
  2. +
+
+
+ + +
+
+ `; + + // Add to page + document.body.appendChild(extensionUI); + + // Add event listeners + setupEventListeners(); + + // Position the UI + positionUI(); + + // Initial suggestions + updateSuggestions(); + } + + // Position the UI to avoid interfering with the game + function positionUI() { + // Try to position next to the game board + const gameContainer = document.querySelector('[data-testid="game-app"], .Game-container, #game, .board'); + + if (gameContainer) { + const rect = gameContainer.getBoundingClientRect(); + const windowWidth = window.innerWidth; + + // Position to the right if there's space, otherwise to the left + if (rect.right + 320 < windowWidth) { + extensionUI.style.left = `${rect.right + 10}px`; + } else { + extensionUI.style.right = '10px'; + } + + extensionUI.style.top = `${Math.max(10, rect.top)}px`; + } + } + + // Setup event listeners for the UI + function setupEventListeners() { + // Toggle minimize/maximize + document.getElementById('ws-toggle').addEventListener('click', () => { + const content = document.querySelector('.ws-content'); + const toggle = document.getElementById('ws-toggle'); + + if (content.style.display === 'none') { + content.style.display = 'block'; + toggle.textContent = '−'; + toggle.title = 'Minimize'; + } else { + content.style.display = 'none'; + toggle.textContent = '+'; + toggle.title = 'Expand'; + } + }); + + // Hard mode toggle + document.getElementById('ws-hard-mode').addEventListener('change', (e) => { + solver.setHardMode(e.target.checked); + updateSuggestions(); + }); + + // Reset button + document.getElementById('ws-reset').addEventListener('click', () => { + solver.reset(); + updateSuggestions(); + console.log('Wordle Solver: Reset'); + }); + + // Help button + document.getElementById('ws-help').addEventListener('click', showHelp); + } + + // Update suggestions display + function updateSuggestions() { + const suggestions = solver.getSuggestions(8); + const suggestionList = document.getElementById('ws-suggestion-list'); + const remainingSpan = document.getElementById('ws-remaining'); + + // Update remaining count + remainingSpan.textContent = `${solver.getRemainingWordCount()} words remaining`; + + // Update suggestions list + if (suggestions.length === 0) { + suggestionList.innerHTML = '
  • No suggestions available
  • '; + return; + } + + suggestionList.innerHTML = suggestions.map((suggestion, index) => { + const icon = suggestion.inWordList ? '🎯' : '🔍'; + const className = suggestion.inWordList ? 'ws-in-list' : 'ws-explore'; + return `
  • + ${index + 1}. + ${suggestion.word.toUpperCase()} + ${suggestion.entropy} + ${icon} +
  • `; + }).join(''); + } + + // Extract game state from the Wordle board + function extractGameState() { + const guesses = []; + + try { + // Try NYTimes Wordle format + const rows = document.querySelectorAll('[data-testid="row"]'); + + for (const row of rows) { + const tiles = row.querySelectorAll('[data-testid="tile"]'); + if (tiles.length !== 5) continue; + + let word = ''; + let colourMap = []; + let isComplete = true; + + for (const tile of tiles) { + const letter = tile.textContent.toLowerCase(); + if (!letter) { + isComplete = false; + break; + } + + word += letter; + + // Determine color from classes or aria-label + const evaluation = tile.getAttribute('data-state') || + tile.getAttribute('aria-label') || + tile.className; + + if (evaluation.includes('correct') || evaluation.includes('green')) { + colourMap.push(2); + } else if (evaluation.includes('present') || evaluation.includes('yellow')) { + colourMap.push(1); + } else if (evaluation.includes('absent') || evaluation.includes('gray') || evaluation.includes('grey')) { + colourMap.push(0); + } else { + isComplete = false; + break; + } + } + + if (isComplete && word.length === 5) { + guesses.push({ word, colourMap }); + } else { + break; // Stop at first incomplete row + } + } + } catch (error) { + console.log('Wordle Solver: Error extracting game state:', error); + } + + return guesses; + } + + // Observe changes to the game board + function observeGameChanges() { + const gameContainer = document.querySelector('[data-testid="game-app"], .Game-container, #game, .board') || document.body; + + const observer = new MutationObserver((mutations) => { + let shouldUpdate = false; + + for (const mutation of mutations) { + // Look for changes that might indicate a new guess + if (mutation.type === 'attributes' && + (mutation.attributeName === 'data-state' || + mutation.attributeName === 'aria-label' || + mutation.attributeName === 'class')) { + shouldUpdate = true; + break; + } + + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + shouldUpdate = true; + break; + } + } + + if (shouldUpdate) { + // Debounce updates + setTimeout(updateFromGameState, 100); + } + }); + + observer.observe(gameContainer, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['data-state', 'aria-label', 'class'] + }); + } + + // Update solver state from game + function updateFromGameState() { + const currentGuesses = extractGameState(); + + // Check if we have new guesses + if (currentGuesses.length > solver.guesses.length) { + // Reset solver and replay all guesses + solver.reset(); + + for (const guess of currentGuesses) { + solver.addGuess(guess.word, guess.colourMap); + } + + updateSuggestions(); + console.log('Wordle Solver: Updated with', currentGuesses.length, 'guesses'); + } + } + + // Show help modal + function showHelp() { + const helpModal = document.createElement('div'); + helpModal.id = 'ws-help-modal'; + helpModal.innerHTML = ` +
    +
    +

    🎯 Wordle Solver Help

    + +
    +
    +

    How it works:

    + + +

    Tips:

    + + +

    Based on information theory principles from 3Blue1Brown

    +
    +
    + `; + + document.body.appendChild(helpModal); + + document.getElementById('ws-close-help').addEventListener('click', () => { + document.body.removeChild(helpModal); + }); + + helpModal.addEventListener('click', (e) => { + if (e.target === helpModal) { + document.body.removeChild(helpModal); + } + }); + } + + // Listen for messages from popup + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === 'getStatus') { + if (isGameDetected && solver) { + sendResponse({ + wordCount: solver.getRemainingWordCount(), + guessCount: solver.guesses.length, + active: true + }); + } else { + sendResponse({ active: false }); + } + } + }); + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initialize); + } else { + initialize(); + } + + // Also try to initialize after a delay in case the game loads dynamically + setTimeout(initialize, 1000); + +})(); \ No newline at end of file diff --git a/chrome-extension/demo.html b/chrome-extension/demo.html new file mode 100644 index 0000000..190e4bf --- /dev/null +++ b/chrome-extension/demo.html @@ -0,0 +1,212 @@ + + + + + + Wordle Game Demo - Test Extension + + + +
    +
    +

    Wordle Game Demo

    +

    This page simulates a Wordle game to test the Chrome extension

    +
    + +
    +

    Testing the Chrome Extension:

    +
      +
    1. Load the Chrome extension in Developer Mode
    2. +
    3. Refresh this page
    4. +
    5. You should see the Wordle Solver appear automatically
    6. +
    7. Click the buttons below to simulate game states
    8. +
    +
    + +
    +
    +
    S
    +
    O
    +
    A
    +
    R
    +
    E
    +
    +
    +
    S
    +
    T
    +
    I
    +
    R
    +
    S
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + +
    +
    + + + + \ No newline at end of file diff --git a/chrome-extension/extension-styles.css b/chrome-extension/extension-styles.css new file mode 100644 index 0000000..e076f9e --- /dev/null +++ b/chrome-extension/extension-styles.css @@ -0,0 +1,332 @@ +/* Wordle Solver Chrome Extension Styles */ + +#wordle-solver-extension { + position: fixed; + top: 10px; + right: 10px; + width: 300px; + background: #ffffff; + border: 2px solid #d3d6da; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-family: 'Clear Sans', 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + z-index: 10000; + color: #1a1a1b; +} + +.ws-header { + background: #6aaa64; + color: white; + padding: 10px 15px; + border-radius: 6px 6px 0 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.ws-header h3 { + margin: 0; + font-size: 16px; + font-weight: 700; +} + +#ws-toggle { + background: none; + border: none; + color: white; + font-size: 18px; + font-weight: bold; + cursor: pointer; + padding: 2px 6px; + border-radius: 3px; + line-height: 1; +} + +#ws-toggle:hover { + background: rgba(255, 255, 255, 0.2); +} + +.ws-content { + padding: 15px; +} + +.ws-status { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid #d3d6da; +} + +#ws-remaining { + font-weight: 600; + color: #787c7e; + font-size: 13px; +} + +.ws-hard-mode { + display: flex; + align-items: center; + font-size: 12px; + color: #787c7e; + cursor: pointer; +} + +.ws-hard-mode input { + margin-right: 5px; +} + +.ws-suggestions h4 { + margin: 0 0 10px 0; + font-size: 14px; + font-weight: 600; + color: #1a1a1b; +} + +#ws-suggestion-list { + list-style: none; + padding: 0; + margin: 0 0 15px 0; + max-height: 200px; + overflow-y: auto; +} + +#ws-suggestion-list li { + display: flex; + align-items: center; + padding: 6px 8px; + margin-bottom: 2px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #e9ecef; + font-size: 13px; +} + +#ws-suggestion-list li:hover { + background: #e9ecef; +} + +#ws-suggestion-list li.ws-in-list { + background: #d4edda; + border-color: #c3e6cb; +} + +#ws-suggestion-list li.ws-explore { + background: #fff3cd; + border-color: #ffeaa7; +} + +.ws-rank { + font-weight: 600; + color: #6c757d; + width: 20px; + text-align: right; + margin-right: 8px; +} + +.ws-word { + font-weight: 700; + font-family: 'Clear Sans', monospace; + flex: 1; + color: #1a1a1b; + letter-spacing: 0.5px; +} + +.ws-entropy { + font-size: 11px; + color: #6c757d; + margin-right: 5px; + min-width: 35px; + text-align: right; +} + +.ws-icon { + font-size: 12px; +} + +.ws-loading { + text-align: center; + color: #6c757d; + font-style: italic; + padding: 10px; +} + +.ws-no-suggestions { + text-align: center; + color: #6c757d; + padding: 10px; +} + +.ws-controls { + display: flex; + gap: 8px; +} + +.ws-controls button { + flex: 1; + padding: 8px 12px; + border: 1px solid #d3d6da; + border-radius: 4px; + background: #f8f9fa; + color: #1a1a1b; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.1s ease; +} + +.ws-controls button:hover { + background: #e9ecef; + border-color: #adb5bd; +} + +.ws-controls button:active { + background: #dee2e6; +} + +/* Help Modal */ +#ws-help-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 10001; + display: flex; + justify-content: center; + align-items: center; +} + +.ws-modal-content { + background: white; + border-radius: 8px; + max-width: 500px; + width: 90%; + max-height: 80%; + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.ws-modal-header { + background: #6aaa64; + color: white; + padding: 15px 20px; + border-radius: 8px 8px 0 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.ws-modal-header h3 { + margin: 0; + font-size: 18px; +} + +#ws-close-help { + background: none; + border: none; + color: white; + font-size: 24px; + cursor: pointer; + padding: 0; + line-height: 1; +} + +.ws-modal-body { + padding: 20px; + font-family: 'Clear Sans', 'Helvetica Neue', Arial, sans-serif; +} + +.ws-modal-body h4 { + margin: 0 0 10px 0; + color: #1a1a1b; +} + +.ws-modal-body ul { + margin: 0 0 15px 0; + padding-left: 20px; +} + +.ws-modal-body li { + margin-bottom: 5px; + line-height: 1.4; +} + +.ws-modal-body p { + margin: 15px 0 0 0; + text-align: center; + color: #6c757d; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + #wordle-solver-extension { + width: 280px; + right: 5px; + top: 5px; + } + + .ws-content { + padding: 12px; + } + + .ws-modal-content { + width: 95%; + margin: 10px; + } +} + +/* Dark mode support for sites that use it */ +@media (prefers-color-scheme: dark) { + #wordle-solver-extension { + background: #121213; + border-color: #3a3a3c; + color: #ffffff; + } + + .ws-content { + background: #121213; + } + + #ws-suggestion-list li { + background: #1a1a1b; + border-color: #3a3a3c; + color: #ffffff; + } + + #ws-suggestion-list li:hover { + background: #2a2a2b; + } + + #ws-suggestion-list li.ws-in-list { + background: #0d4a1f; + border-color: #1a6332; + } + + #ws-suggestion-list li.ws-explore { + background: #4a3d0d; + border-color: #6b5a1a; + } + + .ws-controls button { + background: #1a1a1b; + border-color: #3a3a3c; + color: #ffffff; + } + + .ws-controls button:hover { + background: #2a2a2b; + } + + .ws-modal-content { + background: #121213; + color: #ffffff; + } + + .ws-modal-body { + background: #121213; + } +} \ No newline at end of file diff --git a/chrome-extension/icons/icon128.png b/chrome-extension/icons/icon128.png new file mode 100644 index 0000000..b30cf7d --- /dev/null +++ b/chrome-extension/icons/icon128.png @@ -0,0 +1 @@ +Placeholder for 128px icon - actual icons would be PNG/SVG files \ No newline at end of file diff --git a/chrome-extension/icons/icon16.png b/chrome-extension/icons/icon16.png new file mode 100644 index 0000000..42650cf --- /dev/null +++ b/chrome-extension/icons/icon16.png @@ -0,0 +1 @@ +Placeholder for 16px icon - actual icons would be PNG/SVG files \ No newline at end of file diff --git a/chrome-extension/icons/icon48.png b/chrome-extension/icons/icon48.png new file mode 100644 index 0000000..03c9167 --- /dev/null +++ b/chrome-extension/icons/icon48.png @@ -0,0 +1 @@ +Placeholder for 48px icon - actual icons would be PNG/SVG files \ No newline at end of file diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json new file mode 100644 index 0000000..6427d56 --- /dev/null +++ b/chrome-extension/manifest.json @@ -0,0 +1,32 @@ +{ + "manifest_version": 3, + "name": "Wordle Solver Assistant", + "version": "1.0", + "description": "AI-powered Wordle solver that provides optimal word suggestions while you play", + "permissions": [ + "activeTab" + ], + "content_scripts": [ + { + "matches": [ + "*://*.nytimes.com/games/wordle/*", + "*://www.nytimes.com/games/wordle/*", + "*://wordlegame.org/*", + "*://wordle.com/*", + "*://wordleunlimited.com/*", + "*://wordleonline.com/*" + ], + "js": ["solver-algorithm.js", "content-script.js"], + "css": ["extension-styles.css"] + } + ], + "action": { + "default_popup": "popup.html", + "default_title": "Wordle Solver Assistant" + }, + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } +} \ No newline at end of file diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html new file mode 100644 index 0000000..38311ad --- /dev/null +++ b/chrome-extension/popup.html @@ -0,0 +1,217 @@ + + + + + + Wordle Solver Assistant + + + + + + + + + + + + \ No newline at end of file diff --git a/chrome-extension/popup.js b/chrome-extension/popup.js new file mode 100644 index 0000000..9c41d3c --- /dev/null +++ b/chrome-extension/popup.js @@ -0,0 +1,178 @@ +// Popup script for Wordle Solver Chrome Extension + +document.addEventListener('DOMContentLoaded', function() { + checkActiveTab(); + setupEventListeners(); +}); + +function setupEventListeners() { + // Help link + document.getElementById('help-link').addEventListener('click', function(e) { + e.preventDefault(); + showHelp(); + }); +} + +// Check if extension is active on current tab +async function checkActiveTab() { + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + + if (isWordlePage(tab.url)) { + // Try to get status from content script + try { + const response = await chrome.tabs.sendMessage(tab.id, { action: 'getStatus' }); + if (response) { + showActiveStatus(tab.url, response); + } else { + showInactive(); + } + } catch (error) { + // Content script might not be loaded yet + showInactive(); + } + } else { + showInactive(); + } + } catch (error) { + console.error('Error checking active tab:', error); + showInactive(); + } +} + +// Check if URL is a Wordle page +function isWordlePage(url) { + if (!url) return false; + + const wordlePatterns = [ + 'nytimes.com/games/wordle', + 'wordlegame.org', + 'wordle.com', + 'wordleunlimited.com', + 'wordleonline.com' + ]; + + return wordlePatterns.some(pattern => url.includes(pattern)) || + url.toLowerCase().includes('wordle'); +} + +// Show active status +function showActiveStatus(url, status) { + document.getElementById('not-active').style.display = 'none'; + document.getElementById('status-section').style.display = 'block'; + + // Extract site name from URL + const siteName = extractSiteName(url); + document.getElementById('current-site').textContent = siteName; + + // Update status if available + if (status.wordCount !== undefined) { + document.getElementById('word-count').textContent = status.wordCount; + } + if (status.guessCount !== undefined) { + document.getElementById('guess-count').textContent = status.guessCount; + } +} + +// Show inactive status +function showInactive() { + document.getElementById('not-active').style.display = 'block'; + document.getElementById('status-section').style.display = 'none'; +} + +// Extract readable site name from URL +function extractSiteName(url) { + try { + const hostname = new URL(url).hostname; + + if (hostname.includes('nytimes.com')) return 'NY Times Wordle'; + if (hostname.includes('wordlegame.org')) return 'Wordle Game'; + if (hostname.includes('wordleunlimited.com')) return 'Wordle Unlimited'; + if (hostname.includes('wordleonline.com')) return 'Wordle Online'; + + return hostname.replace('www.', ''); + } catch (error) { + return 'Wordle Site'; + } +} + +// Show help information +function showHelp() { + const helpContent = ` +
    +

    🎯 Wordle Solver Help

    + +

    Understanding Suggestions:

    + + +

    Strategy Tips:

    + + +

    Supported Sites:

    + + +

    Algorithm based on information theory principles from 3Blue1Brown's Wordle analysis.

    +
    + `; + + // Create modal overlay + const modal = document.createElement('div'); + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + `; + + const content = document.createElement('div'); + content.style.cssText = ` + background: white; + border-radius: 8px; + padding: 20px; + max-width: 400px; + width: 90%; + position: relative; + font-family: inherit; + `; + + content.innerHTML = helpContent + ` + + `; + + modal.appendChild(content); + document.body.appendChild(modal); + + // Close handlers + document.getElementById('close-help').onclick = () => document.body.removeChild(modal); + modal.onclick = (e) => { + if (e.target === modal) document.body.removeChild(modal); + }; +} \ No newline at end of file diff --git a/chrome-extension/solver-algorithm.js b/chrome-extension/solver-algorithm.js new file mode 100644 index 0000000..341c90e --- /dev/null +++ b/chrome-extension/solver-algorithm.js @@ -0,0 +1,129 @@ +// Core Wordle Solver Algorithm +// Adapted from the main WordleSolver.github.io application + +// Word list - official Wordle words +const possibleWords = ["aback","abase","abate","abbey","abbot","abhor","abide","abled","abode","abort","about","above","abuse","abyss","acorn","acrid","actor","acute","adage","adapt","adept","admin","admit","adobe","adopt","adore","adorn","adult","affix","afire","afoot","afoul","after","again","agape","agate","agent","agile","aging","aglow","agony","agree","ahead","aider","aisle","alarm","album","alert","algae","alibi","alien","align","alike","alive","allay","alley","allot","allow","alloy","aloft","alone","along","aloof","aloud","alpha","altar","alter","amass","amaze","amber","amble","amend","amiss","amity","among","ample","amply","amuse","angel","anger","angle","angry","angst","anime","ankle","annex","annoy","annul","anode","antic","anvil","aorta","apart","aphid","aping","apnea","apple","apply","apron","aptly","arbor","ardor","arena","argue","arise","armor","aroma","arose","array","arrow","arson","artsy","ascot","ashen","aside","askew","assay","asset","atoll","atone","attic","audio","audit","augur","aunty","avail","avert","avian","avoid","await","awake","award","aware","awash","awful","awoke","axial","axiom","axion","azure","bacon","badge","badly","bagel","baggy","baker","baler","balmy","banal","banjo","barge","baron","basal","basic","basil","basin","basis","baste","batch","bathe","baton","batty","bawdy","bayou","beach","beady","beard","beast","beech","beefy","befit","began","begat","beget","begin","begun","being","belch","belie","belle","belly","below","bench","beret","berry","berth","beset","betel","bevel","bezel","bible","bicep","biddy","bigot","bilge","billy","binge","bingo","biome","birch","birth","bison","bitty","black","blade","blame","bland","blank","blare","blast","blaze","bleak","bleat","bleed","bleep","blend","bless","blimp","blind","blink","bliss","blitz","bloat","block","bloke","blond","blood","bloom","blown","bluer","bluff","blunt","blurb","blurt","blush","board","boast","bobby","boney","bongo","bonus","booby","boost","booth","booty","booze","boozy","borax","borne","bosom","bossy","botch","bough","boule","bound","bowel","boxer","brace","braid","brain","brake","brand","brash","brass","brave","bravo","brawl","brawn","bread","break","breed","briar","bribe","brick","bride","brief","brine","bring","brink","briny","brisk","broad","broil","broke","brood","brook","broom","broth","brown","brunt","brush","brute","buddy","budge","buggy","bugle","build","built","bulge","bulky","bully","bunch","bunny","burly","burnt","burst","bused","bushy","butch","butte","buxom","buyer","bylaw","cabal","cabby","cabin","cable","cacao","cache","cacti","caddy","cadet","cagey","cairn","camel","cameo","canal","candy","canny","canoe","canon","caper","caput","carat","cargo","carol","carry","carve","caste","catch","cater","catty","caulk","cause","cavil","cease","cedar","cello","chafe","chaff","chain","chair","chalk","champ","chant","chaos","chard","charm","chart","chase","chasm","cheap","cheat","check","cheek","cheer","chess","chest","chick","chide","chief","child","chili","chill","chime","china","chirp","chock","choir","choke","chord","chore","chose","chuck","chump","chunk","churn","chute","cider","cigar","cinch","circa","civic","civil","clack","claim","clamp","clang","clank","clash","clasp","class","clean","clear","cleat","cleft","clerk","click","cliff","climb","cling","clink","cloak","clock","clone","close","cloth","cloud","clout","clove","clown","cluck","clued","clump","clung","coach","coast","cobra","cocoa","colon","color","comet","comfy","comic","comma","conch","condo","conic","copse","coral","corer","corny","couch","cough","could","count","coupe","court","coven","cover","covet","covey","cower","coyly","crack","craft","cramp","crane","crank","crash","crass","crate","crave","crawl","craze","crazy","creak","cream","credo","creed","creek","creep","creme","crepe","crept","cress","crest","crick","cried","crier","crime","crimp","crisp","croak","crock","crone","crony","crook","cross","croup","crowd","crown","crude","cruel","crumb","crump","crush","crust","crypt","cubic","cumin","curio","curly","curry","curse","curve","curvy","cutie","cyber","cycle","cynic","daddy","daily","dairy","daisy","dally","dance","dandy","datum","daunt","dealt","death","debar","debit","debug","debut","decal","decay","decor","decoy","decry","defer","deign","deity","delay","delta","delve","demon","demur","denim","dense","depot","depth","derby","deter","detox","deuce","devil","diary","dicey","digit","dilly","dimly","diner","dingo","dingy","diode","dirge","dirty","disco","ditch","ditto","ditty","diver","dizzy","dodge","dodgy","dogma","doing","dolly","donor","donut","dopey","doubt","dough","dowdy","dowel","downy","dowry","dozen","draft","drain","drake","drama","drank","drape","drawl","drawn","dread","dream","dress","dried","drier","drift","drill","drink","drive","droit","droll","drone","drool","droop","dross","drove","drown","druid","drunk","dryer","dryly","duchy","dully","dummy","dumpy","dunce","dusky","dusty","dutch","duvet","dwarf","dwell","dwelt","dying","eager","eagle","early","earth","easel","eaten","eater","ebony","eclat","edict","edify","eerie","egret","eight","eject","eking","elate","elbow","elder","elect","elegy","elfin","elide","elite","elope","elude","email","embed","ember","emcee","empty","enact","endow","enema","enemy","enjoy","ennui","ensue","enter","entry","envoy","epoch","epoxy","equal","equip","erase","erect","erode","error","erupt","essay","ester","ether","ethic","ethos","etude","evade","event","every","evict","evoke","exact","exalt","excel","exert","exile","exist","expel","extol","extra","exult","eying","fable","facet","faint","fairy","faith","false","fancy","fanny","farce","fatal","fatty","fault","fauna","favor","feast","fecal","feign","fella","felon","femme","femur","fence","feral","ferry","fetal","fetch","fetid","fetus","fever","fewer","fiber","ficus","field","fiend","fiery","fifth","fifty","fight","filer","filet","filly","filmy","filth","final","finch","finer","first","fishy","fixer","fizzy","fjord","flack","flail","flair","flake","flaky","flame","flank","flare","flash","flask","fleck","fleet","flesh","flick","flier","fling","flint","flirt","float","flock","flood","floor","flora","floss","flour","flout","flown","fluff","fluid","fluke","flume","flung","flunk","flush","flute","flyer","foamy","focal","focus","foggy","foist","folio","folly","foray","force","forge","forgo","forte","forth","forty","forum","found","foyer","frail","frame","frank","fraud","freak","freed","freer","fresh","friar","fried","frill","frisk","fritz","frock","frond","front","frost","froth","frown","froze","fruit","fudge","fugue","fully","fungi","funky","funny","furor","furry","fussy","fuzzy","gaffe","gaily","gamer","gamma","gamut","gassy","gaudy","gauge","gaunt","gauze","gavel","gawky","gayer","gayly","gazer","gecko","geeky","geese","genie","genre","ghost","ghoul","giant","giddy","gipsy","girly","girth","given","giver","glade","gland","glare","glass","glaze","gleam","glean","glide","glint","gloat","globe","gloom","glory","gloss","glove","glyph","gnash","gnome","godly","going","golem","golly","gonad","goner","goody","gooey","goofy","goose","gorge","gouge","gourd","grace","grade","graft","grail","grain","grand","grant","grape","graph","grasp","grass","grate","grave","gravy","graze","great","greed","green","greet","grief","grill","grime","grimy","grind","gripe","groan","groin","groom","grope","gross","group","grout","grove","growl","grown","gruel","gruff","grunt","guard","guava","guess","guest","guide","guild","guile","guilt","guise","gulch","gully","gumbo","gummy","guppy","gusto","gusty","gypsy","habit","hairy","halve","handy","happy","hardy","harem","harpy","harry","harsh","haste","hasty","hatch","hater","haunt","haute","haven","havoc","hazel","heady","heard","heart","heath","heave","heavy","hedge","hefty","heist","helix","hello","hence","heron","hilly","hinge","hippo","hippy","hitch","hoard","hobby","hoist","holly","homer","honey","honor","horde","horny","horse","hotel","hotly","hound","house","hovel","hover","howdy","human","humid","humor","humph","humus","hunch","hunky","hurry","husky","hussy","hutch","hydro","hyena","hymen","hyper","icily","icing","ideal","idiom","idiot","idler","idyll","igloo","iliac","image","imbue","impel","imply","inane","inbox","incur","index","inept","inert","infer","ingot","inlay","inlet","inner","input","inter","intro","ionic","irate","irony","islet","issue","itchy","ivory","jaunt","jazzy","jelly","jerky","jetty","jewel","jiffy","joint","joist","joker","jolly","joust","judge","juice","juicy","jumbo","jumpy","junta","junto","juror","kappa","karma","kayak","kebab","khaki","kinky","kiosk","kitty","knack","knave","knead","kneed","kneel","knelt","knife","knock","knoll","known","koala","krill","label","labor","laden","ladle","lager","lance","lanky","lapel","lapse","large","larva","lasso","latch","later","lathe","latte","laugh","layer","leach","leafy","leaky","leant","leapt","learn","lease","leash","least","leave","ledge","leech","leery","lefty","legal","leggy","lemon","lemur","leper","level","lever","libel","liege","light","liken","lilac","limbo","limit","linen","liner","lingo","lipid","lithe","liver","livid","llama","loamy","loath","lobby","local","locus","lodge","lofty","logic","login","loopy","loose","lorry","loser","louse","lousy","lover","lower","lowly","loyal","lucid","lucky","lumen","lumpy","lunar","lunch","lunge","lupus","lurch","lurid","lusty","lying","lymph","lyric","macaw","macho","macro","madam","madly","mafia","magic","magma","maize","major","maker","mambo","mamma","mammy","manga","mange","mango","mangy","mania","manic","manly","manor","maple","march","marry","marsh","mason","masse","match","matey","mauve","maxim","maybe","mayor","mealy","meant","meaty","mecca","medal","media","medic","melee","melon","mercy","merge","merit","merry","metal","meter","metro","micro","midge","midst","might","milky","mimic","mince","miner","minim","minor","minty","minus","mirth","miser","missy","mocha","modal","model","modem","mogul","moist","molar","moldy","money","month","moody","moose","moral","moron","morph","mossy","motel","motif","motor","motto","moult","mound","mount","mourn","mouse","mouth","mover","movie","mower","mucky","mucus","muddy","mulch","mummy","munch","mural","murky","mushy","music","musky","musty","myrrh","nadir","naive","nanny","nasal","nasty","natal","naval","navel","needy","neigh","nerdy","nerve","never","newer","newly","nicer","niche","niece","night","ninja","ninny","ninth","noble","nobly","noise","noisy","nomad","noose","north","nosey","notch","novel","nudge","nurse","nutty","nylon","nymph","oaken","obese","occur","ocean","octal","octet","odder","oddly","offal","offer","often","olden","older","olive","ombre","omega","onion","onset","opera","opine","opium","optic","orbit","order","organ","other","otter","ought","ounce","outdo","outer","outgo","ovary","ovate","overt","ovine","ovoid","owing","owner","oxide","ozone","paddy","pagan","paint","paler","palsy","panel","panic","pansy","papal","paper","parer","parka","parry","parse","party","pasta","paste","pasty","patch","patio","patsy","patty","pause","payee","payer","peace","peach","pearl","pecan","pedal","penal","pence","penne","penny","perch","peril","perky","pesky","pesto","petal","petty","phase","phone","phony","photo","piano","picky","piece","piety","piggy","pilot","pinch","piney","pinky","pinto","piper","pique","pitch","pithy","pivot","pixel","pixie","pizza","place","plaid","plain","plait","plane","plank","plant","plate","plaza","plead","pleat","plied","plier","pluck","plumb","plume","plump","plunk","plush","poesy","point","poise","poker","polar","polka","polyp","pooch","poppy","porch","poser","posit","posse","pouch","pound","pouty","power","prank","prawn","preen","press","price","prick","pride","pried","prime","primo","print","prior","prism","privy","prize","probe","prone","prong","proof","prose","proud","prove","prowl","proxy","prude","prune","psalm","pubic","pudgy","puffy","pulpy","pulse","punch","pupil","puppy","puree","purer","purge","purse","pushy","putty","pygmy","quack","quail","quake","qualm","quark","quart","quash","quasi","queen","queer","quell","query","quest","queue","quick","quiet","quill","quilt","quirk","quite","quota","quote","quoth","rabbi","rabid","racer","radar","radii","radio","rainy","raise","rajah","rally","ralph","ramen","ranch","randy","range","rapid","rarer","raspy","ratio","ratty","raven","rayon","razor","reach","react","ready","realm","rearm","rebar","rebel","rebus","rebut","recap","recur","recut","reedy","refer","refit","regal","rehab","reign","relax","relay","relic","remit","renal","renew","repay","repel","reply","rerun","reset","resin","retch","retro","retry","reuse","revel","revue","rhino","rhyme","rider","ridge","rifle","right","rigid","rigor","rinse","ripen","riper","risen","riser","risky","rival","river","rivet","roach","roast","robin","robot","rocky","rodeo","roger","rogue","roomy","roost","rotor","rouge","rough","round","rouse","route","rover","rowdy","rower","royal","ruddy","ruder","rugby","ruler","rumba","rumor","rupee","rural","rusty","sadly","safer","saint","salad","sally","salon","salsa","salty","salve","salvo","sandy","saner","sappy","sassy","satin","satyr","sauce","saucy","sauna","saute","savor","savoy","savvy","scald","scale","scalp","scaly","scamp","scant","scare","scarf","scary","scene","scent","scion","scoff","scold","scone","scoop","scope","score","scorn","scour","scout","scowl","scram","scrap","scree","screw","scrub","scrum","scuba","sedan","seedy","segue","seize","semen","sense","sepia","serif","serum","serve","setup","seven","sever","sewer","shack","shade","shady","shaft","shake","shaky","shale","shall","shalt","shame","shank","shape","shard","share","shark","sharp","shave","shawl","shear","sheen","sheep","sheer","sheet","sheik","shelf","shell","shied","shift","shine","shiny","shire","shirk","shirt","shoal","shock","shone","shook","shoot","shore","shorn","short","shout","shove","shown","showy","shrew","shrub","shrug","shuck","shunt","shush","shyly","siege","sieve","sight","sigma","silky","silly","since","sinew","singe","siren","sissy","sixth","sixty","skate","skier","skiff","skill","skimp","skirt","skulk","skull","skunk","slack","slain","slang","slant","slash","slate","sleek","sleep","sleet","slept","slice","slick","slide","slime","slimy","sling","slink","sloop","slope","slosh","sloth","slump","slung","slunk","slurp","slush","slyly","smack","small","smart","smash","smear","smell","smelt","smile","smirk","smite","smith","smock","smoke","smoky","smote","snack","snail","snake","snaky","snare","snarl","sneak","sneer","snide","sniff","snipe","snoop","snore","snort","snout","snowy","snuck","snuff","soapy","sober","soggy","solar","solid","solve","sonar","sonic","sooth","sooty","sorry","sound","south","sower","space","spade","spank","spare","spark","spasm","spawn","speak","spear","speck","speed","spell","spelt","spend","spent","sperm","spice","spicy","spied","spiel","spike","spiky","spill","spilt","spine","spiny","spire","spite","splat","split","spoil","spoke","spoof","spook","spool","spoon","spore","sport","spout","spray","spree","sprig","spunk","spurn","spurt","squad","squat","squib","stack","staff","stage","staid","stain","stair","stake","stale","stalk","stall","stamp","stand","stank","stare","stark","start","stash","state","stave","stead","steak","steal","steam","steed","steel","steep","steer","stein","stern","stick","stiff","still","stilt","sting","stink","stint","stock","stoic","stoke","stole","stomp","stone","stony","stood","stool","stoop","store","stork","storm","story","stout","stove","strap","straw","stray","strip","strut","stuck","study","stuff","stump","stung","stunk","stunt","style","suave","sugar","suing","suite","sulky","sully","sumac","sunny","super","surer","surge","surly","sushi","swami","swamp","swarm","swash","swath","swear","sweat","sweep","sweet","swell","swept","swift","swill","swine","swing","swirl","swish","swoon","swoop","sword","swore","sworn","swung","synod","syrup","tabby","table","taboo","tacit","tacky","taffy","taint","taken","taker","tally","talon","tamer","tango","tangy","taper","tapir","tardy","tarot","taste","tasty","tatty","taunt","tawny","teach","teary","tease","teddy","teeth","tempo","tenet","tenor","tense","tenth","tepee","tepid","terra","terse","testy","thank","theft","their","theme","there","these","theta","thick","thief","thigh","thing","think","third","thong","thorn","those","three","threw","throb","throw","thrum","thumb","thump","thyme","tiara","tibia","tidal","tiger","tight","tilde","timer","timid","tipsy","titan","tithe","title","toast","today","toddy","token","tonal","tonga","tonic","tooth","topaz","topic","torch","torso","torus","total","totem","touch","tough","towel","tower","toxic","toxin","trace","track","tract","trade","trail","train","trait","tramp","trash","trawl","tread","treat","trend","triad","trial","tribe","trice","trick","tried","tripe","trite","troll","troop","trope","trout","trove","truce","truck","truer","truly","trump","trunk","truss","trust","truth","tryst","tubal","tuber","tulip","tulle","tumor","tunic","turbo","tutor","twang","tweak","tweed","tweet","twice","twine","twirl","twist","twixt","tying","udder","ulcer","ultra","umbra","uncle","uncut","under","undid","undue","unfed","unfit","unify","union","unite","unity","unlit","unmet","unset","untie","until","unwed","unzip","upper","upset","urban","urine","usage","usher","using","usual","usurp","utile","utter","vague","valet","valid","valor","value","valve","vapid","vapor","vault","vaunt","vegan","venom","venue","verge","verse","verso","verve","vicar","video","vigil","vigor","villa","vinyl","viola","viper","viral","virus","visit","visor","vista","vital","vivid","vixen","vocal","vodka","vogue","voice","voila","vomit","voter","vouch","vowel","vying","wacky","wafer","wager","wagon","waist","waive","waltz","warty","waste","watch","water","waver","waxen","weary","weave","wedge","weedy","weigh","weird","welch","welsh","whack","whale","wharf","wheat","wheel","whelp","where","which","whiff","while","whine","whiny","whirl","whisk","white","whole","whoop","whose","widen","wider","widow","width","wield","wight","willy","wimpy","wince","winch","windy","wiser","wispy","witch","witty","woken","woman","women","woody","wooer","wooly","woozy","wordy","world","worry","worse","worst","worth","would","wound","woven","wrack","wrath","wreak","wreck","wrest","wring","wrist","write","wrong","wrote","wrung","wryly","yacht","yearn","yeast","yield","young","youth","zebra","zesty","zonal"]; + +// Valid words list (subset for input validation) +const validWords = possibleWords; // For simplicity, using same list + +// Calculate entropy for information theory-based suggestions +function entrapyArray(listOfLengths) { + const listSum = listOfLengths.reduce((acc, cur) => acc + cur, 0); + return listOfLengths.reduce((acc, cur) => { + if (cur === 0) return acc; + const probability = cur / listSum; + return acc - probability * Math.log2(probability); + }, 0); +} + +// Create color map for a guess against a word +// Returns: 0 = grey (not in word), 1 = yellow (wrong position), 2 = green (correct position) +function createColourMap(guess, word) { + const guessArr = guess.split(""); + const wordArr = word.split(""); + return guessArr.map((letter, index) => { + if (letter === wordArr[index]) return 2; + + let lettersInWord = 0; + wordArr.forEach(char => char === letter ? lettersInWord += 1 : null); + + for (let i = 0; i < index; i++) { + if (guessArr[i] === letter) lettersInWord -= 1; + } + + for (let i = index + 1; i < 5; i++) { + if (guessArr[i] === letter && wordArr[i] === letter) lettersInWord -= 1; + } + + if (0 < lettersInWord) return 1; + + return 0; + }); +} + +// Find remaining possible words based on guess and feedback +function findRemainingWords(guess, colourMap, wordList) { + const colourMapStr = colourMap.join(""); + return wordList.filter(word => { + return colourMapStr === createColourMap(guess, word).join(""); + }); +} + +// Generate suggestion list with entropy scores +function makeSuggestionList(wordList, hardMode = false) { + const frequenciesMap = new Map(); + + // Calculate frequency map for each possible guess + possibleWords.forEach(possibleGuess => { + const frequencies = []; + + for (let i = 0; i < 243; i++) { // 3^5 possible color combinations + frequencies.push(0); + } + + wordList.forEach(possibleWord => { + const colourMap = createColourMap(possibleGuess, possibleWord); + const index = colourMap.reduce((acc, cur, idx) => acc + cur * Math.pow(3, 4 - idx), 0); + frequencies[index] += 1; + }); + + const entrapyVal = entrapyArray(frequencies); + frequenciesMap.set(possibleGuess, entrapyVal); + }); + + // Create sorted suggestion list + let suggestionList = Array.from(frequenciesMap.entries()).map(([word, entropy]) => { + const inWordList = wordList.includes(word); + return [word, entropy, inWordList]; + }).sort((a, b) => b[1] - a[1]); + + return suggestionList; +} + +// WordleSolver class for the Chrome extension +class WordleSolver { + constructor() { + this.wordList = [...possibleWords]; + this.hardMode = false; + this.guesses = []; + } + + // Add a guess and get updated suggestions + addGuess(word, colourMap) { + this.guesses.push({ word, colourMap }); + this.wordList = findRemainingWords(word, colourMap, this.wordList); + return this.getSuggestions(); + } + + // Get current suggestions + getSuggestions(count = 10) { + const suggestions = makeSuggestionList(this.wordList, this.hardMode); + return suggestions.slice(0, count).map(([word, entropy, inWordList]) => ({ + word, + entropy: entropy.toFixed(3), + inWordList + })); + } + + // Reset the solver + reset() { + this.wordList = [...possibleWords]; + this.guesses = []; + } + + // Set hard mode + setHardMode(enabled) { + this.hardMode = enabled; + } + + // Get remaining word count + getRemainingWordCount() { + return this.wordList.length; + } +} + +// Export for use in content script +if (typeof window !== 'undefined') { + window.WordleSolver = WordleSolver; +} \ No newline at end of file