diff --git a/56_Life_for_Two/csharp/Board.cs b/56_Life_for_Two/csharp/Board.cs new file mode 100644 index 00000000..f3cb0329 --- /dev/null +++ b/56_Life_for_Two/csharp/Board.cs @@ -0,0 +1,74 @@ +using System.Collections; +using System.Text; + +namespace LifeforTwo; + +internal class Board : IEnumerable +{ + private readonly Piece[,] _cells = new Piece[7, 7]; + private readonly Dictionary _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 GetEnumerator() + { + for (var x = 1; x <= 5; x++) + { + for (var y = 1; y <= 5; y++) + { + yield return new(x, y); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/56_Life_for_Two/csharp/Coordinates.cs b/56_Life_for_Two/csharp/Coordinates.cs new file mode 100644 index 00000000..09bd3dd4 --- /dev/null +++ b/56_Life_for_Two/csharp/Coordinates.cs @@ -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 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); + } +} diff --git a/56_Life_for_Two/csharp/Game.cs b/56_Life_for_Two/csharp/Game.cs new file mode 100644 index 00000000..de6cb5a5 --- /dev/null +++ b/56_Life_for_Two/csharp/Game.cs @@ -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"); + } +} diff --git a/56_Life_for_Two/csharp/Generation.cs b/56_Life_for_Two/csharp/Generation.cs new file mode 100644 index 00000000..b96ee478 --- /dev/null +++ b/56_Life_for_Two/csharp/Generation.cs @@ -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 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(); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/IOExtensions.cs b/56_Life_for_Two/csharp/IOExtensions.cs new file mode 100644 index 00000000..d7db9bf4 --- /dev/null +++ b/56_Life_for_Two/csharp/IOExtensions.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Life.cs b/56_Life_for_Two/csharp/Life.cs new file mode 100644 index 00000000..7848a348 --- /dev/null +++ b/56_Life_for_Two/csharp/Life.cs @@ -0,0 +1,31 @@ +using System.Collections; + +internal class Life : IEnumerable +{ + 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 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(); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/LifeforTwo.csproj b/56_Life_for_Two/csharp/LifeforTwo.csproj index d3fe4757..3870320c 100644 --- a/56_Life_for_Two/csharp/LifeforTwo.csproj +++ b/56_Life_for_Two/csharp/LifeforTwo.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/56_Life_for_Two/csharp/Piece.cs b/56_Life_for_Two/csharp/Piece.cs new file mode 100644 index 00000000..20e5adba --- /dev/null +++ b/56_Life_for_Two/csharp/Piece.cs @@ -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 _willBePlayer1 = + new[] { 0x0003, 0x0102, 0x0103, 0x0120, 0x0130, 0x0121, 0x0112, 0x0111, 0x0012 }.ToImmutableHashSet(); + private static readonly ImmutableHashSet _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; +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Program.cs b/56_Life_for_Two/csharp/Program.cs new file mode 100644 index 00000000..4c399811 --- /dev/null +++ b/56_Life_for_Two/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using static LifeforTwo.Resources.Resource; +global using LifeforTwo; + +new Game(new ConsoleIO()).Play(); diff --git a/56_Life_for_Two/csharp/Resources/Draw.txt b/56_Life_for_Two/csharp/Resources/Draw.txt new file mode 100644 index 00000000..9b9fd8fc --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Draw.txt @@ -0,0 +1,2 @@ + +A draw \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/IllegalCoords.txt b/56_Life_for_Two/csharp/Resources/IllegalCoords.txt new file mode 100644 index 00000000..ff01dfcb --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/IllegalCoords.txt @@ -0,0 +1 @@ +Illegal coords. Retype diff --git a/56_Life_for_Two/csharp/Resources/InitialPieces.txt b/56_Life_for_Two/csharp/Resources/InitialPieces.txt new file mode 100644 index 00000000..abbadf16 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/InitialPieces.txt @@ -0,0 +1,2 @@ + +Player {0} - 3 live pieces \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/Player.txt b/56_Life_for_Two/csharp/Resources/Player.txt new file mode 100644 index 00000000..f920e489 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Player.txt @@ -0,0 +1,3 @@ + + +Player {0} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/Resource.cs b/56_Life_for_Two/csharp/Resources/Resource.cs new file mode 100644 index 00000000..c6f59d3b --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Resource.cs @@ -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}'."); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/SameCoords.txt b/56_Life_for_Two/csharp/Resources/SameCoords.txt new file mode 100644 index 00000000..8af7b066 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/SameCoords.txt @@ -0,0 +1 @@ +Same coord. Set to 0 diff --git a/56_Life_for_Two/csharp/Resources/Title.txt b/56_Life_for_Two/csharp/Resources/Title.txt new file mode 100644 index 00000000..0e3404a7 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Title.txt @@ -0,0 +1,6 @@ + Life2 + Creative Computing Morristown, New Jersey + + + + U.B. Life Game diff --git a/56_Life_for_Two/csharp/Resources/Winner.txt b/56_Life_for_Two/csharp/Resources/Winner.txt new file mode 100644 index 00000000..e35c5440 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Winner.txt @@ -0,0 +1,2 @@ + +Player {0} is the winner \ No newline at end of file