Move folder for consistency

This commit is contained in:
Josh Gribbon
2022-01-02 18:50:20 -05:00
parent 11e747e3bf
commit b3b460779e
31 changed files with 0 additions and 0 deletions

34
90_Tower/csharp/Tower.sln Normal file
View File

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

View File

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

View File

@@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Tower.Models
{
internal class Needle : IEnumerable<int>
{
private readonly Stack<int> _disks = new Stack<int>();
public bool IsEmpty => _disks.Count == 0;
public int Top => _disks.TryPeek(out var disk) ? disk : default;
public bool TryPut(int disk)
{
if (_disks.Count == 0 || disk < _disks.Peek())
{
_disks.Push(disk);
return true;
}
return false;
}
public bool TryGetTopDisk(out int disk) => _disks.TryPop(out disk);
public IEnumerator<int> GetEnumerator() =>
Enumerable.Repeat(0, 7 - _disks.Count).Concat(_disks).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@@ -0,0 +1,87 @@
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 static int[] _availableDisks = new[] { 15, 13, 11, 9, 7, 5, 3 };
private readonly Needle[] _needles = new[] { new Needle(), new Needle(), new Needle() };
private readonly int _smallestDisk;
public Towers(int diskCount)
{
foreach (int disk in _availableDisks.Take(diskCount))
{
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 (!this[from].TryGetTopDisk(out var disk))
{
throw new InvalidOperationException($"Needle {from} is empty");
}
if (this[to].TryPut(disk)) { return true; }
this[from].TryPut(disk);
return false;
}
public IEnumerator<(int, int, int)> GetEnumerator() => new TowersEnumerator(_needles);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private class TowersEnumerator : IEnumerator<(int, int, int)>
{
private readonly List<IEnumerator<int>> _enumerators;
public TowersEnumerator(Needle[] needles)
{
_enumerators = needles.Select(n => n.GetEnumerator()).ToList();
}
public (int, int, int) 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());
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using Tower.Resources;
using Tower.UI;
namespace Tower
{
class Program
{
static void Main(string[] args)
{
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);
}
}
}

View File

@@ -0,0 +1,2 @@
Congratulations!!

View File

@@ -0,0 +1 @@
How many disks do you want to move (7 is max)

View File

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

View File

@@ -0,0 +1 @@
Sorry, but i can't do that job for you.

View File

@@ -0,0 +1 @@
That disk is not in play. Make another choice.

View File

@@ -0,0 +1 @@
Which disk would you like to move

View File

@@ -0,0 +1 @@
Stop wasting my time. Go bother someone else.

View File

@@ -0,0 +1 @@
Illegal entry... You may only type 3, 5, 7, 9, 11, 13, or 15.

View File

@@ -0,0 +1 @@
That disk is below another one. Make another choice.

View File

@@ -0,0 +1,3 @@
You can't place a larger disk on top of a smaller one,
it might crush it!
Now then,

View File

@@ -0,0 +1,11 @@
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!

View File

@@ -0,0 +1,7 @@
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.

View File

@@ -0,0 +1 @@
Place disk on which needle

View File

@@ -0,0 +1,2 @@
I tried to warn you, but you wouldn't listen,
Bye bye, big shot.

View File

@@ -0,0 +1,2 @@
I'll assume you hit the wrong key this time. But watch it,
I only allow one mistake

View File

@@ -0,0 +1,2 @@
Try again (Yes or No)

View File

@@ -0,0 +1,40 @@
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 DiskNotInPlay => 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 TaskFinished => 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();
}
}
}

View File

@@ -0,0 +1,2 @@
You have performed the task in {0} moves.

View File

@@ -0,0 +1,2 @@
Thanks for the game!

View File

@@ -0,0 +1,5 @@
Towers
Creative Computing Morristown, New Jersey

View File

@@ -0,0 +1,2 @@
Sorry, but I have orders to stop if you make more than
128 moves.

View File

@@ -0,0 +1,2 @@
'Yes' or 'No' please

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\*.txt" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
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)
{
Prompt(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;
}
}
}

View File

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

View File

@@ -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)
{
AppendTower(row.Item1);
AppendTower(row.Item2);
AppendTower(row.Item3);
builder.AppendLine();
}
return builder.ToString();
void AppendTower(int size)
{
var padding = 10 - size / 2;
builder.Append(' ', padding).Append('*', Math.Max(1, size)).Append(' ', padding);
}
}
}
}