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