diff --git a/Codel-Cloud-Native.ApiService/DTOs/GameDtos.cs b/Codel-Cloud-Native.ApiService/DTOs/GameDtos.cs index 1d8a8ac..9b558c5 100644 --- a/Codel-Cloud-Native.ApiService/DTOs/GameDtos.cs +++ b/Codel-Cloud-Native.ApiService/DTOs/GameDtos.cs @@ -28,7 +28,8 @@ public record GameSessionDto( int MaxAttempts, bool IsComplete, bool IsWin, - IReadOnlyList Guesses + IReadOnlyList Guesses, + IReadOnlyDictionary GuessedLetters // Key: letter, Value: status string ); /// diff --git a/Codel-Cloud-Native.ApiService/Mapping/DtoMapper.cs b/Codel-Cloud-Native.ApiService/Mapping/DtoMapper.cs index 80b9001..172949a 100644 --- a/Codel-Cloud-Native.ApiService/Mapping/DtoMapper.cs +++ b/Codel-Cloud-Native.ApiService/Mapping/DtoMapper.cs @@ -37,7 +37,8 @@ public static GameSessionDto ToDto(this GameSession gameSession) gameSession.MaxAttempts, gameSession.IsComplete, gameSession.IsWin, - gameSession.Attempts.Select(a => a.ToDto()).ToList().AsReadOnly() + gameSession.Attempts.Select(a => a.ToDto()).ToList().AsReadOnly(), + gameSession.GuessedLetters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString()).AsReadOnly() ); } diff --git a/Codel-Cloud-Native.Tests/UnitTests.cs b/Codel-Cloud-Native.Tests/UnitTests.cs index 4dfe14d..0a0a183 100644 --- a/Codel-Cloud-Native.Tests/UnitTests.cs +++ b/Codel-Cloud-Native.Tests/UnitTests.cs @@ -1,6 +1,8 @@ using System.Net; using System; using CodeleLogic; +using CodeleLogic.Models; +using CodeleLogic.Services; namespace Codel_Cloud_Native.Tests; @@ -163,4 +165,70 @@ public void TestIsGuessWrongLength() // Assert Assert.False(guess.IsWinningGuess(answer)); } + + [Fact] + public void TestGameSession_TrackGuessedLetters_SingleGuess() + { + // Arrange + var gameSession = new GameSession(Guid.NewGuid(), "APPLE", 5); + var guessEvaluator = new DefaultGuessEvaluator(); + + // Act + var guessResult = guessEvaluator.EvaluateGuess("HELLO", "APPLE"); + gameSession.AddGuess(guessResult); + + // Assert - HELLO vs APPLE: + // H=Incorrect, E=IncorrectPosition (E is in position 4 in APPLE), L=IncorrectPosition (L is in position 2), L=Incorrect, O=Incorrect + // Since we track the best status per letter, L should be IncorrectPosition (not Incorrect) + Assert.Equal(4, gameSession.GuessedLetters.Count); // H, E, L, O (duplicates consolidated) + Assert.Equal(LetterStatus.Incorrect, gameSession.GuessedLetters['H']); + Assert.Equal(LetterStatus.IncorrectPosition, gameSession.GuessedLetters['E']); // E is in APPLE but wrong position + Assert.Equal(LetterStatus.IncorrectPosition, gameSession.GuessedLetters['L']); // L is in APPLE but wrong position (best status wins) + Assert.Equal(LetterStatus.Incorrect, gameSession.GuessedLetters['O']); + } + + [Fact] + public void TestGameSession_TrackGuessedLetters_LetterStatusUpgrade() + { + // Arrange + var gameSession = new GameSession(Guid.NewGuid(), "APPLE", 5); + var guessEvaluator = new DefaultGuessEvaluator(); + + // Act - first guess: HELLO vs APPLE (L will be IncorrectPosition) + var guessResult1 = guessEvaluator.EvaluateGuess("HELLO", "APPLE"); + gameSession.AddGuess(guessResult1); + + // Act - second guess: APPLE vs APPLE (all letters correct, including L upgraded to Correct) + var guessResult2 = guessEvaluator.EvaluateGuess("APPLE", "APPLE"); + gameSession.AddGuess(guessResult2); + + // Assert - L should be upgraded from IncorrectPosition to Correct + Assert.Equal(LetterStatus.Correct, gameSession.GuessedLetters['A']); + Assert.Equal(LetterStatus.Correct, gameSession.GuessedLetters['P']); + Assert.Equal(LetterStatus.Correct, gameSession.GuessedLetters['L']); // Upgraded from IncorrectPosition to Correct + Assert.Equal(LetterStatus.Correct, gameSession.GuessedLetters['E']); // Upgraded from IncorrectPosition to Correct + } + + [Fact] + public void TestGameSession_TrackGuessedLetters_NoDowngrade() + { + // Arrange + var gameSession = new GameSession(Guid.NewGuid(), "APPLE", 5); + var guessEvaluator = new DefaultGuessEvaluator(); + + // Act - first guess: A is correct in APPLE + var guessResult1 = guessEvaluator.EvaluateGuess("ALOFT", "APPLE"); + gameSession.AddGuess(guessResult1); + + // Act - second guess: A would be incorrect position if it appeared elsewhere, but shouldn't downgrade + var guessResult2 = guessEvaluator.EvaluateGuess("BEAUT", "APPLE"); // A is not in APPLE at position 2 + gameSession.AddGuess(guessResult2); + + // Assert - A should remain Correct from first guess + Assert.Equal(LetterStatus.Correct, gameSession.GuessedLetters['A']); + Assert.Contains('B', gameSession.GuessedLetters.Keys); + Assert.Contains('E', gameSession.GuessedLetters.Keys); + Assert.Contains('U', gameSession.GuessedLetters.Keys); + Assert.Contains('T', gameSession.GuessedLetters.Keys); + } } diff --git a/Codel-Cloud-Native.Web/Components/Pages/PlayCodele.razor b/Codel-Cloud-Native.Web/Components/Pages/PlayCodele.razor index 781c5b5..2f24a15 100644 --- a/Codel-Cloud-Native.Web/Components/Pages/PlayCodele.razor +++ b/Codel-Cloud-Native.Web/Components/Pages/PlayCodele.razor @@ -10,6 +10,35 @@
+ +@if (currentGame?.GuessedLetters != null && currentGame.GuessedLetters.Count > 0) +{ +
+
Letters Guessed:
+
+ @foreach (char letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + { + @if (currentGame.GuessedLetters.ContainsKey(letter)) + { + var status = currentGame.GuessedLetters[letter]; + var cssClass = status switch + { + "Correct" => "btn btn-success", + "IncorrectPosition" => "btn btn-warning", + "Incorrect" => "btn btn-secondary", + _ => "btn btn-outline-dark" + }; + + } + else + { + + } + } +
+
+} +

Attempt #: @attempts

diff --git a/Codel-Cloud-Native.Web/DTOs/GameDtos.cs b/Codel-Cloud-Native.Web/DTOs/GameDtos.cs index 2bda2f8..010a581 100644 --- a/Codel-Cloud-Native.Web/DTOs/GameDtos.cs +++ b/Codel-Cloud-Native.Web/DTOs/GameDtos.cs @@ -28,7 +28,8 @@ public record GameSessionDto( int MaxAttempts, bool IsComplete, bool IsWin, - IReadOnlyList Guesses + IReadOnlyList Guesses, + IReadOnlyDictionary GuessedLetters // Key: letter, Value: status string ); /// diff --git a/CodeleLogic/Models/GameSession.cs b/CodeleLogic/Models/GameSession.cs index eb52ae6..b74a447 100644 --- a/CodeleLogic/Models/GameSession.cs +++ b/CodeleLogic/Models/GameSession.cs @@ -14,12 +14,19 @@ public class GameSession public bool IsComplete => IsWin || Attempts.Count >= MaxAttempts; public bool IsWin => Attempts.Any(a => a.IsWin); + /// + /// Dictionary tracking the status of letters that have been guessed + /// Key: letter character (uppercase), Value: best status achieved for that letter + /// + public Dictionary GuessedLetters { get; } + public GameSession(Guid gameId, string targetWord, int maxAttempts = 5) { GameId = gameId; TargetWord = targetWord ?? throw new ArgumentNullException(nameof(targetWord)); MaxAttempts = maxAttempts; Attempts = new List(); + GuessedLetters = new Dictionary(); } /// @@ -33,5 +40,33 @@ public void AddGuess(GuessResult guessResult) throw new InvalidOperationException("Cannot add guess to completed game"); Attempts.Add(guessResult); + + // Update guessed letters tracking + foreach (var letterResult in guessResult.Letters) + { + char upperLetter = char.ToUpper(letterResult.Letter); + + // Only update if we don't have this letter or if the new status is better + if (!GuessedLetters.ContainsKey(upperLetter) || + GetStatusPriority(letterResult.Status) > GetStatusPriority(GuessedLetters[upperLetter])) + { + GuessedLetters[upperLetter] = letterResult.Status; + } + } + } + + /// + /// Gets the priority of a letter status for tracking purposes + /// Higher priority statuses take precedence over lower ones + /// + private static int GetStatusPriority(LetterStatus status) + { + return status switch + { + LetterStatus.Correct => 3, + LetterStatus.IncorrectPosition => 2, + LetterStatus.Incorrect => 1, + _ => 0 + }; } } \ No newline at end of file