using System; using System.Collections.Immutable; using System.Linq; namespace Game { /// /// Contains functions for getting input from the end user. /// public static class Controller { /// /// Maps the letters for each color to the integer value representing /// that color. /// /// /// We derive this map from the Colors list rather than defining the /// entries directly in order to keep all color related information /// in one place. (This makes it easier to change the color options /// later.) /// private static ImmutableDictionary ColorsByKey = Colors.List .Select((info, index) => (key: info.ShortName, index)) .ToImmutableDictionary(entry => entry.key, entry => entry.index); /// /// Gets the number of colors to use in the secret code. /// public static int GetNumberOfColors() { var maximumColors = Colors.List.Length; var colors = 0; while (colors < 1 || colors > maximumColors) { colors = GetInteger(View.PromptNumberOfColors); if (colors > maximumColors) View.NotifyTooManyColors(maximumColors); } return colors; } /// /// Gets the number of positions in the secret code. /// /// public static int GetNumberOfPositions() { // Note: We should probably ensure that the user enters a sane // number of positions here. (Things go south pretty quickly // with a large number of positions.) But since the original // program did not, neither will we. return GetInteger(View.PromptNumberOfPositions); } /// /// Gets the number of rounds to play. /// public static int GetNumberOfRounds() { // Note: Silly numbers of rounds (like 0, or a negative number) // are harmless, but it would still make sense to validate. return GetInteger(View.PromptNumberOfRounds); } /// /// Gets a command from the user. /// /// /// The current move number. /// /// /// The number of code positions. /// /// /// The maximum number of code colors. /// /// /// The entered command and guess (if applicable). /// public static (Command command, Code? guess) GetCommand(int moveNumber, int positions, int colors) { while (true) { View.PromptGuess (moveNumber); var input = Console.ReadLine(); if (input is null) Environment.Exit(0); switch (input.ToUpperInvariant()) { case "BOARD": return (Command.ShowBoard, null); case "QUIT": return (Command.Quit, null); default: if (input.Length != positions) View.NotifyBadNumberOfPositions(); else if (input.FindFirstIndex(c => !TranslateColor(c).HasValue) is int invalidPosition) View.NotifyInvalidColor(input[invalidPosition]); else return (Command.MakeGuess, new Code(input.Select(c => TranslateColor(c)!.Value))); break; } } } /// /// Waits until the user indicates that he or she is ready to continue. /// public static void WaitUntilReady() { View.PromptReady(); var input = Console.ReadLine(); if (input is null) Environment.Exit(0); } /// /// Gets the number of blacks and whites for the given code from the /// user. /// public static (int blacks, int whites) GetBlacksWhites(Code code) { while (true) { View.PromptBlacksWhites(code); var input = Console.ReadLine(); if (input is null) Environment.Exit(0); var parts = input.Split(','); if (parts.Length != 2) View.PromptTwoValues(); else if (!Int32.TryParse(parts[0], out var blacks) || !Int32.TryParse(parts[1], out var whites)) View.PromptValidInteger(); else return (blacks, whites); } } /// /// Gets an integer value from the user. /// private static int GetInteger(Action prompt) { while (true) { prompt(); var input = Console.ReadLine(); if (input is null) Environment.Exit(0); if (Int32.TryParse(input, out var result)) return result; else View.PromptValidInteger(); } } /// /// Translates the given character into the corresponding color. /// private static int? TranslateColor(char c) => ColorsByKey.TryGetValue(c, out var index) ? index : null; } }