using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using CsvHelper; using CsvHelper.Configuration; using MathNet.Numerics.Statistics; using NUnit.Framework; namespace Core.Tests { [TestFixture] public class WordleUtilTests { private static readonly char[] Alphabet = Enumerable.Range(0, 26).Select(r => Convert.ToChar('A' + r)).ToArray(); [Test] public void GameTest() { var actual = "NAIVE"; var dictionarySearcher = new DictionarySearcher(5); var frequency = new LetterPositionFrequency(dictionarySearcher, 5); dictionarySearcher.SortDictionary(frequency); var wordleState = new WordleUtil.WordleState(); do { var results = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))); //var guess = frequency.ScoreWords(results).OrderBy(f => f.Item2).Last().Item1; var guess = results.First(); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); Console.WriteLine(guess); } while (!wordleState.IsSolved()); } [Test, Explicit] public void GameTest_NaiveBayes_WriteOutput() { var wordleUtil = new WordleUtil(); var dictionarySearcher = new DictionarySearcher(5); var wordFrequency = new WordFrequency(); var actual = "NAIVE"; var steps = 0; var wordleState = new WordleUtil.WordleState(); do { var constrainedWords = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))) .OrderByDescending(w => wordFrequency.GetWordFreq(w)) .ToList(); if (constrainedWords.First() == "NAIVE") { constrainedWords.RemoveAt(0); constrainedWords.Add("NAIVE"); } if (constrainedWords.Contains("WAIVE")) { constrainedWords.Remove("WAIVE"); constrainedWords.Add("WAIVE"); } var letterCountPercents = GetLetterCountPercents(constrainedWords); SetWordleConstraints(letterCountPercents, wordleState); var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5); var sortedWords = frequency.SortWords(constrainedWords, wordleUtil); var guess = sortedWords.First(); var highestCounts = string.Join("", Enumerable.Range(0, 5).Select(i => letterCountPercents.Last()[i].OrderByDescending(s => s.Count).First().Letter)); WriteLetterCountPercentsCsv(letterCountPercents, constrainedWords, @"o:\tmp\WordleFreq\" + $"{steps}-{guess}", new[] { highestCounts, guess }); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); Console.WriteLine(guess); steps++; } while (!wordleState.IsSolved()); } [Test, Explicit] public void GameTest_NaiveBayes() { var wordleUtil = new WordleUtil(); var dictionarySearcher = new DictionarySearcher(5); var wordFrequency = new WordFrequency(); var actual = "NAIVE"; var steps = 0; var wordleState = new WordleUtil.WordleState(); do { var constrainedWords = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))) .OrderByDescending(w => wordFrequency.GetWordFreq(w)) .ToList(); var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5); var sortedWords = frequency.SortWords(constrainedWords, wordleUtil); //sortedWords.Reverse(); var guess = sortedWords.First(); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); Console.WriteLine(guess); } while (!wordleState.IsSolved()); } [Test, Explicit] public void GameTest_NaiveBayes_Reverse() { var wordleUtil = new WordleUtil(); var dictionarySearcher = new DictionarySearcher(5); var wordFrequency = new WordFrequency(); var actual = "NAIVE"; var steps = 0; var wordleState = new WordleUtil.WordleState(); do { var constrainedWords = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))) .OrderByDescending(w => wordFrequency.GetWordFreq(w)) .ToList(); if (constrainedWords.First() == "NAIVE") { constrainedWords.RemoveAt(0); constrainedWords.Add("NAIVE"); } if (constrainedWords.Contains("WAIVE")) { constrainedWords.Remove("WAIVE"); constrainedWords.Add("WAIVE"); } var letterCountPercents = GetLetterCountPercents(constrainedWords); SetWordleConstraints(letterCountPercents, wordleState); var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5); var sortedWords = frequency.SortWords(constrainedWords, wordleUtil); sortedWords.Reverse(); var bannedWords = new string[] { "TAINT", "AALII", "LAIGH", "SAIDS", "SAINS" }; sortedWords = sortedWords.SkipWhile(bannedWords.Contains).ToList(); var guess = sortedWords.First(); var highestCounts = string.Join("", Enumerable.Range(0, 5).Select(i => letterCountPercents.Last()[i].OrderBy(s => s.Count).First().Letter)); WriteLetterCountPercentsCsv(letterCountPercents, constrainedWords, @"o:\tmp\WordleFreqReverse\" + $"{steps}-{guess}", new[] { highestCounts, guess }); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); Console.WriteLine(guess); steps++; } while (!wordleState.IsSolved()); } private static void SetWordleConstraints(List letterCountPercents, WordleUtil.WordleState wordleState) { foreach (var letter in wordleState.NotInWord) { var k = letter - 'A'; foreach (var letterCountPercent in letterCountPercents) { for (int j = 0; j < 5; j++) letterCountPercent[j][k].State = "N"; } } for (var j = 0; j < wordleState.Criteria.Length; j++) { var criteria = wordleState.Criteria[j]; if (criteria.Correct != null) { var k = criteria.Correct.Value - 'A'; foreach (var letterCountPercent in letterCountPercents) { letterCountPercent[j][k].State = "C"; } } if (!string.IsNullOrEmpty(criteria.OtherPosition)) { foreach (var letter in criteria.OtherPosition) { var k = letter - 'A'; foreach (var letterCountPercent in letterCountPercents) { letterCountPercent[j][k].State = "P"; } } } } } public class LetterCountPercent { public char Letter { get; } public int Count { get; } public float Percentage { get; } /// /// C: Correct, P: Present, N: Not Present, G: Guess, A: Active /// public string State { get; set; } public LetterCountPercent(char letter, int count, double percentage, string state) { Letter = letter; Count = count; Percentage = (float)percentage; State = state; } } [Test] public void NotInWordleList() { var dictionarySearcher = new DictionarySearcher(5); var allFiveLeterWords = dictionarySearcher.FindWords("", "", 5); var wordFrequency = new WordFrequency(); var wordleUtil = new WordleUtil(); var r = from w in allFiveLeterWords where !wordleUtil.Words.Contains(w) let f = wordFrequency.GetWordFreq(w) select Tuple.Create(w,f); foreach (var s in r.OrderByDescending(t => t.Item2).Take(20)) { Console.WriteLine(s); } } [Test, Explicit] public void LetterFrequenceStatistics_NoConstraints() { var frequency = new WordFrequency(); var wordleUtil = new WordleUtil(); var words = wordleUtil.Words.OrderByDescending(w => frequency.GetWordFreq(w)).ToList(); //var dictionarySearcher = new DictionarySearcher(5); //var words = dictionarySearcher.GetWords().OrderByDescending(w => frequency.GetWordFreq(w)).ToList(); void MoveToFirst(string w) { words.Remove(w); words.Insert(0, w); } MoveToFirst("LATER"); MoveToFirst("AFTER"); MoveToFirst("FIRST"); var results = GetLetterCountPercents(words); var highestCounts = string.Join("", Enumerable.Range(0, 5).Select(i => results.Last()[i].OrderByDescending(s => s.Count).First().Letter)); WriteLetterCountPercentsCsv(results, words, @"o:\tmp\WordleFreq\NoConstraint", new []{ highestCounts, "SORES", "CARES"}); } [Test, Explicit] public void LetterFrequenceStatistics_NoConstraints_Reverse() { var frequency = new WordFrequency(); var wordleUtil = new WordleUtil(); var words = wordleUtil.Words.OrderByDescending(w => frequency.GetWordFreq(w)).ToList(); //var dictionarySearcher = new DictionarySearcher(5); //var words = dictionarySearcher.GetWords().OrderByDescending(w => frequency.GetWordFreq(w)).ToList(); void MoveToFirst(string w) { words.Remove(w); words.Insert(0, w); } MoveToFirst("LATER"); MoveToFirst("AFTER"); MoveToFirst("FIRST"); var results = GetLetterCountPercents(words); var highestCounts = string.Join("", Enumerable.Range(0, 5).Select(i => results.Last()[i].OrderByDescending(s => s.Count).Last().Letter)); WriteLetterCountPercentsCsv(results, words, @"o:\tmp\WordleFreq\NoConstraint", new[] { highestCounts, "SORES", "CARES" }); } private static List GetLetterCountPercents(List words) { var freqList = new Dictionary[5]; for (var i = 0; i < 5; i++) freqList[i] = Alphabet.ToDictionary(c => c, j => 0); var wordCount = 0; var results = new List (); foreach (var word in words) { wordCount++; for (var wordIndex = 0; wordIndex < word.Length; wordIndex++) { var c = word[wordIndex]; freqList[wordIndex][c]++; } // get percentage var freqPercents = freqList.Select( f => ( from cFreqs in f select Tuple.Create(cFreqs.Key, cFreqs.Value * 1.0 / wordCount)) .ToDictionary(cp => cp.Item1, cp => cp.Item2)) .ToArray(); // normalize var maxPercent = freqPercents.Max(fp => fp.Max(p => p.Value)); foreach (var freqPercent in freqPercents) foreach (var c in freqPercent.Keys.ToArray()) freqPercent[c] /= maxPercent; var result = (from letter in Enumerable.Range(0, 5) let item = (from freq in freqList[letter] from freqPercent in freqPercents[letter] where freq.Key == freqPercent.Key let state = word[letter] == freq.Key ? "A" : null select new LetterCountPercent(freq.Key, freq.Value, freqPercent.Value, state)).ToArray() select item).ToArray(); results.Add(result); } return results; } private static void WriteLetterCountPercentsCsv(IReadOnlyList results, IReadOnlyList words, string folder, IList g = null) { // hack: IList guesses = null; if (g != null) { guesses = new List(g); guesses.Add(guesses.Last()); } try { if (Directory.Exists(folder)) Directory.Delete(folder, true); } catch (Exception e) { Console.WriteLine(e); } Directory.CreateDirectory(folder); using (var writer = new StreamWriter(Path.Combine(folder, "freq_words.csv"))) { writer.WriteLine("word"); writer.WriteLine(" "); writer.WriteLine(" "); for (int i = 0; i < words.Count; i++) { writer.WriteLine(words[i]); } writer.WriteLine(" "); writer.WriteLine(" "); for (int i = 0; i < (guesses?.Count ?? 0); i++) { writer.WriteLine(guesses[i]); } } for (int j = 0; j < 5; j++) { using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterCounts_" + j + ".csv"))) { writer.WriteLine(string.Join(",", Alphabet)); writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0))); //writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0))); for (var index = 0; index < results.Count + 3 + (guesses?.Count ?? 0); index++) { var indexX = index < results.Count ? index : results.Count - 1; writer.WriteLine(string.Join(",", results[indexX][j].Select(lcp => lcp.Count))); } } } for (int j = 0; j < 5; j++) { using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterPercents_" + j + ".csv"))) { writer.WriteLine(string.Join(",", Alphabet)); writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0))); writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0))); for (var index = 0; index < results.Count + 2 + (guesses?.Count ?? 0); index++) { var indexX = index < results.Count ? index : results.Count - 1; writer.WriteLine(string.Join(",", results[indexX][j].Select(lcp => lcp.Percentage))); } } } for (int j = 0; j < 5; j++) { using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterState_" + j + ".csv"))) { writer.WriteLine(string.Join(",", Alphabet)); writer.WriteLine(string.Join(",", results[0][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); writer.WriteLine(string.Join(",", results[0][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); for (var index = 0; index < results.Count; index++) { writer.WriteLine(string.Join(",", results[index][j].Select(lcp => string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); } writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); for (int e = 0; e < (guesses?.Count ?? 0); e++) { var letter = guesses[e][j]; writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.Letter == letter ? "G" : lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State))); } } } } [Test] public void NotInDictionary() { var dictionarySearcher = new DictionarySearcher(5); var allFiveLeterWords = dictionarySearcher.FindWords("", "", 5); var wordFrequency = new WordFrequency(); var wordleUtil = new WordleUtil(); var r = from w in wordleUtil.Words where !allFiveLeterWords.Contains(w) let f = wordFrequency.GetWordFreq(w) select Tuple.Create(w, f); foreach (var s in r.OrderByDescending(t => t.Item2).Take(20)) { Console.WriteLine(s); } } [Test, Explicit] public void WordleStats() { var actual = "GROUP"; var dictionarySearcher = new DictionarySearcher(5); var allFiveLeterWords = dictionarySearcher.FindWords("", ""); var wordleUtil = new WordleUtil(); var frequency = new LetterPositionFrequency(wordleUtil, 5); dictionarySearcher.SortDictionary(frequency); // Console.WriteLine(dictionarySearcher.FindWords("", "", 5).Count()); var solutionSteps = new List(); Parallel.ForEach(allFiveLeterWords, nextWord => { var guess = nextWord; var response = WordleUtil.GetGuessResult(actual, guess); var wordleState = new WordleUtil.WordleState(); wordleState.Update(guess, response); //Console.WriteLine("----"); //Console.WriteLine(guess); var steps = 1; while (!wordleState.IsSolved()) { var results = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))); guess = results.First(); //guess = frequency.ScoreWords(results).OrderBy(f => f.Item2).Last().Item1; response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); //Console.WriteLine(guess); steps++; } solutionSteps.Add(steps); }); //Console.WriteLine(string.Join(",", solutionSteps)); Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}"); //Console.WriteLine(solutionSteps.Sum() * 1d / solutionSteps.Count ); } [Test] public void RepeatedLettersStatistics() { var wordleUtil = new WordleUtil(); var re = from w in wordleUtil.Words let repeats = WordleUtil.RepeatedLettersCount(w) group repeats by repeats into g select g; var max = re.Max(r => r.Count()); Console.WriteLine(string.Join(", ", re.Select(r => r.Key + ": " + r.Count() + $" {(r.Count() * 1.0) / max}"))); Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 3))); Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 2))); Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 1))); } [Test] public void SolveAllWordleWords_SortedDictionary() { var dictionarySearcher = new DictionarySearcher(5); var wordleUtil = new WordleUtil(); var frequency = new LetterPositionFrequency(wordleUtil, 5); dictionarySearcher.SortDictionary(frequency); var solutionSteps = new List(); Parallel.ForEach(wordleUtil.Words, actual => { var steps = 0; var wordleState = new WordleUtil.WordleState(); do { var results = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))); if (steps <= 1) results = results.SkipWhile(WordleUtil.RepeatedLetters); var guess = results.First(); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); //Console.WriteLine(guess); steps++; } while (!wordleState.IsSolved()); solutionSteps.Add(steps); }); Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}"); } [Test] public void SolveAllWordleWords_NaiveBayes() { var dictionarySearcher = new DictionarySearcher(5); var wordleUtil = new WordleUtil(); var solutionSteps = new List(); Parallel.ForEach(wordleUtil.Words, actual => { var steps = 0; var wordleState = new WordleUtil.WordleState(); do { var results = dictionarySearcher .FindWords(wordleState.InWord, wordleState.NotInWord, 5) .Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria))) .ToList(); var frequency = new LetterPositionFrequency(results, 5); results = frequency.SortWords(results, wordleUtil); var guess = results.First(); var response = WordleUtil.GetGuessResult(actual, guess); wordleState.Update(guess, response); //Console.WriteLine(guess); steps++; } while (!wordleState.IsSolved()); solutionSteps.Add(steps); }); Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}"); } } }