mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-01 15:49:59 -08:00
21
16_Bug/csharp/Bug.cs
Normal file
21
16_Bug/csharp/Bug.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
86
16_Bug/csharp/Game.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
44
16_Bug/csharp/Parts/Body.cs
Normal file
44
16_Bug/csharp/Parts/Body.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
16_Bug/csharp/Parts/Feeler.cs
Normal file
6
16_Bug/csharp/Parts/Feeler.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace BugGame.Parts;
|
||||
|
||||
internal class Feeler : IPart
|
||||
{
|
||||
public string Name => nameof(Feeler);
|
||||
}
|
||||
14
16_Bug/csharp/Parts/Feelers.cs
Normal file
14
16_Bug/csharp/Parts/Feelers.cs
Normal 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);
|
||||
}
|
||||
38
16_Bug/csharp/Parts/Head.cs
Normal file
38
16_Bug/csharp/Parts/Head.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
6
16_Bug/csharp/Parts/IPart.cs
Normal file
6
16_Bug/csharp/Parts/IPart.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace BugGame.Parts;
|
||||
|
||||
internal interface IPart
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
6
16_Bug/csharp/Parts/Leg.cs
Normal file
6
16_Bug/csharp/Parts/Leg.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace BugGame.Parts;
|
||||
|
||||
internal class Leg : IPart
|
||||
{
|
||||
public string Name => nameof(Leg);
|
||||
}
|
||||
14
16_Bug/csharp/Parts/Legs.cs
Normal file
14
16_Bug/csharp/Parts/Legs.cs
Normal 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');
|
||||
}
|
||||
33
16_Bug/csharp/Parts/Neck.cs
Normal file
33
16_Bug/csharp/Parts/Neck.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
27
16_Bug/csharp/Parts/ParentPart.cs
Normal file
27
16_Bug/csharp/Parts/ParentPart.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
34
16_Bug/csharp/Parts/Part.cs
Normal file
34
16_Bug/csharp/Parts/Part.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
50
16_Bug/csharp/Parts/PartCollection.cs
Normal file
50
16_Bug/csharp/Parts/PartCollection.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
16_Bug/csharp/Parts/Tail.cs
Normal file
20
16_Bug/csharp/Parts/Tail.cs
Normal 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
5
16_Bug/csharp/Program.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using BugGame;
|
||||
using Games.Common.IO;
|
||||
using Games.Common.Randomness;
|
||||
|
||||
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();
|
||||
18
16_Bug/csharp/Resources/Instructions.txt
Normal file
18
16_Bug/csharp/Resources/Instructions.txt
Normal 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
|
||||
|
||||
|
||||
8
16_Bug/csharp/Resources/Introduction.txt
Normal file
8
16_Bug/csharp/Resources/Introduction.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Bug
|
||||
Creative Computing Morristown, New Jersey
|
||||
|
||||
|
||||
|
||||
The Game Bug
|
||||
I hope you enjoy this game.
|
||||
|
||||
46
16_Bug/csharp/Resources/Message.cs
Normal file
46
16_Bug/csharp/Resources/Message.cs
Normal 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));
|
||||
}
|
||||
1
16_Bug/csharp/Resources/PlayAgain.txt
Normal file
1
16_Bug/csharp/Resources/PlayAgain.txt
Normal file
@@ -0,0 +1 @@
|
||||
I hope you enjoyed the game, play it again soon!!
|
||||
19
16_Bug/csharp/Resources/Resource.cs
Normal file
19
16_Bug/csharp/Resources/Resource.cs
Normal 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}'.");
|
||||
}
|
||||
Reference in New Issue
Block a user