using System; using System.Collections.Generic; using System.Linq; namespace Core { public class WordleUtil { public enum CharacterResponse { Actual, OtherPosition, NotInWord } public struct PositionCriteria { public char? Correct; public string OtherPosition; public string GetRegexRestriction() => Correct.HasValue ? Correct.Value.ToString() : !string.IsNullOrEmpty(OtherPosition) ? "[^" + OtherPosition + "]" : "."; public void RemoveLetter(char letter) { OtherPosition = OtherPosition?.Replace(letter.ToString(), string.Empty); } } public class WordleState { public string InWord { get; private set; } = string.Empty; public string NotInWord { get; private set; } = string.Empty; public PositionCriteria[] Criteria { get; } = new PositionCriteria[5]; public bool IsSolved() => Criteria.All(c => c.Correct.HasValue); private void AddInWord(char letter) { if (!InWord.Contains(letter)) InWord += letter; } private void AddNotInWord(char letter) { if (!NotInWord.Contains(letter)) NotInWord += letter; } public void Update(string word, CharacterResponse[] response) { for (int i = 0; i < 5; i++) { var letter = word[i]; switch (response[i]) { case CharacterResponse.Actual: // remove this letter from the criteria foreach (var positionCriteria in Criteria) positionCriteria.RemoveLetter(letter); AddInWord(letter); Criteria[i].Correct = letter; break; case CharacterResponse.OtherPosition: AddInWord(letter); Criteria[i].OtherPosition += letter; break; case CharacterResponse.NotInWord: AddNotInWord(letter); break; } } } } public static string GetRegexRestrictions(PositionCriteria[] criteria) { return criteria .Aggregate(string.Empty, (r, criterion) => r + criterion.GetRegexRestriction() ); } public IList Words { get; } private Dictionary _repeatedLetterFactor; public WordleUtil() : this(@"WordleWords.txt") { } public WordleUtil(string filename) : this(filename.GetFileLines()) { } public WordleUtil(IEnumerable words) { Words = new List(); foreach (var word in words) { var w = word.Canonicalize(); var split = w.Split(','); Words.Add(split[0]); } Words.Remove("ADMIN"); Words.Remove("INBOX"); var re = from w in Words let repeats = RepeatedLettersCount(w) group repeats by repeats into g select g; var max = re.Max(r => r.Count()); _repeatedLetterFactor = re.Select(r => Tuple.Create(r.Key, r.Count() * 1.0 / max)) .ToDictionary(s => s.Item1, s => s.Item2); } public static CharacterResponse[] GetGuessResult(string actual, string guess) { if (actual.Length != 5 || guess.Length != 5) throw new ArgumentOutOfRangeException("not right size word or guess"); var response = new CharacterResponse[5]; for (var i = 0; i < 5; i++) { if (guess[i] == actual[i]) response[i] = CharacterResponse.Actual; else if (actual.Contains(guess[i])) response[i] = CharacterResponse.OtherPosition; else response[i] = CharacterResponse.NotInWord; } return response; } public static bool RepeatedLetters(string word) { var uniqueLetters = new HashSet(); foreach (var c in word) uniqueLetters.Add(c); return uniqueLetters.Count < word.Length; } public static int RepeatedLettersCount(string word) { var uniqueLetters = new HashSet(); foreach (var c in word) uniqueLetters.Add(c); return word.Length - uniqueLetters.Count; } public double FactorRepeatedLetters(string word, double score) { return score * _repeatedLetterFactor[RepeatedLettersCount(word)]; } public Dictionary[] GetLetterPositionFreq(int wordLen) { var ws = Words.Where(w => w.Length == wordLen).ToList(); var freqList = new Dictionary[wordLen]; for (var i = 0; i < wordLen; i++) { var letters = Enumerable.Range(0, 26).Select(r => Convert.ToChar('A' + r)); freqList[i] = letters.ToDictionary(c => c, c => ws.Count(w => w[i] == c) ); } return freqList; } } }