Files

188 lines
6.1 KiB
C#

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<string> Words { get; }
private Dictionary<int, double> _repeatedLetterFactor;
public WordleUtil()
: this(@"WordleWords.txt")
{
}
public WordleUtil(string filename)
: this(filename.GetFileLines())
{
}
public WordleUtil(IEnumerable<string> words)
{
Words = new List<string>();
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<char>();
foreach (var c in word)
uniqueLetters.Add(c);
return uniqueLetters.Count < word.Length;
}
public static int RepeatedLettersCount(string word)
{
var uniqueLetters = new HashSet<char>();
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<char, int>[] GetLetterPositionFreq(int wordLen)
{
var ws = Words.Where(w => w.Length == wordLen).ToList();
var freqList = new Dictionary<char, int>[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;
}
}
}