From 3e88424a5216963e5d0c7c548aae21567d1a4ce0 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 28 Jan 2023 15:25:52 +1100 Subject: [PATCH] Add computer strategy --- 72_Queen/csharp/Computer.cs | 0 72_Queen/csharp/Game.cs | 164 +++++++++++++++++++++ 72_Queen/csharp/Games.cs | 96 ------------ 72_Queen/csharp/Program.cs | 2 +- 72_Queen/csharp/Resources/Board.txt | 1 + 72_Queen/csharp/Resources/Forfeit.txt | 3 +- 72_Queen/csharp/Resources/Instructions.txt | 1 - 72_Queen/csharp/Resources/Thanks.txt | 2 + 8 files changed, 170 insertions(+), 99 deletions(-) create mode 100644 72_Queen/csharp/Computer.cs create mode 100644 72_Queen/csharp/Game.cs delete mode 100644 72_Queen/csharp/Games.cs diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs new file mode 100644 index 00000000..e69de29b diff --git a/72_Queen/csharp/Game.cs b/72_Queen/csharp/Game.cs new file mode 100644 index 00000000..220e4535 --- /dev/null +++ b/72_Queen/csharp/Game.cs @@ -0,0 +1,164 @@ +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); + if (computerPosition.IsEnd) { return Result.ComputerWins; } + } + + } + + 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/Games.cs b/72_Queen/csharp/Games.cs deleted file mode 100644 index 4504c771..00000000 --- a/72_Queen/csharp/Games.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace Queen; - -internal class Games -{ - private readonly IReadWrite _io; - private readonly IRandom _random; - - public Games(IReadWrite io, IRandom random) - { - _io = io; - _random = random; - } - - internal void Play() - { - _io.Write(Streams.Title); - if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } - - while (true) - { - PlayGame(); - - if (!_io.ReadYesNo(Prompts.Anyone)) - { - _io.Write(Streams.Thanks); - return; - } - } - } - - internal void PlayGame() - { - _io.Write(Streams.Board); - var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true) - if (humanPosition.IsZero) - { - _io.Write(Streams.Forfeit); - return; - } - - - } -} - -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}"; -} \ No newline at end of file diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs index a03fe364..5e017df6 100644 --- a/72_Queen/csharp/Program.cs +++ b/72_Queen/csharp/Program.cs @@ -4,4 +4,4 @@ global using static Queen.Resources.Resource; using Queen; -new Games(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file +new Game(new ConsoleIO(), new RandomNumberGenerator()).PlaySeries(); \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Board.txt b/72_Queen/csharp/Resources/Board.txt index 45a8ab0a..854be1bb 100644 --- a/72_Queen/csharp/Resources/Board.txt +++ b/72_Queen/csharp/Resources/Board.txt @@ -1,4 +1,5 @@ + 81 71 61 51 41 31 21 11 diff --git a/72_Queen/csharp/Resources/Forfeit.txt b/72_Queen/csharp/Resources/Forfeit.txt index 120da75d..09858bc1 100644 --- a/72_Queen/csharp/Resources/Forfeit.txt +++ b/72_Queen/csharp/Resources/Forfeit.txt @@ -1,2 +1,3 @@ -Looks like I have won by forfeit. + +It looks like I have won by forfeit. diff --git a/72_Queen/csharp/Resources/Instructions.txt b/72_Queen/csharp/Resources/Instructions.txt index d440b335..fc2e85b0 100644 --- a/72_Queen/csharp/Resources/Instructions.txt +++ b/72_Queen/csharp/Resources/Instructions.txt @@ -13,4 +13,3 @@ 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/Thanks.txt b/72_Queen/csharp/Resources/Thanks.txt index 53980b09..2e2e7b63 100644 --- a/72_Queen/csharp/Resources/Thanks.txt +++ b/72_Queen/csharp/Resources/Thanks.txt @@ -1 +1,3 @@ + + Ok --- thanks again. \ No newline at end of file