From 6b194366fb38ce04927e75e09fc2555264aeb58d Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 30 Oct 2021 22:32:22 +1100 Subject: [PATCH 1/6] Add project sturcture --- 90 Tower/csharp/Tower.sln | 34 ++++++++++++++++++++++++++++++ 90 Tower/csharp/Tower/Program.cs | 12 +++++++++++ 90 Tower/csharp/Tower/Tower.csproj | 8 +++++++ 3 files changed, 54 insertions(+) create mode 100644 90 Tower/csharp/Tower.sln create mode 100644 90 Tower/csharp/Tower/Program.cs create mode 100644 90 Tower/csharp/Tower/Tower.csproj diff --git a/90 Tower/csharp/Tower.sln b/90 Tower/csharp/Tower.sln new file mode 100644 index 00000000..2c6e524d --- /dev/null +++ b/90 Tower/csharp/Tower.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tower", "tower\Tower.csproj", "{2E14FCD5-A52C-4292-A7F4-0C7E5780C962}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|x64.Build.0 = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Debug|x86.Build.0 = Debug|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|Any CPU.Build.0 = Release|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|x64.ActiveCfg = Release|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|x64.Build.0 = Release|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|x86.ActiveCfg = Release|Any CPU + {2E14FCD5-A52C-4292-A7F4-0C7E5780C962}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/90 Tower/csharp/Tower/Program.cs b/90 Tower/csharp/Tower/Program.cs new file mode 100644 index 00000000..4793b817 --- /dev/null +++ b/90 Tower/csharp/Tower/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace Tower +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/90 Tower/csharp/Tower/Tower.csproj b/90 Tower/csharp/Tower/Tower.csproj new file mode 100644 index 00000000..20827042 --- /dev/null +++ b/90 Tower/csharp/Tower/Tower.csproj @@ -0,0 +1,8 @@ + + + + Exe + net5.0 + + + From ee8322dcafd851837eaf93ed06d72d4ebfe1f586 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 30 Oct 2021 22:33:49 +1100 Subject: [PATCH 2/6] Add string resources --- .../Tower/Resources/Congratulations.txt | 2 + .../Tower/Resources/DiskCountPrompt.txt | 1 + .../csharp/Tower/Resources/DiskCountQuit.txt | 2 + .../csharp/Tower/Resources/DiskCountRetry.txt | 1 + .../csharp/Tower/Resources/DiskPrompt.txt | 1 + 90 Tower/csharp/Tower/Resources/DiskQuit.txt | 1 + 90 Tower/csharp/Tower/Resources/DiskRetry.txt | 1 + .../Tower/Resources/DiskUnavailable.txt | 1 + .../csharp/Tower/Resources/IllegalMove.txt | 3 ++ .../csharp/Tower/Resources/Instructions.txt | 10 +++++ 90 Tower/csharp/Tower/Resources/Intro.txt | 11 ++++++ .../csharp/Tower/Resources/NeedlePrompt.txt | 1 + .../csharp/Tower/Resources/NeedleQuit.txt | 2 + .../csharp/Tower/Resources/NeedleRetry.txt | 2 + .../Tower/Resources/PlayAgainPrompt.txt | 2 + 90 Tower/csharp/Tower/Resources/Strings.cs | 38 +++++++++++++++++++ 90 Tower/csharp/Tower/Resources/Thanks.txt | 2 + 90 Tower/csharp/Tower/Resources/Title.txt | 11 ++++++ .../csharp/Tower/Resources/TooManyMoves.txt | 2 + .../csharp/Tower/Resources/YesNoPrompt.txt | 2 + 90 Tower/csharp/Tower/Tower.csproj | 4 ++ 21 files changed, 100 insertions(+) create mode 100644 90 Tower/csharp/Tower/Resources/Congratulations.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskCountPrompt.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskCountQuit.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskCountRetry.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskPrompt.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskQuit.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskRetry.txt create mode 100644 90 Tower/csharp/Tower/Resources/DiskUnavailable.txt create mode 100644 90 Tower/csharp/Tower/Resources/IllegalMove.txt create mode 100644 90 Tower/csharp/Tower/Resources/Instructions.txt create mode 100644 90 Tower/csharp/Tower/Resources/Intro.txt create mode 100644 90 Tower/csharp/Tower/Resources/NeedlePrompt.txt create mode 100644 90 Tower/csharp/Tower/Resources/NeedleQuit.txt create mode 100644 90 Tower/csharp/Tower/Resources/NeedleRetry.txt create mode 100644 90 Tower/csharp/Tower/Resources/PlayAgainPrompt.txt create mode 100644 90 Tower/csharp/Tower/Resources/Strings.cs create mode 100644 90 Tower/csharp/Tower/Resources/Thanks.txt create mode 100644 90 Tower/csharp/Tower/Resources/Title.txt create mode 100644 90 Tower/csharp/Tower/Resources/TooManyMoves.txt create mode 100644 90 Tower/csharp/Tower/Resources/YesNoPrompt.txt diff --git a/90 Tower/csharp/Tower/Resources/Congratulations.txt b/90 Tower/csharp/Tower/Resources/Congratulations.txt new file mode 100644 index 00000000..332297b2 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Congratulations.txt @@ -0,0 +1,2 @@ + +Congratulations diff --git a/90 Tower/csharp/Tower/Resources/DiskCountPrompt.txt b/90 Tower/csharp/Tower/Resources/DiskCountPrompt.txt new file mode 100644 index 00000000..a45c3461 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskCountPrompt.txt @@ -0,0 +1 @@ +How many disks do you want to move (7 is max) \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskCountQuit.txt b/90 Tower/csharp/Tower/Resources/DiskCountQuit.txt new file mode 100644 index 00000000..b573854d --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskCountQuit.txt @@ -0,0 +1,2 @@ +All right, wise guy, if you can't play the game right, I'll +just take my puzzle and go home. So long. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskCountRetry.txt b/90 Tower/csharp/Tower/Resources/DiskCountRetry.txt new file mode 100644 index 00000000..c8ec4b10 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskCountRetry.txt @@ -0,0 +1 @@ +Sorry, but i can't do that job for you. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskPrompt.txt b/90 Tower/csharp/Tower/Resources/DiskPrompt.txt new file mode 100644 index 00000000..a7f127db --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskPrompt.txt @@ -0,0 +1 @@ +Which disk would you like to move \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskQuit.txt b/90 Tower/csharp/Tower/Resources/DiskQuit.txt new file mode 100644 index 00000000..56d19d5f --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskQuit.txt @@ -0,0 +1 @@ +Stop wasting my time. Go bother someone else. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskRetry.txt b/90 Tower/csharp/Tower/Resources/DiskRetry.txt new file mode 100644 index 00000000..1efe29b4 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskRetry.txt @@ -0,0 +1 @@ +Illegal entry... You may only type 3, 5, 7, 9, 11, 13, or 15. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/DiskUnavailable.txt b/90 Tower/csharp/Tower/Resources/DiskUnavailable.txt new file mode 100644 index 00000000..721ddccf --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskUnavailable.txt @@ -0,0 +1 @@ +That disk is below another one. Make another choice. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/IllegalMove.txt b/90 Tower/csharp/Tower/Resources/IllegalMove.txt new file mode 100644 index 00000000..243c8d8f --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/IllegalMove.txt @@ -0,0 +1,3 @@ +You can't place a larger disk on top of a smaller one, +it might crush it! +Now then, \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/Instructions.txt b/90 Tower/csharp/Tower/Resources/Instructions.txt new file mode 100644 index 00000000..93c71b8d --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Instructions.txt @@ -0,0 +1,10 @@ +In this program, we shall refer to disks by numerical code. +3 will represent the smallest disk, 5 the next size, +7 the next, and so on, up to 15. If you do the puzzle with +2 disks, their code names would be 13 and 15. With 3 disks +the code names would be 11, 13 and 15, etc. The needles +are numbered from left to right, 1 to 3. We will +startup with the disks on needle 1, and attempt to move them +to needle 3. + +Good luck! diff --git a/90 Tower/csharp/Tower/Resources/Intro.txt b/90 Tower/csharp/Tower/Resources/Intro.txt new file mode 100644 index 00000000..a88049a9 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Intro.txt @@ -0,0 +1,11 @@ + Towers + Creative Computing Morristown, New Jersey + + + +Towers of Hanoi puzzle. + +You must transfer the disks from the left to the right +tower, one at a time, never putting a larger dish on a +smaller disk. + diff --git a/90 Tower/csharp/Tower/Resources/NeedlePrompt.txt b/90 Tower/csharp/Tower/Resources/NeedlePrompt.txt new file mode 100644 index 00000000..59dcebe7 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/NeedlePrompt.txt @@ -0,0 +1 @@ +Place disk on which needle \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/NeedleQuit.txt b/90 Tower/csharp/Tower/Resources/NeedleQuit.txt new file mode 100644 index 00000000..e75e3d87 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/NeedleQuit.txt @@ -0,0 +1,2 @@ +I tried to warn you, but you wouldn't listen, +Bye bye, big shot. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/NeedleRetry.txt b/90 Tower/csharp/Tower/Resources/NeedleRetry.txt new file mode 100644 index 00000000..19701eec --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/NeedleRetry.txt @@ -0,0 +1,2 @@ +I'll assume you hit the wrong key this time. But watch it, +I only allow one mistake \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/PlayAgainPrompt.txt b/90 Tower/csharp/Tower/Resources/PlayAgainPrompt.txt new file mode 100644 index 00000000..3972e7bd --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/PlayAgainPrompt.txt @@ -0,0 +1,2 @@ + +Try again (Yes or No) \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/Strings.cs b/90 Tower/csharp/Tower/Resources/Strings.cs new file mode 100644 index 00000000..62b84d0a --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Strings.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Tower.Resources +{ + internal static class Strings + { + internal static string Congratulations => GetResource(); + internal static string DiskCountPrompt => GetResource(); + internal static string DiskCountQuit => GetResource(); + internal static string DiskCountRetry => GetResource(); + internal static string DiskPrompt => GetResource(); + internal static string DiskQuit => GetResource(); + internal static string DiskRetry => GetResource(); + internal static string DiskUnavailable => GetResource(); + internal static string IllegalMove => GetResource(); + internal static string Instructions => GetResource(); + internal static string Intro => GetResource(); + internal static string NeedlePrompt => GetResource(); + internal static string NeedleQuit => GetResource(); + internal static string NeedleRetry => GetResource(); + internal static string PlayAgainPrompt => GetResource(); + internal static string Thanks => GetResource(); + internal static string Title => GetResource(); + internal static string TooManyMoves => GetResource(); + internal static string YesNoPrompt => GetResource(); + + private static string GetResource([CallerMemberName] string name = "") + { + var streamName = $"Tower.Resources.{name}.txt"; + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(streamName); + using var reader = new StreamReader(stream); + + return reader.ReadToEnd(); + } + } +} diff --git a/90 Tower/csharp/Tower/Resources/Thanks.txt b/90 Tower/csharp/Tower/Resources/Thanks.txt new file mode 100644 index 00000000..435d89c7 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Thanks.txt @@ -0,0 +1,2 @@ + +Thanks for the game! diff --git a/90 Tower/csharp/Tower/Resources/Title.txt b/90 Tower/csharp/Tower/Resources/Title.txt new file mode 100644 index 00000000..a88049a9 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/Title.txt @@ -0,0 +1,11 @@ + Towers + Creative Computing Morristown, New Jersey + + + +Towers of Hanoi puzzle. + +You must transfer the disks from the left to the right +tower, one at a time, never putting a larger dish on a +smaller disk. + diff --git a/90 Tower/csharp/Tower/Resources/TooManyMoves.txt b/90 Tower/csharp/Tower/Resources/TooManyMoves.txt new file mode 100644 index 00000000..c517218e --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/TooManyMoves.txt @@ -0,0 +1,2 @@ +Sorry, but i have orders to stop is you make more than +128 moves. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/YesNoPrompt.txt b/90 Tower/csharp/Tower/Resources/YesNoPrompt.txt new file mode 100644 index 00000000..62612ce2 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/YesNoPrompt.txt @@ -0,0 +1,2 @@ + +'Yes' or 'No' please \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Tower.csproj b/90 Tower/csharp/Tower/Tower.csproj index 20827042..c0de0594 100644 --- a/90 Tower/csharp/Tower/Tower.csproj +++ b/90 Tower/csharp/Tower/Tower.csproj @@ -5,4 +5,8 @@ net5.0 + + + + From db11f064e9e8ca3e62f5c25c689b0b1223ed5eda Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 30 Oct 2021 23:07:50 +1100 Subject: [PATCH 3/6] Add imput routines --- 90 Tower/csharp/Tower/UI/Input.cs | 83 ++++++++++++++++++++++++++++++ 90 Tower/csharp/Tower/UI/Prompt.cs | 41 +++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 90 Tower/csharp/Tower/UI/Input.cs create mode 100644 90 Tower/csharp/Tower/UI/Prompt.cs diff --git a/90 Tower/csharp/Tower/UI/Input.cs b/90 Tower/csharp/Tower/UI/Input.cs new file mode 100644 index 00000000..9b2d3747 --- /dev/null +++ b/90 Tower/csharp/Tower/UI/Input.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +namespace Tower.UI +{ + // Provides input methods which emulate the BASIC interpreter's keyboard input routines + internal static class Input + { + private static void Prompt(string text = "") => Console.Write($"{text}? "); + + internal static bool ReadYesNo(string prompt, string retryPrompt) + { + var response = ReadString(prompt); + + while (true) + { + if (response.Equals("No", StringComparison.InvariantCultureIgnoreCase)) { return false; } + if (response.Equals("Yes", StringComparison.InvariantCultureIgnoreCase)) { return true; } + response = ReadString(retryPrompt); + } + } + + internal static bool TryReadNumber(Prompt prompt, out int number) + { + var message = prompt.Message; + + for (int retryCount = 0; retryCount <= prompt.RetriesAllowed; retryCount++) + { + if (retryCount > 0) { Console.WriteLine(prompt.RetryMessage); } + + if (prompt.TryValidateResponse(ReadNumber(message), out number)) { return true; } + + if (!prompt.RepeatPrompt) { message = ""; } + } + + Console.WriteLine(prompt.QuitMessage); + + number = 0; + return false; + } + + private static float ReadNumber(string prompt) + { + Prompt(prompt); + + while (true) + { + var inputValues = ReadStrings(); + + if (TryParseNumber(inputValues[0], out var number)) + { + if (inputValues.Length > 1) + { + Console.WriteLine("!Extra input ingored"); + } + + return number; + } + } + } + + private static string ReadString(string prompt) + { + var inputValues = ReadStrings(); + if (inputValues.Length > 1) + { + Console.WriteLine("!Extra input ingored"); + } + return inputValues[0]; + } + + private static string[] ReadStrings() => Console.ReadLine().Split(',', StringSplitOptions.TrimEntries); + + private static bool TryParseNumber(string text, out float number) + { + if (float.TryParse(text, out number)) { return true; } + + Console.WriteLine("!Number expected - retry input line"); + number = default; + return false; + } + } +} diff --git a/90 Tower/csharp/Tower/UI/Prompt.cs b/90 Tower/csharp/Tower/UI/Prompt.cs new file mode 100644 index 00000000..bc83f7a6 --- /dev/null +++ b/90 Tower/csharp/Tower/UI/Prompt.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using static Tower.Resources.Strings; + +namespace Tower.UI +{ + internal class Prompt + { + public static Prompt DiskCount = + new(DiskCountPrompt, DiskCountRetry, DiskCountQuit, 1, 2, 3, 4, 5, 6, 7) { RetriesAllowed = 2 }; + + public static Prompt Disk = + new(DiskPrompt, DiskRetry, DiskQuit, 3, 5, 7, 9, 11, 13, 15) { RepeatPrompt = false }; + + public static Prompt Needle = new(NeedlePrompt, NeedleRetry, NeedleQuit, 1, 2, 3); + + private readonly HashSet _validValues; + + private Prompt(string prompt, string retryMessage, string quitMessage, params int[] validValues) + { + Message = prompt; + RetryMessage = retryMessage; + QuitMessage = quitMessage; + _validValues = validValues.ToHashSet(); + RetriesAllowed = 1; + RepeatPrompt = true; + } + + public string Message { get; } + public string RetryMessage { get; } + public string QuitMessage { get; } + public int RetriesAllowed { get; private set; } + public bool RepeatPrompt { get; private set; } + + public bool TryValidateResponse(float number, out int integer) + { + integer = (int)number; + return integer == number && _validValues.Contains(integer); + } + } +} \ No newline at end of file From 882f51057d5cdca3d559eb2451b7fe7b369af3c5 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 30 Oct 2021 23:52:13 +1100 Subject: [PATCH 4/6] Add tower models --- 90 Tower/csharp/Tower/Models/Needle.cs | 31 +++++++++++++ 90 Tower/csharp/Tower/Models/Towers.cs | 60 ++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 90 Tower/csharp/Tower/Models/Needle.cs create mode 100644 90 Tower/csharp/Tower/Models/Towers.cs diff --git a/90 Tower/csharp/Tower/Models/Needle.cs b/90 Tower/csharp/Tower/Models/Needle.cs new file mode 100644 index 00000000..e373efe9 --- /dev/null +++ b/90 Tower/csharp/Tower/Models/Needle.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Tower.Models +{ + internal class Needle : IEnumerable + { + private readonly Stack _disks = new Stack(); + + public int Top => _disks.TryPeek(out var disc) ? disc : default; + + public bool TryPut(int disc) + { + if (_disks.Count == 0 || disc < _disks.Peek()) + { + _disks.Push(disc); + return true; + } + + return false; + } + + public bool TryGetTopDisk(out int disk) => _disks.TryPop(out disk); + + public IEnumerator GetEnumerator() => + Enumerable.Repeat(1, 7 - _disks.Count).Concat(_disks).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Models/Towers.cs b/90 Tower/csharp/Tower/Models/Towers.cs new file mode 100644 index 00000000..830fa631 --- /dev/null +++ b/90 Tower/csharp/Tower/Models/Towers.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Tower.Models +{ + internal class Towers : IEnumerable<(int, int, int)> + { + private readonly Needle[] _needles = new[] { new Needle(), new Needle(), new Needle() }; + + public bool TryFindDisk(int disk, out int needle) + { + for (needle = 1; needle <= 3; needle++) + { + if (_needles[needle].Top == disk) { return true; } + } + + return false; + } + + public bool TryMoveDisk(int from, int to) + { + if (!_needles[from].TryGetTopDisk(out var disk)) + { + throw new InvalidOperationException($"Needle {from} is empty"); + } + + if (_needles[to].TryPut(disk)) { return true; } + + _needles[from].TryPut(disk); + return false; + } + + public IEnumerator<(int, int, int)> GetEnumerator() => new TowersEnumerator(_needles); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private class TowersEnumerator : IEnumerator<(int First, int Second, int Third)> + { + private readonly List> _enumerators; + + public TowersEnumerator(Needle[] needles) + { + _enumerators = needles.Select(n => n.GetEnumerator()).ToList(); + } + + public (int First, int Second, int Third) Current => + (_enumerators[0].Current, _enumerators[1].Current, _enumerators[2].Current); + + object IEnumerator.Current => Current; + + public void Dispose() => _enumerators.ForEach(e => e.Dispose()); + + public bool MoveNext() => _enumerators.All(e => e.MoveNext()); + + public void Reset() => _enumerators.ForEach(e => e.Reset()); + } + } +} \ No newline at end of file From 26374f143cbb7400d96b1772382b9b7c044663b3 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sun, 31 Oct 2021 10:46:39 +1100 Subject: [PATCH 5/6] Add TowerDisplay --- 90 Tower/csharp/Tower/Models/Needle.cs | 10 +++---- 90 Tower/csharp/Tower/Models/Towers.cs | 4 +-- 90 Tower/csharp/Tower/UI/TowerDisplay.cs | 37 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 90 Tower/csharp/Tower/UI/TowerDisplay.cs diff --git a/90 Tower/csharp/Tower/Models/Needle.cs b/90 Tower/csharp/Tower/Models/Needle.cs index e373efe9..f47329d6 100644 --- a/90 Tower/csharp/Tower/Models/Needle.cs +++ b/90 Tower/csharp/Tower/Models/Needle.cs @@ -8,13 +8,13 @@ namespace Tower.Models { private readonly Stack _disks = new Stack(); - public int Top => _disks.TryPeek(out var disc) ? disc : default; + public int Top => _disks.TryPeek(out var disk) ? disk : default; - public bool TryPut(int disc) + public bool TryPut(int disk) { - if (_disks.Count == 0 || disc < _disks.Peek()) + if (_disks.Count == 0 || disk < _disks.Peek()) { - _disks.Push(disc); + _disks.Push(disk); return true; } @@ -24,7 +24,7 @@ namespace Tower.Models public bool TryGetTopDisk(out int disk) => _disks.TryPop(out disk); public IEnumerator GetEnumerator() => - Enumerable.Repeat(1, 7 - _disks.Count).Concat(_disks).GetEnumerator(); + Enumerable.Repeat(0, 7 - _disks.Count).Concat(_disks).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/90 Tower/csharp/Tower/Models/Towers.cs b/90 Tower/csharp/Tower/Models/Towers.cs index 830fa631..7672ad52 100644 --- a/90 Tower/csharp/Tower/Models/Towers.cs +++ b/90 Tower/csharp/Tower/Models/Towers.cs @@ -36,7 +36,7 @@ namespace Tower.Models IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private class TowersEnumerator : IEnumerator<(int First, int Second, int Third)> + private class TowersEnumerator : IEnumerator<(int, int, int)> { private readonly List> _enumerators; @@ -45,7 +45,7 @@ namespace Tower.Models _enumerators = needles.Select(n => n.GetEnumerator()).ToList(); } - public (int First, int Second, int Third) Current => + public (int, int, int) Current => (_enumerators[0].Current, _enumerators[1].Current, _enumerators[2].Current); object IEnumerator.Current => Current; diff --git a/90 Tower/csharp/Tower/UI/TowerDisplay.cs b/90 Tower/csharp/Tower/UI/TowerDisplay.cs new file mode 100644 index 00000000..f8a56261 --- /dev/null +++ b/90 Tower/csharp/Tower/UI/TowerDisplay.cs @@ -0,0 +1,37 @@ +using System; +using System.Text; +using Tower.Models; + +namespace Tower.UI +{ + internal class TowerDisplay + { + private readonly Towers _towers; + + public TowerDisplay(Towers towers) + { + _towers = towers; + } + + public override string ToString() + { + var builder = new StringBuilder(); + + foreach (var row in _towers) + { + AddTower(row.Item1); + AddTower(row.Item2); + AddTower(row.Item3); + builder.AppendLine(); + } + + return builder.ToString(); + + void AddTower(int size) + { + var padding = 10 - size / 2; + builder.Append(' ', padding).Append('*', Math.Max(1, size)).Append(' ', padding); + } + } + } +} \ No newline at end of file From 9d7a904e680508d7323af54ccb49c75484a66d72 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 27 Nov 2021 22:53:17 +1100 Subject: [PATCH 6/6] Add game logic and fix resources --- 90 Tower/csharp/Tower/Game.cs | 78 +++++++++++++++++++ 90 Tower/csharp/Tower/Models/Needle.cs | 2 + 90 Tower/csharp/Tower/Models/Towers.cs | 41 ++++++++-- 90 Tower/csharp/Tower/Program.cs | 17 +++- .../Tower/Resources/Congratulations.txt | 2 +- .../csharp/Tower/Resources/DiskNotInPlay.txt | 1 + .../csharp/Tower/Resources/Instructions.txt | 1 + 90 Tower/csharp/Tower/Resources/Intro.txt | 4 - 90 Tower/csharp/Tower/Resources/Strings.cs | 2 + .../csharp/Tower/Resources/TaskFinished.txt | 2 + 90 Tower/csharp/Tower/Resources/Title.txt | 6 -- .../csharp/Tower/Resources/TooManyMoves.txt | 2 +- 90 Tower/csharp/Tower/UI/Input.cs | 2 + 90 Tower/csharp/Tower/UI/TowerDisplay.cs | 8 +- 14 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 90 Tower/csharp/Tower/Game.cs create mode 100644 90 Tower/csharp/Tower/Resources/DiskNotInPlay.txt create mode 100644 90 Tower/csharp/Tower/Resources/TaskFinished.txt diff --git a/90 Tower/csharp/Tower/Game.cs b/90 Tower/csharp/Tower/Game.cs new file mode 100644 index 00000000..7e0c7d0b --- /dev/null +++ b/90 Tower/csharp/Tower/Game.cs @@ -0,0 +1,78 @@ +using System; +using Tower.Models; +using Tower.Resources; +using Tower.UI; + +namespace Tower +{ + internal class Game + { + private readonly Towers _towers; + private readonly TowerDisplay _display; + private readonly int _optimalMoveCount; + private int _moveCount; + + public Game(int diskCount) + { + _towers = new Towers(diskCount); + _display = new TowerDisplay(_towers); + _optimalMoveCount = (1 << diskCount) - 1; + } + + public bool Play() + { + Console.Write(Strings.Instructions); + + Console.Write(_display); + + while (true) + { + if (!Input.TryReadNumber(Prompt.Disk, out int disk)) { return false; } + + if (!_towers.TryFindDisk(disk, out var from, out var message)) + { + Console.WriteLine(message); + continue; + } + + if (!Input.TryReadNumber(Prompt.Needle, out var to)) { return false; } + + if (!_towers.TryMoveDisk(from, to)) + { + Console.Write(Strings.IllegalMove); + continue; + } + + Console.Write(_display); + + var result = CheckProgress(); + if (result.HasValue) { return result.Value; } + } + } + + private bool? CheckProgress() + { + _moveCount++; + + if (_moveCount == 128) + { + Console.Write(Strings.TooManyMoves); + return false; + } + + if (_towers.Finished) + { + if (_moveCount == _optimalMoveCount) + { + Console.Write(Strings.Congratulations); + } + + Console.WriteLine(Strings.TaskFinished, _moveCount); + + return true; + } + + return default; + } + } +} diff --git a/90 Tower/csharp/Tower/Models/Needle.cs b/90 Tower/csharp/Tower/Models/Needle.cs index f47329d6..1cd929fa 100644 --- a/90 Tower/csharp/Tower/Models/Needle.cs +++ b/90 Tower/csharp/Tower/Models/Needle.cs @@ -8,6 +8,8 @@ namespace Tower.Models { private readonly Stack _disks = new Stack(); + public bool IsEmpty => _disks.Count == 0; + public int Top => _disks.TryPeek(out var disk) ? disk : default; public bool TryPut(int disk) diff --git a/90 Tower/csharp/Tower/Models/Towers.cs b/90 Tower/csharp/Tower/Models/Towers.cs index 7672ad52..4b03b933 100644 --- a/90 Tower/csharp/Tower/Models/Towers.cs +++ b/90 Tower/csharp/Tower/Models/Towers.cs @@ -2,33 +2,60 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Tower.Resources; namespace Tower.Models { internal class Towers : IEnumerable<(int, int, int)> { - private readonly Needle[] _needles = new[] { new Needle(), new Needle(), new Needle() }; + private static int[] _availableDisks = new[] { 15, 13, 11, 9, 7, 5, 3 }; - public bool TryFindDisk(int disk, out int needle) + private readonly Needle[] _needles = new[] { new Needle(), new Needle(), new Needle() }; + private readonly int _smallestDisk; + + public Towers(int diskCount) { - for (needle = 1; needle <= 3; needle++) + foreach (int disk in _availableDisks.Take(diskCount)) { - if (_needles[needle].Top == disk) { return true; } + this[1].TryPut(disk); + _smallestDisk = disk; + } + } + + private Needle this[int i] => _needles[i-1]; + + public bool Finished => this[1].IsEmpty && this[2].IsEmpty; + + public bool TryFindDisk(int disk, out int needle, out string message) + { + needle = default; + message = default; + + if (disk < _smallestDisk) + { + message = Strings.DiskNotInPlay; + return false; } + for (needle = 1; needle <= 3; needle++) + { + if (this[needle].Top == disk) { return true; } + } + + message = Strings.DiskUnavailable; return false; } public bool TryMoveDisk(int from, int to) { - if (!_needles[from].TryGetTopDisk(out var disk)) + if (!this[from].TryGetTopDisk(out var disk)) { throw new InvalidOperationException($"Needle {from} is empty"); } - if (_needles[to].TryPut(disk)) { return true; } + if (this[to].TryPut(disk)) { return true; } - _needles[from].TryPut(disk); + this[from].TryPut(disk); return false; } diff --git a/90 Tower/csharp/Tower/Program.cs b/90 Tower/csharp/Tower/Program.cs index 4793b817..6bc9f107 100644 --- a/90 Tower/csharp/Tower/Program.cs +++ b/90 Tower/csharp/Tower/Program.cs @@ -1,4 +1,6 @@ using System; +using Tower.Resources; +using Tower.UI; namespace Tower { @@ -6,7 +8,20 @@ namespace Tower { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + Console.Write(Strings.Title); + + do + { + Console.Write(Strings.Intro); + + if (!Input.TryReadNumber(Prompt.DiskCount, out var diskCount)) { return; } + + var game = new Game(diskCount); + + if (!game.Play()) { return; } + } while (Input.ReadYesNo(Strings.PlayAgainPrompt, Strings.YesNoPrompt)); + + Console.Write(Strings.Thanks); } } } diff --git a/90 Tower/csharp/Tower/Resources/Congratulations.txt b/90 Tower/csharp/Tower/Resources/Congratulations.txt index 332297b2..fb078fba 100644 --- a/90 Tower/csharp/Tower/Resources/Congratulations.txt +++ b/90 Tower/csharp/Tower/Resources/Congratulations.txt @@ -1,2 +1,2 @@ -Congratulations +Congratulations!! diff --git a/90 Tower/csharp/Tower/Resources/DiskNotInPlay.txt b/90 Tower/csharp/Tower/Resources/DiskNotInPlay.txt new file mode 100644 index 00000000..270ed0a5 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/DiskNotInPlay.txt @@ -0,0 +1 @@ +That disk is not in play. Make another choice. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/Instructions.txt b/90 Tower/csharp/Tower/Resources/Instructions.txt index 93c71b8d..ca75537b 100644 --- a/90 Tower/csharp/Tower/Resources/Instructions.txt +++ b/90 Tower/csharp/Tower/Resources/Instructions.txt @@ -8,3 +8,4 @@ startup with the disks on needle 1, and attempt to move them to needle 3. Good luck! + diff --git a/90 Tower/csharp/Tower/Resources/Intro.txt b/90 Tower/csharp/Tower/Resources/Intro.txt index a88049a9..e5345da6 100644 --- a/90 Tower/csharp/Tower/Resources/Intro.txt +++ b/90 Tower/csharp/Tower/Resources/Intro.txt @@ -1,7 +1,3 @@ - Towers - Creative Computing Morristown, New Jersey - - Towers of Hanoi puzzle. diff --git a/90 Tower/csharp/Tower/Resources/Strings.cs b/90 Tower/csharp/Tower/Resources/Strings.cs index 62b84d0a..0cd8f6e1 100644 --- a/90 Tower/csharp/Tower/Resources/Strings.cs +++ b/90 Tower/csharp/Tower/Resources/Strings.cs @@ -10,6 +10,7 @@ namespace Tower.Resources internal static string DiskCountPrompt => GetResource(); internal static string DiskCountQuit => GetResource(); internal static string DiskCountRetry => GetResource(); + internal static string DiskNotInPlay => GetResource(); internal static string DiskPrompt => GetResource(); internal static string DiskQuit => GetResource(); internal static string DiskRetry => GetResource(); @@ -21,6 +22,7 @@ namespace Tower.Resources internal static string NeedleQuit => GetResource(); internal static string NeedleRetry => GetResource(); internal static string PlayAgainPrompt => GetResource(); + internal static string TaskFinished => GetResource(); internal static string Thanks => GetResource(); internal static string Title => GetResource(); internal static string TooManyMoves => GetResource(); diff --git a/90 Tower/csharp/Tower/Resources/TaskFinished.txt b/90 Tower/csharp/Tower/Resources/TaskFinished.txt new file mode 100644 index 00000000..e1ea1309 --- /dev/null +++ b/90 Tower/csharp/Tower/Resources/TaskFinished.txt @@ -0,0 +1,2 @@ + +You have performed the task in {0} moves. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/Resources/Title.txt b/90 Tower/csharp/Tower/Resources/Title.txt index a88049a9..ae130577 100644 --- a/90 Tower/csharp/Tower/Resources/Title.txt +++ b/90 Tower/csharp/Tower/Resources/Title.txt @@ -3,9 +3,3 @@ -Towers of Hanoi puzzle. - -You must transfer the disks from the left to the right -tower, one at a time, never putting a larger dish on a -smaller disk. - diff --git a/90 Tower/csharp/Tower/Resources/TooManyMoves.txt b/90 Tower/csharp/Tower/Resources/TooManyMoves.txt index c517218e..680803a5 100644 --- a/90 Tower/csharp/Tower/Resources/TooManyMoves.txt +++ b/90 Tower/csharp/Tower/Resources/TooManyMoves.txt @@ -1,2 +1,2 @@ -Sorry, but i have orders to stop is you make more than +Sorry, but I have orders to stop if you make more than 128 moves. \ No newline at end of file diff --git a/90 Tower/csharp/Tower/UI/Input.cs b/90 Tower/csharp/Tower/UI/Input.cs index 9b2d3747..d1eca384 100644 --- a/90 Tower/csharp/Tower/UI/Input.cs +++ b/90 Tower/csharp/Tower/UI/Input.cs @@ -61,6 +61,8 @@ namespace Tower.UI private static string ReadString(string prompt) { + Prompt(prompt); + var inputValues = ReadStrings(); if (inputValues.Length > 1) { diff --git a/90 Tower/csharp/Tower/UI/TowerDisplay.cs b/90 Tower/csharp/Tower/UI/TowerDisplay.cs index f8a56261..2493201f 100644 --- a/90 Tower/csharp/Tower/UI/TowerDisplay.cs +++ b/90 Tower/csharp/Tower/UI/TowerDisplay.cs @@ -19,15 +19,15 @@ namespace Tower.UI foreach (var row in _towers) { - AddTower(row.Item1); - AddTower(row.Item2); - AddTower(row.Item3); + AppendTower(row.Item1); + AppendTower(row.Item2); + AppendTower(row.Item3); builder.AppendLine(); } return builder.ToString(); - void AddTower(int size) + void AppendTower(int size) { var padding = 10 - size / 2; builder.Append(' ', padding).Append('*', Math.Max(1, size)).Append(' ', padding);