diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs new file mode 100644 index 00000000..459c7337 --- /dev/null +++ b/72_Queen/csharp/Computer.cs @@ -0,0 +1,34 @@ +namespace Queen; + +internal class Computer +{ + private static readonly HashSet _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 }; + private static readonly HashSet _desirable = new() { 73, 75, 126, 127, 158 }; + private readonly IRandom _random; + + public Computer(IRandom random) + { + _random = random; + } + + public Position GetMove(Position from) + => from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from)); + + private Move FindMove(Position from) + { + for (int i = 7; i > 0; i--) + { + if (IsOptimal(Move.Left, out var move)) { return move; } + if (IsOptimal(Move.Down, out move)) { return move; } + if (IsOptimal(Move.DownLeft, out move)) { return move; } + + bool IsOptimal(Move direction, out Move move) + { + move = direction * i; + return _desirable.Contains(from + move); + } + } + + return _random.NextMove(); + } +} diff --git a/72_Queen/csharp/Game.cs b/72_Queen/csharp/Game.cs new file mode 100644 index 00000000..35b1dc9a --- /dev/null +++ b/72_Queen/csharp/Game.cs @@ -0,0 +1,57 @@ +namespace Queen; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Computer _computer; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + _computer = new Computer(random); + } + + internal void PlaySeries() + { + _io.Write(Streams.Title); + if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } + + while (true) + { + var result = PlayGame(); + _io.Write(result switch + { + Result.HumanForfeits => Streams.Forfeit, + Result.HumanWins => Streams.Congratulations, + Result.ComputerWins => Streams.IWin, + _ => throw new InvalidOperationException($"Unexpected result {result}") + }); + + if (!_io.ReadYesNo(Prompts.Anyone)) { break; } + } + + _io.Write(Streams.Thanks); + } + + private Result PlayGame() + { + _io.Write(Streams.Board); + var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + + while (true) + { + var computerPosition = _computer.GetMove(humanPosition); + _io.Write(Strings.ComputerMove(computerPosition)); + if (computerPosition.IsEnd) { return Result.ComputerWins; } + + humanPosition = _io.ReadPosition(Prompts.Move, p => (p - computerPosition).IsValid, Streams.IllegalMove); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + if (humanPosition.IsEnd) { return Result.HumanWins; } + } + } + + private enum Result { ComputerWins, HumanWins, HumanForfeits }; +} diff --git a/72_Queen/csharp/IOExtensions.cs b/72_Queen/csharp/IOExtensions.cs new file mode 100644 index 00000000..51959967 --- /dev/null +++ b/72_Queen/csharp/IOExtensions.cs @@ -0,0 +1,38 @@ +namespace Queen; + +internal static class IOExtensions +{ + internal static bool ReadYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var answer = io.ReadString(prompt).ToLower(); + if (answer == "yes") { return true; } + if (answer == "no") { return false; } + + io.Write(Streams.YesOrNo); + } + } + + internal static Position ReadPosition( + this IReadWrite io, + string prompt, + Predicate isValid, + Stream error, + bool repeatPrompt = false) + { + while (true) + { + var response = io.ReadNumber(prompt); + var number = (int)response; + var position = new Position(number); + if (number == response && (position.IsZero || isValid(position))) + { + return position; + } + + io.Write(error); + if (!repeatPrompt) { prompt = ""; } + } + } +} diff --git a/72_Queen/csharp/Move.cs b/72_Queen/csharp/Move.cs new file mode 100644 index 00000000..4e18647b --- /dev/null +++ b/72_Queen/csharp/Move.cs @@ -0,0 +1,15 @@ +namespace Queen; + +internal record struct Move(int Diagonal, int Row) +{ + public static readonly Move Left = new(1, 0); + public static readonly Move DownLeft = new(2, 1); + public static readonly Move Down = new(1, 1); + + public bool IsValid => Diagonal > 0 && (IsLeft || IsDown || IsDownLeft); + private bool IsLeft => Row == 0; + private bool IsDown => Row == Diagonal; + private bool IsDownLeft => Row * 2 == Diagonal; + + public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale); +} \ No newline at end of file diff --git a/72_Queen/csharp/Position.cs b/72_Queen/csharp/Position.cs new file mode 100644 index 00000000..69971163 --- /dev/null +++ b/72_Queen/csharp/Position.cs @@ -0,0 +1,24 @@ +namespace Queen; + +internal record struct Position(int Diagonal, int Row) +{ + public static readonly Position Zero = new(0); + + public Position(int number) + : this(Diagonal: number / 10, Row: number % 10) + { + } + + public bool IsZero => Row == 0 && Diagonal == 0; + public bool IsStart => Row == 1 || Row == Diagonal; + public bool IsEnd => Row == 8 && Diagonal == 15; + + public override string ToString() => $"{Diagonal}{Row}"; + + public static implicit operator Position(int value) => new(value); + + public static Position operator +(Position position, Move move) + => new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row); + public static Move operator -(Position to, Position from) + => new(Diagonal: to.Diagonal - from.Diagonal, Row: to.Row - from.Row); +} diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs new file mode 100644 index 00000000..5e017df6 --- /dev/null +++ b/72_Queen/csharp/Program.cs @@ -0,0 +1,7 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Queen.Resources.Resource; + +using Queen; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).PlaySeries(); \ No newline at end of file diff --git a/72_Queen/csharp/Queen.csproj b/72_Queen/csharp/Queen.csproj index d3fe4757..3870320c 100644 --- a/72_Queen/csharp/Queen.csproj +++ b/72_Queen/csharp/Queen.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/72_Queen/csharp/RandomExtensions.cs b/72_Queen/csharp/RandomExtensions.cs new file mode 100644 index 00000000..b4e375fd --- /dev/null +++ b/72_Queen/csharp/RandomExtensions.cs @@ -0,0 +1,12 @@ +namespace Queen; + +internal static class RandomExtensions +{ + internal static Move NextMove(this IRandom random) + => random.NextFloat() switch + { + > 0.6F => Move.Down, + > 0.3F => Move.DownLeft, + _ => Move.Left + }; +} diff --git a/72_Queen/csharp/Resources/AnyonePrompt.txt b/72_Queen/csharp/Resources/AnyonePrompt.txt new file mode 100644 index 00000000..a0289fd6 --- /dev/null +++ b/72_Queen/csharp/Resources/AnyonePrompt.txt @@ -0,0 +1 @@ +Anyone else care to try \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Board.txt b/72_Queen/csharp/Resources/Board.txt new file mode 100644 index 00000000..854be1bb --- /dev/null +++ b/72_Queen/csharp/Resources/Board.txt @@ -0,0 +1,27 @@ + + + 81 71 61 51 41 31 21 11 + + + 92 82 72 62 52 42 32 22 + + + 103 93 83 73 63 53 43 33 + + + 114 104 94 84 74 64 54 44 + + + 125 115 105 95 85 75 65 55 + + + 136 126 116 106 96 86 76 66 + + + 147 137 127 117 107 97 87 77 + + + 158 148 138 128 118 108 98 88 + + + diff --git a/72_Queen/csharp/Resources/ComputerMove.txt b/72_Queen/csharp/Resources/ComputerMove.txt new file mode 100644 index 00000000..f31b8e32 --- /dev/null +++ b/72_Queen/csharp/Resources/ComputerMove.txt @@ -0,0 +1 @@ +Computer moves to square {0} diff --git a/72_Queen/csharp/Resources/Congratulations.txt b/72_Queen/csharp/Resources/Congratulations.txt new file mode 100644 index 00000000..9f1db232 --- /dev/null +++ b/72_Queen/csharp/Resources/Congratulations.txt @@ -0,0 +1,7 @@ + +C O N G R A T U L A T I O N S . . . + +You have won--very well played. +It looks like I have met my match. +Thanks for playing--I can't win all the time. + diff --git a/72_Queen/csharp/Resources/Forfeit.txt b/72_Queen/csharp/Resources/Forfeit.txt new file mode 100644 index 00000000..09858bc1 --- /dev/null +++ b/72_Queen/csharp/Resources/Forfeit.txt @@ -0,0 +1,3 @@ + +It looks like I have won by forfeit. + diff --git a/72_Queen/csharp/Resources/IWin.txt b/72_Queen/csharp/Resources/IWin.txt new file mode 100644 index 00000000..ced5d716 --- /dev/null +++ b/72_Queen/csharp/Resources/IWin.txt @@ -0,0 +1,4 @@ + +Nice try, but it looks like I have won. +Thanks for playing. + diff --git a/72_Queen/csharp/Resources/IllegalMove.txt b/72_Queen/csharp/Resources/IllegalMove.txt new file mode 100644 index 00000000..ae4a3bbc --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalMove.txt @@ -0,0 +1,2 @@ + +Y O U C H E A T . . . Try again \ No newline at end of file diff --git a/72_Queen/csharp/Resources/IllegalStart.txt b/72_Queen/csharp/Resources/IllegalStart.txt new file mode 100644 index 00000000..25402bb6 --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalStart.txt @@ -0,0 +1,3 @@ +Please read the instructions again. +You have begun illegally. + diff --git a/72_Queen/csharp/Resources/Instructions.txt b/72_Queen/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..fc2e85b0 --- /dev/null +++ b/72_Queen/csharp/Resources/Instructions.txt @@ -0,0 +1,15 @@ +We are going to play a game based on one of the chess +moves. Our queen will be able to move only to the left, +down, or diagonally down and to the left. + +The object of the game is to place the queen in the lower +left hand square by alternating moves between you and the +computer. The first one to place the queen there wins. + +You go first and place the queen in any one of the squares +on the top row or right hand column. +That will be your first move. +We alternate moves. +You may forfeit by typing '0' as your move. +Be sure to press the return key after each response. + diff --git a/72_Queen/csharp/Resources/InstructionsPrompt.txt b/72_Queen/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/72_Queen/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/72_Queen/csharp/Resources/MovePrompt.txt b/72_Queen/csharp/Resources/MovePrompt.txt new file mode 100644 index 00000000..8cb18999 --- /dev/null +++ b/72_Queen/csharp/Resources/MovePrompt.txt @@ -0,0 +1 @@ +What is your move \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs new file mode 100644 index 00000000..e8297eca --- /dev/null +++ b/72_Queen/csharp/Resources/Resource.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Queen.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream YesOrNo => GetStream(); + public static Stream Board => GetStream(); + public static Stream IllegalStart => GetStream(); + public static Stream IllegalMove => GetStream(); + public static Stream Forfeit => GetStream(); + public static Stream IWin => GetStream(); + public static Stream Congratulations => GetStream(); + public static Stream Thanks => GetStream(); + } + + internal static class Prompts + { + public static string Instructions => GetPrompt(); + public static string Start => GetPrompt(); + public static string Move => GetPrompt(); + public static string Anyone => GetPrompt(); + } + + internal static class Strings + { + public static string ComputerMove(Position position) => string.Format(GetString(), position); + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/72_Queen/csharp/Resources/StartPrompt.txt b/72_Queen/csharp/Resources/StartPrompt.txt new file mode 100644 index 00000000..0b6f395b --- /dev/null +++ b/72_Queen/csharp/Resources/StartPrompt.txt @@ -0,0 +1 @@ +Where would you like to start \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Thanks.txt b/72_Queen/csharp/Resources/Thanks.txt new file mode 100644 index 00000000..2e2e7b63 --- /dev/null +++ b/72_Queen/csharp/Resources/Thanks.txt @@ -0,0 +1,3 @@ + + +Ok --- thanks again. \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Title.txt b/72_Queen/csharp/Resources/Title.txt new file mode 100644 index 00000000..63549d09 --- /dev/null +++ b/72_Queen/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Queen + Creative Computing Morristown, New Jersey + + + diff --git a/72_Queen/csharp/Resources/YesOrNo.txt b/72_Queen/csharp/Resources/YesOrNo.txt new file mode 100644 index 00000000..657bf8aa --- /dev/null +++ b/72_Queen/csharp/Resources/YesOrNo.txt @@ -0,0 +1 @@ +Please answer 'Yes' or 'No'.