diff --git a/60 Mastermind/csharp/Game/Game.csproj b/60 Mastermind/csharp/Game/Game.csproj
new file mode 100644
index 00000000..849a99d4
--- /dev/null
+++ b/60 Mastermind/csharp/Game/Game.csproj
@@ -0,0 +1,7 @@
+
+
+ Exe
+ net5.0
+ enable
+
+
diff --git a/60 Mastermind/csharp/Game/src/Code.cs b/60 Mastermind/csharp/Game/src/Code.cs
new file mode 100644
index 00000000..f8189c48
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/Code.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Game
+{
+ ///
+ /// Represents a secret code in the game.
+ ///
+ public class Code
+ {
+ private readonly int[] m_colors;
+
+ ///
+ /// Initializes a new instance of the Code class from the given set
+ /// of positions.
+ ///
+ ///
+ /// Contains the color for each position.
+ ///
+ public Code(IEnumerable colors)
+ {
+ m_colors = colors.ToArray();
+ if (m_colors.Length == 0)
+ throw new ArgumentException("A code must contain at least one position");
+ }
+
+ ///
+ /// Compares this code with the given code.
+ ///
+ ///
+ /// The code to compare.
+ ///
+ ///
+ /// A number of black pegs and a number of white pegs. The number
+ /// of black pegs is the number of positions that contain the same
+ /// color in both codes. The number of white pegs is the number of
+ /// colors that appear in both codes, but in the wrong positions.
+ ///
+ public (int blacks, int whites) Compare(Code other)
+ {
+ // What follows is the O(N^2) from the original BASIC program
+ // (where N is the number of positions in the code). Note that
+ // there is an O(N) algorithm. (Finding it is left as an
+ // exercise for the reader.)
+ if (other.m_colors.Length != m_colors.Length)
+ throw new ArgumentException("Only codes of the same length can be compared");
+
+ // Keeps track of which positions in the other code have already
+ // been marked as exact or close matches.
+ var consumed = new bool[m_colors.Length];
+
+ var blacks = 0;
+ var whites = 0;
+
+ for (var i = 0; i < m_colors.Length; ++i)
+ {
+ if (m_colors[i] == other.m_colors[i])
+ {
+ ++blacks;
+ consumed[i] = true;
+ }
+ else
+ {
+ // Check if the current color appears elsewhere in the
+ // other code. We must be careful not to consider
+ // positions that are also exact matches.
+ for (var j = 0; j < m_colors.Length; ++j)
+ {
+ if (!consumed[j] &&
+ m_colors[i] == other.m_colors[j] &&
+ m_colors[j] != other.m_colors[j])
+ {
+ ++whites;
+ consumed[j] = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return (blacks, whites);
+ }
+
+ ///
+ /// Gets a string representation of the code.
+ ///
+ public override string ToString() =>
+ new (m_colors.Select(index => Colors.List[index].ShortName).ToArray());
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/CodeFactory.cs b/60 Mastermind/csharp/Game/src/CodeFactory.cs
new file mode 100644
index 00000000..f1d0cb98
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/CodeFactory.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Game
+{
+ ///
+ /// Provides methods for generating codes with a given number of positions
+ /// and colors.
+ ///
+ public class CodeFactory
+ {
+ ///
+ /// Gets the number of colors in codes generated by this factory.
+ ///
+ public int Colors { get; }
+
+ ///
+ /// Gets the number of positions in codes generated by this factory.
+ ///
+ public int Positions { get; }
+
+ ///
+ /// Gets the number of distinct codes that this factory can
+ /// generate.
+ ///
+ public int Possibilities { get; }
+
+ ///
+ /// Initializes a new instance of the CodeFactory class.
+ ///
+ ///
+ /// The number of positions.
+ ///
+ ///
+ /// The number of colors.
+ ///
+ public CodeFactory(int positions, int colors)
+ {
+ if (positions < 1)
+ throw new ArgumentException("A code must contain at least one position");
+
+ if (colors < 1)
+ throw new ArgumentException("A code must contain at least one color");
+
+ if (colors > Game.Colors.List.Length)
+ throw new ArgumentException($"A code can contain no more than {Game.Colors.List.Length} colors");
+
+ Positions = positions;
+ Colors = colors;
+ Possibilities = (int)Math.Pow(colors, positions);
+ }
+
+ ///
+ /// Creates a specified code.
+ ///
+ ///
+ /// The number of the code to create from 0 to Possibilities - 1.
+ ///
+ public Code Create(int number) =>
+ EnumerateCodes().Skip(number).First();
+
+ ///
+ /// Creates a random code using the provided random number generator.
+ ///
+ ///
+ /// The random number generator.
+ ///
+ public Code Create(Random random) =>
+ Create(random.Next(Possibilities));
+
+ ///
+ /// Generates a collection of codes containing every code that this
+ /// factory can create exactly once.
+ ///
+ public IEnumerable EnumerateCodes()
+ {
+ var current = new int[Positions];
+ var position = default(int);
+
+ do
+ {
+ yield return new Code(current);
+
+ position = 0;
+ while (position < Positions && ++current[position] == Colors)
+ current[position++] = 0;
+ }
+ while (position < Positions);
+ }
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/ColorInfo.cs b/60 Mastermind/csharp/Game/src/ColorInfo.cs
new file mode 100644
index 00000000..b05b8fa9
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/ColorInfo.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Game
+{
+ ///
+ /// Stores information about a color.
+ ///
+ public record ColorInfo
+ {
+ ///
+ /// Gets a single character that represents the color.
+ ///
+ public char ShortName { get; init; }
+
+ ///
+ /// Gets the color's full name.
+ ///
+ public string LongName { get; init; } = String.Empty;
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/Colors.cs b/60 Mastermind/csharp/Game/src/Colors.cs
new file mode 100644
index 00000000..6901633c
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/Colors.cs
@@ -0,0 +1,20 @@
+namespace Game
+{
+ ///
+ /// Provides information about the colors that can be used in codes.
+ ///
+ public static class Colors
+ {
+ public static readonly ColorInfo[] List = new[]
+ {
+ new ColorInfo { ShortName = 'B', LongName = "BLACK" },
+ new ColorInfo { ShortName = 'W', LongName = "WHITE" },
+ new ColorInfo { ShortName = 'R', LongName = "RED" },
+ new ColorInfo { ShortName = 'G', LongName = "GREEN" },
+ new ColorInfo { ShortName = 'O', LongName = "ORANGE" },
+ new ColorInfo { ShortName = 'Y', LongName = "YELLOW" },
+ new ColorInfo { ShortName = 'P', LongName = "PURPLE" },
+ new ColorInfo { ShortName = 'T', LongName = "TAN" }
+ };
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/Command.cs b/60 Mastermind/csharp/Game/src/Command.cs
new file mode 100644
index 00000000..5df3201a
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/Command.cs
@@ -0,0 +1,13 @@
+namespace Game
+{
+ ///
+ /// Enumerates the different commands that the user can issue during
+ /// the game.
+ ///
+ public enum Command
+ {
+ MakeGuess,
+ ShowBoard,
+ Quit
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/Controller.cs b/60 Mastermind/csharp/Game/src/Controller.cs
new file mode 100644
index 00000000..85a41f9f
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/Controller.cs
@@ -0,0 +1,175 @@
+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;
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/EnumerableExtensions.cs b/60 Mastermind/csharp/Game/src/EnumerableExtensions.cs
new file mode 100644
index 00000000..3bd6cf96
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/EnumerableExtensions.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Game
+{
+ ///
+ /// Provides additional methods for the
+ /// interface.
+ ///
+ public static class EnumerableExtensions
+ {
+ ///
+ /// Cycles through the integer values in the range [0, count).
+ ///
+ ///
+ /// The first value to return.
+ ///
+ ///
+ /// The number of values to return.
+ ///
+ public static IEnumerable Cycle(int start, int count)
+ {
+ if (count < 1)
+ throw new ArgumentException("count must be at least 1");
+
+ if (start < 0 || start >= count)
+ throw new ArgumentException("start must be in the range [0, count)");
+
+ for (var i = start; i < count; ++i)
+ yield return i;
+
+ for (var i = 0; i < start; ++i)
+ yield return i;
+ }
+
+ ///
+ /// Finds the index of the first item in the given sequence that
+ /// satisfies the given predicate.
+ ///
+ ///
+ /// The type of elements in the sequence.
+ ///
+ ///
+ /// The source sequence.
+ ///
+ ///
+ /// The predicate function.
+ ///
+ ///
+ /// The index of the first element in the source sequence for which
+ /// predicate(element) is true. If there is no such element, return
+ /// is null.
+ ///
+ public static int? FindFirstIndex(this IEnumerable source, Func predicate) =>
+ source.Select((element, index) => predicate(element) ? index : default(int?))
+ .FirstOrDefault(index => index.HasValue);
+
+ ///
+ /// Returns the first item in the given sequence that matches the
+ /// given predicate.
+ ///
+ ///
+ /// The type of elements in the sequence.
+ ///
+ ///
+ /// The source sequence.
+ ///
+ ///
+ /// The predicate to check against each element.
+ ///
+ ///
+ /// The value to return if no elements match the predicate.
+ ///
+ ///
+ /// The first item in the source sequence that matches the given
+ /// predicate, or the provided default value if none do.
+ ///
+ public static T FirstOrDefault(this IEnumerable source, Func predicate, T defaultValue)
+ {
+ foreach (var element in source)
+ if (predicate(element))
+ return element;
+
+ return defaultValue;
+ }
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/Program.cs b/60 Mastermind/csharp/Game/src/Program.cs
new file mode 100644
index 00000000..13f525d7
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/Program.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Game
+{
+ // MASTERMIND II
+ // STEVE NORTH
+ // CREATIVE COMPUTING
+ // PO BOX 789-M MORRISTOWN NEW JERSEY 07960
+ class Program
+ {
+ public const int MaximumGuesses = 10;
+
+ static void Main()
+ {
+ var (codeFactory, rounds) = StartGame();
+
+ var random = new Random();
+ var humanScore = 0;
+ var computerScore = 0;
+
+ for (var round = 1; round <= rounds; ++round)
+ {
+ View.ShowStartOfRound(round);
+
+ if (!HumanTakesTurn())
+ return;
+
+ while (!ComputerTakesTurn())
+ View.ShowInconsistentInformation();
+ }
+
+ View.ShowScores(humanScore, computerScore, isFinal: true);
+
+ ///
+ /// Gets the game start parameters from the user.
+ ///
+ (CodeFactory codeFactory, int rounds) StartGame()
+ {
+ View.ShowBanner();
+
+ var colors = Controller.GetNumberOfColors();
+ var positions = Controller.GetNumberOfPositions();
+ var rounds = Controller.GetNumberOfRounds();
+
+ var codeFactory = new CodeFactory(positions, colors);
+
+ View.ShowTotalPossibilities(codeFactory.Possibilities);
+ View.ShowColorTable(codeFactory.Colors);
+
+ return (codeFactory, rounds);
+ }
+
+ ///
+ /// Executes the human's turn.
+ ///
+ ///
+ /// True if thue human completed his or her turn and false if
+ /// he or she quit the game.
+ ///
+ bool HumanTakesTurn()
+ {
+ // Store a history of the human's guesses (used for the show
+ // board command below).
+ var history = new List();
+ var code = codeFactory.Create(random);
+ var guessNumber = default(int);
+
+ for (guessNumber = 1; guessNumber <= MaximumGuesses; ++guessNumber)
+ {
+ var guess = default(Code);
+
+ while (guess is null)
+ {
+ switch (Controller.GetCommand(guessNumber, codeFactory.Positions, codeFactory.Colors))
+ {
+ case (Command.MakeGuess, Code input):
+ guess = input;
+ break;
+ case (Command.ShowBoard, _):
+ View.ShowBoard(history);
+ break;
+ case (Command.Quit, _):
+ View.ShowQuitGame(code);
+ return false;
+ }
+ }
+
+ var (blacks, whites) = code.Compare(guess);
+ if (blacks == codeFactory.Positions)
+ break;
+
+ View.ShowResults(blacks, whites);
+
+ history.Add(new TurnResult(guess, blacks, whites));
+ }
+
+ if (guessNumber <= MaximumGuesses)
+ View.ShowHumanGuessedCode(guessNumber);
+ else
+ View.ShowHumanFailedToGuessCode(code);
+
+ humanScore += guessNumber;
+
+ View.ShowScores(humanScore, computerScore, isFinal: false);
+ return true;
+ }
+
+ ///
+ /// Executes the computers turn.
+ ///
+ ///
+ /// True if the computer completes its turn successfully and false
+ /// if it does not (due to human error).
+ ///
+ bool ComputerTakesTurn()
+ {
+ var isCandidate = new bool[codeFactory.Possibilities];
+ var guessNumber = default(int);
+
+ Array.Fill(isCandidate, true);
+
+ View.ShowComputerStartTurn();
+ Controller.WaitUntilReady();
+
+ for (guessNumber = 1; guessNumber <= MaximumGuesses; ++guessNumber)
+ {
+ // Starting with a random code, cycle through codes until
+ // we find one that is still a candidate solution. If
+ // there are no remaining candidates, then it implies that
+ // the user made an error in one or more responses.
+ var codeNumber = EnumerableExtensions.Cycle(random.Next(codeFactory.Possibilities), codeFactory.Possibilities)
+ .FirstOrDefault(i => isCandidate[i], -1);
+
+ if (codeNumber < 0)
+ return false;
+
+ var guess = codeFactory.Create(codeNumber);
+
+ var (blacks, whites) = Controller.GetBlacksWhites(guess);
+ if (blacks == codeFactory.Positions)
+ break;
+
+ // Mark codes which are no longer potential solutions. We
+ // know that the current guess yields the above number of
+ // blacks and whites when compared to the solution, so any
+ // code that yields a different number of blacks or whites
+ // can't be the answer.
+ foreach (var (candidate, index) in codeFactory.EnumerateCodes().Select((candidate, index) => (candidate, index)))
+ {
+ if (isCandidate[index])
+ {
+ var (candidateBlacks, candidateWhites) = guess.Compare(candidate);
+ if (blacks != candidateBlacks || whites != candidateWhites)
+ isCandidate[index] = false;
+ }
+ }
+ }
+
+ if (guessNumber <= MaximumGuesses)
+ View.ShowComputerGuessedCode(guessNumber);
+ else
+ View.ShowComputerFailedToGuessCode();
+
+ computerScore += guessNumber;
+ View.ShowScores(humanScore, computerScore, isFinal: false);
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/TurnResult.cs b/60 Mastermind/csharp/Game/src/TurnResult.cs
new file mode 100644
index 00000000..b854e92f
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/TurnResult.cs
@@ -0,0 +1,38 @@
+namespace Game
+{
+ ///
+ /// Stores the result of a player's turn.
+ ///
+ public record TurnResult
+ {
+ ///
+ /// Gets the code guessed by the player.
+ ///
+ public Code Guess { get; }
+
+ ///
+ /// Gets the number of black pegs resulting from the guess.
+ ///
+ public int Blacks { get; }
+
+ ///
+ /// Gets the number of white pegs resulting from the guess.
+ ///
+ public int Whites { get; }
+
+ ///
+ /// Initializes a new instance of the TurnResult record.
+ ///
+ ///
+ /// The player's guess.
+ ///
+ ///
+ /// The number of black pegs.
+ ///
+ ///
+ /// The number of white pegs.
+ ///
+ public TurnResult(Code guess, int blacks, int whites) =>
+ (Guess, Blacks, Whites) = (guess, blacks, whites);
+ }
+}
diff --git a/60 Mastermind/csharp/Game/src/View.cs b/60 Mastermind/csharp/Game/src/View.cs
new file mode 100644
index 00000000..ea8bd898
--- /dev/null
+++ b/60 Mastermind/csharp/Game/src/View.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Game
+{
+ ///
+ /// Contains functions for displaying information to the end user.
+ ///
+ public static class View
+ {
+ public static void ShowBanner()
+ {
+ Console.WriteLine(" MASTERMIND");
+ Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ Console.WriteLine();
+ Console.WriteLine();
+ Console.WriteLine();
+ }
+
+ public static void ShowTotalPossibilities(int possibilities)
+ {
+ Console.WriteLine($"TOTAL POSSIBILITIES = {possibilities}");
+ Console.WriteLine();
+ }
+
+ public static void ShowColorTable(int numberOfColors)
+ {
+ Console.WriteLine();
+ Console.WriteLine("COLOR LETTER");
+ Console.WriteLine("===== ======");
+
+ foreach (var color in Colors.List.Take(numberOfColors))
+ Console.WriteLine($"{color.LongName,-13}{color.ShortName}");
+
+ Console.WriteLine();
+ }
+
+ public static void ShowStartOfRound(int roundNumber)
+ {
+ Console.WriteLine();
+ Console.WriteLine($"ROUND NUMBER {roundNumber} ----");
+ Console.WriteLine();
+ Console.WriteLine("GUESS MY COMBINATION.");
+ Console.WriteLine();
+ }
+
+ public static void ShowBoard(IEnumerable history)
+ {
+ Console.WriteLine();
+ Console.WriteLine("BOARD");
+ Console.WriteLine("MOVE GUESS BLACK WHITE");
+
+ var moveNumber = 0;
+ foreach (var result in history)
+ Console.WriteLine($"{++moveNumber,-9}{result.Guess,-16}{result.Blacks,-10}{result.Whites}");
+
+ Console.WriteLine();
+ }
+
+ public static void ShowQuitGame(Code code)
+ {
+ Console.WriteLine($"QUITTER! MY COMBINATION WAS: {code}");
+ Console.WriteLine("GOOD BYE");
+ }
+
+ public static void ShowResults(int blacks, int whites)
+ {
+ Console.WriteLine($"YOU HAVE {blacks} BLACKS AND {whites} WHITES.");
+ }
+
+ public static void ShowHumanGuessedCode(int guessNumber)
+ {
+ Console.WriteLine($"YOU GUESSED IT IN {guessNumber} MOVES!");
+ }
+
+ public static void ShowHumanFailedToGuessCode(Code code)
+ {
+ // Note: The original code did not print out the combination, but
+ // this appears to be a bug.
+ Console.WriteLine("YOU RAN OUT OF MOVES! THAT'S ALL YOU GET!");
+ Console.WriteLine($"THE ACTUAL COMBINATION WAS: {code}");
+ }
+
+ public static void ShowScores(int humanScore, int computerScore, bool isFinal)
+ {
+ if (isFinal)
+ {
+ Console.WriteLine("GAME OVER");
+ Console.WriteLine("FINAL SCORE:");
+ }
+ else
+ Console.WriteLine("SCORE:");
+
+ Console.WriteLine($" COMPUTER {computerScore}");
+ Console.WriteLine($" HUMAN {humanScore}");
+ Console.WriteLine();
+ }
+
+ public static void ShowComputerStartTurn()
+ {
+ Console.WriteLine("NOW I GUESS. THINK OF A COMBINATION.");
+ }
+
+ public static void ShowInconsistentInformation()
+ {
+ Console.WriteLine("YOU HAVE GIVEN ME INCONSISTENT INFORMATION.");
+ Console.WriteLine("TRY AGAIN, AND THIS TIME PLEASE BE MORE CAREFUL.");
+ }
+
+ public static void ShowComputerGuessedCode(int guessNumber)
+ {
+ Console.WriteLine($"I GOT IT IN {guessNumber} MOVES!");
+ }
+
+ public static void ShowComputerFailedToGuessCode()
+ {
+ Console.WriteLine("I USED UP ALL MY MOVES!");
+ Console.WriteLine("I GUESS MY CPU IS JUST HAVING AN OFF DAY.");
+ }
+
+ public static void PromptNumberOfColors()
+ {
+ Console.Write("NUMBER OF COLORS? ");
+ }
+
+ public static void PromptNumberOfPositions()
+ {
+ Console.Write("NUMBER OF POSITIONS? ");
+ }
+
+ public static void PromptNumberOfRounds()
+ {
+ Console.Write("NUMBER OF ROUNDS? ");
+ }
+
+ public static void PromptGuess(int moveNumber)
+ {
+ Console.Write($"MOVE # {moveNumber} GUESS ? ");
+ }
+
+ public static void PromptReady()
+ {
+ Console.Write("HIT RETURN WHEN READY ? ");
+ }
+
+ public static void PromptBlacksWhites(Code code)
+ {
+ Console.Write($"MY GUESS IS: {code}");
+ Console.Write(" BLACKS, WHITES ? ");
+ }
+
+ public static void PromptTwoValues()
+ {
+ Console.WriteLine("PLEASE ENTER TWO VALUES, SEPARATED BY A COMMA");
+ }
+
+ public static void PromptValidInteger()
+ {
+ Console.WriteLine("PLEASE ENTER AN INTEGER VALUE");
+ }
+
+ public static void NotifyBadNumberOfPositions()
+ {
+ Console.WriteLine("BAD NUMBER OF POSITIONS");
+ }
+
+ public static void NotifyInvalidColor(char colorKey)
+ {
+ Console.WriteLine($"'{colorKey}' IS UNRECOGNIZED.");
+ }
+
+ public static void NotifyTooManyColors(int maxColors)
+ {
+ Console.WriteLine($"NO MORE THAN {maxColors}, PLEASE!");
+ }
+ }
+}
diff --git a/60 Mastermind/csharp/Mastermind.sln b/60 Mastermind/csharp/Mastermind.sln
new file mode 100644
index 00000000..c3827fb7
--- /dev/null
+++ b/60 Mastermind/csharp/Mastermind.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31321.278
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game", "Game\Game.csproj", "{E8D63140-971D-4FBF-8138-964E54CCB7DD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E8D63140-971D-4FBF-8138-964E54CCB7DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8D63140-971D-4FBF-8138-964E54CCB7DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E8D63140-971D-4FBF-8138-964E54CCB7DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E8D63140-971D-4FBF-8138-964E54CCB7DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1BDFBEE6-8345-438C-8FCE-B2C9394CC080}
+ EndGlobalSection
+EndGlobal