mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-30 06:31:46 -08:00
34
72_Queen/csharp/Computer.cs
Normal file
34
72_Queen/csharp/Computer.cs
Normal 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
57
72_Queen/csharp/Game.cs
Normal 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 };
|
||||
}
|
||||
38
72_Queen/csharp/IOExtensions.cs
Normal file
38
72_Queen/csharp/IOExtensions.cs
Normal 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
15
72_Queen/csharp/Move.cs
Normal 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);
|
||||
}
|
||||
24
72_Queen/csharp/Position.cs
Normal file
24
72_Queen/csharp/Position.cs
Normal 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);
|
||||
}
|
||||
7
72_Queen/csharp/Program.cs
Normal file
7
72_Queen/csharp/Program.cs
Normal 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();
|
||||
@@ -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>
|
||||
|
||||
12
72_Queen/csharp/RandomExtensions.cs
Normal file
12
72_Queen/csharp/RandomExtensions.cs
Normal 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
|
||||
};
|
||||
}
|
||||
1
72_Queen/csharp/Resources/AnyonePrompt.txt
Normal file
1
72_Queen/csharp/Resources/AnyonePrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Anyone else care to try
|
||||
27
72_Queen/csharp/Resources/Board.txt
Normal file
27
72_Queen/csharp/Resources/Board.txt
Normal 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
|
||||
|
||||
|
||||
|
||||
1
72_Queen/csharp/Resources/ComputerMove.txt
Normal file
1
72_Queen/csharp/Resources/ComputerMove.txt
Normal file
@@ -0,0 +1 @@
|
||||
Computer moves to square {0}
|
||||
7
72_Queen/csharp/Resources/Congratulations.txt
Normal file
7
72_Queen/csharp/Resources/Congratulations.txt
Normal 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.
|
||||
|
||||
3
72_Queen/csharp/Resources/Forfeit.txt
Normal file
3
72_Queen/csharp/Resources/Forfeit.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
It looks like I have won by forfeit.
|
||||
|
||||
4
72_Queen/csharp/Resources/IWin.txt
Normal file
4
72_Queen/csharp/Resources/IWin.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
Nice try, but it looks like I have won.
|
||||
Thanks for playing.
|
||||
|
||||
2
72_Queen/csharp/Resources/IllegalMove.txt
Normal file
2
72_Queen/csharp/Resources/IllegalMove.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
Y O U C H E A T . . . Try again
|
||||
3
72_Queen/csharp/Resources/IllegalStart.txt
Normal file
3
72_Queen/csharp/Resources/IllegalStart.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Please read the instructions again.
|
||||
You have begun illegally.
|
||||
|
||||
15
72_Queen/csharp/Resources/Instructions.txt
Normal file
15
72_Queen/csharp/Resources/Instructions.txt
Normal 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.
|
||||
|
||||
1
72_Queen/csharp/Resources/InstructionsPrompt.txt
Normal file
1
72_Queen/csharp/Resources/InstructionsPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Do you want instructions
|
||||
1
72_Queen/csharp/Resources/MovePrompt.txt
Normal file
1
72_Queen/csharp/Resources/MovePrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
What is your move
|
||||
47
72_Queen/csharp/Resources/Resource.cs
Normal file
47
72_Queen/csharp/Resources/Resource.cs
Normal 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}'.");
|
||||
}
|
||||
1
72_Queen/csharp/Resources/StartPrompt.txt
Normal file
1
72_Queen/csharp/Resources/StartPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Where would you like to start
|
||||
3
72_Queen/csharp/Resources/Thanks.txt
Normal file
3
72_Queen/csharp/Resources/Thanks.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
Ok --- thanks again.
|
||||
5
72_Queen/csharp/Resources/Title.txt
Normal file
5
72_Queen/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Queen
|
||||
Creative Computing Morristown, New Jersey
|
||||
|
||||
|
||||
|
||||
1
72_Queen/csharp/Resources/YesOrNo.txt
Normal file
1
72_Queen/csharp/Resources/YesOrNo.txt
Normal file
@@ -0,0 +1 @@
|
||||
Please answer 'Yes' or 'No'.
|
||||
Reference in New Issue
Block a user