diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..0fbab21b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.CodeReviews.Console.MathGame.iml +/projectSettingsUpdater.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..df87cf95 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/indexLayout.xml b/.idea/indexLayout.xml new file mode 100644 index 00000000..7b08163c --- /dev/null +++ b/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/.gitignore b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/.gitignore new file mode 100644 index 00000000..9a24c68e --- /dev/null +++ b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +/.idea.ConsoleApp1.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/encodings.xml b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/encodings.xml new file mode 100644 index 00000000..df87cf95 --- /dev/null +++ b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/indexLayout.xml b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/indexLayout.xml new file mode 100644 index 00000000..7b08163c --- /dev/null +++ b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/vcs.xml b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/MathGame.Dknx8888/.idea/.idea.MathGame.Dknx8888/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MathGame.Dknx8888/Difficulty.cs b/MathGame.Dknx8888/Difficulty.cs new file mode 100644 index 00000000..daa9e80c --- /dev/null +++ b/MathGame.Dknx8888/Difficulty.cs @@ -0,0 +1,8 @@ +namespace MathGame.Dknx8888; + +public enum Difficulty +{ + Easy, + Medium, + Hard +} \ No newline at end of file diff --git a/MathGame.Dknx8888/GameHistory.cs b/MathGame.Dknx8888/GameHistory.cs new file mode 100644 index 00000000..c6bb1561 --- /dev/null +++ b/MathGame.Dknx8888/GameHistory.cs @@ -0,0 +1,115 @@ +using System.Text.Json; +using static MathGame.Dknx8888.GameResultTemplate; + +namespace MathGame.Dknx8888; + +public class GameHistory +{ + private readonly JsonSerializerOptions _options = new() { WriteIndented = true }; + public void ShowHistory() + { + while (true) + { + Console.Clear(); + + var filePath = GameHistoryJson.FilePath; + if (!File.Exists(filePath)) + { + EmptyHistoryError(); + return; + } + + var json = File.ReadAllText(filePath); + if (string.IsNullOrWhiteSpace(json)) + { + EmptyHistoryError(); + return; + } + + var gameResults = JsonSerializer.Deserialize>(json, _options) ?? []; + if (gameResults.Count == 0) + { + EmptyHistoryError(); + return; + } + + Console.WriteLine("Game History\n"); + Console.WriteLine("Please select a game via ID (1,2, etc.) to show more details"); + Console.WriteLine("Type q to go back\n"); + Console.WriteLine($"{"ID",-4} | {"Date and Time Started",-22} | {"Score",-7} | {"Mode",-15} | {"Duration (sec)",-10}"); + Console.WriteLine(new string('-', 76)); + + // Show from latest (bad idea?) + // gameResults.Reverse(); + for (var i = 0; i < gameResults.Count; i++) + { + var (gameMode, _, score, duration, _, startTime) = gameResults[i]; + + Console.WriteLine($"{i + 1,-4} | {startTime,-22} | {score,-7} | {gameMode,-15} | {duration,-10:F2}"); + } + + int selectedId; + bool isValidId; + + do + { + var input = Console.ReadLine()?.Trim(); + + // while loop in GameSession.cs does not work here because of this q + if (string.Equals(input, "q", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + isValidId = int.TryParse(input, out selectedId) + && selectedId >= 1 + && selectedId <= gameResults.Count; + + if (!isValidId) + { + Console.WriteLine("Please enter a valid ID number, or q to go back"); + } + } while (!isValidId); + + var selectedGameResult = gameResults[selectedId - 1]; + ShowDetails(selectedGameResult); + } + } + + private static void EmptyHistoryError() + { + Console.WriteLine("No game history found."); + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } + + private static void ShowDetails(GameResult selectedGameResult) + { + var (gameMode, difficulty, score, duration, questionResults, startTime) = selectedGameResult; + + Console.Clear(); + Console.WriteLine($"Start time: {startTime}"); + Console.WriteLine($"Game mode: {gameMode}"); + Console.WriteLine($"Difficulty: {difficulty}"); + Console.WriteLine($"Duration (sec): {duration:F2}"); + Console.WriteLine($"Score: {score}/5\n"); + + // Way better idea to include id in the records + var questionId = 1; + // Mind the one spaces + Console.WriteLine($"{"Q. Number", -10} {"Result", -8} {"Question", -12} {"Your Answer", 12} {"Correct Answer", 14}"); + Console.WriteLine(new string('-', 62)); + + foreach (var questionResult in questionResults) + { + var (num1, num2, sign, playerAnswer, correctAnswer, isCorrect) = questionResult; + var correctDisplay = isCorrect ? "Right" : "Wrong"; + var questionDisplay = $"{num1} {sign} {num2}"; + + Console.WriteLine($"{questionId, -10} {correctDisplay, -8} {questionDisplay, -12} {playerAnswer, 12} {correctAnswer, 14}"); + ++questionId; + } + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } +} \ No newline at end of file diff --git a/MathGame.Dknx8888/GameHistoryJson.cs b/MathGame.Dknx8888/GameHistoryJson.cs new file mode 100644 index 00000000..1a25a48b --- /dev/null +++ b/MathGame.Dknx8888/GameHistoryJson.cs @@ -0,0 +1,18 @@ +using System.Runtime.CompilerServices; + +namespace MathGame.Dknx8888; + +// Repository pattern in the future? +public static class GameHistoryJson +{ + public static string FilePath => GetGameResultsFilePath(); + + // CallerFilePath gets the path of the source file that called this func + private static string GetGameResultsFilePath([CallerFilePath] string sourceFilePath = "") + { + var sourceDirectory = Path.GetDirectoryName(sourceFilePath) + ?? throw new InvalidOperationException("Unable to determine the source file directory."); + + return Path.Combine(sourceDirectory, "game-results.json"); + } +} \ No newline at end of file diff --git a/MathGame.Dknx8888/GameMode.cs b/MathGame.Dknx8888/GameMode.cs new file mode 100644 index 00000000..ddeeb598 --- /dev/null +++ b/MathGame.Dknx8888/GameMode.cs @@ -0,0 +1,10 @@ +namespace MathGame.Dknx8888; + +public enum GameMode +{ + Addition, + Subtraction, + Multiplication, + Division, + Random +} \ No newline at end of file diff --git a/MathGame.Dknx8888/GameResultTemplate.cs b/MathGame.Dknx8888/GameResultTemplate.cs new file mode 100644 index 00000000..9e422fe1 --- /dev/null +++ b/MathGame.Dknx8888/GameResultTemplate.cs @@ -0,0 +1,22 @@ +namespace MathGame.Dknx8888; + +public static class GameResultTemplate +{ + public record QuestionResult ( + int Num1, + int Num2, + char Sign, + double PlayerAnswer, + int CorrectAnswer, + bool IsCorrect + ); + + public record GameResult ( + GameMode GameMode, + Difficulty Difficulty, + int Score, + double Duration, + List Questions, + DateTime StartTime + ); +} \ No newline at end of file diff --git a/MathGame.Dknx8888/GameSession.cs b/MathGame.Dknx8888/GameSession.cs new file mode 100644 index 00000000..fc4c402a --- /dev/null +++ b/MathGame.Dknx8888/GameSession.cs @@ -0,0 +1,156 @@ +using System.Diagnostics; +using System.Text.Json; +using static MathGame.Dknx8888.GameResultTemplate; + +namespace MathGame.Dknx8888; + +public class GameSession(GameMode gameMode, Difficulty difficulty) +{ + private readonly Random _random = new(); + private readonly JsonSerializerOptions _options = new JsonSerializerOptions { WriteIndented = true }; + + public void Start() + { + var count = 0; + var score = 0; + HashSet<(int, int, GameMode)> questions = []; + var timer = Stopwatch.StartNew(); + List questionResults = []; + List gameResults; + var startingDateTime = DateTime.Now; + + Console.Clear(); + Console.WriteLine("Please answer the following questions: "); + + // May implement quitting in the middle of the game later + while (count < 5) + { + int num1; + int num2; + GameMode selectedGameMode; + + // Handle duplicate questions + do + { + (num1, num2) = NumGen(); + + selectedGameMode = gameMode == GameMode.Random ? GetRandomGameMode() : gameMode; + + // Division handling + if (selectedGameMode != GameMode.Division) continue; + while (num2 == 0 || num1 % num2 != 0) + { + (num1, num2) = NumGen(); + } + } while (!questions.Add((num1, num2, selectedGameMode))); // True if new q, False if already had + + var sign = selectedGameMode switch + { + GameMode.Addition => '+', + GameMode.Subtraction => '-', + GameMode.Multiplication => '*', + GameMode.Division => '/', + _ => throw new InvalidOperationException("Invalid game mode selected.") + }; + + var correctResult = selectedGameMode switch + { + GameMode.Addition => num1 + num2, + GameMode.Subtraction => num1 - num2, + GameMode.Multiplication => num1 * num2, + GameMode.Division => num1 / num2, + _ => throw new InvalidOperationException("Invalid game mode selected.") + }; + + Console.WriteLine($"{num1} {sign} {num2} = ?"); + + // Checking int would give the player a hint instead so nah + double playerInput; + // Put ReadLine in while to avoid infinite loops + while (!double.TryParse(Console.ReadLine()?.Trim(), out playerInput)) + { + Console.WriteLine("Please enter a valid number"); + } + + // ReSharper disable once CompareOfFloatsByEqualityOperator + var isCorrect = playerInput == correctResult; + if (isCorrect) + { + ++score; + Console.WriteLine("Congratulations! You are correct!"); + } + else + { + Console.WriteLine($"You are incorrect. The correct answer is {correctResult}"); + } + + // Add record + questionResults.Add(new QuestionResult ( + num1, + num2, + sign, + playerInput, + correctResult, + isCorrect + )); + + count++; + } + timer.Stop(); + var duration = timer.Elapsed.TotalSeconds; + Console.WriteLine($"Your final score is {score} out of 5!"); + Console.WriteLine($"You completed this game in {duration:F2} seconds."); + + // Save + var gameResult = new GameResult( + gameMode, + difficulty, + score, + duration, + questionResults, + startingDateTime + ); + + var filePath = GameHistoryJson.FilePath; + if (File.Exists(filePath)) + { + var existingJson = File.ReadAllText(filePath); + gameResults = JsonSerializer.Deserialize>(existingJson, _options) ?? []; + } + else + { + gameResults = []; + } + + gameResults.Add(gameResult); + var json = JsonSerializer.Serialize(gameResults, _options); + File.WriteAllText(filePath, json); + Console.WriteLine($"Game history saved to: {filePath}"); + + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } + + private (int, int) NumGen() + { + return difficulty switch + { + Difficulty.Easy => (_random.Next(0, 10), _random.Next(0, 10)), + Difficulty.Medium => (_random.Next(10, 101), _random.Next(0, 10)), + Difficulty.Hard => (_random.Next(10, 101), _random.Next(10, 101)), + _ => throw new InvalidOperationException("Somehow an invalid difficulty is selected. Fix your bug.") + }; + } + + private GameMode GetRandomGameMode() + { + GameMode[] gameModes = + [ + GameMode.Addition, + GameMode.Subtraction, + GameMode.Multiplication, + GameMode.Division + ]; + return gameModes[_random.Next(gameModes.Length)]; + } +} diff --git a/MathGame.Dknx8888/MathGame.Dknx8888.csproj b/MathGame.Dknx8888/MathGame.Dknx8888.csproj new file mode 100644 index 00000000..6c1dc922 --- /dev/null +++ b/MathGame.Dknx8888/MathGame.Dknx8888.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/MathGame.Dknx8888/MathGame.Dknx8888.sln b/MathGame.Dknx8888/MathGame.Dknx8888.sln new file mode 100644 index 00000000..195c6325 --- /dev/null +++ b/MathGame.Dknx8888/MathGame.Dknx8888.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathGame.Dknx8888", "MathGame.Dknx8888.csproj", "{A06D9D56-E8B5-402E-BEDC-BE092E066CCE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A06D9D56-E8B5-402E-BEDC-BE092E066CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A06D9D56-E8B5-402E-BEDC-BE092E066CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A06D9D56-E8B5-402E-BEDC-BE092E066CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A06D9D56-E8B5-402E-BEDC-BE092E066CCE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MathGame.Dknx8888/Menu.cs b/MathGame.Dknx8888/Menu.cs new file mode 100644 index 00000000..5adef639 --- /dev/null +++ b/MathGame.Dknx8888/Menu.cs @@ -0,0 +1,124 @@ +namespace MathGame.Dknx8888; + +public class Menu +{ + private Difficulty _difficulty = Difficulty.Easy; + + // NOTE: This is an expression-bodied property, using => not = + private string DifficultyDisplay => _difficulty switch + { + Difficulty.Easy => "Easy", + Difficulty.Medium => "Medium", + Difficulty.Hard => "Hard", + _ => throw new ArgumentOutOfRangeException() + }; + + public void ShowMenu() + { + while (true) + { + Console.Clear(); + Console.WriteLine("Welcome to A Simple Math Game!"); + Console.WriteLine($"\nCurrently selected difficulty: {DifficultyDisplay}\n"); + Console.WriteLine("Press choose one of the options below (1-4):"); + Console.WriteLine("1. Start"); + Console.WriteLine("2. View Game History"); + Console.WriteLine("3. Choose Difficulty"); + Console.WriteLine("4. Quit"); + + var input = Console.ReadLine()?.Trim(); + + switch (input) + { + case "1": + ShowGameModes(); + break; + + case "2": + var gameHistory = new GameHistory(); + gameHistory.ShowHistory(); + break; + + case "3": + DifficultyMenu(); + break; + + case "4": + Console.WriteLine("\nGoodbye!"); + return; + } + } + } + + private void ShowGameModes() + { + while (true) + { + Console.Clear(); + Console.WriteLine("Choose one of the modes below (1-6):"); + Console.WriteLine("1. Addition"); + Console.WriteLine("2. Subtraction"); + Console.WriteLine("3. Multiplication"); + Console.WriteLine("4. Division"); + Console.WriteLine("5. Random"); + Console.WriteLine("6. Go Back"); + + var gameModeInput = Console.ReadLine()?.Trim(); + + // Go back (exit the while loop) + if (gameModeInput == "6") + { + return; + } + + GameMode? gameMode = gameModeInput switch + { + "1" => GameMode.Addition, + "2" => GameMode.Subtraction, + "3" => GameMode.Multiplication, + "4" => GameMode.Division, + "5" => GameMode.Random, + _ => null + }; + + // Nothing ever happens. + if (gameMode is null) + { + continue; + } + + // Starts game here (so .Value here is needed because gameMode is nullable) + var gameSession = new GameSession(gameMode.Value, _difficulty); + gameSession.Start(); + } + } + + private void DifficultyMenu() + { + while (true) + { + Console.Clear(); + Console.WriteLine($"Currently Selected Difficulty: {DifficultyDisplay}\n"); + Console.WriteLine("Please select one of the difficulties below (1-3): "); + Console.WriteLine("1. Easy (1 digit operations) - Default"); + Console.WriteLine("2. Medium (1 digit number and 2 digit number operations)"); + Console.WriteLine("3: Hard (2 digit number operations)"); + Console.WriteLine("4: Go Back"); + + var difficultyInput = Console.ReadLine()?.Trim(); + + if (difficultyInput == "4") + { + break; + } + + _difficulty = difficultyInput switch + { + "1" => Difficulty.Easy, + "2" => Difficulty.Medium, + "3" => Difficulty.Hard, + _ => _difficulty // Invalid keeps the selected one + }; + } + } +} \ No newline at end of file diff --git a/MathGame.Dknx8888/Program.cs b/MathGame.Dknx8888/Program.cs new file mode 100644 index 00000000..4ffbcc5f --- /dev/null +++ b/MathGame.Dknx8888/Program.cs @@ -0,0 +1,4 @@ +using MathGame.Dknx8888; + +var menu = new Menu(); +menu.ShowMenu(); \ No newline at end of file