diff --git a/26_Chomp/csharp/Chomp.csproj b/26_Chomp/csharp/Chomp.csproj index d3fe4757..3870320c 100644 --- a/26_Chomp/csharp/Chomp.csproj +++ b/26_Chomp/csharp/Chomp.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/26_Chomp/csharp/Cookie.cs b/26_Chomp/csharp/Cookie.cs new file mode 100644 index 00000000..725eadfe --- /dev/null +++ b/26_Chomp/csharp/Cookie.cs @@ -0,0 +1,56 @@ +using System.Text; + +namespace Chomp; + +internal class Cookie +{ + private readonly int _rowCount; + private readonly int _columnCount; + private readonly char[][] _bits; + + public Cookie(int rowCount, int columnCount) + { + _rowCount = rowCount; + _columnCount = columnCount; + + // The calls to Math.Max here are to duplicate the original behaviour + // when negative values are given for the row or column count. + _bits = new char[Math.Max(_rowCount, 1)][]; + for (int row = 0; row < _bits.Length; row++) + { + _bits[row] = Enumerable.Repeat('*', Math.Max(_columnCount, 1)).ToArray(); + } + _bits[0][0] = 'P'; + } + + public bool TryChomp(int row, int column, out char chomped) + { + if (row < 1 || row > _rowCount || column < 1 || column > _columnCount || _bits[row - 1][column - 1] == ' ') + { + chomped = default; + return false; + } + + chomped = _bits[row - 1][column - 1]; + + for (int r = row; r <= _rowCount; r++) + { + for (int c = column; c <= _columnCount; c++) + { + _bits[r - 1][c - 1] = ' '; + } + } + + return true; + } + + public override string ToString() + { + var builder = new StringBuilder().AppendLine(" 1 2 3 4 5 6 7 8 9"); + for (int row = 1; row <= _bits.Length; row++) + { + builder.Append(' ').Append(row).Append(" ").AppendLine(string.Join(' ', _bits[row - 1])); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/26_Chomp/csharp/Game.cs b/26_Chomp/csharp/Game.cs new file mode 100644 index 00000000..77a8ccd1 --- /dev/null +++ b/26_Chomp/csharp/Game.cs @@ -0,0 +1,64 @@ +namespace Chomp; + +internal class Game +{ + private readonly IReadWrite _io; + + public Game(IReadWrite io) + { + _io = io; + } + + internal void Play() + { + _io.Write(Resource.Streams.Introduction); + if (_io.ReadNumber("Do you want the rules (1=Yes, 0=No!)") != 0) + { + _io.Write(Resource.Streams.Rules); + } + + while (true) + { + _io.Write(Resource.Streams.HereWeGo); + + var (playerCount, rowCount, columnCount) = _io.ReadParameters(); + + var loser = Play(new Cookie(rowCount, columnCount), new PlayerNumber(playerCount)); + + _io.WriteLine(string.Format(Resource.Formats.YouLose, loser)); + + if (_io.ReadNumber("Again (1=Yes, 0=No!)") != 1) { break; } + } + } + + private PlayerNumber Play(Cookie cookie, PlayerNumber player) + { + while (true) + { + _io.WriteLine(cookie); + + var poisoned = Chomp(cookie, player); + + if (poisoned) { return player; } + + player++; + } + } + + private bool Chomp(Cookie cookie, PlayerNumber player) + { + while (true) + { + _io.WriteLine(string.Format(Resource.Formats.Player, player)); + + var (row, column) = _io.Read2Numbers(Resource.Prompts.Coordinates); + + if (cookie.TryChomp((int)row, (int)column, out char chomped)) + { + return chomped == 'P'; + } + + _io.Write(Resource.Streams.NoFair); + } + } +} diff --git a/26_Chomp/csharp/IOExtensions.cs b/26_Chomp/csharp/IOExtensions.cs new file mode 100644 index 00000000..c94053a9 --- /dev/null +++ b/26_Chomp/csharp/IOExtensions.cs @@ -0,0 +1,24 @@ +namespace Chomp; + +internal static class IOExtensions +{ + public static (float, int, int) ReadParameters(this IReadWrite io) + => ( + (int)io.ReadNumber(Resource.Prompts.HowManyPlayers), + io.ReadNumberWithMax(Resource.Prompts.HowManyRows, 9, Resource.Strings.TooManyRows), + io.ReadNumberWithMax(Resource.Prompts.HowManyColumns, 9, Resource.Strings.TooManyColumns) + ); + + private static int ReadNumberWithMax(this IReadWrite io, string initialPrompt, int max, string reprompt) + { + var prompt = initialPrompt; + + while (true) + { + var response = io.ReadNumber(prompt); + if (response <= 9) { return (int)response; } + + prompt = $"{reprompt} {initialPrompt.ToLowerInvariant()}"; + } + } +} \ No newline at end of file diff --git a/26_Chomp/csharp/PlayerNumber.cs b/26_Chomp/csharp/PlayerNumber.cs new file mode 100644 index 00000000..16138a9b --- /dev/null +++ b/26_Chomp/csharp/PlayerNumber.cs @@ -0,0 +1,32 @@ +namespace Chomp; + +internal class PlayerNumber +{ + private readonly float _playerCount; + private int _counter; + private float _number; + + // The original code does not constrain playerCount to be an integer + public PlayerNumber(float playerCount) + { + _playerCount = playerCount; + _number = 0; + Increment(); + } + + public static PlayerNumber operator ++(PlayerNumber number) => number.Increment(); + + private PlayerNumber Increment() + { + if (_playerCount == 0) { throw new DivideByZeroException(); } + + // The increment logic here is the same as the original program, and exhibits + // interesting behaviour when _playerCount is not an integer. + _counter++; + _number = _counter - (float)Math.Floor(_counter / _playerCount) * _playerCount; + if (_number == 0) { _number = _playerCount; } + return this; + } + + public override string ToString() => (_number >= 0 ? " " : "") + _number.ToString(); +} \ No newline at end of file diff --git a/26_Chomp/csharp/Program.cs b/26_Chomp/csharp/Program.cs new file mode 100644 index 00000000..f541259c --- /dev/null +++ b/26_Chomp/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using Chomp.Resources; +using Chomp; + +new Game(new ConsoleIO()).Play(); \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Coordinates.txt b/26_Chomp/csharp/Resources/Coordinates.txt new file mode 100644 index 00000000..e78dd644 --- /dev/null +++ b/26_Chomp/csharp/Resources/Coordinates.txt @@ -0,0 +1 @@ +Coordinates of Chomp (row, column) \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HereWeGo.txt b/26_Chomp/csharp/Resources/HereWeGo.txt new file mode 100644 index 00000000..00975d31 --- /dev/null +++ b/26_Chomp/csharp/Resources/HereWeGo.txt @@ -0,0 +1,2 @@ +Here we go... + diff --git a/26_Chomp/csharp/Resources/HowManyColumns.txt b/26_Chomp/csharp/Resources/HowManyColumns.txt new file mode 100644 index 00000000..e323f959 --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyColumns.txt @@ -0,0 +1 @@ +How many columns \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HowManyPlayers.txt b/26_Chomp/csharp/Resources/HowManyPlayers.txt new file mode 100644 index 00000000..98ff0ad7 --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyPlayers.txt @@ -0,0 +1 @@ +How many players \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HowManyRows.txt b/26_Chomp/csharp/Resources/HowManyRows.txt new file mode 100644 index 00000000..1ad9464e --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyRows.txt @@ -0,0 +1 @@ +How many rows \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Introduction.txt b/26_Chomp/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..7c95e7a6 --- /dev/null +++ b/26_Chomp/csharp/Resources/Introduction.txt @@ -0,0 +1,6 @@ + Chomp + Creative Computing Morristown, New Jersey + + + +This is the game of Chomp (Scientific American, Jan 1973) diff --git a/26_Chomp/csharp/Resources/NoFair.txt b/26_Chomp/csharp/Resources/NoFair.txt new file mode 100644 index 00000000..9fe310eb --- /dev/null +++ b/26_Chomp/csharp/Resources/NoFair.txt @@ -0,0 +1 @@ +No fair. You're trying to chomp on empty space! diff --git a/26_Chomp/csharp/Resources/Player.txt b/26_Chomp/csharp/Resources/Player.txt new file mode 100644 index 00000000..f743df3a --- /dev/null +++ b/26_Chomp/csharp/Resources/Player.txt @@ -0,0 +1 @@ +Player{0} \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Resource.cs b/26_Chomp/csharp/Resources/Resource.cs new file mode 100644 index 00000000..d080b1fb --- /dev/null +++ b/26_Chomp/csharp/Resources/Resource.cs @@ -0,0 +1,48 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Chomp.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream HereWeGo => GetStream(); + public static Stream Introduction => GetStream(); + public static Stream Rules => GetStream(); + public static Stream NoFair => GetStream(); + } + + internal static class Formats + { + public static string Player => GetString(); + public static string YouLose => GetString(); + } + + internal static class Prompts + { + public static string Coordinates => GetString(); + public static string HowManyPlayers => GetString(); + public static string HowManyRows => GetString(); + public static string HowManyColumns => GetString(); + public static string TooManyColumns => GetString(); + } + + internal static class Strings + { + public static string TooManyColumns => GetString(); + public static string TooManyRows => 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/26_Chomp/csharp/Resources/Rules.txt b/26_Chomp/csharp/Resources/Rules.txt new file mode 100644 index 00000000..2fd9177f --- /dev/null +++ b/26_Chomp/csharp/Resources/Rules.txt @@ -0,0 +1,22 @@ +Chomp is for 1 or more players (humans only). + +Here's how a board looks (this one is 5 by 7): + + 1 2 3 4 5 6 7 8 9 + 1 P * * * * * * + 2 * * * * * * * + 3 * * * * * * * + 4 * * * * * * * + 5 * * * * * * * + + +The board is a big cookie - R rows high and C columns +wide. You input R and C at the start. In the upper left +corner of the cookie is a poison square (P). The one who +chomps the poison square loses. To take a chomp, type the +row and column of one of the squares on the cookie. +All of the squares below and to the right of that square +(including that square, too) disappear -- Chomp!! +No fair chomping on squares that have already been chomped, +or that are outside the original dimensions of the cookie. + diff --git a/26_Chomp/csharp/Resources/TooManyColumns.txt b/26_Chomp/csharp/Resources/TooManyColumns.txt new file mode 100644 index 00000000..1e8766a7 --- /dev/null +++ b/26_Chomp/csharp/Resources/TooManyColumns.txt @@ -0,0 +1 @@ +Too many rows (9 is maximum). Now, \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/TooManyRows.txt b/26_Chomp/csharp/Resources/TooManyRows.txt new file mode 100644 index 00000000..1e8766a7 --- /dev/null +++ b/26_Chomp/csharp/Resources/TooManyRows.txt @@ -0,0 +1 @@ +Too many rows (9 is maximum). Now, \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/YouLose.txt b/26_Chomp/csharp/Resources/YouLose.txt new file mode 100644 index 00000000..1015e271 --- /dev/null +++ b/26_Chomp/csharp/Resources/YouLose.txt @@ -0,0 +1 @@ +You lose, player{0}