Skip to content

Instantly share code, notes, and snippets.

@indyone
Last active January 23, 2022 17:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indyone/3211267ebacdbe5e48228592b81b05ba to your computer and use it in GitHub Desktop.
Save indyone/3211267ebacdbe5e48228592b81b05ba to your computer and use it in GitHub Desktop.
Wordle Solver
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Playwright;
public class Program
{
public static async Task Main(string[] args)
{
if (!File.Exists("words"))
{
var httpClient = new HttpClient();
var content = await httpClient.GetStringAsync("https://github.com/dwyl/english-words/raw/master/words_alpha.txt");
File.WriteAllText("words", content);
}
var possibleWords = File.ReadAllLines("words").Where(w => w.Length == 5).Select(word => word.ToLowerInvariant().Trim()).ToArray();
var startWords = new[] { "aargh", "aback", "abaft", "aboon", "about", "above", "abuse", "accel", "acute", "adieu", "adios", "admit", "adopt", "adown", "adult", "afoot", "afore", "afoul", "after", "again", "agape", "agent", "agogo", "agone", "agree", "ahead", "ahull", "alack", "alcon", "alife", "alike", "aline", "alive", "allow", "aloft", "aloha", "alone", "along", "aloof", "aloud", "alter", "amiss", "among", "amply", "amuck", "anger", "angry", "apace", "apart", "apple", "apply", "aptly", "arear", "argue", "arise", "aside", "askew", "aught", "avast", "avoid", "award", "aware", "awful", "badly", "bakaw", "bally", "basic", "basis", "basta", "beach", "begad", "begin", "below", "birth", "black", "blame", "bless", "blige", "blind", "block", "blood", "board", "bothe", "brain", "brava", "brave", "bravo", "bread", "break", "brief", "bring", "broad", "brown", "build", "burst", "buyer", "canny", "carry", "catch", "cause", "chain", "chair", "cheap", "check", "chest", "chief", "child", "china", "chook", "circa", "civil", "claim", "class", "clean", "clear", "climb", "clock", "close", "coach", "coast", "count", "court", "cover", "coyly", "crazy", "cream", "crime", "cross", "crowd", "crown", "cycle", "daily", "damme", "dance", "death", "depth", "dildo", "dimly", "dirty", "ditto", "doubt", "draft", "drama", "dream", "dress", "drily", "drink", "drive", "dryly", "dully", "early", "earth", "empty", "enemy", "enjoy", "enter", "entry", "equal", "error", "event", "exact", "exist", "extra", "faint", "faith", "false", "fatly", "fault", "feyly", "field", "fifth", "fight", "final", "first", "fitly", "floor", "focus", "force", "forte", "forth", "frame", "frank", "fresh", "frick", "front", "fruit", "fudge", "fully", "funny", "furth", "gaily", "gayly", "giant", "glass", "godly", "golly", "grand", "grant", "grass", "gratz", "great", "green", "gross", "group", "guess", "guide", "hallo", "haply", "happy", "harsh", "hasta", "havoc", "heart", "heavy", "hella", "hello", "hence", "henry", "horse", "hotel", "hotly", "house", "howay", "howdy", "hullo", "human", "huzza", "icily", "ideal", "image", "imply", "index", "infra", "inner", "input", "intl.", "issue", "japan", "jesus", "jildi", "joint", "jolly", "jones", "judge", "kapow", "knife", "large", "laugh", "laura", "laxly", "layer", "learn", "leave", "legal", "lento", "let’s", "level", "lewis", "light", "limit", "local", "loose", "lordy", "lowly", "lucky", "lunch", "madly", "magic", "major", "march", "marry", "match", "maybe", "mercy", "metal", "minor", "minus", "model", "money", "month", "moral", "motor", "mouth", "music", "naked", "nasty", "naval", "neath", "never", "newly", "night", "nobly", "noise", "north", "novel", "nurse", "occur", "oddly", "offer", "often", "one’s", "order", "other", "ought", "outer", "owner", "panel", "paper", "party", "peace", "peter", "phase", "phone", "piano", "piece", "pilot", "pitch", "place", "plain", "plane", "plant", "plate", "plonk", "plumb", "point", "pound", "power", "press", "price", "pride", "prime", "prior", "prize", "proof", "proud", "prove", "psych", "queen", "queer", "quick", "quiet", "quite", "radio", "raise", "ramen", "range", "rapid", "ratio", "reach", "ready", "redly", "refer", "relax", "reply", "right", "river", "roman", "rough", "round", "route", "royal", "rugby", "rural", "sadly", "salve", "scale", "scene", "scope", "score", "secus", "selly", "sense", "serve", "shall", "shape", "share", "sharp", "sheep", "sheer", "sheet", "shift", "shily", "shirt", "shock", "shoot", "short", "shyly", "sight", "silly", "simon", "since", "sixth", "skill", "skoal", "slash", "sleek", "sleep", "slyly", "small", "smart", "smile", "smith", "smoke", "sniff", "so-so", "solid", "solve", "sooey", "sorry", "sound", "south", "space", "spang", "spare", "speak", "speed", "spend", "spite", "split", "sport", "squad", "srsly", "staff", "stage", "stand", "stark", "start", "state", "steam", "steel", "steep", "stick", "still", "stock", "stone", "store", "stour", "study", "stuff", "style", "sugar", "super", "sweet", "table", "tally", "tanto", "taste", "teach", "terry", "thame", "thank", "theme", "there", "thiam", "thick", "thine", "thing", "think", "third", "throw", "thwap", "tight", "title", "today", "tomoz", "total", "touch", "tough", "tower", "track", "trade", "train", "treat", "trend", "trial", "truly", "trust", "truth", "twice", "twirp", "uncle", "under", "union", "unity", "until", "upper", "upset", "urban", "usual", "utter", "vague", "valid", "value", "verry", "video", "viola", "visit", "vital", "vivat", "voice", "wacko", "wahey", "wanly", "waste", "watch", "water", "wetly", "where", "which", "while", "whist", "white", "whole", "whose", "whoso", "wilma", "wirra", "woman", "woops", "world", "worry", "would", "wowie", "write", "wrong", "wryly", "yecch", "yeeha", "yeesh", "young", "yours", "youth", "yowch", "zowie" };
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = false });
var context = await browser.NewContextAsync(new BrowserNewContextOptions { RecordVideoDir = "video" });
var page = await context.NewPageAsync();
await page.GotoAsync("https://www.powerlanguage.co.uk/wordle/");
await page.ClickAsync("div.instructions:visible");
var tries = 1;
var absent = new HashSet<char>();
var present = new HashSet<(char Letter, int Index)>();
var correct = new char?[] { null, null, null, null, null };
var thisWord = startWords.PickRandomWord();
while (tries <= 6)
{
Console.WriteLine($"--- {tries} ---\n" +
$"Absent: {absent.Concat()}\n" +
$"Correct: {correct.ConcatCorrect()}\n" +
$"PossibleWords: {possibleWords.Length}\n" +
$"Word: {thisWord}");
var accepted = await EnterWordAsync(thisWord, tries);
if (!accepted)
{
await DeleteWordAsync();
tries--;
}
else if (correct.All(c => c.HasValue))
{
var correctWord = correct.ConcatCorrect();
Console.WriteLine($"Found it! The word is {correctWord}!");
await page.ScreenshotAsync(new PageScreenshotOptions { Path = $"{DateTime.Now:yyyyMMdd}_{correctWord}.png" });
Console.ReadLine();
return;
}
possibleWords = possibleWords.Where(w => w != thisWord).Where(FilterPossibleWords).ToArray();
if (possibleWords.Length == 0)
{
Console.WriteLine($"Could not solve it this time :(");
return;
}
thisWord = possibleWords.PickRandomWord();
present.Clear();
tries++;
}
await page.CloseAsync();
async Task<bool> EnterWordAsync(string word, int row)
{
foreach (var letter in word)
{
await page.ClickAsync($"#keyboard button[data-key='{letter}']");
}
await page.PressAsync("#keyboard", "Enter");
await Task.Delay(2000);
var rows = page.Locator("#board div.row").Nth(row - 1);
var tiles = await rows.Locator("div.tile").ElementHandlesAsync();
var index = 0;
foreach (var tile in tiles)
{
var letter = (await tile.InnerTextAsync()).Trim().ToLowerInvariant().FirstOrDefault();
var state = await tile.GetAttributeAsync("data-state");
switch (state)
{
case "absent":
absent.Add(letter);
break;
case "present":
present.Add((letter, index));
break;
case "correct":
correct[index] = letter;
break;
case"tbd":
return false;
}
index++;
}
return true;
}
async Task DeleteWordAsync()
{
for (int i = 0; i <= 5; i++) await page.PressAsync("#keyboard", "Backspace");
}
bool FilterPossibleWords(string word)
{
var i = 0;
foreach (var w in word)
{
if (absent.Contains(w)) return false; // if absent, skip
if (present.Contains((w, i))) return false; // if present in the same place, skip
if (correct[i] != null && correct[i] != w) return false; // if not corrent in the same place, skip
i++;
}
if (present.Count > 0 && !present.All(p => word.Contains(p.Letter))) // word should contain all present letters
{
return false;
}
return true;
}
}
}
public static class Extensions
{
private static readonly Random Random = new Random();
public static string PickRandomWord(this string[] words)
{
return words[Random.Next(0, words.Length - 1)];
}
public static string Concat(this IEnumerable<char> characters)
{
return string.Join(", ", characters);
}
public static string ConcatCorrect(this IEnumerable<char?> characters)
{
return string.Join("", characters.Select(c => c ?? ' '));
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.18.0" />
</ItemGroup>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment