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