This commit is contained in:
Alex Gomez
2022-01-21 17:41:05 -06:00
22 changed files with 973 additions and 205 deletions

View File

@@ -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<T1, T2>(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index));
public static bool None<T>(this IEnumerable<T> src, Func<T, bool>? 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")]

View File

@@ -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.
@@ -216,8 +217,8 @@ void generateMissingProjs() {
void checkProjects() {
foreach (var info in infos) {
WriteLine(info.LangPath);
printProjectWarnings(info);
WriteLine();
}
}
@@ -231,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") {
@@ -266,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()) {
@@ -284,6 +290,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
}

View File

@@ -0,0 +1,242 @@
Public Class AceyDucey
''' <summary>
''' Create a single instance of the Random class to be used
''' throughout the program.
''' </summary>
Private ReadOnly Property Rnd As New Random()
''' <summary>
''' Define a varaible to store the the player balance. <br/>
''' Defaults to 0
''' </summary>
''' <remarks>
''' Since <see cref="Integer"/> is a value type, and no value
''' has been explicitly set, the default value of the type is used.
''' </remarks>
Private _balance As Integer
Public Sub New()
DisplayIntroduction()
End Sub
''' <summary>
''' Play multiple games of Acey Ducey until the player chooses to quit.
''' </summary>
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
''' <summary>
''' Play a game of Acey Ducey, which ends when the player balance reaches 0
''' </summary>
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
''' <summary>
''' Play one turn of Acey Ducey
''' </summary>
''' <remarks>
''' A turn consists of displaying to cards, making a wager
''' and determining the result (win/lose)
''' </remarks>
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
''' <summary>
''' Get two cards in ascending order
''' </summary>
''' <remarks>
''' The original version generates two cards (A and B)
''' If A is greater than or equal to B, both cards are regenerated.
''' <br/><br/>
''' 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)
''' </remarks>
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
''' <summary>
''' Get a random number (card) ranked 2 to 14
''' </summary>
Private Function GetCard() As Integer
Return Rnd.Next(2, 15)
End Function
''' <summary>
''' Display the face value of the card
''' </summary>
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
''' <summary>
''' Prompt the user to make a bet
''' </summary>
''' <remarks>
''' The function will not return until a valid bet is made. <br/>
''' <see cref="Int32.TryParse(String, ByRef Integer)"/> is used to validate that the user input is a valid <see cref="Integer"/>
''' </remarks>
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
''' <summary>
''' Prompt the user to try again
''' </summary>
''' <remarks>
''' This function will not return until a valid reponse is given
''' </remarks>
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
''' <summary>
''' Display the opening title and instructions
''' </summary>
''' <remarks>
''' Refer to
''' <see href="https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/strings/interpolated-strings">
''' Interpolated Strings
''' </see> documentation for the use of $ and { } with strings
''' </remarks>
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

View File

@@ -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.
''' </summary>
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

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Animal.Tests</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<OptionStrict>On</OptionStrict>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Animal\Animal.vbproj" />
</ItemGroup>
</Project>

View File

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

View File

@@ -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
''' <summary>Test LIST variants</summary>
<Theory>
<InlineData("LIST")>
<InlineData("list")>
<InlineData("List")>
<InlineData("lIST")>
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
'' <summary>Test YES variants</summary>
<Theory>
<MemberData(NameOf(YesVariants))>
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
'' <summary>Test NO variants</summary>
<Theory>
<MemberData(NameOf(NoVariants))>
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

View File

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

View File

@@ -4,5 +4,6 @@
<RootNamespace>Animal</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>16.9</LangVersion>
<OptionStrict>On</OptionStrict>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,29 @@
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
' Allows walking all the descendants recursively
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

View File

@@ -0,0 +1,152 @@
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},
{"true", True},
{"t", True},
{"1", True},
{"no", False},
{"n", False},
{"false", False},
{"f", False},
{"0", False}
}
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"},
.No = New Branch With {.Text = "BIRD"}
}
''' <summary>Reduces a string or console input to True, False or Nothing. Case-insensitive.</summary>
''' <param name="s">Optional String to reduce via the same logic. If not passed in, will use console.ReadLine</param>
''' <returns>
''' Returns True for a "yes" response (yes, y, true, t, 1) and False for a "no" response (no, n, false, f, 0).<br/>
''' Returns Nothing if the response doesn't match any of these.
''' </returns>
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()
' Print the program heading
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
' 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)
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
' 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,
currentBranch.No
)
Loop
' Now we're at an end branch
console.Write($"IS IT A {currentBranch.Text}? ")
ynResponse = GetYesNo()
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
Else
currentBranch.No = newBranch
currentBranch.Yes = currentBranchCopy
End If
' TODO how do we exit?
Loop
End Sub
End Class

View File

@@ -0,0 +1,6 @@
Module Program
Sub Main()
Dim game As New Game(New ConsoleAdapter)
game.BeginLoop()
End Sub
End Module

View File

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

View File

@@ -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)
''' <summary>Implementations should always return a String without leading or trailing whitespace, never Nothng</summary>
Public MustOverride Function ReadLine() As String
End Class

View File

@@ -0,0 +1,50 @@
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension> 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
<Extension> 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
<Extension> 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
<Extension> 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
<Extension> 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
<Extension> 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
<Extension> 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

View File

@@ -3,7 +3,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>BasicComputerGames.Bagels</RootNamespace>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,8 +2,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>banner</RootNamespace>
<RootNamespace>Banner</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup>
</Project>

View File

@@ -2,8 +2,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>batnum</RootNamespace>
<RootNamespace>Batnum</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup>
</Project>

View File

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

View File

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

View File

@@ -3,7 +3,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_21_calendar</RootNamespace>
</PropertyGroup>
</Project>

View File

@@ -2,8 +2,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>word</RootNamespace>
<RootNamespace>Word</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>16.9</LangVersion>
</PropertyGroup>
</Project>