From 2d44db49a41ea083efd4538bd352262d652cb7de Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Mon, 17 Jan 2022 23:01:42 +0200 Subject: [PATCH 1/7] Fix RootNamespace and LangVersion --- 00_Utilities/DotnetUtils/DotnetUtils/Program.cs | 2 +- 05_Bagels/csharp/Bagels.csproj | 1 - 06_Banner/vbnet/banner.vbproj | 3 ++- 08_Batnum/vbnet/batnum.vbproj | 3 ++- 21_Calendar/csharp/Calendar.csproj | 1 - 96_Word/vbnet/word.vbproj | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs index 550250b1..5a3a70e5 100644 --- a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs +++ b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs @@ -216,8 +216,8 @@ void generateMissingProjs() { void checkProjects() { foreach (var info in infos) { + WriteLine(info.LangPath); printProjectWarnings(info); - WriteLine(); } } diff --git a/05_Bagels/csharp/Bagels.csproj b/05_Bagels/csharp/Bagels.csproj index 68fa11ec..54cdbe42 100644 --- a/05_Bagels/csharp/Bagels.csproj +++ b/05_Bagels/csharp/Bagels.csproj @@ -3,7 +3,6 @@ Exe net5.0 - BasicComputerGames.Bagels diff --git a/06_Banner/vbnet/banner.vbproj b/06_Banner/vbnet/banner.vbproj index 659fea7b..e825a636 100644 --- a/06_Banner/vbnet/banner.vbproj +++ b/06_Banner/vbnet/banner.vbproj @@ -2,8 +2,9 @@ Exe - banner + Banner netcoreapp3.1 + 16.9 diff --git a/08_Batnum/vbnet/batnum.vbproj b/08_Batnum/vbnet/batnum.vbproj index 3c21499c..aad6e218 100644 --- a/08_Batnum/vbnet/batnum.vbproj +++ b/08_Batnum/vbnet/batnum.vbproj @@ -2,8 +2,9 @@ Exe - batnum + Batnum netcoreapp3.1 + 16.9 diff --git a/21_Calendar/csharp/Calendar.csproj b/21_Calendar/csharp/Calendar.csproj index 895f2f3f..20827042 100644 --- a/21_Calendar/csharp/Calendar.csproj +++ b/21_Calendar/csharp/Calendar.csproj @@ -3,7 +3,6 @@ Exe net5.0 - _21_calendar diff --git a/96_Word/vbnet/word.vbproj b/96_Word/vbnet/word.vbproj index 9868dd3e..f80094e4 100644 --- a/96_Word/vbnet/word.vbproj +++ b/96_Word/vbnet/word.vbproj @@ -2,8 +2,9 @@ Exe - word + Word netcoreapp3.1 + 16.9 From 06e1ca2ffc36051551e4a9df38566528530f7164 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Mon, 17 Jan 2022 23:09:30 +0200 Subject: [PATCH 2/7] Script -- output ports with no code files; replace !...Any with None --- .../DotnetUtils/DotnetUtils/Extensions.cs | 5 +++++ .../DotnetUtils/DotnetUtils/Program.cs | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs b/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs index a0f52bee..11f05177 100644 --- a/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs +++ b/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs @@ -10,6 +10,11 @@ public static class Extensions { src.Select(x => selector(x.Item1, x.Item2, x.Item3)); public static IEnumerable<(T1, T2, int)> WithIndex(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index)); + public static bool None(this IEnumerable src, Func? predicate = null) => + predicate is null ? + !src.Any() : + !src.Any(predicate); + public static bool IsNullOrWhitespace([NotNullWhen(false)] this string? s) => string.IsNullOrWhiteSpace(s); [return: NotNullIfNotNull("path")] diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs index 5a3a70e5..560a7207 100644 --- a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs +++ b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs @@ -17,6 +17,7 @@ var actions = new (Action action, string description)[] { (multipleProjs, "Output multiple project files"), (checkProjects, "Check .csproj/.vbproj files for target framework, nullability etc."), (checkExecutableProject, "Check that there is at least one executable project per port"), + (noCodeFiles, "Output ports without any code files"), (printPortInfo, "Print info about a single port"), (generateMissingSlns, "Generate solution files when missing"), @@ -87,7 +88,7 @@ void printInfos() { } void missingSln() { - var data = infos.Where(x => !x.Slns.Any()).ToArray(); + var data = infos.Where(x => x.Slns.None()).ToArray(); foreach (var item in data) { WriteLine(item.LangPath); } @@ -98,7 +99,7 @@ void missingSln() { void unexpectedSlnName() { var counter = 0; foreach (var item in infos) { - if (!item.Slns.Any()) { continue; } + if (item.Slns.None()) { continue; } var expectedSlnName = $"{item.GameName}.sln"; if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName), StringComparer.InvariantCultureIgnoreCase)) { continue; } @@ -125,7 +126,7 @@ void multipleSlns() { } void missingProj() { - var data = infos.Where(x => !x.Projs.Any()).ToArray(); + var data = infos.Where(x => x.Projs.None()).ToArray(); foreach (var item in data) { WriteLine(item.LangPath); } @@ -136,7 +137,7 @@ void missingProj() { void unexpectedProjName() { var counter = 0; foreach (var item in infos) { - if (!item.Projs.Any()) { continue; } + if (item.Projs.None()) { continue; } var expectedProjName = $"{item.GameName}.{item.ProjExt}"; if (item.Projs.Contains(Combine(item.LangPath, expectedProjName))) { continue; } @@ -164,7 +165,7 @@ void multipleProjs() { } void generateMissingSlns() { - foreach (var item in infos.Where(x => !x.Slns.Any())) { + foreach (var item in infos.Where(x => x.Slns.None())) { var result = RunProcess("dotnet", $"new sln -n {item.GameName} -o {item.LangPath}"); WriteLine(result); @@ -177,7 +178,7 @@ void generateMissingSlns() { } void generateMissingProjs() { - foreach (var item in infos.Where(x => !x.Projs.Any())) { + foreach (var item in infos.Where(x => x.Projs.None())) { // We can't use the dotnet command to create a new project using the built-in console template, because part of that template // is a Program.cs / Program.vb file. If there already are code files, there's no need to add a new empty one; and // if there's already such a file, it might try to overwrite it. @@ -284,6 +285,15 @@ void checkExecutableProject() { } } +void noCodeFiles() { + var qry = infos + .Where(x => x.CodeFiles.None()) + .OrderBy(x => x.Lang); + foreach (var item in qry) { + WriteLine(item.LangPath); + } +} + void tryBuild() { // if has code files, try to build } From dc64d34a3cbd485c03ed8fe7ac6deb85b210e804 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Thu, 20 Jan 2022 10:55:46 +0200 Subject: [PATCH 3/7] Detect Option Strict for VB projects --- 00_Utilities/DotnetUtils/DotnetUtils/Program.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs index 560a7207..6e51d4f1 100644 --- a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs +++ b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs @@ -232,13 +232,15 @@ void printProjectWarnings(PortInfo info) { nullable, implicitUsing, rootNamespace, - langVersion + langVersion, + optionStrict ) = ( getValue(parent, "TargetFramework", "TargetFrameworks"), getValue(parent, "Nullable"), getValue(parent, "ImplicitUsings"), getValue(parent, "RootNamespace"), - getValue(parent, "LangVersion") + getValue(parent, "LangVersion"), + getValue(parent, "OptionStrict") ); if (framework != "net6.0") { @@ -267,6 +269,9 @@ void printProjectWarnings(PortInfo info) { if (langVersion != "16.9") { warnings.Add($"LangVersion: {langVersion}"); } + if (optionStrict != "On") { + warnings.Add($"OptionStrict: {optionStrict}"); + } } if (warnings.Any()) { From a8165eec0ea922476e67b0f9d5894474c6ec1580 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Thu, 20 Jan 2022 10:56:19 +0200 Subject: [PATCH 4/7] Animal (VB.NET) implementation --- 03_Animal/vbnet/Animal.vbproj | 1 + 03_Animal/vbnet/Branch.vb | 28 +++++ 03_Animal/vbnet/Game.vb | 126 +++++++++++++++++++ 03_Animal/vbnet/Program.vb | 6 + 03_Animal/vbnet/Shared/ConsoleAdapter.vb | 28 +++++ 03_Animal/vbnet/Shared/ConsoleAdapterBase.vb | 9 ++ 03_Animal/vbnet/Shared/Extensions.vb | 21 ++++ 7 files changed, 219 insertions(+) create mode 100644 03_Animal/vbnet/Branch.vb create mode 100644 03_Animal/vbnet/Game.vb create mode 100644 03_Animal/vbnet/Program.vb create mode 100644 03_Animal/vbnet/Shared/ConsoleAdapter.vb create mode 100644 03_Animal/vbnet/Shared/ConsoleAdapterBase.vb create mode 100644 03_Animal/vbnet/Shared/Extensions.vb diff --git a/03_Animal/vbnet/Animal.vbproj b/03_Animal/vbnet/Animal.vbproj index f54e4e3d..f01d7b62 100644 --- a/03_Animal/vbnet/Animal.vbproj +++ b/03_Animal/vbnet/Animal.vbproj @@ -4,5 +4,6 @@ Animal net6.0 16.9 + On diff --git a/03_Animal/vbnet/Branch.vb b/03_Animal/vbnet/Branch.vb new file mode 100644 index 00000000..6734e39d --- /dev/null +++ b/03_Animal/vbnet/Branch.vb @@ -0,0 +1,28 @@ +Public Class Branch + Public Property Text As String + + Public ReadOnly Property IsEnd As Boolean + Get + Return Yes Is Nothing AndAlso No Is Nothing + End Get + End Property + + Public Property Yes As Branch + Public Property No As Branch + + Public Iterator Function DescendantTexts() As IEnumerable(Of String) + If Yes IsNot Nothing Then + Yield Yes.Text + For Each childText In Yes.DescendantTexts + Yield childText + Next + End If + + If No IsNot Nothing Then + Yield No.Text + For Each childText In No.DescendantTexts + Yield childText + Next + End If + End Function +End Class diff --git a/03_Animal/vbnet/Game.vb b/03_Animal/vbnet/Game.vb new file mode 100644 index 00000000..3bc2b2f6 --- /dev/null +++ b/03_Animal/vbnet/Game.vb @@ -0,0 +1,126 @@ +Option Compare Text + +Public Class Game + Private Shared ReadOnly YesNoResponses As New Dictionary(Of String, Boolean)(StringComparer.InvariantCultureIgnoreCase) From { + {"yes", True}, + {"y", True}, + {"true", True}, + {"t", True}, + {"1", True}, + {"no", False}, + {"n", False}, + {"false", False}, + {"f", False}, + {"0", False} + } + + ReadOnly console As ConsoleAdapterBase + ReadOnly root As New Branch With { + .Text = "DOES IT SWIM?", + .Yes = New Branch With {.Text = "FISH"}, + .No = New Branch With {.Text = "BIRD"} + } + + ''' Reduces a string or console input to True, False or Nothing. Case-insensitive. + ''' Optional String to reduce via the same logic. If not passed in, will use console.ReadLine + ''' + ''' Returns True for a "yes" response (yes, y, true, t, 1) and False for a "no" response (no, n, false, f, 0).
+ ''' Returns Nothing if the response doesn't match any of these. + '''
+ Private Function GetYesNo(Optional s As String = Nothing) As Boolean? + s = If(s, console.ReadLine) + Dim ret As Boolean + If YesNoResponses.TryGetValue(s, ret) Then Return ret + Return Nothing + End Function + + Sub New(console As ConsoleAdapterBase) + If console Is Nothing Then Throw New ArgumentNullException(NameOf(console)) + Me.console = console + End Sub + + + Sub BeginLoop() + console.WriteCenteredLine("ANIMAL") + console.WriteCenteredLine("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + console.Write( +" + + +PLAY 'GUESS THE ANIMAL' + +THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT. + +") + + Do + console.Write("ARE YOU THINKING OF AN ANIMAL? ") + + Dim response = console.ReadLine + If response = "list" Then + console.WriteLine( +" +ANIMALS I ALREADY KNOW ARE:") + root.DescendantTexts.ForEach(Sub(text, index) + If index > 0 AndAlso index Mod 4 = 0 Then console.WriteLine() + console.Write($"{text.MaxLength(15),-15}") + End Sub) + console.WriteLine( +" +") + Continue Do + End If + + Dim ynResponse = GetYesNo(response) + If ynResponse Is Nothing OrElse Not ynResponse Then Continue Do + + Dim currentBranch = root + Do While Not currentBranch.IsEnd + console.Write($"{currentBranch.Text} ") + Do + ynResponse = GetYesNo() + Loop While ynResponse Is Nothing + currentBranch = If( + ynResponse, + currentBranch.Yes, + currentBranch.No + ) + Loop + + 'at end + console.Write($"IS IT A {currentBranch.Text}? ") + ynResponse = GetYesNo() + If ynResponse Then ' No difference between False or Nothing + console.WriteLine("WHY NOT TRY ANOTHER ANIMAL?") + Continue Do + End If + + console.Write("THE ANIMAL YOU WERE THINKING OF WAS A ? ") + Dim newAnimal = console.ReadLine + + console.WriteLine( +$"PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A +{newAnimal} FROM A {currentBranch.Text}") + Dim newQuestion = console.ReadLine + + console.Write( +$"FOR A {newAnimal} THE ANSWER WOULD BE ? ") + Do + ynResponse = GetYesNo() + Loop While ynResponse Is Nothing + + Dim newBranch = New Branch With {.Text = newAnimal} + Dim currentBranchCopy = New Branch With {.Text = currentBranch.Text} + currentBranch.Text = newQuestion + If ynResponse Then + currentBranch.Yes = newBranch + currentBranch.No = currentBranchCopy + Else + currentBranch.No = newBranch + currentBranch.Yes = currentBranchCopy + End If + + ' TODO how do we exit? + Loop + End Sub +End Class diff --git a/03_Animal/vbnet/Program.vb b/03_Animal/vbnet/Program.vb new file mode 100644 index 00000000..adb34932 --- /dev/null +++ b/03_Animal/vbnet/Program.vb @@ -0,0 +1,6 @@ +Module Program + Sub Main() + Dim game As New Game(New ConsoleAdapter) + game.BeginLoop() + End Sub +End Module diff --git a/03_Animal/vbnet/Shared/ConsoleAdapter.vb b/03_Animal/vbnet/Shared/ConsoleAdapter.vb new file mode 100644 index 00000000..132f1968 --- /dev/null +++ b/03_Animal/vbnet/Shared/ConsoleAdapter.vb @@ -0,0 +1,28 @@ +Public Class ConsoleAdapter + Inherits ConsoleAdapterBase + + Public Overrides Sub Write(value As Object) + Console.Write(value) + End Sub + + Public Overrides Sub WriteLine(value As Object) + Console.WriteLine(value) + End Sub + + Public Overrides Sub WriteLine() + Console.WriteLine() + End Sub + + Public Overrides Sub WriteCenteredLine(value As Object) + Dim toWrite = If(value?.ToString, "") + Console.WriteLine($"{Space((Console.WindowWidth - toWrite.Length) \ 2)}{toWrite}") + End Sub + + Public Overrides Function ReadLine() As String + Dim response As String + Do + response = Console.ReadLine + Loop While response Is Nothing + Return response.Trim + End Function +End Class diff --git a/03_Animal/vbnet/Shared/ConsoleAdapterBase.vb b/03_Animal/vbnet/Shared/ConsoleAdapterBase.vb new file mode 100644 index 00000000..4c9166bf --- /dev/null +++ b/03_Animal/vbnet/Shared/ConsoleAdapterBase.vb @@ -0,0 +1,9 @@ +Public MustInherit Class ConsoleAdapterBase + Public MustOverride Sub Write(value As Object) + Public MustOverride Sub WriteLine(value As Object) + Public MustOverride Sub WriteLine() + Public MustOverride Sub WriteCenteredLine(value As Object) + + ''' Implementations should always return a String without leading or trailing whitespace, never Nothng + Public MustOverride Function ReadLine() As String +End Class diff --git a/03_Animal/vbnet/Shared/Extensions.vb b/03_Animal/vbnet/Shared/Extensions.vb new file mode 100644 index 00000000..83d1914b --- /dev/null +++ b/03_Animal/vbnet/Shared/Extensions.vb @@ -0,0 +1,21 @@ +Imports System.Runtime.CompilerServices + +Public Module Extensions + Public Sub ForEach(Of T)(src As IEnumerable(Of T), action As Action(Of T, Integer)) + Dim index As Integer + For Each x In src + action(x, index) + index += 1 + Next + End Sub + + Public Function MaxLength(s As String, value As Integer) As String + If s Is Nothing Then Return Nothing + Return s.Substring(0, Math.Min(s.Length, value)) + End Function + + Public Function ForceEndsWith(s As String, toAppend As String) As String + If Not s.EndsWith(toAppend, StringComparison.OrdinalIgnoreCase) Then s += toAppend + Return s + End Function +End Module From 311776e3a8d6fbe5594499d429e228b84075701d Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Thu, 20 Jan 2022 11:03:38 +0200 Subject: [PATCH 5/7] Create test project --- .../vbnet/Animal.Tests/Animal.Tests.vbproj | 23 +++++++++++++++++ 03_Animal/vbnet/Animal.Tests/UnitTest1.vb | 12 +++++++++ 03_Animal/vbnet/Animal.sln | 25 +++++++++++++------ 03_Animal/vbnet/{ => Animal}/Animal.vbproj | 0 03_Animal/vbnet/{ => Animal}/Branch.vb | 0 03_Animal/vbnet/{ => Animal}/Game.vb | 0 03_Animal/vbnet/{ => Animal}/Program.vb | 0 .../{ => Animal}/Shared/ConsoleAdapter.vb | 0 .../{ => Animal}/Shared/ConsoleAdapterBase.vb | 0 .../vbnet/{ => Animal}/Shared/Extensions.vb | 0 10 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj create mode 100644 03_Animal/vbnet/Animal.Tests/UnitTest1.vb rename 03_Animal/vbnet/{ => Animal}/Animal.vbproj (100%) rename 03_Animal/vbnet/{ => Animal}/Branch.vb (100%) rename 03_Animal/vbnet/{ => Animal}/Game.vb (100%) rename 03_Animal/vbnet/{ => Animal}/Program.vb (100%) rename 03_Animal/vbnet/{ => Animal}/Shared/ConsoleAdapter.vb (100%) rename 03_Animal/vbnet/{ => Animal}/Shared/ConsoleAdapterBase.vb (100%) rename 03_Animal/vbnet/{ => Animal}/Shared/Extensions.vb (100%) diff --git a/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj b/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj new file mode 100644 index 00000000..05d00314 --- /dev/null +++ b/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj @@ -0,0 +1,23 @@ + + + + Animal.Tests + net6.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/03_Animal/vbnet/Animal.Tests/UnitTest1.vb b/03_Animal/vbnet/Animal.Tests/UnitTest1.vb new file mode 100644 index 00000000..fe32490e --- /dev/null +++ b/03_Animal/vbnet/Animal.Tests/UnitTest1.vb @@ -0,0 +1,12 @@ +Imports System +Imports Xunit + +Namespace Animal.Tests + Public Class UnitTest1 + + Sub TestSub() + + End Sub + End Class +End Namespace + diff --git a/03_Animal/vbnet/Animal.sln b/03_Animal/vbnet/Animal.sln index eaaf1b67..f3b48076 100644 --- a/03_Animal/vbnet/Animal.sln +++ b/03_Animal/vbnet/Animal.sln @@ -1,22 +1,31 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Animal", "Animal.vbproj", "{147D66D5-D817-4024-9447-9F5B9A6D2B7D}" +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Animal", "Animal\Animal.vbproj", "{5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Animal.Tests", "Animal.Tests\Animal.Tests.vbproj", "{3986C6A2-77D4-4F00-B3CF-F5736C623B1E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5517E4CE-BCF9-4D1F-9A17-B620C1B96B0D}.Release|Any CPU.Build.0 = Release|Any CPU + {3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3986C6A2-77D4-4F00-B3CF-F5736C623B1E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {147D66D5-D817-4024-9447-9F5B9A6D2B7D}.Release|Any CPU.Build.0 = Release|Any CPU + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {88469A47-E30C-4763-A325-074101D16608} EndGlobalSection EndGlobal diff --git a/03_Animal/vbnet/Animal.vbproj b/03_Animal/vbnet/Animal/Animal.vbproj similarity index 100% rename from 03_Animal/vbnet/Animal.vbproj rename to 03_Animal/vbnet/Animal/Animal.vbproj diff --git a/03_Animal/vbnet/Branch.vb b/03_Animal/vbnet/Animal/Branch.vb similarity index 100% rename from 03_Animal/vbnet/Branch.vb rename to 03_Animal/vbnet/Animal/Branch.vb diff --git a/03_Animal/vbnet/Game.vb b/03_Animal/vbnet/Animal/Game.vb similarity index 100% rename from 03_Animal/vbnet/Game.vb rename to 03_Animal/vbnet/Animal/Game.vb diff --git a/03_Animal/vbnet/Program.vb b/03_Animal/vbnet/Animal/Program.vb similarity index 100% rename from 03_Animal/vbnet/Program.vb rename to 03_Animal/vbnet/Animal/Program.vb diff --git a/03_Animal/vbnet/Shared/ConsoleAdapter.vb b/03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb similarity index 100% rename from 03_Animal/vbnet/Shared/ConsoleAdapter.vb rename to 03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb diff --git a/03_Animal/vbnet/Shared/ConsoleAdapterBase.vb b/03_Animal/vbnet/Animal/Shared/ConsoleAdapterBase.vb similarity index 100% rename from 03_Animal/vbnet/Shared/ConsoleAdapterBase.vb rename to 03_Animal/vbnet/Animal/Shared/ConsoleAdapterBase.vb diff --git a/03_Animal/vbnet/Shared/Extensions.vb b/03_Animal/vbnet/Animal/Shared/Extensions.vb similarity index 100% rename from 03_Animal/vbnet/Shared/Extensions.vb rename to 03_Animal/vbnet/Animal/Shared/Extensions.vb From e8d753ada84b01229398742925be45e52ac31f77 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Fri, 21 Jan 2022 04:31:55 +0200 Subject: [PATCH 6/7] Added tests project --- .../vbnet/Animal.Tests/Animal.Tests.vbproj | 8 +- 03_Animal/vbnet/Animal.Tests/MockConsole.vb | 65 +++++++++++++++ 03_Animal/vbnet/Animal.Tests/TestContainer.vb | 80 +++++++++++++++++++ 03_Animal/vbnet/Animal.Tests/UnitTest1.vb | 12 --- .../vbnet/Animal/Shared/ConsoleAdapter.vb | 1 + 03_Animal/vbnet/Animal/Shared/Extensions.vb | 29 +++++++ 6 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 03_Animal/vbnet/Animal.Tests/MockConsole.vb create mode 100644 03_Animal/vbnet/Animal.Tests/TestContainer.vb delete mode 100644 03_Animal/vbnet/Animal.Tests/UnitTest1.vb diff --git a/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj b/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj index 05d00314..62a341e9 100644 --- a/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj +++ b/03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj @@ -3,12 +3,12 @@ Animal.Tests net6.0 - false + On - + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,4 +20,8 @@ + + + + diff --git a/03_Animal/vbnet/Animal.Tests/MockConsole.vb b/03_Animal/vbnet/Animal.Tests/MockConsole.vb new file mode 100644 index 00000000..a78dc50f --- /dev/null +++ b/03_Animal/vbnet/Animal.Tests/MockConsole.vb @@ -0,0 +1,65 @@ +Imports System.IO + +Public Class MockConsole + Inherits ConsoleAdapterBase + + Private inputs As Queue(Of String) + Public ReadOnly Lines As New List(Of (line As String, centered As Boolean)) From { + ("", False) + } + + ' TODO it's possible to clear all the lines, and we'd have to check once again in WriteString and WriteCenteredLine if there are any lines + + Sub New(Inputs As IEnumerable(Of String)) + Me.inputs = New Queue(Of String)(Inputs) + End Sub + + Private Sub CheckLinesInitialized() + If Lines.Count = 0 Then Lines.Add(("", False)) + End Sub + + Private Sub WriteString(s As String, Optional centered As Boolean = False) + If s Is Nothing Then Return + CheckLinesInitialized() + s.Split(Environment.NewLine).ForEach(Sub(line, index) + If index = 0 Then + Dim currentLast = Lines(Lines.Count - 1) + ' centered should never come from the current last line + ' if WriteCenteredLine is called, it immediately creates a new line + Lines(Lines.Count - 1) = (currentLast.line + line, centered) + Else + Lines.Add((line, centered)) + End If + End Sub) + End Sub + + Public Overrides Sub Write(value As Object) + WriteString(value?.ToString) + End Sub + + Public Overrides Sub WriteLine(value As Object) + WriteString(value?.ToString) + WriteLine() + End Sub + + Public Overrides Sub WriteLine() + Lines.Add(("", False)) + End Sub + + Public Overrides Sub WriteCenteredLine(value As Object) + If Lines.Count = 0 Then Lines.Add(("", False)) + Dim currentLast = Lines(Lines.Count - 1).line + If currentLast.Length > 0 Then Throw New InvalidOperationException("Can only write centered line if cursor is at start of line.") + WriteString(value?.ToString, True) + WriteLine() + End Sub + + Public Overrides Function ReadLine() As String + ' Indicates the end of a test run, for programs which loop endlessly + If inputs.Count = 0 Then Throw New EndOfStreamException("End of inputs") + + Dim nextInput = inputs.Dequeue.Trim + WriteLine(nextInput) + Return nextInput + End Function +End Class diff --git a/03_Animal/vbnet/Animal.Tests/TestContainer.vb b/03_Animal/vbnet/Animal.Tests/TestContainer.vb new file mode 100644 index 00000000..3aed862a --- /dev/null +++ b/03_Animal/vbnet/Animal.Tests/TestContainer.vb @@ -0,0 +1,80 @@ +Imports Xunit +Imports Animal +Imports System.IO + +Public Class TestContainer + Private Shared Function ResponseVariantExpander(src As IEnumerable(Of String)) As TheoryData(Of String) + Dim theoryData = New TheoryData(Of String) + src. + SelectMany(Function(x) {x, x.Substring(0, 1)}). + SelectMany(Function(x) { + x, + x.ToUpperInvariant, + x.ToLowerInvariant, + x.ToTitleCase, + x.ToReverseCase + }). + Distinct. + ForEach(Sub(x) theoryData.Add(x)) + Return theoryData + End Function + Private Shared YesVariantsThepryData As TheoryData(Of String) = ResponseVariantExpander({"yes", "true", "1"}) + Private Shared Function YesVariants() As TheoryData(Of String) + Return YesVariantsThepryData + End Function + Private Shared NoVariantsThepryData As TheoryData(Of String) = ResponseVariantExpander({"no", "false", "0"}) + Private Shared Function NoVariants() As TheoryData(Of String) + Return NoVariantsThepryData + End Function + + ''' Test LIST variants + + + + + + Sub List(listResponse As String) + Dim console As New MockConsole({listResponse}) + Dim game As New Game(console) + Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop()) + Assert.Equal( + { + "ANIMALS I ALREADY KNOW ARE:", + "FISH BIRD " + }, + console.Lines.Slice(-4, -2).Select(Function(x) x.line) + ) + End Sub + + '' Test YES variants + + + Sub YesVariant(yesVariant As String) + Dim console As New MockConsole({yesVariant}) + Dim game As New Game(console) + Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop()) + Assert.Equal( + { + $"ARE YOU THINKING OF AN ANIMAL? {yesVariant}", + "DOES IT SWIM? " + }, + console.Lines.Slice(-2, 0).Select(Function(x) x.line) + ) + End Sub + + '' Test NO variants + + + Sub NoVariant(noVariant As String) + Dim console As New MockConsole({"y", noVariant}) + Dim game As New Game(console) + Assert.Throws(Of EndOfStreamException)(Sub() game.BeginLoop()) + Assert.Equal( + { + $"DOES IT SWIM? {noVariant}", + "IS IT A BIRD? " + }, + console.Lines.Slice(-2, 0).Select(Function(x) x.line) + ) + End Sub +End Class diff --git a/03_Animal/vbnet/Animal.Tests/UnitTest1.vb b/03_Animal/vbnet/Animal.Tests/UnitTest1.vb deleted file mode 100644 index fe32490e..00000000 --- a/03_Animal/vbnet/Animal.Tests/UnitTest1.vb +++ /dev/null @@ -1,12 +0,0 @@ -Imports System -Imports Xunit - -Namespace Animal.Tests - Public Class UnitTest1 - - Sub TestSub() - - End Sub - End Class -End Namespace - diff --git a/03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb b/03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb index 132f1968..f49394e0 100644 --- a/03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb +++ b/03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb @@ -14,6 +14,7 @@ End Sub Public Overrides Sub WriteCenteredLine(value As Object) + If Console.CursorLeft <> 0 Then Throw New InvalidOperationException("Can only write centered line if cursor is at start of line.") Dim toWrite = If(value?.ToString, "") Console.WriteLine($"{Space((Console.WindowWidth - toWrite.Length) \ 2)}{toWrite}") End Sub diff --git a/03_Animal/vbnet/Animal/Shared/Extensions.vb b/03_Animal/vbnet/Animal/Shared/Extensions.vb index 83d1914b..e10df441 100644 --- a/03_Animal/vbnet/Animal/Shared/Extensions.vb +++ b/03_Animal/vbnet/Animal/Shared/Extensions.vb @@ -1,6 +1,11 @@ Imports System.Runtime.CompilerServices Public Module Extensions + Public Sub ForEach(Of T)(src As IEnumerable(Of T), action As Action(Of T)) + For Each x In src + action(x) + Next + End Sub Public Sub ForEach(Of T)(src As IEnumerable(Of T), action As Action(Of T, Integer)) Dim index As Integer For Each x In src @@ -18,4 +23,28 @@ Public Module Extensions If Not s.EndsWith(toAppend, StringComparison.OrdinalIgnoreCase) Then s += toAppend Return s End Function + + Public Function ToTitleCase(s As String) As String + If s Is Nothing Then Return Nothing + Return Char.ToUpperInvariant(s(0)) + s.Substring(1).ToUpperInvariant + End Function + + ' https://stackoverflow.com/a/3681580/111794 + Public Function ToReverseCase(s As String) As String + If s Is Nothing Then Return Nothing + Return New String(s.Select(Function(c) If( + Not Char.IsLetter(c), + c, + If( + Char.IsUpper(c), Char.ToLowerInvariant(c), Char.ToUpperInvariant(c) + ) + )).ToArray) + End Function + + ' https://stackoverflow.com/a/58132204/111794 + Public Function Slice(Of T)(lst As IList(Of T), start As Integer, [end] As Integer) As T() + start = If(start >= 0, start, lst.Count + start) + [end] = If([end] > 0, [end], lst.Count + [end]) + Return lst.Skip(start).Take([end] - start).ToArray + End Function End Module From 0a4fe29ebe96b9743bb1a3bf00a9c387f511b8c3 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Fri, 21 Jan 2022 04:52:44 +0200 Subject: [PATCH 7/7] Update README and add comments --- 03_Animal/vbnet/Animal/Branch.vb | 1 + 03_Animal/vbnet/Animal/Game.vb | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/03_Animal/vbnet/Animal/Branch.vb b/03_Animal/vbnet/Animal/Branch.vb index 6734e39d..4af369bb 100644 --- a/03_Animal/vbnet/Animal/Branch.vb +++ b/03_Animal/vbnet/Animal/Branch.vb @@ -10,6 +10,7 @@ Public Property Yes As Branch Public Property No As Branch + ' Allows walking all the descendants recursively Public Iterator Function DescendantTexts() As IEnumerable(Of String) If Yes IsNot Nothing Then Yield Yes.Text diff --git a/03_Animal/vbnet/Animal/Game.vb b/03_Animal/vbnet/Animal/Game.vb index 3bc2b2f6..bca72005 100644 --- a/03_Animal/vbnet/Animal/Game.vb +++ b/03_Animal/vbnet/Animal/Game.vb @@ -1,6 +1,8 @@ Option Compare Text Public Class Game + ' This Dictionary holds the corresponding value for each of the variants of "YES" and "NO" we accept + ' Note that the Dictionary is case-insensitive, meaning it maps "YES", "yes" and even "yEs" to True Private Shared ReadOnly YesNoResponses As New Dictionary(Of String, Boolean)(StringComparer.InvariantCultureIgnoreCase) From { {"yes", True}, {"y", True}, @@ -15,6 +17,8 @@ Public Class Game } ReadOnly console As ConsoleAdapterBase + + ' The pre-initialized root branch ReadOnly root As New Branch With { .Text = "DOES IT SWIM?", .Yes = New Branch With {.Text = "FISH"}, @@ -41,6 +45,7 @@ Public Class Game Sub BeginLoop() + ' Print the program heading console.WriteCenteredLine("ANIMAL") console.WriteCenteredLine("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") console.Write( @@ -58,10 +63,16 @@ THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT. Dim response = console.ReadLine If response = "list" Then + ' List all the stored animals console.WriteLine( " ANIMALS I ALREADY KNOW ARE:") + + ' We're using a ForEach extension method instead of the regular For Each loop to provide the index alongside the text root.DescendantTexts.ForEach(Sub(text, index) + ' We want to move to the next line after every four animals + ' But for the first animal, where the index is 0, 0 Mod 4 will also return 0 + ' So we have to explicitly exclude the first animal If index > 0 AndAlso index Mod 4 = 0 Then console.WriteLine() console.Write($"{text.MaxLength(15),-15}") End Sub) @@ -76,10 +87,14 @@ ANIMALS I ALREADY KNOW ARE:") Dim currentBranch = root Do While Not currentBranch.IsEnd + ' Branches can either be questions, or end branches + ' We have to walk the questions, prompting each time for "yes" or "no" console.Write($"{currentBranch.Text} ") Do ynResponse = GetYesNo() Loop While ynResponse Is Nothing + + ' Depending on the answer, we'll follow either the branch at "Yes" or "No" currentBranch = If( ynResponse, currentBranch.Yes, @@ -87,31 +102,42 @@ ANIMALS I ALREADY KNOW ARE:") ) Loop - 'at end + ' Now we're at an end branch console.Write($"IS IT A {currentBranch.Text}? ") ynResponse = GetYesNo() - If ynResponse Then ' No difference between False or Nothing + If ynResponse Then ' Only if ynResponse = True will we go into this If Then console.WriteLine("WHY NOT TRY ANOTHER ANIMAL?") Continue Do End If + ' Get the new animal console.Write("THE ANIMAL YOU WERE THINKING OF WAS A ? ") Dim newAnimal = console.ReadLine + ' Get the question used to distinguish the new animal from the current end branch console.WriteLine( $"PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A {newAnimal} FROM A {currentBranch.Text}") Dim newQuestion = console.ReadLine + ' Get the answer to that question, for the new animal + ' for the old animal, the answer would be the opposite console.Write( $"FOR A {newAnimal} THE ANSWER WOULD BE ? ") Do ynResponse = GetYesNo() Loop While ynResponse Is Nothing + ' Create the new end branch for the new animal Dim newBranch = New Branch With {.Text = newAnimal} + + ' Copy over the current animal to another new end branch Dim currentBranchCopy = New Branch With {.Text = currentBranch.Text} + + ' Make the current branch into the distinguishing question currentBranch.Text = newQuestion + + ' Set the Yes and No branches of the current branch according to the answer If ynResponse Then currentBranch.Yes = newBranch currentBranch.No = currentBranchCopy