Merge pull request #773 from drewjcooper/csharp-16-bug

C# 16 bug
This commit is contained in:
Jeff Atwood
2022-07-20 13:16:20 -07:00
committed by GitHub
21 changed files with 505 additions and 0 deletions

21
16_Bug/csharp/Bug.cs Normal file
View File

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

View File

@@ -6,4 +6,13 @@
<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>

86
16_Bug/csharp/Game.cs Normal file
View File

@@ -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<Message, string> 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;
}
}

View File

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

View File

@@ -0,0 +1,6 @@
namespace BugGame.Parts;
internal class Feeler : IPart
{
public string Name => nameof(Feeler);
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
namespace BugGame.Parts;
internal interface IPart
{
string Name { get; }
}

View File

@@ -0,0 +1,6 @@
namespace BugGame.Parts;
internal class Leg : IPart
{
public string Name => nameof(Leg);
}

View File

@@ -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');
}

View File

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

View File

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

View File

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

View File

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

View File

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

5
16_Bug/csharp/Program.cs Normal file
View File

@@ -0,0 +1,5 @@
using BugGame;
using Games.Common.IO;
using Games.Common.Randomness;
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();

View File

@@ -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

View File

@@ -0,0 +1,8 @@
Bug
Creative Computing Morristown, New Jersey
The Game Bug
I hope you enjoy this game.

View File

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

View File

@@ -0,0 +1 @@
I hope you enjoyed the game, play it again soon!!

View File

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