mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 07:10:42 -08:00
Merge pull request #804 from drewjcooper/csharp-56-life-for-two
C# 56 life for two
This commit is contained in:
74
56_Life_for_Two/csharp/Board.cs
Normal file
74
56_Life_for_Two/csharp/Board.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LifeforTwo;
|
||||||
|
|
||||||
|
internal class Board : IEnumerable<Coordinates>
|
||||||
|
{
|
||||||
|
private readonly Piece[,] _cells = new Piece[7, 7];
|
||||||
|
private readonly Dictionary<int, int> _cellCounts =
|
||||||
|
new() { [Piece.None] = 0, [Piece.Player1] = 0, [Piece.Player2] = 0 };
|
||||||
|
|
||||||
|
public Piece this[Coordinates coordinates]
|
||||||
|
{
|
||||||
|
get => this[coordinates.X, coordinates.Y];
|
||||||
|
set => this[coordinates.X, coordinates.Y] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Piece this[int x, int y]
|
||||||
|
{
|
||||||
|
get => _cells[x, y];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!_cells[x, y].IsEmpty) { _cellCounts[_cells[x, y]] -= 1; }
|
||||||
|
_cells[x, y] = value;
|
||||||
|
_cellCounts[value] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Player1Count => _cellCounts[Piece.Player1];
|
||||||
|
public int Player2Count => _cellCounts[Piece.Player2];
|
||||||
|
|
||||||
|
internal bool IsEmptyAt(Coordinates coordinates) => this[coordinates].IsEmpty;
|
||||||
|
|
||||||
|
internal void ClearCell(Coordinates coordinates) => this[coordinates] = Piece.NewNone();
|
||||||
|
internal void AddPlayer1Piece(Coordinates coordinates) => this[coordinates] = Piece.NewPlayer1();
|
||||||
|
internal void AddPlayer2Piece(Coordinates coordinates) => this[coordinates] = Piece.NewPlayer2();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (var y = 0; y <= 6; y++)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
for (var x = 0; x <= 6; x++)
|
||||||
|
{
|
||||||
|
builder.Append(GetCellDisplay(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCellDisplay(int x, int y) =>
|
||||||
|
(x, y) switch
|
||||||
|
{
|
||||||
|
(0 or 6, _) => $" {y % 6} ",
|
||||||
|
(_, 0 or 6) => $" {x % 6} ",
|
||||||
|
_ => $" {this[x, y]} "
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerator<Coordinates> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (var x = 1; x <= 5; x++)
|
||||||
|
{
|
||||||
|
for (var y = 1; y <= 5; y++)
|
||||||
|
{
|
||||||
|
yield return new(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
31
56_Life_for_Two/csharp/Coordinates.cs
Normal file
31
56_Life_for_Two/csharp/Coordinates.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
namespace LifeforTwo;
|
||||||
|
|
||||||
|
internal record Coordinates (int X, int Y)
|
||||||
|
{
|
||||||
|
public static bool TryCreate((float X, float Y) values, out Coordinates coordinates)
|
||||||
|
{
|
||||||
|
if (values.X <= 0 || values.X > 5 || values.Y <= 0 || values.Y > 5)
|
||||||
|
{
|
||||||
|
coordinates = new(0, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
coordinates = new((int)values.X, (int)values.Y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Coordinates operator +(Coordinates coordinates, int value) =>
|
||||||
|
new (coordinates.X + value, coordinates.Y + value);
|
||||||
|
|
||||||
|
public IEnumerable<Coordinates> GetNeighbors()
|
||||||
|
{
|
||||||
|
yield return new(X - 1, Y);
|
||||||
|
yield return new(X + 1, Y);
|
||||||
|
yield return new(X, Y - 1);
|
||||||
|
yield return new(X, Y + 1);
|
||||||
|
yield return new(X - 1, Y - 1);
|
||||||
|
yield return new(X + 1, Y - 1);
|
||||||
|
yield return new(X - 1, Y + 1);
|
||||||
|
yield return new(X + 1, Y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
56_Life_for_Two/csharp/Game.cs
Normal file
26
56_Life_for_Two/csharp/Game.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
internal class Game
|
||||||
|
{
|
||||||
|
private readonly IReadWrite _io;
|
||||||
|
|
||||||
|
public Game(IReadWrite io)
|
||||||
|
{
|
||||||
|
_io = io;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play()
|
||||||
|
{
|
||||||
|
_io.Write(Streams.Title);
|
||||||
|
|
||||||
|
var life = new Life(_io);
|
||||||
|
|
||||||
|
_io.Write(life.FirstGeneration);
|
||||||
|
|
||||||
|
foreach (var generation in life)
|
||||||
|
{
|
||||||
|
_io.WriteLine();
|
||||||
|
_io.Write(generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
_io.WriteLine(life.Result ?? "No result");
|
||||||
|
}
|
||||||
|
}
|
||||||
89
56_Life_for_Two/csharp/Generation.cs
Normal file
89
56_Life_for_Two/csharp/Generation.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
internal class Generation
|
||||||
|
{
|
||||||
|
private readonly Board _board;
|
||||||
|
|
||||||
|
public Generation(Board board)
|
||||||
|
{
|
||||||
|
_board = board;
|
||||||
|
CountNeighbours();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board Board => _board;
|
||||||
|
|
||||||
|
public int Player1Count => _board.Player1Count;
|
||||||
|
public int Player2Count => _board.Player2Count;
|
||||||
|
|
||||||
|
public string? Result =>
|
||||||
|
(Player1Count, Player2Count) switch
|
||||||
|
{
|
||||||
|
(0, 0) => Strings.Draw,
|
||||||
|
(_, 0) => string.Format(Formats.Winner, 1),
|
||||||
|
(0, _) => string.Format(Formats.Winner, 2),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Generation Create(IReadWrite io)
|
||||||
|
{
|
||||||
|
var board = new Board();
|
||||||
|
|
||||||
|
SetInitialPieces(1, coord => board.AddPlayer1Piece(coord));
|
||||||
|
SetInitialPieces(2, coord => board.AddPlayer2Piece(coord));
|
||||||
|
|
||||||
|
return new Generation(board);
|
||||||
|
|
||||||
|
void SetInitialPieces(int player, Action<Coordinates> setPiece)
|
||||||
|
{
|
||||||
|
io.WriteLine(Formats.InitialPieces, player);
|
||||||
|
for (var i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
setPiece(io.ReadCoordinates(board));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Generation CalculateNextGeneration()
|
||||||
|
{
|
||||||
|
var board = new Board();
|
||||||
|
|
||||||
|
foreach (var coordinates in _board)
|
||||||
|
{
|
||||||
|
board[coordinates] = _board[coordinates].GetNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(board);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPieces(IReadWrite io)
|
||||||
|
{
|
||||||
|
var player1Coordinate = io.ReadCoordinates(1, _board);
|
||||||
|
var player2Coordinate = io.ReadCoordinates(2, _board);
|
||||||
|
|
||||||
|
if (player1Coordinate == player2Coordinate)
|
||||||
|
{
|
||||||
|
io.Write(Streams.SameCoords);
|
||||||
|
// This is a bug existing in the original code. The line should be _board[_coordinates[_player]] = 0;
|
||||||
|
_board.ClearCell(player1Coordinate + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_board.AddPlayer1Piece(player1Coordinate);
|
||||||
|
_board.AddPlayer2Piece(player2Coordinate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CountNeighbours()
|
||||||
|
{
|
||||||
|
foreach (var coordinates in _board)
|
||||||
|
{
|
||||||
|
var piece = _board[coordinates];
|
||||||
|
if (piece.IsEmpty) { continue; }
|
||||||
|
|
||||||
|
foreach (var neighbour in coordinates.GetNeighbors())
|
||||||
|
{
|
||||||
|
_board[neighbour] = _board[neighbour].AddNeighbour(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => _board.ToString();
|
||||||
|
}
|
||||||
22
56_Life_for_Two/csharp/IOExtensions.cs
Normal file
22
56_Life_for_Two/csharp/IOExtensions.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
internal static class IOExtensions
|
||||||
|
{
|
||||||
|
internal static Coordinates ReadCoordinates(this IReadWrite io, int player, Board board)
|
||||||
|
{
|
||||||
|
io.Write(Formats.Player, player);
|
||||||
|
return io.ReadCoordinates(board);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Coordinates ReadCoordinates(this IReadWrite io, Board board)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
io.WriteLine("X,Y");
|
||||||
|
var values = io.Read2Numbers("&&&&&&\r");
|
||||||
|
if (Coordinates.TryCreate(values, out var coordinates) && board.IsEmptyAt(coordinates))
|
||||||
|
{
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
io.Write(Streams.IllegalCoords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
56_Life_for_Two/csharp/Life.cs
Normal file
31
56_Life_for_Two/csharp/Life.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
internal class Life : IEnumerable<Generation>
|
||||||
|
{
|
||||||
|
private readonly IReadWrite _io;
|
||||||
|
|
||||||
|
public Life(IReadWrite io)
|
||||||
|
{
|
||||||
|
_io = io;
|
||||||
|
FirstGeneration = Generation.Create(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Generation FirstGeneration { get; }
|
||||||
|
public string? Result { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerator<Generation> GetEnumerator()
|
||||||
|
{
|
||||||
|
var current = FirstGeneration;
|
||||||
|
while (current.Result is null)
|
||||||
|
{
|
||||||
|
current = current.CalculateNextGeneration();
|
||||||
|
yield return current;
|
||||||
|
|
||||||
|
if (current.Result is null) { current.AddPieces(_io); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = current.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
@@ -6,4 +6,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources/*.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
54
56_Life_for_Two/csharp/Piece.cs
Normal file
54
56_Life_for_Two/csharp/Piece.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace LifeforTwo;
|
||||||
|
|
||||||
|
public struct Piece
|
||||||
|
{
|
||||||
|
public const int None = 0x0000;
|
||||||
|
public const int Player1 = 0x0100;
|
||||||
|
public const int Player2 = 0x1000;
|
||||||
|
private const int PieceMask = Player1 | Player2;
|
||||||
|
private const int NeighbourValueOffset = 8;
|
||||||
|
|
||||||
|
private static readonly ImmutableHashSet<int> _willBePlayer1 =
|
||||||
|
new[] { 0x0003, 0x0102, 0x0103, 0x0120, 0x0130, 0x0121, 0x0112, 0x0111, 0x0012 }.ToImmutableHashSet();
|
||||||
|
private static readonly ImmutableHashSet<int> _willBePlayer2 =
|
||||||
|
new[] { 0x0021, 0x0030, 0x1020, 0x1030, 0x1011, 0x1021, 0x1003, 0x1002, 0x1012 }.ToImmutableHashSet();
|
||||||
|
|
||||||
|
private int _value;
|
||||||
|
|
||||||
|
private Piece(int value) => _value = value;
|
||||||
|
|
||||||
|
public int Value => _value & PieceMask;
|
||||||
|
public bool IsEmpty => (_value & PieceMask) == None;
|
||||||
|
|
||||||
|
public static Piece NewNone() => new(None);
|
||||||
|
public static Piece NewPlayer1() => new(Player1);
|
||||||
|
public static Piece NewPlayer2() => new(Player2);
|
||||||
|
|
||||||
|
public Piece AddNeighbour(Piece neighbour)
|
||||||
|
{
|
||||||
|
_value += neighbour.Value >> NeighbourValueOffset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece GetNext() => new(
|
||||||
|
_value switch
|
||||||
|
{
|
||||||
|
_ when _willBePlayer1.Contains(_value) => Player1,
|
||||||
|
_ when _willBePlayer2.Contains(_value) => Player2,
|
||||||
|
_ => None
|
||||||
|
});
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
(_value & PieceMask) switch
|
||||||
|
{
|
||||||
|
Player1 => "*",
|
||||||
|
Player2 => "#",
|
||||||
|
_ => " "
|
||||||
|
};
|
||||||
|
|
||||||
|
public static implicit operator Piece(int value) => new(value);
|
||||||
|
public static implicit operator int(Piece piece) => piece.Value;
|
||||||
|
}
|
||||||
5
56_Life_for_Two/csharp/Program.cs
Normal file
5
56_Life_for_Two/csharp/Program.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
global using Games.Common.IO;
|
||||||
|
global using static LifeforTwo.Resources.Resource;
|
||||||
|
global using LifeforTwo;
|
||||||
|
|
||||||
|
new Game(new ConsoleIO()).Play();
|
||||||
2
56_Life_for_Two/csharp/Resources/Draw.txt
Normal file
2
56_Life_for_Two/csharp/Resources/Draw.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
A draw
|
||||||
1
56_Life_for_Two/csharp/Resources/IllegalCoords.txt
Normal file
1
56_Life_for_Two/csharp/Resources/IllegalCoords.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Illegal coords. Retype
|
||||||
2
56_Life_for_Two/csharp/Resources/InitialPieces.txt
Normal file
2
56_Life_for_Two/csharp/Resources/InitialPieces.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
Player {0} - 3 live pieces
|
||||||
3
56_Life_for_Two/csharp/Resources/Player.txt
Normal file
3
56_Life_for_Two/csharp/Resources/Player.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Player {0}
|
||||||
37
56_Life_for_Two/csharp/Resources/Resource.cs
Normal file
37
56_Life_for_Two/csharp/Resources/Resource.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace LifeforTwo.Resources;
|
||||||
|
|
||||||
|
internal static class Resource
|
||||||
|
{
|
||||||
|
internal static class Streams
|
||||||
|
{
|
||||||
|
public static Stream Title => GetStream();
|
||||||
|
public static Stream IllegalCoords => GetStream();
|
||||||
|
public static Stream SameCoords => GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class Formats
|
||||||
|
{
|
||||||
|
public static string InitialPieces => GetString();
|
||||||
|
public static string Player => GetString();
|
||||||
|
public static string Winner => GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class Strings
|
||||||
|
{
|
||||||
|
public static string Draw => GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
56_Life_for_Two/csharp/Resources/SameCoords.txt
Normal file
1
56_Life_for_Two/csharp/Resources/SameCoords.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Same coord. Set to 0
|
||||||
6
56_Life_for_Two/csharp/Resources/Title.txt
Normal file
6
56_Life_for_Two/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Life2
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
U.B. Life Game
|
||||||
2
56_Life_for_Two/csharp/Resources/Winner.txt
Normal file
2
56_Life_for_Two/csharp/Resources/Winner.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
Player {0} is the winner
|
||||||
Reference in New Issue
Block a user