From 2d44db49a41ea083efd4538bd352262d652cb7de Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Mon, 17 Jan 2022 23:01:42 +0200 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 ed0dcb5add6a4c8ce94e0d85b0e17fbf663c3625 Mon Sep 17 00:00:00 2001 From: openback Date: Thu, 20 Jan 2022 21:05:43 -0500 Subject: [PATCH 06/10] 09_Battle/python - Add multi-line strings and *args --- 09_Battle/python/battle.py | 42 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/09_Battle/python/battle.py b/09_Battle/python/battle.py index 8c992458..d9449b77 100644 --- a/09_Battle/python/battle.py +++ b/09_Battle/python/battle.py @@ -40,8 +40,8 @@ def place_ship(sea: SeaType, size: int, code: int) -> None: point = add_vector(point, vector) points.append(point) - if not (all([is_within_sea(point, sea) for point in points]) and - all([value_at(point, sea) == 0 for point in points])): + if (not all([is_within_sea(point, sea) for point in points]) or + any([value_at(point, sea) for point in points])): # ship out of bounds or crosses other ship, trying again continue @@ -65,7 +65,7 @@ def has_ship(sea: SeaType, code: int) -> bool: return any(code in row for row in sea) -def count_sunk(sea: SeaType, codes: Tuple[int, ...]) -> int: +def count_sunk(sea: SeaType, *codes: int) -> int: return sum(not has_ship(sea, code) for code in codes) @@ -106,20 +106,23 @@ def setup_ships(sea: SeaType): def main() -> None: - print(' BATTLE') - print('CREATIVE COMPUTING MORRISTOWN, NEW JERSEY') - print() sea = tuple(([0 for _ in range(SEA_WIDTH)] for _ in range(SEA_WIDTH))) setup_ships(sea) - print('THE FOLLOWING CODE OF THE BAD GUYS\' FLEET DISPOSITION') - print('HAS BEEN CAPTURED BUT NOT DECODED:') - print() + print(f''' + BATTLE +CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + +THE FOLLOWING CODE OF THE BAD GUYS' FLEET DISPOSITION +HAS BEEN CAPTURED BUT NOT DECODED: + +''') print_encoded_sea(sea) - print() - print('DE-CODE IT AND USE IT IF YOU CAN') - print('BUT KEEP THE DE-CODING METHOD A SECRET.') - print() - print('START GAME') + print(''' + +DE-CODE IT AND USE IT IF YOU CAN +BUT KEEP THE DE-CODING METHOD A SECRET. + +START GAME''') splashes = 0 hits = 0 @@ -142,11 +145,11 @@ def main() -> None: if not has_ship(sea, target_value): print('AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS.') print('SO FAR, THE BAD GUYS HAVE LOST') - print(f'{count_sunk(sea, (1, 2))} DESTROYER(S),', - f'{count_sunk(sea, (3, 4))} CRUISER(S),', - f'AND {count_sunk(sea, (5, 6))} AIRCRAFT CARRIER(S).') + print(f'{count_sunk(sea, 1, 2)} DESTROYER(S),', + f'{count_sunk(sea, 3, 4)} CRUISER(S),', + f'AND {count_sunk(sea, 5, 6)} AIRCRAFT CARRIER(S).') - if any(has_ship(sea, code) for code in range(1, SEA_WIDTH + 1)): + if any(has_ship(sea, code) for code in range(1, 7)): print(f'YOUR CURRENT SPLASH/HIT RATIO IS {splashes}/{hits}') continue @@ -156,8 +159,7 @@ def main() -> None: if not splashes: print('CONGRATULATIONS -- A DIRECT HIT EVERY TIME.') - print() - print('****************************') + print("\n****************************") break From 9f9cdc3781a883a8293fbd192b5fdd3daa4f2361 Mon Sep 17 00:00:00 2001 From: openback Date: Thu, 20 Jan 2022 21:06:27 -0500 Subject: [PATCH 07/10] 09_Battle/python - Add OO version --- 09_Battle/python/battle_oo.py | 203 ++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 09_Battle/python/battle_oo.py diff --git a/09_Battle/python/battle_oo.py b/09_Battle/python/battle_oo.py new file mode 100644 index 00000000..dc35efa6 --- /dev/null +++ b/09_Battle/python/battle_oo.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +from random import randrange + + +DESTROYER_LENGTH = 2 +CRUISER_LENGTH = 3 +AIRCRAFT_CARRIER_LENGTH = 4 + + +@dataclass(frozen=True) +class Point: + x: int + y: int + + @classmethod + def random(cls, start: int, stop: int) -> 'Point': + return Point(randrange(start, stop), randrange(start, stop)) + + def __add__(self, vector: 'Vector') -> 'Point': + return Point(self.x + vector.x, self.y + vector.y) + + +@dataclass(frozen=True) +class Vector: + x: int + y: int + + @staticmethod + def random() -> 'Vector': + return Vector(randrange(-1, 2, 2), randrange(-1, 2, 2)) + + def __mul__(self, factor: int) -> 'Vector': + return Vector(self.x * factor, self.y * factor) + + +class Sea: + WIDTH = 6 + + def __init__(self): + self._graph = tuple(([0 for _ in range(self.WIDTH)] for _ in range(self.WIDTH))) + + def _validate_item_indices(self, point: Point) -> None: + if not isinstance(point, Point): + raise ValueError(f'Sea indices must be Points, not {type(point).__name__}') + + if not((1 <= point.x <= self.WIDTH) and (1 <= point.y <= self.WIDTH)): + raise IndexError('Sea index out of range') + + # Allows us to get the value using a point as a key, for example, `sea[Point(3,2)]` + def __getitem__(self, point: Point) -> int: + self._validate_item_indices(point) + + return self._graph[point.y - 1][point.x -1] + + # Allows us to get the value using a point as a key, for example, `sea[Point(3,2)] = 3` + def __setitem__(self, point: Point, value: int) -> None: + self._validate_item_indices(point) + self._graph[point.y - 1][point.x -1] = value + + # Allows us to check if a point exists in the sea for example, `if Point(3,2) in sea:` + def __contains__(self, point: Point) -> bool: + try: + self._validate_item_indices(point) + except IndexError: + return False + + return True + + # Redefines how python will render this object when asked as a str + def __str__(self): + # Display it encoded + return "\n".join([' '.join([str(self._graph[y][x]) + for y in range(self.WIDTH - 1, -1, -1)]) + for x in range(self.WIDTH)]) + + def has_ship(self, ship_code: int) -> bool: + return any(ship_code in row for row in self._graph) + + def count_sunk(self, *ship_codes: int) -> int: + return sum(not self.has_ship(ship_code) for ship_code in ship_codes) + + +class Battle: + def __init__(self) -> None: + self.sea = Sea() + self.place_ship(DESTROYER_LENGTH, 1) + self.place_ship(DESTROYER_LENGTH, 2) + self.place_ship(CRUISER_LENGTH, 3) + self.place_ship(CRUISER_LENGTH, 4) + self.place_ship(AIRCRAFT_CARRIER_LENGTH, 5) + self.place_ship(AIRCRAFT_CARRIER_LENGTH, 6) + self.splashes = 0 + self.hits = 0 + + def _next_target(self) -> Point: + while True: + try: + guess = input('? ') + coordinates = guess.split(',') + + if len(coordinates) != 2: + raise ValueError() + + point = Point(int(coordinates[0]), int(coordinates[1])) + + if point not in self.sea: + raise ValueError() + + return point + except ValueError: + print(f'INVALID. SPECIFY TWO NUMBERS FROM 1 TO {Sea.WIDTH}, SEPARATED BY A COMMA.') + + @property + def splash_hit_ratio(self) -> str: + return f'{self.splashes}/{self.hits}' + + @property + def _is_finished(self) -> bool: + return self.sea.count_sunk(*(i for i in range(1, 7))) == 6 + + def place_ship(self, size: int, ship_code: int) -> None: + while True: + start = Point.random(1, self.sea.WIDTH + 1) + vector = Vector.random() + # Get potential ship points + points = [start + vector * i for i in range(size)] + + if not (all([point in self.sea for point in points]) and + not any([self.sea[point] for point in points])): + # ship out of bounds or crosses other ship, trying again + continue + + # We found a valid spot, so actually place it now + for point in points: + self.sea[point] = ship_code + + break + + + def loop(self): + while True: + target = self._next_target() + target_value = self.sea[target] + + if target_value < 0: + print(f'YOU ALREADY PUT A HOLE IN SHIP NUMBER {abs(target_value)} AT THAT POINT.') + + if target_value <= 0: + print('SPLASH! TRY AGAIN.') + self.splashes += 1 + continue + + print(f'A DIRECT HIT ON SHIP NUMBER {target_value}') + self.hits += 1 + self.sea[target] = -target_value + + if not self.sea.has_ship(target_value): + print('AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS.') + self._display_sunk_report() + + if self._is_finished: + self._display_game_end() + break + + print(f'YOUR CURRENT SPLASH/HIT RATIO IS {self.splash_hit_ratio}') + + def _display_sunk_report(self): + print('SO FAR, THE BAD GUYS HAVE LOST', + f'{self.sea.count_sunk(1, 2)} DESTROYER(S),', + f'{self.sea.count_sunk(3, 4)} CRUISER(S),', + f'AND {self.sea.count_sunk(5, 6)} AIRCRAFT CARRIER(S).') + + def _display_game_end(self): + print('YOU HAVE TOTALLY WIPED OUT THE BAD GUYS\' FLEET ' + f'WITH A FINAL SPLASH/HIT RATIO OF {self.splash_hit_ratio}') + + if not self.splashes: + print('CONGRATULATIONS -- A DIRECT HIT EVERY TIME.') + + print("\n****************************") + + +def main() -> None: + game = Battle() + print(f''' + BATTLE +CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + +THE FOLLOWING CODE OF THE BAD GUYS' FLEET DISPOSITION +HAS BEEN CAPTURED BUT NOT DECODED: + +{game.sea} + +DE-CODE IT AND USE IT IF YOU CAN +BUT KEEP THE DE-CODING METHOD A SECRET. + +START GAME''') + game.loop() + + +if __name__ == "__main__": + main() From e8d753ada84b01229398742925be45e52ac31f77 Mon Sep 17 00:00:00 2001 From: Zev Spitz Date: Fri, 21 Jan 2022 04:31:55 +0200 Subject: [PATCH 08/10] 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 09/10] 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 From 961c76214540501aba45b4de9e171ffc7b9caa34 Mon Sep 17 00:00:00 2001 From: 0phios Date: Fri, 21 Jan 2022 14:19:16 -0400 Subject: [PATCH 10/10] Update AceyDucey.vb - reduce nested do/loop and if/else statements by utilizing subs and functions. --- 01_Acey_Ducey/vbnet/AceyDucey.vb | 242 +++++++++++++++++++++++++++++++ 01_Acey_Ducey/vbnet/Program.vb | 169 +-------------------- 2 files changed, 248 insertions(+), 163 deletions(-) create mode 100644 01_Acey_Ducey/vbnet/AceyDucey.vb diff --git a/01_Acey_Ducey/vbnet/AceyDucey.vb b/01_Acey_Ducey/vbnet/AceyDucey.vb new file mode 100644 index 00000000..8727e6b2 --- /dev/null +++ b/01_Acey_Ducey/vbnet/AceyDucey.vb @@ -0,0 +1,242 @@ +Public Class AceyDucey + ''' + ''' Create a single instance of the Random class to be used + ''' throughout the program. + ''' + Private ReadOnly Property Rnd As New Random() + + ''' + ''' Define a varaible to store the the player balance.
+ ''' Defaults to 0 + '''
+ ''' + ''' Since is a value type, and no value + ''' has been explicitly set, the default value of the type is used. + ''' + Private _balance As Integer + + Public Sub New() + DisplayIntroduction() + End Sub + + ''' + ''' Play multiple games of Acey Ducey until the player chooses to quit. + ''' + Public Sub Play() + Do + PlayGame() + Loop While TryAgain() 'Loop (play again) based on the Boolean value returned by TryAgain + + Console.WriteLine("O.K., HOPE YOU HAD FUN!") + End Sub + + ''' + ''' Play a game of Acey Ducey, which ends when the player balance reaches 0 + ''' + Private Sub PlayGame() + _balance = 100 'At the start of the game, set the player balance to 100 + + Console.WriteLine() + Console.WriteLine($"YOU NOW HAVE {_balance} DOLLARS.") + + Do + PlayTurn() + Loop While _balance > 0 'Continue playing while the user has a balance + + Console.WriteLine() + Console.WriteLine("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") + End Sub + + ''' + ''' Play one turn of Acey Ducey + ''' + ''' + ''' A turn consists of displaying to cards, making a wager + ''' and determining the result (win/lose) + ''' + Private Sub PlayTurn() + Console.WriteLine() + Console.WriteLine("HERE ARE YOUR NEXT TWO CARDS: ") + + Dim cards = GetOrderedCards() + + For Each card In cards + DisplayCard(card) + Next + + Dim wager As Integer = GetWager() + Dim finalCard As Integer = GetCard() + + If wager = 0 Then + Console.WriteLine("CHICKEN!!") + Return + End If + + DisplayCard(finalCard) + + Console.WriteLine() + + '''Check if the value of the final card is between the first and second cards. + ''' + '''The use of AndAlso is used to short-circuit the evaluation of the IF condition. + '''Short-circuiting means that both sides of the condition do not need to be + '''evaluated. In this case, if the left criteria returns FALSE, the right criteria + '''is ignored and the evaluation result is returned as FALSE. + ''' + '''This works because AndAlso requires both condition to return TRUE in order to be + '''evaluated as TRUE. If the first condition is FALSE we already know the evaluation result. + If finalCard >= cards.First() AndAlso finalCard <= cards.Last() Then + Console.WriteLine("YOU WIN!!!") + _balance += wager 'Condensed version of _balance = _balance + wager + Else + Console.WriteLine("SORRY, YOU LOSE.") + _balance -= wager 'Condensed version of _balance = _balance - wager + End If + End Sub + + ''' + ''' Get two cards in ascending order + ''' + ''' + ''' The original version generates two cards (A and B) + ''' If A is greater than or equal to B, both cards are regenerated. + '''

+ ''' This version generates the two cards, but only regenerates A + ''' if A is equal to B. The cards are then returned is ascending order, + ''' ensuring that A is less than B (maintaining the original end result) + '''
+ Private Function GetOrderedCards() As Integer() + '''When declaring fixed size arrays in VB.NET you declare the MAX INDEX of the array + '''and NOT the SIZE (number of elements) of the array. + '''As such, card(1) gives you and array with index 0 and index 1, which means + '''the array stores two elements and not one + Dim cards(1) As Integer + + cards(0) = GetCard() + cards(1) = GetCard() + + 'Perform this action as long as the first card is equal to the second card + While cards(0) = cards(1) + cards(0) = GetCard() + End While + + Array.Sort(cards) 'Sort the values in ascending order + + Return cards + End Function + + ''' + ''' Get a random number (card) ranked 2 to 14 + ''' + Private Function GetCard() As Integer + Return Rnd.Next(2, 15) + End Function + + ''' + ''' Display the face value of the card + ''' + Private Sub DisplayCard(card As Integer) + Dim output As String + + Select Case card + Case 2 To 10 + output = card.ToString() + Case 11 + output = "JACK" + Case 12 + output = "QUEEN" + Case 13 + output = "KING" + Case 14 + output = "ACE" + Case Else + Throw New ArgumentOutOfRangeException(NameOf(card), "Value must be between 2 and 14") + End Select + + Console.WriteLine(output) + End Sub + + ''' + ''' Prompt the user to make a bet + ''' + ''' + ''' The function will not return until a valid bet is made.
+ ''' is used to validate that the user input is a valid + '''
+ Private Function GetWager() As Integer + Dim wager As Integer + Do + Console.WriteLine() + Console.Write("WHAT IS YOUR BET? ") + + Dim input As String = Console.ReadLine() + + '''Determine if the user input is an Integer + '''If it is an Integer, store the value in the variable wager + If Not Integer.TryParse(input, wager) Then + Console.WriteLine("SORRY, I DID'T QUITE GET THAT.") + Continue Do 'restart the loop + End If + + 'Prevent the user from betting more than their current balance + If _balance < wager Then + Console.WriteLine("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") + Console.WriteLine($"YOU HAVE ONLY {_balance} DOLLARS TO BET.") + Continue Do 'restart the loop + End If + + 'Prevent the user from betting negative values + If wager < 0 Then + Console.WriteLine("FUNNY GUY! YOU CANNOT MAKE A NEGATIVE BET.") + Continue Do 'restart the loop + End If + + Exit Do 'If we get to this line, exit the loop as all above validations passed + Loop + + Return wager + End Function + + ''' + ''' Prompt the user to try again + ''' + ''' + ''' This function will not return until a valid reponse is given + ''' + Private Function TryAgain() As Boolean + Dim response As String + Do + Console.Write("TRY AGAIN (YES OR NO) ") + + response = Console.ReadLine() + + If response.Equals("YES", StringComparison.OrdinalIgnoreCase) Then Return True + If response.Equals("NO", StringComparison.OrdinalIgnoreCase) Then Return False + + Console.WriteLine("SORRY, I DID'T QUITE GET THAT.") + Loop + End Function + + ''' + ''' Display the opening title and instructions + ''' + ''' + ''' Refer to + ''' + ''' Interpolated Strings + ''' documentation for the use of $ and { } with strings + ''' + Private Sub DisplayIntroduction() + Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 10)}ACEY DUCEY CARD GAME") + Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 21)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + Console.WriteLine("") + Console.WriteLine("") + Console.WriteLine("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER") + Console.WriteLine("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") + Console.WriteLine("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") + Console.WriteLine("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") + Console.WriteLine("A VALUE BETWEEN THE FIRST TWO.") + Console.WriteLine("IF YOU DO NOT WANT TO BET, INPUT A 0") + Console.WriteLine("") + End Sub +End Class diff --git a/01_Acey_Ducey/vbnet/Program.vb b/01_Acey_Ducey/vbnet/Program.vb index bfa518ca..2f82fd60 100644 --- a/01_Acey_Ducey/vbnet/Program.vb +++ b/01_Acey_Ducey/vbnet/Program.vb @@ -6,173 +6,16 @@ Imports System ''' The structural changes primarily consist of replacing the many GOTOs with ''' Do/Loop constructs to force the continual execution of the program. ''' -''' Because modern Basic allows multi-line If/Then blocks, many GOTO jumps were -''' able to be eliminated and the logic was able to be moved to more relevant areas, -''' For example, the increment/decrement of the player's balance could be in the same -''' area as the notification of win/loss. +''' Some modern improvements were added, primarily the inclusion of a multiple +''' subroutines and functions, which eliminates repeated logic and reduces +''' then need for nested loops. ''' -''' Some modern improvements were added, primarily the inclusion of a function, which -''' eliminated a thrice-repeated block of logic to display the card value. The archaic -''' RND function is greatly simplified with the .NET Framework's Random class. +''' The archaic RND function is greatly simplified with the .NET Framework's Random class. ''' ''' Elementary comments are provided for non-programmers or novices. ''' Module Program - Sub Main(args As String()) - ' These are the variables that will hold values during the program's execution - Dim input As String - Dim rnd As New Random ' You can create a new instance of an object during declaration - Dim currentBalance As Integer = 100 ' You can set a initial value at declaration - Dim currentWager As Integer - Dim cardA, cardB, cardC As Integer ' You can specify multiple variables of the same type in one declaration statement - - ' Display the opening title and instructions - ' Use a preceding $ to insert calculated values within the string using {} - Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 10)}ACEY DUCEY CARD GAME") - Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 21)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") - Console.WriteLine("") - Console.WriteLine("") - Console.WriteLine("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER") - Console.WriteLine("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP") - Console.WriteLine("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING") - Console.WriteLine("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE") - Console.WriteLine("A VALUE BETWEEN THE FIRST TWO.") - Console.WriteLine("IF YOU DO NOT WANT TO BET, INPUT A 0") - - Do ' This loop continues as long as the player wants to keep playing - - Do ' This loop continues as long as the player has money to play - - Console.WriteLine("") - Console.WriteLine($"YOU NOW HAVE {currentBalance} DOLLARS.") - Console.WriteLine("") - - Console.WriteLine("HERE ARE YOUR NEXT TWO CARDS:") - - ' We need to ensure that card B is a higher value for our later comparison, - ' so we will loop until we have two cards that meet this criteria - Do - cardA = rnd.Next(2, 14) - cardB = rnd.Next(2, 14) - - Loop While cardA > cardB - - ' We use a function to display the text value of the numeric card value - ' because we do this 3 times and a function reduces repetition of code - Console.WriteLine(DisplayCard(cardA)) - Console.WriteLine(DisplayCard(cardB)) - - Do ' This loop continues until the player provides a valid wager value - Console.WriteLine("") - Console.WriteLine("WHAT IS YOUR BET") - - currentWager = 0 - input = Console.ReadLine - - ' Any input from the console is a string, but we require a number. - ' Test the input to make sure it is a numeric value. - If Integer.TryParse(input, currentWager) Then - ' Test to ensure the player has not wagered more than their balance - If currentWager > currentBalance Then - Console.WriteLine("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.") - Console.WriteLine($"YOU HAVE ONLY {currentBalance} DOLLARS TO BET.") - - Else - ' The player has provided a numeric value that is less/equal to their balance, - ' exit the loop and continue play - Exit Do - - End If ' check player balance - - End If ' check numeric input - - Loop ' wager loop - - ' If the player is wagering, draw the third card, otherwise, mock them. - If currentWager > 0 Then - cardC = rnd.Next(2, 14) - - Console.WriteLine(DisplayCard(cardC)) - - ' The effort we made to have two cards in numeric order earlier makes this check easier, - ' otherwise we would have to have a second check in the opposite direction - If cardC < cardA OrElse cardC >= cardB Then - Console.WriteLine("SORRY, YOU LOSE") - currentBalance -= currentWager ' Shorthand code to decrement a number (currentBalance=currentBalance - currentWager) - - Else - Console.WriteLine("YOU WIN!!!") - currentBalance += currentWager ' Shorthand code to increment a number (currentBalance=currentBalance + currentWager) - - End If - - Else - Console.WriteLine("CHICKEN!!") - Console.WriteLine("") - - End If - - Loop While currentBalance > 0 ' loop as long as the player has money - - ' At this point, the player has no money (currentBalance=0). Inform them of such. - Console.WriteLine("") - Console.WriteLine("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") - Console.WriteLine("") - Console.WriteLine("") - - ' We will loop to ensure the player provides some answer. - Do - Console.WriteLine("TRY AGAIN (YES OR NO)") - Console.WriteLine("") - - input = Console.ReadLine - - Loop While String.IsNullOrWhiteSpace(input) - - ' We will assume that the player wants to play again only if they answer yes. - ' (yeah and ya are valid as well, because we only check the first letter) - If input.Substring(0, 1).Equals("y", StringComparison.CurrentCultureIgnoreCase) Then ' This allows upper and lower case to be entered. - currentBalance = 100 ' Reset the players balance before restarting - - Else - ' Exit the outer loop which will end the game. - Exit Do - - End If - - Loop ' The full game loop - - Console.WriteLine("O.K., HOPE YOU HAD FUN!") - + Sub Main() + Call New AceyDucey().Play() End Sub - - ' This function is called for each of the 3 cards used in the game. - ' The input and the output are both consistent, making it a good candidate for a function. - Private Function DisplayCard(value As Integer) As String - ' We check the value of the input and run a block of code for whichever - ' evaluation matches - Select Case value - Case 2 To 10 ' Case statements can be ranges of values, also multiple values (Case 2,3,4,5,6,7,8,9,10) - Return value.ToString - - Case 11 - Return "JACK" - - Case 12 - Return "QUEEN" - - Case 13 - Return "KING" - - Case 14 - Return "ACE" - - End Select - - ' Although we have full knowledge of the program and never plan to send an invalid - ' card value, it's important to provide a message for the next developer who won't - Throw New ArgumentOutOfRangeException("Card value must be between 2 and 14") - - End Function - End Module