mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-03 00:30:36 -08:00
Merge branch 'main' of https://github.com/coding-horror/basic-computer-games into main
This commit is contained in:
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
242
01_Acey_Ducey/vbnet/AceyDucey.vb
Normal file
242
01_Acey_Ducey/vbnet/AceyDucey.vb
Normal 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
|
||||
@@ -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
|
||||
|
||||
27
03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj
Normal file
27
03_Animal/vbnet/Animal.Tests/Animal.Tests.vbproj
Normal 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>
|
||||
65
03_Animal/vbnet/Animal.Tests/MockConsole.vb
Normal file
65
03_Animal/vbnet/Animal.Tests/MockConsole.vb
Normal 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
|
||||
80
03_Animal/vbnet/Animal.Tests/TestContainer.vb
Normal file
80
03_Animal/vbnet/Animal.Tests/TestContainer.vb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
<RootNamespace>Animal</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
<OptionStrict>On</OptionStrict>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
29
03_Animal/vbnet/Animal/Branch.vb
Normal file
29
03_Animal/vbnet/Animal/Branch.vb
Normal 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
|
||||
152
03_Animal/vbnet/Animal/Game.vb
Normal file
152
03_Animal/vbnet/Animal/Game.vb
Normal 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
|
||||
6
03_Animal/vbnet/Animal/Program.vb
Normal file
6
03_Animal/vbnet/Animal/Program.vb
Normal file
@@ -0,0 +1,6 @@
|
||||
Module Program
|
||||
Sub Main()
|
||||
Dim game As New Game(New ConsoleAdapter)
|
||||
game.BeginLoop()
|
||||
End Sub
|
||||
End Module
|
||||
29
03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb
Normal file
29
03_Animal/vbnet/Animal/Shared/ConsoleAdapter.vb
Normal 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
|
||||
9
03_Animal/vbnet/Animal/Shared/ConsoleAdapterBase.vb
Normal file
9
03_Animal/vbnet/Animal/Shared/ConsoleAdapterBase.vb
Normal 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
|
||||
50
03_Animal/vbnet/Animal/Shared/Extensions.vb
Normal file
50
03_Animal/vbnet/Animal/Shared/Extensions.vb
Normal 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
|
||||
@@ -3,7 +3,6 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>BasicComputerGames.Bagels</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>banner</RootNamespace>
|
||||
<RootNamespace>Banner</RootNamespace>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>batnum</RootNamespace>
|
||||
<RootNamespace>Batnum</RootNamespace>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
203
09_Battle/python/battle_oo.py
Normal file
203
09_Battle/python/battle_oo.py
Normal 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()
|
||||
@@ -3,7 +3,6 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>_21_calendar</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>word</RootNamespace>
|
||||
<RootNamespace>Word</RootNamespace>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user