Merge pull request #832 from drewjcooper/csharp-72-queen

C# 72 Queen
This commit is contained in:
Jeff Atwood
2023-02-04 12:01:29 -08:00
committed by GitHub
24 changed files with 317 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
namespace Queen;
internal class Computer
{
private static readonly HashSet<Position> _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 };
private static readonly HashSet<Position> _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();
}
}

57
72_Queen/csharp/Game.cs Normal file
View File

@@ -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 };
}

View File

@@ -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<Position> 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 = ""; }
}
}
}

15
72_Queen/csharp/Move.cs Normal file
View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -6,4 +6,12 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources/*.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -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
};
}

View File

@@ -0,0 +1 @@
Anyone else care to try

View File

@@ -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

View File

@@ -0,0 +1 @@
Computer moves to square {0}

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
It looks like I have won by forfeit.

View File

@@ -0,0 +1,4 @@
Nice try, but it looks like I have won.
Thanks for playing.

View File

@@ -0,0 +1,2 @@
Y O U C H E A T . . . Try again

View File

@@ -0,0 +1,3 @@
Please read the instructions again.
You have begun illegally.

View File

@@ -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.

View File

@@ -0,0 +1 @@
Do you want instructions

View File

@@ -0,0 +1 @@
What is your move

View File

@@ -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}'.");
}

View File

@@ -0,0 +1 @@
Where would you like to start

View File

@@ -0,0 +1,3 @@
Ok --- thanks again.

View File

@@ -0,0 +1,5 @@
Queen
Creative Computing Morristown, New Jersey

View File

@@ -0,0 +1 @@
Please answer 'Yes' or 'No'.