diff --git a/16_Bug/csharp/Bug.cs b/16_Bug/csharp/Bug.cs new file mode 100644 index 00000000..42e686d4 --- /dev/null +++ b/16_Bug/csharp/Bug.cs @@ -0,0 +1,21 @@ +using System.Text; +using BugGame.Parts; +using BugGame.Resources; + +namespace BugGame; + +internal class Bug +{ + private readonly Body _body = new(); + + public bool IsComplete => _body.IsComplete; + + public bool TryAdd(IPart part, out Message message) => _body.TryAdd(part, out message); + + public string ToString(string pronoun, char feelerCharacter) + { + var builder = new StringBuilder($"*****{pronoun} Bug*****").AppendLine().AppendLine().AppendLine(); + _body.AppendTo(builder, feelerCharacter); + return builder.ToString(); + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Bug.csproj b/16_Bug/csharp/Bug.csproj index d3fe4757..91e759c0 100644 --- a/16_Bug/csharp/Bug.csproj +++ b/16_Bug/csharp/Bug.csproj @@ -6,4 +6,13 @@ enable enable + + + + + + + + + diff --git a/16_Bug/csharp/Game.cs b/16_Bug/csharp/Game.cs new file mode 100644 index 00000000..a8989a4c --- /dev/null +++ b/16_Bug/csharp/Game.cs @@ -0,0 +1,86 @@ +using BugGame.Parts; +using BugGame.Resources; +using Games.Common.IO; +using Games.Common.Randomness; +using static System.StringComparison; +namespace BugGame; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + _io.Write(Resource.Streams.Introduction); + if (!_io.ReadString("Do you want instructions").Equals("no", InvariantCultureIgnoreCase)) + { + _io.Write(Resource.Streams.Instructions); + } + + BuildBugs(); + + _io.Write(Resource.Streams.PlayAgain); + } + + private void BuildBugs() + { + var yourBug = new Bug(); + var myBug = new Bug(); + + while (true) + { + var partAdded = TryBuild(yourBug, m => m.You); + Thread.Sleep(500); + _io.WriteLine(); + partAdded |= TryBuild(myBug, m => m.I); + + if (partAdded) + { + if (yourBug.IsComplete) { _io.WriteLine("Your bug is finished."); } + if (myBug.IsComplete) { _io.WriteLine("My bug is finished."); } + + if (!_io.ReadString("Do you want the picture").Equals("no", InvariantCultureIgnoreCase)) + { + _io.Write(yourBug.ToString("Your", 'A')); + _io.WriteLine(); + _io.WriteLine(); + _io.WriteLine(); + _io.WriteLine(); + _io.Write(myBug.ToString("My", 'F')); + } + } + + if (yourBug.IsComplete || myBug.IsComplete) { break; } + } + } + + private bool TryBuild(Bug bug, Func messageTransform) + { + var roll = _random.Next(6) + 1; + _io.WriteLine(messageTransform(Message.Rolled.ForValue(roll))); + + IPart part = roll switch + { + 1 => new Body(), + 2 => new Neck(), + 3 => new Head(), + 4 => new Feeler(), + 5 => new Tail(), + 6 => new Leg(), + _ => throw new Exception("Unexpected roll value") + }; + _io.WriteLine($"{roll}={part.GetType().Name}"); + + var partAdded = bug.TryAdd(part, out var message); + _io.WriteLine(messageTransform.Invoke(message)); + + return partAdded; + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Parts/Body.cs b/16_Bug/csharp/Parts/Body.cs new file mode 100644 index 00000000..58f80405 --- /dev/null +++ b/16_Bug/csharp/Parts/Body.cs @@ -0,0 +1,44 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Body : ParentPart +{ + private readonly Neck _neck = new(); + private readonly Tail _tail = new(); + private readonly Legs _legs = new(); + + public Body() + : base(Message.BodyAdded, Message.BodyNotNeeded) + { + } + + public override bool IsComplete => _neck.IsComplete && _tail.IsComplete && _legs.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Neck => _neck.TryAdd(out message), + Head or Feeler => _neck.TryAdd(part, out message), + Tail => _tail.TryAdd(out message), + Leg => _legs.TryAddOne(out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _neck.AppendTo(builder, feelerCharacter); + builder + .AppendLine(" BBBBBBBBBBBB") + .AppendLine(" B B") + .AppendLine(" B B"); + _tail.AppendTo(builder); + builder + .AppendLine(" BBBBBBBBBBBB"); + _legs.AppendTo(builder); + } + } +} diff --git a/16_Bug/csharp/Parts/Feeler.cs b/16_Bug/csharp/Parts/Feeler.cs new file mode 100644 index 00000000..9508d541 --- /dev/null +++ b/16_Bug/csharp/Parts/Feeler.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal class Feeler : IPart +{ + public string Name => nameof(Feeler); +} diff --git a/16_Bug/csharp/Parts/Feelers.cs b/16_Bug/csharp/Parts/Feelers.cs new file mode 100644 index 00000000..165a073d --- /dev/null +++ b/16_Bug/csharp/Parts/Feelers.cs @@ -0,0 +1,14 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Feelers : PartCollection +{ + public Feelers() + : base(2, Message.FeelerAdded, Message.FeelersFull) + { + } + + public void AppendTo(StringBuilder builder, char character) => AppendTo(builder, 10, 4, character); +} diff --git a/16_Bug/csharp/Parts/Head.cs b/16_Bug/csharp/Parts/Head.cs new file mode 100644 index 00000000..d7135575 --- /dev/null +++ b/16_Bug/csharp/Parts/Head.cs @@ -0,0 +1,38 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Head : ParentPart +{ + private Feelers _feelers = new(); + + public Head() + : base(Message.HeadAdded, Message.HeadNotNeeded) + { + } + + public override bool IsComplete => _feelers.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Feeler => _feelers.TryAddOne(out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _feelers.AppendTo(builder, feelerCharacter); + builder + .AppendLine(" HHHHHHH") + .AppendLine(" H H") + .AppendLine(" H O O H") + .AppendLine(" H H") + .AppendLine(" H V H") + .AppendLine(" HHHHHHH"); + } + } +} diff --git a/16_Bug/csharp/Parts/IPart.cs b/16_Bug/csharp/Parts/IPart.cs new file mode 100644 index 00000000..e325a7c1 --- /dev/null +++ b/16_Bug/csharp/Parts/IPart.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal interface IPart +{ + string Name { get; } +} diff --git a/16_Bug/csharp/Parts/Leg.cs b/16_Bug/csharp/Parts/Leg.cs new file mode 100644 index 00000000..c2d4aaaf --- /dev/null +++ b/16_Bug/csharp/Parts/Leg.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal class Leg : IPart +{ + public string Name => nameof(Leg); +} diff --git a/16_Bug/csharp/Parts/Legs.cs b/16_Bug/csharp/Parts/Legs.cs new file mode 100644 index 00000000..0ed8d8fc --- /dev/null +++ b/16_Bug/csharp/Parts/Legs.cs @@ -0,0 +1,14 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Legs : PartCollection +{ + public Legs() + : base(6, Message.LegAdded, Message.LegsFull) + { + } + + public void AppendTo(StringBuilder builder) => AppendTo(builder, 6, 2, 'L'); +} diff --git a/16_Bug/csharp/Parts/Neck.cs b/16_Bug/csharp/Parts/Neck.cs new file mode 100644 index 00000000..23dacfb7 --- /dev/null +++ b/16_Bug/csharp/Parts/Neck.cs @@ -0,0 +1,33 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Neck : ParentPart +{ + private Head _head = new(); + + public Neck() + : base(Message.NeckAdded, Message.NeckNotNeeded) + { + } + + public override bool IsComplete => _head.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Head => _head.TryAdd(out message), + Feeler => _head.TryAdd(part, out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _head.AppendTo(builder, feelerCharacter); + builder.AppendLine(" N N").AppendLine(" N N"); + } + } +} diff --git a/16_Bug/csharp/Parts/ParentPart.cs b/16_Bug/csharp/Parts/ParentPart.cs new file mode 100644 index 00000000..1ca6682f --- /dev/null +++ b/16_Bug/csharp/Parts/ParentPart.cs @@ -0,0 +1,27 @@ +using BugGame.Resources; + +namespace BugGame.Parts; + +internal abstract class ParentPart : Part +{ + public ParentPart(Message addedMessage, Message duplicateMessage) + : base(addedMessage, duplicateMessage) + { + } + + public bool TryAdd(IPart part, out Message message) + => (part.GetType() == GetType(), IsPresent) switch + { + (true, _) => TryAdd(out message), + (false, false) => ReportDoNotHave(out message), + _ => TryAddCore(part, out message) + }; + + protected abstract bool TryAddCore(IPart part, out Message message); + + private bool ReportDoNotHave(out Message message) + { + message = Message.DoNotHaveA(this); + return false; + } +} diff --git a/16_Bug/csharp/Parts/Part.cs b/16_Bug/csharp/Parts/Part.cs new file mode 100644 index 00000000..f29fbd81 --- /dev/null +++ b/16_Bug/csharp/Parts/Part.cs @@ -0,0 +1,34 @@ +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Part : IPart +{ + private readonly Message _addedMessage; + private readonly Message _duplicateMessage; + + public Part(Message addedMessage, Message duplicateMessage) + { + _addedMessage = addedMessage; + _duplicateMessage = duplicateMessage; + } + + public virtual bool IsComplete => IsPresent; + + protected bool IsPresent { get; private set; } + + public string Name => GetType().Name; + + public bool TryAdd(out Message message) + { + if (IsPresent) + { + message = _duplicateMessage; + return false; + } + + message = _addedMessage; + IsPresent = true; + return true; + } +} diff --git a/16_Bug/csharp/Parts/PartCollection.cs b/16_Bug/csharp/Parts/PartCollection.cs new file mode 100644 index 00000000..9a0fd2ee --- /dev/null +++ b/16_Bug/csharp/Parts/PartCollection.cs @@ -0,0 +1,50 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class PartCollection +{ + private readonly int _maxCount; + private readonly Message _addedMessage; + private readonly Message _fullMessage; + private int _count; + + public PartCollection(int maxCount, Message addedMessage, Message fullMessage) + { + _maxCount = maxCount; + _addedMessage = addedMessage; + _fullMessage = fullMessage; + } + + public bool IsComplete => _count == _maxCount; + + public bool TryAddOne(out Message message) + { + if (_count < _maxCount) + { + _count++; + message = _addedMessage.ForValue(_count); + return true; + } + + message = _fullMessage; + return false; + } + + protected void AppendTo(StringBuilder builder, int offset, int length, char character) + { + if (_count == 0) { return; } + + for (var i = 0; i < length; i++) + { + builder.Append(' ', offset); + + for (var j = 0; j < _count; j++) + { + builder.Append(character).Append(' '); + } + builder.AppendLine(); + } + } +} diff --git a/16_Bug/csharp/Parts/Tail.cs b/16_Bug/csharp/Parts/Tail.cs new file mode 100644 index 00000000..ebf4b28f --- /dev/null +++ b/16_Bug/csharp/Parts/Tail.cs @@ -0,0 +1,20 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Tail : Part +{ + public Tail() + : base(Message.TailAdded, Message.TailNotNeeded) + { + } + + public void AppendTo(StringBuilder builder) + { + if (IsPresent) + { + builder.AppendLine("TTTTTB B"); + } + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Program.cs b/16_Bug/csharp/Program.cs new file mode 100644 index 00000000..883e62d1 --- /dev/null +++ b/16_Bug/csharp/Program.cs @@ -0,0 +1,5 @@ +using BugGame; +using Games.Common.IO; +using Games.Common.Randomness; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/16_Bug/csharp/Resources/Instructions.txt b/16_Bug/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..bdbdadd3 --- /dev/null +++ b/16_Bug/csharp/Resources/Instructions.txt @@ -0,0 +1,18 @@ +The object of Bug is to finish your bug before I finish +mine. Each number stands for a part of the bug body. +I will roll the die for you, tell you what I rolled for you +what the number stands for, and if you can get the part. +If you can get the part I will give it to you. +The same will happen on my turn. +If there is a change in either bug I will give you the +option of seeing the pictures of the bugs. +The numbers stand for parts as follows: +Number Part Number of part needed + 1 Body 1 + 2 Neck 1 + 3 Head 1 + 4 Feelers 2 + 5 Tail 1 + 6 Legs 6 + + diff --git a/16_Bug/csharp/Resources/Introduction.txt b/16_Bug/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..e74a833e --- /dev/null +++ b/16_Bug/csharp/Resources/Introduction.txt @@ -0,0 +1,8 @@ + Bug + Creative Computing Morristown, New Jersey + + + +The Game Bug +I hope you enjoy this game. + diff --git a/16_Bug/csharp/Resources/Message.cs b/16_Bug/csharp/Resources/Message.cs new file mode 100644 index 00000000..20a59d9a --- /dev/null +++ b/16_Bug/csharp/Resources/Message.cs @@ -0,0 +1,46 @@ +using BugGame.Parts; + +namespace BugGame.Resources; + +internal class Message +{ + public static Message Rolled = new("rolled a {0}"); + + public static Message BodyAdded = new("now have a body."); + public static Message BodyNotNeeded = new("do not need a body."); + + public static Message NeckAdded = new("now have a neck."); + public static Message NeckNotNeeded = new("do not need a neck."); + + public static Message HeadAdded = new("needed a head."); + public static Message HeadNotNeeded = new("I do not need a head.", "You have a head."); + + public static Message TailAdded = new("I now have a tail.", "I now give you a tail."); + public static Message TailNotNeeded = new("I do not need a tail.", "You already have a tail."); + + public static Message FeelerAdded = new("I get a feeler.", "I now give you a feeler"); + public static Message FeelersFull = new("I have 2 feelers already.", "You have two feelers already"); + + public static Message LegAdded = new("now have {0} legs"); + public static Message LegsFull = new("I have 6 feet.", "You have 6 feet already"); + + public static Message Complete = new("bug is finished."); + + private Message(string common) + : this("I " + common, "You " + common) + { + } + + private Message(string i, string you) + { + I = i; + You = you; + } + + public string I { get; } + public string You { get; } + + public static Message DoNotHaveA(Part part) => new($"do not have a {part.Name}"); + + public Message ForValue(int quantity) => new(string.Format(I, quantity), string.Format(You, quantity)); +} \ No newline at end of file diff --git a/16_Bug/csharp/Resources/PlayAgain.txt b/16_Bug/csharp/Resources/PlayAgain.txt new file mode 100644 index 00000000..2380e130 --- /dev/null +++ b/16_Bug/csharp/Resources/PlayAgain.txt @@ -0,0 +1 @@ +I hope you enjoyed the game, play it again soon!! diff --git a/16_Bug/csharp/Resources/Resource.cs b/16_Bug/csharp/Resources/Resource.cs new file mode 100644 index 00000000..2b34a4e6 --- /dev/null +++ b/16_Bug/csharp/Resources/Resource.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace BugGame.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Introduction => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream PlayAgain => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"Bug.Resources.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file