diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs index e69de29b..459c7337 100644 --- a/72_Queen/csharp/Computer.cs +++ 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 index 220e4535..35b1dc9a 100644 --- a/72_Queen/csharp/Game.cs +++ b/72_Queen/csharp/Game.cs @@ -44,121 +44,14 @@ internal class Game 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 }; } - -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(); - } -} - -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 = ""; } - } - } -} - -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); -} - -internal static class RandomExtensions -{ - internal static Move NextMove(this IRandom random) - => random.NextFloat() switch - { - > 0.6F => Move.Down, - > 0.3F => Move.DownLeft, - _ => Move.Left - }; -} - -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 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/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/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/IllegalMove.txt b/72_Queen/csharp/Resources/IllegalMove.txt index 3d31638d..ae4a3bbc 100644 --- a/72_Queen/csharp/Resources/IllegalMove.txt +++ b/72_Queen/csharp/Resources/IllegalMove.txt @@ -1,2 +1,2 @@ -Y O U C H E A T . . . Try again +Y O U C H E A T . . . Try again \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs index 9ff43842..e8297eca 100644 --- a/72_Queen/csharp/Resources/Resource.cs +++ b/72_Queen/csharp/Resources/Resource.cs @@ -12,7 +12,6 @@ internal static class Resource public static Stream YesOrNo => GetStream(); public static Stream Board => GetStream(); public static Stream IllegalStart => GetStream(); - public static Stream ComputerMove => GetStream(); public static Stream IllegalMove => GetStream(); public static Stream Forfeit => GetStream(); public static Stream IWin => GetStream(); @@ -28,9 +27,9 @@ internal static class Resource public static string Anyone => GetPrompt(); } - internal static class Formats + internal static class Strings { - public static string Balance => GetString(); + public static string ComputerMove(Position position) => string.Format(GetString(), position); } private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt");