mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-06-27 08:53:57 -07:00
Merge branch 'main' of https://github.com/masykur/basic-computer-games into main
This commit is contained in:
+20
@@ -1,3 +1,17 @@
|
||||
.local/
|
||||
.vscode/
|
||||
.gradle/
|
||||
node_modules/
|
||||
buildJvm/bin
|
||||
buildJvm/*/build/
|
||||
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.metadata
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
*.class
|
||||
*/.vs
|
||||
*.suo
|
||||
@@ -13,6 +27,12 @@ obj/
|
||||
out/
|
||||
|
||||
*.py[co]
|
||||
|
||||
Pipfile
|
||||
|
||||
.DS_Store
|
||||
.vs/
|
||||
**/target/
|
||||
Cargo.lock
|
||||
**/*.rs.bk
|
||||
/target
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
root = true
|
||||
|
||||
|
||||
[*.{cs,vb}]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent
|
||||
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_operator_placement_when_wrapping = end_of_line
|
||||
dotnet_style_prefer_auto_properties = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_private_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_members_should_be_pascal_case.symbols = non_private_members
|
||||
dotnet_naming_rule.non_private_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.private_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.private_members_should_be_pascal_case.symbols = private_members
|
||||
dotnet_naming_rule.private_members_should_be_pascal_case.style = camel_case
|
||||
|
||||
|
||||
# Symbols for use with naming rules
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, delegate
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_private_members.applicable_kinds = property, method, field, event
|
||||
dotnet_naming_symbols.non_private_members.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
|
||||
dotnet_naming_symbols.private_members.applicable_kinds = property, method, field, event
|
||||
dotnet_naming_symbols.private_members.applicable_accessibilities = private
|
||||
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.camel_case.required_prefix =
|
||||
dotnet_naming_style.camel_case.required_suffix =
|
||||
dotnet_naming_style.camel_case.word_separator =
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
|
||||
[*.cs]
|
||||
csharp_new_line_before_catch = false
|
||||
csharp_new_line_before_else = false
|
||||
csharp_new_line_before_finally = false
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
csharp_prefer_braces = true:warning
|
||||
|
||||
csharp_style_expression_bodied_constructors = true:suggestion
|
||||
csharp_style_expression_bodied_methods = true:suggestion
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
csharp_preferred_modifier_order = internal,protected,public,private,static,readonly,abstract,override,sealed,virtual:suggestion
|
||||
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
|
||||
[*.vb]
|
||||
visual_basic_preferred_modifier_order = partial,default,private,protected,public,friend,notoverridable,overridable,mustoverride,overloads,overrides,mustinherit,notinheritable,static,shared,shadows,readonly,writeonly,dim,const,withevents,widening,narrowing,custom,async,iterator:silent
|
||||
visual_basic_style_unused_value_assignment_preference = unused_local_variable:suggestion
|
||||
visual_basic_style_unused_value_expression_statement_preference = unused_local_variable:silent
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetUtils", "DotnetUtils\DotnetUtils.csproj", "{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {30FCF56E-4E83-42F8-AB43-A52C86C7C9B4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static System.IO.Path;
|
||||
|
||||
namespace DotnetUtils;
|
||||
|
||||
public static class Extensions {
|
||||
public static IEnumerable<TResult> SelectT<T1, T2, TResult>(this IEnumerable<(T1, T2)> src, Func<T1, T2, TResult> selector) =>
|
||||
src.Select(x => selector(x.Item1, x.Item2));
|
||||
public static IEnumerable<TResult> SelectT<T1, T2, T3, TResult>(this IEnumerable<(T1, T2, T3)> src, Func<T1, T2, T3, TResult> selector) =>
|
||||
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")]
|
||||
public static string? RelativePath(this string? path, string? rootPath) {
|
||||
if (
|
||||
path.IsNullOrWhitespace() ||
|
||||
rootPath.IsNullOrWhitespace()
|
||||
) { return path; }
|
||||
|
||||
path = path.TrimEnd('\\'); // remove trailing backslash, if present
|
||||
return GetRelativePath(rootPath, path.TrimEnd('\\'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Xml.Linq;
|
||||
using static System.Console;
|
||||
|
||||
namespace DotnetUtils;
|
||||
|
||||
public static class Functions {
|
||||
public static string? getValue(string path, params string[] names) {
|
||||
if (names.Length == 0) { throw new InvalidOperationException(); }
|
||||
var parent = XDocument.Load(path).Element("Project")?.Element("PropertyGroup");
|
||||
return getValue(parent, names);
|
||||
}
|
||||
|
||||
public static string? getValue(XElement? parent, params string[] names) {
|
||||
if (names.Length == 0) { throw new InvalidOperationException(); }
|
||||
XElement? elem = null;
|
||||
foreach (var name in names) {
|
||||
elem = parent?.Element(name);
|
||||
if (elem != null) { break; }
|
||||
}
|
||||
return elem?.Value;
|
||||
}
|
||||
|
||||
public static int getChoice(int maxValue) => getChoice(0, maxValue);
|
||||
|
||||
public static int getChoice(int minValue, int maxValue) {
|
||||
int result;
|
||||
do {
|
||||
Write("? ");
|
||||
} while (!int.TryParse(ReadLine(), out result) || result < minValue || result > maxValue);
|
||||
//WriteLine();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DotnetUtils;
|
||||
|
||||
public static class Globals {
|
||||
public static readonly Dictionary<string, (string codefileExtension, string projExtension)> LangData = new() {
|
||||
{ "csharp", ("cs", "csproj") },
|
||||
{ "vbnet", ("vb", "vbproj") }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace DotnetUtils;
|
||||
|
||||
public static class Methods {
|
||||
public static ProcessResult RunProcess(string filename, string arguments) {
|
||||
var process = new Process() {
|
||||
StartInfo = {
|
||||
FileName = filename,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
return RunProcess(process);
|
||||
}
|
||||
|
||||
public static ProcessResult RunProcess(Process process, string input = "") {
|
||||
var (output, error) = ("", "");
|
||||
var (redirectOut, redirectErr) = (
|
||||
process.StartInfo.RedirectStandardOutput,
|
||||
process.StartInfo.RedirectStandardError
|
||||
);
|
||||
if (redirectOut) {
|
||||
process.OutputDataReceived += (s, ea) => output += ea.Data + "\n";
|
||||
}
|
||||
if (redirectErr) {
|
||||
process.ErrorDataReceived += (s, ea) => error += ea.Data + "\n";
|
||||
}
|
||||
|
||||
if (!process.Start()) {
|
||||
throw new InvalidOperationException();
|
||||
};
|
||||
|
||||
if (redirectOut) { process.BeginOutputReadLine(); }
|
||||
if (redirectErr) { process.BeginErrorReadLine(); }
|
||||
if (!string.IsNullOrEmpty(input)) {
|
||||
process.StandardInput.WriteLine(input);
|
||||
process.StandardInput.Close();
|
||||
}
|
||||
process.WaitForExit();
|
||||
return new ProcessResult(process.ExitCode, output, error);
|
||||
}
|
||||
|
||||
public static Task<ProcessResult> RunProcessAsync(Process process, string input = "") {
|
||||
var tcs = new TaskCompletionSource<ProcessResult>();
|
||||
var (output, error) = ("", "");
|
||||
var (redirectOut, redirectErr) = (
|
||||
process.StartInfo.RedirectStandardOutput,
|
||||
process.StartInfo.RedirectStandardError
|
||||
);
|
||||
|
||||
process.Exited += (s, e) => tcs.SetResult(new ProcessResult(process.ExitCode, output, error));
|
||||
|
||||
if (redirectOut) {
|
||||
process.OutputDataReceived += (s, ea) => output += ea.Data + "\n";
|
||||
}
|
||||
if (redirectErr) {
|
||||
process.ErrorDataReceived += (s, ea) => error += ea.Data + "\n";
|
||||
}
|
||||
|
||||
if (!process.Start()) {
|
||||
// what happens to the Exited event if process doesn't start successfully?
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (redirectOut) { process.BeginOutputReadLine(); }
|
||||
if (redirectErr) { process.BeginErrorReadLine(); }
|
||||
if (!string.IsNullOrEmpty(input)) {
|
||||
process.StandardInput.WriteLine(input);
|
||||
process.StandardInput.Close();
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ProcessResult(int ExitCode, string StdOut, string StdErr) {
|
||||
public override string? ToString() =>
|
||||
StdOut +
|
||||
(StdOut is not (null or "") && ExitCode > 0 ? "\n" : "") +
|
||||
(ExitCode != 0 ?
|
||||
$"{ExitCode}\n{StdErr}" :
|
||||
"");
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using static System.IO.Directory;
|
||||
using static System.IO.Path;
|
||||
using static DotnetUtils.Globals;
|
||||
|
||||
namespace DotnetUtils;
|
||||
|
||||
public record PortInfo(
|
||||
string GamePath, string FolderName, int Index, string GameName,
|
||||
string LangPath, string Lang, string Ext, string ProjExt,
|
||||
string[] CodeFiles, string[] Slns, string[] Projs
|
||||
) {
|
||||
|
||||
private static readonly EnumerationOptions enumerationOptions = new() {
|
||||
RecurseSubdirectories = true,
|
||||
MatchType = MatchType.Simple,
|
||||
MatchCasing = MatchCasing.CaseInsensitive
|
||||
};
|
||||
|
||||
// .NET namespaces cannot have a digit as the first character
|
||||
// For games whose name starts with a digit, we map the name to a specific string
|
||||
private static readonly Dictionary<string, string> specialGameNames = new() {
|
||||
{ "3-D_Plot", "Plot" },
|
||||
{ "3-D_Tic-Tac-Toe", "ThreeDTicTacToe" },
|
||||
{ "23_Matches", "TwentyThreeMatches"}
|
||||
};
|
||||
|
||||
public static PortInfo? Create(string gamePath, string langKeyword) {
|
||||
var folderName = GetFileName(gamePath);
|
||||
var parts = folderName.Split('_', 2);
|
||||
|
||||
if (parts.Length <= 1) { return null; }
|
||||
|
||||
var (index, gameName) = (
|
||||
int.TryParse(parts[0], out var n) && n > 0 ? // ignore utilities folder
|
||||
n :
|
||||
(int?)null,
|
||||
specialGameNames.TryGetValue(parts[1], out var specialName) ?
|
||||
specialName :
|
||||
parts[1].Replace("_", "").Replace("-", "")
|
||||
);
|
||||
|
||||
if (index is null || gameName is null) { return null; }
|
||||
|
||||
var (ext, projExt) = LangData[langKeyword];
|
||||
var langPath = Combine(gamePath, langKeyword);
|
||||
var codeFiles =
|
||||
GetFiles(langPath, $"*.{ext}", enumerationOptions)
|
||||
.Where(x => !x.Contains("\\bin\\") && !x.Contains("\\obj\\"))
|
||||
.ToArray();
|
||||
|
||||
return new PortInfo(
|
||||
gamePath, folderName, index.Value, gameName,
|
||||
langPath, langKeyword, ext, projExt,
|
||||
codeFiles,
|
||||
GetFiles(langPath, "*.sln", enumerationOptions),
|
||||
GetFiles(langPath, $"*.{projExt}", enumerationOptions)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using static System.IO.Directory;
|
||||
using static DotnetUtils.Globals;
|
||||
|
||||
namespace DotnetUtils;
|
||||
|
||||
public static class PortInfos {
|
||||
public static readonly string Root;
|
||||
|
||||
static PortInfos() {
|
||||
Root = GetParent(Assembly.GetEntryAssembly()!.Location)!.FullName;
|
||||
Root = Root[..Root.IndexOf(@"\00_Utilities")];
|
||||
|
||||
Get = GetDirectories(Root)
|
||||
.SelectMany(gamePath => LangData.Keys.Select(keyword => (gamePath, keyword)))
|
||||
.SelectT((gamePath, keyword) => PortInfo.Create(gamePath, keyword))
|
||||
.Where(x => x is not null)
|
||||
.ToArray()!;
|
||||
}
|
||||
|
||||
public static readonly PortInfo[] Get;
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
using System.Xml.Linq;
|
||||
using DotnetUtils;
|
||||
using static System.Console;
|
||||
using static System.IO.Path;
|
||||
using static DotnetUtils.Methods;
|
||||
using static DotnetUtils.Functions;
|
||||
|
||||
var infos = PortInfos.Get;
|
||||
|
||||
var actions = new (Action action, string description)[] {
|
||||
(printInfos, "Output information -- solution, project, and code files"),
|
||||
(missingSln, "Output missing sln"),
|
||||
(unexpectedSlnName, "Output misnamed sln"),
|
||||
(multipleSlns, "Output multiple sln files"),
|
||||
(missingProj, "Output missing project file"),
|
||||
(unexpectedProjName, "Output misnamed project files"),
|
||||
(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"),
|
||||
(generateMissingProjs, "Generate project files when missing")
|
||||
};
|
||||
|
||||
foreach (var (_, description, index) in actions.WithIndex()) {
|
||||
WriteLine($"{index}: {description}");
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
actions[getChoice(actions.Length - 1)].action();
|
||||
|
||||
void printSlns(PortInfo pi) {
|
||||
switch (pi.Slns.Length) {
|
||||
case 0:
|
||||
WriteLine("No sln");
|
||||
break;
|
||||
case 1:
|
||||
WriteLine($"Solution: {pi.Slns[0].RelativePath(pi.LangPath)}");
|
||||
break;
|
||||
case > 1:
|
||||
WriteLine("Solutions:");
|
||||
foreach (var sln in pi.Slns) {
|
||||
Write(sln.RelativePath(pi.LangPath));
|
||||
WriteLine();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printProjs(PortInfo pi) {
|
||||
switch (pi.Projs.Length) {
|
||||
case 0:
|
||||
WriteLine("No project");
|
||||
break;
|
||||
case 1:
|
||||
WriteLine($"Project: {pi.Projs[0].RelativePath(pi.LangPath)}");
|
||||
break;
|
||||
case > 1:
|
||||
WriteLine("Projects:");
|
||||
foreach (var proj in pi.Projs) {
|
||||
Write(proj.RelativePath(pi.LangPath));
|
||||
WriteLine();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printInfos() {
|
||||
foreach (var item in infos) {
|
||||
WriteLine(item.LangPath);
|
||||
WriteLine();
|
||||
|
||||
printSlns(item);
|
||||
WriteLine();
|
||||
|
||||
printProjs(item);
|
||||
WriteLine();
|
||||
|
||||
// get code files
|
||||
foreach (var file in item.CodeFiles) {
|
||||
WriteLine(file.RelativePath(item.LangPath));
|
||||
}
|
||||
WriteLine(new string('-', 50));
|
||||
}
|
||||
}
|
||||
|
||||
void missingSln() {
|
||||
var data = infos.Where(x => x.Slns.None()).ToArray();
|
||||
foreach (var item in data) {
|
||||
WriteLine(item.LangPath);
|
||||
}
|
||||
WriteLine();
|
||||
WriteLine($"Count: {data.Length}");
|
||||
}
|
||||
|
||||
void unexpectedSlnName() {
|
||||
var counter = 0;
|
||||
foreach (var item in infos) {
|
||||
if (item.Slns.None()) { continue; }
|
||||
|
||||
var expectedSlnName = $"{item.GameName}.sln";
|
||||
if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName), StringComparer.InvariantCultureIgnoreCase)) { continue; }
|
||||
|
||||
counter += 1;
|
||||
WriteLine(item.LangPath);
|
||||
WriteLine($"Expected: {expectedSlnName}");
|
||||
|
||||
printSlns(item);
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
WriteLine($"Count: {counter}");
|
||||
}
|
||||
|
||||
void multipleSlns() {
|
||||
var data = infos.Where(x => x.Slns.Length > 1).ToArray();
|
||||
foreach (var item in data) {
|
||||
WriteLine(item.LangPath);
|
||||
printSlns(item);
|
||||
}
|
||||
WriteLine();
|
||||
WriteLine($"Count: {data.Length}");
|
||||
}
|
||||
|
||||
void missingProj() {
|
||||
var data = infos.Where(x => x.Projs.None()).ToArray();
|
||||
foreach (var item in data) {
|
||||
WriteLine(item.LangPath);
|
||||
}
|
||||
WriteLine();
|
||||
WriteLine($"Count: {data.Length}");
|
||||
}
|
||||
|
||||
void unexpectedProjName() {
|
||||
var counter = 0;
|
||||
foreach (var item in infos) {
|
||||
if (item.Projs.None()) { continue; }
|
||||
|
||||
var expectedProjName = $"{item.GameName}.{item.ProjExt}";
|
||||
if (item.Projs.Contains(Combine(item.LangPath, expectedProjName))) { continue; }
|
||||
|
||||
counter += 1;
|
||||
WriteLine(item.LangPath);
|
||||
WriteLine($"Expected: {expectedProjName}");
|
||||
|
||||
printProjs(item);
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
WriteLine($"Count: {counter}");
|
||||
}
|
||||
|
||||
void multipleProjs() {
|
||||
var data = infos.Where(x => x.Projs.Length > 1).ToArray();
|
||||
foreach (var item in data) {
|
||||
WriteLine(item.LangPath);
|
||||
WriteLine();
|
||||
printProjs(item);
|
||||
}
|
||||
WriteLine();
|
||||
WriteLine($"Count: {data.Length}");
|
||||
}
|
||||
|
||||
void generateMissingSlns() {
|
||||
foreach (var item in infos.Where(x => x.Slns.None())) {
|
||||
var result = RunProcess("dotnet", $"new sln -n {item.GameName} -o {item.LangPath}");
|
||||
WriteLine(result);
|
||||
|
||||
var slnFullPath = Combine(item.LangPath, $"{item.GameName}.sln");
|
||||
foreach (var proj in item.Projs) {
|
||||
result = RunProcess("dotnet", $"sln {slnFullPath} add {proj}");
|
||||
WriteLine(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generateMissingProjs() {
|
||||
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.
|
||||
|
||||
var projText = item.Lang switch {
|
||||
"csharp" => @"<Project Sdk=""Microsoft.NET.Sdk"">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
",
|
||||
"vbnet" => @$"<Project Sdk=""Microsoft.NET.Sdk"">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>{item.GameName}</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
",
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
var projFullPath = Combine(item.LangPath, $"{item.GameName}.{item.ProjExt}");
|
||||
File.WriteAllText(projFullPath, projText);
|
||||
|
||||
if (item.Slns.Length == 1) {
|
||||
var result = RunProcess("dotnet", $"sln {item.Slns[0]} add {projFullPath}");
|
||||
WriteLine(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkProjects() {
|
||||
foreach (var info in infos) {
|
||||
WriteLine(info.LangPath);
|
||||
printProjectWarnings(info);
|
||||
}
|
||||
}
|
||||
|
||||
void printProjectWarnings(PortInfo info) {
|
||||
foreach (var proj in info.Projs) {
|
||||
var warnings = new List<string>();
|
||||
var parent = XDocument.Load(proj).Element("Project")?.Element("PropertyGroup");
|
||||
|
||||
var (
|
||||
framework,
|
||||
nullable,
|
||||
implicitUsing,
|
||||
rootNamespace,
|
||||
langVersion,
|
||||
optionStrict
|
||||
) = (
|
||||
getValue(parent, "TargetFramework", "TargetFrameworks"),
|
||||
getValue(parent, "Nullable"),
|
||||
getValue(parent, "ImplicitUsings"),
|
||||
getValue(parent, "RootNamespace"),
|
||||
getValue(parent, "LangVersion"),
|
||||
getValue(parent, "OptionStrict")
|
||||
);
|
||||
|
||||
if (framework != "net6.0") {
|
||||
warnings.Add($"Target: {framework}");
|
||||
}
|
||||
|
||||
if (info.Lang == "csharp") {
|
||||
if (nullable != "enable") {
|
||||
warnings.Add($"Nullable: {nullable}");
|
||||
}
|
||||
if (implicitUsing != "enable") {
|
||||
warnings.Add($"ImplicitUsings: {implicitUsing}");
|
||||
}
|
||||
if (rootNamespace != null && rootNamespace != info.GameName) {
|
||||
warnings.Add($"RootNamespace: {rootNamespace}");
|
||||
}
|
||||
if (langVersion != "10") {
|
||||
warnings.Add($"LangVersion: {langVersion}");
|
||||
}
|
||||
}
|
||||
|
||||
if (info.Lang == "vbnet") {
|
||||
if (rootNamespace != info.GameName) {
|
||||
warnings.Add($"RootNamespace: {rootNamespace}");
|
||||
}
|
||||
if (langVersion != "16.9") {
|
||||
warnings.Add($"LangVersion: {langVersion}");
|
||||
}
|
||||
if (optionStrict != "On") {
|
||||
warnings.Add($"OptionStrict: {optionStrict}");
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.Any()) {
|
||||
WriteLine(proj.RelativePath(info.LangPath));
|
||||
WriteLine(string.Join("\n", warnings));
|
||||
WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkExecutableProject() {
|
||||
foreach (var item in infos) {
|
||||
if (item.Projs.All(proj => getValue(proj, "OutputType") != "Exe")) {
|
||||
WriteLine($"{item.LangPath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void printPortInfo() {
|
||||
// prompt for port number
|
||||
Write("Enter number from 1 to 96 ");
|
||||
var index = getChoice(1, 96);
|
||||
|
||||
Write("Enter 0 for C#, 1 for VB ");
|
||||
var lang = getChoice(1) switch {
|
||||
0 => "csharp",
|
||||
1 => "vbnet",
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
WriteLine();
|
||||
|
||||
var info = infos.Single(x => x.Index == index && x.Lang == lang);
|
||||
|
||||
WriteLine(info.LangPath);
|
||||
WriteLine(new string('-', 50));
|
||||
|
||||
// print solutions
|
||||
printSlns(info);
|
||||
|
||||
// mismatched solution name/location? (expected x)
|
||||
var expectedSlnName = Combine(info.LangPath, $"{info.GameName}.sln");
|
||||
if (!info.Slns.Contains(expectedSlnName)) {
|
||||
WriteLine($"Expected name/path: {expectedSlnName.RelativePath(info.LangPath)}");
|
||||
}
|
||||
|
||||
// has executable project?
|
||||
if (info.Projs.All(proj => getValue(proj, "OutputType") != "Exe")) {
|
||||
WriteLine("No executable project");
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
// print projects
|
||||
printProjs(info);
|
||||
|
||||
// mimsatched project name/location? (expected x)
|
||||
var expectedProjName = Combine(info.LangPath, $"{info.GameName}.{info.ProjExt}");
|
||||
if (info.Projs.Length < 2 && !info.Projs.Contains(expectedProjName)) {
|
||||
WriteLine($"Expected name/path: {expectedProjName.RelativePath(info.LangPath)}");
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
// verify project properties
|
||||
printProjectWarnings(info);
|
||||
|
||||
WriteLine("Code files:");
|
||||
|
||||
// list code files
|
||||
foreach (var codeFile in info.CodeFiles) {
|
||||
WriteLine(codeFile.RelativePath(info.LangPath));
|
||||
}
|
||||
|
||||
// try build
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<NotepadPlus>
|
||||
<UserLang name="Vintage BASIC" ext="bas" udlVersion="2.1">
|
||||
<Settings>
|
||||
<Global caseIgnored="no" allowFoldOfComments="no" foldCompact="no" forcePureLC="0" decimalSeparator="0" />
|
||||
<Prefix Keywords1="no" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="no" Keywords7="no" Keywords8="no" />
|
||||
</Settings>
|
||||
<KeywordLists>
|
||||
<Keywords name="Comments">00REM 01 02 03 04</Keywords>
|
||||
<Keywords name="Numbers, prefix1"></Keywords>
|
||||
<Keywords name="Numbers, prefix2"></Keywords>
|
||||
<Keywords name="Numbers, extras1"></Keywords>
|
||||
<Keywords name="Numbers, extras2"></Keywords>
|
||||
<Keywords name="Numbers, suffix1"></Keywords>
|
||||
<Keywords name="Numbers, suffix2"></Keywords>
|
||||
<Keywords name="Numbers, range"></Keywords>
|
||||
<Keywords name="Operators1">- + ^ * / = <> < > <= >=</Keywords>
|
||||
<Keywords name="Operators2"></Keywords>
|
||||
<Keywords name="Folders in code1, open"></Keywords>
|
||||
<Keywords name="Folders in code1, middle"></Keywords>
|
||||
<Keywords name="Folders in code1, close"></Keywords>
|
||||
<Keywords name="Folders in code2, open"></Keywords>
|
||||
<Keywords name="Folders in code2, middle"></Keywords>
|
||||
<Keywords name="Folders in code2, close"></Keywords>
|
||||
<Keywords name="Folders in comment, open"></Keywords>
|
||||
<Keywords name="Folders in comment, middle"></Keywords>
|
||||
<Keywords name="Folders in comment, close"></Keywords>
|
||||
<Keywords name="Keywords1">DATA DEF FN DIM END FOR GOSUB GOTO IF THEN INPUT LET NEXT ON PRINT RANDOMIZE REM RESTORE RETURN STOP TO</Keywords>
|
||||
<Keywords name="Keywords2">ABS ASC ATN CHR$ COS EXP INT LEFT$ LEN LOG MID$ RIGHT$ RND SGN SIN SPC SQR STR TAB TAN VAL</Keywords>
|
||||
<Keywords name="Keywords3">NOT AND OR</Keywords>
|
||||
<Keywords name="Keywords4"></Keywords>
|
||||
<Keywords name="Keywords5"></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
<Keywords name="Keywords8"></Keywords>
|
||||
<Keywords name="Delimiters">00" 01 02" 03( 04 05) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
|
||||
</KeywordLists>
|
||||
<Styles>
|
||||
<WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="Courier New" fontStyle="0" fontSize="14" nesting="0" />
|
||||
<WordsStyle name="COMMENTS" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="LINE COMMENTS" fgColor="00FF00" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="NUMBERS" fgColor="FF0000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS1" fgColor="8000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS2" fgColor="0080C0" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS3" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="OPERATORS" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN CODE1" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS1" fgColor="0000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS2" fgColor="FF8040" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS3" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
</Styles>
|
||||
</UserLang>
|
||||
</NotepadPlus>
|
||||
Executable
+380
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
|
||||
|
||||
my $Mode= lc($ARGV[0]); #trace #convert
|
||||
my $File= $ARGV[1];
|
||||
my $LN= "Line";
|
||||
my $Pedantic= 0;
|
||||
my $Indent= 0;
|
||||
|
||||
my %Vars; # num | str | anm | ast
|
||||
my @Data;
|
||||
my %Code;
|
||||
open(FH, $File);
|
||||
while (my $Line = <FH>) {
|
||||
chomp $Line;
|
||||
my $Space= index($Line, " ");
|
||||
my $Key= substr($Line, 0, $Space);
|
||||
my $Code= substr($Line, $Space+1);
|
||||
$Code{$Key}=$Code;
|
||||
}
|
||||
close(FH);
|
||||
|
||||
|
||||
foreach my $Lin (sort {$a<=>$b} keys %Code) {
|
||||
if ($Mode eq "trace") { print "==> $Lin $Code{$Lin}\n"; }
|
||||
my $Ret= &PROCLINE($Code{$Lin});
|
||||
if ($Mode eq "trace") { print " $Ret\n"; }
|
||||
$Code{$Lin}= $Ret;
|
||||
}
|
||||
|
||||
|
||||
if ($Mode eq "convert") {
|
||||
$Code{'0.1'}= "#!/usr/bin/perl";
|
||||
$Code{'0.2'}= "#use strict;";
|
||||
$Code{'0.3'}= "# Automatic converted by bas2perl.pl";
|
||||
$Code{'0.4'}= "";
|
||||
foreach my $Lin (sort {$a<=>$b} keys %Code) {
|
||||
print "$Code{$Lin}\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (@Data) { &DATAIL(); }
|
||||
print "\n\n\n";
|
||||
|
||||
|
||||
exit;
|
||||
|
||||
|
||||
sub PROCLINE {
|
||||
my ($Line)= @_;
|
||||
my @Sente= &SMARPLIT($Line, ":", "\"");
|
||||
|
||||
my $Perl;
|
||||
foreach my $Sen (@Sente) {
|
||||
my $Flag=0;
|
||||
if ($Pedantic==0) {
|
||||
#REM: Resolves some ugly syntaxis...
|
||||
$Sen=~ s/\bPRINT"/PRINT "/g; # PRINT"Hello"
|
||||
$Sen=~ s/"([A-Z])\$/"; $1\$/g; # PRINT "Hello "N$
|
||||
}
|
||||
$Sen= &TRIM($Sen);
|
||||
if ($Sen>0) { $Sen= "GOTO $Sen"; }
|
||||
if ($Sen=~ /^DATA\b/) { $Sen= &DATA($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^DIM\b/) { $Sen= &DIM($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^END\b/) { $Sen= &ENDD($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^FOR\b/) { $Sen= &FOR($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^GOTO\b/) { $Sen= &GOTO($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^GOSUB\b/) { $Sen= &GOSUB($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^IF\b/) { $Sen= &IF($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^INPUT\b/) { $Sen= &INPUT($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^NEXT\b/) { $Sen= &NEXT($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^ON\b/ && $Sen=~ / GOTO /) { $Sen= &ONGOTO($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^PRINT\b/) { $Sen= &PRINT($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^READ\b/) { $Sen= &READ($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^REM\b/) { $Sen= &REM($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^RETURN\b/) { $Sen= &RETURN($Sen); $Flag=1; }
|
||||
if ($Sen=~ /^STOP\b/) { $Sen= &ENDD($Sen); $Flag=1; }
|
||||
if ($Flag==0) { $Sen= &FORMULA($Sen); } # LET
|
||||
$Sen.=";";
|
||||
$Sen=~ s/\{;$/\{/g;
|
||||
$Sen=~ s/\};$/\}/g;
|
||||
$Perl.= "$Sen ";
|
||||
}
|
||||
$Perl= &TRIM($Perl);
|
||||
my $Adj= 0;
|
||||
if ($Perl=~ /^for\b/) { $Adj--; }
|
||||
if ($Perl eq "}") { $Adj++; }
|
||||
$Perl= "\t"x ($Indent+$Adj) . $Perl;
|
||||
return $Perl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# BASIC STATEMENTS #
|
||||
####################
|
||||
|
||||
sub DATA {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/DATA //;
|
||||
push @Data, $Str;
|
||||
return "# TO DATA SEGMENT";
|
||||
}
|
||||
|
||||
|
||||
sub DIM {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/DIM //;
|
||||
my @Parts= split(/\,(?![^\(]*\))/, $Str);
|
||||
my $Out;
|
||||
foreach my $Par (@Parts) {
|
||||
my $Type= $Par=~ /\$/ ? "ast" : "anm";
|
||||
$Par=~ s/\$//g;
|
||||
$Par=~ s/\(.*\)//;
|
||||
$Vars{$Par}= "anm";
|
||||
$Out.= "my \@$Par; ";
|
||||
}
|
||||
chop $Out;
|
||||
chop $Out;
|
||||
return $Out;
|
||||
}
|
||||
|
||||
|
||||
sub ENDD {
|
||||
return "exit";
|
||||
}
|
||||
|
||||
|
||||
sub FOR {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/= /=/g;
|
||||
my @Parts= split(" ", $Str);
|
||||
$Parts[1]= &FORMULA($Parts[1]);
|
||||
my $Var=substr($Parts[1],0,index($Parts[1],"="));
|
||||
$Parts[3]= "$Var<=".&FORMULA($Parts[3]);
|
||||
if ($Parts[5]<0) { $Parts[3]=~ s/</>/; }
|
||||
$Parts[5]= $Parts[5] eq "" ? "$Var++" : "$Var+=".&FORMULA($Parts[5]);
|
||||
$Str= "for ($Parts[1]; $Parts[3]; $Parts[5]) {";
|
||||
$Indent++;
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub GOTO {
|
||||
# The birth of spaguetti code!
|
||||
# Dijkstra would not like this...
|
||||
my ($Str)= @_;
|
||||
my @Parts= split(" ", $Str);
|
||||
my $Label= "$LN$Parts[1]";
|
||||
$Str= lc($Parts[0])." $Label";
|
||||
$Code{($Parts[1]-.2)}="";
|
||||
$Code{($Parts[1]-.1)}="$Label:";
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub GOSUB {
|
||||
my ($Str)= @_;
|
||||
my @Parts= split(" ", $Str);
|
||||
my $Label= "$LN$Parts[1]";
|
||||
$Str= "\&$Label()";
|
||||
$Code{($Parts[1]-.2)}="";
|
||||
$Code{($Parts[1]-.1)}="sub $Label {";
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub IF {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/^IF //g;
|
||||
my @Parts= split(" THEN ", $Str);
|
||||
$Parts[0]= &FORMULA($Parts[0], 1);
|
||||
$Parts[1]= &PROCLINE($Parts[1]);
|
||||
my $Str= "if ($Parts[0]) { $Parts[1] }";
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub INPUT {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/INPUT //;
|
||||
$Str=~ s/"(.*)"//g;
|
||||
|
||||
my $Txt= qq|print "$1\? "; |;
|
||||
|
||||
my @Parts= split(/,/, $Str);
|
||||
my @Multi;
|
||||
foreach my $Par (@Parts) {
|
||||
my $Type= "num";
|
||||
if ($Par=~ /\$/) {
|
||||
$Type= "str";
|
||||
$Par=~ s/\$//g;
|
||||
}
|
||||
if ($Par=~ /\(/) {
|
||||
if ($Type eq "num") { $Type= "anm"; }
|
||||
if ($Type eq "str") { $Type= "ast"; }
|
||||
$Par=~ s/\(/\[/g;
|
||||
$Par=~ s/\)/\]/g;
|
||||
}
|
||||
$Par=~ s/\;//g;
|
||||
push @Multi, "\$$Par";
|
||||
|
||||
my $Name= $Par;
|
||||
$Name=~ s/\[.*\]//;
|
||||
$Vars{$Name}= $Type;
|
||||
}
|
||||
|
||||
$Str= join(",", @Multi);
|
||||
|
||||
my $Spl= "";
|
||||
if (scalar @Parts>1) {
|
||||
$Spl= "; ($Str)= split(/,/, \$Inp_)";
|
||||
$Str= "\$Inp_";
|
||||
}
|
||||
my $Inp= qq|chomp($Str = uc(<STDIN>))$Spl|;
|
||||
|
||||
|
||||
if ($Str=~ /,/) {
|
||||
$Str= "\$$Str";
|
||||
$Str=~ s/,/,\$/g;
|
||||
$Str= "Inp";
|
||||
}
|
||||
return $Txt.$Inp;
|
||||
}
|
||||
|
||||
|
||||
sub NEXT {
|
||||
$Indent--;
|
||||
return "}";
|
||||
}
|
||||
|
||||
|
||||
sub ONGOTO {
|
||||
# Base 1, if not match it will be skipped.
|
||||
my ($Str)= @_;
|
||||
my @Parts= split(" ", $Str);
|
||||
my $Var= $Parts[1];
|
||||
my @Lines= split(",", $Parts[3]);
|
||||
my $Count=0;
|
||||
my $Text;
|
||||
foreach my $Lin (@Lines) {
|
||||
$Count++;
|
||||
my $This= "\telsif (\$$Var==$Count) ";
|
||||
if ($Count==1) { $This= "if (\$$Var==1) "; }
|
||||
|
||||
my $Goto= &GOTO("GOTO $Lin");
|
||||
$This.="{ $Goto; }\n";
|
||||
$Text.= $This;
|
||||
}
|
||||
return $Text;
|
||||
}
|
||||
|
||||
|
||||
sub PRINT {
|
||||
my ($Str)= @_;
|
||||
if ($Str eq "PRINT") { return 'print "\n"' };
|
||||
$Str=~ s/^PRINT //;
|
||||
|
||||
my $Enter= 1;
|
||||
if ($Str=~ /;$/) { $Enter= 0; }
|
||||
|
||||
my @Parts= &SMARPLIT($Str, ";", "\"");
|
||||
|
||||
my @Out;
|
||||
foreach my $Par (@Parts) {
|
||||
if ($Par=~ /"/) {
|
||||
push @Out, $Par;
|
||||
next;
|
||||
}
|
||||
|
||||
if ($Par=~ /TAB\((.*?)\)/) {
|
||||
push @Out, "' 'x".&FORMULA($1)." ";
|
||||
next;
|
||||
}
|
||||
|
||||
$Par= &FORMULA($Par);
|
||||
push @Out, $Par;
|
||||
}
|
||||
|
||||
my $Out= join(". ", @Out);
|
||||
if ($Enter) { $Out.= qq|. "\\n"|; }
|
||||
return "print ".$Out;
|
||||
}
|
||||
|
||||
|
||||
sub READ {
|
||||
my ($Str)= @_;
|
||||
$Str=~ s/READ //;
|
||||
$Str= &FORMULA($Str);
|
||||
$Str.="= <DATA>; chomp($Str)";
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub REM {
|
||||
my ($Str)= @_;
|
||||
return "#".$Str;
|
||||
}
|
||||
|
||||
|
||||
sub RETURN {
|
||||
return "return; }";
|
||||
}
|
||||
|
||||
|
||||
|
||||
###########
|
||||
# HELPERS #
|
||||
###########
|
||||
|
||||
sub TRIM {
|
||||
my ($Str)= @_;
|
||||
#$Str=~ s/\s+/ /g;
|
||||
$Str=~ s/^\s+//;
|
||||
$Str=~ s/\s+$//;
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub DATAIL {
|
||||
print "\n\n\n";
|
||||
print "__DATA__\n";
|
||||
foreach my $Dat (@Data) {
|
||||
$Dat=~ s/"//g;
|
||||
$Dat=~ s/,/\n/g;
|
||||
print "$Dat\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub FORMULA {
|
||||
my ($Str, $Cond)= @_;
|
||||
$Str=~ s/\$//g;
|
||||
$Str=~ s/ABS\(/abs\(/g;
|
||||
$Str=~ s/COS\(/cos\(/g;
|
||||
$Str=~ s/LEN\(/length\(/g;
|
||||
$Str=~ s/INT\(/int\(/g;
|
||||
$Str=~ s/MID\$?\(/substr\(/g;
|
||||
$Str=~ s/RND\(/rand\(/g;
|
||||
$Str=~ s/SIN\(/sin\(/g;
|
||||
$Str=~ s/SQR\(/sqr\(/g;
|
||||
$Str=~ s/(\b[A-Z][0-9]?\b)/\$$&/g;
|
||||
|
||||
#==> Check for arrays...
|
||||
foreach my $Key (keys %Vars) {
|
||||
if ($Vars{$Key}!~ /^a/) { next; }
|
||||
$Str=~ s/\$$Key\((.*?)\)/\$$Key\[$1\]/g;
|
||||
}
|
||||
|
||||
if ($Cond==1) {
|
||||
$Str=~ s/<>/ ne /g;
|
||||
$Str=~ s/=/ eq /g;
|
||||
}
|
||||
return $Str;
|
||||
}
|
||||
|
||||
|
||||
sub SMARPLIT {
|
||||
my ($Str, $Sep, $Nin)= @_;
|
||||
my @Parts;
|
||||
my $Text= "";
|
||||
my $Flag= 0;
|
||||
my $Prev;
|
||||
foreach my $Char (split('', $Str)) {
|
||||
if ($Char eq $Nin) { $Flag= !$Flag; }
|
||||
if ($Char eq $Sep && $Flag==0) {
|
||||
push @Parts, &TRIM($Text);
|
||||
$Text= "";
|
||||
next;
|
||||
}
|
||||
$Prev= $Char;
|
||||
$Text.= $Char;
|
||||
}
|
||||
if ($Text) { push @Parts, &TRIM($Text); }
|
||||
return @Parts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Program to find games that are missing solutions in a given language
|
||||
*
|
||||
* Scan each game folder, check for a folder for each language, and also make
|
||||
* sure there's at least one file of the expected extension and not just a
|
||||
* readme or something
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const glob = require("glob");
|
||||
|
||||
// relative path to the repository root
|
||||
const ROOT_PATH = "../.";
|
||||
|
||||
const languages = [
|
||||
{ name: "csharp", extension: "cs" },
|
||||
{ name: "java", extension: "java" },
|
||||
{ name: "javascript", extension: "html" },
|
||||
{ name: "pascal", extension: "pas" },
|
||||
{ name: "perl", extension: "pl" },
|
||||
{ name: "python", extension: "py" },
|
||||
{ name: "ruby", extension: "rb" },
|
||||
{ name: "vbnet", extension: "vb" },
|
||||
];
|
||||
|
||||
const getFilesRecursive = async (path, extension) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(`${path}/**/*.${extension}`, (err, matches) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(matches);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getPuzzleFolders = () => {
|
||||
return fs
|
||||
.readdirSync(ROOT_PATH, { withFileTypes: true })
|
||||
.filter((dirEntry) => dirEntry.isDirectory())
|
||||
.filter(
|
||||
(dirEntry) =>
|
||||
![".git", "node_modules", "00_Utilities"].includes(dirEntry.name)
|
||||
)
|
||||
.map((dirEntry) => dirEntry.name);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
let missingGames = {};
|
||||
let missingLanguageCounts = {};
|
||||
languages.forEach((l) => (missingLanguageCounts[l.name] = 0));
|
||||
const puzzles = getPuzzleFolders();
|
||||
for (const puzzle of puzzles) {
|
||||
for (const { name: language, extension } of languages) {
|
||||
const files = await getFilesRecursive(
|
||||
`${ROOT_PATH}/${puzzle}/${language}`,
|
||||
extension
|
||||
);
|
||||
if (files.length === 0) {
|
||||
if (!missingGames[puzzle]) missingGames[puzzle] = [];
|
||||
|
||||
missingGames[puzzle].push(language);
|
||||
missingLanguageCounts[language]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
const missingCount = Object.values(missingGames).flat().length;
|
||||
if (missingCount === 0) {
|
||||
console.log("All games have solutions for all languages");
|
||||
} else {
|
||||
console.log(`Missing ${missingCount} implementations:`);
|
||||
|
||||
Object.entries(missingGames).forEach(
|
||||
([p, ls]) => (missingGames[p] = ls.join(", "))
|
||||
);
|
||||
|
||||
console.log(`\nMissing languages by game:`);
|
||||
console.table(missingGames);
|
||||
console.log(`\nBy language:`);
|
||||
console.table(missingLanguageCounts);
|
||||
}
|
||||
})();
|
||||
|
||||
return;
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Program to show unimplemented games by language, optionally filtered by
|
||||
* language
|
||||
*
|
||||
* Usage: node find-unimplemented.js [[[lang1] lang2] ...]
|
||||
*
|
||||
* Adapted from find-missing-implementtion.js
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const glob = require("glob");
|
||||
|
||||
// relative path to the repository root
|
||||
const ROOT_PATH = "../.";
|
||||
|
||||
let languages = [
|
||||
{ name: "csharp", extension: "cs" },
|
||||
{ name: "java", extension: "java" },
|
||||
{ name: "javascript", extension: "html" },
|
||||
{ name: "pascal", extension: "pas" },
|
||||
{ name: "perl", extension: "pl" },
|
||||
{ name: "python", extension: "py" },
|
||||
{ name: "ruby", extension: "rb" },
|
||||
{ name: "vbnet", extension: "vb" },
|
||||
];
|
||||
|
||||
const getFilesRecursive = async (path, extension) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(`${path}/**/*.${extension}`, (err, matches) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(matches);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getPuzzleFolders = () => {
|
||||
return fs
|
||||
.readdirSync(ROOT_PATH, { withFileTypes: true })
|
||||
.filter((dirEntry) => dirEntry.isDirectory())
|
||||
.filter(
|
||||
(dirEntry) =>
|
||||
![".git", "node_modules", "00_Utilities", "buildJvm"].includes(dirEntry.name)
|
||||
)
|
||||
.map((dirEntry) => dirEntry.name);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const result = {};
|
||||
if (process.argv.length > 2) {
|
||||
languages = languages.filter((language) => process.argv.slice(2).includes(language.name));
|
||||
}
|
||||
for (const { name: language } of languages) {
|
||||
result[language] = [];
|
||||
}
|
||||
|
||||
const puzzleFolders = getPuzzleFolders();
|
||||
for (const puzzleFolder of puzzleFolders) {
|
||||
for (const { name: language, extension } of languages) {
|
||||
const files = await getFilesRecursive(
|
||||
`${ROOT_PATH}/${puzzleFolder}/${language}`, extension
|
||||
);
|
||||
if (files.length === 0) {
|
||||
result[language].push(puzzleFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('Unimplementation by language:')
|
||||
console.dir(result);
|
||||
})();
|
||||
|
||||
return;
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.pcholt.console.testutils
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.Rule
|
||||
import org.junit.contrib.java.lang.system.SystemOutRule
|
||||
import org.junit.contrib.java.lang.system.TextFromStandardInputStream
|
||||
|
||||
abstract class ConsoleTest {
|
||||
@get:Rule
|
||||
val inputRule = TextFromStandardInputStream.emptyStandardInputStream()
|
||||
|
||||
@get:Rule
|
||||
val systemOutRule = SystemOutRule().enableLog()
|
||||
|
||||
val regexInputCommand = "\\{(.*)}".toRegex()
|
||||
|
||||
fun assertConversation(conversation: String, runMain: () -> Unit) {
|
||||
|
||||
inputRule.provideLines(*regexInputCommand
|
||||
.findAll(conversation)
|
||||
.map { it.groupValues[1] }
|
||||
.toList().toTypedArray())
|
||||
|
||||
runMain()
|
||||
|
||||
Truth.assertThat(
|
||||
systemOutRule.log.trimWhiteSpace()
|
||||
)
|
||||
.isEqualTo(
|
||||
regexInputCommand
|
||||
.replace(conversation, "").trimWhiteSpace()
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.trimWhiteSpace() =
|
||||
replace("[\\s]+".toRegex(), " ")
|
||||
}
|
||||
@@ -18,7 +18,7 @@ checklist = ["game", "csharp", "java", "javascript",
|
||||
prev_game = ""
|
||||
|
||||
for dirName, subdirList, fileList in os.walk(rootDir):
|
||||
split_dir = dirName.split("/")
|
||||
split_dir = dirName.split(os.path.sep)
|
||||
|
||||
if len(split_dir) == 2 and not split_dir[1] in ['.git', '00_Utilities']:
|
||||
if prev_game == "":
|
||||
@@ -46,5 +46,5 @@ sorted_strings = list(map(lambda l: " | ".join(l) + "\n",
|
||||
write_string += ''.join(sorted_strings)
|
||||
|
||||
|
||||
with open("README.md", "w") as f:
|
||||
with open("README.md", "w", encoding='utf-8') as f:
|
||||
f.write(write_string)
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/perl
|
||||
#YATOL: Yet Another TOdo List
|
||||
use strict;
|
||||
|
||||
#REM: Get list of basic files ordered by number of lines.
|
||||
#REM: This way you can do the easier ones first.
|
||||
my @Ret=`find .. -iname '*.bas' -exec wc -l \{\} \\; | sort -h`;
|
||||
|
||||
|
||||
my @Langs= qw(PL JS VB PAS RB C# JAVA PY);
|
||||
my @Dirs= qw(perl javascript vbnet pascal ruby csharp java python);
|
||||
my %Sum;
|
||||
|
||||
my $Row=25;
|
||||
my $Tab=7;
|
||||
|
||||
printf("%-$Row\s", "PATH");
|
||||
printf("%$Tab\s", "BAS");
|
||||
|
||||
foreach my $Dir (@Langs) {
|
||||
printf("%$Tab\s", $Dir);
|
||||
}
|
||||
print "\n";
|
||||
|
||||
my $Count;
|
||||
foreach my $Lin (@Ret) {
|
||||
$Count++;
|
||||
chomp $Lin;
|
||||
my ($Num, $File)= split (" ", $Lin);
|
||||
my @Parts= split(/\//, $File);
|
||||
my $Base= $Parts[1];
|
||||
|
||||
printf("%-$Row\s", $Base);
|
||||
printf("%$Tab\s", "$Num");
|
||||
|
||||
foreach my $Dir (@Dirs) {
|
||||
my $Path= "../$Base/$Dir/";
|
||||
my $Ret= `ls $Path | wc -l`;
|
||||
if ($Ret>1) { printf("%$Tab\s", "YES"); $Sum{$Dir}++; }
|
||||
else { printf("%$Tab\s", " ");}
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
|
||||
printf("%$Row\s", "FILES:");
|
||||
printf("%$Tab\s", " ");
|
||||
foreach my $Dir (@Dirs) {
|
||||
printf("%$Tab\s", "$Sum{$Dir}");
|
||||
}
|
||||
print "\n";
|
||||
|
||||
printf("%$Row\s", "ADVANCE:");
|
||||
printf("%$Tab\s", " ");
|
||||
foreach my $Dir (@Dirs) {
|
||||
my $Per= int($Sum{$Dir}/$Count*100)."%";
|
||||
printf("%$Tab\s", "$Per");
|
||||
}
|
||||
print "\n";
|
||||
|
||||
@@ -17,3 +17,4 @@ http://www.vintage-basic.net/games.html
|
||||
|
||||
#### External Links
|
||||
- Common Lisp: https://github.com/koalahedron/lisp-computer-games/blob/master/01%20Acey%20Ducey/common-lisp/acey-deucy.lisp
|
||||
- PowerShell: https://github.com/eweilnau/basic-computer-games-powershell/blob/main/AceyDucey.ps1
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Oracle Java](https://openjdk.java.net/)
|
||||
|
||||
Two versions of Acey Ducey have been contributed.
|
||||
|
||||
The original upload supported JDK 8/JDK 11 and uses multiple files and the second uses features in JDK 17 and is implemented in a single file AceyDucey17.java.
|
||||
|
||||
Both are in the src folder.
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* A modern version (JDK17) of ACEY DUCEY using post Java 8 features. Notes
|
||||
* regarding new java features or differences in the original basic
|
||||
* implementation are numbered and at the bottom of this code.
|
||||
* The goal is to recreate the exact look and feel of the original program
|
||||
* minus a large glaring bug in the original code that lets you cheat.
|
||||
*/
|
||||
public class AceyDucey17 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// notes [1]
|
||||
System.out.println("""
|
||||
ACEY DUCEY CARD GAME
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER
|
||||
THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP
|
||||
YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING
|
||||
ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE
|
||||
A VALUE BETWEEN THE FIRST TWO.
|
||||
IF YOU DO NOT WANT TO BET, INPUT A 0""");
|
||||
|
||||
do {
|
||||
playGame();
|
||||
} while (stillInterested());
|
||||
System.out.println("O.K., HOPE YOU HAD FUN!");
|
||||
}
|
||||
|
||||
public static void playGame() {
|
||||
int cashOnHand = 100; // our only mutable variable note [11]
|
||||
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");// note [6]
|
||||
while (cashOnHand > 0) {
|
||||
System.out.println();
|
||||
System.out.println("HERE ARE YOUR NEXT TWO CARDS:");
|
||||
|
||||
final Card lowCard = Card.getRandomCard(2, Card.KING); //note [3]
|
||||
System.out.println(lowCard);
|
||||
final Card highCard = Card.getRandomCard(lowCard.rank() + 1, Card.ACE);
|
||||
System.out.println(highCard);
|
||||
|
||||
final int bet = getBet(cashOnHand);
|
||||
final int winnings = determineWinnings(lowCard,highCard,bet);
|
||||
cashOnHand += winnings;
|
||||
if(winnings != 0 || cashOnHand != 0){ //note [2]
|
||||
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");//note [6]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int determineWinnings(Card lowCard, Card highCard, int bet){
|
||||
if (bet <= 0) { // note [5]
|
||||
System.out.println("CHICKEN!!");
|
||||
return 0;
|
||||
}
|
||||
Card nextCard = Card.getRandomCard(2, Card.ACE);
|
||||
System.out.println(nextCard);
|
||||
if(nextCard.between(lowCard,highCard)){
|
||||
System.out.println("YOU WIN!!!");
|
||||
return bet;
|
||||
}
|
||||
System.out.println("SORRY, YOU LOSE");
|
||||
return -bet;
|
||||
}
|
||||
|
||||
public static boolean stillInterested(){
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
System.out.println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.");
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
System.out.print("TRY AGAIN (YES OR NO)? ");
|
||||
Scanner input = new Scanner(System.in);
|
||||
boolean playAgain = input.nextLine()
|
||||
.toUpperCase()
|
||||
.startsWith("Y"); // note [9]
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
return playAgain;
|
||||
}
|
||||
|
||||
public static int getBet(int cashOnHand){
|
||||
int bet;
|
||||
do{
|
||||
System.out.println();
|
||||
System.out.print("WHAT IS YOUR BET? ");
|
||||
bet = inputNumber();
|
||||
if (bet > cashOnHand) {
|
||||
System.out.println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.");
|
||||
System.out.println("YOU HAVE ONLY "+cashOnHand+" DOLLARS TO BET.");
|
||||
}
|
||||
}while(bet > cashOnHand);
|
||||
return bet;
|
||||
}
|
||||
|
||||
public static int inputNumber() {
|
||||
final Scanner input = new Scanner(System.in);
|
||||
// set to negative to mark as not entered yet in case of input error.
|
||||
int number = -1;
|
||||
while (number < 0) {
|
||||
try {
|
||||
number = input.nextInt();
|
||||
} catch(Exception ex) { // note [7]
|
||||
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
|
||||
System.out.print("? ");
|
||||
try{
|
||||
input.nextLine();
|
||||
}
|
||||
catch(Exception ns_ex){ // received EOF (ctrl-d or ctrl-z if windows)
|
||||
System.out.println("END OF INPUT, STOPPING PROGRAM.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
record Card(int rank){
|
||||
// Some constants to describe face cards.
|
||||
public static final int JACK = 11, QUEEN = 12, KING = 13, ACE = 14;
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static Card getRandomCard(int from, int to){
|
||||
return new Card(random.nextInt(from, to+1)); // note [4]
|
||||
}
|
||||
|
||||
public boolean between(Card lower, Card higher){
|
||||
return lower.rank() < this.rank() && this.rank() < higher.rank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { // note [13]
|
||||
return switch (rank) {
|
||||
case JACK -> "JACK";
|
||||
case QUEEN -> "QUEEN";
|
||||
case KING -> "KING";
|
||||
case ACE -> "ACE\n"; // note [10]
|
||||
default -> " "+rank+" "; // note [6]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Notes:
|
||||
1. Multiline strings, a.k.a. text blocks, were added in JDK15.
|
||||
2. The original game only displays the players balance if it changed,
|
||||
which it does not when the player chickens out and bets zero.
|
||||
It also doesn't display the balance when it becomes zero because it has
|
||||
a more appropriate message: Sorry, You Lose.
|
||||
3. To pick two cards to show, the original BASIC implementation has a
|
||||
bug that could cause a race condition if the RND function never chose
|
||||
a lower number first and higher number second. It loops infinitely
|
||||
re-choosing random numbers until the condition is met of the first
|
||||
one being lower. The logic is changed a bit here so that the first
|
||||
card picked is anything but an ACE, the highest possible card,
|
||||
and then the second card is between the just picked first card upto
|
||||
and including the ACE.
|
||||
4. Random.nextInt(origin, bound) was added in JDK17, and allows to
|
||||
directly pick a range for a random integer to be generated. The second
|
||||
parameter is exclusive of the range and thus why they are stated with
|
||||
+1's to the face card.
|
||||
5. The original BASIC implementation has a bug that allows negative value
|
||||
bets. Since you can't bet MORE cash than you have you can always bet
|
||||
less including a very, very large negative value. You would do this when
|
||||
the chances of winning are slim or zero since losing a hand SUBTRACTS
|
||||
your bet from your cash; subtracting a negative number actually ADDS
|
||||
to your cash, potentially making you an instant billionaire.
|
||||
This loophole is now closed.
|
||||
6. The subtle behavior of the BASIC PRINT command causes a space to be
|
||||
printed before all positive numbers as well as a trailing space. Any
|
||||
place a non-face card or the players balance is printed has extra space
|
||||
to mimic this behavior.
|
||||
7. Errors on input were probably specific to the interpreter. This program
|
||||
tries to match the Vintage Basic interpreter's error messages. The final
|
||||
input.nextLine() command exists to clear the blockage of whatever
|
||||
non-number input was entered. But even that could fail if the user
|
||||
types Ctrl-D (windows Ctrl-Z), signifying an EOF (end of file) and thus
|
||||
the closing of STDIN channel. The original program on an EOF signal prints
|
||||
"END OF INPUT IN LINE 660" and thus we cover it roughly the same way.
|
||||
All of this is necessary to avoid a messy stack trace from being
|
||||
printed as the program crashes.
|
||||
9. The original game only accepted a full upper case "YES" to continue
|
||||
playing if bankrupted. This program is more lenient and will accept
|
||||
any input that starts with the letter 'y', uppercase or not.
|
||||
10. The original game prints an extra blank line if the card is an ACE. There
|
||||
is seemingly no rationale for this.
|
||||
11. Modern java best practices are edging toward a more functional paradigm
|
||||
and as such, mutating state is discouraged. All other variables besides
|
||||
the cashOnHand are final and initialized only once.
|
||||
12. Refactoring of the concept of a card is done with a record. Records were
|
||||
introduced in JDK14. Card functionality is encapsulated in this example
|
||||
of a record. An enum could be a better alternative since there are
|
||||
technically only 13 cards possible.
|
||||
13. Switch expressions were introduced as far back as JDK12 but continue to
|
||||
be refined for clarity, exhaustiveness. As of JDK17 pattern matching
|
||||
for switch expressions can be accessed by enabling preview features.
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import java.util.Random
|
||||
|
||||
fun printCard(a: Int) {
|
||||
if (a < 11) println(a)
|
||||
if (a == 11) println("JACK")
|
||||
if (a == 12) println("QUEEN")
|
||||
if (a == 13) println("KING")
|
||||
if (a == 14) println("ACE")
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println("ACEY DUCEY CARD GAME")
|
||||
println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
||||
println()
|
||||
println()
|
||||
println("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ")
|
||||
println("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP")
|
||||
println("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING")
|
||||
println("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE")
|
||||
println("A VALUE BETWEEN THE FIRST TWO.")
|
||||
println("IF YOU DO NOT WANT TO BET, INPUT A 0")
|
||||
var random = Random()
|
||||
do {
|
||||
var q = 100
|
||||
var a : Int
|
||||
var b : Int
|
||||
var m : Int
|
||||
println("YOU NOW HAVE " + q + " DOLLARS.")
|
||||
println()
|
||||
do {
|
||||
do {
|
||||
do {
|
||||
println("HERE ARE YOUR NEXT TWO CARDS: ")
|
||||
do {
|
||||
a = random.nextInt(12) + 2
|
||||
b = random.nextInt(12) + 2
|
||||
} while (a >= b);
|
||||
printCard(a)
|
||||
printCard(b)
|
||||
println()
|
||||
println()
|
||||
print("WHAT IS YOUR BET")
|
||||
m = readLine()!!.toInt()
|
||||
if (m == 0) {
|
||||
println("CHICKEN!!")
|
||||
println()
|
||||
}
|
||||
} while (m == 0);
|
||||
if (m > q) {
|
||||
println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.")
|
||||
println("YOU HAVE ONLY " + q + " DOLLARS TO BET.")
|
||||
}
|
||||
} while (m > q);
|
||||
var c = random.nextInt(12) + 2
|
||||
printCard(c)
|
||||
println()
|
||||
if (c > a && c < b) {
|
||||
println("YOU WIN!!!")
|
||||
q += m
|
||||
}
|
||||
else {
|
||||
println("SORRY, YOU LOSE")
|
||||
if (m < q) q -= m
|
||||
}
|
||||
} while (m < q);
|
||||
println()
|
||||
println()
|
||||
println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.")
|
||||
println()
|
||||
println()
|
||||
println("TRY AGAIN (YES OR NO)")
|
||||
} while (readLine() == "YES");
|
||||
println("O.K., HOPE YOU HAD FUN!")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rust"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
@@ -0,0 +1,7 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Rust](https://www.rust-lang.org/) by Alex Kotov [mur4ik18@github](https://github.com/mur4ik18).
|
||||
|
||||
Further edits by
|
||||
|
||||
- Berker Şal [berkersal@github](https://github.com/berkersal)
|
||||
@@ -0,0 +1,139 @@
|
||||
use rand::{prelude::ThreadRng, Rng};
|
||||
use std::{fmt, io, mem};
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Card(u8);
|
||||
|
||||
impl Card {
|
||||
fn new_random(rng: &mut ThreadRng) -> Card {
|
||||
Card(rng.gen_range(2..15))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Card {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self.0 {
|
||||
11 => String::from("JACK"),
|
||||
12 => String::from("QUEEN"),
|
||||
13 => String::from("KING"),
|
||||
14 => String::from("ACE"),
|
||||
otherwise => otherwise.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct CardsPool(Card, Card, Card);
|
||||
|
||||
impl CardsPool {
|
||||
fn new() -> CardsPool {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut first = Card::new_random(&mut rng);
|
||||
let mut second = Card::new_random(&mut rng);
|
||||
let third = Card::new_random(&mut rng);
|
||||
|
||||
if first > second {
|
||||
mem::swap(&mut first, &mut second);
|
||||
}
|
||||
|
||||
CardsPool(first, second, third)
|
||||
}
|
||||
|
||||
fn is_in_win_range(&self) -> bool {
|
||||
self.0 <= self.2 && self.2 <= self.1
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
hello();
|
||||
// user start bank
|
||||
let mut user_bank: u16 = 100;
|
||||
|
||||
loop {
|
||||
println!("YOU NOW HAVE {} DOLLARS.", &mut user_bank);
|
||||
println!("\nHERE ARE YOUR NEXT TWO CARDS:");
|
||||
// get new random cards
|
||||
let cards = CardsPool::new();
|
||||
|
||||
println!("{}", cards.0);
|
||||
println!("{}", cards.1);
|
||||
|
||||
let user_bet: u16 = get_bet(user_bank);
|
||||
|
||||
if user_bet == 0 {
|
||||
println!("CHICKEN!!!\n");
|
||||
continue;
|
||||
} else {
|
||||
println!("THANK YOU! YOUR BET IS {} DOLLARS.", user_bet);
|
||||
}
|
||||
|
||||
println!("\nTHE THIRD CARD IS:");
|
||||
println!("{}", cards.2);
|
||||
|
||||
if cards.is_in_win_range() {
|
||||
println!("\nYOU WIN!!!");
|
||||
user_bank += user_bet;
|
||||
} else {
|
||||
println!("\nSORRY, YOU LOSE");
|
||||
user_bank -= user_bet;
|
||||
}
|
||||
|
||||
if user_bank == 0 {
|
||||
println!("\nSORRY, FRIEND, BUT YOU BLEW YOUR WAD.");
|
||||
println!("\nTRY AGAIN? (YES OR NO)");
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).expect("Incorrect input");
|
||||
|
||||
if input.trim().to_lowercase() == "yes" {
|
||||
user_bank = 100;
|
||||
println!();
|
||||
} else {
|
||||
println!("\nO.K., HOPE YOU HAD FUN!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hello() {
|
||||
println!(" 🂡 ACEY DUCEY CARD GAME 🂱");
|
||||
println!("CREATIVE COMPUTING - MORRISTOWN, NEW JERSEY");
|
||||
println!("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER");
|
||||
println!("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP");
|
||||
println!("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING");
|
||||
println!("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE");
|
||||
println!("A VALUE BETWEEN THE FIRST TWO.");
|
||||
println!("IF YOU DO NOT WANT TO BET IN A ROUND, ENTER 0");
|
||||
println!("\n\n");
|
||||
}
|
||||
|
||||
fn get_bet(user_bank: u16) -> u16 {
|
||||
loop {
|
||||
println!("\nWHAT IS YOUR BET? ENTER 0 IF YOU DON'T WANT TO BET (CTRL+C TO EXIT)");
|
||||
let bet: u16;
|
||||
let mut input = String::new();
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("CANNOT READ INPUT!");
|
||||
|
||||
match input.trim().parse::<u16>() {
|
||||
Ok(i) => bet = i,
|
||||
Err(e) => {
|
||||
println!("CHECK YOUR INPUT! {}!", e.to_string().to_uppercase());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match bet {
|
||||
bet if bet <= user_bank => return bet,
|
||||
_ => {
|
||||
println!("\nSORRY, MY FRIEND, BUT YOU BET TOO MUCH.");
|
||||
println!("YOU HAVE ONLY {} DOLLARS TO BET.", user_bank);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "AceyDucy", "AceyDucy\AceyDucy.vbproj", "{37496710-B458-4502-ADCB-4C57203866F9}"
|
||||
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "AceyDucey", "AceyDucey.vbproj", "{54C05475-238D-4A82-A67B-B6C1BF2CA337}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -11,10 +11,10 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{54C05475-238D-4A82-A67B-B6C1BF2CA337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{54C05475-238D-4A82-A67B-B6C1BF2CA337}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{54C05475-238D-4A82-A67B-B6C1BF2CA337}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{54C05475-238D-4A82-A67B-B6C1BF2CA337}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>AceyDucey</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,178 +0,0 @@
|
||||
Imports System
|
||||
|
||||
''' <summary>
|
||||
''' This is a modern adapation of Acey Ducey from BASIC Computer Games.
|
||||
'''
|
||||
''' 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 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.
|
||||
'''
|
||||
''' 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!")
|
||||
|
||||
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
|
||||
@@ -0,0 +1,21 @@
|
||||
Imports System
|
||||
|
||||
''' <summary>
|
||||
''' This is a modern adapation of Acey Ducey from BASIC Computer Games.
|
||||
'''
|
||||
''' The structural changes primarily consist of replacing the many GOTOs with
|
||||
''' Do/Loop constructs to force the continual execution of the program.
|
||||
'''
|
||||
''' 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.
|
||||
'''
|
||||
''' 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()
|
||||
Call New AceyDucey().Play()
|
||||
End Sub
|
||||
End Module
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Amazing", "Amazing.vbproj", "{FB9DF301-CB34-4C9A-8823-F034303F5DA3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FB9DF301-CB34-4C9A-8823-F034303F5DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FB9DF301-CB34-4C9A-8823-F034303F5DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FB9DF301-CB34-4C9A-8823-F034303F5DA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FB9DF301-CB34-4C9A-8823-F034303F5DA3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Amazing</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,295 @@
|
||||
Imports System
|
||||
|
||||
Module Program
|
||||
|
||||
Enum Directions
|
||||
SolveAndReset = 0
|
||||
Left = 1
|
||||
Up = 2
|
||||
Right = 3
|
||||
Down = 4
|
||||
End Enum
|
||||
|
||||
'Program State
|
||||
Dim Width As Integer = 0, Height As Integer = 0, Q As Integer = 0, CellsVisited As Integer = 2, curCol As Integer, curRow As Integer = 1
|
||||
Dim SolutionCompleted As Boolean = False
|
||||
Dim CellVisitHistory(,) As Integer
|
||||
Dim CellState(,) As Integer
|
||||
|
||||
Dim rnd As New Random()
|
||||
|
||||
Public ReadOnly Property BlockedLeft As Boolean
|
||||
Get
|
||||
Return curCol - 1 = 0 OrElse CellVisitHistory(curCol - 1, curRow) <> 0
|
||||
End Get
|
||||
End Property
|
||||
Public ReadOnly Property BlockedAbove As Boolean
|
||||
Get
|
||||
Return curRow - 1 = 0 OrElse CellVisitHistory(curCol, curRow - 1) <> 0
|
||||
End Get
|
||||
End Property
|
||||
Public ReadOnly Property BlockedRight As Boolean
|
||||
Get
|
||||
Return curCol = Width OrElse CellVisitHistory(curCol + 1, curRow) <> 0
|
||||
End Get
|
||||
End Property
|
||||
'Note: "BlockedBelow" does NOT include checking if we have a solution!
|
||||
Public ReadOnly Property BlockedBelow As Boolean
|
||||
Get
|
||||
Return curRow = Height OrElse CellVisitHistory(curCol, curRow + 1) <> 0
|
||||
End Get
|
||||
End Property
|
||||
Public ReadOnly Property OnBottomRow As Boolean
|
||||
Get
|
||||
Return curRow.Equals(Height)
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Sub Main(args As String())
|
||||
Const header As String =
|
||||
" AMAZING PROGRAM
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
|
||||
"
|
||||
Console.WriteLine(header)
|
||||
|
||||
While Width <= 1 OrElse Height <= 1
|
||||
Console.Write("WHAT ARE YOUR WIDTH AND LENGTH? ")
|
||||
|
||||
'We no longer have the old convenient INPUT command, so need to parse out the inputs
|
||||
Dim parts = Console.ReadLine().Split(","c).Select(Function(s) Convert.ToInt32(s.Trim())).ToList()
|
||||
Width = parts(0)
|
||||
Height = parts(1)
|
||||
|
||||
If Width <= 1 OrElse Height <= 1 Then Console.WriteLine($"MEANINGLESS DIMENSIONS. TRY AGAIN.{vbCrLf}")
|
||||
End While
|
||||
|
||||
ReDim CellVisitHistory(Width, Height), CellState(Width, Height)
|
||||
|
||||
Console.WriteLine("
|
||||
|
||||
")
|
||||
|
||||
curCol = rnd.Next(1, Width + 1) 'Starting X position
|
||||
CellVisitHistory(curCol, 1) = 1
|
||||
Dim startXPos As Integer = curCol 'we need to know this at the end to print opening line
|
||||
|
||||
Dim keepGoing As Boolean = True
|
||||
While keepGoing
|
||||
If BlockedLeft Then
|
||||
keepGoing = ChoosePath_BlockedToTheLeft()
|
||||
ElseIf BlockedAbove Then
|
||||
keepGoing = ChoosePath_BlockedAbove()
|
||||
ElseIf BlockedRight Then
|
||||
keepGoing = ChoosePath_BlockedToTheRight()
|
||||
Else
|
||||
keepGoing = SelectRandomDirection(Directions.Left, Directions.Up, Directions.Right) 'Go anywhere but down
|
||||
End If
|
||||
End While
|
||||
|
||||
PrintFinalResults(startXPos)
|
||||
End Sub
|
||||
|
||||
Public Sub ResetCurrentPosition()
|
||||
Do
|
||||
If curCol <> Width Then 'not at the right edge
|
||||
curCol += 1
|
||||
ElseIf curRow <> Height Then 'not at the bottom
|
||||
curCol = 1
|
||||
curRow += 1
|
||||
Else
|
||||
curCol = 1
|
||||
curRow = 1
|
||||
End If
|
||||
Loop While CellVisitHistory(curCol, curRow) = 0
|
||||
End Sub
|
||||
|
||||
Dim methods() As Func(Of Boolean) = {AddressOf MarkSolvedAndResetPosition, AddressOf GoLeft, AddressOf GoUp, AddressOf GoRight, AddressOf GoDown}
|
||||
Public Function SelectRandomDirection(ParamArray possibles() As Directions) As Boolean
|
||||
Dim x As Integer = rnd.Next(0, possibles.Length)
|
||||
Return methods(possibles(x))()
|
||||
End Function
|
||||
|
||||
Public Function ChoosePath_BlockedToTheLeft() As Boolean
|
||||
If BlockedAbove Then
|
||||
If BlockedRight Then
|
||||
If curRow <> Height Then
|
||||
If CellVisitHistory(curCol, curRow + 1) <> 0 Then ' Can't go down, but not at the edge...blocked. Reset and try again
|
||||
ResetCurrentPosition()
|
||||
Return True
|
||||
Else
|
||||
Return GoDown()
|
||||
End If
|
||||
ElseIf SolutionCompleted Then 'Can't go Down (there's already another solution)
|
||||
ResetCurrentPosition()
|
||||
Return True
|
||||
Else 'Can't go LEFT, UP, RIGHT, or DOWN, but we're on the bottom and there's no solution yet
|
||||
Return MarkSolvedAndResetPosition()
|
||||
End If
|
||||
ElseIf BlockedBelow Then
|
||||
Return GoRight()
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Right, Directions.Down)
|
||||
ElseIf SolutionCompleted Then 'Can only go right, and we're at the bottom
|
||||
Return GoRight()
|
||||
Else 'Can only go right, we're at the bottom, and there's not a solution yet
|
||||
Return SelectRandomDirection(Directions.Right, Directions.SolveAndReset)
|
||||
End If
|
||||
'== Definitely can go Up ==
|
||||
ElseIf BlockedRight Then
|
||||
If BlockedBelow Then
|
||||
Return GoUp()
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Up, Directions.Down)
|
||||
ElseIf SolutionCompleted Then 'We're on the bottom row, can only go up
|
||||
Return GoUp()
|
||||
Else 'We're on the bottom row, can only go up, but there's no solution
|
||||
Return SelectRandomDirection(Directions.Up, Directions.SolveAndReset)
|
||||
End If
|
||||
'== Definitely can go Up and Right ==
|
||||
ElseIf BlockedBelow Then
|
||||
Return SelectRandomDirection(Directions.Up, Directions.Right)
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Up, Directions.Right, Directions.Down)
|
||||
ElseIf SolutionCompleted Then 'at the bottom, but already have a solution
|
||||
Return SelectRandomDirection(Directions.Up, Directions.Right)
|
||||
Else
|
||||
Return SelectRandomDirection(Directions.Up, Directions.Right, Directions.SolveAndReset)
|
||||
End If
|
||||
End Function
|
||||
|
||||
Public Function ChoosePath_BlockedAbove() As Boolean
|
||||
'No need to check the left side, only called from the "keepGoing" loop where LEFT is already cleared
|
||||
If BlockedRight Then
|
||||
If BlockedBelow Then
|
||||
Return GoLeft()
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Down)
|
||||
ElseIf SolutionCompleted Then 'Can't go down because there's already a solution
|
||||
Return GoLeft()
|
||||
Else 'At the bottom, no solution yet...
|
||||
Return SelectRandomDirection(Directions.Left, Directions.SolveAndReset)
|
||||
End If
|
||||
ElseIf BlockedBelow Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Right)
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Right, Directions.Down)
|
||||
ElseIf SolutionCompleted Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Right)
|
||||
Else
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Right, Directions.SolveAndReset)
|
||||
End If
|
||||
End Function
|
||||
|
||||
Public Function ChoosePath_BlockedToTheRight() As Boolean
|
||||
'No need to check Left or Up, only called from the "keepGoing" loop where LEFT and UP are already cleared
|
||||
If BlockedRight Then 'Can't go Right -- why? we knew this when calling the function
|
||||
If BlockedBelow Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Up)
|
||||
ElseIf Not OnBottomRow Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.Down)
|
||||
ElseIf SolutionCompleted Then
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Up)
|
||||
Else
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.SolveAndReset)
|
||||
End If
|
||||
Else 'Should never get here
|
||||
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.Right) 'Go Left, Up, or Right (but path is blocked?)
|
||||
End If
|
||||
End Function
|
||||
|
||||
Public Sub PrintFinalResults(startPos As Integer)
|
||||
For i As Integer = 0 To Width - 1
|
||||
If i = startPos Then Console.Write(". ") Else Console.Write(".--")
|
||||
Next
|
||||
Console.WriteLine(".")
|
||||
|
||||
If Not SolutionCompleted Then 'Pick a random exit
|
||||
Dim X As Integer = rnd.Next(1, Width + 1)
|
||||
If CellState(X, Height) = 0 Then
|
||||
CellState(X, Height) = 1
|
||||
Else
|
||||
CellState(X, Height) = 3
|
||||
End If
|
||||
End If
|
||||
|
||||
For j As Integer = 1 To Height
|
||||
Console.Write("I")
|
||||
For i As Integer = 1 To Width
|
||||
If CellState(i, j) < 2 Then
|
||||
Console.Write(" I")
|
||||
Else
|
||||
Console.Write(" ")
|
||||
End If
|
||||
Next
|
||||
Console.WriteLine()
|
||||
|
||||
For i As Integer = 1 To Width
|
||||
If CellState(i, j) = 0 OrElse CellState(i, j) = 2 Then
|
||||
Console.Write(":--")
|
||||
Else
|
||||
Console.Write(": ")
|
||||
End If
|
||||
Next
|
||||
Console.WriteLine(".")
|
||||
Next
|
||||
End Sub
|
||||
|
||||
Public Function GoLeft() As Boolean
|
||||
curCol -= 1
|
||||
CellVisitHistory(curCol, curRow) = CellsVisited
|
||||
CellsVisited += 1
|
||||
CellState(curCol, curRow) = 2
|
||||
If CellsVisited > Width * Height Then Return False
|
||||
Q = 0
|
||||
Return True
|
||||
End Function
|
||||
|
||||
Public Function GoUp() As Boolean
|
||||
curRow -= 1
|
||||
CellVisitHistory(curCol, curRow) = CellsVisited
|
||||
CellsVisited += 1
|
||||
CellState(curCol, curRow) = 1
|
||||
If CellsVisited > Width * Height Then Return False
|
||||
Q = 0
|
||||
Return True
|
||||
End Function
|
||||
|
||||
Public Function GoRight() As Boolean
|
||||
CellVisitHistory(curCol + 1, curRow) = CellsVisited
|
||||
CellsVisited += 1
|
||||
If CellState(curCol, curRow) = 0 Then CellState(curCol, curRow) = 2 Else CellState(curCol, curRow) = 3
|
||||
curCol += 1
|
||||
If CellsVisited > Width * Height Then Return False
|
||||
Return ChoosePath_BlockedToTheLeft()
|
||||
End Function
|
||||
|
||||
Public Function GoDown() As Boolean
|
||||
If Q = 1 Then Return MarkSolvedAndResetPosition()
|
||||
|
||||
CellVisitHistory(curCol, curRow + 1) = CellsVisited
|
||||
CellsVisited += 1
|
||||
If CellState(curCol, curRow) = 0 Then CellState(curCol, curRow) = 1 Else CellState(curCol, curRow) = 3
|
||||
curRow += 1
|
||||
If CellsVisited > Width * Height Then Return False
|
||||
Return True
|
||||
End Function
|
||||
|
||||
Public Function MarkSolvedAndResetPosition() As Boolean
|
||||
' AlWAYS returns true
|
||||
SolutionCompleted = True
|
||||
Q = 1
|
||||
If CellState(curCol, curRow) = 0 Then
|
||||
CellState(curCol, curRow) = 1
|
||||
curCol = 1
|
||||
curRow = 1
|
||||
If CellVisitHistory(curCol, curRow) = 0 Then ResetCurrentPosition()
|
||||
Else
|
||||
CellState(curCol, curRow) = 3
|
||||
ResetCurrentPosition()
|
||||
End If
|
||||
Return True
|
||||
End Function
|
||||
End Module
|
||||
@@ -1,160 +0,0 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* ANIMAL
|
||||
* <p>
|
||||
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
|
||||
*/
|
||||
public class Animal {
|
||||
|
||||
public static void main(String[] args) {
|
||||
printIntro();
|
||||
Scanner scan = new Scanner(System.in);
|
||||
|
||||
List<Question> questions = new ArrayList<>();
|
||||
questions.add(new Question("DOES IT SWIM", "FISH", "BIRD"));
|
||||
|
||||
boolean stopGame = false;
|
||||
while (!stopGame) {
|
||||
String choice = readMainChoice(scan);
|
||||
switch (choice) {
|
||||
case "LIST":
|
||||
printKnownAnimals(questions);
|
||||
break;
|
||||
case "Q":
|
||||
case "QUIT":
|
||||
stopGame = true;
|
||||
break;
|
||||
default:
|
||||
if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
|
||||
int k = 0;
|
||||
boolean correctGuess = false;
|
||||
while (questions.size() > k && !correctGuess) {
|
||||
Question question = questions.get(k);
|
||||
correctGuess = askQuestion(question, scan);
|
||||
if (correctGuess) {
|
||||
System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
|
||||
} else {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!correctGuess) {
|
||||
askForInformationAndSave(scan, questions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void askForInformationAndSave(Scanner scan, List<Question> questions) {
|
||||
//Failed to get it right and ran out of questions
|
||||
//Let's ask the user for the new information
|
||||
System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ");
|
||||
String animal = scan.nextLine();
|
||||
System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, questions.get(
|
||||
questions.size() - 1).falseAnswer);
|
||||
String newQuestion = scan.nextLine();
|
||||
System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal);
|
||||
boolean newAnswer = readYesOrNo(scan);
|
||||
//Add it to our list
|
||||
addNewAnimal(questions, animal, newQuestion, newAnswer);
|
||||
}
|
||||
|
||||
private static void addNewAnimal(List<Question> questions, String animal, String newQuestion, boolean newAnswer) {
|
||||
Question lastQuestion = questions.get(questions.size() - 1);
|
||||
String lastAnimal = lastQuestion.falseAnswer;
|
||||
lastQuestion.falseAnswer = null; //remove the false option to indicate that there is a next question
|
||||
|
||||
Question newOption;
|
||||
if (newAnswer) {
|
||||
newOption = new Question(newQuestion, animal, lastAnimal);
|
||||
} else {
|
||||
newOption = new Question(newQuestion, lastAnimal, animal);
|
||||
}
|
||||
questions.add(newOption);
|
||||
}
|
||||
|
||||
private static boolean askQuestion(Question question, Scanner scanner) {
|
||||
System.out.printf("%s ? ", question.question);
|
||||
|
||||
boolean chosenAnswer = readYesOrNo(scanner);
|
||||
if (chosenAnswer) {
|
||||
if (question.trueAnswer != null) {
|
||||
System.out.printf("IS IT A %s ? ", question.trueAnswer);
|
||||
return readYesOrNo(scanner);
|
||||
}
|
||||
//else go to the next question
|
||||
} else {
|
||||
if (question.falseAnswer != null) {
|
||||
System.out.printf("IS IT A %s ? ", question.falseAnswer);
|
||||
return readYesOrNo(scanner);
|
||||
}
|
||||
//else go to the next question
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean readYesOrNo(Scanner scanner) {
|
||||
boolean validAnswer = false;
|
||||
Boolean choseAnswer = null;
|
||||
while (!validAnswer) {
|
||||
String answer = scanner.nextLine();
|
||||
if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
|
||||
validAnswer = true;
|
||||
choseAnswer = true;
|
||||
} else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
|
||||
validAnswer = true;
|
||||
choseAnswer = false;
|
||||
}
|
||||
}
|
||||
return choseAnswer;
|
||||
}
|
||||
|
||||
private static void printKnownAnimals(List<Question> questions) {
|
||||
System.out.println("\nANIMALS I ALREADY KNOW ARE:");
|
||||
List<String> animals = new ArrayList<>();
|
||||
questions.forEach(q -> {
|
||||
if (q.trueAnswer != null) {
|
||||
animals.add(q.trueAnswer);
|
||||
}
|
||||
if (q.falseAnswer != null) {
|
||||
animals.add(q.falseAnswer);
|
||||
}
|
||||
});
|
||||
System.out.println(String.join("\t\t", animals));
|
||||
}
|
||||
|
||||
private static String readMainChoice(Scanner scan) {
|
||||
System.out.print("ARE YOU THINKING OF AN ANIMAL ? ");
|
||||
return scan.nextLine();
|
||||
}
|
||||
|
||||
private static void printIntro() {
|
||||
System.out.println(" ANIMAL");
|
||||
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
System.out.println("\n\n");
|
||||
System.out.println("PLAY 'GUESS THE ANIMAL'");
|
||||
System.out.println("\n");
|
||||
System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
|
||||
}
|
||||
|
||||
|
||||
public static class Question {
|
||||
String question;
|
||||
String trueAnswer;
|
||||
String falseAnswer;
|
||||
|
||||
public Question(String question, String trueAnswer, String falseAnswer) {
|
||||
this.question = question;
|
||||
this.trueAnswer = trueAnswer;
|
||||
this.falseAnswer = falseAnswer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* ANIMAL
|
||||
* <p>
|
||||
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
|
||||
* The original BASIC program uses an array to maintain the questions and answers and to decide which question to
|
||||
* ask next. Updated this Java implementation to use a tree instead of the earlier faulty one based on a list (thanks @patimen).
|
||||
*
|
||||
* Bonus option: TREE --> prints the game decision data as a tree to visualize/debug the state of the game
|
||||
*/
|
||||
public class Animal {
|
||||
|
||||
public static void main(String[] args) {
|
||||
printIntro();
|
||||
Scanner scan = new Scanner(System.in);
|
||||
|
||||
Node root = new QuestionNode("DOES IT SWIM",
|
||||
new AnimalNode("FISH"), new AnimalNode("BIRD"));
|
||||
|
||||
boolean stopGame = false;
|
||||
while (!stopGame) {
|
||||
String choice = readMainChoice(scan);
|
||||
switch (choice) {
|
||||
case "TREE":
|
||||
printTree(root);
|
||||
break;
|
||||
case "LIST":
|
||||
printKnownAnimals(root);
|
||||
break;
|
||||
case "Q":
|
||||
case "QUIT":
|
||||
stopGame = true;
|
||||
break;
|
||||
default:
|
||||
if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
|
||||
Node current = root; //where we are in the question tree
|
||||
Node previous; //keep track of parent of current in order to place new questions later on.
|
||||
|
||||
while (current instanceof QuestionNode) {
|
||||
var currentQuestion = (QuestionNode) current;
|
||||
var reply = askQuestionAndGetReply(currentQuestion, scan);
|
||||
|
||||
previous = current;
|
||||
current = reply ? currentQuestion.getTrueAnswer() : currentQuestion.getFalseAnswer();
|
||||
if (current instanceof AnimalNode) {
|
||||
//We have reached a animal node, so offer it as the guess
|
||||
var currentAnimal = (AnimalNode) current;
|
||||
System.out.printf("IS IT A %s ? ", currentAnimal.getAnimal());
|
||||
var animalGuessResponse = readYesOrNo(scan);
|
||||
if (animalGuessResponse) {
|
||||
//we guessed right! end this round
|
||||
System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
|
||||
} else {
|
||||
//we guessed wrong :(, ask for feedback
|
||||
//cast previous to QuestionNode since we know at this point that it is not a leaf node
|
||||
askForInformationAndSave(scan, currentAnimal, (QuestionNode) previous, reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for information about the animal we got wrong
|
||||
* @param current The animal that we guessed wrong
|
||||
* @param previous The root of current
|
||||
* @param previousToCurrentDecisionChoice Whether it was a Y or N answer that got us here. true = Y, false = N
|
||||
*/
|
||||
private static void askForInformationAndSave(Scanner scan, AnimalNode current, QuestionNode previous, boolean previousToCurrentDecisionChoice) {
|
||||
//Failed to get it right and ran out of questions
|
||||
//Let's ask the user for the new information
|
||||
System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ? ");
|
||||
String animal = scan.nextLine();
|
||||
System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ? ", animal, current.getAnimal());
|
||||
String newQuestion = scan.nextLine();
|
||||
System.out.printf("FOR A %s THE ANSWER WOULD BE ? ", animal);
|
||||
boolean newAnswer = readYesOrNo(scan);
|
||||
//Add it to our question store
|
||||
addNewAnimal(current, previous, animal, newQuestion, newAnswer, previousToCurrentDecisionChoice);
|
||||
}
|
||||
|
||||
private static void addNewAnimal(Node current,
|
||||
QuestionNode previous,
|
||||
String animal,
|
||||
String newQuestion,
|
||||
boolean newAnswer,
|
||||
boolean previousToCurrentDecisionChoice) {
|
||||
var animalNode = new AnimalNode(animal);
|
||||
var questionNode = new QuestionNode(newQuestion,
|
||||
newAnswer ? animalNode : current,
|
||||
!newAnswer ? animalNode : current);
|
||||
|
||||
if (previous != null) {
|
||||
if (previousToCurrentDecisionChoice) {
|
||||
previous.setTrueAnswer(questionNode);
|
||||
} else {
|
||||
previous.setFalseAnswer(questionNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean askQuestionAndGetReply(QuestionNode questionNode, Scanner scanner) {
|
||||
System.out.printf("%s ? ", questionNode.question);
|
||||
return readYesOrNo(scanner);
|
||||
}
|
||||
|
||||
private static boolean readYesOrNo(Scanner scanner) {
|
||||
boolean validAnswer = false;
|
||||
Boolean choseAnswer = null;
|
||||
while (!validAnswer) {
|
||||
String answer = scanner.nextLine();
|
||||
if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
|
||||
validAnswer = true;
|
||||
choseAnswer = true;
|
||||
} else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
|
||||
validAnswer = true;
|
||||
choseAnswer = false;
|
||||
}
|
||||
}
|
||||
return choseAnswer;
|
||||
}
|
||||
|
||||
private static void printKnownAnimals(Node root) {
|
||||
System.out.println("\nANIMALS I ALREADY KNOW ARE:");
|
||||
|
||||
List<AnimalNode> leafNodes = collectLeafNodes(root);
|
||||
String allAnimalsString = leafNodes.stream().map(AnimalNode::getAnimal).collect(Collectors.joining("\t\t"));
|
||||
|
||||
System.out.println(allAnimalsString);
|
||||
}
|
||||
|
||||
//Traverse the tree and collect all the leaf nodes, which basically have all the animals.
|
||||
private static List<AnimalNode> collectLeafNodes(Node root) {
|
||||
List<AnimalNode> collectedNodes = new ArrayList<>();
|
||||
if (root instanceof AnimalNode) {
|
||||
collectedNodes.add((AnimalNode) root);
|
||||
} else {
|
||||
var q = (QuestionNode) root;
|
||||
collectedNodes.addAll(collectLeafNodes(q.getTrueAnswer()));
|
||||
collectedNodes.addAll(collectLeafNodes(q.getFalseAnswer()));
|
||||
}
|
||||
return collectedNodes;
|
||||
}
|
||||
|
||||
private static String readMainChoice(Scanner scan) {
|
||||
System.out.print("ARE YOU THINKING OF AN ANIMAL ? ");
|
||||
return scan.nextLine();
|
||||
}
|
||||
|
||||
private static void printIntro() {
|
||||
System.out.println(" ANIMAL");
|
||||
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
System.out.println("\n\n");
|
||||
System.out.println("PLAY 'GUESS THE ANIMAL'");
|
||||
System.out.println("\n");
|
||||
System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
|
||||
}
|
||||
|
||||
//Based on https://stackoverflow.com/a/8948691/74057
|
||||
private static void printTree(Node root) {
|
||||
StringBuilder buffer = new StringBuilder(50);
|
||||
print(root, buffer, "", "");
|
||||
System.out.println(buffer);
|
||||
}
|
||||
|
||||
private static void print(Node root, StringBuilder buffer, String prefix, String childrenPrefix) {
|
||||
buffer.append(prefix);
|
||||
buffer.append(root.toString());
|
||||
buffer.append('\n');
|
||||
|
||||
if (root instanceof QuestionNode) {
|
||||
var questionNode = (QuestionNode) root;
|
||||
print(questionNode.getTrueAnswer(), buffer, childrenPrefix + "├─Y─ ", childrenPrefix + "│ ");
|
||||
print(questionNode.getFalseAnswer(), buffer, childrenPrefix + "└─N─ ", childrenPrefix + " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base interface for all nodes in our question tree
|
||||
*/
|
||||
private interface Node {
|
||||
}
|
||||
|
||||
private static class QuestionNode implements Node {
|
||||
private final String question;
|
||||
private Node trueAnswer;
|
||||
private Node falseAnswer;
|
||||
|
||||
public QuestionNode(String question, Node trueAnswer, Node falseAnswer) {
|
||||
this.question = question;
|
||||
this.trueAnswer = trueAnswer;
|
||||
this.falseAnswer = falseAnswer;
|
||||
}
|
||||
|
||||
public String getQuestion() {
|
||||
return question;
|
||||
}
|
||||
|
||||
public Node getTrueAnswer() {
|
||||
return trueAnswer;
|
||||
}
|
||||
|
||||
public void setTrueAnswer(Node trueAnswer) {
|
||||
this.trueAnswer = trueAnswer;
|
||||
}
|
||||
|
||||
public Node getFalseAnswer() {
|
||||
return falseAnswer;
|
||||
}
|
||||
|
||||
public void setFalseAnswer(Node falseAnswer) {
|
||||
this.falseAnswer = falseAnswer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Question{'" + question + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
private static class AnimalNode implements Node {
|
||||
private final String animal;
|
||||
|
||||
public AnimalNode(String animal) {
|
||||
this.animal = animal;
|
||||
}
|
||||
|
||||
public String getAnimal() {
|
||||
return animal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Animal{'" + animal + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import com.pcholt.console.testutils.ConsoleTest
|
||||
import org.junit.Test
|
||||
|
||||
class AnimalJavaTest : ConsoleTest() {
|
||||
|
||||
@Test
|
||||
fun `given a standard setup, find the fish`() {
|
||||
assertConversation(
|
||||
"""
|
||||
$title
|
||||
ARE YOU THINKING OF AN ANIMAL ? {YES}
|
||||
DOES IT SWIM ? {YES}
|
||||
IS IT A FISH ? {YES}
|
||||
WHY NOT TRY ANOTHER ANIMAL?
|
||||
ARE YOU THINKING OF AN ANIMAL ? {QUIT}
|
||||
"""
|
||||
) {
|
||||
Animal.main(emptyArray())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a standard setup, create a cow, and verify`() {
|
||||
assertConversation(
|
||||
"""
|
||||
$title
|
||||
ARE YOU THINKING OF AN ANIMAL ? {YES}
|
||||
DOES IT SWIM ? {NO}
|
||||
IS IT A BIRD ? {NO}
|
||||
THE ANIMAL YOU WERE THINKING OF WAS A ? {COW}
|
||||
PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A
|
||||
COW FROM A BIRD
|
||||
? {DOES IT EAT GRASS}
|
||||
FOR A COW THE ANSWER WOULD BE ? {YES}
|
||||
ARE YOU THINKING OF AN ANIMAL ? {YES}
|
||||
DOES IT SWIM ? {NO}
|
||||
DOES IT EAT GRASS ? {YES}
|
||||
IS IT A COW ? {YES}
|
||||
WHY NOT TRY ANOTHER ANIMAL?
|
||||
ARE YOU THINKING OF AN ANIMAL ? {QUIT}
|
||||
"""
|
||||
) {
|
||||
Animal.main(emptyArray())
|
||||
}
|
||||
}
|
||||
|
||||
private val title = """
|
||||
ANIMAL
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
PLAY 'GUESS THE ANIMAL'
|
||||
THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.
|
||||
"""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import com.pcholt.console.testutils.ConsoleTest
|
||||
import org.junit.Test
|
||||
|
||||
class AnimalKtTest : ConsoleTest() {
|
||||
|
||||
@Test
|
||||
fun `given a standard setup, find the fish`() {
|
||||
assertConversation(
|
||||
"""
|
||||
$title
|
||||
ARE YOU THINKING OF AN ANIMAL? {YES}
|
||||
DOES IT SWIM? {YES}
|
||||
IS IT A FISH? {YES}
|
||||
WHY NOT TRY ANOTHER ANIMAL?
|
||||
ARE YOU THINKING OF AN ANIMAL? {QUIT}
|
||||
"""
|
||||
) {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a standard setup, create a cow, and verify`() {
|
||||
assertConversation(
|
||||
"""
|
||||
$title
|
||||
ARE YOU THINKING OF AN ANIMAL? {YES}
|
||||
DOES IT SWIM? {NO}
|
||||
IS IT A BIRD? {NO}
|
||||
THE ANIMAL YOU WERE THINKING OF WAS A? {COW}
|
||||
PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A
|
||||
COW FROM A BIRD
|
||||
? {DOES IT EAT GRASS}
|
||||
FOR A COW THE ANSWER WOULD BE? {YES}
|
||||
ARE YOU THINKING OF AN ANIMAL? {YES}
|
||||
DOES IT SWIM? {NO}
|
||||
DOES IT EAT GRASS? {YES}
|
||||
IS IT A COW? {YES}
|
||||
WHY NOT TRY ANOTHER ANIMAL?
|
||||
ARE YOU THINKING OF AN ANIMAL? {QUIT}
|
||||
"""
|
||||
) {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
private val title = """
|
||||
ANIMAL
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
PLAY 'GUESS THE ANIMAL'
|
||||
THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.
|
||||
"""
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,7 @@
|
||||
''' <summary>
|
||||
''' <para>Indicates that there are no more inputs in the MockConsole.</para>
|
||||
''' We need this while testing, because otherwise the game loop will continue forever, waiting for a nonexistent input.
|
||||
''' </summary>
|
||||
Public Class EndOfInputsException
|
||||
Inherits Exception
|
||||
End Class
|
||||
@@ -0,0 +1,60 @@
|
||||
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()
|
||||
Lines.Add(("", False))
|
||||
End Sub
|
||||
|
||||
Public Overrides Sub WriteCenteredLines(value As Object)
|
||||
If Lines.Count = 0 Then Lines.Add(("", False))
|
||||
Dim currentLast = Lines(Lines.Count - 1).line
|
||||
If currentLast.Length > 0 Then Lines.Add(("", False))
|
||||
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 EndOfInputsException
|
||||
|
||||
Dim nextInput = inputs.Dequeue.Trim
|
||||
WriteLine(nextInput)
|
||||
Return nextInput
|
||||
End Function
|
||||
End Class
|
||||
@@ -0,0 +1,118 @@
|
||||
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 EndOfInputsException)(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 EndOfInputsException)(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 EndOfInputsException)(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
|
||||
|
||||
''' <summary>Test adding a new animal and using the new animal in the game</summary>
|
||||
<Fact>
|
||||
Sub TestAddedAnimal()
|
||||
Dim console As New MockConsole({
|
||||
"y",
|
||||
"y",
|
||||
"n",
|
||||
"whale",
|
||||
"is it a mammal?",
|
||||
"y",
|
||||
"y",
|
||||
"y",
|
||||
"y",
|
||||
"y"
|
||||
})
|
||||
Dim game As New Game(console)
|
||||
Assert.Throws(Of EndOfInputsException)(Sub() game.BeginLoop())
|
||||
Assert.Equal(
|
||||
{
|
||||
"ARE YOU THINKING OF AN ANIMAL? y",
|
||||
"DOES IT SWIM? y",
|
||||
"IS IT A FISH? n",
|
||||
"THE ANIMAL YOU WERE THINKING OF WAS A ? whale",
|
||||
"PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A",
|
||||
"WHALE FROM A FISH",
|
||||
"is it a mammal?",
|
||||
"FOR A WHALE THE ANSWER WOULD BE? y",
|
||||
"ARE YOU THINKING OF AN ANIMAL? y",
|
||||
"DOES IT SWIM? y",
|
||||
"IS IT A MAMMAL? y",
|
||||
"IS IT A WHALE? y",
|
||||
"WHY NOT TRY ANOTHER ANIMAL?",
|
||||
"ARE YOU THINKING OF AN ANIMAL? "
|
||||
},
|
||||
console.Lines.Slice(9, 100).Select(Function(x) x.line)
|
||||
)
|
||||
End Sub
|
||||
End Class
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32112.339
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
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(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {88469A47-E30C-4763-A325-074101D16608}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Animal</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
<OptionStrict>On</OptionStrict>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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
|
||||
@@ -0,0 +1,158 @@
|
||||
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.WriteCenteredLines(
|
||||
"ANIMAL
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
|
||||
|
||||
' Print the program description
|
||||
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.Write(
|
||||
"
|
||||
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.Write(
|
||||
"
|
||||
|
||||
")
|
||||
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 block
|
||||
' The computer guessed the animal; we can go back to the beginning of the game
|
||||
console.WriteLine("WHY NOT TRY ANOTHER ANIMAL?")
|
||||
Continue Do
|
||||
End If
|
||||
|
||||
' Get the new animal from the user
|
||||
console.Write("THE ANIMAL YOU WERE THINKING OF WAS A ? ")
|
||||
Dim newAnimal = console.ReadLine.ToUpperInvariant
|
||||
|
||||
' Get the question used to distinguish the new animal from the current end branch
|
||||
console.Write(
|
||||
$"PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A
|
||||
{newAnimal} FROM A {currentBranch.Text}
|
||||
")
|
||||
Dim newQuestion = console.ReadLine.ToUpperInvariant
|
||||
|
||||
' Get the answer to that question, for the new animal
|
||||
' for the old animal, the answer will 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
|
||||
@@ -0,0 +1,6 @@
|
||||
Module Program
|
||||
Sub Main()
|
||||
Dim game As New Game(New ConsoleAdapter)
|
||||
game.BeginLoop()
|
||||
End Sub
|
||||
End Module
|
||||
@@ -0,0 +1,28 @@
|
||||
Public Class ConsoleAdapter
|
||||
Inherits ConsoleAdapterBase
|
||||
|
||||
Public Overrides Sub Write(value As Object)
|
||||
Console.Write(value)
|
||||
End Sub
|
||||
|
||||
Public Overrides Sub WriteLine()
|
||||
Console.WriteLine()
|
||||
End Sub
|
||||
|
||||
Public Overrides Sub WriteCenteredLines(value As Object)
|
||||
If Console.CursorLeft <> 0 Then WriteLine()
|
||||
Dim toWrite = If(value?.ToString, "")
|
||||
For Each line In toWrite.Split(Environment.NewLine)
|
||||
Write($"{Space((Console.WindowWidth - line.Length) \ 2)}{line}")
|
||||
WriteLine()
|
||||
Next
|
||||
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
|
||||
@@ -0,0 +1,13 @@
|
||||
Public MustInherit Class ConsoleAdapterBase
|
||||
Public MustOverride Sub Write(value As Object)
|
||||
Public MustOverride Sub WriteLine()
|
||||
Public MustOverride Sub WriteCenteredLines(value As Object)
|
||||
|
||||
''' <summary>Implementations should always return a String without leading or trailing whitespace, never Nothng</summary>
|
||||
Public MustOverride Function ReadLine() As String
|
||||
|
||||
Public Sub WriteLine(value As Object)
|
||||
Write(value)
|
||||
WriteLine()
|
||||
End Sub
|
||||
End Class
|
||||
@@ -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
|
||||
@@ -1,3 +1,9 @@
|
||||
Original BASIC source [downloaded from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Visual Basic .NET](https://en.wikipedia.org/wiki/Visual_Basic_.NET)
|
||||
|
||||
This takes some inspiration from the [C# port of Animal](https://github.com/zspitz/basic-computer-games/tree/main/03_Animal/csharp).
|
||||
|
||||
The `Game` class takes a console abstraction (`ConsoleAdapterBase`), which could also be used for different UIs, such as WinForms or a web page.
|
||||
This solution also has an xUnit tests project.
|
||||
Responses can be entered in any capitalization, but animals and the distinguishing question will be converted to uppercase.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Awari", "Awari.csproj", "{DD161F58-D90F-481A-8275-96E01D229A70}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DD161F58-D90F-481A-8275-96E01D229A70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DD161F58-D90F-481A-8275-96E01D229A70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DD161F58-D90F-481A-8275-96E01D229A70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DD161F58-D90F-481A-8275-96E01D229A70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7F5C288A-A6C6-4AC0-96E3-6A3B482A0947}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,3 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Ruby](https://www.ruby-lang.org/en/)
|
||||
Conversion to [Ruby](https://www.ruby-lang.org/en/) by [Alex Scown](https://github.com/TheScown)
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
require 'strscan'
|
||||
|
||||
# Prints a number according to Vintage Basic's PRINT statement
|
||||
# @param n The number to print
|
||||
def print_number(n)
|
||||
# PRINT adds padding after a number and before a positive number
|
||||
print ' ' if n >= 0
|
||||
print n.to_s
|
||||
print ' '
|
||||
end
|
||||
|
||||
# Mimic the INPUT statement using Vintage Basic as a reference
|
||||
# @param prompt The prompt to show to the user
|
||||
# @return An array of strings representing the inputted values
|
||||
def input(prompt)
|
||||
prompt_suffix = '? '
|
||||
print "#{prompt}#{prompt_suffix}"
|
||||
|
||||
input = gets.chomp.strip
|
||||
scanner = StringScanner.new(input)
|
||||
input_values = []
|
||||
|
||||
until scanner.eos?
|
||||
scanner.scan(/\s+/)
|
||||
|
||||
if scanner.check(/"/)
|
||||
scanner.scan(/"/)
|
||||
next_string = scanner.scan_until(/"/)
|
||||
|
||||
if next_string
|
||||
# Remove the trailing close quote
|
||||
next_string.chomp!('"')
|
||||
else
|
||||
# No close quote – Vintage Basic crashes in this case
|
||||
raise 'Unmatched quotes in input'
|
||||
end
|
||||
elsif scanner.exist?(/,/)
|
||||
next_string = scanner.scan_until(/,/).chomp(',')
|
||||
else
|
||||
next_string = scanner.scan_until(/\s+|$/).rstrip
|
||||
end
|
||||
|
||||
input_values << next_string
|
||||
end
|
||||
|
||||
input_values << '' if input_values.empty?
|
||||
|
||||
input_values
|
||||
end
|
||||
|
||||
class Game
|
||||
def initialize(history, non_win_count)
|
||||
@beans = Array.new(13, 3)
|
||||
@beans[6] = 0
|
||||
@beans[13] = 0
|
||||
|
||||
@turn_counter = 0
|
||||
|
||||
@history = history
|
||||
@non_win_count = non_win_count
|
||||
end
|
||||
|
||||
# @return [Boolean] True if the computer did not win the game
|
||||
def play
|
||||
while true
|
||||
print_beans
|
||||
|
||||
move = get_move("YOUR MOVE")
|
||||
home_pit = 6
|
||||
computer_home_pit = 13
|
||||
|
||||
last_pit = perform_move(move, home_pit)
|
||||
|
||||
print_beans
|
||||
|
||||
break if game_over
|
||||
|
||||
if home_pit == last_pit
|
||||
second_move = get_move("AGAIN")
|
||||
|
||||
perform_move(second_move, home_pit)
|
||||
|
||||
print_beans
|
||||
|
||||
break if game_over
|
||||
end
|
||||
|
||||
computer_move, computer_last_pit = get_computer_move
|
||||
print "MY MOVE IS #{computer_move - 6}"
|
||||
|
||||
break if game_over
|
||||
|
||||
if computer_last_pit == computer_home_pit
|
||||
second_computer_move, _ = get_computer_move
|
||||
print ",#{second_computer_move - 6}"
|
||||
|
||||
break if game_over
|
||||
end
|
||||
end
|
||||
|
||||
end_game
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def game_over
|
||||
@beans[0...6].all? { |b| b == 0 } || @beans[7...13].all? { |b| b == 0 }
|
||||
end
|
||||
|
||||
# @return [Boolean] True if the computer did not win
|
||||
def end_game
|
||||
puts
|
||||
puts "GAME OVER"
|
||||
|
||||
difference = @beans[6] - @beans[13]
|
||||
|
||||
if difference < 0
|
||||
puts "I WIN BY #{-difference} POINTS"
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
puts "YOU WIN BY #{difference} POINTS" if difference > 0
|
||||
puts "DRAWN GAME" if difference == 0
|
||||
|
||||
difference >= 0
|
||||
end
|
||||
|
||||
# @param [Integer] move
|
||||
# @param [Integer] home_pit
|
||||
def perform_move(move, home_pit)
|
||||
last_pit = distribute_beans(move, home_pit)
|
||||
|
||||
update_history(move)
|
||||
|
||||
last_pit
|
||||
end
|
||||
|
||||
def update_history(current_move)
|
||||
k = current_move % 7
|
||||
@turn_counter += 1
|
||||
|
||||
# Add the move to the history
|
||||
@history[@non_win_count] = @history[@non_win_count] * 6 + k if @turn_counter < 9
|
||||
end
|
||||
|
||||
def print_beans
|
||||
puts
|
||||
|
||||
# Print computer beans
|
||||
print ' ' * 3
|
||||
@beans[7...13].reverse.each { |bean_count| print_bean(bean_count) }
|
||||
puts
|
||||
|
||||
# Print home beans
|
||||
print_bean(@beans[13])
|
||||
print ' ' * 23
|
||||
print_number(@beans[6]) # This is not print_bean in line with the original version
|
||||
puts
|
||||
|
||||
# Print player beans
|
||||
print ' ' * 3
|
||||
@beans[0...6].each { |bean_count| print_bean(bean_count) }
|
||||
puts
|
||||
|
||||
puts
|
||||
end
|
||||
|
||||
def get_move(prompt)
|
||||
move = get_integer_input(prompt)
|
||||
|
||||
while move < 1 || move > 6 || @beans[move - 1] == 0
|
||||
puts "ILLEGAL MOVE"
|
||||
move = get_integer_input("AGAIN")
|
||||
end
|
||||
|
||||
move - 1
|
||||
end
|
||||
|
||||
def distribute_beans(start_pit, home_pit, beans = @beans)
|
||||
beans_to_distribute = beans[start_pit]
|
||||
beans[start_pit] = 0
|
||||
|
||||
current_pit = start_pit
|
||||
|
||||
(0...beans_to_distribute).each do
|
||||
current_pit = (current_pit + 1) % beans.size
|
||||
beans[current_pit] += 1
|
||||
end
|
||||
|
||||
# If the last pit was empty before we put a bean in it (and it's not a scoring pit), add beans to score
|
||||
if beans[current_pit] == 1 && current_pit != 6 && current_pit != 13 && beans[12 - current_pit] != 0
|
||||
beans[home_pit] = beans[home_pit] + beans[12 - current_pit] + 1
|
||||
beans[current_pit] = 0
|
||||
beans[12 - current_pit] = 0
|
||||
end
|
||||
|
||||
current_pit
|
||||
end
|
||||
|
||||
def print_bean(bean_count)
|
||||
print ' ' if bean_count < 10
|
||||
print_number(bean_count)
|
||||
end
|
||||
|
||||
def get_integer_input(prompt)
|
||||
integer_value = nil
|
||||
|
||||
input_values = input(prompt)
|
||||
|
||||
while integer_value.nil?
|
||||
print '!EXTRA INPUT IGNORED' if (input_values.size > 1)
|
||||
|
||||
value = input_values.first
|
||||
|
||||
begin
|
||||
integer_value = Integer(value)
|
||||
rescue
|
||||
puts '!NUMBER EXPECTED - RETRY INPUT LINE'
|
||||
input_values = input('')
|
||||
end
|
||||
end
|
||||
|
||||
integer_value
|
||||
end
|
||||
|
||||
def get_computer_move
|
||||
d = -99
|
||||
home_pit = 13
|
||||
|
||||
chosen_move = 7
|
||||
|
||||
# Test all possible moves
|
||||
(7...13).each do |move_under_test|
|
||||
# Create a copy of the beans to test against
|
||||
beans_copy = @beans.dup
|
||||
|
||||
# If the move is not legal, skip it
|
||||
next if beans_copy[move_under_test] == 0
|
||||
|
||||
# Determine the best response the player may make to this move
|
||||
player_max_score = 0
|
||||
|
||||
# Make the move under test against the copy
|
||||
distribute_beans(move_under_test, home_pit, beans_copy)
|
||||
|
||||
# Test every player response
|
||||
(0...6).each do |i|
|
||||
# Skip the move if it would be illegal
|
||||
next if beans_copy[i] == 0
|
||||
|
||||
# Determine the last
|
||||
landing_with_overflow = beans_copy[i] + i
|
||||
# If landing > 13 the player has put a bean in both home pits
|
||||
player_move_score = (landing_with_overflow > 14) ? 1 : 0
|
||||
# Find the actual pit
|
||||
landing = landing_with_overflow % 14
|
||||
|
||||
# If the landing pit is empty, the player will steal beans
|
||||
if beans_copy[landing] == 0 && landing != 6 && landing != 13
|
||||
player_move_score = beans_copy[12 - landing] + player_move_score
|
||||
end
|
||||
|
||||
# Update the max score if this move is the best player move
|
||||
player_max_score = player_move_score if player_move_score > player_max_score
|
||||
end
|
||||
|
||||
# Final score for move is computer score, minus the player's score and any player gains from their best move
|
||||
final_score = beans_copy[13] - beans_copy[6] - player_max_score
|
||||
|
||||
if @turn_counter < 9
|
||||
k = move_under_test % 7
|
||||
|
||||
(0...@non_win_count).each do |i|
|
||||
# Penalise move if it was used in a losing game
|
||||
final_score = final_score - 2 if @history[@non_win_count] * 6 + k == ((Float(@history[i]) / 6 ** (7 - @turn_counter)) + 0.1).floor
|
||||
end
|
||||
end
|
||||
|
||||
# Choose the move if it is the best move found so far
|
||||
if final_score >= d
|
||||
chosen_move = move_under_test
|
||||
d = final_score
|
||||
end
|
||||
end
|
||||
|
||||
last_pit = perform_move(chosen_move, home_pit)
|
||||
|
||||
[chosen_move, last_pit]
|
||||
end
|
||||
end
|
||||
|
||||
puts 'AWARI'.center(80)
|
||||
puts 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'.center(80)
|
||||
|
||||
# Initialise stable variables
|
||||
history = Array.new(50)
|
||||
non_win_count = 0
|
||||
|
||||
# APPLICATION LOOP
|
||||
while true
|
||||
puts
|
||||
puts
|
||||
|
||||
history[non_win_count] = 0
|
||||
|
||||
game = Game.new(history, non_win_count)
|
||||
|
||||
computer_didnt_win = game.play
|
||||
non_win_count += 1 if computer_didnt_win
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Awari", "Awari.vbproj", "{718AECEB-CC24-49C8-B620-B2F93E021C51}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{718AECEB-CC24-49C8-B620-B2F93E021C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{718AECEB-CC24-49C8-B620-B2F93E021C51}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{718AECEB-CC24-49C8-B620-B2F93E021C51}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{718AECEB-CC24-49C8-B620-B2F93E021C51}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Awari</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -3,7 +3,6 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>BasicComputerGames.Bagels</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bagels", "Bagels.csproj", "{2FC5F33F-2C4B-4707-94E5-3C9B2B633EFE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2FC5F33F-2C4B-4707-94E5-3C9B2B633EFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2FC5F33F-2C4B-4707-94E5-3C9B2B633EFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2FC5F33F-2C4B-4707-94E5-3C9B2B633EFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2FC5F33F-2C4B-4707-94E5-3C9B2B633EFE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Bagels", "Bagels.vbproj", "{913FE7A8-B481-4EB3-8938-566BEAD5B0F2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{913FE7A8-B481-4EB3-8938-566BEAD5B0F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{913FE7A8-B481-4EB3-8938-566BEAD5B0F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{913FE7A8-B481-4EB3-8938-566BEAD5B0F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{913FE7A8-B481-4EB3-8938-566BEAD5B0F2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Bagels</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31321.278
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "banner", "banner.csproj", "{9E24FA30-F2AC-4BF3-ADFB-92D3F796561C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Banner", "Banner.csproj", "{7E8612AB-AFFD-4F72-855F-8172786F28FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -11,10 +11,10 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9E24FA30-F2AC-4BF3-ADFB-92D3F796561C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9E24FA30-F2AC-4BF3-ADFB-92D3F796561C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9E24FA30-F2AC-4BF3-ADFB-92D3F796561C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9E24FA30-F2AC-4BF3-ADFB-92D3F796561C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7E8612AB-AFFD-4F72-855F-8172786F28FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7E8612AB-AFFD-4F72-855F-8172786F28FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7E8612AB-AFFD-4F72-855F-8172786F28FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7E8612AB-AFFD-4F72-855F-8172786F28FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# Banner
|
||||
# reinterpreted from BASIC by stephan.com
|
||||
|
||||
# this implementation diverges from the original in some notable
|
||||
# ways, but maintains the same font definition as before as well
|
||||
# as the same somewhat bizarre way of interpreting it. It would
|
||||
# be more efficient to redesign the font to allow `"%09b" % row`
|
||||
# and then some substitutions.
|
||||
|
||||
FONT = {
|
||||
' ' => [0, 0, 0, 0, 0, 0, 0].freeze,
|
||||
'!' => [1, 1, 1, 384, 1, 1, 1].freeze,
|
||||
'*' => [69, 41, 17, 512, 17, 41, 69].freeze,
|
||||
'.' => [1, 1, 129, 449, 129, 1, 1].freeze,
|
||||
'0' => [57, 69, 131, 258, 131, 69, 57].freeze,
|
||||
'1' => [0, 0, 261, 259, 512, 257, 257].freeze,
|
||||
'2' => [261, 387, 322, 290, 274, 267, 261].freeze,
|
||||
'3' => [66, 130, 258, 274, 266, 150, 100].freeze,
|
||||
'4' => [33, 49, 41, 37, 35, 512, 33].freeze,
|
||||
'5' => [160, 274, 274, 274, 274, 274, 226].freeze,
|
||||
'6' => [194, 291, 293, 297, 305, 289, 193].freeze,
|
||||
'7' => [258, 130, 66, 34, 18, 10, 8].freeze,
|
||||
'8' => [69, 171, 274, 274, 274, 171, 69].freeze,
|
||||
'9' => [263, 138, 74, 42, 26, 10, 7].freeze,
|
||||
'=' => [41, 41, 41, 41, 41, 41, 41].freeze,
|
||||
'?' => [5, 3, 2, 354, 18, 11, 5].freeze,
|
||||
'a' => [505, 37, 35, 34, 35, 37, 505].freeze,
|
||||
'b' => [512, 274, 274, 274, 274, 274, 239].freeze,
|
||||
'c' => [125, 131, 258, 258, 258, 131, 69].freeze,
|
||||
'd' => [512, 258, 258, 258, 258, 131, 125].freeze,
|
||||
'e' => [512, 274, 274, 274, 274, 258, 258].freeze,
|
||||
'f' => [512, 18, 18, 18, 18, 2, 2].freeze,
|
||||
'g' => [125, 131, 258, 258, 290, 163, 101].freeze,
|
||||
'h' => [512, 17, 17, 17, 17, 17, 512].freeze,
|
||||
'i' => [258, 258, 258, 512, 258, 258, 258].freeze,
|
||||
'j' => [65, 129, 257, 257, 257, 129, 128].freeze,
|
||||
'k' => [512, 17, 17, 41, 69, 131, 258].freeze,
|
||||
'l' => [512, 257, 257, 257, 257, 257, 257].freeze,
|
||||
'm' => [512, 7, 13, 25, 13, 7, 512].freeze,
|
||||
'n' => [512, 7, 9, 17, 33, 193, 512].freeze,
|
||||
'o' => [125, 131, 258, 258, 258, 131, 125].freeze,
|
||||
'p' => [512, 18, 18, 18, 18, 18, 15].freeze,
|
||||
'q' => [125, 131, 258, 258, 322, 131, 381].freeze,
|
||||
'r' => [512, 18, 18, 50, 82, 146, 271].freeze,
|
||||
's' => [69, 139, 274, 274, 274, 163, 69].freeze,
|
||||
't' => [2, 2, 2, 512, 2, 2, 2].freeze,
|
||||
'u' => [128, 129, 257, 257, 257, 129, 128].freeze,
|
||||
'v' => [64, 65, 129, 257, 129, 65, 64].freeze,
|
||||
'w' => [256, 257, 129, 65, 129, 257, 256].freeze,
|
||||
'x' => [388, 69, 41, 17, 41, 69, 388].freeze,
|
||||
'y' => [8, 9, 17, 481, 17, 9, 8].freeze,
|
||||
'z' => [386, 322, 290, 274, 266, 262, 260].freeze
|
||||
}.freeze
|
||||
|
||||
puts 'horizontal'
|
||||
x = gets.strip.to_i
|
||||
puts 'vertical'
|
||||
y = gets.strip.to_i
|
||||
puts 'centered'
|
||||
centered = gets.strip.downcase.chars.first == 'y'
|
||||
puts 'character ("all" for character being printed)'
|
||||
fill = gets.strip.downcase
|
||||
puts 'statement'
|
||||
statement = gets.strip.downcase
|
||||
|
||||
all = (fill.downcase == 'all')
|
||||
lenxs = all ? 1 : fill.length
|
||||
start = 1
|
||||
start += (63 - 4.5 * y) / lenxs if centered
|
||||
|
||||
statement.each_char do |char|
|
||||
next puts "\n" * 7 * x if char == ' '
|
||||
|
||||
xs = all ? char : fill
|
||||
FONT[char].each do |su|
|
||||
print ' ' * start
|
||||
8.downto(0) do |k|
|
||||
if (1 << k) < su
|
||||
print xs * y
|
||||
su -= (1 << k)
|
||||
else
|
||||
print ' ' * (y * lenxs)
|
||||
end
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
(2 * x).times { puts }
|
||||
end
|
||||
75.times { puts }
|
||||
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31321.278
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "banner", "banner.vbproj", "{1738D297-A04C-4E6E-8219-D9E72982C39D}"
|
||||
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Banner", "Banner.vbproj", "{091ABE13-3E70-4848-B836-592F725915A3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -11,10 +11,10 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1738D297-A04C-4E6E-8219-D9E72982C39D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1738D297-A04C-4E6E-8219-D9E72982C39D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1738D297-A04C-4E6E-8219-D9E72982C39D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1738D297-A04C-4E6E-8219-D9E72982C39D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{091ABE13-3E70-4848-B836-592F725915A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{091ABE13-3E70-4848-B836-592F725915A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{091ABE13-3E70-4848-B836-592F725915A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{091ABE13-3E70-4848-B836-592F725915A3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>banner</RootNamespace>
|
||||
<RootNamespace>Banner</RootNamespace>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basketball", "Basketball.csproj", "{00D03FB3-B485-480F-B14D-746371BDE08B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{00D03FB3-B485-480F-B14D-746371BDE08B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{00D03FB3-B485-480F-B14D-746371BDE08B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{00D03FB3-B485-480F-B14D-746371BDE08B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{00D03FB3-B485-480F-B14D-746371BDE08B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,469 @@
|
||||
import java.lang.Math;
|
||||
import java.util.*;
|
||||
import java.util.Scanner;
|
||||
|
||||
/* The basketball class is a computer game that allows you to play as
|
||||
Dartmouth College's captain and playmaker
|
||||
The game uses set probabilites to simulate outcomes of each posession
|
||||
You are able to choose your shot types as well as defensive formations */
|
||||
|
||||
public class Basketball {
|
||||
int time = 0;
|
||||
int[] score = {0, 0};
|
||||
double defense = -1;
|
||||
List<Double> defense_choices = Arrays.asList(6.0, 6.5, 7.0, 7.5);
|
||||
int shot = -1;
|
||||
List<Integer> shot_choices = Arrays.asList(0, 1, 2, 3, 4);
|
||||
double opponent_chance = 0;
|
||||
String opponent = null;
|
||||
|
||||
public Basketball() {
|
||||
|
||||
// Explains the keyboard inputs
|
||||
System.out.println("\t\t\t Basketball");
|
||||
System.out.println("\t Creative Computing Morristown, New Jersey\n\n\n");
|
||||
System.out.println("This is Dartmouth College basketball. ");
|
||||
System.out.println("Υou will be Dartmouth captain and playmaker.");
|
||||
System.out.println("Call shots as follows:");
|
||||
System.out.println("1. Long (30ft.) Jump Shot; 2. Short (15 ft.) Jump Shot; "
|
||||
+ "3. Lay up; 4. Set Shot");
|
||||
System.out.println("Both teams will use the same defense. Call Defense as follows:");
|
||||
System.out.println("6. Press; 6.5 Man-to-Man; 7. Zone; 7.5 None.");
|
||||
System.out.println("To change defense, just type 0 as your next shot.");
|
||||
System.out.print("Your starting defense will be? ");
|
||||
|
||||
Scanner scanner = new Scanner(System.in); // creates a scanner
|
||||
|
||||
// takes input for a defense
|
||||
if (scanner.hasNextDouble()) {
|
||||
defense = scanner.nextDouble();
|
||||
}
|
||||
else {
|
||||
scanner.next();
|
||||
}
|
||||
|
||||
// makes sure that input is legal
|
||||
while (!defense_choices.contains(defense)) {
|
||||
System.out.print("Your new defensive allignment is? ");
|
||||
if (scanner.hasNextDouble()) {
|
||||
defense = scanner.nextDouble();
|
||||
}
|
||||
else {
|
||||
scanner.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// takes input for opponent's name
|
||||
System.out.print("\nChoose your opponent? ");
|
||||
|
||||
opponent = scanner.next();
|
||||
start_of_period();
|
||||
}
|
||||
|
||||
// adds points to the score
|
||||
// team can take 0 or 1, for opponent or Dartmouth, respectively
|
||||
private void add_points(int team, int points) {
|
||||
score[team] += points;
|
||||
print_score();
|
||||
}
|
||||
|
||||
|
||||
private void ball_passed_back() {
|
||||
System.out.print("Ball passed back to you. ");
|
||||
dartmouth_ball();
|
||||
}
|
||||
|
||||
// change defense, called when the user enters 0 for their shot
|
||||
private void change_defense() {
|
||||
defense = -1;
|
||||
Scanner scanner = new Scanner(System.in); // creates a scanner
|
||||
|
||||
while (!defense_choices.contains(defense)) {
|
||||
System.out.println("Your new defensive allignment is? ");
|
||||
if (scanner.hasNextDouble()) {
|
||||
defense = (double)(scanner.nextDouble());
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
dartmouth_ball();
|
||||
}
|
||||
|
||||
// simulates two foul shots for a player and adds the points
|
||||
private void foul_shots(int team) {
|
||||
System.out.println("Shooter fouled. Two shots.");
|
||||
|
||||
if (Math.random() > .49) {
|
||||
if (Math.random() > .75) {
|
||||
System.out.println("Both shots missed.");
|
||||
}
|
||||
else {
|
||||
System.out.println("Shooter makes one shot and misses one.");
|
||||
score[team] += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shooter makes both shots.");
|
||||
score[team] += 2;
|
||||
}
|
||||
|
||||
print_score();
|
||||
}
|
||||
|
||||
// called when time = 50, starts a new period
|
||||
private void halftime() {
|
||||
System.out.println("\n ***** End of first half *****\n");
|
||||
print_score();
|
||||
start_of_period();
|
||||
}
|
||||
|
||||
// prints the current score
|
||||
private void print_score() {
|
||||
System.out.println("Score: " + score[1] + " to " + score[0] + "\n");
|
||||
}
|
||||
|
||||
// simulates a center jump for posession at the beginning of a period
|
||||
private void start_of_period() {
|
||||
System.out.println("Center jump");
|
||||
if (Math.random() > .6) {
|
||||
System.out.println("Dartmouth controls the tap.\n");
|
||||
dartmouth_ball();
|
||||
}
|
||||
else {
|
||||
System.out.println(opponent + " controls the tap.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
|
||||
// called when t = 92
|
||||
private void two_minute_warning() {
|
||||
System.out.println(" *** Two minutes left in the game ***");
|
||||
}
|
||||
|
||||
// called when the user enters 1 or 2 for their shot
|
||||
private void dartmouth_jump_shot() {
|
||||
time ++;
|
||||
if (time == 50) {
|
||||
halftime();
|
||||
}
|
||||
else if (time == 92) {
|
||||
two_minute_warning();
|
||||
}
|
||||
|
||||
System.out.println("Jump Shot.");
|
||||
// simulates chances of different possible outcomes
|
||||
if (Math.random() > .341 * defense / 8) {
|
||||
if (Math.random() > .682 * defense / 8) {
|
||||
if (Math.random() > .782 * defense / 8) {
|
||||
if (Math.random() > .843 * defense / 8) {
|
||||
System.out.println("Charging foul. Dartmouth loses ball.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
// player is fouled
|
||||
foul_shots(1);
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Math.random() > .5) {
|
||||
System.out.println("Shot is blocked. Ball controlled by " +
|
||||
opponent + ".\n");
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is blocked. Ball controlled by Dartmouth.");
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is off target.");
|
||||
if (defense / 6 * Math.random() > .45) {
|
||||
System.out.println("Rebound to " + opponent + "\n");
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
System.out.println("Dartmouth controls the rebound.");
|
||||
if (Math.random() > .4) {
|
||||
if (defense == 6 && Math.random() > .6) {
|
||||
System.out.println("Pass stolen by " + opponent
|
||||
+ ", easy lay up");
|
||||
add_points(0, 2);
|
||||
dartmouth_ball();
|
||||
}
|
||||
else {
|
||||
// ball is passed back to you
|
||||
ball_passed_back();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("");
|
||||
dartmouth_non_jump_shot();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is good.");
|
||||
add_points(1, 2);
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
|
||||
// called when the user enters 0, 3, or 4
|
||||
// lay up, set shot, or defense change
|
||||
private void dartmouth_non_jump_shot() {
|
||||
time ++;
|
||||
if (time == 50) {
|
||||
halftime();
|
||||
}
|
||||
else if (time == 92) {
|
||||
two_minute_warning();
|
||||
}
|
||||
|
||||
if (shot == 4) {
|
||||
System.out.println("Set shot.");
|
||||
}
|
||||
else if (shot == 3) {
|
||||
System.out.println("Lay up.");
|
||||
}
|
||||
else if (shot == 0) {
|
||||
change_defense();
|
||||
}
|
||||
|
||||
// simulates different outcomes after a lay up or set shot
|
||||
if (7/defense*Math.random() > .4) {
|
||||
if (7/defense*Math.random() > .7) {
|
||||
if (7/defense*Math.random() > .875) {
|
||||
if (7/defense*Math.random() > .925) {
|
||||
System.out.println("Charging foul. Dartmouth loses the ball.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot blocked. " + opponent + "'s ball.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
else {
|
||||
foul_shots(1);
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is off the rim.");
|
||||
if (Math.random() > 2/3) {
|
||||
System.out.println("Dartmouth controls the rebound.");
|
||||
if (Math.random() > .4) {
|
||||
System.out.println("Ball passed back to you.\n");
|
||||
dartmouth_ball();
|
||||
}
|
||||
else {
|
||||
dartmouth_non_jump_shot();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println(opponent + " controls the rebound.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is good. Two points.");
|
||||
add_points(1, 2);
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// plays out a Dartmouth posession, starting with your choice of shot
|
||||
private void dartmouth_ball() {
|
||||
Scanner scanner = new Scanner(System.in); // creates a scanner
|
||||
System.out.print("Your shot? ");
|
||||
shot = -1;
|
||||
if (scanner.hasNextInt()) {
|
||||
shot = scanner.nextInt();
|
||||
}
|
||||
else {
|
||||
System.out.println("");
|
||||
scanner.next();
|
||||
}
|
||||
|
||||
while (!shot_choices.contains(shot)) {
|
||||
System.out.print("Incorrect answer. Retype it. Your shot?");
|
||||
if (scanner.hasNextInt()) {
|
||||
shot = scanner.nextInt();
|
||||
}
|
||||
else {
|
||||
System.out.println("");
|
||||
scanner.next();
|
||||
}
|
||||
}
|
||||
|
||||
if (time < 100 || Math.random() < .5) {
|
||||
if (shot == 1 || shot == 2) {
|
||||
dartmouth_jump_shot();
|
||||
}
|
||||
else {
|
||||
dartmouth_non_jump_shot();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (score[0] != score[1]) {
|
||||
System.out.println("\n ***** End Of Game *****");
|
||||
System.out.println("Final Score: Dartmouth: " + score[1] + " "
|
||||
+ opponent + ": " + score[0]);
|
||||
System.exit(0);
|
||||
}
|
||||
else {
|
||||
System.out.println("\n ***** End Of Second Half *****");
|
||||
System.out.println("Score at end of regulation time:");
|
||||
System.out.println(" Dartmouth: " + score[1] + " " +
|
||||
opponent + ": " + score[0]);
|
||||
System.out.println("Begin two minute overtime period");
|
||||
time = 93;
|
||||
start_of_period();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simulates the opponents jumpshot
|
||||
private void opponent_jumpshot() {
|
||||
System.out.println("Jump Shot.");
|
||||
if (8/defense*Math.random() > .35) {
|
||||
if (8/defense*Math.random() > .75) {
|
||||
if (8/defense*Math.random() > .9) {
|
||||
System.out.println("Offensive foul. Dartmouth's ball.\n");
|
||||
dartmouth_ball();
|
||||
}
|
||||
else {
|
||||
foul_shots(0);
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is off the rim.");
|
||||
if (defense/6*Math.random() > .5) {
|
||||
System.out.println(opponent + " controls the rebound.");
|
||||
if (defense == 6) {
|
||||
if (Math.random() > .75) {
|
||||
System.out.println("Ball stolen. Easy lay up for Dartmouth.");
|
||||
add_points(1, 2);
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
if (Math.random() > .5) {
|
||||
System.out.println("");
|
||||
opponent_non_jumpshot();
|
||||
}
|
||||
else {
|
||||
System.out.println("Pass back to " + opponent +
|
||||
" guard.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Math.random() > .5) {
|
||||
opponent_non_jumpshot();
|
||||
}
|
||||
else {
|
||||
System.out.println("Pass back to " + opponent +
|
||||
" guard.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Dartmouth controls the rebound.\n");
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is good.");
|
||||
add_points(0, 2);
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
|
||||
// simulates opponents lay up or set shot
|
||||
private void opponent_non_jumpshot() {
|
||||
if (opponent_chance > 3) {
|
||||
System.out.println("Set shot.");
|
||||
}
|
||||
else {
|
||||
System.out.println("Lay up");
|
||||
}
|
||||
if (7/defense*Math.random() > .413) {
|
||||
System.out.println("Shot is missed.");
|
||||
if (defense/6*Math.random() > .5) {
|
||||
System.out.println(opponent + " controls the rebound.");
|
||||
if (defense == 6) {
|
||||
if (Math.random() > .75) {
|
||||
System.out.println("Ball stolen. Easy lay up for Dartmouth.");
|
||||
add_points(1, 2);
|
||||
opponent_ball();
|
||||
}
|
||||
else {
|
||||
if (Math.random() > .5) {
|
||||
System.out.println("");
|
||||
opponent_non_jumpshot();
|
||||
}
|
||||
else {
|
||||
System.out.println("Pass back to " + opponent +
|
||||
" guard.\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (Math.random() > .5) {
|
||||
System.out.println("");
|
||||
opponent_non_jumpshot();
|
||||
}
|
||||
else {
|
||||
System.out.println("Pass back to " + opponent + " guard\n");
|
||||
opponent_ball();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Dartmouth controls the rebound.\n");
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.out.println("Shot is good.");
|
||||
add_points(0, 2);
|
||||
dartmouth_ball();
|
||||
}
|
||||
}
|
||||
|
||||
// simulates an opponents possesion
|
||||
// #randomly picks jump shot or lay up / set shot.
|
||||
private void opponent_ball() {
|
||||
time ++;
|
||||
if (time == 50) {
|
||||
halftime();
|
||||
}
|
||||
opponent_chance = 10/4*Math.random()+1;
|
||||
if (opponent_chance > 2) {
|
||||
opponent_non_jumpshot();
|
||||
}
|
||||
else {
|
||||
opponent_jumpshot();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Basketball new_game = new Basketball();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
import random
|
||||
|
||||
# The basketball class is a computer game that allows you to play as
|
||||
# Dartmouth College's captain and playmaker
|
||||
# The game uses set probabilites to simulate outcomes of each posession
|
||||
# You are able to choose your shot types as well as defensive formations
|
||||
|
||||
|
||||
class Basketball():
|
||||
def __init__(self):
|
||||
self.time = 0
|
||||
self.score = [0, 0] # first value is opponents score, second is home
|
||||
self.defense = None
|
||||
self.defense_choices = [6, 6.5, 7, 7.5]
|
||||
self.shot = None
|
||||
self.shot_choices = [0, 1, 2, 3, 4]
|
||||
self.z1 = None
|
||||
|
||||
# Explains the keyboard inputs
|
||||
print("\t\t\t Basketball")
|
||||
print("\t Creative Computing Morristown, New Jersey\n\n\n")
|
||||
print("This is Dartmouth College basketball. ")
|
||||
print("Υou will be Dartmouth captain and playmaker.")
|
||||
print("Call shots as follows:")
|
||||
print("1. Long (30ft.) Jump Shot; 2. Short (15 ft.) Jump Shot; "
|
||||
+ "3. Lay up; 4. Set Shot")
|
||||
print("Both teams will use the same defense. Call Defense as follows:")
|
||||
print("6. Press; 6.5 Man-to-Man; 7. Zone; 7.5 None.")
|
||||
print("To change defense, just type 0 as your next shot.")
|
||||
print("Your starting defense will be? ", end='')
|
||||
|
||||
# takes input for a defense
|
||||
try:
|
||||
self.defense = float(input())
|
||||
|
||||
except ValueError:
|
||||
self.defense = None
|
||||
|
||||
# if the input wasn't a valid defense, takes input again
|
||||
while self.defense not in self.defense_choices:
|
||||
print("Your new defensive allignment is? ", end='')
|
||||
try:
|
||||
self.defense = float(input())
|
||||
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# takes input for opponent's name
|
||||
print("\nChoose your opponent? ", end='')
|
||||
|
||||
self.opponent = input()
|
||||
self.start_of_period()
|
||||
|
||||
# adds points to the score
|
||||
# team can take 0 or 1, for opponent or Dartmouth, respectively
|
||||
def add_points(self, team, points):
|
||||
self.score[team] += points
|
||||
self.print_score()
|
||||
|
||||
def ball_passed_back(self):
|
||||
print("Ball passed back to you. ", end='')
|
||||
self.dartmouth_ball()
|
||||
|
||||
# change defense, called when the user enters 0 for their shot
|
||||
def change_defense(self):
|
||||
self.defense = None
|
||||
|
||||
while self.defense not in self.defense_choices:
|
||||
print("Your new defensive allignment is? ")
|
||||
try:
|
||||
self.defense = float(input())
|
||||
|
||||
except ValueError:
|
||||
continue
|
||||
self.dartmouth_ball()
|
||||
|
||||
# simulates two foul shots for a player and adds the points
|
||||
def foul_shots(self, team):
|
||||
print("Shooter fouled. Two shots.")
|
||||
if random.random() > .49:
|
||||
if random.random() > .75:
|
||||
print("Both shots missed.")
|
||||
else:
|
||||
print("Shooter makes one shot and misses one.")
|
||||
self.score[team] += 1
|
||||
else:
|
||||
print("Shooter makes both shots.")
|
||||
self.score[team] += 2
|
||||
|
||||
self.print_score()
|
||||
|
||||
# called when t = 50, starts a new period
|
||||
def halftime(self):
|
||||
print("\n ***** End of first half *****\n")
|
||||
self.print_score()
|
||||
self.start_of_period()
|
||||
|
||||
# prints the current score
|
||||
def print_score(self):
|
||||
print("Score: " + str(self.score[1])
|
||||
+ " to " + str(self.score[0]) + "\n")
|
||||
|
||||
# simulates a center jump for posession at the beginning of a period
|
||||
def start_of_period(self):
|
||||
print("Center jump")
|
||||
if random.random() > .6:
|
||||
print("Dartmouth controls the tap.\n")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
print(self.opponent + " controls the tap.\n")
|
||||
self.opponent_ball()
|
||||
|
||||
# called when t = 92
|
||||
def two_minute_warning(self):
|
||||
print(" *** Two minutes left in the game ***")
|
||||
|
||||
# called when the user enters 1 or 2 for their shot
|
||||
def dartmouth_jump_shot(self):
|
||||
self.time += 1
|
||||
if self.time == 50:
|
||||
self.halftime()
|
||||
elif self.time == 92:
|
||||
self.two_minute_warning()
|
||||
print("Jump Shot.")
|
||||
# simulates chances of different possible outcomes
|
||||
if random.random() > .341 * self.defense / 8:
|
||||
if random.random() > .682 * self.defense / 8:
|
||||
if random.random() > .782 * self.defense / 8:
|
||||
if random.random() > .843 * self.defense / 8:
|
||||
print("Charging foul. Dartmouth loses ball.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
# player is fouled
|
||||
self.foul_shots(1)
|
||||
self.opponent_ball()
|
||||
else:
|
||||
if random.random() > .5:
|
||||
print("Shot is blocked. Ball controlled by " +
|
||||
self.opponent + ".\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Shot is blocked. Ball controlled by Dartmouth.")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
print("Shot is off target.")
|
||||
if self.defense / 6 * random.random() > .45:
|
||||
print("Rebound to " + self.opponent + "\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Dartmouth controls the rebound.")
|
||||
if random.random() > .4:
|
||||
if self.defense == 6 and random.random() > .6:
|
||||
print("Pass stolen by " + self.opponent
|
||||
+ ", easy lay up")
|
||||
self.add_points(0, 2)
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
# ball is passed back to you
|
||||
self.ball_passed_back()
|
||||
else:
|
||||
print("")
|
||||
self.dartmouth_non_jump_shot()
|
||||
else:
|
||||
print("Shot is good.")
|
||||
self.add_points(1, 2)
|
||||
self.opponent_ball()
|
||||
|
||||
# called when the user enters 0, 3, or 4
|
||||
# lay up, set shot, or defense change
|
||||
def dartmouth_non_jump_shot(self):
|
||||
self.time += 1
|
||||
if self.time == 50:
|
||||
self.halftime()
|
||||
elif self.time == 92:
|
||||
self.two_minute_warning()
|
||||
|
||||
if self.shot == 4:
|
||||
print("Set shot.")
|
||||
elif self.shot == 3:
|
||||
print("Lay up.")
|
||||
elif self.shot == 0:
|
||||
self.change_defense()
|
||||
|
||||
# simulates different outcomes after a lay up or set shot
|
||||
if 7/self.defense*random.random() > .4:
|
||||
if 7/self.defense*random.random() > .7:
|
||||
if 7/self.defense*random.random() > .875:
|
||||
if 7/self.defense*random.random() > .925:
|
||||
print("Charging foul. Dartmouth loses the ball.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Shot blocked. " + self.opponent + "'s ball.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
self.foul_shots(1)
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Shot is off the rim.")
|
||||
if random.random() > 2/3:
|
||||
print("Dartmouth controls the rebound.")
|
||||
if random.random() > .4:
|
||||
print("Ball passed back to you.\n")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
self.dartmouth_non_jump_shot()
|
||||
else:
|
||||
print(self.opponent + " controls the rebound.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Shot is good. Two points.")
|
||||
self.add_points(1, 2)
|
||||
self.opponent_ball()
|
||||
|
||||
# plays out a Dartmouth posession, starting with your choice of shot
|
||||
def dartmouth_ball(self):
|
||||
print("Your shot? ", end='')
|
||||
self.shot = None
|
||||
try:
|
||||
self.shot = int(input())
|
||||
except ValueError:
|
||||
self.shot = None
|
||||
|
||||
while self.shot not in self.shot_choices:
|
||||
print("Incorrect answer. Retype it. Your shot? ", end='')
|
||||
try:
|
||||
self.shot = int(input())
|
||||
except:
|
||||
continue
|
||||
|
||||
if self.time < 100 or random.random() < .5:
|
||||
if self.shot == 1 or self.shot == 2:
|
||||
self.dartmouth_jump_shot()
|
||||
else:
|
||||
self.dartmouth_non_jump_shot()
|
||||
else:
|
||||
if self.score[0] != self.score[1]:
|
||||
print("\n ***** End Of Game *****")
|
||||
print("Final Score: Dartmouth: " + str(self.score[1]) + " "
|
||||
+ self.opponent + ": " + str(self.score[0]))
|
||||
else:
|
||||
print("\n ***** End Of Second Half *****")
|
||||
print("Score at end of regulation time:")
|
||||
print(" Dartmouth: " + str(self.score[1]) + " " +
|
||||
self.opponent + ": " + str(self.score[0]))
|
||||
print("Begin two minute overtime period")
|
||||
self.time = 93
|
||||
self.start_of_period()
|
||||
|
||||
# simulates the opponents jumpshot
|
||||
def opponent_jumpshot(self):
|
||||
print("Jump Shot.")
|
||||
if 8/self.defense*random.random() > .35:
|
||||
if 8/self.defense*random.random() > .75:
|
||||
if 8/self.defense*random.random() > .9:
|
||||
print("Offensive foul. Dartmouth's ball.\n")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
self.foul_shots(0)
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
print("Shot is off the rim.")
|
||||
if self.defense/6*random.random() > .5:
|
||||
print(self.opponent + " controls the rebound.")
|
||||
if self.defense == 6:
|
||||
if random.random() > .75:
|
||||
print("Ball stolen. Easy lay up for Dartmouth.")
|
||||
self.add_points(1, 2)
|
||||
self.opponent_ball()
|
||||
else:
|
||||
if random.random() > .5:
|
||||
print("")
|
||||
self.opponent_non_jumpshot()
|
||||
else:
|
||||
print("Pass back to " + self.opponent +
|
||||
" guard.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
if random.random() > .5:
|
||||
self.opponent_non_jumpshot()
|
||||
else:
|
||||
print("Pass back to " + self.opponent +
|
||||
" guard.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Dartmouth controls the rebound.\n")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
print("Shot is good.")
|
||||
self.add_points(0, 2)
|
||||
self.dartmouth_ball()
|
||||
|
||||
# simulates opponents lay up or set shot
|
||||
def opponent_non_jumpshot(self):
|
||||
if self.z1 > 3:
|
||||
print("Set shot.")
|
||||
else:
|
||||
print("Lay up")
|
||||
if 7/self.defense*random.random() > .413:
|
||||
print("Shot is missed.")
|
||||
if self.defense/6*random.random() > .5:
|
||||
print(self.opponent + " controls the rebound.")
|
||||
if self.defense == 6:
|
||||
if random.random() > .75:
|
||||
print("Ball stolen. Easy lay up for Dartmouth.")
|
||||
self.add_points(1, 2)
|
||||
self.opponent_ball()
|
||||
else:
|
||||
if random.random() > .5:
|
||||
print("")
|
||||
self.opponent_non_jumpshot()
|
||||
else:
|
||||
print("Pass back to " + self.opponent +
|
||||
" guard.\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
if random.random() > .5:
|
||||
print("")
|
||||
self.opponent_non_jumpshot()
|
||||
else:
|
||||
print("Pass back to " + self.opponent + " guard\n")
|
||||
self.opponent_ball()
|
||||
else:
|
||||
print("Dartmouth controls the rebound.\n")
|
||||
self.dartmouth_ball()
|
||||
else:
|
||||
print("Shot is good.")
|
||||
self.add_points(0, 2)
|
||||
self.dartmouth_ball()
|
||||
|
||||
# simulates an opponents possesion
|
||||
# #randomly picks jump shot or lay up / set shot.
|
||||
def opponent_ball(self):
|
||||
self.time += 1
|
||||
if self.time == 50:
|
||||
self.halftime()
|
||||
self.z1 = 10/4*random.random()+1
|
||||
if self.z1 > 2:
|
||||
self.opponent_non_jumpshot()
|
||||
else:
|
||||
self.opponent_jumpshot()
|
||||
|
||||
|
||||
new_game = Basketball()
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Basketball", "Basketball.vbproj", "{09C533F2-4874-4BA4-9F80-BBE9E8E17456}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{09C533F2-4874-4BA4-9F80-BBE9E8E17456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09C533F2-4874-4BA4-9F80-BBE9E8E17456}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09C533F2-4874-4BA4-9F80-BBE9E8E17456}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{09C533F2-4874-4BA4-9F80-BBE9E8E17456}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Basketball</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31321.278
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "batnum", "batnum.vbproj", "{D577E429-F84D-4E84-86E7-E6526CFD5FD9}"
|
||||
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Batnum", "Batnum.vbproj", "{D577E429-F84D-4E84-86E7-E6526CFD5FD9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>batnum</RootNamespace>
|
||||
<RootNamespace>Batnum</RootNamespace>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*~
|
||||
@@ -0,0 +1,168 @@
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
|
||||
/* This class holds the game state and the game logic */
|
||||
public class Battle {
|
||||
|
||||
/* parameters of the game */
|
||||
private int seaSize;
|
||||
private int[] sizes;
|
||||
private int[] counts;
|
||||
|
||||
/* The game setup - the ships and the sea */
|
||||
private ArrayList<Ship> ships;
|
||||
private Sea sea;
|
||||
|
||||
/* game state counts */
|
||||
private int[] losses; // how many of each type of ship have been sunk
|
||||
private int hits; // how many hits the player has made
|
||||
private int misses; // how many misses the player has made
|
||||
|
||||
// Names of ships of each size. The game as written has ships of size 3, 4 and 5 but
|
||||
// can easily be modified. It makes no sense to have a ship of size zero though.
|
||||
private static String NAMES_BY_SIZE[] = {
|
||||
"error",
|
||||
"size1",
|
||||
"destroyer",
|
||||
"cruiser",
|
||||
"aircraft carrier",
|
||||
"size5" };
|
||||
|
||||
// Entrypoint
|
||||
public static void main(String args[]) {
|
||||
Battle game = new Battle(6, // Sea is 6 x 6 tiles
|
||||
new int[] { 2, 3, 4 }, // Ships are of sizes 2, 3, and 4
|
||||
new int[] { 2, 2, 2 }); // there are two ships of each size
|
||||
game.play();
|
||||
}
|
||||
|
||||
public Battle(int scale, int[] shipSizes, int[] shipCounts) {
|
||||
seaSize = scale;
|
||||
sizes = shipSizes;
|
||||
counts = shipCounts;
|
||||
|
||||
// validate parameters
|
||||
if (seaSize < 4) throw new RuntimeException("Sea Size " + seaSize + " invalid, must be at least 4");
|
||||
|
||||
for (int sz : sizes) {
|
||||
if ((sz < 1) || (sz > seaSize))
|
||||
throw new RuntimeException("Ship has invalid size " + sz);
|
||||
}
|
||||
|
||||
if (counts.length != sizes.length) {
|
||||
throw new RuntimeException("Ship counts must match");
|
||||
}
|
||||
|
||||
// Initialize game state
|
||||
sea = new Sea(seaSize); // holds what ship if any occupies each tile
|
||||
ships = new ArrayList<Ship>(); // positions and states of all the ships
|
||||
losses = new int[counts.length]; // how many ships of each type have been sunk
|
||||
|
||||
// Build up the list of all the ships
|
||||
int shipNumber = 1;
|
||||
for (int type = 0; type < counts.length; ++type) {
|
||||
for (int i = 0; i < counts[i]; ++i) {
|
||||
ships.add(new Ship(shipNumber++, sizes[type]));
|
||||
}
|
||||
}
|
||||
|
||||
// When we put the ships in the sea, we put the biggest ones in first, or they might
|
||||
// not fit
|
||||
ArrayList<Ship> largestFirst = new ArrayList<>(ships);
|
||||
Collections.sort(largestFirst, Comparator.comparingInt((Ship ship) -> ship.size()).reversed());
|
||||
|
||||
// place each ship into the sea
|
||||
for (Ship ship : largestFirst) {
|
||||
ship.placeRandom(sea);
|
||||
}
|
||||
}
|
||||
|
||||
public void play() {
|
||||
System.out.println("The following code of the bad guys' fleet disposition\nhas been captured but not decoded:\n");
|
||||
System.out.println(sea.encodedDump());
|
||||
System.out.println("De-code it and use it if you can\nbut keep the de-coding method a secret.\n");
|
||||
|
||||
int lost = 0;
|
||||
System.out.println("Start game");
|
||||
Input input = new Input(seaSize);
|
||||
try {
|
||||
while (lost < ships.size()) { // the game continues while some ships remain unsunk
|
||||
if (! input.readCoordinates()) { // ... unless there is no more input from the user
|
||||
return;
|
||||
}
|
||||
|
||||
// The computer thinks of the sea as a grid of rows, from top to bottom.
|
||||
// However, the user will use X and Y coordinates, with Y going bottom to top
|
||||
int row = seaSize - input.y();
|
||||
int col = input.x() - 1;
|
||||
|
||||
if (sea.isEmpty(col, row)) {
|
||||
++misses;
|
||||
System.out.println("Splash! Try again.");
|
||||
} else {
|
||||
Ship ship = ships.get(sea.get(col, row) - 1);
|
||||
if (ship.isSunk()) {
|
||||
++misses;
|
||||
System.out.println("There used to be a ship at that point, but you sunk it.");
|
||||
System.out.println("Splash! Try again.");
|
||||
} else if (ship.wasHit(col, row)) {
|
||||
++misses;
|
||||
System.out.println("You already put a hole in ship number " + ship.id());
|
||||
System.out.println("Splash! Try again.");
|
||||
} else {
|
||||
ship.hit(col, row);
|
||||
++hits;
|
||||
System.out.println("A direct hit on ship number " + ship.id());
|
||||
|
||||
// If a ship was hit, we need to know whether it was sunk.
|
||||
// If so, tell the player and update our counts
|
||||
if (ship.isSunk()) {
|
||||
++lost;
|
||||
System.out.println("And you sunk it. Hurrah for the good guys.");
|
||||
System.out.print("So far, the bad guys have lost ");
|
||||
ArrayList<String> typeDescription = new ArrayList<>();
|
||||
for (int i = 0 ; i < sizes.length; ++i) {
|
||||
if (sizes[i] == ship.size()) {
|
||||
++losses[i];
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(losses[i]);
|
||||
sb.append(" ");
|
||||
sb.append(NAMES_BY_SIZE[sizes[i]]);
|
||||
if (losses[i] != 1)
|
||||
sb.append("s");
|
||||
typeDescription.add(sb.toString());
|
||||
}
|
||||
System.out.println(String.join(", ", typeDescription));
|
||||
double ratioNum = ((double)misses)/hits;
|
||||
String ratio = NumberFormat.getInstance().format(ratioNum);
|
||||
System.out.println("Your current splash/hit ratio is " + ratio);
|
||||
|
||||
if (lost == ships.size()) {
|
||||
System.out.println("You have totally wiped out the bad guys' fleet");
|
||||
System.out.println("With a final splash/hit ratio of " + ratio);
|
||||
|
||||
if (misses == 0) {
|
||||
System.out.println("Congratulations - A direct hit every time.");
|
||||
}
|
||||
|
||||
System.out.println("\n****************************\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// This should not happen running from console, but java requires us to check for it
|
||||
System.err.println("System error.\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
// This class handles reading input from the player
|
||||
// Each input is an x and y coordinate
|
||||
// e.g. 5,3
|
||||
public class Input {
|
||||
private BufferedReader reader;
|
||||
private NumberFormat parser;
|
||||
private int scale; // size of the sea, needed to validate input
|
||||
private boolean isQuit; // whether the input has ended
|
||||
private int[] coords; // the last coordinates read
|
||||
|
||||
public Input(int seaSize) {
|
||||
scale = seaSize;
|
||||
reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
parser = NumberFormat.getIntegerInstance();
|
||||
}
|
||||
|
||||
public boolean readCoordinates() throws IOException {
|
||||
while (true) {
|
||||
// Write a prompt
|
||||
System.out.print("\nTarget x,y\n> ");
|
||||
String inputLine = reader.readLine();
|
||||
if (inputLine == null) {
|
||||
// If the input stream is ended, there is no way to continue the game
|
||||
System.out.println("\nGame quit\n");
|
||||
isQuit = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// split the input into two fields
|
||||
String[] fields = inputLine.split(",");
|
||||
if (fields.length != 2) {
|
||||
// has to be exactly two
|
||||
System.out.println("Need two coordinates separated by ','");
|
||||
continue;
|
||||
}
|
||||
|
||||
coords = new int[2];
|
||||
boolean error = false;
|
||||
// each field should contain an integer from 1 to the size of the sea
|
||||
try {
|
||||
for (int c = 0 ; c < 2; ++c ) {
|
||||
int val = Integer.parseInt(fields[c].strip());
|
||||
if ((val < 1) || (val > scale)) {
|
||||
System.out.println("Coordinates must be from 1 to " + scale);
|
||||
error = true;
|
||||
} else {
|
||||
coords[c] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ne) {
|
||||
// this happens if the field is not a valid number
|
||||
System.out.println("Coordinates must be numbers");
|
||||
error = true;
|
||||
}
|
||||
if (!error) return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int x() { return coords[0]; }
|
||||
public int y() { return coords[1]; }
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Track the content of the sea
|
||||
class Sea {
|
||||
// the sea is a square grid of tiles. It is a one-dimensional array, and this
|
||||
// class maps x and y coordinates to an array index
|
||||
// Each tile is either empty (value of tiles at index is 0)
|
||||
// or contains a ship (value of tiles at index is the ship number)
|
||||
private int tiles[];
|
||||
|
||||
private int size;
|
||||
|
||||
public Sea(int make_size) {
|
||||
size = make_size;
|
||||
tiles = new int[size*size];
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
|
||||
// This writes out a representation of the sea, but in a funny order
|
||||
// The idea is to give the player the job of working it out
|
||||
public String encodedDump() {
|
||||
StringBuilder out = new StringBuilder();
|
||||
for (int x = 0; x < size; ++x) {
|
||||
for (int y = 0; y < size; ++y)
|
||||
out.append(Integer.toString(get(x, y)));
|
||||
out.append('\n');
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/* return true if x,y is in the sea and empty
|
||||
* return false if x,y is occupied or is out of range
|
||||
* Doing this in one method makes placing ships much easier
|
||||
*/
|
||||
public boolean isEmpty(int x, int y) {
|
||||
if ((x<0)||(x>=size)||(y<0)||(y>=size)) return false;
|
||||
return (get(x,y) == 0);
|
||||
}
|
||||
|
||||
/* return the ship number, or zero if no ship.
|
||||
* Unlike isEmpty(x,y), these other methods require that the
|
||||
* coordinates passed be valid
|
||||
*/
|
||||
public int get(int x, int y) {
|
||||
return tiles[index(x,y)];
|
||||
}
|
||||
|
||||
public void set(int x, int y, int value) {
|
||||
tiles[index(x, y)] = value;
|
||||
}
|
||||
|
||||
// map the coordinates to the array index
|
||||
private int index(int x, int y) {
|
||||
if ((x < 0) || (x >= size))
|
||||
throw new ArrayIndexOutOfBoundsException("Program error: x cannot be " + x);
|
||||
if ((y < 0) || (y >= size))
|
||||
throw new ArrayIndexOutOfBoundsException("Program error: y cannot be " + y);
|
||||
|
||||
return y*size + x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** A single ship, with its position and where it has been hit */
|
||||
class Ship {
|
||||
// These are the four directions that ships can be in
|
||||
public static final int ORIENT_E=0; // goes East from starting position
|
||||
public static final int ORIENT_SE=1; // goes SouthEast from starting position
|
||||
public static final int ORIENT_S=2; // goes South from starting position
|
||||
public static final int ORIENT_SW=3; // goes SouthWest from starting position
|
||||
|
||||
private int id; // ship number
|
||||
private int size; // how many tiles it occupies
|
||||
private boolean placed; // whether this ship is in the sea yet
|
||||
private boolean sunk; // whether this ship has been sunk
|
||||
private ArrayList<Boolean> hits; // which tiles of the ship have been hit
|
||||
|
||||
private int startX; // starting position coordinates
|
||||
private int startY;
|
||||
private int orientX; // x and y deltas from each tile occupied to the next
|
||||
private int orientY;
|
||||
|
||||
public Ship(int i, int sz) {
|
||||
id = i; size = sz;
|
||||
sunk = false; placed = false;
|
||||
hits = new ArrayList<>(Collections.nCopies(size, false));
|
||||
}
|
||||
|
||||
/** @returns the ship number */
|
||||
public int id() { return id; }
|
||||
/** @returns the ship size */
|
||||
public int size() { return size; }
|
||||
|
||||
/* record the ship as having been hit at the given coordinates */
|
||||
public void hit(int x, int y) {
|
||||
// need to work out how many tiles from the ship's starting position the hit is at
|
||||
// that can be worked out from the difference between the starting X coord and this one
|
||||
// unless the ship runs N-S, in which case use the Y coord instead
|
||||
int offset;
|
||||
if (orientX != 0) {
|
||||
offset = (x - startX) / orientX;
|
||||
} else {
|
||||
offset = (y - startY) / orientY;
|
||||
}
|
||||
hits.set(offset, true);
|
||||
|
||||
// if every tile of the ship has been hit, the ship is sunk
|
||||
sunk = hits.stream().allMatch(Predicate.isEqual(true));
|
||||
}
|
||||
|
||||
public boolean isSunk() { return sunk; }
|
||||
|
||||
// whether the ship has already been hit at the given coordinates
|
||||
public boolean wasHit(int x, int y) {
|
||||
int offset;
|
||||
if (orientX != 0) {
|
||||
offset = (x - startX) / orientX;
|
||||
} else {
|
||||
offset = (y - startY) / orientY;
|
||||
}
|
||||
return hits.get(offset);
|
||||
};
|
||||
|
||||
// Place the ship in the sea.
|
||||
// choose a random starting position, and a random direction
|
||||
// if that doesn't fit, keep picking different positions and directions
|
||||
public void placeRandom(Sea s) {
|
||||
Random random = new Random();
|
||||
for (int tries = 0 ; tries < 1000 ; ++tries) {
|
||||
int x = random.nextInt(s.size());
|
||||
int y = random.nextInt(s.size());
|
||||
int orient = random.nextInt(4);
|
||||
|
||||
if (place(s, x, y, orient)) return;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Could not place any more ships");
|
||||
}
|
||||
|
||||
// Attempt to fit the ship into the sea, starting from a given position and
|
||||
// in a given direction
|
||||
// This is by far the most complicated part of the program.
|
||||
// It will start at the position provided, and attempt to occupy tiles in the
|
||||
// requested direction. If it does not fit, either because of the edge of the
|
||||
// sea, or because of ships already in place, it will try to extend the ship
|
||||
// in the opposite direction instead. If that is not possible, it fails.
|
||||
public boolean place(Sea s, int x, int y, int orient) {
|
||||
if (placed) {
|
||||
throw new RuntimeException("Program error - placed ship " + id + " twice");
|
||||
}
|
||||
switch(orient) {
|
||||
case ORIENT_E: // east is increasing X coordinate
|
||||
orientX = 1; orientY = 0;
|
||||
break;
|
||||
case ORIENT_SE: // southeast is increasing X and Y
|
||||
orientX = 1; orientY = 1;
|
||||
break;
|
||||
case ORIENT_S: // south is increasing Y
|
||||
orientX = 0; orientY = 1;
|
||||
break;
|
||||
case ORIENT_SW: // southwest is increasing Y but decreasing X
|
||||
orientX = -1; orientY = 1;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Invalid orientation " + orient);
|
||||
}
|
||||
|
||||
if (!s.isEmpty(x, y)) return false; // starting position is occupied - placing fails
|
||||
|
||||
startX = x; startY = y;
|
||||
int tilesPlaced = 1;
|
||||
int nextX = startX;
|
||||
int nextY = startY;
|
||||
while (tilesPlaced < size) {
|
||||
if (extendShip(s, nextX, nextY, nextX + orientX, nextY + orientY)) {
|
||||
// It is clear to extend the ship forwards
|
||||
tilesPlaced += 1;
|
||||
nextX = nextX + orientX;
|
||||
nextY = nextY + orientY;
|
||||
} else {
|
||||
int backX = startX - orientX;
|
||||
int backY = startY - orientY;
|
||||
|
||||
if (extendShip(s, startX, startY, backX, backY)) {
|
||||
// We can move the ship backwards, so it can be one tile longer
|
||||
tilesPlaced +=1;
|
||||
startX = backX;
|
||||
startY = backY;
|
||||
} else {
|
||||
// Could not make it longer or move it backwards
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark in the sea which tiles this ship occupies
|
||||
for (int i = 0; i < size; ++i) {
|
||||
int sx = startX + i * orientX;
|
||||
int sy = startY + i * orientY;
|
||||
s.set(sx, sy, id);
|
||||
}
|
||||
|
||||
placed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check whether a ship which already occupies the "from" coordinates,
|
||||
// can also occupy the "to" coordinates.
|
||||
// They must be within the sea area, empty, and not cause the ship to cross
|
||||
// over another ship
|
||||
private boolean extendShip(Sea s, int fromX, int fromY, int toX, int toY) {
|
||||
if (!s.isEmpty(toX, toY)) return false; // no space
|
||||
if ((fromX == toX)||(fromY == toY)) return true; // horizontal or vertical
|
||||
|
||||
// we can extend the ship without colliding, but we are going diagonally
|
||||
// and it should not be possible for two ships to cross each other on
|
||||
// opposite diagonals.
|
||||
|
||||
// check the two tiles that would cross us here - if either is empty, we are OK
|
||||
// if they both contain different ships, we are OK
|
||||
// but if they both contain the same ship, we are crossing!
|
||||
int corner1 = s.get(fromX, toY);
|
||||
int corner2 = s.get(toX, fromY);
|
||||
if ((corner1 == 0) || (corner1 != corner2)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
from random import randrange
|
||||
from typing import List, Tuple
|
||||
|
||||
PointType = Tuple[int, int]
|
||||
VectorType = PointType
|
||||
SeaType = Tuple[List[int], ...]
|
||||
|
||||
SEA_WIDTH = 6
|
||||
DESTROYER_LENGTH = 2
|
||||
CRUISER_LENGTH = 3
|
||||
AIRCRAFT_CARRIER_LENGTH = 4
|
||||
|
||||
|
||||
def random_vector() -> Tuple[int, int]:
|
||||
while True:
|
||||
vector = (randrange(-1, 2), randrange(-1, 2))
|
||||
|
||||
if vector == (0, 0):
|
||||
# We can't have a zero vector, so try again
|
||||
continue
|
||||
|
||||
return vector
|
||||
|
||||
|
||||
def add_vector(point: PointType, vector: VectorType) -> PointType:
|
||||
return (point[0] + vector[0], point[1] + vector[1])
|
||||
|
||||
|
||||
def place_ship(sea: SeaType, size: int, code: int) -> None:
|
||||
while True:
|
||||
start = (randrange(1, SEA_WIDTH + 1), randrange(1, SEA_WIDTH + 1))
|
||||
vector = random_vector()
|
||||
|
||||
# Get potential ship points
|
||||
point = start
|
||||
points = []
|
||||
|
||||
for _ in range(size):
|
||||
point = add_vector(point, vector)
|
||||
points.append(point)
|
||||
|
||||
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
|
||||
|
||||
# We found a valid spot, so actually place it now
|
||||
for point in points:
|
||||
set_value_at(code, point, sea)
|
||||
|
||||
break
|
||||
|
||||
|
||||
def print_encoded_sea(sea: SeaType) -> None:
|
||||
for x in range(len(sea)):
|
||||
print(' '.join([str(sea[y][x]) for y in range(len(sea) - 1, -1, -1)]))
|
||||
|
||||
|
||||
def is_within_sea(point: PointType, sea: SeaType) -> bool:
|
||||
return (1 <= point[0] <= len(sea)) and (1 <= point[1] <= len(sea))
|
||||
|
||||
|
||||
def has_ship(sea: SeaType, code: int) -> bool:
|
||||
return any(code in row for row in sea)
|
||||
|
||||
|
||||
def count_sunk(sea: SeaType, *codes: int) -> int:
|
||||
return sum(not has_ship(sea, code) for code in codes)
|
||||
|
||||
|
||||
def value_at(point: PointType, sea: SeaType) -> int:
|
||||
return sea[point[1] - 1][point[0] -1]
|
||||
|
||||
|
||||
def set_value_at(value: int, point: PointType, sea: SeaType) -> None:
|
||||
sea[point[1] - 1][point[0] -1] = value
|
||||
|
||||
|
||||
def get_next_target(sea: SeaType) -> PointType:
|
||||
while True:
|
||||
try:
|
||||
guess = input('? ')
|
||||
point = guess.split(',')
|
||||
|
||||
if len(point) != 2:
|
||||
raise ValueError()
|
||||
|
||||
point = (int(point[0]), int(point[1]))
|
||||
|
||||
if not is_within_sea(point, sea):
|
||||
raise ValueError()
|
||||
|
||||
return point
|
||||
except ValueError:
|
||||
print(f'INVALID. SPECIFY TWO NUMBERS FROM 1 TO {len(sea)}, SEPARATED BY A COMMA.')
|
||||
|
||||
|
||||
def setup_ships(sea: SeaType):
|
||||
place_ship(sea, DESTROYER_LENGTH, 1)
|
||||
place_ship(sea, DESTROYER_LENGTH, 2)
|
||||
place_ship(sea, CRUISER_LENGTH, 3)
|
||||
place_ship(sea, CRUISER_LENGTH, 4)
|
||||
place_ship(sea, AIRCRAFT_CARRIER_LENGTH, 5)
|
||||
place_ship(sea, AIRCRAFT_CARRIER_LENGTH, 6)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
sea = tuple(([0 for _ in range(SEA_WIDTH)] for _ in range(SEA_WIDTH)))
|
||||
setup_ships(sea)
|
||||
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('''
|
||||
|
||||
DE-CODE IT AND USE IT IF YOU CAN
|
||||
BUT KEEP THE DE-CODING METHOD A SECRET.
|
||||
|
||||
START GAME''')
|
||||
splashes = 0
|
||||
hits = 0
|
||||
|
||||
while True:
|
||||
target = get_next_target(sea)
|
||||
target_value = value_at(target, sea)
|
||||
|
||||
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.')
|
||||
splashes += 1
|
||||
continue
|
||||
|
||||
print(f'A DIRECT HIT ON SHIP NUMBER {target_value}')
|
||||
hits += 1
|
||||
set_value_at(-target_value, target, sea)
|
||||
|
||||
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).')
|
||||
|
||||
if any(has_ship(sea, code) for code in range(1, 7)):
|
||||
print(f'YOUR CURRENT SPLASH/HIT RATIO IS {splashes}/{hits}')
|
||||
continue
|
||||
|
||||
print('YOU HAVE TOTALLY WIPED OUT THE BAD GUYS\' FLEET '
|
||||
f'WITH A FINAL SPLASH/HIT RATIO OF {splashes}/{hits}')
|
||||
|
||||
if not splashes:
|
||||
print('CONGRATULATIONS -- A DIRECT HIT EVERY TIME.')
|
||||
|
||||
print("\n****************************")
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Battle", "Battle.vbproj", "{D8475464-CB9B-448F-89A7-5BA15193C495}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D8475464-CB9B-448F-89A7-5BA15193C495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D8475464-CB9B-448F-89A7-5BA15193C495}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D8475464-CB9B-448F-89A7-5BA15193C495}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D8475464-CB9B-448F-89A7-5BA15193C495}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Battle</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blackjack", "Blackjack.csproj", "{83253F48-9CCD-475C-A990-8703F1A2E31C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{83253F48-9CCD-475C-A990-8703F1A2E31C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83253F48-9CCD-475C-A990-8703F1A2E31C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83253F48-9CCD-475C-A990-8703F1A2E31C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83253F48-9CCD-475C-A990-8703F1A2E31C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Blackjack", "Blackjack.vbproj", "{B112CA5F-142B-46E9-92CB-5E3A84816AAE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B112CA5F-142B-46E9-92CB-5E3A84816AAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B112CA5F-142B-46E9-92CB-5E3A84816AAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B112CA5F-142B-46E9-92CB-5E3A84816AAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B112CA5F-142B-46E9-92CB-5E3A84816AAE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Blackjack</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bombardment", "Bombardment.csproj", "{1DCFD283-9300-405B-A2B4-231F30265730}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1DCFD283-9300-405B-A2B4-231F30265730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1DCFD283-9300-405B-A2B4-231F30265730}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1DCFD283-9300-405B-A2B4-231F30265730}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1DCFD283-9300-405B-A2B4-231F30265730}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Bombardment", "Bombardment.vbproj", "{7C967BC0-101C-413F-92A8-3A8A7D9846FE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7C967BC0-101C-413F-92A8-3A8A7D9846FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C967BC0-101C-413F-92A8-3A8A7D9846FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C967BC0-101C-413F-92A8-3A8A7D9846FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C967BC0-101C-413F-92A8-3A8A7D9846FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Bombardment</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Bombs away
|
||||
|
||||
Ported from BASIC to Python3 by Bernard Cooke (bernardcooke53)
|
||||
Tested with Python 3.8.10, formatted with Black and type checked with mypy.
|
||||
"""
|
||||
import random
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def _stdin_choice(prompt: str, *, choices: Iterable[str]) -> str:
|
||||
ret = input(prompt)
|
||||
while ret not in choices:
|
||||
print("TRY AGAIN...")
|
||||
ret = input(prompt)
|
||||
return ret
|
||||
|
||||
|
||||
def player_survived() -> None:
|
||||
print("YOU MADE IT THROUGH TREMENDOUS FLAK!!")
|
||||
|
||||
|
||||
def player_death() -> None:
|
||||
print("* * * * BOOM * * * *")
|
||||
print("YOU HAVE BEEN SHOT DOWN.....")
|
||||
print("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR")
|
||||
print("LAST TRIBUTE...")
|
||||
|
||||
|
||||
def mission_success() -> None:
|
||||
print(f"DIRECT HIT!!!! {int(100 * random.random())} KILLED.")
|
||||
print("MISSION SUCCESSFUL.")
|
||||
|
||||
|
||||
def death_with_chance(p_death: float) -> bool:
|
||||
"""
|
||||
Takes a float between 0 and 1 and returns a boolean
|
||||
if the player has survived (based on random chance)
|
||||
|
||||
Returns True if death, False if survived
|
||||
"""
|
||||
return p_death > random.random()
|
||||
|
||||
|
||||
def commence_non_kamikazi_attack() -> None:
|
||||
while True:
|
||||
try:
|
||||
nmissions = int(input("HOW MANY MISSIONS HAVE YOU FLOWN? "))
|
||||
|
||||
while nmissions >= 160:
|
||||
print("MISSIONS, NOT MILES...")
|
||||
print("150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS")
|
||||
nmissions = int(input("NOW THEN, HOW MANY MISSIONS HAVE YOU FLOWN? "))
|
||||
break
|
||||
except ValueError:
|
||||
# In the BASIC implementation this
|
||||
# wasn't accounted for
|
||||
print("TRY AGAIN...")
|
||||
continue
|
||||
|
||||
if nmissions >= 100:
|
||||
print("THAT'S PUSHING THE ODDS!")
|
||||
|
||||
if nmissions < 25:
|
||||
print("FRESH OUT OF TRAINING, EH?")
|
||||
|
||||
print()
|
||||
return (
|
||||
mission_success() if nmissions >= 160 * random.random() else mission_failure()
|
||||
)
|
||||
|
||||
|
||||
def mission_failure() -> None:
|
||||
weapons_choices = {
|
||||
"1": "GUNS",
|
||||
"2": "MISSILES",
|
||||
"3": "BOTH",
|
||||
}
|
||||
print(f"MISSED TARGET BY {int(2 + 30 * random.random())} MILES!")
|
||||
print("NOW YOU'RE REALLY IN FOR IT !!")
|
||||
print()
|
||||
enemy_weapons = _stdin_choice(
|
||||
prompt="DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)? ",
|
||||
choices=weapons_choices,
|
||||
)
|
||||
|
||||
# If there are no gunners (i.e. weapon choice 2) then
|
||||
# we say that the gunners have 0 accuracy for the purposes
|
||||
# of calculating probability of player death
|
||||
|
||||
enemy_gunner_accuracy = 0.0
|
||||
if enemy_weapons != "2":
|
||||
# If the enemy has guns, how accurate are the gunners?
|
||||
while True:
|
||||
try:
|
||||
enemy_gunner_accuracy = float(
|
||||
input("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ")
|
||||
)
|
||||
break
|
||||
except ValueError:
|
||||
# In the BASIC implementation this
|
||||
# wasn't accounted for
|
||||
print("TRY AGAIN...")
|
||||
continue
|
||||
|
||||
if enemy_gunner_accuracy < 10:
|
||||
print("YOU LIE, BUT YOU'LL PAY...")
|
||||
return player_death()
|
||||
|
||||
missile_threat_weighting = 0 if enemy_weapons == "1" else 35
|
||||
|
||||
death = death_with_chance(
|
||||
p_death=(enemy_gunner_accuracy + missile_threat_weighting) / 100
|
||||
)
|
||||
|
||||
return player_survived() if not death else player_death()
|
||||
|
||||
|
||||
def play_italy() -> None:
|
||||
targets_to_messages = {
|
||||
# 1 - ALBANIA, 2 - GREECE, 3 - NORTH AFRICA
|
||||
"1": "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE.",
|
||||
"2": "BE CAREFUL!!!",
|
||||
"3": "YOU'RE GOING FOR THE OIL, EH?",
|
||||
}
|
||||
target = _stdin_choice(
|
||||
prompt="YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)",
|
||||
choices=targets_to_messages,
|
||||
)
|
||||
|
||||
print(targets_to_messages[target])
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_allies() -> None:
|
||||
aircraft_to_message = {
|
||||
"1": "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI.",
|
||||
"2": "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA.",
|
||||
"3": "YOU'RE CHASING THE BISMARK IN THE NORTH SEA.",
|
||||
"4": "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.",
|
||||
}
|
||||
aircraft = _stdin_choice(
|
||||
prompt="AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4): ",
|
||||
choices=aircraft_to_message,
|
||||
)
|
||||
|
||||
print(aircraft_to_message[aircraft])
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_japan() -> None:
|
||||
print("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.")
|
||||
first_mission = input("YOUR FIRST KAMIKAZE MISSION? (Y OR N): ")
|
||||
if first_mission.lower() == "n":
|
||||
return player_death()
|
||||
return mission_success() if random.random() > 0.65 else player_death()
|
||||
|
||||
|
||||
def play_germany() -> None:
|
||||
targets_to_messages = {
|
||||
# 1 - RUSSIA, 2 - ENGLAND, 3 - FRANCE
|
||||
"1": "YOU'RE NEARING STALINGRAD.",
|
||||
"2": "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR.",
|
||||
"3": "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.",
|
||||
}
|
||||
target = _stdin_choice(
|
||||
prompt="A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\nENGLAND(2), OR FRANCE(3)? ",
|
||||
choices=targets_to_messages,
|
||||
)
|
||||
|
||||
print(targets_to_messages[target])
|
||||
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_game() -> None:
|
||||
print("YOU ARE A PILOT IN A WORLD WAR II BOMBER.")
|
||||
sides = {"1": play_italy, "2": play_allies, "3": play_japan, "4": play_germany}
|
||||
side = _stdin_choice(
|
||||
prompt="WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4): ", choices=sides
|
||||
)
|
||||
return sides[side]()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
again = True
|
||||
while again:
|
||||
play_game()
|
||||
again = True if input("ANOTHER MISSION? (Y OR N): ").upper() == "Y" else False
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "BombsAway", "BombsAway.vbproj", "{7FB28848-EC1C-4594-B823-BA6DB06B34A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7FB28848-EC1C-4594-B823-BA6DB06B34A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7FB28848-EC1C-4594-B823-BA6DB06B34A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7FB28848-EC1C-4594-B823-BA6DB06B34A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7FB28848-EC1C-4594-B823-BA6DB06B34A8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>BombsAway</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bounce", "Bounce.csproj", "{4A967985-8CB0-49D2-B322-B2668491CA6E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4A967985-8CB0-49D2-B322-B2668491CA6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A967985-8CB0-49D2-B322-B2668491CA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A967985-8CB0-49D2-B322-B2668491CA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A967985-8CB0-49D2-B322-B2668491CA6E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Bounce", "Bounce.vbproj", "{95D84C53-AE4E-4A5A-869A-A49D6FC89618}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{95D84C53-AE4E-4A5A-869A-A49D6FC89618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{95D84C53-AE4E-4A5A-869A-A49D6FC89618}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{95D84C53-AE4E-4A5A-869A-A49D6FC89618}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{95D84C53-AE4E-4A5A-869A-A49D6FC89618}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Bounce</RootNamespace>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>16.9</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bowling
|
||||
{
|
||||
public class Bowling
|
||||
{
|
||||
private readonly Pins pins = new();
|
||||
|
||||
private int players;
|
||||
|
||||
public void Play()
|
||||
{
|
||||
ShowBanner();
|
||||
MaybeShowInstructions();
|
||||
Setup();
|
||||
GameLoop();
|
||||
}
|
||||
|
||||
private static void ShowBanner()
|
||||
{
|
||||
Utility.PrintString(34, "BOWL");
|
||||
Utility.PrintString(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
Utility.PrintString();
|
||||
Utility.PrintString();
|
||||
Utility.PrintString();
|
||||
Utility.PrintString("WELCOME TO THE ALLEY");
|
||||
Utility.PrintString("BRING YOUR FRIENDS");
|
||||
Utility.PrintString("OKAY LET'S FIRST GET ACQUAINTED");
|
||||
Utility.PrintString();
|
||||
}
|
||||
private static void MaybeShowInstructions()
|
||||
{
|
||||
Utility.PrintString("THE INSTRUCTIONS (Y/N)");
|
||||
if (Utility.InputString() == "N") return;
|
||||
Utility.PrintString("THE GAME OF BOWLING TAKES MIND AND SKILL.DURING THE GAME");
|
||||
Utility.PrintString("THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH");
|
||||
Utility.PrintString("OTHER PLAYERS[UP TO FOUR].YOU WILL BE PLAYING TEN FRAMES");
|
||||
Utility.PrintString("ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE");
|
||||
Utility.PrintString("PIN IS STANDING.AFTER THE GAME THE COMPUTER WILL SHOW YOUR");
|
||||
Utility.PrintString("SCORES .");
|
||||
}
|
||||
private void Setup()
|
||||
{
|
||||
Utility.PrintString("FIRST OF ALL...HOW MANY ARE PLAYING", false);
|
||||
var input = Utility.InputInt();
|
||||
players = input < 1 ? 1 : input;
|
||||
Utility.PrintString();
|
||||
Utility.PrintString("VERY GOOD...");
|
||||
}
|
||||
private void GameLoop()
|
||||
{
|
||||
GameResults[] gameResults = InitGameResults();
|
||||
var done = false;
|
||||
while (!done)
|
||||
{
|
||||
ResetGameResults(gameResults);
|
||||
for (int frame = 0; frame < GameResults.FramesPerGame; ++frame)
|
||||
{
|
||||
for (int player = 0; player < players; ++player)
|
||||
{
|
||||
pins.Reset();
|
||||
int pinsDownThisFrame = pins.GetPinsDown();
|
||||
|
||||
int ball = 1;
|
||||
while (ball == 1 || ball == 2) // One or two rolls
|
||||
{
|
||||
Utility.PrintString("TYPE ROLL TO GET THE BALL GOING.");
|
||||
_ = Utility.InputString();
|
||||
|
||||
int pinsDownAfterRoll = pins.Roll();
|
||||
ShowPins(player, frame, ball);
|
||||
|
||||
if (pinsDownAfterRoll == pinsDownThisFrame)
|
||||
{
|
||||
Utility.PrintString("GUTTER!!");
|
||||
}
|
||||
|
||||
if (ball == 1)
|
||||
{
|
||||
// Store current pin count
|
||||
gameResults[player].Results[frame].PinsBall1 = pinsDownAfterRoll;
|
||||
|
||||
// Special handling for strike
|
||||
if (pinsDownAfterRoll == Pins.TotalPinCount)
|
||||
{
|
||||
Utility.PrintString("STRIKE!!!!!\a\a\a\a");
|
||||
// No second roll
|
||||
ball = 0;
|
||||
gameResults[player].Results[frame].PinsBall2 = pinsDownAfterRoll;
|
||||
gameResults[player].Results[frame].Score = FrameResult.Points.Strike;
|
||||
}
|
||||
else
|
||||
{
|
||||
ball = 2; // Roll again
|
||||
Utility.PrintString("ROLL YOUR SECOND BALL");
|
||||
}
|
||||
}
|
||||
else if (ball == 2)
|
||||
{
|
||||
// Store current pin count
|
||||
gameResults[player].Results[frame].PinsBall2 = pinsDownAfterRoll;
|
||||
ball = 0;
|
||||
|
||||
// Determine the score for the frame
|
||||
if (pinsDownAfterRoll == Pins.TotalPinCount)
|
||||
{
|
||||
Utility.PrintString("SPARE!!!!");
|
||||
gameResults[player].Results[frame].Score = FrameResult.Points.Spare;
|
||||
}
|
||||
else
|
||||
{
|
||||
Utility.PrintString("ERROR!!!");
|
||||
gameResults[player].Results[frame].Score = FrameResult.Points.Error;
|
||||
}
|
||||
}
|
||||
Utility.PrintString();
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowGameResults(gameResults);
|
||||
Utility.PrintString("DO YOU WANT ANOTHER GAME");
|
||||
var a = Utility.InputString();
|
||||
done = a.Length == 0 || a[0] != 'Y';
|
||||
}
|
||||
}
|
||||
|
||||
private GameResults[] InitGameResults()
|
||||
{
|
||||
var gameResults = new GameResults[players];
|
||||
for (int i = 0; i < gameResults.Length; i++)
|
||||
{
|
||||
gameResults[i] = new GameResults();
|
||||
}
|
||||
return gameResults;
|
||||
}
|
||||
|
||||
private void ShowPins(int player, int frame, int ball)
|
||||
{
|
||||
Utility.PrintString($"FRAME: {frame + 1} PLAYER: {player + 1} BALL: {ball}");
|
||||
var breakPins = new bool[] { true, false, false, false, true, false, false, true, false, true };
|
||||
var indent = 0;
|
||||
for (int pin = 0; pin < Pins.TotalPinCount; ++pin)
|
||||
{
|
||||
if (breakPins[pin])
|
||||
{
|
||||
Utility.PrintString(); // End row
|
||||
Utility.PrintString(indent++, false); // Indent next row
|
||||
}
|
||||
var s = pins[pin] == Pins.State.Down ? "+ " : "o ";
|
||||
Utility.PrintString(s, false);
|
||||
}
|
||||
Utility.PrintString();
|
||||
Utility.PrintString();
|
||||
}
|
||||
private void ResetGameResults(GameResults[] gameResults)
|
||||
{
|
||||
foreach (var gameResult in gameResults)
|
||||
{
|
||||
foreach (var frameResult in gameResult.Results)
|
||||
{
|
||||
frameResult.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
private void ShowGameResults(GameResults[] gameResults)
|
||||
{
|
||||
Utility.PrintString("FRAMES");
|
||||
for (int i = 0; i < GameResults.FramesPerGame; ++i)
|
||||
{
|
||||
Utility.PrintString(Utility.PadInt(i, 3), false);
|
||||
}
|
||||
Utility.PrintString();
|
||||
foreach (var gameResult in gameResults)
|
||||
{
|
||||
foreach (var frameResult in gameResult.Results)
|
||||
{
|
||||
Utility.PrintString(Utility.PadInt(frameResult.PinsBall1, 3), false);
|
||||
}
|
||||
Utility.PrintString();
|
||||
foreach (var frameResult in gameResult.Results)
|
||||
{
|
||||
Utility.PrintString(Utility.PadInt(frameResult.PinsBall2, 3), false);
|
||||
}
|
||||
Utility.PrintString();
|
||||
foreach (var frameResult in gameResult.Results)
|
||||
{
|
||||
Utility.PrintString(Utility.PadInt((int)frameResult.Score, 3), false);
|
||||
}
|
||||
Utility.PrintString();
|
||||
Utility.PrintString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bowling", "Bowling.csproj", "{9951637A-8D70-42A4-8CB7-315FA414F960}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9951637A-8D70-42A4-8CB7-315FA414F960}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9951637A-8D70-42A4-8CB7-315FA414F960}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9951637A-8D70-42A4-8CB7-315FA414F960}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9951637A-8D70-42A4-8CB7-315FA414F960}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bowling
|
||||
{
|
||||
public class FrameResult
|
||||
{
|
||||
public enum Points { None, Error, Spare, Strike };
|
||||
|
||||
public int PinsBall1 { get; set; }
|
||||
public int PinsBall2 { get; set; }
|
||||
public Points Score { get; set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PinsBall1 = PinsBall2 = 0;
|
||||
Score = Points.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bowling
|
||||
{
|
||||
public class GameResults
|
||||
{
|
||||
public static readonly int FramesPerGame = 10;
|
||||
public FrameResult[] Results { get; set; }
|
||||
|
||||
public GameResults()
|
||||
{
|
||||
Results = new FrameResult[FramesPerGame];
|
||||
for (int i = 0; i < FramesPerGame; ++i)
|
||||
{
|
||||
Results[i] = new FrameResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user