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