diff --git a/GottfriedsNackenWebseite/GottfriedsNackenUtility/GottfriedsNackenUtility.csproj b/GottfriedsNackenWebseite/GottfriedsNackenUtility/GottfriedsNackenUtility.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/GottfriedsNackenWebseite/GottfriedsNackenUtility/GottfriedsNackenUtility.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/GottfriedsNackenWebseite/GottfriedsNackenUtility/MathHelper.cs b/GottfriedsNackenWebseite/GottfriedsNackenUtility/MathHelper.cs new file mode 100644 index 0000000..ead18bf --- /dev/null +++ b/GottfriedsNackenWebseite/GottfriedsNackenUtility/MathHelper.cs @@ -0,0 +1,32 @@ +using System.Numerics; + +namespace GottfriedsNackenUtility; + +/// +/// Bietet nütliche mathematische Funktionen. +/// +public static class MathHelper +{ + /// + /// Enumeriert die Zahlen der Fibonacci-Folge. + /// + /// + /// Der Datentyp ist , da es ein paar Fibonacci-Zahlen gibt die nicht von dargestellt werden können. + /// + public static IEnumerable Fibonacci() + { + BigInteger i = 0; + BigInteger j = 1; + + while (true) + { + BigInteger fib = i + j; + + i = j; + j = fib; + + yield return fib; + } + } +} + diff --git a/GottfriedsNackenWebseite/GottfriedsNackenUtility/SusHelper.cs b/GottfriedsNackenWebseite/GottfriedsNackenUtility/SusHelper.cs new file mode 100644 index 0000000..7de9991 --- /dev/null +++ b/GottfriedsNackenWebseite/GottfriedsNackenUtility/SusHelper.cs @@ -0,0 +1,42 @@ +using System.Numerics; + +namespace GottfriedsNackenUtility; + +/// +/// Hier sind suspiziöse Funktionen versteckt. +/// +public static class SusHelper +{ + /// + /// Dies ist der ausgeklügelste fake-Fortschrittbalkensalgorithmus den die Welt je erblickt hat. + /// + /// + /// Dieser Task läuft in einer Endlosschleife und MUSS mit Hilfe des Abbruchtokens abgebrochen werden! + /// + /// Das callback das aufgerufen wird, wenn der Fortschritt sich geändert hat. + /// Gibt einen Int zwischen 0 und 100 zurück oder -1, wenn der Fortschrittsbalken auf einen "indefinite" Zustand wechseln soll. + /// Ein Abbruchtoken, das beim Warten auf den Abschluss der Aufgabe überwacht werden soll. + public static async Task UpdateDefinitelyNotFakeProgress(Action progressChangedCallback, CancellationToken cancellationToken) + { + try + { + foreach (BigInteger fibonacci in MathHelper.Fibonacci()) + { + await Task.Delay(250 + (int)(fibonacci % 420), cancellationToken); + + if (cancellationToken.IsCancellationRequested) + break; + + if (fibonacci.IsEven && (int)(fibonacci % 42) == 0) + { + progressChangedCallback(-1); + await Task.Delay(1337, cancellationToken); + continue; + } + + progressChangedCallback((int)(fibonacci % 69)); + } + } + catch (TaskCanceledException) { } + } +} diff --git a/GottfriedsNackenWebseite/GottfriedsNackenWebseite.sln b/GottfriedsNackenWebseite/GottfriedsNackenWebseite.sln index d1895ef..9270526 100644 --- a/GottfriedsNackenWebseite/GottfriedsNackenWebseite.sln +++ b/GottfriedsNackenWebseite/GottfriedsNackenWebseite.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GottfriedsNackenWebseite", "GottfriedsNackenWebseite\GottfriedsNackenWebseite.csproj", "{464DD643-DAD9-4E16-BB24-36F789ED4C15}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GottfriedsNackenUtility", "GottfriedsNackenUtility\GottfriedsNackenUtility.csproj", "{0122762F-9961-4D28-8AA9-0D6AE7630208}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {464DD643-DAD9-4E16-BB24-36F789ED4C15}.Debug|Any CPU.Build.0 = Debug|Any CPU {464DD643-DAD9-4E16-BB24-36F789ED4C15}.Release|Any CPU.ActiveCfg = Release|Any CPU {464DD643-DAD9-4E16-BB24-36F789ED4C15}.Release|Any CPU.Build.0 = Release|Any CPU + {0122762F-9961-4D28-8AA9-0D6AE7630208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0122762F-9961-4D28-8AA9-0D6AE7630208}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0122762F-9961-4D28-8AA9-0D6AE7630208}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0122762F-9961-4D28-8AA9-0D6AE7630208}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleComponent.razor b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleComponent.razor index f798f8f..4b49608 100644 --- a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleComponent.razor +++ b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleComponent.razor @@ -1,15 +1,25 @@ @using System.Diagnostics - + - Korrekt! + @if (_alert == AlertState.Correct) + { + Korrekt! + } + else if (_alert == AlertState.Invalid) + { + Eingabe ungültig! + } - + @if (_alert == AlertState.Correct) + { + + } @@ -19,7 +29,7 @@ - @foreach (CharData[] guess in PreviousGuesses) + @foreach (CharData[] guess in _previousGuesses) { @foreach (CharData cd in guess) @@ -48,25 +58,35 @@ -@Input - +@* + + +*@ - + - + - + + @**@ - OK + OK - + @code { + private enum AlertState + { + None, + Correct, + Invalid + } + public enum CharState { None, @@ -86,112 +106,88 @@ State = state; } } - - private string _input = string.Empty; - - [Parameter] - public string Input - { - get => _input; - set => _input = value; - } - public async Task SetText(string text) - { - textField.SetText(text); - } + private bool _found = false; - public bool hideAlert = true; + private AlertState _alert = AlertState.None; - public MudTextField textField; + private List _previousGuesses = new(); - public List PreviousGuesses = new(); - - private bool found = false; - private int _columnCount; - private string? _secret = null; - private IMask _inputMask = new RegexMask(@"^[A-Za-z]{0,5}$"); - private Func? _checkInput; - private Func? _generateSecret; + private MudTextField _textField = default!; [Parameter] - public int ColumnCount - { - get => _columnCount; - set => _columnCount = value; - } + public string Input { get; set; } = String.Empty; [Parameter] - public string? Secret - { - get => _secret; - set => _secret = value; - } + public int ColumnCount { get; set; } + + public string? Secret { get; set; } = null; [Parameter] - public IMask InputMask - { - get => _inputMask; - set => _inputMask = value; - } + public IMask InputMask { get; set; } = new RegexMask("^.*$"); + /// + /// Prüft ob die getätigte Eingabe eine mögliche Lösung ist. + /// [Parameter] - public Func? CheckInput - { - get => _checkInput; - set => _checkInput = value; - } - - [Parameter] - public Func? GenerateSecret - { - get => _generateSecret; - set => _generateSecret = value; - } + public Func CheckInput { get; set; } = _ => true; /// /// Wird ausgelöst wenn eine Eingabe getätigt und validiert wurde. /// - public event EventHandler? InputValidated; + [Parameter] + public EventCallback InputValidated { get; set; } - public void Check4Enter(KeyboardEventArgs e) + /// + /// Wird ausgelöst wenn ein neues Secret generiert werden muss. + /// + [Parameter] + public EventCallback GenerateSecret { get; set; } + + public async Task OnKeyDown(KeyboardEventArgs e) { + _alert = AlertState.None; + if (e.Code == "Enter" || e.Code == "NumpadEnter") { - if(found) + if(_found) { - PlayAgain(); + await PlayAgain(); } else { - ButtonOnClick(); + await OnEnterGuessClick(); } } } - public void ButtonOnClick() + public async Task OnEnterGuessClick() { - if (Input.Length != ColumnCount || found) return; + if (Input.Length != ColumnCount || _found) return; - if (CheckInput != null && !CheckInput(Input)) - return; // TODO: Eingabe ungültig! - - if (Secret == null && GenerateSecret != null) - Secret = GenerateSecret(); - - if(TestInput(Input)) + if (!CheckInput(Input)) { - found = true; - hideAlert = false; + _alert = AlertState.Invalid; + StateHasChanged(); + return; } - textField.Clear(); + if (Secret == null) + await GenerateSecret.InvokeAsync(); + + if(await TestInput(Input)) + { + _found = true; + _alert = AlertState.Correct; + } + + await _textField.Clear(); } /// /// Prüft ob die Eingabe korrekt ist. /// - private bool TestInput(string input) + private async Task TestInput(string input) { CharData[] validatedInput = new CharData[ColumnCount]; @@ -221,20 +217,19 @@ validatedInput[i] = new CharData(guessedChar, state); } - PreviousGuesses.Add(validatedInput); + _previousGuesses.Add(validatedInput); - InputValidated?.Invoke(this, validatedInput); + await InputValidated.InvokeAsync(validatedInput); return correct; } - public void PlayAgain() + public async Task PlayAgain() { - PreviousGuesses.Clear(); - hideAlert = true; - found = false; - - if (GenerateSecret != null) - Secret = GenerateSecret(); + _previousGuesses.Clear(); + _alert = AlertState.None; + _found = false; + + await GenerateSecret.InvokeAsync(); } } diff --git a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleKeyboardComponent.razor b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleKeyboardComponent.razor index aa00042..71a197e 100644 --- a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleKeyboardComponent.razor +++ b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Components/WordleKeyboardComponent.razor @@ -1,5 +1,4 @@ - - + @{ @@ -13,28 +12,30 @@ char key = Keys[index]; char upperKey = char.ToUpper(key); + + _knownKeyStates.TryGetValue(upperKey, out var keyState); - if (_correctKeys.Contains(upperKey)) + if (keyState == WordleComponent.CharState.Correct) { - ButtonPressed(key)"> + @upperKey } - else if (_misplacedKeys.Contains(upperKey)) + else if (keyState == WordleComponent.CharState.Misplaced) { - ButtonPressed(key)"> + @upperKey } - else if (_wrongKeys.Contains(upperKey)) + else if (keyState == WordleComponent.CharState.Wrong) { - ButtonPressed(key)"> + @upperKey } else { - ButtonPressed(key)"> + @upperKey } @@ -49,13 +50,9 @@ [Parameter] public char[] Keys { get; set; } = Array.Empty(); - - private List _correctKeys = new(); - - private List _misplacedKeys = new(); - - private List _wrongKeys = new(); + private Dictionary _knownKeyStates = new(); + [Parameter] public int KeysPerRow { get; set; } = 10; @@ -69,4 +66,36 @@ { await KeyPressed.InvokeAsync(c); } + + public void UpdateKeyInfo(WordleComponent.CharData cd) + { + _knownKeyStates.TryGetValue(cd.Char, out var oldState); + + WordleComponent.CharState newState = WordleComponent.CharState.None; + + switch (cd.State) + { + case WordleComponent.CharState.Correct: + newState = WordleComponent.CharState.Correct; + break; + case WordleComponent.CharState.Misplaced: + // Den besseren Zustand nicht durch schlechteren ersetzen + if (oldState != WordleComponent.CharState.Correct) + { + newState = WordleComponent.CharState.Misplaced; + } + break; + case WordleComponent.CharState.Wrong: + newState = WordleComponent.CharState.Wrong; + break; + } + + _knownKeyStates[cd.Char] = newState; + } + + public void ResetKeyInfo() + { + _knownKeyStates.Clear(); + StateHasChanged(); + } } \ No newline at end of file diff --git a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/GottfriedsNackenWebseite.csproj b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/GottfriedsNackenWebseite.csproj index 613d321..423398d 100644 --- a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/GottfriedsNackenWebseite.csproj +++ b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/GottfriedsNackenWebseite.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Pages/Numberle.razor b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Pages/Numberle.razor index e4dd3bf..6fece0a 100644 --- a/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Pages/Numberle.razor +++ b/GottfriedsNackenWebseite/GottfriedsNackenWebseite/Pages/Numberle.razor @@ -1,29 +1,50 @@ @page "/numberle" @using GottfriedsNackenWebseite.Components -@using System.Diagnostics @using GottfriedsNackenWebseite.Numberle +@using GottfriedsNackenUtility +@using System.Diagnostics +@using System.Numerics Nacken Numberle Gebe eine (korrekte) mathematische Gleichung ein: - + - + + + + + + Bitte warten... + + + Erzeuge eine unknackbare Formel mit roher Gewalt! + + + + @code { - private IMask _inputMask = new RegexMask(@"^[0-9+\-*/=]{0,8}$"); + private static readonly IMask _inputMask = new RegexMask(@"^[0-9+\-*/=]{0,8}$"); - private Parser _parser = new(); + private readonly Parser _parser = new(); - private WordleComponent _wordle; + private WordleComponent _wordle = default!; + private WordleKeyboardComponent _keyboard = default!; + + private bool _showLoadingOverlay; + private int _progress; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); } + /// + /// Prüft ob input eine gültige Gleichung ist. + /// private bool CheckInput(string input) { try @@ -36,7 +57,7 @@ (decimal value, int equalSigns) = CrunchyValidaty(ast); - // Wir akzeptieren nur, wenn es exakt ein Gleichheitszeichen gibt und diese erfüllt ist. + // Wir akzeptieren nur, wenn es exakt ein Gleichheitszeichen gibt und die Gleichung erfüllt ist. return value == 1 && equalSigns == 1; } catch @@ -84,12 +105,55 @@ return (0, 0); } + /// + /// Die Zeichen aus denen eine Antwort besteht. + /// private static readonly char[] Chars = { '=', '+', '-', '*', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; - private string GenerateSecret() + /// + /// Generiert ein neues Geheimnis und zeigt während dessen den Ladebalken an. + /// + /// + private async Task OnGenerateNewSecret() + { + _keyboard.ResetKeyInfo(); + + CancellationTokenSource cts = new CancellationTokenSource(); + + Task? amazingTask = null; + + try + { + // Ladebalkenaktualisierer starten + amazingTask = SusHelper.UpdateDefinitelyNotFakeProgress(progress => + { + _progress = progress; + StateHasChanged(); + }, cts.Token); + + _showLoadingOverlay = true; + + _wordle.Secret = await Task.Run(GenerateFormula); + + _showLoadingOverlay = false; + } + finally + { + // Ladebalkenaktualisierer stoppen + cts.Cancel(); + if (amazingTask != null) + await amazingTask; + } + } + + /// + /// Erzeugt eine neue (gültige) Formel. + /// + /// Die Formel + private string GenerateFormula() { int charCount = 8; @@ -99,6 +163,8 @@ int equalsIndex = Random.Shared.Next(1, charCount - 1); formula[equalsIndex] = '='; + // Ersetzt das Zeichen mit Index "index" durch einen Zufälligen wert. + // TODO: ein etwas gewiefterer Algorithmus würde nicht schaden. void PlaceRandomChar(int index) { char left = (index > 0) ? formula[index - 1] : '\0'; @@ -127,13 +193,26 @@ Debug.WriteLine(str); if (CheckInput(str)) + { return str; + } } } } private async Task KeyboardKeyPressed(char key) { - await _wordle.SetText(_wordle.Input + key); //Input += key; + // TODO: Virtuelles Keyboard + //await _wordle.SetText(_wordle.Input + key); //Input += key; + await Task.CompletedTask; + } + + private void OnInputValidated(WordleComponent.CharData[] data) + { + // Auswertung in das Keyboard packen + foreach (WordleComponent.CharData cd in data) + { + _keyboard.UpdateKeyInfo(cd); + } } }
@Input