Merge pull request #804 from drewjcooper/csharp-56-life-for-two

C# 56 life for two
This commit is contained in:
Jeff Atwood
2022-09-26 01:26:30 -07:00
committed by GitHub
17 changed files with 394 additions and 0 deletions

View 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();
}

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

View 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");
}
}

View 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();
}

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

View 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();
}

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

View File

@@ -0,0 +1,5 @@
global using Games.Common.IO;
global using static LifeforTwo.Resources.Resource;
global using LifeforTwo;
new Game(new ConsoleIO()).Play();

View File

@@ -0,0 +1,2 @@
A draw

View File

@@ -0,0 +1 @@
Illegal coords. Retype

View File

@@ -0,0 +1,2 @@
Player {0} - 3 live pieces

View File

@@ -0,0 +1,3 @@
Player {0}

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

View File

@@ -0,0 +1 @@
Same coord. Set to 0

View File

@@ -0,0 +1,6 @@
Life2
Creative Computing Morristown, New Jersey
U.B. Life Game

View File

@@ -0,0 +1,2 @@
Player {0} is the winner