diff --git a/00_Utilities/DotnetUtils/.editorconfig b/00_Utilities/DotnetUtils/.editorconfig
new file mode 100644
index 00000000..9e7a90d5
--- /dev/null
+++ b/00_Utilities/DotnetUtils/.editorconfig
@@ -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
diff --git a/00_Utilities/DotnetUtils/DotnetUtils.sln b/00_Utilities/DotnetUtils/DotnetUtils.sln
new file mode 100644
index 00000000..ecf9588c
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils.sln
@@ -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
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/DotnetUtils.csproj b/00_Utilities/DotnetUtils/DotnetUtils/DotnetUtils.csproj
new file mode 100644
index 00000000..74abf5c9
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/DotnetUtils.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs b/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs
new file mode 100644
index 00000000..82ff9532
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/Extensions.cs
@@ -0,0 +1,27 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace DotnetUtils;
+
+public static class Extensions {
+ public static IEnumerable SelectT(this IEnumerable<(T1, T2)> src, Func selector) =>
+ src.Select(x => selector(x.Item1, x.Item2));
+ public static IEnumerable SelectT(this IEnumerable<(T1, T2, T3)> src, Func selector) =>
+ src.Select(x => selector(x.Item1, x.Item2, x.Item3));
+ public static IEnumerable<(T1, T2, int)> WithIndex(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index));
+
+ public static bool 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; }
+
+ var path1 = path.TrimEnd('\\');
+ rootPath = rootPath.TrimEnd('\\');
+ if (!path1.StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase)) { return path; }
+
+ return path1[(rootPath.Length + 1)..]; // ignore the initial /
+ }
+}
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Globals.cs b/00_Utilities/DotnetUtils/DotnetUtils/Globals.cs
new file mode 100644
index 00000000..1ff7c09d
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/Globals.cs
@@ -0,0 +1,8 @@
+namespace DotnetUtils;
+
+public static class Globals {
+ public static readonly Dictionary LangData = new() {
+ { "csharp", ("cs", "csproj") },
+ { "vbnet", ("vb", "vbproj") }
+ };
+}
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/PortInfo.cs b/00_Utilities/DotnetUtils/DotnetUtils/PortInfo.cs
new file mode 100644
index 00000000..894c8834
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/PortInfo.cs
@@ -0,0 +1,51 @@
+using System.Reflection;
+using static System.IO.Directory;
+using static System.IO.Path;
+using static DotnetUtils.Globals;
+
+namespace DotnetUtils;
+
+public record PortInfo(
+ string FullPath, 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
+ };
+
+ public static PortInfo? Create(string fullPath, string langKeyword) {
+ var folderName = GetFileName(fullPath);
+ var parts = folderName.Split('_', 2);
+
+ var index =
+ parts.Length > 0 && int.TryParse(parts[0], out var n) ?
+ n :
+ (int?)null;
+
+ var gameName =
+ parts.Length > 1 ?
+ parts[1].Replace("_", "") :
+ null;
+
+ if (index is 0 or null || gameName is null) { return null; }
+
+ var (ext, projExt) = LangData[langKeyword];
+ var langPath = Combine(fullPath, langKeyword);
+ var codeFiles =
+ GetFiles(langPath, $"*.{ext}", enumerationOptions)
+ .Where(x => !x.Contains("\\bin\\") && !x.Contains("\\obj\\"))
+ .ToArray();
+
+ return new PortInfo(
+ fullPath, folderName, index.Value, gameName,
+ langPath, langKeyword, ext, projExt,
+ codeFiles,
+ GetFiles(langPath, "*.sln", enumerationOptions),
+ GetFiles(langPath, $"*.{projExt}", enumerationOptions)
+ );
+ }
+}
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/PortInfos.cs b/00_Utilities/DotnetUtils/DotnetUtils/PortInfos.cs
new file mode 100644
index 00000000..b8c1afd3
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/PortInfos.cs
@@ -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(fullPath => LangData.Keys.Select(keyword => (fullPath, keyword)))
+ .SelectT((fullPath, keyword) => PortInfo.Create(fullPath, keyword))
+ .Where(x => x is not null)
+ .ToArray()!;
+ }
+
+ public static readonly PortInfo[] Get;
+}
diff --git a/00_Utilities/DotnetUtils/DotnetUtils/Program.cs b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs
new file mode 100644
index 00000000..6d4594b7
--- /dev/null
+++ b/00_Utilities/DotnetUtils/DotnetUtils/Program.cs
@@ -0,0 +1,166 @@
+using DotnetUtils;
+using static System.Console;
+using static System.IO.Path;
+
+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")
+};
+
+foreach (var (_, description, index) in actions.WithIndex()) {
+ WriteLine($"{index}: {description}");
+}
+
+WriteLine();
+
+actions[getChoice(actions.Length - 1)].action();
+
+int getChoice(int maxValue) {
+ int result;
+ do {
+ Write("? ");
+ } while (!int.TryParse(ReadLine(), out result) || result < 0 || result > maxValue);
+ WriteLine();
+ return result;
+}
+
+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;
+ }
+ WriteLine();
+}
+
+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.Any()).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.Any()) { continue; }
+
+ var expectedSlnName = $"{item.GameName}.sln";
+ if (item.Slns.Contains(Combine(item.LangPath, expectedSlnName))) { 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.Any()).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.Any()) { 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}");
+}
diff --git a/00_Utilities/VintageBASIC.xml b/00_Utilities/VintageBASIC.xml
new file mode 100644
index 00000000..4b49b888
--- /dev/null
+++ b/00_Utilities/VintageBASIC.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+ 00REM 01 02 03 04
+
+
+
+
+
+
+
+ - + ^ * / = <> < > <= >=
+
+
+
+
+
+
+
+
+
+
+ DATA DEF FN DIM END FOR GOSUB GOTO IF THEN INPUT LET NEXT ON PRINT RANDOMIZE REM RESTORE RETURN STOP TO
+ ABS ASC ATN CHR$ COS EXP INT LEFT$ LEN LOG MID$ RIGHT$ RND SGN SIN SPC SQR STR TAB TAN VAL
+ NOT AND OR
+
+
+
+
+
+ 00" 01 02" 03( 04 05) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/00_Utilities/find-missing-implementations.js b/00_Utilities/find-missing-implementations.js
new file mode 100644
index 00000000..61d849fe
--- /dev/null
+++ b/00_Utilities/find-missing-implementations.js
@@ -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;
diff --git a/01_Acey_Ducey/java/README.md b/01_Acey_Ducey/java/README.md
index 51edd8d4..4153020a 100644
--- a/01_Acey_Ducey/java/README.md
+++ b/01_Acey_Ducey/java/README.md
@@ -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.
diff --git a/07_Basketball/java/Basketball.java b/07_Basketball/java/Basketball.java
new file mode 100644
index 00000000..3ff9e7ab
--- /dev/null
+++ b/07_Basketball/java/Basketball.java
@@ -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 defense_choices = Arrays.asList(6.0, 6.5, 7.0, 7.5);
+ int shot = -1;
+ List 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();
+ }
+}
+
+
+
+
diff --git a/07_Basketball/python/basketball.py b/07_Basketball/python/basketball.py
index 5ba88188..20a6cd94 100644
--- a/07_Basketball/python/basketball.py
+++ b/07_Basketball/python/basketball.py
@@ -13,7 +13,7 @@ class Basketball():
self.defense = None
self.defense_choices = [6, 6.5, 7, 7.5]
self.shot = None
- self.shot_choices = [1, 2, 3, 4]
+ self.shot_choices = [0, 1, 2, 3, 4]
self.z1 = None
# Explains the keyboard inputs
diff --git a/09_Battle/java/.gitignore b/09_Battle/java/.gitignore
new file mode 100644
index 00000000..b25c15b8
--- /dev/null
+++ b/09_Battle/java/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/09_Battle/java/Battle.java b/09_Battle/java/Battle.java
new file mode 100644
index 00000000..c0b27906
--- /dev/null
+++ b/09_Battle/java/Battle.java
@@ -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 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(); // 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 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 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);
+ }
+ }
+}
diff --git a/09_Battle/java/Input.java b/09_Battle/java/Input.java
new file mode 100644
index 00000000..ee87465f
--- /dev/null
+++ b/09_Battle/java/Input.java
@@ -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]; }
+}
diff --git a/09_Battle/java/Sea.java b/09_Battle/java/Sea.java
new file mode 100644
index 00000000..f0c31fac
--- /dev/null
+++ b/09_Battle/java/Sea.java
@@ -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;
+ }
+}
diff --git a/09_Battle/java/Ship.java b/09_Battle/java/Ship.java
new file mode 100644
index 00000000..23605e5c
--- /dev/null
+++ b/09_Battle/java/Ship.java
@@ -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 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;
+ }
+}
diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py
new file mode 100644
index 00000000..5c591f44
--- /dev/null
+++ b/12_Bombs_Away/python/bombs_away.py
@@ -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
diff --git a/20_Buzzword/java/src/Buzzword.java b/20_Buzzword/java/src/Buzzword.java
index 82ed9100..85fecfd3 100755
--- a/20_Buzzword/java/src/Buzzword.java
+++ b/20_Buzzword/java/src/Buzzword.java
@@ -1,41 +1,17 @@
import java.util.Scanner;
-import static java.lang.System.out;
-// This is very close to the original BASIC. Changes:
-// 1) the array indexing is adjusted by 1
-// 2) the user can enter a lower case "y"
-// 3) moved the word list to the top 8~)
public class Buzzword {
- private static final String[] A = {
- "ABILITY","BASAL","BEHAVIORAL","CHILD-CENTERED",
- "DIFFERENTIATED","DISCOVERY","FLEXIBLE","HETEROGENEOUS",
- "HOMOGENEOUS","MANIPULATIVE","MODULAR","TAVISTOCK",
- "INDIVIDUALIZED","LEARNING","EVALUATIVE","OBJECTIVE",
- "COGNITIVE","ENRICHMENT","SCHEDULING","HUMANISTIC",
- "INTEGRATED","NON-GRADED","TRAINING","VERTICAL AGE",
- "MOTIVATIONAL","CREATIVE","GROUPING","MODIFICATION",
- "ACCOUNTABILITY","PROCESS","CORE CURRICULUM","ALGORITHM",
- "PERFORMANCE","REINFORCEMENT","OPEN CLASSROOM","RESOURCE",
- "STRUCTURE","FACILITY","ENVIRONMENT"
- };
- private static Scanner scanner = new Scanner( System.in );
- public static void main( final String [] args ) {
- out.println( " BUZZWORD GENERATOR" );
- out.println( " CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" );
- out.println();out.println();out.println();
- out.println( "THIS PROGRAM PRINTS HIGHLY ACCEPTABLE PHRASES IN" );
- out.println( "'EDUCATOR-SPEAK' THAT YOU CAN WORK INTO REPORTS" );
- out.println( "AND SPEECHES. WHENEVER A QUESTION MARK IS PRINTED," );
- out.println( "TYPE A 'Y' FOR ANOTHER PHRASE OR 'N' TO QUIT." );
- out.println();out.println();out.println( "HERE'S THE FIRST PHRASE:" );
- do {
- out.print( A[ (int)( 13 * Math.random() ) ] + " " );
- out.print( A[ (int)( 13 * Math.random() + 13 ) ] + " " );
- out.print( A[ (int)( 13 * Math.random() + 26 ) ] ); out.println();
- out.print( "?" );
+ public static void main(final String[] args) {
+ try (
+ // Scanner is a Closeable so it must be closed
+ // before the program ends.
+ final Scanner scanner = new Scanner(System.in);
+ ) {
+ final BuzzwordSupplier buzzwords = new BuzzwordSupplier();
+ final UserInterface userInterface = new UserInterface(
+ scanner, System.out, buzzwords);
+ userInterface.run();
}
- while ( "Y".equals( scanner.nextLine().toUpperCase() ) );
- out.println( "COME BACK WHEN YOU NEED HELP WITH ANOTHER REPORT!" );
}
}
diff --git a/20_Buzzword/java/src/BuzzwordSupplier.java b/20_Buzzword/java/src/BuzzwordSupplier.java
new file mode 100644
index 00000000..679969f9
--- /dev/null
+++ b/20_Buzzword/java/src/BuzzwordSupplier.java
@@ -0,0 +1,39 @@
+import java.util.Random;
+import java.util.function.Supplier;
+
+/**
+ * A string supplier that provides an endless stream of random buzzwords.
+ */
+public class BuzzwordSupplier implements Supplier {
+
+ private static final String[] SET_1 = {
+ "ABILITY","BASAL","BEHAVIORAL","CHILD-CENTERED",
+ "DIFFERENTIATED","DISCOVERY","FLEXIBLE","HETEROGENEOUS",
+ "HOMOGENEOUS","MANIPULATIVE","MODULAR","TAVISTOCK",
+ "INDIVIDUALIZED" };
+
+ private static final String[] SET_2 = {
+ "LEARNING","EVALUATIVE","OBJECTIVE",
+ "COGNITIVE","ENRICHMENT","SCHEDULING","HUMANISTIC",
+ "INTEGRATED","NON-GRADED","TRAINING","VERTICAL AGE",
+ "MOTIVATIONAL","CREATIVE" };
+
+ private static final String[] SET_3 = {
+ "GROUPING","MODIFICATION", "ACCOUNTABILITY","PROCESS",
+ "CORE CURRICULUM","ALGORITHM", "PERFORMANCE",
+ "REINFORCEMENT","OPEN CLASSROOM","RESOURCE", "STRUCTURE",
+ "FACILITY","ENVIRONMENT" };
+
+ private final Random random = new Random();
+
+ /**
+ * Create a buzzword by concatenating a random word from each of the
+ * three word sets.
+ */
+ @Override
+ public String get() {
+ return SET_1[random.nextInt(SET_1.length)] + ' ' +
+ SET_2[random.nextInt(SET_2.length)] + ' ' +
+ SET_3[random.nextInt(SET_3.length)];
+ }
+}
diff --git a/20_Buzzword/java/src/UserInterface.java b/20_Buzzword/java/src/UserInterface.java
new file mode 100644
index 00000000..103e88c8
--- /dev/null
+++ b/20_Buzzword/java/src/UserInterface.java
@@ -0,0 +1,64 @@
+import java.io.PrintStream;
+import java.util.Scanner;
+import java.util.function.Supplier;
+
+/**
+ * A command line user interface that outputs a buzzword every
+ * time the user requests a new one.
+ */
+public class UserInterface implements Runnable {
+
+ /**
+ * Input from the user.
+ */
+ private final Scanner input;
+
+ /**
+ * Output to the user.
+ */
+ private final PrintStream output;
+
+ /**
+ * The buzzword generator.
+ */
+ private final Supplier buzzwords;
+
+ /**
+ * Create a new user interface.
+ *
+ * @param input The input scanner with which the user gives commands.
+ * @param output The output to show messages to the user.
+ * @param buzzwords The buzzword supplier.
+ */
+ public UserInterface(final Scanner input,
+ final PrintStream output,
+ final Supplier buzzwords) {
+ this.input = input;
+ this.output = output;
+ this.buzzwords = buzzwords;
+ }
+
+ @Override
+ public void run() {
+ output.println(" BUZZWORD GENERATOR");
+ output.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ output.println();
+ output.println();
+ output.println();
+ output.println("THIS PROGRAM PRINTS HIGHLY ACCEPTABLE PHRASES IN");
+ output.println("'EDUCATOR-SPEAK' THAT YOU CAN WORK INTO REPORTS");
+ output.println("AND SPEECHES. WHENEVER A QUESTION MARK IS PRINTED,");
+ output.println("TYPE A 'Y' FOR ANOTHER PHRASE OR 'N' TO QUIT.");
+ output.println();
+ output.println();
+ output.println("HERE'S THE FIRST PHRASE:");
+
+ do {
+ output.println(buzzwords.get());
+ output.println();
+ output.print("?");
+ } while ("Y".equals(input.nextLine().toUpperCase()));
+
+ output.println("COME BACK WHEN YOU NEED HELP WITH ANOTHER REPORT!");
+ }
+}
diff --git a/21_Calendar/perl/README.md b/21_Calendar/perl/README.md
index e69c8b81..043be194 100644
--- a/21_Calendar/perl/README.md
+++ b/21_Calendar/perl/README.md
@@ -1,3 +1,13 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
+
+Actually, this is not so much a port as a complete rewrite, making use of
+Perl's Posix time functionality. The calendar is for the current year (not
+1979), but you can get another year by specifying it on the command line, e.g.
+
+ `perl 21_Calendar/perl/calendar.pl 2001`
+
+It *may* even produce output in languages other than English. But the
+leftmost column will still be Sunday, even in locales where it is
+typically Monday.
diff --git a/21_Calendar/perl/calendar.pl b/21_Calendar/perl/calendar.pl
new file mode 100755
index 00000000..96ba387b
--- /dev/null
+++ b/21_Calendar/perl/calendar.pl
@@ -0,0 +1,130 @@
+#!/usr/bin/env perl
+
+use 5.010; # To get 'state' and 'say'
+
+use strict; # Require explicit declaration of variables
+use warnings; # Enable optional compiler warnings
+
+use English; # Use more friendly names for Perl's magic variables
+use POSIX qw{ strftime };
+use Term::ReadLine; # Prompt and return user input
+use Time::Local ();
+
+BEGIN {
+ *time_gm =
+ Time::Local->can( 'timegm_modern' ) ||
+ Time::Local->can( 'timegm' );
+}
+
+our $VERSION = '0.000_01';
+
+use constant COLUMN_WIDTH => 6;
+use constant SECONDS_PER_DAY => 86400;
+
+binmode STDOUT, ':encoding(utf-8)';
+
+my $year = @ARGV ? $ARGV[0] : ( localtime )[5] + 1900;
+my $is_leap_year = is_leap_year( $year );
+my $year_len = 365 + $is_leap_year;
+print <<'EOD';
+ CALENDAR
+ Creative Computing Morristown, New Jersey
+
+
+EOD
+
+my @mon_len = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
+$mon_len[1] += $is_leap_year;
+
+foreach my $month ( 0 .. 11 ) {
+ my $epoch = time_gm( 0, 0, 0, 1, $month, $year );
+ my @start_time = gmtime( $epoch );
+ my ( $week_day, $year_day ) = @start_time[ 6, 7 ];
+ my $label = strftime( '%B %Y', @start_time );
+ $label .= ' ' x ( ( 14 - length $label ) / 2 );
+ printf "\n** %3d ****** %14s ****** %3d **\n",
+ $year_day, $label, $year_len - $year_day;
+ {
+ my $day = 1 + ( 7 - $week_day ) % 7;
+ foreach my $wd ( 0 .. 6 ) {
+ my $ep = time_gm( 0, 0, 0, $day + $wd, $month, $year );
+ printf '%*s', COLUMN_WIDTH, strftime( '%a', gmtime $ep );
+ }
+ print "\n";
+ }
+ say '*' x ( COLUMN_WIDTH * 7 );
+ print ' ' x ( COLUMN_WIDTH * $week_day );
+ my $month_day = 1;
+ while ( $week_day < 7 ) {
+ printf '%*d', COLUMN_WIDTH, $month_day++;
+ $week_day++;
+ }
+ print "\n";
+ $week_day = 0;
+ while ( $month_day <= $mon_len[$month] ) {
+ printf '%*d', COLUMN_WIDTH, $month_day++;
+ $week_day++;
+ unless ( $week_day % 7 ) {
+ print "\n";
+ $week_day = 0;
+ }
+ }
+ print "\n" if $week_day;
+
+}
+
+sub is_leap_year {
+ my ( $year ) = 1;
+ return 0 if $year % 4;
+ return 1 if $year % 100;
+ return 0 if $year % 400;
+ return 1;
+}
+
+__END__
+
+=head1 TITLE
+
+calendar - Play the game 'Calendar' from Basic Computer Games
+
+=head1 SYNOPSIS
+
+ calendar.pl
+
+=head1 DETAILS
+
+This Perl script is a port of calendar, which is the 21st
+entry in Basic Computer Games.
+
+Actually, it is not so much a port as a complete rewrite, making use of
+Perl's Posix time functionality. The calendar is for the current year
+(not 1979), but you can get another year by specifying it on the command
+line, e.g.
+
+ perl 21_Calendar/perl/calendar.pl 2001
+
+It B even produce output in languages other than English. But the
+leftmost column will still be Sunday, even in locales where it is
+typically Monday.
+
+=head1 PORTED BY
+
+Thomas R. Wyant, III F
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2022 by Thomas R. Wyant, III
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl 5.10.0. For more details, see the Artistic
+License 1.0 at
+L, and/or the
+Gnu GPL at L.
+
+This program is distributed in the hope that it will be useful, but
+without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
+
+=cut
+
+# ex: set expandtab tabstop=4 textwidth=72 :
diff --git a/25_Chief/ruby/chief.rb b/25_Chief/ruby/chief.rb
new file mode 100644
index 00000000..c60b2519
--- /dev/null
+++ b/25_Chief/ruby/chief.rb
@@ -0,0 +1,98 @@
+def tab(size)
+ str = ''
+ size.times do
+ str += ' '
+ end
+
+ str
+end
+
+def input
+ gets.chomp
+end
+
+def bye
+ print "BYE!!!\n"
+end
+
+def main
+ print tab(30), "CHIEF\n"
+ print tab(15), "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"
+ print "\n"
+ print "\n"
+ print "\n"
+ print "I AM CHIEF NUMBERS FREEK, THE GREAT INDIAN MATH GOD.\n"
+ print "ARE YOU READY TO TAKE THE TEST YOU CALLED ME OUT FOR\n"
+
+ a = input
+ if a != 'YES'
+ print "SHUT UP, PALE FACE WITH WISE TONGUE.\n"
+ end
+
+ print " TAKE A NUMBER AND ADD 3. DIVIDE THIS NUMBER BY 5 AND\n"
+ print "MULTIPLY BY 8. DIVIDE BY 5 AND ADD THE SAME. SUBTRACT 1.\n"
+ print " WHAT DO YOU HAVE\n"
+
+ b = input.to_f
+ c = ((b + 1 - 5) * 5 / 8 * 5 -3).to_f
+ print "I BET YOUR NUMBER WAS #{c}. AM I RIGHT\n"
+
+ d = input
+ if d == 'YES'
+ return bye
+ end
+
+ print "WHAT WAS YOUR ORIGINAL NUMBER\n"
+
+ k = input.to_f
+ f = k + 3
+ g = f / 5
+ h = g * 8
+ i = h / 5 + 5
+ j = i - 1
+
+ print "SO YOU THINK YOU'RE SO SMART, EH?\n"
+ print "NOW WATCH.\n"
+ print k, " PLUS 3 EQUALS ", f, ". THIS DIVIDED BY 5 EQUALS ", g, ";\n"
+ print "THIS TIMES 8 EQUALS ", h, ". IF WE DIVIDE BY 5 AND ADD 5,\n"
+ print "WE GET ", i, ", WHICH, MINUS 1, EQUALS ", j, ".\n"
+ print "NOW DO YOU BELIEVE ME\n"
+
+ z = input
+ if z == 'YES'
+ return bye
+ end
+
+ print "YOU HAVE MADE ME MAD!!!\n"
+ print "THERE MUST BE A GREAT LIGHTNING BOLT!\n"
+ print "\n"
+ print "\n"
+
+ x = 30
+ while x >= 22
+ print tab(x), "X X\n"
+ x -= 1
+ end
+
+ print tab(21), "X XXX\n"
+ print tab(20), "X X\n"
+ print tab(19), "XX X\n"
+
+ y = 20
+ while y >= 13
+ print tab(y), "X X\n"
+ y -= 1
+ end
+
+ print tab(12), "XX\n"
+ print tab(11), "X\n"
+ print tab(10), "*\n"
+
+ print "\n"
+ print "#########################\n"
+ print "\n"
+ print "I HOPE YOU BELIEVE ME NOW, FOR YOUR SAKE!!\n"
+ return bye
+end
+
+main
diff --git a/29_Craps/README.md b/29_Craps/README.md
index 7372b99f..3c45c62b 100644
--- a/29_Craps/README.md
+++ b/29_Craps/README.md
@@ -17,3 +17,211 @@ As published in Basic Computer Games (1978):
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+
+### Comments on the BASIC code for re-implementers.
+
+ 15 LET R=0
+`R` is a variable that tracks winnings and losings. Unlike other games that
+start out with a lump sum of cash to spend this game assumes the user has as
+much money as they want and we only track how much they lost or won.
+
+ 21 LET T=1
+ 22 PRINT "PICK A NUMBER AND INPUT TO ROLL DICE";
+ 23 INPUT Z
+ 24 LET X=(RND(0))
+ 25 LET T =T+1
+ 26 IF T<=Z THEN 24
+This block of code does nothing other than try to scramble the random number
+generator. Random number generation is not random, they are generated from the
+previous generated number. Because of the slow speed of these systems back then,
+gaming random number generators was a concern, mostly for gameplay quality.
+If you could know the "seed value" to the generator then you could effectively
+know how to get the exact same dice rolls to happen and change your bet to
+maximize your winnings and minimize your losses.
+
+The first reason this is an example of bad coding practice is the user is asked
+to input a number but no clue is given as to the use of this number. This number
+has no bearing on the game and as we'll see only has bearing on the internal
+implementation of somehow trying to get an un-game-able seed for the random number
+generator (since all future random numbers generated are based off this seed value.)
+
+The `RND(1)` command generates a number from a seed value that is always
+the same, everytime, from when the machine is booted up (old C64 behavior). In
+order to avoid the same dice rolls being generated, a special call to `RND(-TI)`
+would initialize the random generator with something else. But RND(-TI) is not
+a valid command on all systems. So `RND(0)`, which generates a random number
+from the system clock is used. But technically this could be gamed because the
+system clock was driven by the bootup time, there wasn't a BIOS battery on these
+systems that kept an internal real time clock going even when the system was
+turned off, unlike your regular PC. Therefore, in order to ensure as true
+randomness as possible, insert human reaction time by asking for human input.
+
+But a human could just be holding down the enter key on bootup and that would
+just skip any kind of multi-millisecond variance assigned by a natural human
+reaction time. So, paranoia being a great motivator, a number is asked of the
+user to avoid just holding down the enter key which negates the timing variance
+of a human reaction.
+
+What comes next is a bit of nonsense. The block of code loops a counter, recalling
+the `RND(0)` function (and thus reseeding it with the system clock value)
+and then comparing the counter to the user's number input
+in order to bail out of the loop. Because the `RND(0)` function is based off the
+system clock and the loop of code has no branching other than the bailout
+condition, the loop also takes a fixed amount of time to execute, thus making
+repeated calls to `RND(0)` predictive and this scheming to get a better random
+number is pointless. Furthermore, the loop is based on the number the user inputs
+so a huge number like ten million causes a very noticable delay and leaves the
+user wondering if the program has errored. The author could have simply called
+`RND(0)` once and used a prompt that made more sense like asking for the users
+name and then using that name in the game's replies.
+
+It is advised that you use whatever your languages' random number generator
+provides and simply skip trying to recreate this bit of nonsense including
+the user input.
+
+ 27 PRINT"INPUT THE AMOUNT OF YOUR WAGER.";
+ 28 INPUT F
+ 30 PRINT "I WILL NOW THROW THE DICE"
+ 40 LET E=INT(7*RND(1))
+ 41 LET S=INT(7*RND(1))
+ 42 LET X=E+S
+ .... a bit later ....
+ 60 IF X=1 THEN 40
+ 65 IF X=0 THEN 40
+
+
+`F` is a variable that represents the users wager for this betting round.
+`E` and `S` represent the two individual and random dice being rolled.
+This code is actually wrong because it returns a value between 0 and 6.
+`X` is the sum of these dice rolls. As you'll see though further down in the
+code, if `X` is zero or one it re-rolls the dice to maintain a potential
+outcome of the sum of two dice between 2 and 12. This skews the normal distribution
+of dice values to favor lower numbers because it does not consider that `E`
+could be zero and `S` could be 2 or higher. To show this skewing of values
+you can run the `distribution.bas` program which creates a histogram of the
+distribution of the bad dice throw code and proper dice throw code.
+
+Here are the results:
+
+ DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)
+ THE INT(7*RND(1)) DISTRIBUTION:
+ 2 3 4 5 6 7 8 9 10 11 12
+ 6483 8662 10772 13232 15254 13007 10746 8878 6486 4357 2123
+ THE INT(6*RND(1)+1) DISTRIBUTION
+ 2 3 4 5 6 7 8 9 10 11 12
+ 2788 5466 8363 11072 13947 16656 13884 11149 8324 5561 2790
+If the dice rolls are fair then we should see the largest occurrence be a 7 and
+the smallest should be 2 and 12. Furthermore the occurrences should be
+symetrical meaning there should be roughly the same amount of 2's as 12's, the
+same amount of 3's as 11's, 4's as 10's and so on until you reach the middle, 7.
+But notice in the skewed dice roll, 6 is the most rolled number not 7, and the
+rest of the numbers are not symetrical, there are many more 2's than 12's.
+So the lesson is test your code.
+
+The proper way to model a dice throw, in almost every language is
+ `INT(6*RND(1)+1)` or `INT(6*RND(1))+1`
+
+SideNote: `X` was used already in the
+previous code block discussed but its value was never used. This is another
+poor coding practice: **Don't reuse variable names for different purposes.**
+
+ 50 IF X=7 THEN 180
+ 55 IF X=11 THEN 180
+ 60 IF X=1 THEN 40
+ 62 IF X=2 THEN 195
+ 65 IF X=0 THEN 40
+ 70 IF X=2 THEN 200
+ 80 IF X=3 THEN 200
+ 90 IF X=12 THEN 200
+ 125 IF X=5 THEN 220
+ 130 IF X =6 THEN 220
+ 140 IF X=8 THEN 220
+ 150 IF X=9 THEN 220
+ 160 IF X =10 THEN 220
+ 170 IF X=4 THEN 220
+
+This bit of code determines the routing of where to go for payout, or loss.
+Of course, line 60 and 65 are pointless as we've just shown and should be removed
+as long as the correct dice algorithm is also changed.
+
+ 62 IF X=2 THEN 195
+ ....
+ 70 IF X=2 THEN 200
+The check for a 2 has already been made and the jump is done. Line 70 is
+therefore redundant and can be left out. The purpose of line 62 is only to
+print a special output, "SNAKE EYES!" which we'll see in the next block creates
+duplicate code.
+
+Lines 125-170 are also pointlessly checked because we know previous values have
+been ruled out, only these last values must remain, and they are all going to
+the same place, line 220. Line 125-170 could have simply been replaced with
+`GOTO 220`
+
+
+
+ 180 PRINT X "- NATURAL....A WINNER!!!!"
+ 185 PRINT X"PAYS EVEN MONEY, YOU WIN"F"DOLLARS"
+ 190 GOTO 210
+ 195 PRINT X"- SNAKE EYES....YOU LOSE."
+ 196 PRINT "YOU LOSE"F "DOLLARS."
+ 197 LET F=0-F
+ 198 GOTO 210
+ 200 PRINT X " - CRAPS...YOU LOSE."
+ 205 PRINT "YOU LOSE"F"DOLLARS."
+ 206 LET F=0-F
+ 210 LET R= R+F
+ 211 GOTO 320
+
+This bit of code manages instant wins or losses due to 7,11 or 2,3,12. As
+mentioned previously, lines 196 and 197 are essentially the same as lines
+205 and 206. A simpler code would be just to jump after printing the special
+message of "SNAKE EYES!" to line 205.
+
+Lines 197 and 206 just negate the wager by subtracting it from zero. Just saying
+`F = -F` would have sufficed. Line 210 updates your running total of winnings
+or losses with this bet.
+
+ 220 PRINT X "IS THE POINT. I WILL ROLL AGAIN"
+ 230 LET H=INT(7*RND(1))
+ 231 LET Q=INT(7*RND(1))
+ 232 LET O=H+Q
+ 240 IF O=1 THEN 230
+ 250 IF O=7 THEN 290
+ 255 IF O=0 THEN 230
+
+This code sets the point, the number you must re-roll to win without rolling
+a 7, the most probable number to roll. Except in this case again, it has the
+same incorrect dice rolling code and therefore 6 is the most probable number
+to roll. The concept of DRY (don't repeat yourself) is a coding practice which
+encourages non-duplication of code because if there is an error in the code, it
+can be fixed in one place and not multiple places like in this code. The scenario
+might be that a programmer sees some wrong code, fixes it, but neglects to
+consider that there might be duplicates of the same wrong code elsewhere. If
+you practice DRY then you never worry much about behaviors in your code diverging
+due to duplicate code snippets.
+
+ 260 IF O=X THEN 310
+ 270 PRINT O " - NO POINT. I WILL ROLL AGAIN"
+ 280 GOTO 230
+ 290 PRINT O "- CRAPS. YOU LOSE."
+ 291 PRINT "YOU LOSE $"F
+ 292 F=0-F
+ 293 GOTO 210
+ 300 GOTO 320
+ 310 PRINT X"- A WINNER.........CONGRATS!!!!!!!!"
+ 311 PRINT X "AT 2 TO 1 ODDS PAYS YOU...LET ME SEE..."2*F"DOLLARS"
+ 312 LET F=2*F
+ 313 GOTO 210
+
+This is the code to keep rolling until the point is made or a seven is rolled.
+Again we see the negated `F` wager and lose message duplicated. This code could
+have been reorganized using a subroutine, or in BASIC, the GOSUB command, but
+in your language its most likely just known as a function or method. You can
+do a `grep -r 'GOSUB'` from the root directory to see other BASIC programs in
+this set that use GOSUB.
+
+The rest of the code if fairly straight forward, replay the game or end with
+a report of your winnings or losings.
+
+
+
diff --git a/29_Craps/distributions.bas b/29_Craps/distributions.bas
new file mode 100644
index 00000000..a963b228
--- /dev/null
+++ b/29_Craps/distributions.bas
@@ -0,0 +1,24 @@
+10 PRINT "DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)"
+20 DIM A(12)
+30 DIM B(12)
+100 FOR X = 1 TO 100000 : REM CHOOSE A LARGE NUMBER TO GET A FINER GRAINED HISTOGRAM
+140 REM GET A NUMBER FROM 0 TO 6 INCLUSIVE WITH THE INTENT TO THROW AWAY ZEROES.
+150 LET D1 = INT(7*RND(1))
+155 LET D2 = INT(7*RND(1))
+160 LET S1 = D1+D2
+165 REM IF THIS SUM IS LESS THAN TWO THEN TRY AGAIN.
+170 IF S1<2 THEN 150
+199 REM GET A NUMBER FROM 0 TO 5 THEN ADD 1 TO IT TO MAKE IT 1 TO 6
+200 LET D3 = INT(6*RND(1))+1
+210 LET D4 = INT(6*RND(1))+1
+220 LET S2 = D3+D4
+245 REM USE OUR ARRAY AS A HISTOGRAM, COUNTING EACH OCCURRENCE OF DICE ROLL
+250 A(S1) = A(S1) + 1
+260 B(S2) = B(S2) + 1
+290 NEXT X
+300 PRINT "THE INT(7*RND(1)) DISTRIBUTION:"
+310 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
+320 FOR I = 2 TO 12 :PRINT A(I),:NEXT:PRINT
+325 PRINT "THE INT(6*RND(1)+1) DISTRIBUTION"
+330 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
+340 FOR I = 2 TO 12 :PRINT B(I),:NEXT:PRINT
diff --git a/29_Craps/java/src/Craps.java b/29_Craps/java/src/Craps.java
new file mode 100644
index 00000000..483c16a9
--- /dev/null
+++ b/29_Craps/java/src/Craps.java
@@ -0,0 +1,125 @@
+import java.util.Random;
+import java.util.Scanner;
+
+/**
+ * Port of Craps from BASIC to Java 17.
+ */
+public class Craps {
+ public static final Random random = new Random();
+
+ public static void main(String[] args) {
+ System.out.println("""
+ CRAPS
+ CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
+
+
+ 2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.
+ """);
+ double winnings = 0.0;
+ do {
+ winnings = playCraps(winnings);
+ } while (stillInterested(winnings));
+ winningsReport(winnings);
+ }
+
+ public static double playCraps(double winnings) {
+ double wager = getWager();
+ System.out.println("I WILL NOW THROW THE DICE");
+ int roll = rollDice();
+ double payout = switch (roll) {
+ case 7, 11 -> naturalWin(roll, wager);
+ case 2, 3, 12 -> lose(roll, wager);
+ default -> setPoint(roll, wager);
+ };
+ return winnings + payout;
+ }
+
+ public static int rollDice() {
+ return random.nextInt(1, 7) + random.nextInt(1, 7);
+ }
+
+ private static double setPoint(int point, double wager) {
+ System.out.printf("%1$ d IS THE POINT. I WILL ROLL AGAIN%n",point);
+ return makePoint(point, wager);
+ }
+
+ private static double makePoint(int point, double wager) {
+ int roll = rollDice();
+ if (roll == 7)
+ return lose(roll, wager);
+ if (roll == point)
+ return win(roll, wager);
+ System.out.printf("%1$ d - NO POINT. I WILL ROLL AGAIN%n", roll);
+ return makePoint(point, wager); // recursive
+ }
+
+ private static double win(int roll, double wager) {
+ double payout = 2 * wager;
+ System.out.printf("%1$ d - A WINNER.........CONGRATS!!!!!!!!%n", roll);
+ System.out.printf("%1$ d AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$%2$3.2f%n",
+ roll, payout);
+ return payout;
+ }
+
+ private static double lose(int roll, double wager) {
+ String msg = roll == 2 ? "SNAKE EYES.":"CRAPS";
+ System.out.printf("%1$ d - %2$s...YOU LOSE.%n", roll, msg);
+ System.out.printf("YOU LOSE $%3.2f%n", wager);
+ return -wager;
+ }
+
+ public static double naturalWin(int roll, double wager) {
+ System.out.printf("%1$ d - NATURAL....A WINNER!!!!%n", roll);
+ System.out.printf("%1$ d PAYS EVEN MONEY, YOU WIN $%2$3.2f%n", roll, wager);
+ return wager;
+ }
+
+ public static void winningsUpdate(double winnings) {
+ System.out.println(switch ((int) Math.signum(winnings)) {
+ case 1 -> "YOU ARE NOW AHEAD $%3.2f".formatted(winnings);
+ case 0 -> "YOU ARE NOW EVEN AT 0";
+ default -> "YOU ARE NOW UNDER $%3.2f".formatted(-winnings);
+ });
+ }
+
+ public static void winningsReport(double winnings) {
+ System.out.println(
+ switch ((int) Math.signum(winnings)) {
+ case 1 -> "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!";
+ case 0 -> "CONGRATULATIONS---YOU CAME OUT EVEN, NOT BAD FOR AN AMATEUR";
+ default -> "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN.";
+ }
+ );
+ }
+
+ public static boolean stillInterested(double winnings) {
+ System.out.print(" IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2 ");
+ int fiveOrTwo = (int)getInput();
+ winningsUpdate(winnings);
+ return fiveOrTwo == 5;
+ }
+
+ public static double getWager() {
+ System.out.print("INPUT THE AMOUNT OF YOUR WAGER. ");
+ return getInput();
+ }
+
+ public static double getInput() {
+ Scanner scanner = new Scanner(System.in);
+ System.out.print("> ");
+ while (true) {
+ try {
+ return scanner.nextDouble();
+ } catch (Exception ex) {
+ try {
+ scanner.nextLine(); // flush whatever this non number stuff is.
+ } catch (Exception ns_ex) { // received EOF (ctrl-d or ctrl-z if windows)
+ System.out.println("END OF INPUT, STOPPING PROGRAM.");
+ System.exit(1);
+ }
+ }
+ System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
+ System.out.print("> ");
+ }
+ }
+}
diff --git a/29_Craps/javascript/craps.js b/29_Craps/javascript/craps.js
index 93351011..108f6067 100644
--- a/29_Craps/javascript/craps.js
+++ b/29_Craps/javascript/craps.js
@@ -42,6 +42,11 @@ function tab(space)
return str;
}
+function roll()
+{
+ return Math.floor(6 * Math.random())+1 + Math.floor(6 * Math.random())+1;
+}
+
// Main program
async function main()
{
@@ -52,22 +57,11 @@ async function main()
print("\n");
r = 0;
print("2,3,12 ARE LOSERS: 4,5,6,8,9,10 ARE POINTS: 7,11 ARE NATURAL WINNERS.\n");
- t = 1;
- print("PICK A NUMBER AND INPUT TO ROLL DICE");
- z = parseInt(await input());
- do {
- x = Math.random();
- t++;
- } while (t <= z) ;
while (1) {
print("INPUT THE AMOUNT OF YOUR WAGER.");
f = parseInt(await input());
print("I WILL NOW THROW THE DICE\n");
- do {
- e = Math.floor(7 * Math.random());
- s = Math.floor(7 * Math.random());
- x = e + s;
- } while (x == 0 || x == 1) ;
+ x = roll();
if (x == 7 || x == 11) {
print(x + " - NATURAL....A WINNER!!!!\n");
print(x + " PAYS EVEN MONEY, YOU WIN " + f + " DOLLARS\n");
@@ -83,11 +77,7 @@ async function main()
} else {
print(x + " IS THE POINT. I WILL ROLL AGAIN\n");
while (1) {
- do {
- h = Math.floor(7 * Math.random());
- q = Math.floor(7 * Math.random());
- o = h + q;
- } while (o == 0 || o == 1) ;
+ o = roll();
if (o == 7) {
print(o + " - CRAPS, YOU LOSE.\n");
print("YOU LOSE $" + f + "\n");
diff --git a/31_Depth_Charge/ruby/depthcharge.rb b/31_Depth_Charge/ruby/depthcharge.rb
index ba2a5d83..67ab7cc8 100755
--- a/31_Depth_Charge/ruby/depthcharge.rb
+++ b/31_Depth_Charge/ruby/depthcharge.rb
@@ -1,59 +1,60 @@
#!/usr/bin/ruby
class DepthCharge
-
def run_game
- output_title()
- while true
- printf("----------\n")
- print_instructions()
- setup_game()
- printf("\n")
- game_loop()
- break if ! get_input_another_game()
+ output_title
+
+ loop do
+ puts "----------"
+ print_instructions
+ setup_game
+ puts
+ game_loop
+ break unless get_input_another_game
end
- printf("OK. HOPE YOU ENJOYED YOURSELF.\n")
+ puts "OK. HOPE YOU ENJOYED YOURSELF."
end
def output_title
- printf("--- DEPTH CHARGE ---\n")
- printf("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n")
- printf("\n")
+ puts "--- DEPTH CHARGE ---"
+ puts "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
+ puts
end
def get_input_y_or_n(message)
- while true
- print(message)
+ loop do
+ print message
value = gets.chomp
- if (value == 'Y' || value == 'y')
+ if value.downcase == "y"
return true
- elsif value == 'N' || value == 'n'
+ elsif value.downcase == "n"
return false
end
- printf("PLEASE ENTER Y/y OR N/n...\n\n")
+ puts "PLEASE ENTER Y/y OR N/n..."
+ puts
end
end
def get_input_positive_integer(message)
-
- while true
- print(message)
+ loop do
+ print message
value = gets.chomp
- if (value == 'd')
- debug_game()
+
+ if value == "d"
+ debug_game
next
end
- the_input = Integer(value) rescue nil
+ the_input = Integer(value) rescue 0
- if the_input == nil || the_input < 1
- printf("PLEASE ENTER A POSITIVE NUMBER\n\n")
+ if the_input < 1
+ puts "PLEASE ENTER A POSITIVE NUMBER"
+ puts
next
-
end
return the_input
@@ -61,42 +62,39 @@ class DepthCharge
end
def print_instructions
- printf( <<~INSTRUCTIONS
-YOU ARE THE CAPTAIN OF THE DESTROYER USS COMPUTER
-AN ENEMY SUB HAS BEEN CAUSING YOU TROUBLE. YOUR
-MISSION IS TO DESTROY IT.
+ puts <<~INSTRUCTIONS
+ YOU ARE THE CAPTAIN OF THE DESTROYER USS COMPUTER
+ AN ENEMY SUB HAS BEEN CAUSING YOU TROUBLE. YOUR
+ MISSION IS TO DESTROY IT.
-SPECIFY DEPTH CHARGE EXPLOSION POINT WITH A
-TRIO OF NUMBERS -- THE FIRST TWO ARE THE
-SURFACE COORDINATES (X, Y):
- WEST < X < EAST
- SOUTH < Y < NORTH
+ SPECIFY DEPTH CHARGE EXPLOSION POINT WITH A
+ TRIO OF NUMBERS -- THE FIRST TWO ARE THE
+ SURFACE COORDINATES (X, Y):
+ WEST < X < EAST
+ SOUTH < Y < NORTH
-THE THIRD IS THE DEPTH (Z):
- SHALLOW < Z < DEEP
+ THE THIRD IS THE DEPTH (Z):
+ SHALLOW < Z < DEEP
-GOOD LUCK !
+ GOOD LUCK !
INSTRUCTIONS
- )
end
def debug_game
- printf("@enemy_x: %d\n", @enemy_x)
- printf("@enemy_y: %d\n", @enemy_y)
- printf("@enemy_z: %d\n", @enemy_z)
- printf("@num_tries: %d\n", @num_tries)
- printf("@trial: %d\n", @trial)
- printf("\n")
+ puts "@enemy_x: %d" % @enemy_x
+ puts "@enemy_y: %d" % @enemy_y
+ puts "@enemy_z: %d" % @enemy_z
+ puts "@num_tries: %d" % @num_tries
+ puts "@trial: %d" % @trial
+ puts
end
def setup_game
@search_area_dimension = get_input_positive_integer("DIMENSION OF SEARCH AREA: ")
- @num_tries = Integer(
- Math.log(@search_area_dimension)/Math.log(2) + 1
- )
- setup_enemy()
+ @num_tries = Integer(Math.log(@search_area_dimension) / Math.log(2) + 1)
+ setup_enemy
end
def setup_enemy
@@ -113,32 +111,34 @@ GOOD LUCK !
@shot_y = get_input_positive_integer("Y: ")
@shot_z = get_input_positive_integer("Z: ")
- if (
- (@enemy_x - @shot_x).abs \
- + (@enemy_y - @shot_y).abs \
- + (@enemy_z - @shot_z).abs \
- == 0
- )
- you_win()
+
+ distance = (@enemy_x - @shot_x).abs +
+ (@enemy_y - @shot_y).abs +
+ (@enemy_z - @shot_z).abs
+
+ if distance == 0
+ you_win
return
else
- missed_shot()
+ missed_shot
end
end
- printf("\n")
-
- you_lose()
+ puts
+ you_lose
end
def output_game_status
- printf("YOU HAVE %d SHOTS REMAINING.\n", @num_tries - @trial + 1)
- printf("TRIAL \#%d\n", @trial)
+ puts "YOU HAVE %d SHOTS REMAINING." % @num_tries - @trial + 1
+ puts "TRIAL \#%d" % @trial
end
+
def you_win
- printf("\nB O O M ! ! YOU FOUND IT IN %d TRIES!\n\n", @trial )
+ puts "\nB O O M ! ! YOU FOUND IT IN %d TRIES!" % @trial
+ puts
end
+
def missed_shot
missed_directions = []
@@ -160,14 +160,13 @@ GOOD LUCK !
missed_directions.push('TOO SHALLOW')
end
- printf("SONAR REPORTS SHOT WAS: \n")
- printf("%s\n", "\t" + missed_directions.join("\n\t"))
+ puts "SONAR REPORTS SHOT WAS: "
+ puts "\t#{missed_directions.join("\n\t")}"
end
def you_lose
- printf("YOU HAVE BEEN TORPEDOED! ABANDON SHIP!\n")
- printf("THE SUBMARINE WAS AT %d %d %d\n", @enemy_x, @enemy_y, @enemy_z)
-
+ puts "YOU HAVE BEEN TORPEDOED! ABANDON SHIP!"
+ puts "THE SUBMARINE WAS AT %d %d %d" % [@enemy_x, @enemy_y, @enemy_z]
end
def get_input_another_game
@@ -176,4 +175,4 @@ GOOD LUCK !
end
game = DepthCharge.new
-game.run_game()
+game.run_game
diff --git a/40_Gomoko/perl/gomoko.pl b/40_Gomoko/perl/gomoko.pl
new file mode 100644
index 00000000..9bd2f3db
--- /dev/null
+++ b/40_Gomoko/perl/gomoko.pl
@@ -0,0 +1,121 @@
+#!/usr/bin/perl
+use strict;
+
+
+print ' 'x 33 . "GOMOKO\n";
+print ' 'x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
+print "\n"; print "\n"; print "\n";
+#my @A;
+print "WELCOME TO THE ORIENTAL GAME OF GOMOKO.\n";
+print "\n"; print "THE GAME IS PLAYED ON AN N BY N GRID OF A SIZE\n";
+print "THAT YOU SPECIFY. DURING YOUR PLAY, YOU MAY COVER ONE GRID\n";
+print "INTERSECTION WITH A MARKER. THE OBJECT OF THE GAME IS TO GET\n";
+print "5 ADJACENT MARKERS IN A ROW -- HORIZONTALLY, VERTICALLY, OR\n";
+print "DIAGONALLY. ON THE BOARD DIAGRAM, YOUR MOVES ARE MARKED\n";
+print "WITH A '1' AND THE COMPUTER MOVES WITH A '2'.\n";
+print "\n"; print "THE COMPUTER DOES NOT KEEP TRACK OF WHO HAS WON.\n";
+print "TO END THE GAME, TYPE -1,-1 FOR YOUR MOVE.\n"; print "\n";
+
+
+my $Ret;
+my $I;
+my $J;
+
+my @Board;
+my $Size= 0;
+
+
+while (1) {
+
+ do {
+ $Size= 0;
+ print "WHAT IS YOUR BOARD SIZE (MIN 7/ MAX 19)"; print "? "; chomp($Size = uc());
+ if ($Size<7 || $Size>19) {
+ $Size=0;
+ print "I SAID, THE MINIMUM IS 7, THE MAXIMUM IS 19.\n";
+ }
+ } until ($Size);
+
+ #==> Reset Board to zeroes...
+ for (my $I=1; $I<=$Size; $I++) {
+ for (my $J=1; $J<=$Size; $J++) {
+ $Board[$I][$J]= 0;
+ }
+ }
+
+ print "\n"; print "WE ALTERNATE MOVES. YOU GO FIRST...\n"; print "\n";
+
+ while (1) {
+ do {
+ print "YOUR PLAY (I,J)"; print "? "; chomp(my $Inp = uc());
+ ($I, $J)= split(",", $Inp);
+ print "\n";
+ if ($I==-1) { last; }
+ $Ret= &ValidMove($I, $J, 1);
+ } until ($Ret==1);
+ if ($I==-1) { last; }
+ $Board[$I][$J]= 1;
+
+ my $X;
+ my $Y;
+ my $Found=0;
+ # REM *** COMPUTER TRIES AN INTELLIGENT MOVE ***
+ #==> Too complex, original basic code seems only move below user.
+ $Ret= &ValidMove($I+1, $J);
+ if ($Ret==1) {
+ $Found=1;
+ $X= $I+1;
+ $Y= $J;
+ }
+
+ while($Found==0) {
+ # REM *** COMPUTER TRIES A RANDOM MOVE ***
+ $X= int($Size*rand(1)+1);
+ $Y= int($Size*rand(1)+1);
+ $Ret= &ValidMove($X, $Y, 2);
+ if ($Ret==1) { $Found= 1; }
+ };
+ $Board[$X][$Y]=2;
+
+ &ShowBoard();
+ }
+
+ print "\n"; print "THANKS FOR THE GAME!!\n";
+ print "PLAY AGAIN (1 FOR YES, 0 FOR NO)"; print "? "; chomp(my $Q = uc());
+ if ($Q==0) { last; }
+ }
+
+
+
+exit;
+
+
+sub ShowBoard {
+ for (my $I=1; $I<=$Size; $I++) {
+ print " ";
+ for (my $J=1; $J<=$Size; $J++) {
+ print "$Board[$I][$J] ";
+ }
+ print "\n";
+ }
+ print "\n";
+ return;
+ }
+
+
+sub ValidMove {
+ my ($X, $Y, $Val)= @_;
+ if ($X<1 || $X>$Size || $Y<1 || $Y>$Size) {
+ if ($Val==1) { print "ILLEGAL MOVE. TRY AGAIN...\n"; }
+ return 0;
+ }
+ if ($Board[$X][$Y]!=0) {
+ if ($Val==1) { print "SQUARE OCCUPIED. TRY AGAIN...\n"; }
+ return 0;
+ }
+
+ #$Board[$X][$Y]= $Val;
+ return 1;
+ }
+
+
diff --git a/43_Hammurabi/python/hamurabi.py b/43_Hammurabi/python/hamurabi.py
index 7c18f892..e7310fc7 100644
--- a/43_Hammurabi/python/hamurabi.py
+++ b/43_Hammurabi/python/hamurabi.py
@@ -137,7 +137,7 @@ while Z < 11: # line 270. main loop. while the year is less than 11
# REM *** ENOUGH GRAIN FOR SEED?
bad_input_710(S)
D = -99
- elif D >= 10 * P:
+ elif D > 10 * P:
# REM *** ENOUGH PEOPLE TO TEND THE CROPS?
print("BUT YOU HAVE ONLY", P, "PEOPLE TO TEND THE FIELDS! NOW THEN,")
D = -99
@@ -154,7 +154,8 @@ while Z < 11: # line 270. main loop. while the year is less than 11
if int(C / 2) == C / 2: # even number. 50/50 chance
# REM *** RATS ARE RUNNING WILD!!
E = int(S / C) # calc losses due to rats, based on previous random number
- S = S - E + H # deduct losses from stores
+
+ S = S - E + H # deduct losses from stores
C = gen_random()
# REM *** LET'S HAVE SOME BABIES
diff --git a/48_High_IQ/python/High_IQ.py b/48_High_IQ/python/High_IQ.py
new file mode 100644
index 00000000..4e14ddc1
--- /dev/null
+++ b/48_High_IQ/python/High_IQ.py
@@ -0,0 +1,127 @@
+
+def new_board():
+ # Using a dictionary in python to store the board, since we are not including all numbers within a given range.
+ board = {}
+ for i in [13, 14, 15, 22, 23, 24, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 42, 43, 44, 47, 48, 49, 50, 51, 52, 53, 58, 59, 60, 67, 68, 69]:
+ board[i] = '!'
+ board[41] = 'O'
+ return board
+
+
+def print_instructions():
+ print("""
+HERE IS THE BOARD:
+
+ ! ! !
+ 13 14 15
+
+ ! ! !
+ 22 23 24
+
+! ! ! ! ! ! !
+29 30 31 32 33 34 35
+
+! ! ! ! ! ! !
+38 39 40 41 42 43 44
+
+! ! ! ! ! ! !
+47 48 49 50 51 52 53
+
+ ! ! !
+ 58 59 60
+
+ ! ! !
+ 67 68 69
+
+TO SAVE TYPING TIME, A COMPRESSED VERSION OF THE GAME BOARD
+WILL BE USED DURING PLAY. REFER TO THE ABOVE ONE FOR PEG
+NUMBERS. OK, LET'S BEGIN.
+ """)
+
+def print_board(board):
+ """Prints the boards using indexes in the passed parameter"""
+ print(" " * 2 + board[13] + board[14] + board[15])
+ print(" " * 2 + board[22] + board[23] + board[24])
+ print(board[29] + board[30] + board[31] + board[32] + board[33] + board[34] + board[35])
+ print(board[38] + board[39] + board[40] + board[41] + board[42] + board[43] + board[44])
+ print(board[47] + board[48] + board[49] + board[50] + board[51] + board[52] + board[53])
+ print(" " * 2 + board[58] + board[59] + board[60])
+ print(" " * 2 + board[67] + board[68] + board[69])
+
+def play_game():
+ # Create new board
+ board = new_board()
+
+ # Main game loop
+ while not is_game_finished(board):
+ print_board(board)
+ while not move(board):
+ print("ILLEGAL MOVE! TRY AGAIN")
+
+ # Check peg count and print the user's score
+ peg_count = 0
+ for key in board.keys():
+ if board[key] == '!':
+ peg_count += 1
+
+ print("YOU HAD " + str(peg_count) + " PEGS REMAINING")
+
+ if peg_count == 1:
+ print("BRAVO! YOU MADE A PERFECT SCORE!")
+ print("SAVE THIS PAPER AS A RECORD OF YOUR ACCOMPLISHMENT!")
+
+def move(board):
+ """Queries the user to move. Returns false if the user puts in an invalid input or move, returns true if the move was successful"""
+ start_input = input("MOVE WHICH PIECE? ")
+
+ if not start_input.isdigit():
+ return False
+
+ start = int(start_input)
+
+ if start not in board or board[start] != '!':
+ return False
+
+ end_input = input("TO WHERE? ")
+
+ if not end_input.isdigit():
+ return False
+
+ end = int(end_input)
+
+ if end not in board or board[end] != 'O':
+ return False
+
+ difference = abs(start - end)
+ center = (end + start) / 2
+ if (difference == 2 or difference == 18) and board[end] == 'O' and board[center] == '!':
+ board[start] = 'O'
+ board[center] = 'O'
+ board[end] = '!'
+ return True
+ else:
+ return False
+
+
+def main():
+ print(" " * 33 + "H-I-Q")
+ print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
+ print_instructions()
+ play_game()
+
+def is_game_finished(board):
+ # Checks all locations and whether or not a move is possible at that location.
+ for pos in board.keys():
+ if board[pos] == '!':
+ for space in [1,9]:
+ # Checks if the next location has a peg
+ nextToPeg = ((pos + space) in board) and board[pos + space] == '!'
+ # Checks both going forward (+ location) or backwards (-location)
+ hasMovableSpace = (not ((pos - space) in board and board[pos - space] == '!')) or (not ((pos + space * 2) in board and board[pos + space * 2] == '!'))
+ if nextToPeg and hasMovableSpace:
+ return False
+ return True
+
+
+if __name__ == "__main__":
+ main()
diff --git a/48_High_IQ/python/README.md b/48_High_IQ/python/README.md
index 781945ec..a0738c90 100644
--- a/48_High_IQ/python/README.md
+++ b/48_High_IQ/python/README.md
@@ -1,3 +1,5 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Python](https://www.python.org/about/)
+
+[Implementation](./High_IQ.py) by [Thomas Kwashnak](https://github.com/LittleTealeaf)
\ No newline at end of file
diff --git a/55_Life/csharp/.gitignore b/55_Life/csharp/.gitignore
new file mode 100644
index 00000000..e96e7522
--- /dev/null
+++ b/55_Life/csharp/.gitignore
@@ -0,0 +1,509 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore,rider,visualstudio,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore,rider,visualstudio,visualstudiocode
+
+### DotnetCore ###
+# .NET Core build folders
+bin/
+obj/
+
+# Common node modules locations
+/node_modules
+/wwwroot/node_modules
+
+### Rider ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+# Support for Project snippet scope
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+*.code-workspace
+
+# Local History for Visual Studio Code
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+### VisualStudio Patch ###
+# Additional files built by Visual Studio
+
+# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,rider,visualstudio,visualstudiocode
diff --git a/55_Life/csharp/Life.csproj b/55_Life/csharp/Life.csproj
new file mode 100644
index 00000000..7311ef16
--- /dev/null
+++ b/55_Life/csharp/Life.csproj
@@ -0,0 +1,9 @@
+
+
+
+ Exe
+ net6.0
+ enable
+
+
+
diff --git a/55_Life/csharp/Life.sln b/55_Life/csharp/Life.sln
new file mode 100644
index 00000000..1f6131b8
--- /dev/null
+++ b/55_Life/csharp/Life.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Life", "Life.csproj", "{28B02688-78D1-4B3E-B998-BCC78C292D03}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {28B02688-78D1-4B3E-B998-BCC78C292D03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28B02688-78D1-4B3E-B998-BCC78C292D03}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28B02688-78D1-4B3E-B998-BCC78C292D03}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28B02688-78D1-4B3E-B998-BCC78C292D03}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/55_Life/csharp/Program.cs b/55_Life/csharp/Program.cs
new file mode 100644
index 00000000..eeb00465
--- /dev/null
+++ b/55_Life/csharp/Program.cs
@@ -0,0 +1,324 @@
+using System.Text;
+
+const int maxWidth = 70;
+const int maxHeight = 24;
+
+Console.WriteLine("ENTER YOUR PATTERN:");
+var pattern = new Pattern(ReadPattern(limitHeight: maxHeight).ToArray());
+
+var minX = 10 - pattern.Height / 2;
+var minY = 34 - pattern.Width / 2;
+var maxX = maxHeight - 1;
+var maxY = maxWidth - 1;
+
+var matrix = new Matrix(height: maxHeight, width: maxWidth);
+var simulation = InitializeSimulation(pattern, matrix);
+
+PrintHeader();
+ProcessSimulation();
+
+IEnumerable ReadPattern(int limitHeight)
+{
+ for (var i = 0; i < limitHeight; i++)
+ {
+ var input = Console.ReadLine();
+ if (input.ToUpper() == "DONE")
+ {
+ break;
+ }
+
+ // In the original version, BASIC would trim the spaces in the beginning of an input, so the original
+ // game allowed you to input an '.' before the spaces to circumvent this limitation. This behavior was
+ // kept for compatibility.
+ if (input.StartsWith('.'))
+ yield return input.Substring(1, input.Length - 1);
+
+ yield return input;
+ }
+}
+
+void PrintHeader()
+{
+ void PrintCentered(string text)
+ {
+ const int pageWidth = 64;
+
+ var spaceCount = (pageWidth - text.Length) / 2;
+ Console.Write(new string(' ', spaceCount));
+ Console.WriteLine(text);
+ }
+
+ PrintCentered("LIFE");
+ PrintCentered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ Console.WriteLine();
+ Console.WriteLine();
+ Console.WriteLine();
+}
+
+Simulation InitializeSimulation(Pattern pattern, Matrix matrixToInitialize) {
+ var newSimulation = new Simulation();
+
+ // transcribes the pattern to the middle of the simulation and counts initial population
+ for (var x = 0; x < pattern.Height; x++)
+ {
+ for (var y = 0; y < pattern.Width; y++)
+ {
+ if (pattern.Content[x][y] == ' ')
+ continue;
+
+ matrixToInitialize[minX + x, minY + y] = CellState.Stable;
+ newSimulation.IncreasePopulation();
+ }
+ }
+
+ return newSimulation;
+}
+
+TimeSpan GetPauseBetweenIterations()
+{
+ if (args.Length != 2) return TimeSpan.Zero;
+
+ var parameter = args[0].ToLower();
+ if (parameter.Contains("wait"))
+ {
+ var value = args[1];
+ if (int.TryParse(value, out var sleepMilliseconds))
+ return TimeSpan.FromMilliseconds(sleepMilliseconds);
+ }
+
+ return TimeSpan.Zero;
+}
+
+void ProcessSimulation()
+{
+ var pauseBetweenIterations = GetPauseBetweenIterations();
+ var isInvalid = false;
+
+ while (true)
+ {
+ var invalidText = isInvalid ? "INVALID!" : "";
+ Console.WriteLine($"GENERATION: {simulation.Generation}\tPOPULATION: {simulation.Population} {invalidText}");
+
+ simulation.StartNewGeneration();
+
+ var nextMinX = maxHeight - 1;
+ var nextMinY = maxWidth - 1;
+ var nextMaxX = 0;
+ var nextMaxY = 0;
+
+ var matrixOutput = new StringBuilder();
+
+ // prints the empty lines before search area
+ for (var x = 0; x < minX; x++)
+ {
+ matrixOutput.AppendLine();
+ }
+
+ // refreshes the matrix and updates search area
+ for (var x = minX; x <= maxX; x++)
+ {
+ var printedLine = Enumerable.Repeat(' ', maxWidth).ToList();
+ for (var y = minY; y <= maxY; y++)
+ {
+ if (matrix[x, y] == CellState.Dying)
+ {
+ matrix[x, y] = CellState.Empty;
+ continue;
+ }
+ if (matrix[x, y] == CellState.New)
+ {
+ matrix[x, y] = CellState.Stable;
+ }
+ else if (matrix[x, y] != CellState.Stable)
+ {
+ continue;
+ }
+
+ printedLine[y] = '*';
+
+ nextMinX = Math.Min(x, nextMinX);
+ nextMaxX = Math.Max(x, nextMaxX);
+ nextMinY = Math.Min(y, nextMinY);
+ nextMaxY = Math.Max(y, nextMaxY);
+ }
+
+ matrixOutput.AppendLine(string.Join(separator: null, values: printedLine));
+ }
+
+ // prints empty lines after search area
+ for (var x = maxX + 1; x < maxHeight; x++)
+ {
+ matrixOutput.AppendLine();
+ }
+ Console.Write(matrixOutput);
+
+ void UpdateSearchArea()
+ {
+ minX = nextMinX;
+ maxX = nextMaxX;
+ minY = nextMinY;
+ maxY = nextMaxY;
+
+ const int limitX = 21;
+ const int limitY = 67;
+
+ if (minX < 2)
+ {
+ minX = 2;
+ isInvalid = true;
+ }
+
+ if (maxX > limitX)
+ {
+ maxX = limitX;
+ isInvalid = true;
+ }
+
+ if (minY < 2)
+ {
+ minY = 2;
+ isInvalid = true;
+ }
+
+ if (maxY > limitY)
+ {
+ maxY = limitY;
+ isInvalid = true;
+ }
+ }
+ UpdateSearchArea();
+
+ for (var x = minX - 1; x <= maxX + 1; x++)
+ {
+ for (var y = minY - 1; y <= maxY + 1; y++)
+ {
+ int CountNeighbors()
+ {
+ var neighbors = 0;
+ for (var i = x - 1; i <= x + 1; i++)
+ {
+ for (var j = y - 1; j <= y + 1; j++)
+ {
+ if (matrix[i, j] == CellState.Stable || matrix[i, j] == CellState.Dying)
+ neighbors++;
+ }
+ }
+
+ return neighbors;
+ }
+
+ var neighbors = CountNeighbors();
+ if (matrix[x, y] == CellState.Empty)
+ {
+ if (neighbors == 3)
+ {
+ matrix[x, y] = CellState.New;
+ simulation.IncreasePopulation();
+ }
+ }
+ else if (neighbors is < 3 or > 4)
+ {
+ matrix[x, y] = CellState.Dying;
+ }
+ else
+ {
+ simulation.IncreasePopulation();
+ }
+ }
+ }
+
+ // expands search area to accommodate new cells
+ minX--;
+ minY--;
+ maxX++;
+ maxY++;
+
+ if (pauseBetweenIterations > TimeSpan.Zero)
+ Thread.Sleep(pauseBetweenIterations);
+ }
+}
+
+public class Pattern
+{
+ public string[] Content { get; }
+ public int Height { get; }
+ public int Width { get; }
+
+ public Pattern(IReadOnlyCollection patternLines)
+ {
+ Height = patternLines.Count;
+ Width = patternLines.Max(x => x.Length);
+ Content = NormalizeWidth(patternLines);
+ }
+
+ private string[] NormalizeWidth(IReadOnlyCollection patternLines)
+ {
+ return patternLines
+ .Select(x => x.PadRight(Width, ' '))
+ .ToArray();
+ }
+}
+
+///
+/// Indicates the state of a given cell in the simulation.
+///
+internal enum CellState
+{
+ Empty = 0,
+ Stable = 1,
+ Dying = 2,
+ New = 3
+}
+
+public class Simulation
+{
+ public int Generation { get; private set; }
+
+ public int Population { get; private set; }
+
+ public void StartNewGeneration()
+ {
+ Generation++;
+ Population = 0;
+ }
+
+ public void IncreasePopulation()
+ {
+ Population++;
+ }
+}
+
+///
+/// This class was created to aid debugging, through the implementation of the ToString() method.
+///
+class Matrix
+{
+ private readonly CellState[,] _matrix;
+
+ public Matrix(int height, int width)
+ {
+ _matrix = new CellState[height, width];
+ }
+
+ public CellState this[int x, int y]
+ {
+ get => _matrix[x, y];
+ set => _matrix[x, y] = value;
+ }
+
+ public override string ToString()
+ {
+ var stringBuilder = new StringBuilder();
+ for (var x = 0; x < _matrix.GetLength(0); x++)
+ {
+ for (var y = 0; y < _matrix.GetLength(1); y++)
+ {
+ var character = _matrix[x, y] == 0 ? " ": ((int)_matrix[x, y]).ToString();
+ stringBuilder.Append(character);
+ }
+
+ stringBuilder.AppendLine();
+ }
+ return stringBuilder.ToString();
+ }
+}
\ No newline at end of file
diff --git a/55_Life/csharp/README.md b/55_Life/csharp/README.md
index 4daabb5c..5b4ffe62 100644
--- a/55_Life/csharp/README.md
+++ b/55_Life/csharp/README.md
@@ -1,3 +1,71 @@
+# Life
+
+An implementation of John Conway's popular cellular automaton, also know as **Conway's Game of Life**. The original source was downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html).
+
+Ported by Dyego Alekssander Maas.
+
+## How to run
+
+This program requires you to install [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0). After installed, you just need to run `dotnet run` from this directory in the terminal.
+
+## Know more about Conway's Game of Life
+
+You can find more about Conway's Game of Life on this page of the [Cornell Math Explorers' Club](http://pi.math.cornell.edu/~lipa/mec/lesson6.html), alongside many examples of patterns you can try.
+
+### Optional parameters
+
+Optionally, you can run this program with the `--wait 1000` argument, the number being the time in milliseconds
+that the application will pause between each iteration. This is enables you to watch the simulation unfolding. By default, there is no pause between iterations.
+
+The complete command would be `dotnet run --wait 1000`.
+
+## Entering patterns
+
+Once running the game, you are expected to enter a pattern. This pattern consists of multiple lines of text with either **spaces** or **some character**, usually an asterisk (`*`).
+
+Spaces represent empty cells. Asterisks represent alive cells.
+
+After entering the pattern, you need to enter the word "DONE". It is not case sensitive. An example of pattern would be:
+
+```
+ *
+***
+DONE
+```
+
+### Some patterns you could try
+
+```
+ *
+***
+```
+
+```
+*
+***
+```
+
+```
+**
+**
+```
+
+```
+ *
+ *
+*
+```
+
+This one is known as **glider**:
+
+```
+***
+*
+ *
+```
+
+## Instructions to the port
+
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
diff --git a/62_Mugwump/perl/mugwump.pl b/62_Mugwump/perl/mugwump.pl
index d02a9036..33835b77 100755
--- a/62_Mugwump/perl/mugwump.pl
+++ b/62_Mugwump/perl/mugwump.pl
@@ -1,96 +1,96 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-# global variables defined here
-my(@MUGWUMP) = ();
-
-# subroutines defined here
-
-# init_mugwump: pick the random places for the Mugwumps
-sub init_mugwump() {
- @MUGWUMP = ();
- for (1 .. 4) {
- push @MUGWUMP, [ int(rand 10), int(rand 10) ];
- }
-}
-
-
-# main code starts here
-
-# print introductory text
-print <);
- my($M,$N) = split(/,/,$in);
- $M = int($M);
- $N = int($N);
-
- for my $i (0 .. $#MUGWUMP) {
- # -1 indicates a Mugwump that was already found
- next if $MUGWUMP[$i]->[0] == -1;
-
- if ($MUGWUMP[$i]->[0] == $M && $MUGWUMP[$i]->[1] == $N) {
- $MUGWUMP[$i]->[0] = -1;
- printf("You have found Mugwump %d\n", $i+1);
- } else {
- my $d = sqrt(($MUGWUMP[$i]->[0] - $M) ** 2 + ($MUGWUMP[$i]->[1] - $N) ** 2);
- printf("You are %.1f units away from Mugwump %d\n", $d, $i+1);
- }
- }
-
- # If a Mugwump still has not been found,
- # go to the next turn
- for my $j (0 .. $#MUGWUMP) {
- if ($MUGWUMP[$j]->[0] != -1) {
- next TURN;
- }
- }
- # You win!
- printf("You got all of them in %d %s!\n\n", $turn, ($turn == 1 ? 'turn' : 'turns'));
- # Pass execution down to the continue block
- next PLAY;
-
- } # end of TURN loop
-
- print "\nSorry, that's 10 tries. Here's where they're hiding:\n";
- for my $i (0 .. $#MUGWUMP) {
- printf("Mugwump %d is at (%d, %d)\n", $i+1, $MUGWUMP[$i]->[0], $MUGWUMP[$i]->[1])
- if $MUGWUMP[$i]->[0] != -1;
- }
-}
-continue {
- print "\nThat was fun! Let's play again.......\n";
- print "Four more Mugwumps are now in hiding.\n\n";
-}
-
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+# global variables defined here
+my(@MUGWUMP) = ();
+
+# subroutines defined here
+
+# init_mugwump: pick the random places for the Mugwumps
+sub init_mugwump() {
+ @MUGWUMP = ();
+ for (1 .. 4) {
+ push @MUGWUMP, [ int(rand 10), int(rand 10) ];
+ }
+}
+
+
+# main code starts here
+
+# print introductory text
+print <);
+ my($M,$N) = split(/,/,$in);
+ $M = int($M);
+ $N = int($N);
+
+ for my $i (0 .. $#MUGWUMP) {
+ # -1 indicates a Mugwump that was already found
+ next if $MUGWUMP[$i]->[0] == -1;
+
+ if ($MUGWUMP[$i]->[0] == $M && $MUGWUMP[$i]->[1] == $N) {
+ $MUGWUMP[$i]->[0] = -1;
+ printf("You have found Mugwump %d\n", $i+1);
+ } else {
+ my $d = sqrt(($MUGWUMP[$i]->[0] - $M) ** 2 + ($MUGWUMP[$i]->[1] - $N) ** 2);
+ printf("You are %.1f units away from Mugwump %d\n", $d, $i+1);
+ }
+ }
+
+ # If a Mugwump still has not been found,
+ # go to the next turn
+ for my $j (0 .. $#MUGWUMP) {
+ if ($MUGWUMP[$j]->[0] != -1) {
+ next TURN;
+ }
+ }
+ # You win!
+ printf("You got all of them in %d %s!\n\n", $turn, ($turn == 1 ? 'turn' : 'turns'));
+ # Pass execution down to the continue block
+ next PLAY;
+
+ } # end of TURN loop
+
+ print "\nSorry, that's 10 tries. Here's where they're hiding:\n";
+ for my $i (0 .. $#MUGWUMP) {
+ printf("Mugwump %d is at (%d, %d)\n", $i+1, $MUGWUMP[$i]->[0], $MUGWUMP[$i]->[1])
+ if $MUGWUMP[$i]->[0] != -1;
+ }
+}
+continue {
+ print "\nThat was fun! Let's play again.......\n";
+ print "Four more Mugwumps are now in hiding.\n\n";
+}
+
diff --git a/65_Nim/ruby/nim.rb b/65_Nim/ruby/nim.rb
new file mode 100644
index 00000000..a326695f
--- /dev/null
+++ b/65_Nim/ruby/nim.rb
@@ -0,0 +1,282 @@
+puts "NIM".center(80)
+puts"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY".center(80)
+puts "\n\n\n"
+
+#210 DIM A(100),B(100,10),D(2)
+$pileArray = Array.new[100]
+$bArray = Array.new
+$dArray = Array.new[2]
+$winOption = 0 # take-last option
+$numberOfPiles = 1
+$c = 0
+$e = 0
+$f = 0
+$g = 0
+$h = 0
+
+def displayTheRules
+puts "THE GAME IS PLAYED WITH A NUMBER OF PILES OF OBJECTS."
+puts "ANY NUMBER OF OBJECTS ARE REMOVED FROM ONE PILE BY YOU AND"
+puts "THE MACHINE ALTERNATELY. ON YOUR TURN, YOU MAY TAKE"
+puts "ALL THE OBJECTS THAT REMAIN IN ANY PILE, BUT YOU MUST"
+puts "TAKE AT LEAST ONE OBJECT, AND YOU MAY TAKE OBJECTS FROM"
+puts "ONLY ONE PILE ON A SINGLE TURN. YOU MUST SPECIFY WHETHER"
+puts "WINNING IS DEFINED AS TAKING OR NOT TAKING THE LAST OBJECT,"
+puts "THE NUMBER OF PILES IN THE GAME, AND HOW MANY OBJECTS ARE"
+puts "ORIGINALLY IN EACH PILE. EACH PILE MAY CONTAIN A"
+puts "DIFFERENT NUMBER OF OBJECTS."
+puts "THE MACHINE WILL SHOW ITS MOVE BY LISTING EACH PILE AND THE"
+puts "NUMBER OF OBJECTS REMAINING IN THE PILES AFTER EACH OF ITS"
+puts "MOVES."
+end
+
+def sub1570
+ $z=0
+ for i in 1..$numberOfPiles do
+ if $pileArray[i] != 0 then
+ return
+ end
+ $z=1
+ return
+ end
+end
+
+def playAnother
+ put "do you want to play another game";
+ return gets.strip.ucase == "YES"
+end
+puts "THIS IS THE GAME OF NIM."
+print "DO YOU WANT INSTRUCTIONS?"
+240
+wantInstructions = gets.strip.upcase
+if wantInstructions == "YES" then
+ displayTheRules
+end
+#250 IF Z$="NO" THEN 440
+#260 IF Z$="no" THEN 440
+#270 IF Z$="YES" THEN displayTheRules
+#280 IF Z$="yes" THEN displayTheRules
+#290 PRINT "PLEASE ANSWER YES OR NO"
+#300 GOTO 240
+
+def sub490 # get number of piles
+ print "ENTER NUMBER OF PILES:"
+ while $numberOfPiles < 0 && $numberOfPiles <= 100 do
+ $numberOfPiles = gets.strip.to_i
+ end
+end
+
+def getPileSizes
+ puts "ENTER PILE SIZES:"
+ for i in 1..$numberOfPiles do
+ print i
+ while true do
+ $pileArray[i] = gets.strip.to_i
+ if $pileArray[i] < 2000 && $pileArray[i] > 0 then
+ break
+ end
+ end
+ end
+end
+
+def sub440 # get win option
+ puts ""
+ $winOption = 0
+ while $winOption != 1 && q != 2 do
+ puts "ENTER WIN OPTION - 1 TO TAKE LAST, 2 TO AVOID LAST"
+ $winOption = gets.strip.to_i
+ end
+end
+
+puts "DO YOU WANT TO MOVE FIRST?";
+#630 INPUT Q9$
+moveFirst = ""
+while moveFirst != "YES" && moveFirst != "NO" do
+ moveFirst = gets.strip.upcase
+ case moveFirst
+ when "YES"
+ yourMove
+ when "NO"
+ machineMove
+ end
+end
+
+#640 IF Q9$="YES" THEN 1450
+#650 IF Q9$="yes" THEN 1450
+#660 IF Q9$="NO" THEN 700
+#670 IF Q9$="no" THEN 700
+#680 PRINT "PLEASE ANSWER YES OR NO."
+#690 GOTO 630
+
+def machineMove
+ if $winOption == 1 then
+ sub940 # take last
+ end
+ $c=0
+ for i in 1..$numberOfPiles do
+ if $pileArray[i] != 0 then 770
+ $c=$c+1
+ if $c == 3 then
+ sub840
+ end
+ $dArray[$c-1]=i
+ end
+ end
+
+ if $c == 2 then
+ sub920
+ end
+ if $pileArray[$dArray[0]] > 1 then
+ machineWins
+ end
+ machineLoses
+end
+
+def machineLoses
+ puts "MACHINE LOSES"
+# 810 GOTO playAnother
+ if playAnother then
+ sub440 # loop for another
+ end
+end
+
+def machineWins
+ puts "MACHINE WINS"
+# 830 GOTO playAnother
+ if playAnother then
+ sub440 # loop for another
+ end
+end
+
+def sub840
+ $c=0
+ for i in 1..$numberOfPiles do
+ if $pileArray[i] > 1 then
+ sub940
+ end
+ if $pileArray[i] == 0 then 890
+ $c=$c+1
+ end
+ if $c/2 != ($c/2).to_i then
+ machineLoses
+ end
+ sub940 # goto
+ end
+end
+
+def sub920
+ if $pileArray[$dArray[0]] == 1 then
+ machineWins
+ end
+ if $pileArray[$dArray[1]] == 1 then
+ machineWins
+ end
+end
+
+def sub940
+ for i in 1..$numberOfPiles do
+ e=$pileArray[i]
+ for j in 0..10 do
+ $f = $e/2
+ $bArray[i][j] = 2*($f-($f.to_i))
+ $e = $f.to_i
+ end
+ end
+end
+
+#for j in 10..0 STEP -1 do
+10..0.step(-1).each do|index|
+ $c=0
+ $h=0
+ for i in 1..$numberOfPiles do
+ if $bArray[i][index] != 0 then
+ $c=$c+1
+ if $pileArray[i] > $h then
+ $h = $pileArray[i]
+ $g = i
+ end
+ end
+ end
+end
+
+if $c/2 != ($c/2).to_i then 1190
+end
+$e = rand($numberOfPiles).to_i
+#if $pileArray[$e] == 0 then 1140
+
+$f = rand($pileArray[$e]).to_i
+$pileArray[$e] = $pileArray[$e]-$f
+sub1380
+$pileArray[$g]=0
+for j in 0..10 do
+$bArray[$g][index]=0
+$c=0
+for i in 1..$numberOfPiles do
+ if $bArray[i][index] != 0 then
+ $c=$c+1
+ end
+ end
+$pileArray[$g]=$pileArray[$g]+2*($c/2-($c/2)).to_i*2^j
+end
+if $winOption == 1 then
+ sub1380
+end
+$c=0
+for i in 1..$numberOfPiles do
+if $pileArray[i]>1 then
+ sub1380
+end
+if $pileArray[i] != 0 then
+ $c=$c+1
+end
+if $c/2 == ($c/2).to_i then
+ sub1380
+end
+$pileArray[$g] == 1 -$pileArray[$g]
+
+def sub1380
+ puts "PILE SIZE"
+ for i in 1..$numberOfPiles do
+ put i
+ put $pileArray[i]
+ end
+ if $winOption == 2 then # avoid take-last option
+ yourMove
+ end
+ sub1570
+ if $z == 1 then
+ machineWins
+ end
+end
+
+def yourMove
+ put "YOUR MOVE - PILE, NUMBER TO BE REMOVED"
+# 1460 INPUT x,y
+x = gets.strip.to_i
+y = gets.strip.to_i
+ if x > $numberOfPiles then yourMove
+ if x < 1 then yourMove
+ if x != INT(x) then yourMove
+ if y > $pileArray[x] then yourMove
+ if y < 1 then
+ yourMove
+ end
+ if y != INT(y) then
+ yourMove
+ end
+
+ $pileArray[x] = $pileArray[x]-y
+ sub1570 # gosub
+ if $z == 1 then
+ machineLoses
+ end
+# 1560 GOTO 700
+end
+
+end
+end
+end
+end
+end
+
+
diff --git a/67_One_Check/python/onecheck.py b/67_One_Check/python/onecheck.py
new file mode 100644
index 00000000..b3d828b1
--- /dev/null
+++ b/67_One_Check/python/onecheck.py
@@ -0,0 +1,115 @@
+# ONE CHECK
+
+# Port to python by imiro
+
+def tab(x):
+ return ' '*x
+
+def main():
+
+ # Initial instructions
+ print(tab(30) + "ONE CHECK");
+ print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ print();
+ print();
+ print();
+ print("SOLITAIRE CHECKER PUZZLE BY DAVID AHL");
+ print();
+ print("48 CHECKERS ARE PLACED ON THE 2 OUTSIDE SPACES OF A");
+ print("STANDARD 64-SQUARE CHECKERBOARD. THE OBJECT IS TO");
+ print("REMOVE AS MANY CHECKERS AS POSSIBLE BY DIAGONAL JUMPS");
+ print("(AS IN STANDARD CHECKERS). USE THE NUMBERED BOARD TO");
+ print("INDICATE THE SQUARE YOU WISH TO JUMP FROM AND TO. ON");
+ print("THE BOARD PRINTED OUT ON EACH TURN '1' INDICATES A");
+ print("CHECKER AND '0' AN EMPTY SQUARE. WHEN YOU HAVE NO");
+ print("POSSIBLE JUMPS REMAINING, INPUT A '0' IN RESPONSE TO");
+ print("QUESTION 'JUMP FROM ?'");
+ print();
+ print("HERE IS THE NUMERICAL BOARD:");
+ print();
+
+ while(True):
+ for j in range(1,64,8):
+ for i in range(j,j+7):
+ print(i, end=(' '*(3 if i < 10 else 2)))
+ print(j+7)
+ print()
+ print("AND HERE IS THE OPENING POSITION OF THE CHECKERS.")
+ print()
+
+ (jumps, left) = play_game()
+
+ print()
+ print("YOU MADE " + jumps + " JUMPS AND HAD " + left + " PIECES")
+ print("REMAINING ON THE BOARD.")
+ print()
+
+ if not(try_again()):
+ break
+
+ print()
+ print("O.K. HOPE YOU HAD FUN!!")
+
+def play_game():
+ # Initialize board
+ # Give more than 64 elements to accomodate 1-based indexing
+ board = [1]*70
+ for j in range(19,44,8):
+ for i in range(j,j+4):
+ board[i] = 0
+ jumps = 0
+ while True:
+ # print board
+ for j in range(1,64,8):
+ for i in range(j,j+7):
+ print(board[i], end=' ')
+ print(board[j+7])
+ print()
+
+ while True:
+ print("JUMP FROM", end=' ')
+ f = input()
+ f = int(f)
+ if f == 0:
+ break
+ print("TO", end=' ')
+ t = input()
+ t = int(t)
+ print()
+
+ # Check legality of move
+ f1 = ((f-1) // 8)
+ f2 = f - 8 * f1
+ t1 = ((t-1) // 8)
+ t2 = t - 8 * t1
+ if (f1 > 7 or t1 > 7 or f2 > 8 or t2 > 8 or abs(f1 - t1) != 2 or
+ abs(f2 - t2) != 2 or board[(t + f) // 2] == 0 or
+ board[f] == 0 or board[t] == 1):
+ print("ILLEGAL MOVE. TRY AGAIN...")
+ continue
+ break
+
+ if(f == 0):
+ break
+ board[t] = 1
+ board[f] = 0
+ board[(t+f) // 2] = 0
+ jumps = jumps + 1
+
+ left = 0
+ for i in range(1,64+1):
+ left = left + board[i]
+ return (str(jumps), str(left))
+
+def try_again():
+ print("TRY AGAIN", end=' ')
+ answer = input()
+ if (answer.upper() == "YES"):
+ return True
+ elif (answer.upper() == "NO"):
+ return False
+ print("PLEASE ANSWER 'YES' OR 'NO'.")
+ try_again()
+
+if __name__ == '__main__':
+ main()
diff --git a/73_Reverse/csharp/Reverse/Reverse.Tests/Generators/PositiveIntegerGenerator.cs b/73_Reverse/csharp/Reverse/Reverse.Tests/Generators/PositiveIntegerGenerator.cs
new file mode 100644
index 00000000..66889bb5
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse.Tests/Generators/PositiveIntegerGenerator.cs
@@ -0,0 +1,10 @@
+using FsCheck;
+
+namespace Reverse.Tests.Generators
+{
+ public static class PositiveIntegerGenerator
+ {
+ public static Arbitrary Generate() =>
+ Arb.Default.Int32().Filter(x => x > 0);
+ }
+}
diff --git a/73_Reverse/csharp/Reverse/Reverse.Tests/Reverse.Tests.csproj b/73_Reverse/csharp/Reverse/Reverse.Tests/Reverse.Tests.csproj
new file mode 100644
index 00000000..260de5e1
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse.Tests/Reverse.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net5.0
+
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/73_Reverse/csharp/Reverse/Reverse.Tests/ReverserTests.cs b/73_Reverse/csharp/Reverse/Reverse.Tests/ReverserTests.cs
new file mode 100644
index 00000000..6fe3bb77
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse.Tests/ReverserTests.cs
@@ -0,0 +1,148 @@
+using FsCheck.Xunit;
+using Reverse.Tests.Generators;
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Reverse.Tests
+{
+ public class ReverserTests
+ {
+ [Fact]
+ public void Constructor_CannotAcceptNumberLessThanZero()
+ {
+ Action action = () => new Reverser(0);
+
+ Assert.Throws(action);
+ }
+
+ [Property(Arbitrary = new[] { typeof(PositiveIntegerGenerator) })]
+ public void Constructor_CreatesRandomArrayOfSpecifiedLength(int size)
+ {
+ var sut = new TestReverser(size);
+
+ Assert.Equal(size, sut.GetArray().Length);
+ }
+
+ [Property(Arbitrary = new[] { typeof(PositiveIntegerGenerator) })]
+ public void ConstructorArray_MaxElementValueIsEqualToSize(int size)
+ {
+ var sut = new TestReverser(size);
+
+ Assert.Equal(size, sut.GetArray().Max());
+ }
+
+ [Property(Arbitrary = new[] { typeof(PositiveIntegerGenerator) })]
+ public void ConstructorArray_ReturnsRandomArrayWithDistinctElements(int size)
+ {
+ var sut = new TestReverser(size);
+ var array = sut.GetArray();
+ var arrayGroup = array.GroupBy(x => x);
+ var duplicateFound = arrayGroup.Any(x => x.Count() > 1);
+
+ Assert.False(duplicateFound);
+ }
+
+ [Theory]
+ [InlineData(new int[] { 1 }, new int[] { 1 })]
+ [InlineData(new int[] { 1, 2 }, new int[] { 2, 1 })]
+ [InlineData(new int[] { 1, 2, 3 }, new int[] { 3, 2, 1 })]
+ public void Reverse_WillReverseEntireArray(int[] input, int[] output)
+ {
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ sut.Reverse(input.Length);
+
+ Assert.True(sut.GetArray().SequenceEqual(output));
+ }
+
+ [Fact]
+ public void Reverse_WithSpecifiedIndex_ReversesItemsUpToThatIndex()
+ {
+ var input = new int[] { 1, 2, 3, 4 };
+ var output = new int[] { 2, 1, 3, 4 };
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ sut.Reverse(2);
+
+ Assert.True(sut.GetArray().SequenceEqual(output));
+ }
+
+ [Fact]
+ public void Reverse_WithIndexOne_DoesNothing()
+ {
+ var input = new int[] { 1, 2 };
+ var output = new int[] { 1, 2 };
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ sut.Reverse(1);
+
+ Assert.True(sut.GetArray().SequenceEqual(output));
+ }
+
+ [Fact]
+ public void Reverse_WithIndexGreaterThanArrayLength_DoesNothing()
+ {
+ var input = new int[] { 1, 2 };
+ var output = new int[] { 1, 2 };
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ sut.Reverse(sut.GetArray().Length + 1);
+
+ Assert.True(sut.GetArray().SequenceEqual(output));
+ }
+
+ [Fact]
+ public void Reverse_WithIndexLessThanZero_DoesNothing()
+ {
+ var input = new int[] { 1, 2 };
+ var output = new int[] { 1, 2 };
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ sut.Reverse(-1);
+
+ Assert.True(sut.GetArray().SequenceEqual(output));
+ }
+
+ [Theory]
+ [InlineData(new int[] { 1 })]
+ [InlineData(new int[] { 1, 2 })]
+ [InlineData(new int[] { 1, 1 })]
+ public void IsArrayInAscendingOrder_WhenArrayElementsAreInNumericAscendingOrder_ReturnsTrue(int[] input)
+ {
+ var sut = new TestReverser(1);
+ sut.SetArray(input);
+
+ var result = sut.IsArrayInAscendingOrder();
+
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsArrayInOrder_WhenArrayElementsAreNotInNumericAscendingOrder_ReturnsFalse()
+ {
+ var sut = new TestReverser(1);
+ sut.SetArray(new int[] { 2, 1 });
+
+ var result = sut.IsArrayInAscendingOrder();
+
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void GetArrayString_ReturnsSpaceSeparatedElementsOfArrayInStringFormat()
+ {
+ var sut = new TestReverser(1);
+ sut.SetArray(new int[] { 1, 2 });
+
+ var result = sut.GetArrayString();
+
+ Assert.Equal(" 1 2 ", result);
+ }
+ }
+}
diff --git a/73_Reverse/csharp/Reverse/Reverse.Tests/TestReverser.cs b/73_Reverse/csharp/Reverse/Reverse.Tests/TestReverser.cs
new file mode 100644
index 00000000..a53004e1
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse.Tests/TestReverser.cs
@@ -0,0 +1,17 @@
+namespace Reverse.Tests
+{
+ internal class TestReverser : Reverser
+ {
+ public TestReverser(int arraySize) : base(arraySize) { }
+
+ public int[] GetArray()
+ {
+ return _array;
+ }
+
+ public void SetArray(int[] array)
+ {
+ _array = array;
+ }
+ }
+}
diff --git a/73_Reverse/csharp/Reverse/Reverse.sln b/73_Reverse/csharp/Reverse/Reverse.sln
new file mode 100644
index 00000000..96c338be
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32002.261
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reverse", "Reverse\Reverse.csproj", "{39463B63-6A71-4DCF-A4F2-FAA74FDEEC01}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reverse.Tests", "Reverse.Tests\Reverse.Tests.csproj", "{96E824F8-0353-4FF2-9FEA-F850E2BE7312}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {39463B63-6A71-4DCF-A4F2-FAA74FDEEC01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {39463B63-6A71-4DCF-A4F2-FAA74FDEEC01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {39463B63-6A71-4DCF-A4F2-FAA74FDEEC01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {39463B63-6A71-4DCF-A4F2-FAA74FDEEC01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {96E824F8-0353-4FF2-9FEA-F850E2BE7312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {96E824F8-0353-4FF2-9FEA-F850E2BE7312}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {96E824F8-0353-4FF2-9FEA-F850E2BE7312}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {96E824F8-0353-4FF2-9FEA-F850E2BE7312}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1DCA2723-D126-4B37-A698-D40DA03643A9}
+ EndGlobalSection
+EndGlobal
diff --git a/73_Reverse/csharp/Reverse/Reverse/Program.cs b/73_Reverse/csharp/Reverse/Reverse/Program.cs
new file mode 100644
index 00000000..5f8c5967
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse/Program.cs
@@ -0,0 +1,130 @@
+using System;
+
+namespace Reverse
+{
+ class Program
+ {
+ private static int arrayLength = 9;
+ static void Main(string[] args)
+ {
+ PrintTitle();
+ Console.Write("DO YOU WANT THE RULES? ");
+ var needRulesInput = Console.ReadLine();
+ Console.WriteLine();
+ if (string.Equals(needRulesInput, "YES", StringComparison.OrdinalIgnoreCase))
+ {
+ DisplayRules();
+ }
+
+ var tryAgain = string.Empty;
+ while (!string.Equals(tryAgain, "NO", StringComparison.OrdinalIgnoreCase))
+ {
+ var reverser = new Reverser(arrayLength);
+
+ Console.WriteLine("HERE WE GO ... THE LIST IS:");
+ PrintList(reverser.GetArrayString());
+ var arrayIsInAscendingOrder = false;
+ var numberOfMoves = 0;
+ while (arrayIsInAscendingOrder == false)
+ {
+ int index = ReadNextInput();
+
+ if (index == 0)
+ {
+ break;
+ }
+
+ reverser.Reverse(index);
+ PrintList(reverser.GetArrayString());
+ arrayIsInAscendingOrder = reverser.IsArrayInAscendingOrder();
+ numberOfMoves++;
+ }
+
+ if (arrayIsInAscendingOrder)
+ {
+ Console.WriteLine($"YOU WON IT IN {numberOfMoves} MOVES!!!");
+
+ }
+
+ Console.WriteLine();
+ Console.WriteLine();
+ Console.Write("TRY AGAIN (YES OR NO) ");
+ tryAgain = Console.ReadLine();
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("OK HOPE YOU HAD FUN!!");
+ }
+
+ private static int ReadNextInput()
+ {
+ Console.Write("HOW MANY SHALL I REVERSE? ");
+ var input = ReadIntegerInput();
+ while (input > 9 || input < 0)
+ {
+ if (input > 9)
+ {
+ Console.WriteLine($"OOPS! TOO MANY! I CAN REVERSE AT MOST {arrayLength}");
+ }
+
+ if (input < 0)
+ {
+ Console.WriteLine($"OOPS! TOO FEW! I CAN REVERSE BETWEEN 1 AND {arrayLength}");
+ }
+ Console.Write("HOW MANY SHALL I REVERSE? ");
+ input = ReadIntegerInput();
+ }
+
+ return input;
+ }
+
+ private static int ReadIntegerInput()
+ {
+ var input = Console.ReadLine();
+ int.TryParse(input, out var index);
+ return index;
+ }
+
+ private static void PrintList(string list)
+ {
+ Console.WriteLine();
+ Console.WriteLine(list);
+ Console.WriteLine();
+ }
+
+ private static void PrintTitle()
+ {
+ Console.WriteLine("\t\t REVERSE");
+ Console.WriteLine(" CREATIVE COMPUTING MORRISTON, NEW JERSEY");
+ Console.WriteLine();
+ Console.WriteLine();
+ Console.WriteLine("REVERSE -- A GAME OF SKILL");
+ Console.WriteLine();
+ }
+
+ private static void DisplayRules()
+ {
+ Console.WriteLine();
+ Console.WriteLine("THIS IS THE GAME OF 'REVERSE'. TO WIN, ALL YOU HAVE");
+ Console.WriteLine("TO DO IS ARRANGE A LIST OF NUMBERS (1 THOUGH 9 )");
+ Console.WriteLine("IN NUMERICAL ORDER FROM LEFT TO RIGHT. TO MOVE, YOU");
+ Console.WriteLine("TELL ME HOW MANY NUMBERS (COUNTING FROM THE LEFT) TO");
+ Console.WriteLine("REVERSE. FOR EXAMPLE, IF THE CURRENT LIST IS:");
+ Console.WriteLine();
+ Console.WriteLine("2 3 4 5 1 6 7 8 9");
+ Console.WriteLine();
+ Console.WriteLine("AND YOU REVERSE 4, THE RESULT WILL BE:");
+ Console.WriteLine();
+ Console.WriteLine("5 4 3 2 1 6 7 8 9");
+ Console.WriteLine();
+ Console.WriteLine("NOW IF YOU REVERSE 5, YOU WIN!");
+ Console.WriteLine();
+ Console.WriteLine("1 2 3 4 5 6 7 8 9");
+ Console.WriteLine();
+ Console.WriteLine("NO DOUBT YOU WILL LIKE THIS GAME, BUT ");
+ Console.WriteLine("IF YOU WANT TO QUIT, REVERSE 0 (ZERO)");
+ Console.WriteLine();
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/73_Reverse/csharp/Reverse/Reverse/Reverse.csproj b/73_Reverse/csharp/Reverse/Reverse/Reverse.csproj
new file mode 100644
index 00000000..20827042
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse/Reverse.csproj
@@ -0,0 +1,8 @@
+
+
+
+ Exe
+ net5.0
+
+
+
diff --git a/73_Reverse/csharp/Reverse/Reverse/Reverser.cs b/73_Reverse/csharp/Reverse/Reverse/Reverser.cs
new file mode 100644
index 00000000..fdab5e96
--- /dev/null
+++ b/73_Reverse/csharp/Reverse/Reverse/Reverser.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Text;
+
+namespace Reverse
+{
+ public class Reverser
+ {
+ protected int[] _array;
+
+ public Reverser(int arraySize)
+ {
+ _array = CreateRandomArray(arraySize);
+ }
+
+ public void Reverse(int index)
+ {
+ if (index > _array.Length)
+ {
+ return;
+ }
+
+ for (int i = 0; i < index / 2; i++)
+ {
+ int temp = _array[i];
+ int upperIndex = index - 1 - i;
+ _array[i] = _array[upperIndex];
+ _array[upperIndex] = temp;
+ }
+ }
+
+ public bool IsArrayInAscendingOrder()
+ {
+ for (int i = 1; i < _array.Length; i++)
+ {
+ if (_array[i] < _array[i - 1])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private int[] CreateRandomArray(int size)
+ {
+ if (size < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(size), "Array size must be a positive integer");
+ }
+
+ var array = new int[size];
+ for (int i = 1; i <= size; i++)
+ {
+ array[i - 1] = i;
+ }
+
+ var rnd = new Random();
+
+ for (int i = size; i > 1;)
+ {
+ int k = rnd.Next(i);
+ --i;
+ int temp = array[i];
+ array[i] = array[k];
+ array[k] = temp;
+ }
+ return array;
+ }
+
+ public string GetArrayString()
+ {
+ var sb = new StringBuilder();
+
+ foreach (int i in _array)
+ {
+ sb.Append(" " + i + " ");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/75_Roulette/java/README.md b/75_Roulette/java/README.md
index 51edd8d4..bc05d92c 100644
--- a/75_Roulette/java/README.md
+++ b/75_Roulette/java/README.md
@@ -1,3 +1,10 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Oracle Java](https://openjdk.java.net/)
+
+Two versions of Roulette has been contributed. They are indicated within given sub-folders
+
+- [oop](./oop) - Conversion by Andrew McGuinness (andrew@arobeia.co.uk)
+- [iterative](./iterative) - Conversion by Thomas Kwashnak ([Github](https://github.com/LittleTealeaf)).
+ - Implements features from JDK 17.
+ - Does make use of some object oriented programming, but acts as a more iterative solution.
\ No newline at end of file
diff --git a/75_Roulette/java/iterative/Roulette.java b/75_Roulette/java/iterative/Roulette.java
new file mode 100644
index 00000000..4d7b0e36
--- /dev/null
+++ b/75_Roulette/java/iterative/Roulette.java
@@ -0,0 +1,277 @@
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Random;
+import java.util.Scanner;
+import java.util.Set;
+
+public class Roulette {
+
+ private static Set RED_NUMBERS;
+
+ static {
+ RED_NUMBERS = Set.of(1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36);
+ }
+
+ private PrintStream out;
+ private Scanner scanner;
+ private int houseBalance, playerBalance;
+ private Random random;
+
+ public Roulette(PrintStream out, InputStream in) {
+ this.out = out;
+ this.scanner = new Scanner(in);
+ houseBalance = 100000;
+ playerBalance = 1000;
+ random = new Random();
+ }
+
+ public static void main(String[] args) {
+ new Roulette(System.out, System.in).play();
+ }
+
+ public void play() {
+ out.println(" ROULETTE");
+ out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ out.println("WELCOME TO THE ROULETTE TABLE\n");
+ out.print("DO YOU WANT INSTRUCTIONS? ");
+ if (scanner.nextLine().toLowerCase().charAt(0) != 'n') {
+ printInstructions();
+ }
+
+ do {
+
+ Bet[] bets = queryBets();
+
+ out.print("SPINNING...\n\n");
+ int result = random.nextInt(1, 39);
+
+ /*
+ Equivalent to following line
+ if(result == 37) {
+ out.println("00");
+ } else if(result == 38) {
+ out.println("0");
+ } else if(RED_NUMBERS.contains(result)) {
+ out.println(result + " RED");
+ } else {
+ out.println(result + " BLACK");
+ }
+ */
+ out.println(switch (result) {
+ case 37 -> "00";
+ case 38 -> "0";
+ default -> result + (RED_NUMBERS.contains(result) ? " RED" : " BLACK");
+ });
+
+ betResults(bets, result);
+ out.println();
+
+ out.println("TOTALS:\tME\t\tYOU");
+ out.format("\t\t%5d\t%d\n", houseBalance, playerBalance);
+ } while (playAgain());
+ if (playerBalance <= 0) {
+ out.println("THANKS FOR YOUR MONEY\nI'LL USE IT TO BUY A SOLID GOLD ROULETTE WHEEL");
+ } else {
+ printCheck();
+ }
+ out.println("COME BACK SOON!");
+ }
+
+ public void printInstructions() {
+ out.println();
+ out.println("THIS IS THE BETTING LAYOUT");
+ out.println(" (*=RED)");
+ out.println();
+ out.println(" 1* 2 3*");
+ out.println(" 4 5* 6 ");
+ out.println(" 7* 8 9*");
+ out.println("10 11 12*");
+ out.println("---------------");
+ out.println("13 14* 15 ");
+ out.println("16* 17 18*");
+ out.println("19* 20 21*");
+ out.println("22 23* 24 ");
+ out.println("---------------");
+ out.println("25* 26 27*");
+ out.println("28 29 30*");
+ out.println("31 32* 33 ");
+ out.println("34* 35 36*");
+ out.println("---------------");
+ out.println(" 00 0 ");
+ out.println();
+ out.println("TYPES OF BETS");
+ out.println();
+ out.println("THE NUMBERS 1 TO 36 SIGNIFY A STRAIGHT BET");
+ out.println("ON THAT NUMBER.");
+ out.println("THESE PAY OFF 35:1");
+ out.println();
+ out.println("THE 2:1 BETS ARE:");
+ out.println(" 37) 1-12 40) FIRST COLUMN");
+ out.println(" 38) 13-24 41) SECOND COLUMN");
+ out.println(" 39) 25-36 42) THIRD COLUMN");
+ out.println();
+ out.println("THE EVEN MONEY BETS ARE:");
+ out.println(" 43) 1-18 46) ODD");
+ out.println(" 44) 19-36 47) RED");
+ out.println(" 45) EVEN 48) BLACK");
+ out.println();
+ out.println(" 49)0 AND 50)00 PAY OFF 35:1");
+ out.println(" NOTE: 0 AND 00 DO NOT COUNT UNDER ANY");
+ out.println(" BETS EXCEPT THEIR OWN.");
+ out.println();
+ out.println("WHEN I ASK FOR EACH BET, TYPE THE NUMBER");
+ out.println("AND THE AMOUNT, SEPARATED BY A COMMA.");
+ out.println("FOR EXAMPLE: TO BET $500 ON BLACK, TYPE 48,500");
+ out.println("WHEN I ASK FOR A BET.");
+ out.println();
+ out.println("THE MINIMUM BET IS $5, THE MAXIMUM IS $500.");
+ }
+
+ private Bet[] queryBets() {
+ int numBets = -1;
+ while (numBets < 1) {
+ out.print("HOW MANY BETS? ");
+ try {
+ numBets = Integer.parseInt(scanner.nextLine());
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ Bet[] bets = new Bet[numBets];
+
+ for (int i = 0; i < numBets; i++) {
+ while (bets[i] == null) {
+ try {
+ out.print("NUMBER" + (i + 1) + "? ");
+ String[] values = scanner.nextLine().split(",");
+ int betNumber = Integer.parseInt(values[0]);
+ int betValue = Integer.parseInt(values[1]);
+
+ for (int j = 0; j < i; j++) {
+ if (bets[j].num == betNumber) {
+ out.println("YOU MADE THAT BET ONCE ALREADY,DUM-DUM");
+ betNumber = -1; //Since -1 is out of the range, this will throw it out at the end
+ }
+ }
+
+ if (betNumber > 0 && betNumber <= 50 && betValue >= 5 && betValue <= 500) {
+ bets[i] = new Bet(betNumber,betValue);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ return bets;
+ }
+
+ private void betResults(Bet[] bets, int num) {
+ for (int i = 0; i < bets.length; i++) {
+ Bet bet = bets[i];
+ /*
+ Using a switch statement of ternary operators that check if a certain condition is met based on the bet value
+ Returns the coefficient that the bet amount should be multiplied by to get the resulting value
+ */
+ int coefficient = switch (bet.num) {
+ case 37 -> (num <= 12) ? 2 : -1;
+ case 38 -> (num > 12 && num <= 24) ? 2 : -1;
+ case 39 -> (num > 24 && num < 37) ? 2 : -1;
+ case 40 -> (num < 37 && num % 3 == 1) ? 2 : -1;
+ case 41 -> (num < 37 && num % 3 == 2) ? 2 : -1;
+ case 42 -> (num < 37 && num % 3 == 0) ? 2 : -1;
+ case 43 -> (num <= 18) ? 1 : -1;
+ case 44 -> (num > 18 && num <= 36) ? 1 : -1;
+ case 45 -> (num % 2 == 0) ? 1 : -1;
+ case 46 -> (num % 2 == 1) ? 1 : -1;
+ case 47 -> RED_NUMBERS.contains(num) ? 1 : -1;
+ case 48 -> !RED_NUMBERS.contains(num) ? 1 : -1;
+ case 49 -> (num == 37) ? 35 : -1;
+ case 50 -> (num == 38) ? 35 : -1;
+ default -> (bet.num < 49 && bet.num == num) ? 35 : -1;
+ };
+
+ int betResult = bet.amount * coefficient;
+
+ if (betResult < 0) {
+ out.println("YOU LOSE " + -betResult + " DOLLARS ON BET " + (i + 1));
+ } else {
+ out.println("YOU WIN " + betResult + " DOLLARS ON BET " + (i + 1));
+ }
+
+ playerBalance += betResult;
+ houseBalance -= betResult;
+ }
+ }
+
+ private boolean playAgain() {
+
+ if (playerBalance <= 0) {
+ out.println("OOPS! YOU JUST SPENT YOUR LAST DOLLAR!");
+ return false;
+ } else if (houseBalance <= 0) {
+ out.println("YOU BROKE THE HOUSE!");
+ playerBalance = 101000;
+ houseBalance = 0;
+ return false;
+ } else {
+ out.println("PLAY AGAIN?");
+ return scanner.nextLine().toLowerCase().charAt(0) == 'y';
+ }
+ }
+
+ private void printCheck() {
+ out.print("TO WHOM SHALL I MAKE THE CHECK? ");
+ String name = scanner.nextLine();
+
+ out.println();
+ for (int i = 0; i < 72; i++) {
+ out.print("-");
+ }
+ out.println();
+
+ for (int i = 0; i < 50; i++) {
+ out.print(" ");
+ }
+ out.println("CHECK NO. " + random.nextInt(0, 100));
+
+ for (int i = 0; i < 40; i++) {
+ out.print(" ");
+ }
+ out.println(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
+ out.println();
+
+ out.println("PAY TO THE ORDER OF -----" + name + "----- $" + (playerBalance));
+ out.println();
+
+ for (int i = 0; i < 40; i++) {
+ out.print(" ");
+ }
+ out.println("THE MEMORY BANK OF NEW YORK");
+
+ for (int i = 0; i < 40; i++) {
+ out.print(" ");
+ }
+ out.println("THE COMPUTER");
+
+ for (int i = 0; i < 40; i++) {
+ out.print(" ");
+ }
+ out.println("----------X-----");
+
+ for (int i = 0; i < 72; i++) {
+ out.print("-");
+ }
+ out.println();
+ }
+
+ public class Bet {
+
+ final int num, amount;
+
+ public Bet(int num, int amount) {
+ this.num = num;
+ this.amount = amount;
+ }
+ }
+}
diff --git a/75_Roulette/java/oop/Bet.java b/75_Roulette/java/oop/Bet.java
new file mode 100644
index 00000000..6d9ea48a
--- /dev/null
+++ b/75_Roulette/java/oop/Bet.java
@@ -0,0 +1,65 @@
+/* A bet has a target (the code entered, which is 1-36, or special values for
+ * the various groups, zero and double-zero), and an amount in dollars
+ */
+
+public class Bet {
+ public int target;
+ public int amount;
+
+ /* bet on a target, of an amount */
+ public Bet(int on, int of) {
+ target = on; amount = of;
+ }
+
+ /* check if this is a valid bet - on a real target and of a valid amount */
+ public boolean isValid() {
+ return ((target > 0) && (target <= 50) &&
+ (amount >= 5) && (amount <= 500));
+ }
+
+ /* utility to return either the odds amount in the case of a win, or zero for a loss */
+ private int m(boolean isWon, int odds) {
+ return isWon? odds: 0;
+ }
+
+ /* look at the wheel to see if this bet won.
+ * returns 0 if it didn't, or the odds if it did
+ */
+ public int winsOn(Wheel w) {
+ if (target < 37) {
+ // A number bet 1-36 wins at odds of 35 if it is the exact number
+ return m(w.isNumber() && (w.number() == target), 35);
+ } else
+ switch (target) {
+ case 37: // 1-12, odds of 2
+ return m(w.isNumber() && (w.number() <= 12), 2);
+ case 38: // 13-24, odds of 2
+ return m(w.isNumber() && (w.number() > 12) && (w.number() <= 24), 2);
+ case 39: // 25-36, odds of 2
+ return m(w.isNumber() && (w.number() > 24), 2);
+ case 40: // Column 1, odds of 2
+ return m(w.isNumber() && ((w.number() % 3) == 1), 2);
+ case 41: // Column 2, odds of 2
+ return m(w.isNumber() && ((w.number() % 3) == 2), 2);
+ case 42: // Column 3, odds of 2
+ return m(w.isNumber() && ((w.number() % 3) == 0), 2);
+ case 43: // 1-18, odds of 1
+ return m(w.isNumber() && (w.number() <= 18), 1);
+ case 44: // 19-36, odds of 1
+ return m(w.isNumber() && (w.number() > 18), 1);
+ case 45: // even, odds of 1
+ return m(w.isNumber() && ((w.number() %2) == 0), 1);
+ case 46: // odd, odds of 1
+ return m(w.isNumber() && ((w.number() %2) == 1), 1);
+ case 47: // red, odds of 1
+ return m(w.isNumber() && (w.color() == Wheel.BLACK), 1);
+ case 48: // black, odds of 1
+ return m(w.isNumber() && (w.color() == Wheel.RED), 1);
+ case 49: // single zero, odds of 35
+ return m(w.value().equals("0"), 35);
+ case 50: // double zero, odds of 35
+ return m(w.value().equals("00"), 35);
+ }
+ throw new RuntimeException("Program Error - invalid bet");
+ }
+}
diff --git a/75_Roulette/java/oop/Roulette.java b/75_Roulette/java/oop/Roulette.java
new file mode 100644
index 00000000..77ae3463
--- /dev/null
+++ b/75_Roulette/java/oop/Roulette.java
@@ -0,0 +1,234 @@
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Random;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+
+public class Roulette {
+ public static void main(String args[]) throws Exception {
+ Roulette r = new Roulette();
+ r.play();
+ }
+
+ private BufferedReader reader;
+ private PrintStream writer;
+
+ private int house; // how much money does the house have
+ private int player; // how much money does the player have
+ private Wheel wheel = new Wheel();
+
+ public Roulette() {
+ reader = new BufferedReader(new InputStreamReader(System.in));
+ writer = System.out;
+ house = 100000;
+ player = 1000;
+ }
+
+ // for a test / cheat mode -- set the random number generator to a known value
+ private void setSeed(long l) {
+ wheel.setSeed(l);
+ }
+
+ public void play() {
+ try {
+ intro();
+ writer.println("WELCOME TO THE ROULETTE TABLE\n" +
+ "DO YOU WANT INSTRUCTIONS");
+ String instr = reader.readLine();
+ if (!instr.toUpperCase().startsWith("N"))
+ instructions();
+
+ while (betAndSpin()) { // returns true if the game is to continue
+ }
+
+ if (player <= 0) {
+ // player ran out of money
+ writer.println("THANKS FOR YOUR MONEY.\nI'LL USE IT TO BUY A SOLID GOLD ROULETTE WHEEL");
+ } else {
+ // player has money -- print them a check
+ writer.println("TO WHOM SHALL I MAKE THE CHECK");
+
+ String payee = reader.readLine();
+
+ writer.println("-".repeat(72));
+ tab(50); writer.println("CHECK NO. " + (new Random().nextInt(100) + 1));
+ writer.println();
+ tab(40); writer.println(LocalDate.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)));
+ writer.println("\n\nPAY TO THE ORDER OF-----" + payee + "-----$ " + player);
+ writer.print("\n\n");
+ tab(10); writer.println("THE MEMORY BANK OF NEW YORK\n");
+ tab(40); writer.println("THE COMPUTER");
+ tab(40); writer.println("----------X-----\n");
+ writer.println("-".repeat(72));
+ writer.println("COME BACK SOON!\n");
+ }
+ }
+ catch (IOException e) {
+ // this should not happen
+ System.err.println("System error:\n" + e);
+ }
+ }
+
+ /* Write the starting introduction */
+ private void intro() throws IOException {
+ tab(32); writer.println("ROULETTE");
+ tab(15); writer.println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n");
+ }
+
+ /* Display the game instructions */
+ private void instructions() {
+ String[] instLines = new String[] {
+ "THIS IS THE BETTING LAYOUT",
+ " (*=RED)",
+ "" ,
+ " 1* 2 3*",
+ " 4 5* 6 ",
+ " 7* 8 9*",
+ "10 11 12*",
+ "---------------",
+ "13 14* 15 ",
+ "16* 17 18*",
+ "19* 20 21*",
+ "22 23* 24 ",
+ "---------------",
+ "25* 26 27*",
+ "28 29 30*",
+ "31 32* 33 ",
+ "34* 35 36*",
+ "---------------",
+ " 00 0 ",
+ "" ,
+ "TYPES OF BETS",
+ "" ,
+ "THE NUMBERS 1 TO 36 SIGNIFY A STRAIGHT BET",
+ "ON THAT NUMBER.",
+ "THESE PAY OFF 35:1",
+ "" ,
+ "THE 2:1 BETS ARE:",
+ " 37) 1-12 40) FIRST COLUMN",
+ " 38) 13-24 41) SECOND COLUMN",
+ " 39) 25-36 42) THIRD COLUMN",
+ "" ,
+ "THE EVEN MONEY BETS ARE:",
+ " 43) 1-18 46) ODD",
+ " 44) 19-36 47) RED",
+ " 45) EVEN 48) BLACK",
+ "",
+ " 49)0 AND 50)00 PAY OFF 35:1",
+ " NOTE: 0 AND 00 DO NOT COUNT UNDER ANY",
+ " BETS EXCEPT THEIR OWN.",
+ "",
+ "WHEN I ASK FOR EACH BET, TYPE THE NUMBER",
+ "AND THE AMOUNT, SEPARATED BY A COMMA.",
+ "FOR EXAMPLE: TO BET $500 ON BLACK, TYPE 48,500",
+ "WHEN I ASK FOR A BET.",
+ "",
+ "THE MINIMUM BET IS $5, THE MAXIMUM IS $500.",
+ "" };
+ writer.println(String.join("\n", instLines));
+ }
+
+ /* Take a set of bets from the player, then spin the wheel and work out the winnings *
+ * This returns true if the game is to continue afterwards
+ */
+ private boolean betAndSpin() throws IOException {
+ int betCount = 0;
+
+ while (betCount == 0) { // keep asking how many bets until we get a good answer
+ try {
+ writer.println("HOW MANY BETS");
+ String howMany = reader.readLine();
+ betCount = Integer.parseInt(howMany.strip());
+
+ if ((betCount < 1) || (betCount > 100)) betCount = 0; // bad -- set zero and ask again
+ }
+ catch (NumberFormatException e) {
+ // this happens if the input is not a number
+ writer.println("INPUT ERROR");
+ }
+ }
+
+ HashSet betsMade = new HashSet<>(); // Bet targets already made, so we can spot repeats
+ ArrayList bets = new ArrayList<>(); // All the bets for this round
+
+ while (bets.size() < betCount) {
+ Bet bet = new Bet(0, 0); // an invalid bet to hold the place
+ while (!bet.isValid()) { // keep asking until it is valid
+ try {
+ writer.println("NUMBER " + (bets.size() + 1));
+ String fields[] = reader.readLine().split(",");
+ if (fields.length == 2) {
+ bet = new Bet(Integer.parseInt(fields[0].strip()),
+ Integer.parseInt(fields[1].strip()));
+ }
+ }
+ catch (NumberFormatException e) {
+ writer.println("INPUT ERROR");
+ }
+ }
+
+ // Check if there is already a bet on the same target
+ if (betsMade.contains(bet.target)) {
+ writer.println("YOU MADE THAT BET ONCE ALREADY,DUM-DUM");
+ } else {
+ betsMade.add(bet.target); // note this target has now been bet on
+ bets.add(bet);
+ }
+ }
+
+ writer.println("SPINNING\n\n");
+
+ wheel.spin(); // this deliberately takes some random amount of time
+
+ writer.println(wheel.value());
+
+ // go through the bets, and evaluate each one
+ int betNumber = 1;
+ for (Bet b : bets) {
+ int multiplier = b.winsOn(wheel);
+ if (multiplier == 0) {
+ // lost the amount of the bet
+ writer.println("YOU LOSE " + b.amount + " DOLLARS ON BET " + betNumber);
+ house += b.amount;
+ player -= b.amount;
+ } else {
+ // won the amount of the bet, multiplied by the odds
+ int winnings = b.amount * multiplier;
+ writer.println("YOU WIN " + winnings + " DOLLARS ON BET " + betNumber);
+ house -= winnings;
+ player += winnings;
+ }
+ ++betNumber;
+ }
+
+ writer.println("\nTOTALS:\tME\tYOU\n\t" + house + "\t" + player);
+
+ if (player <= 0) {
+ writer.println("OOPS! YOU JUST SPENT YOUR LAST DOLLAR");
+ return false; // do not repeat since the player has no more money
+ }
+ if (house <= 0) {
+ writer.println("YOU BROKE THE HOUSE!");
+ player = 101000; // can't win more than the house started with
+ return false; // do not repeat since the house has no more money
+ }
+
+ // player still has money, and the house still has money, so ask the player
+ // if they want to continue
+ writer.println("AGAIN");
+ String doContinue = reader.readLine();
+
+ // repeat if the answer was not "n" or "no"
+ return (!doContinue.toUpperCase().startsWith("N"));
+ }
+
+ // utility to print n spaces for formatting
+ private void tab(int n) {
+ writer.print(" ".repeat(n));
+ }
+}
diff --git a/75_Roulette/java/oop/Wheel.java b/75_Roulette/java/oop/Wheel.java
new file mode 100644
index 00000000..497c8b53
--- /dev/null
+++ b/75_Roulette/java/oop/Wheel.java
@@ -0,0 +1,69 @@
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Random;
+
+// The roulette wheel
+public class Wheel {
+ // List the numbers which are black
+ private HashSet black = new HashSet<>(Arrays.asList(new Integer[] { 1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36 }));
+
+ private Random random = new Random();
+ private int pocket = 38;
+
+ public static final int ZERO=0;
+ public static final int BLACK=1;
+ public static final int RED=2;
+
+ // Set up a wheel. You call "spin", and then can check the result.
+ public Wheel() {
+ }
+
+ // Cheat / test mode
+ void setSeed(long l) {
+ random.setSeed(l);
+ }
+
+ // Spin the wheel onto a new random value.
+ public void spin() {
+ // keep spinning for a while
+ do {
+ try {
+ // 1 second delay. Where it stops, nobody knows
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e) {}
+
+ pocket = random.nextInt(38) + 1;
+ } while (random.nextInt(4) > 0); // keep spinning until it stops
+ }
+
+ // The string representation of the number; 1-36, 0, or 00
+ public String value() {
+ if (pocket == 37) return "0";
+ else if (pocket == 38) return "00";
+ else return String.valueOf(pocket);
+ }
+
+ // True if either 0 or 00 is hit
+ public boolean zero() {
+ return (pocket > 36);
+ }
+
+ // True if anything other than 0 or 00 is hit
+ public boolean isNumber() {
+ return (pocket < 37);
+ }
+
+ // The number rolled
+ public int number() {
+ if (zero()) return 0;
+ else return pocket;
+ }
+
+ // Either ZERO, BLACK, or RED
+ public int color() {
+ if (zero()) return ZERO;
+ else if (black.contains(pocket)) return BLACK;
+ else return RED;
+ }
+}
diff --git a/75_Roulette/perl/README.md b/75_Roulette/perl/README.md
index e69c8b81..e7dcb811 100644
--- a/75_Roulette/perl/README.md
+++ b/75_Roulette/perl/README.md
@@ -1,3 +1,15 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
+
+This conversion consists of three files in `75_Roulette/perl/`:
+
+- `roulette.pl` is the port of the BASIC to Perl;
+- `roulette-test.t` is a Perl test for correctness of display and payout;
+- `make-roulette-test.pl` generates roulette-test.t from roulette.bas.
+
+The ported version of the game numbers the slots from 0 rather than 1, and uses a dispatch table to figure out the payout.
+
+The Perl test loads `roulette.pl` and verifies the Perl slot display and payout logic against the BASIC for all combinations of slots and bets. If any tests fail that fact will be noted at the end of the output.
+
+The test code is generated by reading the BASIC, retaining only the slot display and payout logic (based on line numbers), and wrapping this in code that generates all combinations of bet and spin result. The result is run, and the result is captured and parsed to produce `roulette-test.t`. `make-roulette-test.pl` has some command-line options that may be of interest. `--help` will display the documentation.
diff --git a/75_Roulette/perl/make-roulette-test.pl b/75_Roulette/perl/make-roulette-test.pl
new file mode 100755
index 00000000..100fd8d7
--- /dev/null
+++ b/75_Roulette/perl/make-roulette-test.pl
@@ -0,0 +1,263 @@
+#!/usr/bin/env perl
+
+use 5.014; # For s///r
+
+use strict;
+use warnings;
+
+use File::Temp;
+use Getopt::Long 2.33 qw{ :config auto_version };
+use IPC::Cmd qw{ can_run }; # Core as of Perl 5.9.5.
+use Pod::Usage;
+
+our $VERSION = '0.000_01';
+
+my %opt = (
+ program => find_basic(),
+ output => make_default_output(),
+);
+
+GetOptions( \%opt,
+ qw{ output=s program=s },
+ help => sub { pod2usage( { -verbose => 2 } ) },
+) or pod2usage( { -verbose => 0 } );
+
+die "No default BASIC found; you must specify --program\n"
+ unless defined $opt{program};
+
+my $game_dir = ( File::Spec->splitdir( $0 ) )[0];
+my $basic_file = File::Spec->catfile( $game_dir, 'roulette.bas' );
+open my $basic_handle, '<', $basic_file
+ or die "Unable to open $basic_file: $!\n";
+
+my $munged = File::Temp->new();
+
+print { $munged } <<'EOD';
+1000 Y=50
+1010 DIM B(100),C(100),T(100)
+1090 FOR S=1 TO 38
+1095 PRINT "SPIN ";S
+1100 FOR C=1 TO Y
+1110 B(C)=1
+1120 T(C)=C
+1130 NEXT C
+EOD
+
+transcribe( $basic_file, $basic_handle, $munged, 1860, 2810 );
+transcribe( $basic_file, $basic_handle, $munged, 2950 );
+
+say { $munged } '4000 NEXT S';
+
+$munged->flush();
+
+if ( $opt{output} ne '-' ) {
+ my $dir = ( File::Spec->splitpath( $0 ) )[1];
+ my $fn = File::Spec->rel2abs( $opt{output}, $dir );
+ $fn = File::Spec->abs2rel( $fn );
+ open my $fh, '>', $fn
+ or die "Unable to open $fn: $!\n";
+ warn "Writing $fn\n";
+ select $fh;
+}
+
+print <<'EOD';
+package main;
+
+use 5.010;
+
+use strict;
+use warnings;
+
+use File::Spec;
+use Test::More 0.88; # Because of done_testing();
+
+EOD
+
+print <<"EOD";
+# NOTE: This file is generated by $0.
+# Any edits made to it will be lost the next time it is regenerated.
+# Caveat coder.
+
+EOD
+
+print <<'EOD';
+my $dir = ( File::Spec->splitpath( $0 ) )[1];
+my $script = File::Spec->catfile( $dir, 'roulette.pl' );
+{
+ # Modern Perls do not have . in @INC, but we need it there to load a
+ # relative path.
+ local @INC = ( File::Spec->curdir(), @INC );
+ require $script; # Load game as module
+}
+
+EOD
+
+my $spin;
+my $name;
+foreach ( `$opt{program} @{[ $munged->filename() ]}` ) {
+ s/\N{U+1D}/ /smxg; # Artifact of the BASIC I'm using.
+ s/ \s+ \z //smx;
+ s/ \A \s+ //smx;
+ if ( $_ eq '' ) {
+ # Ignore empty lines.
+ } elsif ( m/ \A SPIN \s* ( [0-9]+ ) /smx ) {
+ $spin = $1 - 1; # BASIC is 1-based, but Perl is 0-based
+ } elsif ( m/ \A YOU \s+ WIN \s* ( [0-9]+ ) \s*
+ DOLLARS \s+ ON \s+ BET \s* ( [0-9]+ ) /smx ) {
+ say "is payout( $spin, $2 ), $1, 'Spin $spin ($name), bet $2 pays $1';";
+ } elsif ( m/ \A YOU \s+ LOSE \s* ( [0-9]+ ) \s*
+ DOLLARS \s+ ON \s+ BET \s* ( [0-9]+ ) /smx ) {
+ say "is payout( $spin, $2 ), -$1, 'Spin $spin ($name), bet $2 pays -$1';";
+ } elsif ( m/ \A \s* ( [0-9]+ ) (?: \s* ( [[:alpha:]]+ ) )? \z /smx ) {
+ $name = $2 ? sprintf( '%d %s', $1, ucfirst lc $2 ) : $1;
+ say "is format_spin( $spin ), '$name', 'Spin $spin is $name';";
+ } else {
+ die "Unexpected input $_";
+ }
+}
+
+print <<'EOD';
+
+done_testing;
+
+1;
+
+# ex: set textwidth=72 :
+EOD
+
+sub find_basic {
+ # yabasic seems not to work
+ foreach my $prog ( qw{ basic cbmbasic } ) {
+ return $prog if can_run( $prog )
+ }
+ return undef;
+}
+
+sub make_default_output {
+ ( my $rslt = $0 ) =~ s/ [.] pl \z /.t/smx;
+ $rslt =~ s/ .* \b make- //smx;
+ return $rslt;
+}
+
+sub transcribe {
+ my ( $in_file, $in_handle, $out_handle, $first_line, $last_line ) = @_;
+ $last_line //= $first_line;
+
+ while ( <$in_handle> ) {
+ m/ \A \s* ( [0-9]+ )+ \s /smx
+ or next;
+ $1 < $first_line
+ and next;
+ say { $out_handle } sprintf '%04d REM BEGIN VERBATIM FROM %s',
+ $first_line - 10, $in_file;
+ print { $out_handle } $_;
+ last;
+ }
+ while ( <$in_handle> ) {
+ m/ \A \s* ( [0-9]+ )+ \s /smx
+ and $1 > $last_line
+ and last;
+ print { $out_handle } $_;
+ }
+ say { $out_handle } sprintf '%04d REM END VERBATIM FROM %s',
+ $last_line + 10, $in_file;
+
+ return;
+}
+
+__END__
+
+=head1 TITLE
+
+make-roulette-test.pl - Generate the tests for 75_Roulette/perl/roulette.pl
+
+=head1 SYNOPSIS
+
+ perl 75_Roulette/perl/make-roulette-test.pl
+ perl 75_Roulette/perl/make-roulette-test.pl --program mybasic
+ perl 75_Roulette/perl/make-roulette-test.pl --help
+ perl 75_Roulette/perl/make-roulette-test.pl --version
+
+=head1 OPTIONS
+
+<<< replace boiler plate >>>
+
+=head2 --help
+
+This option displays the documentation for this script. The script then
+exits.
+
+=head2 --output
+
+ --output fubar.t
+
+This option specifies the output file. This needs to be in the same
+directory as F, and defaults to that directory. A single
+dash (C<'-'>) is special-cased to send the output to standard out.
+
+The default is C<--output=test-roulette.t>.
+
+=head2 --program
+
+ --program my_basic
+
+This option specifies the name of your BASIC interpreter. This must be
+the name of an executable file in your PATH (aliases do not work).
+
+The default is the first-found in the list C.
+
+=head2 --version
+
+This option displays the version of this script. The script then exits.
+
+=head1 DETAILS
+
+This Perl script generates F, which tests
+F. The latter is expected to be written as a modulino.
+
+This script assumes that:
+
+=over
+
+=item * it is in the same directory as F;
+
+=item * F is in the first-level subdirectory under the current directory;
+
+=back
+
+The generated test assumes that it is in the same directory as
+F.
+
+This script works by abstracting the internals of F and
+wrapping them in a loop that generates all possible spins, and places
+all possible bets on each spin. The generated BASIC is written to a
+temporary file, and executed by a BASIC interpreter. The output is
+parsed and used to generate the output.
+
+Obviously there is some ad-hocery going on, and this script has only
+been tested under C, which was what I had on hand.
+
+B the abstraction process is driven by BASIC line numbers. Any
+change of these puts the ad-hocery at risk.
+
+=head1 AUTHOR
+
+Thomas R. Wyant, III F
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2022 by Thomas R. Wyant, III
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl 5.10.0. For more details, see the Artistic
+License 1.0 at
+L, and/or the
+Gnu GPL at L.
+
+This program is distributed in the hope that it will be useful, but
+without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
+
+=cut
+
+# ex: set textwidth=72 :
diff --git a/75_Roulette/perl/roulette-test.t b/75_Roulette/perl/roulette-test.t
new file mode 100644
index 00000000..6fe43fa7
--- /dev/null
+++ b/75_Roulette/perl/roulette-test.t
@@ -0,0 +1,1967 @@
+package main;
+
+use 5.010;
+
+use strict;
+use warnings;
+
+use File::Spec;
+use Test::More 0.88; # Because of done_testing();
+
+# NOTE: This file is generated by 75_Roulette/perl/make-roulette-test.pl.
+# Any edits made to it will be lost the next time it is regenerated.
+# Caveat coder.
+
+my $dir = ( File::Spec->splitpath( $0 ) )[1];
+my $script = File::Spec->catfile( $dir, 'roulette.pl' );
+{
+ # Modern Perls do not have . in @INC, but we need it there to load a
+ # relative path.
+ local @INC = ( File::Spec->curdir(), @INC );
+ require $script; # Load game as module
+}
+
+is format_spin( 0 ), '1 Red', 'Spin 0 is 1 Red';
+is payout( 0, 1 ), 35, 'Spin 0 (1 Red), bet 1 pays 35';
+is payout( 0, 2 ), -1, 'Spin 0 (1 Red), bet 2 pays -1';
+is payout( 0, 3 ), -1, 'Spin 0 (1 Red), bet 3 pays -1';
+is payout( 0, 4 ), -1, 'Spin 0 (1 Red), bet 4 pays -1';
+is payout( 0, 5 ), -1, 'Spin 0 (1 Red), bet 5 pays -1';
+is payout( 0, 6 ), -1, 'Spin 0 (1 Red), bet 6 pays -1';
+is payout( 0, 7 ), -1, 'Spin 0 (1 Red), bet 7 pays -1';
+is payout( 0, 8 ), -1, 'Spin 0 (1 Red), bet 8 pays -1';
+is payout( 0, 9 ), -1, 'Spin 0 (1 Red), bet 9 pays -1';
+is payout( 0, 10 ), -1, 'Spin 0 (1 Red), bet 10 pays -1';
+is payout( 0, 11 ), -1, 'Spin 0 (1 Red), bet 11 pays -1';
+is payout( 0, 12 ), -1, 'Spin 0 (1 Red), bet 12 pays -1';
+is payout( 0, 13 ), -1, 'Spin 0 (1 Red), bet 13 pays -1';
+is payout( 0, 14 ), -1, 'Spin 0 (1 Red), bet 14 pays -1';
+is payout( 0, 15 ), -1, 'Spin 0 (1 Red), bet 15 pays -1';
+is payout( 0, 16 ), -1, 'Spin 0 (1 Red), bet 16 pays -1';
+is payout( 0, 17 ), -1, 'Spin 0 (1 Red), bet 17 pays -1';
+is payout( 0, 18 ), -1, 'Spin 0 (1 Red), bet 18 pays -1';
+is payout( 0, 19 ), -1, 'Spin 0 (1 Red), bet 19 pays -1';
+is payout( 0, 20 ), -1, 'Spin 0 (1 Red), bet 20 pays -1';
+is payout( 0, 21 ), -1, 'Spin 0 (1 Red), bet 21 pays -1';
+is payout( 0, 22 ), -1, 'Spin 0 (1 Red), bet 22 pays -1';
+is payout( 0, 23 ), -1, 'Spin 0 (1 Red), bet 23 pays -1';
+is payout( 0, 24 ), -1, 'Spin 0 (1 Red), bet 24 pays -1';
+is payout( 0, 25 ), -1, 'Spin 0 (1 Red), bet 25 pays -1';
+is payout( 0, 26 ), -1, 'Spin 0 (1 Red), bet 26 pays -1';
+is payout( 0, 27 ), -1, 'Spin 0 (1 Red), bet 27 pays -1';
+is payout( 0, 28 ), -1, 'Spin 0 (1 Red), bet 28 pays -1';
+is payout( 0, 29 ), -1, 'Spin 0 (1 Red), bet 29 pays -1';
+is payout( 0, 30 ), -1, 'Spin 0 (1 Red), bet 30 pays -1';
+is payout( 0, 31 ), -1, 'Spin 0 (1 Red), bet 31 pays -1';
+is payout( 0, 32 ), -1, 'Spin 0 (1 Red), bet 32 pays -1';
+is payout( 0, 33 ), -1, 'Spin 0 (1 Red), bet 33 pays -1';
+is payout( 0, 34 ), -1, 'Spin 0 (1 Red), bet 34 pays -1';
+is payout( 0, 35 ), -1, 'Spin 0 (1 Red), bet 35 pays -1';
+is payout( 0, 36 ), -1, 'Spin 0 (1 Red), bet 36 pays -1';
+is payout( 0, 37 ), 2, 'Spin 0 (1 Red), bet 37 pays 2';
+is payout( 0, 38 ), -1, 'Spin 0 (1 Red), bet 38 pays -1';
+is payout( 0, 39 ), -1, 'Spin 0 (1 Red), bet 39 pays -1';
+is payout( 0, 40 ), 2, 'Spin 0 (1 Red), bet 40 pays 2';
+is payout( 0, 41 ), -1, 'Spin 0 (1 Red), bet 41 pays -1';
+is payout( 0, 42 ), -1, 'Spin 0 (1 Red), bet 42 pays -1';
+is payout( 0, 43 ), 1, 'Spin 0 (1 Red), bet 43 pays 1';
+is payout( 0, 44 ), -1, 'Spin 0 (1 Red), bet 44 pays -1';
+is payout( 0, 45 ), -1, 'Spin 0 (1 Red), bet 45 pays -1';
+is payout( 0, 46 ), 1, 'Spin 0 (1 Red), bet 46 pays 1';
+is payout( 0, 47 ), 1, 'Spin 0 (1 Red), bet 47 pays 1';
+is payout( 0, 48 ), -1, 'Spin 0 (1 Red), bet 48 pays -1';
+is payout( 0, 49 ), -1, 'Spin 0 (1 Red), bet 49 pays -1';
+is payout( 0, 50 ), -1, 'Spin 0 (1 Red), bet 50 pays -1';
+is format_spin( 1 ), '2 Black', 'Spin 1 is 2 Black';
+is payout( 1, 1 ), -1, 'Spin 1 (2 Black), bet 1 pays -1';
+is payout( 1, 2 ), 35, 'Spin 1 (2 Black), bet 2 pays 35';
+is payout( 1, 3 ), -1, 'Spin 1 (2 Black), bet 3 pays -1';
+is payout( 1, 4 ), -1, 'Spin 1 (2 Black), bet 4 pays -1';
+is payout( 1, 5 ), -1, 'Spin 1 (2 Black), bet 5 pays -1';
+is payout( 1, 6 ), -1, 'Spin 1 (2 Black), bet 6 pays -1';
+is payout( 1, 7 ), -1, 'Spin 1 (2 Black), bet 7 pays -1';
+is payout( 1, 8 ), -1, 'Spin 1 (2 Black), bet 8 pays -1';
+is payout( 1, 9 ), -1, 'Spin 1 (2 Black), bet 9 pays -1';
+is payout( 1, 10 ), -1, 'Spin 1 (2 Black), bet 10 pays -1';
+is payout( 1, 11 ), -1, 'Spin 1 (2 Black), bet 11 pays -1';
+is payout( 1, 12 ), -1, 'Spin 1 (2 Black), bet 12 pays -1';
+is payout( 1, 13 ), -1, 'Spin 1 (2 Black), bet 13 pays -1';
+is payout( 1, 14 ), -1, 'Spin 1 (2 Black), bet 14 pays -1';
+is payout( 1, 15 ), -1, 'Spin 1 (2 Black), bet 15 pays -1';
+is payout( 1, 16 ), -1, 'Spin 1 (2 Black), bet 16 pays -1';
+is payout( 1, 17 ), -1, 'Spin 1 (2 Black), bet 17 pays -1';
+is payout( 1, 18 ), -1, 'Spin 1 (2 Black), bet 18 pays -1';
+is payout( 1, 19 ), -1, 'Spin 1 (2 Black), bet 19 pays -1';
+is payout( 1, 20 ), -1, 'Spin 1 (2 Black), bet 20 pays -1';
+is payout( 1, 21 ), -1, 'Spin 1 (2 Black), bet 21 pays -1';
+is payout( 1, 22 ), -1, 'Spin 1 (2 Black), bet 22 pays -1';
+is payout( 1, 23 ), -1, 'Spin 1 (2 Black), bet 23 pays -1';
+is payout( 1, 24 ), -1, 'Spin 1 (2 Black), bet 24 pays -1';
+is payout( 1, 25 ), -1, 'Spin 1 (2 Black), bet 25 pays -1';
+is payout( 1, 26 ), -1, 'Spin 1 (2 Black), bet 26 pays -1';
+is payout( 1, 27 ), -1, 'Spin 1 (2 Black), bet 27 pays -1';
+is payout( 1, 28 ), -1, 'Spin 1 (2 Black), bet 28 pays -1';
+is payout( 1, 29 ), -1, 'Spin 1 (2 Black), bet 29 pays -1';
+is payout( 1, 30 ), -1, 'Spin 1 (2 Black), bet 30 pays -1';
+is payout( 1, 31 ), -1, 'Spin 1 (2 Black), bet 31 pays -1';
+is payout( 1, 32 ), -1, 'Spin 1 (2 Black), bet 32 pays -1';
+is payout( 1, 33 ), -1, 'Spin 1 (2 Black), bet 33 pays -1';
+is payout( 1, 34 ), -1, 'Spin 1 (2 Black), bet 34 pays -1';
+is payout( 1, 35 ), -1, 'Spin 1 (2 Black), bet 35 pays -1';
+is payout( 1, 36 ), -1, 'Spin 1 (2 Black), bet 36 pays -1';
+is payout( 1, 37 ), 2, 'Spin 1 (2 Black), bet 37 pays 2';
+is payout( 1, 38 ), -1, 'Spin 1 (2 Black), bet 38 pays -1';
+is payout( 1, 39 ), -1, 'Spin 1 (2 Black), bet 39 pays -1';
+is payout( 1, 40 ), -1, 'Spin 1 (2 Black), bet 40 pays -1';
+is payout( 1, 41 ), 2, 'Spin 1 (2 Black), bet 41 pays 2';
+is payout( 1, 42 ), -1, 'Spin 1 (2 Black), bet 42 pays -1';
+is payout( 1, 43 ), 1, 'Spin 1 (2 Black), bet 43 pays 1';
+is payout( 1, 44 ), -1, 'Spin 1 (2 Black), bet 44 pays -1';
+is payout( 1, 45 ), 1, 'Spin 1 (2 Black), bet 45 pays 1';
+is payout( 1, 46 ), -1, 'Spin 1 (2 Black), bet 46 pays -1';
+is payout( 1, 47 ), -1, 'Spin 1 (2 Black), bet 47 pays -1';
+is payout( 1, 48 ), 1, 'Spin 1 (2 Black), bet 48 pays 1';
+is payout( 1, 49 ), -1, 'Spin 1 (2 Black), bet 49 pays -1';
+is payout( 1, 50 ), -1, 'Spin 1 (2 Black), bet 50 pays -1';
+is format_spin( 2 ), '3 Red', 'Spin 2 is 3 Red';
+is payout( 2, 1 ), -1, 'Spin 2 (3 Red), bet 1 pays -1';
+is payout( 2, 2 ), -1, 'Spin 2 (3 Red), bet 2 pays -1';
+is payout( 2, 3 ), 35, 'Spin 2 (3 Red), bet 3 pays 35';
+is payout( 2, 4 ), -1, 'Spin 2 (3 Red), bet 4 pays -1';
+is payout( 2, 5 ), -1, 'Spin 2 (3 Red), bet 5 pays -1';
+is payout( 2, 6 ), -1, 'Spin 2 (3 Red), bet 6 pays -1';
+is payout( 2, 7 ), -1, 'Spin 2 (3 Red), bet 7 pays -1';
+is payout( 2, 8 ), -1, 'Spin 2 (3 Red), bet 8 pays -1';
+is payout( 2, 9 ), -1, 'Spin 2 (3 Red), bet 9 pays -1';
+is payout( 2, 10 ), -1, 'Spin 2 (3 Red), bet 10 pays -1';
+is payout( 2, 11 ), -1, 'Spin 2 (3 Red), bet 11 pays -1';
+is payout( 2, 12 ), -1, 'Spin 2 (3 Red), bet 12 pays -1';
+is payout( 2, 13 ), -1, 'Spin 2 (3 Red), bet 13 pays -1';
+is payout( 2, 14 ), -1, 'Spin 2 (3 Red), bet 14 pays -1';
+is payout( 2, 15 ), -1, 'Spin 2 (3 Red), bet 15 pays -1';
+is payout( 2, 16 ), -1, 'Spin 2 (3 Red), bet 16 pays -1';
+is payout( 2, 17 ), -1, 'Spin 2 (3 Red), bet 17 pays -1';
+is payout( 2, 18 ), -1, 'Spin 2 (3 Red), bet 18 pays -1';
+is payout( 2, 19 ), -1, 'Spin 2 (3 Red), bet 19 pays -1';
+is payout( 2, 20 ), -1, 'Spin 2 (3 Red), bet 20 pays -1';
+is payout( 2, 21 ), -1, 'Spin 2 (3 Red), bet 21 pays -1';
+is payout( 2, 22 ), -1, 'Spin 2 (3 Red), bet 22 pays -1';
+is payout( 2, 23 ), -1, 'Spin 2 (3 Red), bet 23 pays -1';
+is payout( 2, 24 ), -1, 'Spin 2 (3 Red), bet 24 pays -1';
+is payout( 2, 25 ), -1, 'Spin 2 (3 Red), bet 25 pays -1';
+is payout( 2, 26 ), -1, 'Spin 2 (3 Red), bet 26 pays -1';
+is payout( 2, 27 ), -1, 'Spin 2 (3 Red), bet 27 pays -1';
+is payout( 2, 28 ), -1, 'Spin 2 (3 Red), bet 28 pays -1';
+is payout( 2, 29 ), -1, 'Spin 2 (3 Red), bet 29 pays -1';
+is payout( 2, 30 ), -1, 'Spin 2 (3 Red), bet 30 pays -1';
+is payout( 2, 31 ), -1, 'Spin 2 (3 Red), bet 31 pays -1';
+is payout( 2, 32 ), -1, 'Spin 2 (3 Red), bet 32 pays -1';
+is payout( 2, 33 ), -1, 'Spin 2 (3 Red), bet 33 pays -1';
+is payout( 2, 34 ), -1, 'Spin 2 (3 Red), bet 34 pays -1';
+is payout( 2, 35 ), -1, 'Spin 2 (3 Red), bet 35 pays -1';
+is payout( 2, 36 ), -1, 'Spin 2 (3 Red), bet 36 pays -1';
+is payout( 2, 37 ), 2, 'Spin 2 (3 Red), bet 37 pays 2';
+is payout( 2, 38 ), -1, 'Spin 2 (3 Red), bet 38 pays -1';
+is payout( 2, 39 ), -1, 'Spin 2 (3 Red), bet 39 pays -1';
+is payout( 2, 40 ), -1, 'Spin 2 (3 Red), bet 40 pays -1';
+is payout( 2, 41 ), -1, 'Spin 2 (3 Red), bet 41 pays -1';
+is payout( 2, 42 ), 2, 'Spin 2 (3 Red), bet 42 pays 2';
+is payout( 2, 43 ), 1, 'Spin 2 (3 Red), bet 43 pays 1';
+is payout( 2, 44 ), -1, 'Spin 2 (3 Red), bet 44 pays -1';
+is payout( 2, 45 ), -1, 'Spin 2 (3 Red), bet 45 pays -1';
+is payout( 2, 46 ), 1, 'Spin 2 (3 Red), bet 46 pays 1';
+is payout( 2, 47 ), 1, 'Spin 2 (3 Red), bet 47 pays 1';
+is payout( 2, 48 ), -1, 'Spin 2 (3 Red), bet 48 pays -1';
+is payout( 2, 49 ), -1, 'Spin 2 (3 Red), bet 49 pays -1';
+is payout( 2, 50 ), -1, 'Spin 2 (3 Red), bet 50 pays -1';
+is format_spin( 3 ), '4 Black', 'Spin 3 is 4 Black';
+is payout( 3, 1 ), -1, 'Spin 3 (4 Black), bet 1 pays -1';
+is payout( 3, 2 ), -1, 'Spin 3 (4 Black), bet 2 pays -1';
+is payout( 3, 3 ), -1, 'Spin 3 (4 Black), bet 3 pays -1';
+is payout( 3, 4 ), 35, 'Spin 3 (4 Black), bet 4 pays 35';
+is payout( 3, 5 ), -1, 'Spin 3 (4 Black), bet 5 pays -1';
+is payout( 3, 6 ), -1, 'Spin 3 (4 Black), bet 6 pays -1';
+is payout( 3, 7 ), -1, 'Spin 3 (4 Black), bet 7 pays -1';
+is payout( 3, 8 ), -1, 'Spin 3 (4 Black), bet 8 pays -1';
+is payout( 3, 9 ), -1, 'Spin 3 (4 Black), bet 9 pays -1';
+is payout( 3, 10 ), -1, 'Spin 3 (4 Black), bet 10 pays -1';
+is payout( 3, 11 ), -1, 'Spin 3 (4 Black), bet 11 pays -1';
+is payout( 3, 12 ), -1, 'Spin 3 (4 Black), bet 12 pays -1';
+is payout( 3, 13 ), -1, 'Spin 3 (4 Black), bet 13 pays -1';
+is payout( 3, 14 ), -1, 'Spin 3 (4 Black), bet 14 pays -1';
+is payout( 3, 15 ), -1, 'Spin 3 (4 Black), bet 15 pays -1';
+is payout( 3, 16 ), -1, 'Spin 3 (4 Black), bet 16 pays -1';
+is payout( 3, 17 ), -1, 'Spin 3 (4 Black), bet 17 pays -1';
+is payout( 3, 18 ), -1, 'Spin 3 (4 Black), bet 18 pays -1';
+is payout( 3, 19 ), -1, 'Spin 3 (4 Black), bet 19 pays -1';
+is payout( 3, 20 ), -1, 'Spin 3 (4 Black), bet 20 pays -1';
+is payout( 3, 21 ), -1, 'Spin 3 (4 Black), bet 21 pays -1';
+is payout( 3, 22 ), -1, 'Spin 3 (4 Black), bet 22 pays -1';
+is payout( 3, 23 ), -1, 'Spin 3 (4 Black), bet 23 pays -1';
+is payout( 3, 24 ), -1, 'Spin 3 (4 Black), bet 24 pays -1';
+is payout( 3, 25 ), -1, 'Spin 3 (4 Black), bet 25 pays -1';
+is payout( 3, 26 ), -1, 'Spin 3 (4 Black), bet 26 pays -1';
+is payout( 3, 27 ), -1, 'Spin 3 (4 Black), bet 27 pays -1';
+is payout( 3, 28 ), -1, 'Spin 3 (4 Black), bet 28 pays -1';
+is payout( 3, 29 ), -1, 'Spin 3 (4 Black), bet 29 pays -1';
+is payout( 3, 30 ), -1, 'Spin 3 (4 Black), bet 30 pays -1';
+is payout( 3, 31 ), -1, 'Spin 3 (4 Black), bet 31 pays -1';
+is payout( 3, 32 ), -1, 'Spin 3 (4 Black), bet 32 pays -1';
+is payout( 3, 33 ), -1, 'Spin 3 (4 Black), bet 33 pays -1';
+is payout( 3, 34 ), -1, 'Spin 3 (4 Black), bet 34 pays -1';
+is payout( 3, 35 ), -1, 'Spin 3 (4 Black), bet 35 pays -1';
+is payout( 3, 36 ), -1, 'Spin 3 (4 Black), bet 36 pays -1';
+is payout( 3, 37 ), 2, 'Spin 3 (4 Black), bet 37 pays 2';
+is payout( 3, 38 ), -1, 'Spin 3 (4 Black), bet 38 pays -1';
+is payout( 3, 39 ), -1, 'Spin 3 (4 Black), bet 39 pays -1';
+is payout( 3, 40 ), 2, 'Spin 3 (4 Black), bet 40 pays 2';
+is payout( 3, 41 ), -1, 'Spin 3 (4 Black), bet 41 pays -1';
+is payout( 3, 42 ), -1, 'Spin 3 (4 Black), bet 42 pays -1';
+is payout( 3, 43 ), 1, 'Spin 3 (4 Black), bet 43 pays 1';
+is payout( 3, 44 ), -1, 'Spin 3 (4 Black), bet 44 pays -1';
+is payout( 3, 45 ), 1, 'Spin 3 (4 Black), bet 45 pays 1';
+is payout( 3, 46 ), -1, 'Spin 3 (4 Black), bet 46 pays -1';
+is payout( 3, 47 ), -1, 'Spin 3 (4 Black), bet 47 pays -1';
+is payout( 3, 48 ), 1, 'Spin 3 (4 Black), bet 48 pays 1';
+is payout( 3, 49 ), -1, 'Spin 3 (4 Black), bet 49 pays -1';
+is payout( 3, 50 ), -1, 'Spin 3 (4 Black), bet 50 pays -1';
+is format_spin( 4 ), '5 Red', 'Spin 4 is 5 Red';
+is payout( 4, 1 ), -1, 'Spin 4 (5 Red), bet 1 pays -1';
+is payout( 4, 2 ), -1, 'Spin 4 (5 Red), bet 2 pays -1';
+is payout( 4, 3 ), -1, 'Spin 4 (5 Red), bet 3 pays -1';
+is payout( 4, 4 ), -1, 'Spin 4 (5 Red), bet 4 pays -1';
+is payout( 4, 5 ), 35, 'Spin 4 (5 Red), bet 5 pays 35';
+is payout( 4, 6 ), -1, 'Spin 4 (5 Red), bet 6 pays -1';
+is payout( 4, 7 ), -1, 'Spin 4 (5 Red), bet 7 pays -1';
+is payout( 4, 8 ), -1, 'Spin 4 (5 Red), bet 8 pays -1';
+is payout( 4, 9 ), -1, 'Spin 4 (5 Red), bet 9 pays -1';
+is payout( 4, 10 ), -1, 'Spin 4 (5 Red), bet 10 pays -1';
+is payout( 4, 11 ), -1, 'Spin 4 (5 Red), bet 11 pays -1';
+is payout( 4, 12 ), -1, 'Spin 4 (5 Red), bet 12 pays -1';
+is payout( 4, 13 ), -1, 'Spin 4 (5 Red), bet 13 pays -1';
+is payout( 4, 14 ), -1, 'Spin 4 (5 Red), bet 14 pays -1';
+is payout( 4, 15 ), -1, 'Spin 4 (5 Red), bet 15 pays -1';
+is payout( 4, 16 ), -1, 'Spin 4 (5 Red), bet 16 pays -1';
+is payout( 4, 17 ), -1, 'Spin 4 (5 Red), bet 17 pays -1';
+is payout( 4, 18 ), -1, 'Spin 4 (5 Red), bet 18 pays -1';
+is payout( 4, 19 ), -1, 'Spin 4 (5 Red), bet 19 pays -1';
+is payout( 4, 20 ), -1, 'Spin 4 (5 Red), bet 20 pays -1';
+is payout( 4, 21 ), -1, 'Spin 4 (5 Red), bet 21 pays -1';
+is payout( 4, 22 ), -1, 'Spin 4 (5 Red), bet 22 pays -1';
+is payout( 4, 23 ), -1, 'Spin 4 (5 Red), bet 23 pays -1';
+is payout( 4, 24 ), -1, 'Spin 4 (5 Red), bet 24 pays -1';
+is payout( 4, 25 ), -1, 'Spin 4 (5 Red), bet 25 pays -1';
+is payout( 4, 26 ), -1, 'Spin 4 (5 Red), bet 26 pays -1';
+is payout( 4, 27 ), -1, 'Spin 4 (5 Red), bet 27 pays -1';
+is payout( 4, 28 ), -1, 'Spin 4 (5 Red), bet 28 pays -1';
+is payout( 4, 29 ), -1, 'Spin 4 (5 Red), bet 29 pays -1';
+is payout( 4, 30 ), -1, 'Spin 4 (5 Red), bet 30 pays -1';
+is payout( 4, 31 ), -1, 'Spin 4 (5 Red), bet 31 pays -1';
+is payout( 4, 32 ), -1, 'Spin 4 (5 Red), bet 32 pays -1';
+is payout( 4, 33 ), -1, 'Spin 4 (5 Red), bet 33 pays -1';
+is payout( 4, 34 ), -1, 'Spin 4 (5 Red), bet 34 pays -1';
+is payout( 4, 35 ), -1, 'Spin 4 (5 Red), bet 35 pays -1';
+is payout( 4, 36 ), -1, 'Spin 4 (5 Red), bet 36 pays -1';
+is payout( 4, 37 ), 2, 'Spin 4 (5 Red), bet 37 pays 2';
+is payout( 4, 38 ), -1, 'Spin 4 (5 Red), bet 38 pays -1';
+is payout( 4, 39 ), -1, 'Spin 4 (5 Red), bet 39 pays -1';
+is payout( 4, 40 ), -1, 'Spin 4 (5 Red), bet 40 pays -1';
+is payout( 4, 41 ), 2, 'Spin 4 (5 Red), bet 41 pays 2';
+is payout( 4, 42 ), -1, 'Spin 4 (5 Red), bet 42 pays -1';
+is payout( 4, 43 ), 1, 'Spin 4 (5 Red), bet 43 pays 1';
+is payout( 4, 44 ), -1, 'Spin 4 (5 Red), bet 44 pays -1';
+is payout( 4, 45 ), -1, 'Spin 4 (5 Red), bet 45 pays -1';
+is payout( 4, 46 ), 1, 'Spin 4 (5 Red), bet 46 pays 1';
+is payout( 4, 47 ), 1, 'Spin 4 (5 Red), bet 47 pays 1';
+is payout( 4, 48 ), -1, 'Spin 4 (5 Red), bet 48 pays -1';
+is payout( 4, 49 ), -1, 'Spin 4 (5 Red), bet 49 pays -1';
+is payout( 4, 50 ), -1, 'Spin 4 (5 Red), bet 50 pays -1';
+is format_spin( 5 ), '6 Black', 'Spin 5 is 6 Black';
+is payout( 5, 1 ), -1, 'Spin 5 (6 Black), bet 1 pays -1';
+is payout( 5, 2 ), -1, 'Spin 5 (6 Black), bet 2 pays -1';
+is payout( 5, 3 ), -1, 'Spin 5 (6 Black), bet 3 pays -1';
+is payout( 5, 4 ), -1, 'Spin 5 (6 Black), bet 4 pays -1';
+is payout( 5, 5 ), -1, 'Spin 5 (6 Black), bet 5 pays -1';
+is payout( 5, 6 ), 35, 'Spin 5 (6 Black), bet 6 pays 35';
+is payout( 5, 7 ), -1, 'Spin 5 (6 Black), bet 7 pays -1';
+is payout( 5, 8 ), -1, 'Spin 5 (6 Black), bet 8 pays -1';
+is payout( 5, 9 ), -1, 'Spin 5 (6 Black), bet 9 pays -1';
+is payout( 5, 10 ), -1, 'Spin 5 (6 Black), bet 10 pays -1';
+is payout( 5, 11 ), -1, 'Spin 5 (6 Black), bet 11 pays -1';
+is payout( 5, 12 ), -1, 'Spin 5 (6 Black), bet 12 pays -1';
+is payout( 5, 13 ), -1, 'Spin 5 (6 Black), bet 13 pays -1';
+is payout( 5, 14 ), -1, 'Spin 5 (6 Black), bet 14 pays -1';
+is payout( 5, 15 ), -1, 'Spin 5 (6 Black), bet 15 pays -1';
+is payout( 5, 16 ), -1, 'Spin 5 (6 Black), bet 16 pays -1';
+is payout( 5, 17 ), -1, 'Spin 5 (6 Black), bet 17 pays -1';
+is payout( 5, 18 ), -1, 'Spin 5 (6 Black), bet 18 pays -1';
+is payout( 5, 19 ), -1, 'Spin 5 (6 Black), bet 19 pays -1';
+is payout( 5, 20 ), -1, 'Spin 5 (6 Black), bet 20 pays -1';
+is payout( 5, 21 ), -1, 'Spin 5 (6 Black), bet 21 pays -1';
+is payout( 5, 22 ), -1, 'Spin 5 (6 Black), bet 22 pays -1';
+is payout( 5, 23 ), -1, 'Spin 5 (6 Black), bet 23 pays -1';
+is payout( 5, 24 ), -1, 'Spin 5 (6 Black), bet 24 pays -1';
+is payout( 5, 25 ), -1, 'Spin 5 (6 Black), bet 25 pays -1';
+is payout( 5, 26 ), -1, 'Spin 5 (6 Black), bet 26 pays -1';
+is payout( 5, 27 ), -1, 'Spin 5 (6 Black), bet 27 pays -1';
+is payout( 5, 28 ), -1, 'Spin 5 (6 Black), bet 28 pays -1';
+is payout( 5, 29 ), -1, 'Spin 5 (6 Black), bet 29 pays -1';
+is payout( 5, 30 ), -1, 'Spin 5 (6 Black), bet 30 pays -1';
+is payout( 5, 31 ), -1, 'Spin 5 (6 Black), bet 31 pays -1';
+is payout( 5, 32 ), -1, 'Spin 5 (6 Black), bet 32 pays -1';
+is payout( 5, 33 ), -1, 'Spin 5 (6 Black), bet 33 pays -1';
+is payout( 5, 34 ), -1, 'Spin 5 (6 Black), bet 34 pays -1';
+is payout( 5, 35 ), -1, 'Spin 5 (6 Black), bet 35 pays -1';
+is payout( 5, 36 ), -1, 'Spin 5 (6 Black), bet 36 pays -1';
+is payout( 5, 37 ), 2, 'Spin 5 (6 Black), bet 37 pays 2';
+is payout( 5, 38 ), -1, 'Spin 5 (6 Black), bet 38 pays -1';
+is payout( 5, 39 ), -1, 'Spin 5 (6 Black), bet 39 pays -1';
+is payout( 5, 40 ), -1, 'Spin 5 (6 Black), bet 40 pays -1';
+is payout( 5, 41 ), -1, 'Spin 5 (6 Black), bet 41 pays -1';
+is payout( 5, 42 ), 2, 'Spin 5 (6 Black), bet 42 pays 2';
+is payout( 5, 43 ), 1, 'Spin 5 (6 Black), bet 43 pays 1';
+is payout( 5, 44 ), -1, 'Spin 5 (6 Black), bet 44 pays -1';
+is payout( 5, 45 ), 1, 'Spin 5 (6 Black), bet 45 pays 1';
+is payout( 5, 46 ), -1, 'Spin 5 (6 Black), bet 46 pays -1';
+is payout( 5, 47 ), -1, 'Spin 5 (6 Black), bet 47 pays -1';
+is payout( 5, 48 ), 1, 'Spin 5 (6 Black), bet 48 pays 1';
+is payout( 5, 49 ), -1, 'Spin 5 (6 Black), bet 49 pays -1';
+is payout( 5, 50 ), -1, 'Spin 5 (6 Black), bet 50 pays -1';
+is format_spin( 6 ), '7 Red', 'Spin 6 is 7 Red';
+is payout( 6, 1 ), -1, 'Spin 6 (7 Red), bet 1 pays -1';
+is payout( 6, 2 ), -1, 'Spin 6 (7 Red), bet 2 pays -1';
+is payout( 6, 3 ), -1, 'Spin 6 (7 Red), bet 3 pays -1';
+is payout( 6, 4 ), -1, 'Spin 6 (7 Red), bet 4 pays -1';
+is payout( 6, 5 ), -1, 'Spin 6 (7 Red), bet 5 pays -1';
+is payout( 6, 6 ), -1, 'Spin 6 (7 Red), bet 6 pays -1';
+is payout( 6, 7 ), 35, 'Spin 6 (7 Red), bet 7 pays 35';
+is payout( 6, 8 ), -1, 'Spin 6 (7 Red), bet 8 pays -1';
+is payout( 6, 9 ), -1, 'Spin 6 (7 Red), bet 9 pays -1';
+is payout( 6, 10 ), -1, 'Spin 6 (7 Red), bet 10 pays -1';
+is payout( 6, 11 ), -1, 'Spin 6 (7 Red), bet 11 pays -1';
+is payout( 6, 12 ), -1, 'Spin 6 (7 Red), bet 12 pays -1';
+is payout( 6, 13 ), -1, 'Spin 6 (7 Red), bet 13 pays -1';
+is payout( 6, 14 ), -1, 'Spin 6 (7 Red), bet 14 pays -1';
+is payout( 6, 15 ), -1, 'Spin 6 (7 Red), bet 15 pays -1';
+is payout( 6, 16 ), -1, 'Spin 6 (7 Red), bet 16 pays -1';
+is payout( 6, 17 ), -1, 'Spin 6 (7 Red), bet 17 pays -1';
+is payout( 6, 18 ), -1, 'Spin 6 (7 Red), bet 18 pays -1';
+is payout( 6, 19 ), -1, 'Spin 6 (7 Red), bet 19 pays -1';
+is payout( 6, 20 ), -1, 'Spin 6 (7 Red), bet 20 pays -1';
+is payout( 6, 21 ), -1, 'Spin 6 (7 Red), bet 21 pays -1';
+is payout( 6, 22 ), -1, 'Spin 6 (7 Red), bet 22 pays -1';
+is payout( 6, 23 ), -1, 'Spin 6 (7 Red), bet 23 pays -1';
+is payout( 6, 24 ), -1, 'Spin 6 (7 Red), bet 24 pays -1';
+is payout( 6, 25 ), -1, 'Spin 6 (7 Red), bet 25 pays -1';
+is payout( 6, 26 ), -1, 'Spin 6 (7 Red), bet 26 pays -1';
+is payout( 6, 27 ), -1, 'Spin 6 (7 Red), bet 27 pays -1';
+is payout( 6, 28 ), -1, 'Spin 6 (7 Red), bet 28 pays -1';
+is payout( 6, 29 ), -1, 'Spin 6 (7 Red), bet 29 pays -1';
+is payout( 6, 30 ), -1, 'Spin 6 (7 Red), bet 30 pays -1';
+is payout( 6, 31 ), -1, 'Spin 6 (7 Red), bet 31 pays -1';
+is payout( 6, 32 ), -1, 'Spin 6 (7 Red), bet 32 pays -1';
+is payout( 6, 33 ), -1, 'Spin 6 (7 Red), bet 33 pays -1';
+is payout( 6, 34 ), -1, 'Spin 6 (7 Red), bet 34 pays -1';
+is payout( 6, 35 ), -1, 'Spin 6 (7 Red), bet 35 pays -1';
+is payout( 6, 36 ), -1, 'Spin 6 (7 Red), bet 36 pays -1';
+is payout( 6, 37 ), 2, 'Spin 6 (7 Red), bet 37 pays 2';
+is payout( 6, 38 ), -1, 'Spin 6 (7 Red), bet 38 pays -1';
+is payout( 6, 39 ), -1, 'Spin 6 (7 Red), bet 39 pays -1';
+is payout( 6, 40 ), 2, 'Spin 6 (7 Red), bet 40 pays 2';
+is payout( 6, 41 ), -1, 'Spin 6 (7 Red), bet 41 pays -1';
+is payout( 6, 42 ), -1, 'Spin 6 (7 Red), bet 42 pays -1';
+is payout( 6, 43 ), 1, 'Spin 6 (7 Red), bet 43 pays 1';
+is payout( 6, 44 ), -1, 'Spin 6 (7 Red), bet 44 pays -1';
+is payout( 6, 45 ), -1, 'Spin 6 (7 Red), bet 45 pays -1';
+is payout( 6, 46 ), 1, 'Spin 6 (7 Red), bet 46 pays 1';
+is payout( 6, 47 ), 1, 'Spin 6 (7 Red), bet 47 pays 1';
+is payout( 6, 48 ), -1, 'Spin 6 (7 Red), bet 48 pays -1';
+is payout( 6, 49 ), -1, 'Spin 6 (7 Red), bet 49 pays -1';
+is payout( 6, 50 ), -1, 'Spin 6 (7 Red), bet 50 pays -1';
+is format_spin( 7 ), '8 Black', 'Spin 7 is 8 Black';
+is payout( 7, 1 ), -1, 'Spin 7 (8 Black), bet 1 pays -1';
+is payout( 7, 2 ), -1, 'Spin 7 (8 Black), bet 2 pays -1';
+is payout( 7, 3 ), -1, 'Spin 7 (8 Black), bet 3 pays -1';
+is payout( 7, 4 ), -1, 'Spin 7 (8 Black), bet 4 pays -1';
+is payout( 7, 5 ), -1, 'Spin 7 (8 Black), bet 5 pays -1';
+is payout( 7, 6 ), -1, 'Spin 7 (8 Black), bet 6 pays -1';
+is payout( 7, 7 ), -1, 'Spin 7 (8 Black), bet 7 pays -1';
+is payout( 7, 8 ), 35, 'Spin 7 (8 Black), bet 8 pays 35';
+is payout( 7, 9 ), -1, 'Spin 7 (8 Black), bet 9 pays -1';
+is payout( 7, 10 ), -1, 'Spin 7 (8 Black), bet 10 pays -1';
+is payout( 7, 11 ), -1, 'Spin 7 (8 Black), bet 11 pays -1';
+is payout( 7, 12 ), -1, 'Spin 7 (8 Black), bet 12 pays -1';
+is payout( 7, 13 ), -1, 'Spin 7 (8 Black), bet 13 pays -1';
+is payout( 7, 14 ), -1, 'Spin 7 (8 Black), bet 14 pays -1';
+is payout( 7, 15 ), -1, 'Spin 7 (8 Black), bet 15 pays -1';
+is payout( 7, 16 ), -1, 'Spin 7 (8 Black), bet 16 pays -1';
+is payout( 7, 17 ), -1, 'Spin 7 (8 Black), bet 17 pays -1';
+is payout( 7, 18 ), -1, 'Spin 7 (8 Black), bet 18 pays -1';
+is payout( 7, 19 ), -1, 'Spin 7 (8 Black), bet 19 pays -1';
+is payout( 7, 20 ), -1, 'Spin 7 (8 Black), bet 20 pays -1';
+is payout( 7, 21 ), -1, 'Spin 7 (8 Black), bet 21 pays -1';
+is payout( 7, 22 ), -1, 'Spin 7 (8 Black), bet 22 pays -1';
+is payout( 7, 23 ), -1, 'Spin 7 (8 Black), bet 23 pays -1';
+is payout( 7, 24 ), -1, 'Spin 7 (8 Black), bet 24 pays -1';
+is payout( 7, 25 ), -1, 'Spin 7 (8 Black), bet 25 pays -1';
+is payout( 7, 26 ), -1, 'Spin 7 (8 Black), bet 26 pays -1';
+is payout( 7, 27 ), -1, 'Spin 7 (8 Black), bet 27 pays -1';
+is payout( 7, 28 ), -1, 'Spin 7 (8 Black), bet 28 pays -1';
+is payout( 7, 29 ), -1, 'Spin 7 (8 Black), bet 29 pays -1';
+is payout( 7, 30 ), -1, 'Spin 7 (8 Black), bet 30 pays -1';
+is payout( 7, 31 ), -1, 'Spin 7 (8 Black), bet 31 pays -1';
+is payout( 7, 32 ), -1, 'Spin 7 (8 Black), bet 32 pays -1';
+is payout( 7, 33 ), -1, 'Spin 7 (8 Black), bet 33 pays -1';
+is payout( 7, 34 ), -1, 'Spin 7 (8 Black), bet 34 pays -1';
+is payout( 7, 35 ), -1, 'Spin 7 (8 Black), bet 35 pays -1';
+is payout( 7, 36 ), -1, 'Spin 7 (8 Black), bet 36 pays -1';
+is payout( 7, 37 ), 2, 'Spin 7 (8 Black), bet 37 pays 2';
+is payout( 7, 38 ), -1, 'Spin 7 (8 Black), bet 38 pays -1';
+is payout( 7, 39 ), -1, 'Spin 7 (8 Black), bet 39 pays -1';
+is payout( 7, 40 ), -1, 'Spin 7 (8 Black), bet 40 pays -1';
+is payout( 7, 41 ), 2, 'Spin 7 (8 Black), bet 41 pays 2';
+is payout( 7, 42 ), -1, 'Spin 7 (8 Black), bet 42 pays -1';
+is payout( 7, 43 ), 1, 'Spin 7 (8 Black), bet 43 pays 1';
+is payout( 7, 44 ), -1, 'Spin 7 (8 Black), bet 44 pays -1';
+is payout( 7, 45 ), 1, 'Spin 7 (8 Black), bet 45 pays 1';
+is payout( 7, 46 ), -1, 'Spin 7 (8 Black), bet 46 pays -1';
+is payout( 7, 47 ), -1, 'Spin 7 (8 Black), bet 47 pays -1';
+is payout( 7, 48 ), 1, 'Spin 7 (8 Black), bet 48 pays 1';
+is payout( 7, 49 ), -1, 'Spin 7 (8 Black), bet 49 pays -1';
+is payout( 7, 50 ), -1, 'Spin 7 (8 Black), bet 50 pays -1';
+is format_spin( 8 ), '9 Red', 'Spin 8 is 9 Red';
+is payout( 8, 1 ), -1, 'Spin 8 (9 Red), bet 1 pays -1';
+is payout( 8, 2 ), -1, 'Spin 8 (9 Red), bet 2 pays -1';
+is payout( 8, 3 ), -1, 'Spin 8 (9 Red), bet 3 pays -1';
+is payout( 8, 4 ), -1, 'Spin 8 (9 Red), bet 4 pays -1';
+is payout( 8, 5 ), -1, 'Spin 8 (9 Red), bet 5 pays -1';
+is payout( 8, 6 ), -1, 'Spin 8 (9 Red), bet 6 pays -1';
+is payout( 8, 7 ), -1, 'Spin 8 (9 Red), bet 7 pays -1';
+is payout( 8, 8 ), -1, 'Spin 8 (9 Red), bet 8 pays -1';
+is payout( 8, 9 ), 35, 'Spin 8 (9 Red), bet 9 pays 35';
+is payout( 8, 10 ), -1, 'Spin 8 (9 Red), bet 10 pays -1';
+is payout( 8, 11 ), -1, 'Spin 8 (9 Red), bet 11 pays -1';
+is payout( 8, 12 ), -1, 'Spin 8 (9 Red), bet 12 pays -1';
+is payout( 8, 13 ), -1, 'Spin 8 (9 Red), bet 13 pays -1';
+is payout( 8, 14 ), -1, 'Spin 8 (9 Red), bet 14 pays -1';
+is payout( 8, 15 ), -1, 'Spin 8 (9 Red), bet 15 pays -1';
+is payout( 8, 16 ), -1, 'Spin 8 (9 Red), bet 16 pays -1';
+is payout( 8, 17 ), -1, 'Spin 8 (9 Red), bet 17 pays -1';
+is payout( 8, 18 ), -1, 'Spin 8 (9 Red), bet 18 pays -1';
+is payout( 8, 19 ), -1, 'Spin 8 (9 Red), bet 19 pays -1';
+is payout( 8, 20 ), -1, 'Spin 8 (9 Red), bet 20 pays -1';
+is payout( 8, 21 ), -1, 'Spin 8 (9 Red), bet 21 pays -1';
+is payout( 8, 22 ), -1, 'Spin 8 (9 Red), bet 22 pays -1';
+is payout( 8, 23 ), -1, 'Spin 8 (9 Red), bet 23 pays -1';
+is payout( 8, 24 ), -1, 'Spin 8 (9 Red), bet 24 pays -1';
+is payout( 8, 25 ), -1, 'Spin 8 (9 Red), bet 25 pays -1';
+is payout( 8, 26 ), -1, 'Spin 8 (9 Red), bet 26 pays -1';
+is payout( 8, 27 ), -1, 'Spin 8 (9 Red), bet 27 pays -1';
+is payout( 8, 28 ), -1, 'Spin 8 (9 Red), bet 28 pays -1';
+is payout( 8, 29 ), -1, 'Spin 8 (9 Red), bet 29 pays -1';
+is payout( 8, 30 ), -1, 'Spin 8 (9 Red), bet 30 pays -1';
+is payout( 8, 31 ), -1, 'Spin 8 (9 Red), bet 31 pays -1';
+is payout( 8, 32 ), -1, 'Spin 8 (9 Red), bet 32 pays -1';
+is payout( 8, 33 ), -1, 'Spin 8 (9 Red), bet 33 pays -1';
+is payout( 8, 34 ), -1, 'Spin 8 (9 Red), bet 34 pays -1';
+is payout( 8, 35 ), -1, 'Spin 8 (9 Red), bet 35 pays -1';
+is payout( 8, 36 ), -1, 'Spin 8 (9 Red), bet 36 pays -1';
+is payout( 8, 37 ), 2, 'Spin 8 (9 Red), bet 37 pays 2';
+is payout( 8, 38 ), -1, 'Spin 8 (9 Red), bet 38 pays -1';
+is payout( 8, 39 ), -1, 'Spin 8 (9 Red), bet 39 pays -1';
+is payout( 8, 40 ), -1, 'Spin 8 (9 Red), bet 40 pays -1';
+is payout( 8, 41 ), -1, 'Spin 8 (9 Red), bet 41 pays -1';
+is payout( 8, 42 ), 2, 'Spin 8 (9 Red), bet 42 pays 2';
+is payout( 8, 43 ), 1, 'Spin 8 (9 Red), bet 43 pays 1';
+is payout( 8, 44 ), -1, 'Spin 8 (9 Red), bet 44 pays -1';
+is payout( 8, 45 ), -1, 'Spin 8 (9 Red), bet 45 pays -1';
+is payout( 8, 46 ), 1, 'Spin 8 (9 Red), bet 46 pays 1';
+is payout( 8, 47 ), 1, 'Spin 8 (9 Red), bet 47 pays 1';
+is payout( 8, 48 ), -1, 'Spin 8 (9 Red), bet 48 pays -1';
+is payout( 8, 49 ), -1, 'Spin 8 (9 Red), bet 49 pays -1';
+is payout( 8, 50 ), -1, 'Spin 8 (9 Red), bet 50 pays -1';
+is format_spin( 9 ), '10 Black', 'Spin 9 is 10 Black';
+is payout( 9, 1 ), -1, 'Spin 9 (10 Black), bet 1 pays -1';
+is payout( 9, 2 ), -1, 'Spin 9 (10 Black), bet 2 pays -1';
+is payout( 9, 3 ), -1, 'Spin 9 (10 Black), bet 3 pays -1';
+is payout( 9, 4 ), -1, 'Spin 9 (10 Black), bet 4 pays -1';
+is payout( 9, 5 ), -1, 'Spin 9 (10 Black), bet 5 pays -1';
+is payout( 9, 6 ), -1, 'Spin 9 (10 Black), bet 6 pays -1';
+is payout( 9, 7 ), -1, 'Spin 9 (10 Black), bet 7 pays -1';
+is payout( 9, 8 ), -1, 'Spin 9 (10 Black), bet 8 pays -1';
+is payout( 9, 9 ), -1, 'Spin 9 (10 Black), bet 9 pays -1';
+is payout( 9, 10 ), 35, 'Spin 9 (10 Black), bet 10 pays 35';
+is payout( 9, 11 ), -1, 'Spin 9 (10 Black), bet 11 pays -1';
+is payout( 9, 12 ), -1, 'Spin 9 (10 Black), bet 12 pays -1';
+is payout( 9, 13 ), -1, 'Spin 9 (10 Black), bet 13 pays -1';
+is payout( 9, 14 ), -1, 'Spin 9 (10 Black), bet 14 pays -1';
+is payout( 9, 15 ), -1, 'Spin 9 (10 Black), bet 15 pays -1';
+is payout( 9, 16 ), -1, 'Spin 9 (10 Black), bet 16 pays -1';
+is payout( 9, 17 ), -1, 'Spin 9 (10 Black), bet 17 pays -1';
+is payout( 9, 18 ), -1, 'Spin 9 (10 Black), bet 18 pays -1';
+is payout( 9, 19 ), -1, 'Spin 9 (10 Black), bet 19 pays -1';
+is payout( 9, 20 ), -1, 'Spin 9 (10 Black), bet 20 pays -1';
+is payout( 9, 21 ), -1, 'Spin 9 (10 Black), bet 21 pays -1';
+is payout( 9, 22 ), -1, 'Spin 9 (10 Black), bet 22 pays -1';
+is payout( 9, 23 ), -1, 'Spin 9 (10 Black), bet 23 pays -1';
+is payout( 9, 24 ), -1, 'Spin 9 (10 Black), bet 24 pays -1';
+is payout( 9, 25 ), -1, 'Spin 9 (10 Black), bet 25 pays -1';
+is payout( 9, 26 ), -1, 'Spin 9 (10 Black), bet 26 pays -1';
+is payout( 9, 27 ), -1, 'Spin 9 (10 Black), bet 27 pays -1';
+is payout( 9, 28 ), -1, 'Spin 9 (10 Black), bet 28 pays -1';
+is payout( 9, 29 ), -1, 'Spin 9 (10 Black), bet 29 pays -1';
+is payout( 9, 30 ), -1, 'Spin 9 (10 Black), bet 30 pays -1';
+is payout( 9, 31 ), -1, 'Spin 9 (10 Black), bet 31 pays -1';
+is payout( 9, 32 ), -1, 'Spin 9 (10 Black), bet 32 pays -1';
+is payout( 9, 33 ), -1, 'Spin 9 (10 Black), bet 33 pays -1';
+is payout( 9, 34 ), -1, 'Spin 9 (10 Black), bet 34 pays -1';
+is payout( 9, 35 ), -1, 'Spin 9 (10 Black), bet 35 pays -1';
+is payout( 9, 36 ), -1, 'Spin 9 (10 Black), bet 36 pays -1';
+is payout( 9, 37 ), 2, 'Spin 9 (10 Black), bet 37 pays 2';
+is payout( 9, 38 ), -1, 'Spin 9 (10 Black), bet 38 pays -1';
+is payout( 9, 39 ), -1, 'Spin 9 (10 Black), bet 39 pays -1';
+is payout( 9, 40 ), 2, 'Spin 9 (10 Black), bet 40 pays 2';
+is payout( 9, 41 ), -1, 'Spin 9 (10 Black), bet 41 pays -1';
+is payout( 9, 42 ), -1, 'Spin 9 (10 Black), bet 42 pays -1';
+is payout( 9, 43 ), 1, 'Spin 9 (10 Black), bet 43 pays 1';
+is payout( 9, 44 ), -1, 'Spin 9 (10 Black), bet 44 pays -1';
+is payout( 9, 45 ), 1, 'Spin 9 (10 Black), bet 45 pays 1';
+is payout( 9, 46 ), -1, 'Spin 9 (10 Black), bet 46 pays -1';
+is payout( 9, 47 ), -1, 'Spin 9 (10 Black), bet 47 pays -1';
+is payout( 9, 48 ), 1, 'Spin 9 (10 Black), bet 48 pays 1';
+is payout( 9, 49 ), -1, 'Spin 9 (10 Black), bet 49 pays -1';
+is payout( 9, 50 ), -1, 'Spin 9 (10 Black), bet 50 pays -1';
+is format_spin( 10 ), '11 Black', 'Spin 10 is 11 Black';
+is payout( 10, 1 ), -1, 'Spin 10 (11 Black), bet 1 pays -1';
+is payout( 10, 2 ), -1, 'Spin 10 (11 Black), bet 2 pays -1';
+is payout( 10, 3 ), -1, 'Spin 10 (11 Black), bet 3 pays -1';
+is payout( 10, 4 ), -1, 'Spin 10 (11 Black), bet 4 pays -1';
+is payout( 10, 5 ), -1, 'Spin 10 (11 Black), bet 5 pays -1';
+is payout( 10, 6 ), -1, 'Spin 10 (11 Black), bet 6 pays -1';
+is payout( 10, 7 ), -1, 'Spin 10 (11 Black), bet 7 pays -1';
+is payout( 10, 8 ), -1, 'Spin 10 (11 Black), bet 8 pays -1';
+is payout( 10, 9 ), -1, 'Spin 10 (11 Black), bet 9 pays -1';
+is payout( 10, 10 ), -1, 'Spin 10 (11 Black), bet 10 pays -1';
+is payout( 10, 11 ), 35, 'Spin 10 (11 Black), bet 11 pays 35';
+is payout( 10, 12 ), -1, 'Spin 10 (11 Black), bet 12 pays -1';
+is payout( 10, 13 ), -1, 'Spin 10 (11 Black), bet 13 pays -1';
+is payout( 10, 14 ), -1, 'Spin 10 (11 Black), bet 14 pays -1';
+is payout( 10, 15 ), -1, 'Spin 10 (11 Black), bet 15 pays -1';
+is payout( 10, 16 ), -1, 'Spin 10 (11 Black), bet 16 pays -1';
+is payout( 10, 17 ), -1, 'Spin 10 (11 Black), bet 17 pays -1';
+is payout( 10, 18 ), -1, 'Spin 10 (11 Black), bet 18 pays -1';
+is payout( 10, 19 ), -1, 'Spin 10 (11 Black), bet 19 pays -1';
+is payout( 10, 20 ), -1, 'Spin 10 (11 Black), bet 20 pays -1';
+is payout( 10, 21 ), -1, 'Spin 10 (11 Black), bet 21 pays -1';
+is payout( 10, 22 ), -1, 'Spin 10 (11 Black), bet 22 pays -1';
+is payout( 10, 23 ), -1, 'Spin 10 (11 Black), bet 23 pays -1';
+is payout( 10, 24 ), -1, 'Spin 10 (11 Black), bet 24 pays -1';
+is payout( 10, 25 ), -1, 'Spin 10 (11 Black), bet 25 pays -1';
+is payout( 10, 26 ), -1, 'Spin 10 (11 Black), bet 26 pays -1';
+is payout( 10, 27 ), -1, 'Spin 10 (11 Black), bet 27 pays -1';
+is payout( 10, 28 ), -1, 'Spin 10 (11 Black), bet 28 pays -1';
+is payout( 10, 29 ), -1, 'Spin 10 (11 Black), bet 29 pays -1';
+is payout( 10, 30 ), -1, 'Spin 10 (11 Black), bet 30 pays -1';
+is payout( 10, 31 ), -1, 'Spin 10 (11 Black), bet 31 pays -1';
+is payout( 10, 32 ), -1, 'Spin 10 (11 Black), bet 32 pays -1';
+is payout( 10, 33 ), -1, 'Spin 10 (11 Black), bet 33 pays -1';
+is payout( 10, 34 ), -1, 'Spin 10 (11 Black), bet 34 pays -1';
+is payout( 10, 35 ), -1, 'Spin 10 (11 Black), bet 35 pays -1';
+is payout( 10, 36 ), -1, 'Spin 10 (11 Black), bet 36 pays -1';
+is payout( 10, 37 ), 2, 'Spin 10 (11 Black), bet 37 pays 2';
+is payout( 10, 38 ), -1, 'Spin 10 (11 Black), bet 38 pays -1';
+is payout( 10, 39 ), -1, 'Spin 10 (11 Black), bet 39 pays -1';
+is payout( 10, 40 ), -1, 'Spin 10 (11 Black), bet 40 pays -1';
+is payout( 10, 41 ), 2, 'Spin 10 (11 Black), bet 41 pays 2';
+is payout( 10, 42 ), -1, 'Spin 10 (11 Black), bet 42 pays -1';
+is payout( 10, 43 ), 1, 'Spin 10 (11 Black), bet 43 pays 1';
+is payout( 10, 44 ), -1, 'Spin 10 (11 Black), bet 44 pays -1';
+is payout( 10, 45 ), -1, 'Spin 10 (11 Black), bet 45 pays -1';
+is payout( 10, 46 ), 1, 'Spin 10 (11 Black), bet 46 pays 1';
+is payout( 10, 47 ), -1, 'Spin 10 (11 Black), bet 47 pays -1';
+is payout( 10, 48 ), 1, 'Spin 10 (11 Black), bet 48 pays 1';
+is payout( 10, 49 ), -1, 'Spin 10 (11 Black), bet 49 pays -1';
+is payout( 10, 50 ), -1, 'Spin 10 (11 Black), bet 50 pays -1';
+is format_spin( 11 ), '12 Red', 'Spin 11 is 12 Red';
+is payout( 11, 1 ), -1, 'Spin 11 (12 Red), bet 1 pays -1';
+is payout( 11, 2 ), -1, 'Spin 11 (12 Red), bet 2 pays -1';
+is payout( 11, 3 ), -1, 'Spin 11 (12 Red), bet 3 pays -1';
+is payout( 11, 4 ), -1, 'Spin 11 (12 Red), bet 4 pays -1';
+is payout( 11, 5 ), -1, 'Spin 11 (12 Red), bet 5 pays -1';
+is payout( 11, 6 ), -1, 'Spin 11 (12 Red), bet 6 pays -1';
+is payout( 11, 7 ), -1, 'Spin 11 (12 Red), bet 7 pays -1';
+is payout( 11, 8 ), -1, 'Spin 11 (12 Red), bet 8 pays -1';
+is payout( 11, 9 ), -1, 'Spin 11 (12 Red), bet 9 pays -1';
+is payout( 11, 10 ), -1, 'Spin 11 (12 Red), bet 10 pays -1';
+is payout( 11, 11 ), -1, 'Spin 11 (12 Red), bet 11 pays -1';
+is payout( 11, 12 ), 35, 'Spin 11 (12 Red), bet 12 pays 35';
+is payout( 11, 13 ), -1, 'Spin 11 (12 Red), bet 13 pays -1';
+is payout( 11, 14 ), -1, 'Spin 11 (12 Red), bet 14 pays -1';
+is payout( 11, 15 ), -1, 'Spin 11 (12 Red), bet 15 pays -1';
+is payout( 11, 16 ), -1, 'Spin 11 (12 Red), bet 16 pays -1';
+is payout( 11, 17 ), -1, 'Spin 11 (12 Red), bet 17 pays -1';
+is payout( 11, 18 ), -1, 'Spin 11 (12 Red), bet 18 pays -1';
+is payout( 11, 19 ), -1, 'Spin 11 (12 Red), bet 19 pays -1';
+is payout( 11, 20 ), -1, 'Spin 11 (12 Red), bet 20 pays -1';
+is payout( 11, 21 ), -1, 'Spin 11 (12 Red), bet 21 pays -1';
+is payout( 11, 22 ), -1, 'Spin 11 (12 Red), bet 22 pays -1';
+is payout( 11, 23 ), -1, 'Spin 11 (12 Red), bet 23 pays -1';
+is payout( 11, 24 ), -1, 'Spin 11 (12 Red), bet 24 pays -1';
+is payout( 11, 25 ), -1, 'Spin 11 (12 Red), bet 25 pays -1';
+is payout( 11, 26 ), -1, 'Spin 11 (12 Red), bet 26 pays -1';
+is payout( 11, 27 ), -1, 'Spin 11 (12 Red), bet 27 pays -1';
+is payout( 11, 28 ), -1, 'Spin 11 (12 Red), bet 28 pays -1';
+is payout( 11, 29 ), -1, 'Spin 11 (12 Red), bet 29 pays -1';
+is payout( 11, 30 ), -1, 'Spin 11 (12 Red), bet 30 pays -1';
+is payout( 11, 31 ), -1, 'Spin 11 (12 Red), bet 31 pays -1';
+is payout( 11, 32 ), -1, 'Spin 11 (12 Red), bet 32 pays -1';
+is payout( 11, 33 ), -1, 'Spin 11 (12 Red), bet 33 pays -1';
+is payout( 11, 34 ), -1, 'Spin 11 (12 Red), bet 34 pays -1';
+is payout( 11, 35 ), -1, 'Spin 11 (12 Red), bet 35 pays -1';
+is payout( 11, 36 ), -1, 'Spin 11 (12 Red), bet 36 pays -1';
+is payout( 11, 37 ), 2, 'Spin 11 (12 Red), bet 37 pays 2';
+is payout( 11, 38 ), -1, 'Spin 11 (12 Red), bet 38 pays -1';
+is payout( 11, 39 ), -1, 'Spin 11 (12 Red), bet 39 pays -1';
+is payout( 11, 40 ), -1, 'Spin 11 (12 Red), bet 40 pays -1';
+is payout( 11, 41 ), -1, 'Spin 11 (12 Red), bet 41 pays -1';
+is payout( 11, 42 ), 2, 'Spin 11 (12 Red), bet 42 pays 2';
+is payout( 11, 43 ), 1, 'Spin 11 (12 Red), bet 43 pays 1';
+is payout( 11, 44 ), -1, 'Spin 11 (12 Red), bet 44 pays -1';
+is payout( 11, 45 ), 1, 'Spin 11 (12 Red), bet 45 pays 1';
+is payout( 11, 46 ), -1, 'Spin 11 (12 Red), bet 46 pays -1';
+is payout( 11, 47 ), 1, 'Spin 11 (12 Red), bet 47 pays 1';
+is payout( 11, 48 ), -1, 'Spin 11 (12 Red), bet 48 pays -1';
+is payout( 11, 49 ), -1, 'Spin 11 (12 Red), bet 49 pays -1';
+is payout( 11, 50 ), -1, 'Spin 11 (12 Red), bet 50 pays -1';
+is format_spin( 12 ), '13 Black', 'Spin 12 is 13 Black';
+is payout( 12, 1 ), -1, 'Spin 12 (13 Black), bet 1 pays -1';
+is payout( 12, 2 ), -1, 'Spin 12 (13 Black), bet 2 pays -1';
+is payout( 12, 3 ), -1, 'Spin 12 (13 Black), bet 3 pays -1';
+is payout( 12, 4 ), -1, 'Spin 12 (13 Black), bet 4 pays -1';
+is payout( 12, 5 ), -1, 'Spin 12 (13 Black), bet 5 pays -1';
+is payout( 12, 6 ), -1, 'Spin 12 (13 Black), bet 6 pays -1';
+is payout( 12, 7 ), -1, 'Spin 12 (13 Black), bet 7 pays -1';
+is payout( 12, 8 ), -1, 'Spin 12 (13 Black), bet 8 pays -1';
+is payout( 12, 9 ), -1, 'Spin 12 (13 Black), bet 9 pays -1';
+is payout( 12, 10 ), -1, 'Spin 12 (13 Black), bet 10 pays -1';
+is payout( 12, 11 ), -1, 'Spin 12 (13 Black), bet 11 pays -1';
+is payout( 12, 12 ), -1, 'Spin 12 (13 Black), bet 12 pays -1';
+is payout( 12, 13 ), 35, 'Spin 12 (13 Black), bet 13 pays 35';
+is payout( 12, 14 ), -1, 'Spin 12 (13 Black), bet 14 pays -1';
+is payout( 12, 15 ), -1, 'Spin 12 (13 Black), bet 15 pays -1';
+is payout( 12, 16 ), -1, 'Spin 12 (13 Black), bet 16 pays -1';
+is payout( 12, 17 ), -1, 'Spin 12 (13 Black), bet 17 pays -1';
+is payout( 12, 18 ), -1, 'Spin 12 (13 Black), bet 18 pays -1';
+is payout( 12, 19 ), -1, 'Spin 12 (13 Black), bet 19 pays -1';
+is payout( 12, 20 ), -1, 'Spin 12 (13 Black), bet 20 pays -1';
+is payout( 12, 21 ), -1, 'Spin 12 (13 Black), bet 21 pays -1';
+is payout( 12, 22 ), -1, 'Spin 12 (13 Black), bet 22 pays -1';
+is payout( 12, 23 ), -1, 'Spin 12 (13 Black), bet 23 pays -1';
+is payout( 12, 24 ), -1, 'Spin 12 (13 Black), bet 24 pays -1';
+is payout( 12, 25 ), -1, 'Spin 12 (13 Black), bet 25 pays -1';
+is payout( 12, 26 ), -1, 'Spin 12 (13 Black), bet 26 pays -1';
+is payout( 12, 27 ), -1, 'Spin 12 (13 Black), bet 27 pays -1';
+is payout( 12, 28 ), -1, 'Spin 12 (13 Black), bet 28 pays -1';
+is payout( 12, 29 ), -1, 'Spin 12 (13 Black), bet 29 pays -1';
+is payout( 12, 30 ), -1, 'Spin 12 (13 Black), bet 30 pays -1';
+is payout( 12, 31 ), -1, 'Spin 12 (13 Black), bet 31 pays -1';
+is payout( 12, 32 ), -1, 'Spin 12 (13 Black), bet 32 pays -1';
+is payout( 12, 33 ), -1, 'Spin 12 (13 Black), bet 33 pays -1';
+is payout( 12, 34 ), -1, 'Spin 12 (13 Black), bet 34 pays -1';
+is payout( 12, 35 ), -1, 'Spin 12 (13 Black), bet 35 pays -1';
+is payout( 12, 36 ), -1, 'Spin 12 (13 Black), bet 36 pays -1';
+is payout( 12, 37 ), -1, 'Spin 12 (13 Black), bet 37 pays -1';
+is payout( 12, 38 ), 2, 'Spin 12 (13 Black), bet 38 pays 2';
+is payout( 12, 39 ), -1, 'Spin 12 (13 Black), bet 39 pays -1';
+is payout( 12, 40 ), 2, 'Spin 12 (13 Black), bet 40 pays 2';
+is payout( 12, 41 ), -1, 'Spin 12 (13 Black), bet 41 pays -1';
+is payout( 12, 42 ), -1, 'Spin 12 (13 Black), bet 42 pays -1';
+is payout( 12, 43 ), 1, 'Spin 12 (13 Black), bet 43 pays 1';
+is payout( 12, 44 ), -1, 'Spin 12 (13 Black), bet 44 pays -1';
+is payout( 12, 45 ), -1, 'Spin 12 (13 Black), bet 45 pays -1';
+is payout( 12, 46 ), 1, 'Spin 12 (13 Black), bet 46 pays 1';
+is payout( 12, 47 ), -1, 'Spin 12 (13 Black), bet 47 pays -1';
+is payout( 12, 48 ), 1, 'Spin 12 (13 Black), bet 48 pays 1';
+is payout( 12, 49 ), -1, 'Spin 12 (13 Black), bet 49 pays -1';
+is payout( 12, 50 ), -1, 'Spin 12 (13 Black), bet 50 pays -1';
+is format_spin( 13 ), '14 Red', 'Spin 13 is 14 Red';
+is payout( 13, 1 ), -1, 'Spin 13 (14 Red), bet 1 pays -1';
+is payout( 13, 2 ), -1, 'Spin 13 (14 Red), bet 2 pays -1';
+is payout( 13, 3 ), -1, 'Spin 13 (14 Red), bet 3 pays -1';
+is payout( 13, 4 ), -1, 'Spin 13 (14 Red), bet 4 pays -1';
+is payout( 13, 5 ), -1, 'Spin 13 (14 Red), bet 5 pays -1';
+is payout( 13, 6 ), -1, 'Spin 13 (14 Red), bet 6 pays -1';
+is payout( 13, 7 ), -1, 'Spin 13 (14 Red), bet 7 pays -1';
+is payout( 13, 8 ), -1, 'Spin 13 (14 Red), bet 8 pays -1';
+is payout( 13, 9 ), -1, 'Spin 13 (14 Red), bet 9 pays -1';
+is payout( 13, 10 ), -1, 'Spin 13 (14 Red), bet 10 pays -1';
+is payout( 13, 11 ), -1, 'Spin 13 (14 Red), bet 11 pays -1';
+is payout( 13, 12 ), -1, 'Spin 13 (14 Red), bet 12 pays -1';
+is payout( 13, 13 ), -1, 'Spin 13 (14 Red), bet 13 pays -1';
+is payout( 13, 14 ), 35, 'Spin 13 (14 Red), bet 14 pays 35';
+is payout( 13, 15 ), -1, 'Spin 13 (14 Red), bet 15 pays -1';
+is payout( 13, 16 ), -1, 'Spin 13 (14 Red), bet 16 pays -1';
+is payout( 13, 17 ), -1, 'Spin 13 (14 Red), bet 17 pays -1';
+is payout( 13, 18 ), -1, 'Spin 13 (14 Red), bet 18 pays -1';
+is payout( 13, 19 ), -1, 'Spin 13 (14 Red), bet 19 pays -1';
+is payout( 13, 20 ), -1, 'Spin 13 (14 Red), bet 20 pays -1';
+is payout( 13, 21 ), -1, 'Spin 13 (14 Red), bet 21 pays -1';
+is payout( 13, 22 ), -1, 'Spin 13 (14 Red), bet 22 pays -1';
+is payout( 13, 23 ), -1, 'Spin 13 (14 Red), bet 23 pays -1';
+is payout( 13, 24 ), -1, 'Spin 13 (14 Red), bet 24 pays -1';
+is payout( 13, 25 ), -1, 'Spin 13 (14 Red), bet 25 pays -1';
+is payout( 13, 26 ), -1, 'Spin 13 (14 Red), bet 26 pays -1';
+is payout( 13, 27 ), -1, 'Spin 13 (14 Red), bet 27 pays -1';
+is payout( 13, 28 ), -1, 'Spin 13 (14 Red), bet 28 pays -1';
+is payout( 13, 29 ), -1, 'Spin 13 (14 Red), bet 29 pays -1';
+is payout( 13, 30 ), -1, 'Spin 13 (14 Red), bet 30 pays -1';
+is payout( 13, 31 ), -1, 'Spin 13 (14 Red), bet 31 pays -1';
+is payout( 13, 32 ), -1, 'Spin 13 (14 Red), bet 32 pays -1';
+is payout( 13, 33 ), -1, 'Spin 13 (14 Red), bet 33 pays -1';
+is payout( 13, 34 ), -1, 'Spin 13 (14 Red), bet 34 pays -1';
+is payout( 13, 35 ), -1, 'Spin 13 (14 Red), bet 35 pays -1';
+is payout( 13, 36 ), -1, 'Spin 13 (14 Red), bet 36 pays -1';
+is payout( 13, 37 ), -1, 'Spin 13 (14 Red), bet 37 pays -1';
+is payout( 13, 38 ), 2, 'Spin 13 (14 Red), bet 38 pays 2';
+is payout( 13, 39 ), -1, 'Spin 13 (14 Red), bet 39 pays -1';
+is payout( 13, 40 ), -1, 'Spin 13 (14 Red), bet 40 pays -1';
+is payout( 13, 41 ), 2, 'Spin 13 (14 Red), bet 41 pays 2';
+is payout( 13, 42 ), -1, 'Spin 13 (14 Red), bet 42 pays -1';
+is payout( 13, 43 ), 1, 'Spin 13 (14 Red), bet 43 pays 1';
+is payout( 13, 44 ), -1, 'Spin 13 (14 Red), bet 44 pays -1';
+is payout( 13, 45 ), 1, 'Spin 13 (14 Red), bet 45 pays 1';
+is payout( 13, 46 ), -1, 'Spin 13 (14 Red), bet 46 pays -1';
+is payout( 13, 47 ), 1, 'Spin 13 (14 Red), bet 47 pays 1';
+is payout( 13, 48 ), -1, 'Spin 13 (14 Red), bet 48 pays -1';
+is payout( 13, 49 ), -1, 'Spin 13 (14 Red), bet 49 pays -1';
+is payout( 13, 50 ), -1, 'Spin 13 (14 Red), bet 50 pays -1';
+is format_spin( 14 ), '15 Black', 'Spin 14 is 15 Black';
+is payout( 14, 1 ), -1, 'Spin 14 (15 Black), bet 1 pays -1';
+is payout( 14, 2 ), -1, 'Spin 14 (15 Black), bet 2 pays -1';
+is payout( 14, 3 ), -1, 'Spin 14 (15 Black), bet 3 pays -1';
+is payout( 14, 4 ), -1, 'Spin 14 (15 Black), bet 4 pays -1';
+is payout( 14, 5 ), -1, 'Spin 14 (15 Black), bet 5 pays -1';
+is payout( 14, 6 ), -1, 'Spin 14 (15 Black), bet 6 pays -1';
+is payout( 14, 7 ), -1, 'Spin 14 (15 Black), bet 7 pays -1';
+is payout( 14, 8 ), -1, 'Spin 14 (15 Black), bet 8 pays -1';
+is payout( 14, 9 ), -1, 'Spin 14 (15 Black), bet 9 pays -1';
+is payout( 14, 10 ), -1, 'Spin 14 (15 Black), bet 10 pays -1';
+is payout( 14, 11 ), -1, 'Spin 14 (15 Black), bet 11 pays -1';
+is payout( 14, 12 ), -1, 'Spin 14 (15 Black), bet 12 pays -1';
+is payout( 14, 13 ), -1, 'Spin 14 (15 Black), bet 13 pays -1';
+is payout( 14, 14 ), -1, 'Spin 14 (15 Black), bet 14 pays -1';
+is payout( 14, 15 ), 35, 'Spin 14 (15 Black), bet 15 pays 35';
+is payout( 14, 16 ), -1, 'Spin 14 (15 Black), bet 16 pays -1';
+is payout( 14, 17 ), -1, 'Spin 14 (15 Black), bet 17 pays -1';
+is payout( 14, 18 ), -1, 'Spin 14 (15 Black), bet 18 pays -1';
+is payout( 14, 19 ), -1, 'Spin 14 (15 Black), bet 19 pays -1';
+is payout( 14, 20 ), -1, 'Spin 14 (15 Black), bet 20 pays -1';
+is payout( 14, 21 ), -1, 'Spin 14 (15 Black), bet 21 pays -1';
+is payout( 14, 22 ), -1, 'Spin 14 (15 Black), bet 22 pays -1';
+is payout( 14, 23 ), -1, 'Spin 14 (15 Black), bet 23 pays -1';
+is payout( 14, 24 ), -1, 'Spin 14 (15 Black), bet 24 pays -1';
+is payout( 14, 25 ), -1, 'Spin 14 (15 Black), bet 25 pays -1';
+is payout( 14, 26 ), -1, 'Spin 14 (15 Black), bet 26 pays -1';
+is payout( 14, 27 ), -1, 'Spin 14 (15 Black), bet 27 pays -1';
+is payout( 14, 28 ), -1, 'Spin 14 (15 Black), bet 28 pays -1';
+is payout( 14, 29 ), -1, 'Spin 14 (15 Black), bet 29 pays -1';
+is payout( 14, 30 ), -1, 'Spin 14 (15 Black), bet 30 pays -1';
+is payout( 14, 31 ), -1, 'Spin 14 (15 Black), bet 31 pays -1';
+is payout( 14, 32 ), -1, 'Spin 14 (15 Black), bet 32 pays -1';
+is payout( 14, 33 ), -1, 'Spin 14 (15 Black), bet 33 pays -1';
+is payout( 14, 34 ), -1, 'Spin 14 (15 Black), bet 34 pays -1';
+is payout( 14, 35 ), -1, 'Spin 14 (15 Black), bet 35 pays -1';
+is payout( 14, 36 ), -1, 'Spin 14 (15 Black), bet 36 pays -1';
+is payout( 14, 37 ), -1, 'Spin 14 (15 Black), bet 37 pays -1';
+is payout( 14, 38 ), 2, 'Spin 14 (15 Black), bet 38 pays 2';
+is payout( 14, 39 ), -1, 'Spin 14 (15 Black), bet 39 pays -1';
+is payout( 14, 40 ), -1, 'Spin 14 (15 Black), bet 40 pays -1';
+is payout( 14, 41 ), -1, 'Spin 14 (15 Black), bet 41 pays -1';
+is payout( 14, 42 ), 2, 'Spin 14 (15 Black), bet 42 pays 2';
+is payout( 14, 43 ), 1, 'Spin 14 (15 Black), bet 43 pays 1';
+is payout( 14, 44 ), -1, 'Spin 14 (15 Black), bet 44 pays -1';
+is payout( 14, 45 ), -1, 'Spin 14 (15 Black), bet 45 pays -1';
+is payout( 14, 46 ), 1, 'Spin 14 (15 Black), bet 46 pays 1';
+is payout( 14, 47 ), -1, 'Spin 14 (15 Black), bet 47 pays -1';
+is payout( 14, 48 ), 1, 'Spin 14 (15 Black), bet 48 pays 1';
+is payout( 14, 49 ), -1, 'Spin 14 (15 Black), bet 49 pays -1';
+is payout( 14, 50 ), -1, 'Spin 14 (15 Black), bet 50 pays -1';
+is format_spin( 15 ), '16 Red', 'Spin 15 is 16 Red';
+is payout( 15, 1 ), -1, 'Spin 15 (16 Red), bet 1 pays -1';
+is payout( 15, 2 ), -1, 'Spin 15 (16 Red), bet 2 pays -1';
+is payout( 15, 3 ), -1, 'Spin 15 (16 Red), bet 3 pays -1';
+is payout( 15, 4 ), -1, 'Spin 15 (16 Red), bet 4 pays -1';
+is payout( 15, 5 ), -1, 'Spin 15 (16 Red), bet 5 pays -1';
+is payout( 15, 6 ), -1, 'Spin 15 (16 Red), bet 6 pays -1';
+is payout( 15, 7 ), -1, 'Spin 15 (16 Red), bet 7 pays -1';
+is payout( 15, 8 ), -1, 'Spin 15 (16 Red), bet 8 pays -1';
+is payout( 15, 9 ), -1, 'Spin 15 (16 Red), bet 9 pays -1';
+is payout( 15, 10 ), -1, 'Spin 15 (16 Red), bet 10 pays -1';
+is payout( 15, 11 ), -1, 'Spin 15 (16 Red), bet 11 pays -1';
+is payout( 15, 12 ), -1, 'Spin 15 (16 Red), bet 12 pays -1';
+is payout( 15, 13 ), -1, 'Spin 15 (16 Red), bet 13 pays -1';
+is payout( 15, 14 ), -1, 'Spin 15 (16 Red), bet 14 pays -1';
+is payout( 15, 15 ), -1, 'Spin 15 (16 Red), bet 15 pays -1';
+is payout( 15, 16 ), 35, 'Spin 15 (16 Red), bet 16 pays 35';
+is payout( 15, 17 ), -1, 'Spin 15 (16 Red), bet 17 pays -1';
+is payout( 15, 18 ), -1, 'Spin 15 (16 Red), bet 18 pays -1';
+is payout( 15, 19 ), -1, 'Spin 15 (16 Red), bet 19 pays -1';
+is payout( 15, 20 ), -1, 'Spin 15 (16 Red), bet 20 pays -1';
+is payout( 15, 21 ), -1, 'Spin 15 (16 Red), bet 21 pays -1';
+is payout( 15, 22 ), -1, 'Spin 15 (16 Red), bet 22 pays -1';
+is payout( 15, 23 ), -1, 'Spin 15 (16 Red), bet 23 pays -1';
+is payout( 15, 24 ), -1, 'Spin 15 (16 Red), bet 24 pays -1';
+is payout( 15, 25 ), -1, 'Spin 15 (16 Red), bet 25 pays -1';
+is payout( 15, 26 ), -1, 'Spin 15 (16 Red), bet 26 pays -1';
+is payout( 15, 27 ), -1, 'Spin 15 (16 Red), bet 27 pays -1';
+is payout( 15, 28 ), -1, 'Spin 15 (16 Red), bet 28 pays -1';
+is payout( 15, 29 ), -1, 'Spin 15 (16 Red), bet 29 pays -1';
+is payout( 15, 30 ), -1, 'Spin 15 (16 Red), bet 30 pays -1';
+is payout( 15, 31 ), -1, 'Spin 15 (16 Red), bet 31 pays -1';
+is payout( 15, 32 ), -1, 'Spin 15 (16 Red), bet 32 pays -1';
+is payout( 15, 33 ), -1, 'Spin 15 (16 Red), bet 33 pays -1';
+is payout( 15, 34 ), -1, 'Spin 15 (16 Red), bet 34 pays -1';
+is payout( 15, 35 ), -1, 'Spin 15 (16 Red), bet 35 pays -1';
+is payout( 15, 36 ), -1, 'Spin 15 (16 Red), bet 36 pays -1';
+is payout( 15, 37 ), -1, 'Spin 15 (16 Red), bet 37 pays -1';
+is payout( 15, 38 ), 2, 'Spin 15 (16 Red), bet 38 pays 2';
+is payout( 15, 39 ), -1, 'Spin 15 (16 Red), bet 39 pays -1';
+is payout( 15, 40 ), 2, 'Spin 15 (16 Red), bet 40 pays 2';
+is payout( 15, 41 ), -1, 'Spin 15 (16 Red), bet 41 pays -1';
+is payout( 15, 42 ), -1, 'Spin 15 (16 Red), bet 42 pays -1';
+is payout( 15, 43 ), 1, 'Spin 15 (16 Red), bet 43 pays 1';
+is payout( 15, 44 ), -1, 'Spin 15 (16 Red), bet 44 pays -1';
+is payout( 15, 45 ), 1, 'Spin 15 (16 Red), bet 45 pays 1';
+is payout( 15, 46 ), -1, 'Spin 15 (16 Red), bet 46 pays -1';
+is payout( 15, 47 ), 1, 'Spin 15 (16 Red), bet 47 pays 1';
+is payout( 15, 48 ), -1, 'Spin 15 (16 Red), bet 48 pays -1';
+is payout( 15, 49 ), -1, 'Spin 15 (16 Red), bet 49 pays -1';
+is payout( 15, 50 ), -1, 'Spin 15 (16 Red), bet 50 pays -1';
+is format_spin( 16 ), '17 Black', 'Spin 16 is 17 Black';
+is payout( 16, 1 ), -1, 'Spin 16 (17 Black), bet 1 pays -1';
+is payout( 16, 2 ), -1, 'Spin 16 (17 Black), bet 2 pays -1';
+is payout( 16, 3 ), -1, 'Spin 16 (17 Black), bet 3 pays -1';
+is payout( 16, 4 ), -1, 'Spin 16 (17 Black), bet 4 pays -1';
+is payout( 16, 5 ), -1, 'Spin 16 (17 Black), bet 5 pays -1';
+is payout( 16, 6 ), -1, 'Spin 16 (17 Black), bet 6 pays -1';
+is payout( 16, 7 ), -1, 'Spin 16 (17 Black), bet 7 pays -1';
+is payout( 16, 8 ), -1, 'Spin 16 (17 Black), bet 8 pays -1';
+is payout( 16, 9 ), -1, 'Spin 16 (17 Black), bet 9 pays -1';
+is payout( 16, 10 ), -1, 'Spin 16 (17 Black), bet 10 pays -1';
+is payout( 16, 11 ), -1, 'Spin 16 (17 Black), bet 11 pays -1';
+is payout( 16, 12 ), -1, 'Spin 16 (17 Black), bet 12 pays -1';
+is payout( 16, 13 ), -1, 'Spin 16 (17 Black), bet 13 pays -1';
+is payout( 16, 14 ), -1, 'Spin 16 (17 Black), bet 14 pays -1';
+is payout( 16, 15 ), -1, 'Spin 16 (17 Black), bet 15 pays -1';
+is payout( 16, 16 ), -1, 'Spin 16 (17 Black), bet 16 pays -1';
+is payout( 16, 17 ), 35, 'Spin 16 (17 Black), bet 17 pays 35';
+is payout( 16, 18 ), -1, 'Spin 16 (17 Black), bet 18 pays -1';
+is payout( 16, 19 ), -1, 'Spin 16 (17 Black), bet 19 pays -1';
+is payout( 16, 20 ), -1, 'Spin 16 (17 Black), bet 20 pays -1';
+is payout( 16, 21 ), -1, 'Spin 16 (17 Black), bet 21 pays -1';
+is payout( 16, 22 ), -1, 'Spin 16 (17 Black), bet 22 pays -1';
+is payout( 16, 23 ), -1, 'Spin 16 (17 Black), bet 23 pays -1';
+is payout( 16, 24 ), -1, 'Spin 16 (17 Black), bet 24 pays -1';
+is payout( 16, 25 ), -1, 'Spin 16 (17 Black), bet 25 pays -1';
+is payout( 16, 26 ), -1, 'Spin 16 (17 Black), bet 26 pays -1';
+is payout( 16, 27 ), -1, 'Spin 16 (17 Black), bet 27 pays -1';
+is payout( 16, 28 ), -1, 'Spin 16 (17 Black), bet 28 pays -1';
+is payout( 16, 29 ), -1, 'Spin 16 (17 Black), bet 29 pays -1';
+is payout( 16, 30 ), -1, 'Spin 16 (17 Black), bet 30 pays -1';
+is payout( 16, 31 ), -1, 'Spin 16 (17 Black), bet 31 pays -1';
+is payout( 16, 32 ), -1, 'Spin 16 (17 Black), bet 32 pays -1';
+is payout( 16, 33 ), -1, 'Spin 16 (17 Black), bet 33 pays -1';
+is payout( 16, 34 ), -1, 'Spin 16 (17 Black), bet 34 pays -1';
+is payout( 16, 35 ), -1, 'Spin 16 (17 Black), bet 35 pays -1';
+is payout( 16, 36 ), -1, 'Spin 16 (17 Black), bet 36 pays -1';
+is payout( 16, 37 ), -1, 'Spin 16 (17 Black), bet 37 pays -1';
+is payout( 16, 38 ), 2, 'Spin 16 (17 Black), bet 38 pays 2';
+is payout( 16, 39 ), -1, 'Spin 16 (17 Black), bet 39 pays -1';
+is payout( 16, 40 ), -1, 'Spin 16 (17 Black), bet 40 pays -1';
+is payout( 16, 41 ), 2, 'Spin 16 (17 Black), bet 41 pays 2';
+is payout( 16, 42 ), -1, 'Spin 16 (17 Black), bet 42 pays -1';
+is payout( 16, 43 ), 1, 'Spin 16 (17 Black), bet 43 pays 1';
+is payout( 16, 44 ), -1, 'Spin 16 (17 Black), bet 44 pays -1';
+is payout( 16, 45 ), -1, 'Spin 16 (17 Black), bet 45 pays -1';
+is payout( 16, 46 ), 1, 'Spin 16 (17 Black), bet 46 pays 1';
+is payout( 16, 47 ), -1, 'Spin 16 (17 Black), bet 47 pays -1';
+is payout( 16, 48 ), 1, 'Spin 16 (17 Black), bet 48 pays 1';
+is payout( 16, 49 ), -1, 'Spin 16 (17 Black), bet 49 pays -1';
+is payout( 16, 50 ), -1, 'Spin 16 (17 Black), bet 50 pays -1';
+is format_spin( 17 ), '18 Red', 'Spin 17 is 18 Red';
+is payout( 17, 1 ), -1, 'Spin 17 (18 Red), bet 1 pays -1';
+is payout( 17, 2 ), -1, 'Spin 17 (18 Red), bet 2 pays -1';
+is payout( 17, 3 ), -1, 'Spin 17 (18 Red), bet 3 pays -1';
+is payout( 17, 4 ), -1, 'Spin 17 (18 Red), bet 4 pays -1';
+is payout( 17, 5 ), -1, 'Spin 17 (18 Red), bet 5 pays -1';
+is payout( 17, 6 ), -1, 'Spin 17 (18 Red), bet 6 pays -1';
+is payout( 17, 7 ), -1, 'Spin 17 (18 Red), bet 7 pays -1';
+is payout( 17, 8 ), -1, 'Spin 17 (18 Red), bet 8 pays -1';
+is payout( 17, 9 ), -1, 'Spin 17 (18 Red), bet 9 pays -1';
+is payout( 17, 10 ), -1, 'Spin 17 (18 Red), bet 10 pays -1';
+is payout( 17, 11 ), -1, 'Spin 17 (18 Red), bet 11 pays -1';
+is payout( 17, 12 ), -1, 'Spin 17 (18 Red), bet 12 pays -1';
+is payout( 17, 13 ), -1, 'Spin 17 (18 Red), bet 13 pays -1';
+is payout( 17, 14 ), -1, 'Spin 17 (18 Red), bet 14 pays -1';
+is payout( 17, 15 ), -1, 'Spin 17 (18 Red), bet 15 pays -1';
+is payout( 17, 16 ), -1, 'Spin 17 (18 Red), bet 16 pays -1';
+is payout( 17, 17 ), -1, 'Spin 17 (18 Red), bet 17 pays -1';
+is payout( 17, 18 ), 35, 'Spin 17 (18 Red), bet 18 pays 35';
+is payout( 17, 19 ), -1, 'Spin 17 (18 Red), bet 19 pays -1';
+is payout( 17, 20 ), -1, 'Spin 17 (18 Red), bet 20 pays -1';
+is payout( 17, 21 ), -1, 'Spin 17 (18 Red), bet 21 pays -1';
+is payout( 17, 22 ), -1, 'Spin 17 (18 Red), bet 22 pays -1';
+is payout( 17, 23 ), -1, 'Spin 17 (18 Red), bet 23 pays -1';
+is payout( 17, 24 ), -1, 'Spin 17 (18 Red), bet 24 pays -1';
+is payout( 17, 25 ), -1, 'Spin 17 (18 Red), bet 25 pays -1';
+is payout( 17, 26 ), -1, 'Spin 17 (18 Red), bet 26 pays -1';
+is payout( 17, 27 ), -1, 'Spin 17 (18 Red), bet 27 pays -1';
+is payout( 17, 28 ), -1, 'Spin 17 (18 Red), bet 28 pays -1';
+is payout( 17, 29 ), -1, 'Spin 17 (18 Red), bet 29 pays -1';
+is payout( 17, 30 ), -1, 'Spin 17 (18 Red), bet 30 pays -1';
+is payout( 17, 31 ), -1, 'Spin 17 (18 Red), bet 31 pays -1';
+is payout( 17, 32 ), -1, 'Spin 17 (18 Red), bet 32 pays -1';
+is payout( 17, 33 ), -1, 'Spin 17 (18 Red), bet 33 pays -1';
+is payout( 17, 34 ), -1, 'Spin 17 (18 Red), bet 34 pays -1';
+is payout( 17, 35 ), -1, 'Spin 17 (18 Red), bet 35 pays -1';
+is payout( 17, 36 ), -1, 'Spin 17 (18 Red), bet 36 pays -1';
+is payout( 17, 37 ), -1, 'Spin 17 (18 Red), bet 37 pays -1';
+is payout( 17, 38 ), 2, 'Spin 17 (18 Red), bet 38 pays 2';
+is payout( 17, 39 ), -1, 'Spin 17 (18 Red), bet 39 pays -1';
+is payout( 17, 40 ), -1, 'Spin 17 (18 Red), bet 40 pays -1';
+is payout( 17, 41 ), -1, 'Spin 17 (18 Red), bet 41 pays -1';
+is payout( 17, 42 ), 2, 'Spin 17 (18 Red), bet 42 pays 2';
+is payout( 17, 43 ), 1, 'Spin 17 (18 Red), bet 43 pays 1';
+is payout( 17, 44 ), -1, 'Spin 17 (18 Red), bet 44 pays -1';
+is payout( 17, 45 ), 1, 'Spin 17 (18 Red), bet 45 pays 1';
+is payout( 17, 46 ), -1, 'Spin 17 (18 Red), bet 46 pays -1';
+is payout( 17, 47 ), 1, 'Spin 17 (18 Red), bet 47 pays 1';
+is payout( 17, 48 ), -1, 'Spin 17 (18 Red), bet 48 pays -1';
+is payout( 17, 49 ), -1, 'Spin 17 (18 Red), bet 49 pays -1';
+is payout( 17, 50 ), -1, 'Spin 17 (18 Red), bet 50 pays -1';
+is format_spin( 18 ), '19 Red', 'Spin 18 is 19 Red';
+is payout( 18, 1 ), -1, 'Spin 18 (19 Red), bet 1 pays -1';
+is payout( 18, 2 ), -1, 'Spin 18 (19 Red), bet 2 pays -1';
+is payout( 18, 3 ), -1, 'Spin 18 (19 Red), bet 3 pays -1';
+is payout( 18, 4 ), -1, 'Spin 18 (19 Red), bet 4 pays -1';
+is payout( 18, 5 ), -1, 'Spin 18 (19 Red), bet 5 pays -1';
+is payout( 18, 6 ), -1, 'Spin 18 (19 Red), bet 6 pays -1';
+is payout( 18, 7 ), -1, 'Spin 18 (19 Red), bet 7 pays -1';
+is payout( 18, 8 ), -1, 'Spin 18 (19 Red), bet 8 pays -1';
+is payout( 18, 9 ), -1, 'Spin 18 (19 Red), bet 9 pays -1';
+is payout( 18, 10 ), -1, 'Spin 18 (19 Red), bet 10 pays -1';
+is payout( 18, 11 ), -1, 'Spin 18 (19 Red), bet 11 pays -1';
+is payout( 18, 12 ), -1, 'Spin 18 (19 Red), bet 12 pays -1';
+is payout( 18, 13 ), -1, 'Spin 18 (19 Red), bet 13 pays -1';
+is payout( 18, 14 ), -1, 'Spin 18 (19 Red), bet 14 pays -1';
+is payout( 18, 15 ), -1, 'Spin 18 (19 Red), bet 15 pays -1';
+is payout( 18, 16 ), -1, 'Spin 18 (19 Red), bet 16 pays -1';
+is payout( 18, 17 ), -1, 'Spin 18 (19 Red), bet 17 pays -1';
+is payout( 18, 18 ), -1, 'Spin 18 (19 Red), bet 18 pays -1';
+is payout( 18, 19 ), 35, 'Spin 18 (19 Red), bet 19 pays 35';
+is payout( 18, 20 ), -1, 'Spin 18 (19 Red), bet 20 pays -1';
+is payout( 18, 21 ), -1, 'Spin 18 (19 Red), bet 21 pays -1';
+is payout( 18, 22 ), -1, 'Spin 18 (19 Red), bet 22 pays -1';
+is payout( 18, 23 ), -1, 'Spin 18 (19 Red), bet 23 pays -1';
+is payout( 18, 24 ), -1, 'Spin 18 (19 Red), bet 24 pays -1';
+is payout( 18, 25 ), -1, 'Spin 18 (19 Red), bet 25 pays -1';
+is payout( 18, 26 ), -1, 'Spin 18 (19 Red), bet 26 pays -1';
+is payout( 18, 27 ), -1, 'Spin 18 (19 Red), bet 27 pays -1';
+is payout( 18, 28 ), -1, 'Spin 18 (19 Red), bet 28 pays -1';
+is payout( 18, 29 ), -1, 'Spin 18 (19 Red), bet 29 pays -1';
+is payout( 18, 30 ), -1, 'Spin 18 (19 Red), bet 30 pays -1';
+is payout( 18, 31 ), -1, 'Spin 18 (19 Red), bet 31 pays -1';
+is payout( 18, 32 ), -1, 'Spin 18 (19 Red), bet 32 pays -1';
+is payout( 18, 33 ), -1, 'Spin 18 (19 Red), bet 33 pays -1';
+is payout( 18, 34 ), -1, 'Spin 18 (19 Red), bet 34 pays -1';
+is payout( 18, 35 ), -1, 'Spin 18 (19 Red), bet 35 pays -1';
+is payout( 18, 36 ), -1, 'Spin 18 (19 Red), bet 36 pays -1';
+is payout( 18, 37 ), -1, 'Spin 18 (19 Red), bet 37 pays -1';
+is payout( 18, 38 ), 2, 'Spin 18 (19 Red), bet 38 pays 2';
+is payout( 18, 39 ), -1, 'Spin 18 (19 Red), bet 39 pays -1';
+is payout( 18, 40 ), 2, 'Spin 18 (19 Red), bet 40 pays 2';
+is payout( 18, 41 ), -1, 'Spin 18 (19 Red), bet 41 pays -1';
+is payout( 18, 42 ), -1, 'Spin 18 (19 Red), bet 42 pays -1';
+is payout( 18, 43 ), -1, 'Spin 18 (19 Red), bet 43 pays -1';
+is payout( 18, 44 ), 1, 'Spin 18 (19 Red), bet 44 pays 1';
+is payout( 18, 45 ), -1, 'Spin 18 (19 Red), bet 45 pays -1';
+is payout( 18, 46 ), 1, 'Spin 18 (19 Red), bet 46 pays 1';
+is payout( 18, 47 ), 1, 'Spin 18 (19 Red), bet 47 pays 1';
+is payout( 18, 48 ), -1, 'Spin 18 (19 Red), bet 48 pays -1';
+is payout( 18, 49 ), -1, 'Spin 18 (19 Red), bet 49 pays -1';
+is payout( 18, 50 ), -1, 'Spin 18 (19 Red), bet 50 pays -1';
+is format_spin( 19 ), '20 Black', 'Spin 19 is 20 Black';
+is payout( 19, 1 ), -1, 'Spin 19 (20 Black), bet 1 pays -1';
+is payout( 19, 2 ), -1, 'Spin 19 (20 Black), bet 2 pays -1';
+is payout( 19, 3 ), -1, 'Spin 19 (20 Black), bet 3 pays -1';
+is payout( 19, 4 ), -1, 'Spin 19 (20 Black), bet 4 pays -1';
+is payout( 19, 5 ), -1, 'Spin 19 (20 Black), bet 5 pays -1';
+is payout( 19, 6 ), -1, 'Spin 19 (20 Black), bet 6 pays -1';
+is payout( 19, 7 ), -1, 'Spin 19 (20 Black), bet 7 pays -1';
+is payout( 19, 8 ), -1, 'Spin 19 (20 Black), bet 8 pays -1';
+is payout( 19, 9 ), -1, 'Spin 19 (20 Black), bet 9 pays -1';
+is payout( 19, 10 ), -1, 'Spin 19 (20 Black), bet 10 pays -1';
+is payout( 19, 11 ), -1, 'Spin 19 (20 Black), bet 11 pays -1';
+is payout( 19, 12 ), -1, 'Spin 19 (20 Black), bet 12 pays -1';
+is payout( 19, 13 ), -1, 'Spin 19 (20 Black), bet 13 pays -1';
+is payout( 19, 14 ), -1, 'Spin 19 (20 Black), bet 14 pays -1';
+is payout( 19, 15 ), -1, 'Spin 19 (20 Black), bet 15 pays -1';
+is payout( 19, 16 ), -1, 'Spin 19 (20 Black), bet 16 pays -1';
+is payout( 19, 17 ), -1, 'Spin 19 (20 Black), bet 17 pays -1';
+is payout( 19, 18 ), -1, 'Spin 19 (20 Black), bet 18 pays -1';
+is payout( 19, 19 ), -1, 'Spin 19 (20 Black), bet 19 pays -1';
+is payout( 19, 20 ), 35, 'Spin 19 (20 Black), bet 20 pays 35';
+is payout( 19, 21 ), -1, 'Spin 19 (20 Black), bet 21 pays -1';
+is payout( 19, 22 ), -1, 'Spin 19 (20 Black), bet 22 pays -1';
+is payout( 19, 23 ), -1, 'Spin 19 (20 Black), bet 23 pays -1';
+is payout( 19, 24 ), -1, 'Spin 19 (20 Black), bet 24 pays -1';
+is payout( 19, 25 ), -1, 'Spin 19 (20 Black), bet 25 pays -1';
+is payout( 19, 26 ), -1, 'Spin 19 (20 Black), bet 26 pays -1';
+is payout( 19, 27 ), -1, 'Spin 19 (20 Black), bet 27 pays -1';
+is payout( 19, 28 ), -1, 'Spin 19 (20 Black), bet 28 pays -1';
+is payout( 19, 29 ), -1, 'Spin 19 (20 Black), bet 29 pays -1';
+is payout( 19, 30 ), -1, 'Spin 19 (20 Black), bet 30 pays -1';
+is payout( 19, 31 ), -1, 'Spin 19 (20 Black), bet 31 pays -1';
+is payout( 19, 32 ), -1, 'Spin 19 (20 Black), bet 32 pays -1';
+is payout( 19, 33 ), -1, 'Spin 19 (20 Black), bet 33 pays -1';
+is payout( 19, 34 ), -1, 'Spin 19 (20 Black), bet 34 pays -1';
+is payout( 19, 35 ), -1, 'Spin 19 (20 Black), bet 35 pays -1';
+is payout( 19, 36 ), -1, 'Spin 19 (20 Black), bet 36 pays -1';
+is payout( 19, 37 ), -1, 'Spin 19 (20 Black), bet 37 pays -1';
+is payout( 19, 38 ), 2, 'Spin 19 (20 Black), bet 38 pays 2';
+is payout( 19, 39 ), -1, 'Spin 19 (20 Black), bet 39 pays -1';
+is payout( 19, 40 ), -1, 'Spin 19 (20 Black), bet 40 pays -1';
+is payout( 19, 41 ), 2, 'Spin 19 (20 Black), bet 41 pays 2';
+is payout( 19, 42 ), -1, 'Spin 19 (20 Black), bet 42 pays -1';
+is payout( 19, 43 ), -1, 'Spin 19 (20 Black), bet 43 pays -1';
+is payout( 19, 44 ), 1, 'Spin 19 (20 Black), bet 44 pays 1';
+is payout( 19, 45 ), 1, 'Spin 19 (20 Black), bet 45 pays 1';
+is payout( 19, 46 ), -1, 'Spin 19 (20 Black), bet 46 pays -1';
+is payout( 19, 47 ), -1, 'Spin 19 (20 Black), bet 47 pays -1';
+is payout( 19, 48 ), 1, 'Spin 19 (20 Black), bet 48 pays 1';
+is payout( 19, 49 ), -1, 'Spin 19 (20 Black), bet 49 pays -1';
+is payout( 19, 50 ), -1, 'Spin 19 (20 Black), bet 50 pays -1';
+is format_spin( 20 ), '21 Red', 'Spin 20 is 21 Red';
+is payout( 20, 1 ), -1, 'Spin 20 (21 Red), bet 1 pays -1';
+is payout( 20, 2 ), -1, 'Spin 20 (21 Red), bet 2 pays -1';
+is payout( 20, 3 ), -1, 'Spin 20 (21 Red), bet 3 pays -1';
+is payout( 20, 4 ), -1, 'Spin 20 (21 Red), bet 4 pays -1';
+is payout( 20, 5 ), -1, 'Spin 20 (21 Red), bet 5 pays -1';
+is payout( 20, 6 ), -1, 'Spin 20 (21 Red), bet 6 pays -1';
+is payout( 20, 7 ), -1, 'Spin 20 (21 Red), bet 7 pays -1';
+is payout( 20, 8 ), -1, 'Spin 20 (21 Red), bet 8 pays -1';
+is payout( 20, 9 ), -1, 'Spin 20 (21 Red), bet 9 pays -1';
+is payout( 20, 10 ), -1, 'Spin 20 (21 Red), bet 10 pays -1';
+is payout( 20, 11 ), -1, 'Spin 20 (21 Red), bet 11 pays -1';
+is payout( 20, 12 ), -1, 'Spin 20 (21 Red), bet 12 pays -1';
+is payout( 20, 13 ), -1, 'Spin 20 (21 Red), bet 13 pays -1';
+is payout( 20, 14 ), -1, 'Spin 20 (21 Red), bet 14 pays -1';
+is payout( 20, 15 ), -1, 'Spin 20 (21 Red), bet 15 pays -1';
+is payout( 20, 16 ), -1, 'Spin 20 (21 Red), bet 16 pays -1';
+is payout( 20, 17 ), -1, 'Spin 20 (21 Red), bet 17 pays -1';
+is payout( 20, 18 ), -1, 'Spin 20 (21 Red), bet 18 pays -1';
+is payout( 20, 19 ), -1, 'Spin 20 (21 Red), bet 19 pays -1';
+is payout( 20, 20 ), -1, 'Spin 20 (21 Red), bet 20 pays -1';
+is payout( 20, 21 ), 35, 'Spin 20 (21 Red), bet 21 pays 35';
+is payout( 20, 22 ), -1, 'Spin 20 (21 Red), bet 22 pays -1';
+is payout( 20, 23 ), -1, 'Spin 20 (21 Red), bet 23 pays -1';
+is payout( 20, 24 ), -1, 'Spin 20 (21 Red), bet 24 pays -1';
+is payout( 20, 25 ), -1, 'Spin 20 (21 Red), bet 25 pays -1';
+is payout( 20, 26 ), -1, 'Spin 20 (21 Red), bet 26 pays -1';
+is payout( 20, 27 ), -1, 'Spin 20 (21 Red), bet 27 pays -1';
+is payout( 20, 28 ), -1, 'Spin 20 (21 Red), bet 28 pays -1';
+is payout( 20, 29 ), -1, 'Spin 20 (21 Red), bet 29 pays -1';
+is payout( 20, 30 ), -1, 'Spin 20 (21 Red), bet 30 pays -1';
+is payout( 20, 31 ), -1, 'Spin 20 (21 Red), bet 31 pays -1';
+is payout( 20, 32 ), -1, 'Spin 20 (21 Red), bet 32 pays -1';
+is payout( 20, 33 ), -1, 'Spin 20 (21 Red), bet 33 pays -1';
+is payout( 20, 34 ), -1, 'Spin 20 (21 Red), bet 34 pays -1';
+is payout( 20, 35 ), -1, 'Spin 20 (21 Red), bet 35 pays -1';
+is payout( 20, 36 ), -1, 'Spin 20 (21 Red), bet 36 pays -1';
+is payout( 20, 37 ), -1, 'Spin 20 (21 Red), bet 37 pays -1';
+is payout( 20, 38 ), 2, 'Spin 20 (21 Red), bet 38 pays 2';
+is payout( 20, 39 ), -1, 'Spin 20 (21 Red), bet 39 pays -1';
+is payout( 20, 40 ), -1, 'Spin 20 (21 Red), bet 40 pays -1';
+is payout( 20, 41 ), -1, 'Spin 20 (21 Red), bet 41 pays -1';
+is payout( 20, 42 ), 2, 'Spin 20 (21 Red), bet 42 pays 2';
+is payout( 20, 43 ), -1, 'Spin 20 (21 Red), bet 43 pays -1';
+is payout( 20, 44 ), 1, 'Spin 20 (21 Red), bet 44 pays 1';
+is payout( 20, 45 ), -1, 'Spin 20 (21 Red), bet 45 pays -1';
+is payout( 20, 46 ), 1, 'Spin 20 (21 Red), bet 46 pays 1';
+is payout( 20, 47 ), 1, 'Spin 20 (21 Red), bet 47 pays 1';
+is payout( 20, 48 ), -1, 'Spin 20 (21 Red), bet 48 pays -1';
+is payout( 20, 49 ), -1, 'Spin 20 (21 Red), bet 49 pays -1';
+is payout( 20, 50 ), -1, 'Spin 20 (21 Red), bet 50 pays -1';
+is format_spin( 21 ), '22 Black', 'Spin 21 is 22 Black';
+is payout( 21, 1 ), -1, 'Spin 21 (22 Black), bet 1 pays -1';
+is payout( 21, 2 ), -1, 'Spin 21 (22 Black), bet 2 pays -1';
+is payout( 21, 3 ), -1, 'Spin 21 (22 Black), bet 3 pays -1';
+is payout( 21, 4 ), -1, 'Spin 21 (22 Black), bet 4 pays -1';
+is payout( 21, 5 ), -1, 'Spin 21 (22 Black), bet 5 pays -1';
+is payout( 21, 6 ), -1, 'Spin 21 (22 Black), bet 6 pays -1';
+is payout( 21, 7 ), -1, 'Spin 21 (22 Black), bet 7 pays -1';
+is payout( 21, 8 ), -1, 'Spin 21 (22 Black), bet 8 pays -1';
+is payout( 21, 9 ), -1, 'Spin 21 (22 Black), bet 9 pays -1';
+is payout( 21, 10 ), -1, 'Spin 21 (22 Black), bet 10 pays -1';
+is payout( 21, 11 ), -1, 'Spin 21 (22 Black), bet 11 pays -1';
+is payout( 21, 12 ), -1, 'Spin 21 (22 Black), bet 12 pays -1';
+is payout( 21, 13 ), -1, 'Spin 21 (22 Black), bet 13 pays -1';
+is payout( 21, 14 ), -1, 'Spin 21 (22 Black), bet 14 pays -1';
+is payout( 21, 15 ), -1, 'Spin 21 (22 Black), bet 15 pays -1';
+is payout( 21, 16 ), -1, 'Spin 21 (22 Black), bet 16 pays -1';
+is payout( 21, 17 ), -1, 'Spin 21 (22 Black), bet 17 pays -1';
+is payout( 21, 18 ), -1, 'Spin 21 (22 Black), bet 18 pays -1';
+is payout( 21, 19 ), -1, 'Spin 21 (22 Black), bet 19 pays -1';
+is payout( 21, 20 ), -1, 'Spin 21 (22 Black), bet 20 pays -1';
+is payout( 21, 21 ), -1, 'Spin 21 (22 Black), bet 21 pays -1';
+is payout( 21, 22 ), 35, 'Spin 21 (22 Black), bet 22 pays 35';
+is payout( 21, 23 ), -1, 'Spin 21 (22 Black), bet 23 pays -1';
+is payout( 21, 24 ), -1, 'Spin 21 (22 Black), bet 24 pays -1';
+is payout( 21, 25 ), -1, 'Spin 21 (22 Black), bet 25 pays -1';
+is payout( 21, 26 ), -1, 'Spin 21 (22 Black), bet 26 pays -1';
+is payout( 21, 27 ), -1, 'Spin 21 (22 Black), bet 27 pays -1';
+is payout( 21, 28 ), -1, 'Spin 21 (22 Black), bet 28 pays -1';
+is payout( 21, 29 ), -1, 'Spin 21 (22 Black), bet 29 pays -1';
+is payout( 21, 30 ), -1, 'Spin 21 (22 Black), bet 30 pays -1';
+is payout( 21, 31 ), -1, 'Spin 21 (22 Black), bet 31 pays -1';
+is payout( 21, 32 ), -1, 'Spin 21 (22 Black), bet 32 pays -1';
+is payout( 21, 33 ), -1, 'Spin 21 (22 Black), bet 33 pays -1';
+is payout( 21, 34 ), -1, 'Spin 21 (22 Black), bet 34 pays -1';
+is payout( 21, 35 ), -1, 'Spin 21 (22 Black), bet 35 pays -1';
+is payout( 21, 36 ), -1, 'Spin 21 (22 Black), bet 36 pays -1';
+is payout( 21, 37 ), -1, 'Spin 21 (22 Black), bet 37 pays -1';
+is payout( 21, 38 ), 2, 'Spin 21 (22 Black), bet 38 pays 2';
+is payout( 21, 39 ), -1, 'Spin 21 (22 Black), bet 39 pays -1';
+is payout( 21, 40 ), 2, 'Spin 21 (22 Black), bet 40 pays 2';
+is payout( 21, 41 ), -1, 'Spin 21 (22 Black), bet 41 pays -1';
+is payout( 21, 42 ), -1, 'Spin 21 (22 Black), bet 42 pays -1';
+is payout( 21, 43 ), -1, 'Spin 21 (22 Black), bet 43 pays -1';
+is payout( 21, 44 ), 1, 'Spin 21 (22 Black), bet 44 pays 1';
+is payout( 21, 45 ), 1, 'Spin 21 (22 Black), bet 45 pays 1';
+is payout( 21, 46 ), -1, 'Spin 21 (22 Black), bet 46 pays -1';
+is payout( 21, 47 ), -1, 'Spin 21 (22 Black), bet 47 pays -1';
+is payout( 21, 48 ), 1, 'Spin 21 (22 Black), bet 48 pays 1';
+is payout( 21, 49 ), -1, 'Spin 21 (22 Black), bet 49 pays -1';
+is payout( 21, 50 ), -1, 'Spin 21 (22 Black), bet 50 pays -1';
+is format_spin( 22 ), '23 Red', 'Spin 22 is 23 Red';
+is payout( 22, 1 ), -1, 'Spin 22 (23 Red), bet 1 pays -1';
+is payout( 22, 2 ), -1, 'Spin 22 (23 Red), bet 2 pays -1';
+is payout( 22, 3 ), -1, 'Spin 22 (23 Red), bet 3 pays -1';
+is payout( 22, 4 ), -1, 'Spin 22 (23 Red), bet 4 pays -1';
+is payout( 22, 5 ), -1, 'Spin 22 (23 Red), bet 5 pays -1';
+is payout( 22, 6 ), -1, 'Spin 22 (23 Red), bet 6 pays -1';
+is payout( 22, 7 ), -1, 'Spin 22 (23 Red), bet 7 pays -1';
+is payout( 22, 8 ), -1, 'Spin 22 (23 Red), bet 8 pays -1';
+is payout( 22, 9 ), -1, 'Spin 22 (23 Red), bet 9 pays -1';
+is payout( 22, 10 ), -1, 'Spin 22 (23 Red), bet 10 pays -1';
+is payout( 22, 11 ), -1, 'Spin 22 (23 Red), bet 11 pays -1';
+is payout( 22, 12 ), -1, 'Spin 22 (23 Red), bet 12 pays -1';
+is payout( 22, 13 ), -1, 'Spin 22 (23 Red), bet 13 pays -1';
+is payout( 22, 14 ), -1, 'Spin 22 (23 Red), bet 14 pays -1';
+is payout( 22, 15 ), -1, 'Spin 22 (23 Red), bet 15 pays -1';
+is payout( 22, 16 ), -1, 'Spin 22 (23 Red), bet 16 pays -1';
+is payout( 22, 17 ), -1, 'Spin 22 (23 Red), bet 17 pays -1';
+is payout( 22, 18 ), -1, 'Spin 22 (23 Red), bet 18 pays -1';
+is payout( 22, 19 ), -1, 'Spin 22 (23 Red), bet 19 pays -1';
+is payout( 22, 20 ), -1, 'Spin 22 (23 Red), bet 20 pays -1';
+is payout( 22, 21 ), -1, 'Spin 22 (23 Red), bet 21 pays -1';
+is payout( 22, 22 ), -1, 'Spin 22 (23 Red), bet 22 pays -1';
+is payout( 22, 23 ), 35, 'Spin 22 (23 Red), bet 23 pays 35';
+is payout( 22, 24 ), -1, 'Spin 22 (23 Red), bet 24 pays -1';
+is payout( 22, 25 ), -1, 'Spin 22 (23 Red), bet 25 pays -1';
+is payout( 22, 26 ), -1, 'Spin 22 (23 Red), bet 26 pays -1';
+is payout( 22, 27 ), -1, 'Spin 22 (23 Red), bet 27 pays -1';
+is payout( 22, 28 ), -1, 'Spin 22 (23 Red), bet 28 pays -1';
+is payout( 22, 29 ), -1, 'Spin 22 (23 Red), bet 29 pays -1';
+is payout( 22, 30 ), -1, 'Spin 22 (23 Red), bet 30 pays -1';
+is payout( 22, 31 ), -1, 'Spin 22 (23 Red), bet 31 pays -1';
+is payout( 22, 32 ), -1, 'Spin 22 (23 Red), bet 32 pays -1';
+is payout( 22, 33 ), -1, 'Spin 22 (23 Red), bet 33 pays -1';
+is payout( 22, 34 ), -1, 'Spin 22 (23 Red), bet 34 pays -1';
+is payout( 22, 35 ), -1, 'Spin 22 (23 Red), bet 35 pays -1';
+is payout( 22, 36 ), -1, 'Spin 22 (23 Red), bet 36 pays -1';
+is payout( 22, 37 ), -1, 'Spin 22 (23 Red), bet 37 pays -1';
+is payout( 22, 38 ), 2, 'Spin 22 (23 Red), bet 38 pays 2';
+is payout( 22, 39 ), -1, 'Spin 22 (23 Red), bet 39 pays -1';
+is payout( 22, 40 ), -1, 'Spin 22 (23 Red), bet 40 pays -1';
+is payout( 22, 41 ), 2, 'Spin 22 (23 Red), bet 41 pays 2';
+is payout( 22, 42 ), -1, 'Spin 22 (23 Red), bet 42 pays -1';
+is payout( 22, 43 ), -1, 'Spin 22 (23 Red), bet 43 pays -1';
+is payout( 22, 44 ), 1, 'Spin 22 (23 Red), bet 44 pays 1';
+is payout( 22, 45 ), -1, 'Spin 22 (23 Red), bet 45 pays -1';
+is payout( 22, 46 ), 1, 'Spin 22 (23 Red), bet 46 pays 1';
+is payout( 22, 47 ), 1, 'Spin 22 (23 Red), bet 47 pays 1';
+is payout( 22, 48 ), -1, 'Spin 22 (23 Red), bet 48 pays -1';
+is payout( 22, 49 ), -1, 'Spin 22 (23 Red), bet 49 pays -1';
+is payout( 22, 50 ), -1, 'Spin 22 (23 Red), bet 50 pays -1';
+is format_spin( 23 ), '24 Black', 'Spin 23 is 24 Black';
+is payout( 23, 1 ), -1, 'Spin 23 (24 Black), bet 1 pays -1';
+is payout( 23, 2 ), -1, 'Spin 23 (24 Black), bet 2 pays -1';
+is payout( 23, 3 ), -1, 'Spin 23 (24 Black), bet 3 pays -1';
+is payout( 23, 4 ), -1, 'Spin 23 (24 Black), bet 4 pays -1';
+is payout( 23, 5 ), -1, 'Spin 23 (24 Black), bet 5 pays -1';
+is payout( 23, 6 ), -1, 'Spin 23 (24 Black), bet 6 pays -1';
+is payout( 23, 7 ), -1, 'Spin 23 (24 Black), bet 7 pays -1';
+is payout( 23, 8 ), -1, 'Spin 23 (24 Black), bet 8 pays -1';
+is payout( 23, 9 ), -1, 'Spin 23 (24 Black), bet 9 pays -1';
+is payout( 23, 10 ), -1, 'Spin 23 (24 Black), bet 10 pays -1';
+is payout( 23, 11 ), -1, 'Spin 23 (24 Black), bet 11 pays -1';
+is payout( 23, 12 ), -1, 'Spin 23 (24 Black), bet 12 pays -1';
+is payout( 23, 13 ), -1, 'Spin 23 (24 Black), bet 13 pays -1';
+is payout( 23, 14 ), -1, 'Spin 23 (24 Black), bet 14 pays -1';
+is payout( 23, 15 ), -1, 'Spin 23 (24 Black), bet 15 pays -1';
+is payout( 23, 16 ), -1, 'Spin 23 (24 Black), bet 16 pays -1';
+is payout( 23, 17 ), -1, 'Spin 23 (24 Black), bet 17 pays -1';
+is payout( 23, 18 ), -1, 'Spin 23 (24 Black), bet 18 pays -1';
+is payout( 23, 19 ), -1, 'Spin 23 (24 Black), bet 19 pays -1';
+is payout( 23, 20 ), -1, 'Spin 23 (24 Black), bet 20 pays -1';
+is payout( 23, 21 ), -1, 'Spin 23 (24 Black), bet 21 pays -1';
+is payout( 23, 22 ), -1, 'Spin 23 (24 Black), bet 22 pays -1';
+is payout( 23, 23 ), -1, 'Spin 23 (24 Black), bet 23 pays -1';
+is payout( 23, 24 ), 35, 'Spin 23 (24 Black), bet 24 pays 35';
+is payout( 23, 25 ), -1, 'Spin 23 (24 Black), bet 25 pays -1';
+is payout( 23, 26 ), -1, 'Spin 23 (24 Black), bet 26 pays -1';
+is payout( 23, 27 ), -1, 'Spin 23 (24 Black), bet 27 pays -1';
+is payout( 23, 28 ), -1, 'Spin 23 (24 Black), bet 28 pays -1';
+is payout( 23, 29 ), -1, 'Spin 23 (24 Black), bet 29 pays -1';
+is payout( 23, 30 ), -1, 'Spin 23 (24 Black), bet 30 pays -1';
+is payout( 23, 31 ), -1, 'Spin 23 (24 Black), bet 31 pays -1';
+is payout( 23, 32 ), -1, 'Spin 23 (24 Black), bet 32 pays -1';
+is payout( 23, 33 ), -1, 'Spin 23 (24 Black), bet 33 pays -1';
+is payout( 23, 34 ), -1, 'Spin 23 (24 Black), bet 34 pays -1';
+is payout( 23, 35 ), -1, 'Spin 23 (24 Black), bet 35 pays -1';
+is payout( 23, 36 ), -1, 'Spin 23 (24 Black), bet 36 pays -1';
+is payout( 23, 37 ), -1, 'Spin 23 (24 Black), bet 37 pays -1';
+is payout( 23, 38 ), 2, 'Spin 23 (24 Black), bet 38 pays 2';
+is payout( 23, 39 ), -1, 'Spin 23 (24 Black), bet 39 pays -1';
+is payout( 23, 40 ), -1, 'Spin 23 (24 Black), bet 40 pays -1';
+is payout( 23, 41 ), -1, 'Spin 23 (24 Black), bet 41 pays -1';
+is payout( 23, 42 ), 2, 'Spin 23 (24 Black), bet 42 pays 2';
+is payout( 23, 43 ), -1, 'Spin 23 (24 Black), bet 43 pays -1';
+is payout( 23, 44 ), 1, 'Spin 23 (24 Black), bet 44 pays 1';
+is payout( 23, 45 ), 1, 'Spin 23 (24 Black), bet 45 pays 1';
+is payout( 23, 46 ), -1, 'Spin 23 (24 Black), bet 46 pays -1';
+is payout( 23, 47 ), -1, 'Spin 23 (24 Black), bet 47 pays -1';
+is payout( 23, 48 ), 1, 'Spin 23 (24 Black), bet 48 pays 1';
+is payout( 23, 49 ), -1, 'Spin 23 (24 Black), bet 49 pays -1';
+is payout( 23, 50 ), -1, 'Spin 23 (24 Black), bet 50 pays -1';
+is format_spin( 24 ), '25 Red', 'Spin 24 is 25 Red';
+is payout( 24, 1 ), -1, 'Spin 24 (25 Red), bet 1 pays -1';
+is payout( 24, 2 ), -1, 'Spin 24 (25 Red), bet 2 pays -1';
+is payout( 24, 3 ), -1, 'Spin 24 (25 Red), bet 3 pays -1';
+is payout( 24, 4 ), -1, 'Spin 24 (25 Red), bet 4 pays -1';
+is payout( 24, 5 ), -1, 'Spin 24 (25 Red), bet 5 pays -1';
+is payout( 24, 6 ), -1, 'Spin 24 (25 Red), bet 6 pays -1';
+is payout( 24, 7 ), -1, 'Spin 24 (25 Red), bet 7 pays -1';
+is payout( 24, 8 ), -1, 'Spin 24 (25 Red), bet 8 pays -1';
+is payout( 24, 9 ), -1, 'Spin 24 (25 Red), bet 9 pays -1';
+is payout( 24, 10 ), -1, 'Spin 24 (25 Red), bet 10 pays -1';
+is payout( 24, 11 ), -1, 'Spin 24 (25 Red), bet 11 pays -1';
+is payout( 24, 12 ), -1, 'Spin 24 (25 Red), bet 12 pays -1';
+is payout( 24, 13 ), -1, 'Spin 24 (25 Red), bet 13 pays -1';
+is payout( 24, 14 ), -1, 'Spin 24 (25 Red), bet 14 pays -1';
+is payout( 24, 15 ), -1, 'Spin 24 (25 Red), bet 15 pays -1';
+is payout( 24, 16 ), -1, 'Spin 24 (25 Red), bet 16 pays -1';
+is payout( 24, 17 ), -1, 'Spin 24 (25 Red), bet 17 pays -1';
+is payout( 24, 18 ), -1, 'Spin 24 (25 Red), bet 18 pays -1';
+is payout( 24, 19 ), -1, 'Spin 24 (25 Red), bet 19 pays -1';
+is payout( 24, 20 ), -1, 'Spin 24 (25 Red), bet 20 pays -1';
+is payout( 24, 21 ), -1, 'Spin 24 (25 Red), bet 21 pays -1';
+is payout( 24, 22 ), -1, 'Spin 24 (25 Red), bet 22 pays -1';
+is payout( 24, 23 ), -1, 'Spin 24 (25 Red), bet 23 pays -1';
+is payout( 24, 24 ), -1, 'Spin 24 (25 Red), bet 24 pays -1';
+is payout( 24, 25 ), 35, 'Spin 24 (25 Red), bet 25 pays 35';
+is payout( 24, 26 ), -1, 'Spin 24 (25 Red), bet 26 pays -1';
+is payout( 24, 27 ), -1, 'Spin 24 (25 Red), bet 27 pays -1';
+is payout( 24, 28 ), -1, 'Spin 24 (25 Red), bet 28 pays -1';
+is payout( 24, 29 ), -1, 'Spin 24 (25 Red), bet 29 pays -1';
+is payout( 24, 30 ), -1, 'Spin 24 (25 Red), bet 30 pays -1';
+is payout( 24, 31 ), -1, 'Spin 24 (25 Red), bet 31 pays -1';
+is payout( 24, 32 ), -1, 'Spin 24 (25 Red), bet 32 pays -1';
+is payout( 24, 33 ), -1, 'Spin 24 (25 Red), bet 33 pays -1';
+is payout( 24, 34 ), -1, 'Spin 24 (25 Red), bet 34 pays -1';
+is payout( 24, 35 ), -1, 'Spin 24 (25 Red), bet 35 pays -1';
+is payout( 24, 36 ), -1, 'Spin 24 (25 Red), bet 36 pays -1';
+is payout( 24, 37 ), -1, 'Spin 24 (25 Red), bet 37 pays -1';
+is payout( 24, 38 ), -1, 'Spin 24 (25 Red), bet 38 pays -1';
+is payout( 24, 39 ), 2, 'Spin 24 (25 Red), bet 39 pays 2';
+is payout( 24, 40 ), 2, 'Spin 24 (25 Red), bet 40 pays 2';
+is payout( 24, 41 ), -1, 'Spin 24 (25 Red), bet 41 pays -1';
+is payout( 24, 42 ), -1, 'Spin 24 (25 Red), bet 42 pays -1';
+is payout( 24, 43 ), -1, 'Spin 24 (25 Red), bet 43 pays -1';
+is payout( 24, 44 ), 1, 'Spin 24 (25 Red), bet 44 pays 1';
+is payout( 24, 45 ), -1, 'Spin 24 (25 Red), bet 45 pays -1';
+is payout( 24, 46 ), 1, 'Spin 24 (25 Red), bet 46 pays 1';
+is payout( 24, 47 ), 1, 'Spin 24 (25 Red), bet 47 pays 1';
+is payout( 24, 48 ), -1, 'Spin 24 (25 Red), bet 48 pays -1';
+is payout( 24, 49 ), -1, 'Spin 24 (25 Red), bet 49 pays -1';
+is payout( 24, 50 ), -1, 'Spin 24 (25 Red), bet 50 pays -1';
+is format_spin( 25 ), '26 Black', 'Spin 25 is 26 Black';
+is payout( 25, 1 ), -1, 'Spin 25 (26 Black), bet 1 pays -1';
+is payout( 25, 2 ), -1, 'Spin 25 (26 Black), bet 2 pays -1';
+is payout( 25, 3 ), -1, 'Spin 25 (26 Black), bet 3 pays -1';
+is payout( 25, 4 ), -1, 'Spin 25 (26 Black), bet 4 pays -1';
+is payout( 25, 5 ), -1, 'Spin 25 (26 Black), bet 5 pays -1';
+is payout( 25, 6 ), -1, 'Spin 25 (26 Black), bet 6 pays -1';
+is payout( 25, 7 ), -1, 'Spin 25 (26 Black), bet 7 pays -1';
+is payout( 25, 8 ), -1, 'Spin 25 (26 Black), bet 8 pays -1';
+is payout( 25, 9 ), -1, 'Spin 25 (26 Black), bet 9 pays -1';
+is payout( 25, 10 ), -1, 'Spin 25 (26 Black), bet 10 pays -1';
+is payout( 25, 11 ), -1, 'Spin 25 (26 Black), bet 11 pays -1';
+is payout( 25, 12 ), -1, 'Spin 25 (26 Black), bet 12 pays -1';
+is payout( 25, 13 ), -1, 'Spin 25 (26 Black), bet 13 pays -1';
+is payout( 25, 14 ), -1, 'Spin 25 (26 Black), bet 14 pays -1';
+is payout( 25, 15 ), -1, 'Spin 25 (26 Black), bet 15 pays -1';
+is payout( 25, 16 ), -1, 'Spin 25 (26 Black), bet 16 pays -1';
+is payout( 25, 17 ), -1, 'Spin 25 (26 Black), bet 17 pays -1';
+is payout( 25, 18 ), -1, 'Spin 25 (26 Black), bet 18 pays -1';
+is payout( 25, 19 ), -1, 'Spin 25 (26 Black), bet 19 pays -1';
+is payout( 25, 20 ), -1, 'Spin 25 (26 Black), bet 20 pays -1';
+is payout( 25, 21 ), -1, 'Spin 25 (26 Black), bet 21 pays -1';
+is payout( 25, 22 ), -1, 'Spin 25 (26 Black), bet 22 pays -1';
+is payout( 25, 23 ), -1, 'Spin 25 (26 Black), bet 23 pays -1';
+is payout( 25, 24 ), -1, 'Spin 25 (26 Black), bet 24 pays -1';
+is payout( 25, 25 ), -1, 'Spin 25 (26 Black), bet 25 pays -1';
+is payout( 25, 26 ), 35, 'Spin 25 (26 Black), bet 26 pays 35';
+is payout( 25, 27 ), -1, 'Spin 25 (26 Black), bet 27 pays -1';
+is payout( 25, 28 ), -1, 'Spin 25 (26 Black), bet 28 pays -1';
+is payout( 25, 29 ), -1, 'Spin 25 (26 Black), bet 29 pays -1';
+is payout( 25, 30 ), -1, 'Spin 25 (26 Black), bet 30 pays -1';
+is payout( 25, 31 ), -1, 'Spin 25 (26 Black), bet 31 pays -1';
+is payout( 25, 32 ), -1, 'Spin 25 (26 Black), bet 32 pays -1';
+is payout( 25, 33 ), -1, 'Spin 25 (26 Black), bet 33 pays -1';
+is payout( 25, 34 ), -1, 'Spin 25 (26 Black), bet 34 pays -1';
+is payout( 25, 35 ), -1, 'Spin 25 (26 Black), bet 35 pays -1';
+is payout( 25, 36 ), -1, 'Spin 25 (26 Black), bet 36 pays -1';
+is payout( 25, 37 ), -1, 'Spin 25 (26 Black), bet 37 pays -1';
+is payout( 25, 38 ), -1, 'Spin 25 (26 Black), bet 38 pays -1';
+is payout( 25, 39 ), 2, 'Spin 25 (26 Black), bet 39 pays 2';
+is payout( 25, 40 ), -1, 'Spin 25 (26 Black), bet 40 pays -1';
+is payout( 25, 41 ), 2, 'Spin 25 (26 Black), bet 41 pays 2';
+is payout( 25, 42 ), -1, 'Spin 25 (26 Black), bet 42 pays -1';
+is payout( 25, 43 ), -1, 'Spin 25 (26 Black), bet 43 pays -1';
+is payout( 25, 44 ), 1, 'Spin 25 (26 Black), bet 44 pays 1';
+is payout( 25, 45 ), 1, 'Spin 25 (26 Black), bet 45 pays 1';
+is payout( 25, 46 ), -1, 'Spin 25 (26 Black), bet 46 pays -1';
+is payout( 25, 47 ), -1, 'Spin 25 (26 Black), bet 47 pays -1';
+is payout( 25, 48 ), 1, 'Spin 25 (26 Black), bet 48 pays 1';
+is payout( 25, 49 ), -1, 'Spin 25 (26 Black), bet 49 pays -1';
+is payout( 25, 50 ), -1, 'Spin 25 (26 Black), bet 50 pays -1';
+is format_spin( 26 ), '27 Red', 'Spin 26 is 27 Red';
+is payout( 26, 1 ), -1, 'Spin 26 (27 Red), bet 1 pays -1';
+is payout( 26, 2 ), -1, 'Spin 26 (27 Red), bet 2 pays -1';
+is payout( 26, 3 ), -1, 'Spin 26 (27 Red), bet 3 pays -1';
+is payout( 26, 4 ), -1, 'Spin 26 (27 Red), bet 4 pays -1';
+is payout( 26, 5 ), -1, 'Spin 26 (27 Red), bet 5 pays -1';
+is payout( 26, 6 ), -1, 'Spin 26 (27 Red), bet 6 pays -1';
+is payout( 26, 7 ), -1, 'Spin 26 (27 Red), bet 7 pays -1';
+is payout( 26, 8 ), -1, 'Spin 26 (27 Red), bet 8 pays -1';
+is payout( 26, 9 ), -1, 'Spin 26 (27 Red), bet 9 pays -1';
+is payout( 26, 10 ), -1, 'Spin 26 (27 Red), bet 10 pays -1';
+is payout( 26, 11 ), -1, 'Spin 26 (27 Red), bet 11 pays -1';
+is payout( 26, 12 ), -1, 'Spin 26 (27 Red), bet 12 pays -1';
+is payout( 26, 13 ), -1, 'Spin 26 (27 Red), bet 13 pays -1';
+is payout( 26, 14 ), -1, 'Spin 26 (27 Red), bet 14 pays -1';
+is payout( 26, 15 ), -1, 'Spin 26 (27 Red), bet 15 pays -1';
+is payout( 26, 16 ), -1, 'Spin 26 (27 Red), bet 16 pays -1';
+is payout( 26, 17 ), -1, 'Spin 26 (27 Red), bet 17 pays -1';
+is payout( 26, 18 ), -1, 'Spin 26 (27 Red), bet 18 pays -1';
+is payout( 26, 19 ), -1, 'Spin 26 (27 Red), bet 19 pays -1';
+is payout( 26, 20 ), -1, 'Spin 26 (27 Red), bet 20 pays -1';
+is payout( 26, 21 ), -1, 'Spin 26 (27 Red), bet 21 pays -1';
+is payout( 26, 22 ), -1, 'Spin 26 (27 Red), bet 22 pays -1';
+is payout( 26, 23 ), -1, 'Spin 26 (27 Red), bet 23 pays -1';
+is payout( 26, 24 ), -1, 'Spin 26 (27 Red), bet 24 pays -1';
+is payout( 26, 25 ), -1, 'Spin 26 (27 Red), bet 25 pays -1';
+is payout( 26, 26 ), -1, 'Spin 26 (27 Red), bet 26 pays -1';
+is payout( 26, 27 ), 35, 'Spin 26 (27 Red), bet 27 pays 35';
+is payout( 26, 28 ), -1, 'Spin 26 (27 Red), bet 28 pays -1';
+is payout( 26, 29 ), -1, 'Spin 26 (27 Red), bet 29 pays -1';
+is payout( 26, 30 ), -1, 'Spin 26 (27 Red), bet 30 pays -1';
+is payout( 26, 31 ), -1, 'Spin 26 (27 Red), bet 31 pays -1';
+is payout( 26, 32 ), -1, 'Spin 26 (27 Red), bet 32 pays -1';
+is payout( 26, 33 ), -1, 'Spin 26 (27 Red), bet 33 pays -1';
+is payout( 26, 34 ), -1, 'Spin 26 (27 Red), bet 34 pays -1';
+is payout( 26, 35 ), -1, 'Spin 26 (27 Red), bet 35 pays -1';
+is payout( 26, 36 ), -1, 'Spin 26 (27 Red), bet 36 pays -1';
+is payout( 26, 37 ), -1, 'Spin 26 (27 Red), bet 37 pays -1';
+is payout( 26, 38 ), -1, 'Spin 26 (27 Red), bet 38 pays -1';
+is payout( 26, 39 ), 2, 'Spin 26 (27 Red), bet 39 pays 2';
+is payout( 26, 40 ), -1, 'Spin 26 (27 Red), bet 40 pays -1';
+is payout( 26, 41 ), -1, 'Spin 26 (27 Red), bet 41 pays -1';
+is payout( 26, 42 ), 2, 'Spin 26 (27 Red), bet 42 pays 2';
+is payout( 26, 43 ), -1, 'Spin 26 (27 Red), bet 43 pays -1';
+is payout( 26, 44 ), 1, 'Spin 26 (27 Red), bet 44 pays 1';
+is payout( 26, 45 ), -1, 'Spin 26 (27 Red), bet 45 pays -1';
+is payout( 26, 46 ), 1, 'Spin 26 (27 Red), bet 46 pays 1';
+is payout( 26, 47 ), 1, 'Spin 26 (27 Red), bet 47 pays 1';
+is payout( 26, 48 ), -1, 'Spin 26 (27 Red), bet 48 pays -1';
+is payout( 26, 49 ), -1, 'Spin 26 (27 Red), bet 49 pays -1';
+is payout( 26, 50 ), -1, 'Spin 26 (27 Red), bet 50 pays -1';
+is format_spin( 27 ), '28 Black', 'Spin 27 is 28 Black';
+is payout( 27, 1 ), -1, 'Spin 27 (28 Black), bet 1 pays -1';
+is payout( 27, 2 ), -1, 'Spin 27 (28 Black), bet 2 pays -1';
+is payout( 27, 3 ), -1, 'Spin 27 (28 Black), bet 3 pays -1';
+is payout( 27, 4 ), -1, 'Spin 27 (28 Black), bet 4 pays -1';
+is payout( 27, 5 ), -1, 'Spin 27 (28 Black), bet 5 pays -1';
+is payout( 27, 6 ), -1, 'Spin 27 (28 Black), bet 6 pays -1';
+is payout( 27, 7 ), -1, 'Spin 27 (28 Black), bet 7 pays -1';
+is payout( 27, 8 ), -1, 'Spin 27 (28 Black), bet 8 pays -1';
+is payout( 27, 9 ), -1, 'Spin 27 (28 Black), bet 9 pays -1';
+is payout( 27, 10 ), -1, 'Spin 27 (28 Black), bet 10 pays -1';
+is payout( 27, 11 ), -1, 'Spin 27 (28 Black), bet 11 pays -1';
+is payout( 27, 12 ), -1, 'Spin 27 (28 Black), bet 12 pays -1';
+is payout( 27, 13 ), -1, 'Spin 27 (28 Black), bet 13 pays -1';
+is payout( 27, 14 ), -1, 'Spin 27 (28 Black), bet 14 pays -1';
+is payout( 27, 15 ), -1, 'Spin 27 (28 Black), bet 15 pays -1';
+is payout( 27, 16 ), -1, 'Spin 27 (28 Black), bet 16 pays -1';
+is payout( 27, 17 ), -1, 'Spin 27 (28 Black), bet 17 pays -1';
+is payout( 27, 18 ), -1, 'Spin 27 (28 Black), bet 18 pays -1';
+is payout( 27, 19 ), -1, 'Spin 27 (28 Black), bet 19 pays -1';
+is payout( 27, 20 ), -1, 'Spin 27 (28 Black), bet 20 pays -1';
+is payout( 27, 21 ), -1, 'Spin 27 (28 Black), bet 21 pays -1';
+is payout( 27, 22 ), -1, 'Spin 27 (28 Black), bet 22 pays -1';
+is payout( 27, 23 ), -1, 'Spin 27 (28 Black), bet 23 pays -1';
+is payout( 27, 24 ), -1, 'Spin 27 (28 Black), bet 24 pays -1';
+is payout( 27, 25 ), -1, 'Spin 27 (28 Black), bet 25 pays -1';
+is payout( 27, 26 ), -1, 'Spin 27 (28 Black), bet 26 pays -1';
+is payout( 27, 27 ), -1, 'Spin 27 (28 Black), bet 27 pays -1';
+is payout( 27, 28 ), 35, 'Spin 27 (28 Black), bet 28 pays 35';
+is payout( 27, 29 ), -1, 'Spin 27 (28 Black), bet 29 pays -1';
+is payout( 27, 30 ), -1, 'Spin 27 (28 Black), bet 30 pays -1';
+is payout( 27, 31 ), -1, 'Spin 27 (28 Black), bet 31 pays -1';
+is payout( 27, 32 ), -1, 'Spin 27 (28 Black), bet 32 pays -1';
+is payout( 27, 33 ), -1, 'Spin 27 (28 Black), bet 33 pays -1';
+is payout( 27, 34 ), -1, 'Spin 27 (28 Black), bet 34 pays -1';
+is payout( 27, 35 ), -1, 'Spin 27 (28 Black), bet 35 pays -1';
+is payout( 27, 36 ), -1, 'Spin 27 (28 Black), bet 36 pays -1';
+is payout( 27, 37 ), -1, 'Spin 27 (28 Black), bet 37 pays -1';
+is payout( 27, 38 ), -1, 'Spin 27 (28 Black), bet 38 pays -1';
+is payout( 27, 39 ), 2, 'Spin 27 (28 Black), bet 39 pays 2';
+is payout( 27, 40 ), 2, 'Spin 27 (28 Black), bet 40 pays 2';
+is payout( 27, 41 ), -1, 'Spin 27 (28 Black), bet 41 pays -1';
+is payout( 27, 42 ), -1, 'Spin 27 (28 Black), bet 42 pays -1';
+is payout( 27, 43 ), -1, 'Spin 27 (28 Black), bet 43 pays -1';
+is payout( 27, 44 ), 1, 'Spin 27 (28 Black), bet 44 pays 1';
+is payout( 27, 45 ), 1, 'Spin 27 (28 Black), bet 45 pays 1';
+is payout( 27, 46 ), -1, 'Spin 27 (28 Black), bet 46 pays -1';
+is payout( 27, 47 ), -1, 'Spin 27 (28 Black), bet 47 pays -1';
+is payout( 27, 48 ), 1, 'Spin 27 (28 Black), bet 48 pays 1';
+is payout( 27, 49 ), -1, 'Spin 27 (28 Black), bet 49 pays -1';
+is payout( 27, 50 ), -1, 'Spin 27 (28 Black), bet 50 pays -1';
+is format_spin( 28 ), '29 Black', 'Spin 28 is 29 Black';
+is payout( 28, 1 ), -1, 'Spin 28 (29 Black), bet 1 pays -1';
+is payout( 28, 2 ), -1, 'Spin 28 (29 Black), bet 2 pays -1';
+is payout( 28, 3 ), -1, 'Spin 28 (29 Black), bet 3 pays -1';
+is payout( 28, 4 ), -1, 'Spin 28 (29 Black), bet 4 pays -1';
+is payout( 28, 5 ), -1, 'Spin 28 (29 Black), bet 5 pays -1';
+is payout( 28, 6 ), -1, 'Spin 28 (29 Black), bet 6 pays -1';
+is payout( 28, 7 ), -1, 'Spin 28 (29 Black), bet 7 pays -1';
+is payout( 28, 8 ), -1, 'Spin 28 (29 Black), bet 8 pays -1';
+is payout( 28, 9 ), -1, 'Spin 28 (29 Black), bet 9 pays -1';
+is payout( 28, 10 ), -1, 'Spin 28 (29 Black), bet 10 pays -1';
+is payout( 28, 11 ), -1, 'Spin 28 (29 Black), bet 11 pays -1';
+is payout( 28, 12 ), -1, 'Spin 28 (29 Black), bet 12 pays -1';
+is payout( 28, 13 ), -1, 'Spin 28 (29 Black), bet 13 pays -1';
+is payout( 28, 14 ), -1, 'Spin 28 (29 Black), bet 14 pays -1';
+is payout( 28, 15 ), -1, 'Spin 28 (29 Black), bet 15 pays -1';
+is payout( 28, 16 ), -1, 'Spin 28 (29 Black), bet 16 pays -1';
+is payout( 28, 17 ), -1, 'Spin 28 (29 Black), bet 17 pays -1';
+is payout( 28, 18 ), -1, 'Spin 28 (29 Black), bet 18 pays -1';
+is payout( 28, 19 ), -1, 'Spin 28 (29 Black), bet 19 pays -1';
+is payout( 28, 20 ), -1, 'Spin 28 (29 Black), bet 20 pays -1';
+is payout( 28, 21 ), -1, 'Spin 28 (29 Black), bet 21 pays -1';
+is payout( 28, 22 ), -1, 'Spin 28 (29 Black), bet 22 pays -1';
+is payout( 28, 23 ), -1, 'Spin 28 (29 Black), bet 23 pays -1';
+is payout( 28, 24 ), -1, 'Spin 28 (29 Black), bet 24 pays -1';
+is payout( 28, 25 ), -1, 'Spin 28 (29 Black), bet 25 pays -1';
+is payout( 28, 26 ), -1, 'Spin 28 (29 Black), bet 26 pays -1';
+is payout( 28, 27 ), -1, 'Spin 28 (29 Black), bet 27 pays -1';
+is payout( 28, 28 ), -1, 'Spin 28 (29 Black), bet 28 pays -1';
+is payout( 28, 29 ), 35, 'Spin 28 (29 Black), bet 29 pays 35';
+is payout( 28, 30 ), -1, 'Spin 28 (29 Black), bet 30 pays -1';
+is payout( 28, 31 ), -1, 'Spin 28 (29 Black), bet 31 pays -1';
+is payout( 28, 32 ), -1, 'Spin 28 (29 Black), bet 32 pays -1';
+is payout( 28, 33 ), -1, 'Spin 28 (29 Black), bet 33 pays -1';
+is payout( 28, 34 ), -1, 'Spin 28 (29 Black), bet 34 pays -1';
+is payout( 28, 35 ), -1, 'Spin 28 (29 Black), bet 35 pays -1';
+is payout( 28, 36 ), -1, 'Spin 28 (29 Black), bet 36 pays -1';
+is payout( 28, 37 ), -1, 'Spin 28 (29 Black), bet 37 pays -1';
+is payout( 28, 38 ), -1, 'Spin 28 (29 Black), bet 38 pays -1';
+is payout( 28, 39 ), 2, 'Spin 28 (29 Black), bet 39 pays 2';
+is payout( 28, 40 ), -1, 'Spin 28 (29 Black), bet 40 pays -1';
+is payout( 28, 41 ), 2, 'Spin 28 (29 Black), bet 41 pays 2';
+is payout( 28, 42 ), -1, 'Spin 28 (29 Black), bet 42 pays -1';
+is payout( 28, 43 ), -1, 'Spin 28 (29 Black), bet 43 pays -1';
+is payout( 28, 44 ), 1, 'Spin 28 (29 Black), bet 44 pays 1';
+is payout( 28, 45 ), -1, 'Spin 28 (29 Black), bet 45 pays -1';
+is payout( 28, 46 ), 1, 'Spin 28 (29 Black), bet 46 pays 1';
+is payout( 28, 47 ), -1, 'Spin 28 (29 Black), bet 47 pays -1';
+is payout( 28, 48 ), 1, 'Spin 28 (29 Black), bet 48 pays 1';
+is payout( 28, 49 ), -1, 'Spin 28 (29 Black), bet 49 pays -1';
+is payout( 28, 50 ), -1, 'Spin 28 (29 Black), bet 50 pays -1';
+is format_spin( 29 ), '30 Red', 'Spin 29 is 30 Red';
+is payout( 29, 1 ), -1, 'Spin 29 (30 Red), bet 1 pays -1';
+is payout( 29, 2 ), -1, 'Spin 29 (30 Red), bet 2 pays -1';
+is payout( 29, 3 ), -1, 'Spin 29 (30 Red), bet 3 pays -1';
+is payout( 29, 4 ), -1, 'Spin 29 (30 Red), bet 4 pays -1';
+is payout( 29, 5 ), -1, 'Spin 29 (30 Red), bet 5 pays -1';
+is payout( 29, 6 ), -1, 'Spin 29 (30 Red), bet 6 pays -1';
+is payout( 29, 7 ), -1, 'Spin 29 (30 Red), bet 7 pays -1';
+is payout( 29, 8 ), -1, 'Spin 29 (30 Red), bet 8 pays -1';
+is payout( 29, 9 ), -1, 'Spin 29 (30 Red), bet 9 pays -1';
+is payout( 29, 10 ), -1, 'Spin 29 (30 Red), bet 10 pays -1';
+is payout( 29, 11 ), -1, 'Spin 29 (30 Red), bet 11 pays -1';
+is payout( 29, 12 ), -1, 'Spin 29 (30 Red), bet 12 pays -1';
+is payout( 29, 13 ), -1, 'Spin 29 (30 Red), bet 13 pays -1';
+is payout( 29, 14 ), -1, 'Spin 29 (30 Red), bet 14 pays -1';
+is payout( 29, 15 ), -1, 'Spin 29 (30 Red), bet 15 pays -1';
+is payout( 29, 16 ), -1, 'Spin 29 (30 Red), bet 16 pays -1';
+is payout( 29, 17 ), -1, 'Spin 29 (30 Red), bet 17 pays -1';
+is payout( 29, 18 ), -1, 'Spin 29 (30 Red), bet 18 pays -1';
+is payout( 29, 19 ), -1, 'Spin 29 (30 Red), bet 19 pays -1';
+is payout( 29, 20 ), -1, 'Spin 29 (30 Red), bet 20 pays -1';
+is payout( 29, 21 ), -1, 'Spin 29 (30 Red), bet 21 pays -1';
+is payout( 29, 22 ), -1, 'Spin 29 (30 Red), bet 22 pays -1';
+is payout( 29, 23 ), -1, 'Spin 29 (30 Red), bet 23 pays -1';
+is payout( 29, 24 ), -1, 'Spin 29 (30 Red), bet 24 pays -1';
+is payout( 29, 25 ), -1, 'Spin 29 (30 Red), bet 25 pays -1';
+is payout( 29, 26 ), -1, 'Spin 29 (30 Red), bet 26 pays -1';
+is payout( 29, 27 ), -1, 'Spin 29 (30 Red), bet 27 pays -1';
+is payout( 29, 28 ), -1, 'Spin 29 (30 Red), bet 28 pays -1';
+is payout( 29, 29 ), -1, 'Spin 29 (30 Red), bet 29 pays -1';
+is payout( 29, 30 ), 35, 'Spin 29 (30 Red), bet 30 pays 35';
+is payout( 29, 31 ), -1, 'Spin 29 (30 Red), bet 31 pays -1';
+is payout( 29, 32 ), -1, 'Spin 29 (30 Red), bet 32 pays -1';
+is payout( 29, 33 ), -1, 'Spin 29 (30 Red), bet 33 pays -1';
+is payout( 29, 34 ), -1, 'Spin 29 (30 Red), bet 34 pays -1';
+is payout( 29, 35 ), -1, 'Spin 29 (30 Red), bet 35 pays -1';
+is payout( 29, 36 ), -1, 'Spin 29 (30 Red), bet 36 pays -1';
+is payout( 29, 37 ), -1, 'Spin 29 (30 Red), bet 37 pays -1';
+is payout( 29, 38 ), -1, 'Spin 29 (30 Red), bet 38 pays -1';
+is payout( 29, 39 ), 2, 'Spin 29 (30 Red), bet 39 pays 2';
+is payout( 29, 40 ), -1, 'Spin 29 (30 Red), bet 40 pays -1';
+is payout( 29, 41 ), -1, 'Spin 29 (30 Red), bet 41 pays -1';
+is payout( 29, 42 ), 2, 'Spin 29 (30 Red), bet 42 pays 2';
+is payout( 29, 43 ), -1, 'Spin 29 (30 Red), bet 43 pays -1';
+is payout( 29, 44 ), 1, 'Spin 29 (30 Red), bet 44 pays 1';
+is payout( 29, 45 ), 1, 'Spin 29 (30 Red), bet 45 pays 1';
+is payout( 29, 46 ), -1, 'Spin 29 (30 Red), bet 46 pays -1';
+is payout( 29, 47 ), 1, 'Spin 29 (30 Red), bet 47 pays 1';
+is payout( 29, 48 ), -1, 'Spin 29 (30 Red), bet 48 pays -1';
+is payout( 29, 49 ), -1, 'Spin 29 (30 Red), bet 49 pays -1';
+is payout( 29, 50 ), -1, 'Spin 29 (30 Red), bet 50 pays -1';
+is format_spin( 30 ), '31 Black', 'Spin 30 is 31 Black';
+is payout( 30, 1 ), -1, 'Spin 30 (31 Black), bet 1 pays -1';
+is payout( 30, 2 ), -1, 'Spin 30 (31 Black), bet 2 pays -1';
+is payout( 30, 3 ), -1, 'Spin 30 (31 Black), bet 3 pays -1';
+is payout( 30, 4 ), -1, 'Spin 30 (31 Black), bet 4 pays -1';
+is payout( 30, 5 ), -1, 'Spin 30 (31 Black), bet 5 pays -1';
+is payout( 30, 6 ), -1, 'Spin 30 (31 Black), bet 6 pays -1';
+is payout( 30, 7 ), -1, 'Spin 30 (31 Black), bet 7 pays -1';
+is payout( 30, 8 ), -1, 'Spin 30 (31 Black), bet 8 pays -1';
+is payout( 30, 9 ), -1, 'Spin 30 (31 Black), bet 9 pays -1';
+is payout( 30, 10 ), -1, 'Spin 30 (31 Black), bet 10 pays -1';
+is payout( 30, 11 ), -1, 'Spin 30 (31 Black), bet 11 pays -1';
+is payout( 30, 12 ), -1, 'Spin 30 (31 Black), bet 12 pays -1';
+is payout( 30, 13 ), -1, 'Spin 30 (31 Black), bet 13 pays -1';
+is payout( 30, 14 ), -1, 'Spin 30 (31 Black), bet 14 pays -1';
+is payout( 30, 15 ), -1, 'Spin 30 (31 Black), bet 15 pays -1';
+is payout( 30, 16 ), -1, 'Spin 30 (31 Black), bet 16 pays -1';
+is payout( 30, 17 ), -1, 'Spin 30 (31 Black), bet 17 pays -1';
+is payout( 30, 18 ), -1, 'Spin 30 (31 Black), bet 18 pays -1';
+is payout( 30, 19 ), -1, 'Spin 30 (31 Black), bet 19 pays -1';
+is payout( 30, 20 ), -1, 'Spin 30 (31 Black), bet 20 pays -1';
+is payout( 30, 21 ), -1, 'Spin 30 (31 Black), bet 21 pays -1';
+is payout( 30, 22 ), -1, 'Spin 30 (31 Black), bet 22 pays -1';
+is payout( 30, 23 ), -1, 'Spin 30 (31 Black), bet 23 pays -1';
+is payout( 30, 24 ), -1, 'Spin 30 (31 Black), bet 24 pays -1';
+is payout( 30, 25 ), -1, 'Spin 30 (31 Black), bet 25 pays -1';
+is payout( 30, 26 ), -1, 'Spin 30 (31 Black), bet 26 pays -1';
+is payout( 30, 27 ), -1, 'Spin 30 (31 Black), bet 27 pays -1';
+is payout( 30, 28 ), -1, 'Spin 30 (31 Black), bet 28 pays -1';
+is payout( 30, 29 ), -1, 'Spin 30 (31 Black), bet 29 pays -1';
+is payout( 30, 30 ), -1, 'Spin 30 (31 Black), bet 30 pays -1';
+is payout( 30, 31 ), 35, 'Spin 30 (31 Black), bet 31 pays 35';
+is payout( 30, 32 ), -1, 'Spin 30 (31 Black), bet 32 pays -1';
+is payout( 30, 33 ), -1, 'Spin 30 (31 Black), bet 33 pays -1';
+is payout( 30, 34 ), -1, 'Spin 30 (31 Black), bet 34 pays -1';
+is payout( 30, 35 ), -1, 'Spin 30 (31 Black), bet 35 pays -1';
+is payout( 30, 36 ), -1, 'Spin 30 (31 Black), bet 36 pays -1';
+is payout( 30, 37 ), -1, 'Spin 30 (31 Black), bet 37 pays -1';
+is payout( 30, 38 ), -1, 'Spin 30 (31 Black), bet 38 pays -1';
+is payout( 30, 39 ), 2, 'Spin 30 (31 Black), bet 39 pays 2';
+is payout( 30, 40 ), 2, 'Spin 30 (31 Black), bet 40 pays 2';
+is payout( 30, 41 ), -1, 'Spin 30 (31 Black), bet 41 pays -1';
+is payout( 30, 42 ), -1, 'Spin 30 (31 Black), bet 42 pays -1';
+is payout( 30, 43 ), -1, 'Spin 30 (31 Black), bet 43 pays -1';
+is payout( 30, 44 ), 1, 'Spin 30 (31 Black), bet 44 pays 1';
+is payout( 30, 45 ), -1, 'Spin 30 (31 Black), bet 45 pays -1';
+is payout( 30, 46 ), 1, 'Spin 30 (31 Black), bet 46 pays 1';
+is payout( 30, 47 ), -1, 'Spin 30 (31 Black), bet 47 pays -1';
+is payout( 30, 48 ), 1, 'Spin 30 (31 Black), bet 48 pays 1';
+is payout( 30, 49 ), -1, 'Spin 30 (31 Black), bet 49 pays -1';
+is payout( 30, 50 ), -1, 'Spin 30 (31 Black), bet 50 pays -1';
+is format_spin( 31 ), '32 Red', 'Spin 31 is 32 Red';
+is payout( 31, 1 ), -1, 'Spin 31 (32 Red), bet 1 pays -1';
+is payout( 31, 2 ), -1, 'Spin 31 (32 Red), bet 2 pays -1';
+is payout( 31, 3 ), -1, 'Spin 31 (32 Red), bet 3 pays -1';
+is payout( 31, 4 ), -1, 'Spin 31 (32 Red), bet 4 pays -1';
+is payout( 31, 5 ), -1, 'Spin 31 (32 Red), bet 5 pays -1';
+is payout( 31, 6 ), -1, 'Spin 31 (32 Red), bet 6 pays -1';
+is payout( 31, 7 ), -1, 'Spin 31 (32 Red), bet 7 pays -1';
+is payout( 31, 8 ), -1, 'Spin 31 (32 Red), bet 8 pays -1';
+is payout( 31, 9 ), -1, 'Spin 31 (32 Red), bet 9 pays -1';
+is payout( 31, 10 ), -1, 'Spin 31 (32 Red), bet 10 pays -1';
+is payout( 31, 11 ), -1, 'Spin 31 (32 Red), bet 11 pays -1';
+is payout( 31, 12 ), -1, 'Spin 31 (32 Red), bet 12 pays -1';
+is payout( 31, 13 ), -1, 'Spin 31 (32 Red), bet 13 pays -1';
+is payout( 31, 14 ), -1, 'Spin 31 (32 Red), bet 14 pays -1';
+is payout( 31, 15 ), -1, 'Spin 31 (32 Red), bet 15 pays -1';
+is payout( 31, 16 ), -1, 'Spin 31 (32 Red), bet 16 pays -1';
+is payout( 31, 17 ), -1, 'Spin 31 (32 Red), bet 17 pays -1';
+is payout( 31, 18 ), -1, 'Spin 31 (32 Red), bet 18 pays -1';
+is payout( 31, 19 ), -1, 'Spin 31 (32 Red), bet 19 pays -1';
+is payout( 31, 20 ), -1, 'Spin 31 (32 Red), bet 20 pays -1';
+is payout( 31, 21 ), -1, 'Spin 31 (32 Red), bet 21 pays -1';
+is payout( 31, 22 ), -1, 'Spin 31 (32 Red), bet 22 pays -1';
+is payout( 31, 23 ), -1, 'Spin 31 (32 Red), bet 23 pays -1';
+is payout( 31, 24 ), -1, 'Spin 31 (32 Red), bet 24 pays -1';
+is payout( 31, 25 ), -1, 'Spin 31 (32 Red), bet 25 pays -1';
+is payout( 31, 26 ), -1, 'Spin 31 (32 Red), bet 26 pays -1';
+is payout( 31, 27 ), -1, 'Spin 31 (32 Red), bet 27 pays -1';
+is payout( 31, 28 ), -1, 'Spin 31 (32 Red), bet 28 pays -1';
+is payout( 31, 29 ), -1, 'Spin 31 (32 Red), bet 29 pays -1';
+is payout( 31, 30 ), -1, 'Spin 31 (32 Red), bet 30 pays -1';
+is payout( 31, 31 ), -1, 'Spin 31 (32 Red), bet 31 pays -1';
+is payout( 31, 32 ), 35, 'Spin 31 (32 Red), bet 32 pays 35';
+is payout( 31, 33 ), -1, 'Spin 31 (32 Red), bet 33 pays -1';
+is payout( 31, 34 ), -1, 'Spin 31 (32 Red), bet 34 pays -1';
+is payout( 31, 35 ), -1, 'Spin 31 (32 Red), bet 35 pays -1';
+is payout( 31, 36 ), -1, 'Spin 31 (32 Red), bet 36 pays -1';
+is payout( 31, 37 ), -1, 'Spin 31 (32 Red), bet 37 pays -1';
+is payout( 31, 38 ), -1, 'Spin 31 (32 Red), bet 38 pays -1';
+is payout( 31, 39 ), 2, 'Spin 31 (32 Red), bet 39 pays 2';
+is payout( 31, 40 ), -1, 'Spin 31 (32 Red), bet 40 pays -1';
+is payout( 31, 41 ), 2, 'Spin 31 (32 Red), bet 41 pays 2';
+is payout( 31, 42 ), -1, 'Spin 31 (32 Red), bet 42 pays -1';
+is payout( 31, 43 ), -1, 'Spin 31 (32 Red), bet 43 pays -1';
+is payout( 31, 44 ), 1, 'Spin 31 (32 Red), bet 44 pays 1';
+is payout( 31, 45 ), 1, 'Spin 31 (32 Red), bet 45 pays 1';
+is payout( 31, 46 ), -1, 'Spin 31 (32 Red), bet 46 pays -1';
+is payout( 31, 47 ), 1, 'Spin 31 (32 Red), bet 47 pays 1';
+is payout( 31, 48 ), -1, 'Spin 31 (32 Red), bet 48 pays -1';
+is payout( 31, 49 ), -1, 'Spin 31 (32 Red), bet 49 pays -1';
+is payout( 31, 50 ), -1, 'Spin 31 (32 Red), bet 50 pays -1';
+is format_spin( 32 ), '33 Black', 'Spin 32 is 33 Black';
+is payout( 32, 1 ), -1, 'Spin 32 (33 Black), bet 1 pays -1';
+is payout( 32, 2 ), -1, 'Spin 32 (33 Black), bet 2 pays -1';
+is payout( 32, 3 ), -1, 'Spin 32 (33 Black), bet 3 pays -1';
+is payout( 32, 4 ), -1, 'Spin 32 (33 Black), bet 4 pays -1';
+is payout( 32, 5 ), -1, 'Spin 32 (33 Black), bet 5 pays -1';
+is payout( 32, 6 ), -1, 'Spin 32 (33 Black), bet 6 pays -1';
+is payout( 32, 7 ), -1, 'Spin 32 (33 Black), bet 7 pays -1';
+is payout( 32, 8 ), -1, 'Spin 32 (33 Black), bet 8 pays -1';
+is payout( 32, 9 ), -1, 'Spin 32 (33 Black), bet 9 pays -1';
+is payout( 32, 10 ), -1, 'Spin 32 (33 Black), bet 10 pays -1';
+is payout( 32, 11 ), -1, 'Spin 32 (33 Black), bet 11 pays -1';
+is payout( 32, 12 ), -1, 'Spin 32 (33 Black), bet 12 pays -1';
+is payout( 32, 13 ), -1, 'Spin 32 (33 Black), bet 13 pays -1';
+is payout( 32, 14 ), -1, 'Spin 32 (33 Black), bet 14 pays -1';
+is payout( 32, 15 ), -1, 'Spin 32 (33 Black), bet 15 pays -1';
+is payout( 32, 16 ), -1, 'Spin 32 (33 Black), bet 16 pays -1';
+is payout( 32, 17 ), -1, 'Spin 32 (33 Black), bet 17 pays -1';
+is payout( 32, 18 ), -1, 'Spin 32 (33 Black), bet 18 pays -1';
+is payout( 32, 19 ), -1, 'Spin 32 (33 Black), bet 19 pays -1';
+is payout( 32, 20 ), -1, 'Spin 32 (33 Black), bet 20 pays -1';
+is payout( 32, 21 ), -1, 'Spin 32 (33 Black), bet 21 pays -1';
+is payout( 32, 22 ), -1, 'Spin 32 (33 Black), bet 22 pays -1';
+is payout( 32, 23 ), -1, 'Spin 32 (33 Black), bet 23 pays -1';
+is payout( 32, 24 ), -1, 'Spin 32 (33 Black), bet 24 pays -1';
+is payout( 32, 25 ), -1, 'Spin 32 (33 Black), bet 25 pays -1';
+is payout( 32, 26 ), -1, 'Spin 32 (33 Black), bet 26 pays -1';
+is payout( 32, 27 ), -1, 'Spin 32 (33 Black), bet 27 pays -1';
+is payout( 32, 28 ), -1, 'Spin 32 (33 Black), bet 28 pays -1';
+is payout( 32, 29 ), -1, 'Spin 32 (33 Black), bet 29 pays -1';
+is payout( 32, 30 ), -1, 'Spin 32 (33 Black), bet 30 pays -1';
+is payout( 32, 31 ), -1, 'Spin 32 (33 Black), bet 31 pays -1';
+is payout( 32, 32 ), -1, 'Spin 32 (33 Black), bet 32 pays -1';
+is payout( 32, 33 ), 35, 'Spin 32 (33 Black), bet 33 pays 35';
+is payout( 32, 34 ), -1, 'Spin 32 (33 Black), bet 34 pays -1';
+is payout( 32, 35 ), -1, 'Spin 32 (33 Black), bet 35 pays -1';
+is payout( 32, 36 ), -1, 'Spin 32 (33 Black), bet 36 pays -1';
+is payout( 32, 37 ), -1, 'Spin 32 (33 Black), bet 37 pays -1';
+is payout( 32, 38 ), -1, 'Spin 32 (33 Black), bet 38 pays -1';
+is payout( 32, 39 ), 2, 'Spin 32 (33 Black), bet 39 pays 2';
+is payout( 32, 40 ), -1, 'Spin 32 (33 Black), bet 40 pays -1';
+is payout( 32, 41 ), -1, 'Spin 32 (33 Black), bet 41 pays -1';
+is payout( 32, 42 ), 2, 'Spin 32 (33 Black), bet 42 pays 2';
+is payout( 32, 43 ), -1, 'Spin 32 (33 Black), bet 43 pays -1';
+is payout( 32, 44 ), 1, 'Spin 32 (33 Black), bet 44 pays 1';
+is payout( 32, 45 ), -1, 'Spin 32 (33 Black), bet 45 pays -1';
+is payout( 32, 46 ), 1, 'Spin 32 (33 Black), bet 46 pays 1';
+is payout( 32, 47 ), -1, 'Spin 32 (33 Black), bet 47 pays -1';
+is payout( 32, 48 ), 1, 'Spin 32 (33 Black), bet 48 pays 1';
+is payout( 32, 49 ), -1, 'Spin 32 (33 Black), bet 49 pays -1';
+is payout( 32, 50 ), -1, 'Spin 32 (33 Black), bet 50 pays -1';
+is format_spin( 33 ), '34 Red', 'Spin 33 is 34 Red';
+is payout( 33, 1 ), -1, 'Spin 33 (34 Red), bet 1 pays -1';
+is payout( 33, 2 ), -1, 'Spin 33 (34 Red), bet 2 pays -1';
+is payout( 33, 3 ), -1, 'Spin 33 (34 Red), bet 3 pays -1';
+is payout( 33, 4 ), -1, 'Spin 33 (34 Red), bet 4 pays -1';
+is payout( 33, 5 ), -1, 'Spin 33 (34 Red), bet 5 pays -1';
+is payout( 33, 6 ), -1, 'Spin 33 (34 Red), bet 6 pays -1';
+is payout( 33, 7 ), -1, 'Spin 33 (34 Red), bet 7 pays -1';
+is payout( 33, 8 ), -1, 'Spin 33 (34 Red), bet 8 pays -1';
+is payout( 33, 9 ), -1, 'Spin 33 (34 Red), bet 9 pays -1';
+is payout( 33, 10 ), -1, 'Spin 33 (34 Red), bet 10 pays -1';
+is payout( 33, 11 ), -1, 'Spin 33 (34 Red), bet 11 pays -1';
+is payout( 33, 12 ), -1, 'Spin 33 (34 Red), bet 12 pays -1';
+is payout( 33, 13 ), -1, 'Spin 33 (34 Red), bet 13 pays -1';
+is payout( 33, 14 ), -1, 'Spin 33 (34 Red), bet 14 pays -1';
+is payout( 33, 15 ), -1, 'Spin 33 (34 Red), bet 15 pays -1';
+is payout( 33, 16 ), -1, 'Spin 33 (34 Red), bet 16 pays -1';
+is payout( 33, 17 ), -1, 'Spin 33 (34 Red), bet 17 pays -1';
+is payout( 33, 18 ), -1, 'Spin 33 (34 Red), bet 18 pays -1';
+is payout( 33, 19 ), -1, 'Spin 33 (34 Red), bet 19 pays -1';
+is payout( 33, 20 ), -1, 'Spin 33 (34 Red), bet 20 pays -1';
+is payout( 33, 21 ), -1, 'Spin 33 (34 Red), bet 21 pays -1';
+is payout( 33, 22 ), -1, 'Spin 33 (34 Red), bet 22 pays -1';
+is payout( 33, 23 ), -1, 'Spin 33 (34 Red), bet 23 pays -1';
+is payout( 33, 24 ), -1, 'Spin 33 (34 Red), bet 24 pays -1';
+is payout( 33, 25 ), -1, 'Spin 33 (34 Red), bet 25 pays -1';
+is payout( 33, 26 ), -1, 'Spin 33 (34 Red), bet 26 pays -1';
+is payout( 33, 27 ), -1, 'Spin 33 (34 Red), bet 27 pays -1';
+is payout( 33, 28 ), -1, 'Spin 33 (34 Red), bet 28 pays -1';
+is payout( 33, 29 ), -1, 'Spin 33 (34 Red), bet 29 pays -1';
+is payout( 33, 30 ), -1, 'Spin 33 (34 Red), bet 30 pays -1';
+is payout( 33, 31 ), -1, 'Spin 33 (34 Red), bet 31 pays -1';
+is payout( 33, 32 ), -1, 'Spin 33 (34 Red), bet 32 pays -1';
+is payout( 33, 33 ), -1, 'Spin 33 (34 Red), bet 33 pays -1';
+is payout( 33, 34 ), 35, 'Spin 33 (34 Red), bet 34 pays 35';
+is payout( 33, 35 ), -1, 'Spin 33 (34 Red), bet 35 pays -1';
+is payout( 33, 36 ), -1, 'Spin 33 (34 Red), bet 36 pays -1';
+is payout( 33, 37 ), -1, 'Spin 33 (34 Red), bet 37 pays -1';
+is payout( 33, 38 ), -1, 'Spin 33 (34 Red), bet 38 pays -1';
+is payout( 33, 39 ), 2, 'Spin 33 (34 Red), bet 39 pays 2';
+is payout( 33, 40 ), 2, 'Spin 33 (34 Red), bet 40 pays 2';
+is payout( 33, 41 ), -1, 'Spin 33 (34 Red), bet 41 pays -1';
+is payout( 33, 42 ), -1, 'Spin 33 (34 Red), bet 42 pays -1';
+is payout( 33, 43 ), -1, 'Spin 33 (34 Red), bet 43 pays -1';
+is payout( 33, 44 ), 1, 'Spin 33 (34 Red), bet 44 pays 1';
+is payout( 33, 45 ), 1, 'Spin 33 (34 Red), bet 45 pays 1';
+is payout( 33, 46 ), -1, 'Spin 33 (34 Red), bet 46 pays -1';
+is payout( 33, 47 ), 1, 'Spin 33 (34 Red), bet 47 pays 1';
+is payout( 33, 48 ), -1, 'Spin 33 (34 Red), bet 48 pays -1';
+is payout( 33, 49 ), -1, 'Spin 33 (34 Red), bet 49 pays -1';
+is payout( 33, 50 ), -1, 'Spin 33 (34 Red), bet 50 pays -1';
+is format_spin( 34 ), '35 Black', 'Spin 34 is 35 Black';
+is payout( 34, 1 ), -1, 'Spin 34 (35 Black), bet 1 pays -1';
+is payout( 34, 2 ), -1, 'Spin 34 (35 Black), bet 2 pays -1';
+is payout( 34, 3 ), -1, 'Spin 34 (35 Black), bet 3 pays -1';
+is payout( 34, 4 ), -1, 'Spin 34 (35 Black), bet 4 pays -1';
+is payout( 34, 5 ), -1, 'Spin 34 (35 Black), bet 5 pays -1';
+is payout( 34, 6 ), -1, 'Spin 34 (35 Black), bet 6 pays -1';
+is payout( 34, 7 ), -1, 'Spin 34 (35 Black), bet 7 pays -1';
+is payout( 34, 8 ), -1, 'Spin 34 (35 Black), bet 8 pays -1';
+is payout( 34, 9 ), -1, 'Spin 34 (35 Black), bet 9 pays -1';
+is payout( 34, 10 ), -1, 'Spin 34 (35 Black), bet 10 pays -1';
+is payout( 34, 11 ), -1, 'Spin 34 (35 Black), bet 11 pays -1';
+is payout( 34, 12 ), -1, 'Spin 34 (35 Black), bet 12 pays -1';
+is payout( 34, 13 ), -1, 'Spin 34 (35 Black), bet 13 pays -1';
+is payout( 34, 14 ), -1, 'Spin 34 (35 Black), bet 14 pays -1';
+is payout( 34, 15 ), -1, 'Spin 34 (35 Black), bet 15 pays -1';
+is payout( 34, 16 ), -1, 'Spin 34 (35 Black), bet 16 pays -1';
+is payout( 34, 17 ), -1, 'Spin 34 (35 Black), bet 17 pays -1';
+is payout( 34, 18 ), -1, 'Spin 34 (35 Black), bet 18 pays -1';
+is payout( 34, 19 ), -1, 'Spin 34 (35 Black), bet 19 pays -1';
+is payout( 34, 20 ), -1, 'Spin 34 (35 Black), bet 20 pays -1';
+is payout( 34, 21 ), -1, 'Spin 34 (35 Black), bet 21 pays -1';
+is payout( 34, 22 ), -1, 'Spin 34 (35 Black), bet 22 pays -1';
+is payout( 34, 23 ), -1, 'Spin 34 (35 Black), bet 23 pays -1';
+is payout( 34, 24 ), -1, 'Spin 34 (35 Black), bet 24 pays -1';
+is payout( 34, 25 ), -1, 'Spin 34 (35 Black), bet 25 pays -1';
+is payout( 34, 26 ), -1, 'Spin 34 (35 Black), bet 26 pays -1';
+is payout( 34, 27 ), -1, 'Spin 34 (35 Black), bet 27 pays -1';
+is payout( 34, 28 ), -1, 'Spin 34 (35 Black), bet 28 pays -1';
+is payout( 34, 29 ), -1, 'Spin 34 (35 Black), bet 29 pays -1';
+is payout( 34, 30 ), -1, 'Spin 34 (35 Black), bet 30 pays -1';
+is payout( 34, 31 ), -1, 'Spin 34 (35 Black), bet 31 pays -1';
+is payout( 34, 32 ), -1, 'Spin 34 (35 Black), bet 32 pays -1';
+is payout( 34, 33 ), -1, 'Spin 34 (35 Black), bet 33 pays -1';
+is payout( 34, 34 ), -1, 'Spin 34 (35 Black), bet 34 pays -1';
+is payout( 34, 35 ), 35, 'Spin 34 (35 Black), bet 35 pays 35';
+is payout( 34, 36 ), -1, 'Spin 34 (35 Black), bet 36 pays -1';
+is payout( 34, 37 ), -1, 'Spin 34 (35 Black), bet 37 pays -1';
+is payout( 34, 38 ), -1, 'Spin 34 (35 Black), bet 38 pays -1';
+is payout( 34, 39 ), 2, 'Spin 34 (35 Black), bet 39 pays 2';
+is payout( 34, 40 ), -1, 'Spin 34 (35 Black), bet 40 pays -1';
+is payout( 34, 41 ), 2, 'Spin 34 (35 Black), bet 41 pays 2';
+is payout( 34, 42 ), -1, 'Spin 34 (35 Black), bet 42 pays -1';
+is payout( 34, 43 ), -1, 'Spin 34 (35 Black), bet 43 pays -1';
+is payout( 34, 44 ), 1, 'Spin 34 (35 Black), bet 44 pays 1';
+is payout( 34, 45 ), -1, 'Spin 34 (35 Black), bet 45 pays -1';
+is payout( 34, 46 ), 1, 'Spin 34 (35 Black), bet 46 pays 1';
+is payout( 34, 47 ), -1, 'Spin 34 (35 Black), bet 47 pays -1';
+is payout( 34, 48 ), 1, 'Spin 34 (35 Black), bet 48 pays 1';
+is payout( 34, 49 ), -1, 'Spin 34 (35 Black), bet 49 pays -1';
+is payout( 34, 50 ), -1, 'Spin 34 (35 Black), bet 50 pays -1';
+is format_spin( 35 ), '36 Red', 'Spin 35 is 36 Red';
+is payout( 35, 1 ), -1, 'Spin 35 (36 Red), bet 1 pays -1';
+is payout( 35, 2 ), -1, 'Spin 35 (36 Red), bet 2 pays -1';
+is payout( 35, 3 ), -1, 'Spin 35 (36 Red), bet 3 pays -1';
+is payout( 35, 4 ), -1, 'Spin 35 (36 Red), bet 4 pays -1';
+is payout( 35, 5 ), -1, 'Spin 35 (36 Red), bet 5 pays -1';
+is payout( 35, 6 ), -1, 'Spin 35 (36 Red), bet 6 pays -1';
+is payout( 35, 7 ), -1, 'Spin 35 (36 Red), bet 7 pays -1';
+is payout( 35, 8 ), -1, 'Spin 35 (36 Red), bet 8 pays -1';
+is payout( 35, 9 ), -1, 'Spin 35 (36 Red), bet 9 pays -1';
+is payout( 35, 10 ), -1, 'Spin 35 (36 Red), bet 10 pays -1';
+is payout( 35, 11 ), -1, 'Spin 35 (36 Red), bet 11 pays -1';
+is payout( 35, 12 ), -1, 'Spin 35 (36 Red), bet 12 pays -1';
+is payout( 35, 13 ), -1, 'Spin 35 (36 Red), bet 13 pays -1';
+is payout( 35, 14 ), -1, 'Spin 35 (36 Red), bet 14 pays -1';
+is payout( 35, 15 ), -1, 'Spin 35 (36 Red), bet 15 pays -1';
+is payout( 35, 16 ), -1, 'Spin 35 (36 Red), bet 16 pays -1';
+is payout( 35, 17 ), -1, 'Spin 35 (36 Red), bet 17 pays -1';
+is payout( 35, 18 ), -1, 'Spin 35 (36 Red), bet 18 pays -1';
+is payout( 35, 19 ), -1, 'Spin 35 (36 Red), bet 19 pays -1';
+is payout( 35, 20 ), -1, 'Spin 35 (36 Red), bet 20 pays -1';
+is payout( 35, 21 ), -1, 'Spin 35 (36 Red), bet 21 pays -1';
+is payout( 35, 22 ), -1, 'Spin 35 (36 Red), bet 22 pays -1';
+is payout( 35, 23 ), -1, 'Spin 35 (36 Red), bet 23 pays -1';
+is payout( 35, 24 ), -1, 'Spin 35 (36 Red), bet 24 pays -1';
+is payout( 35, 25 ), -1, 'Spin 35 (36 Red), bet 25 pays -1';
+is payout( 35, 26 ), -1, 'Spin 35 (36 Red), bet 26 pays -1';
+is payout( 35, 27 ), -1, 'Spin 35 (36 Red), bet 27 pays -1';
+is payout( 35, 28 ), -1, 'Spin 35 (36 Red), bet 28 pays -1';
+is payout( 35, 29 ), -1, 'Spin 35 (36 Red), bet 29 pays -1';
+is payout( 35, 30 ), -1, 'Spin 35 (36 Red), bet 30 pays -1';
+is payout( 35, 31 ), -1, 'Spin 35 (36 Red), bet 31 pays -1';
+is payout( 35, 32 ), -1, 'Spin 35 (36 Red), bet 32 pays -1';
+is payout( 35, 33 ), -1, 'Spin 35 (36 Red), bet 33 pays -1';
+is payout( 35, 34 ), -1, 'Spin 35 (36 Red), bet 34 pays -1';
+is payout( 35, 35 ), -1, 'Spin 35 (36 Red), bet 35 pays -1';
+is payout( 35, 36 ), 35, 'Spin 35 (36 Red), bet 36 pays 35';
+is payout( 35, 37 ), -1, 'Spin 35 (36 Red), bet 37 pays -1';
+is payout( 35, 38 ), -1, 'Spin 35 (36 Red), bet 38 pays -1';
+is payout( 35, 39 ), 2, 'Spin 35 (36 Red), bet 39 pays 2';
+is payout( 35, 40 ), -1, 'Spin 35 (36 Red), bet 40 pays -1';
+is payout( 35, 41 ), -1, 'Spin 35 (36 Red), bet 41 pays -1';
+is payout( 35, 42 ), 2, 'Spin 35 (36 Red), bet 42 pays 2';
+is payout( 35, 43 ), -1, 'Spin 35 (36 Red), bet 43 pays -1';
+is payout( 35, 44 ), 1, 'Spin 35 (36 Red), bet 44 pays 1';
+is payout( 35, 45 ), 1, 'Spin 35 (36 Red), bet 45 pays 1';
+is payout( 35, 46 ), -1, 'Spin 35 (36 Red), bet 46 pays -1';
+is payout( 35, 47 ), 1, 'Spin 35 (36 Red), bet 47 pays 1';
+is payout( 35, 48 ), -1, 'Spin 35 (36 Red), bet 48 pays -1';
+is payout( 35, 49 ), -1, 'Spin 35 (36 Red), bet 49 pays -1';
+is payout( 35, 50 ), -1, 'Spin 35 (36 Red), bet 50 pays -1';
+is format_spin( 36 ), '0', 'Spin 36 is 0';
+is payout( 36, 1 ), -1, 'Spin 36 (0), bet 1 pays -1';
+is payout( 36, 2 ), -1, 'Spin 36 (0), bet 2 pays -1';
+is payout( 36, 3 ), -1, 'Spin 36 (0), bet 3 pays -1';
+is payout( 36, 4 ), -1, 'Spin 36 (0), bet 4 pays -1';
+is payout( 36, 5 ), -1, 'Spin 36 (0), bet 5 pays -1';
+is payout( 36, 6 ), -1, 'Spin 36 (0), bet 6 pays -1';
+is payout( 36, 7 ), -1, 'Spin 36 (0), bet 7 pays -1';
+is payout( 36, 8 ), -1, 'Spin 36 (0), bet 8 pays -1';
+is payout( 36, 9 ), -1, 'Spin 36 (0), bet 9 pays -1';
+is payout( 36, 10 ), -1, 'Spin 36 (0), bet 10 pays -1';
+is payout( 36, 11 ), -1, 'Spin 36 (0), bet 11 pays -1';
+is payout( 36, 12 ), -1, 'Spin 36 (0), bet 12 pays -1';
+is payout( 36, 13 ), -1, 'Spin 36 (0), bet 13 pays -1';
+is payout( 36, 14 ), -1, 'Spin 36 (0), bet 14 pays -1';
+is payout( 36, 15 ), -1, 'Spin 36 (0), bet 15 pays -1';
+is payout( 36, 16 ), -1, 'Spin 36 (0), bet 16 pays -1';
+is payout( 36, 17 ), -1, 'Spin 36 (0), bet 17 pays -1';
+is payout( 36, 18 ), -1, 'Spin 36 (0), bet 18 pays -1';
+is payout( 36, 19 ), -1, 'Spin 36 (0), bet 19 pays -1';
+is payout( 36, 20 ), -1, 'Spin 36 (0), bet 20 pays -1';
+is payout( 36, 21 ), -1, 'Spin 36 (0), bet 21 pays -1';
+is payout( 36, 22 ), -1, 'Spin 36 (0), bet 22 pays -1';
+is payout( 36, 23 ), -1, 'Spin 36 (0), bet 23 pays -1';
+is payout( 36, 24 ), -1, 'Spin 36 (0), bet 24 pays -1';
+is payout( 36, 25 ), -1, 'Spin 36 (0), bet 25 pays -1';
+is payout( 36, 26 ), -1, 'Spin 36 (0), bet 26 pays -1';
+is payout( 36, 27 ), -1, 'Spin 36 (0), bet 27 pays -1';
+is payout( 36, 28 ), -1, 'Spin 36 (0), bet 28 pays -1';
+is payout( 36, 29 ), -1, 'Spin 36 (0), bet 29 pays -1';
+is payout( 36, 30 ), -1, 'Spin 36 (0), bet 30 pays -1';
+is payout( 36, 31 ), -1, 'Spin 36 (0), bet 31 pays -1';
+is payout( 36, 32 ), -1, 'Spin 36 (0), bet 32 pays -1';
+is payout( 36, 33 ), -1, 'Spin 36 (0), bet 33 pays -1';
+is payout( 36, 34 ), -1, 'Spin 36 (0), bet 34 pays -1';
+is payout( 36, 35 ), -1, 'Spin 36 (0), bet 35 pays -1';
+is payout( 36, 36 ), -1, 'Spin 36 (0), bet 36 pays -1';
+is payout( 36, 37 ), -1, 'Spin 36 (0), bet 37 pays -1';
+is payout( 36, 38 ), -1, 'Spin 36 (0), bet 38 pays -1';
+is payout( 36, 39 ), -1, 'Spin 36 (0), bet 39 pays -1';
+is payout( 36, 40 ), -1, 'Spin 36 (0), bet 40 pays -1';
+is payout( 36, 41 ), -1, 'Spin 36 (0), bet 41 pays -1';
+is payout( 36, 42 ), -1, 'Spin 36 (0), bet 42 pays -1';
+is payout( 36, 43 ), -1, 'Spin 36 (0), bet 43 pays -1';
+is payout( 36, 44 ), -1, 'Spin 36 (0), bet 44 pays -1';
+is payout( 36, 45 ), -1, 'Spin 36 (0), bet 45 pays -1';
+is payout( 36, 46 ), -1, 'Spin 36 (0), bet 46 pays -1';
+is payout( 36, 47 ), -1, 'Spin 36 (0), bet 47 pays -1';
+is payout( 36, 48 ), -1, 'Spin 36 (0), bet 48 pays -1';
+is payout( 36, 49 ), 35, 'Spin 36 (0), bet 49 pays 35';
+is payout( 36, 50 ), -1, 'Spin 36 (0), bet 50 pays -1';
+is format_spin( 37 ), '00', 'Spin 37 is 00';
+is payout( 37, 1 ), -1, 'Spin 37 (00), bet 1 pays -1';
+is payout( 37, 2 ), -1, 'Spin 37 (00), bet 2 pays -1';
+is payout( 37, 3 ), -1, 'Spin 37 (00), bet 3 pays -1';
+is payout( 37, 4 ), -1, 'Spin 37 (00), bet 4 pays -1';
+is payout( 37, 5 ), -1, 'Spin 37 (00), bet 5 pays -1';
+is payout( 37, 6 ), -1, 'Spin 37 (00), bet 6 pays -1';
+is payout( 37, 7 ), -1, 'Spin 37 (00), bet 7 pays -1';
+is payout( 37, 8 ), -1, 'Spin 37 (00), bet 8 pays -1';
+is payout( 37, 9 ), -1, 'Spin 37 (00), bet 9 pays -1';
+is payout( 37, 10 ), -1, 'Spin 37 (00), bet 10 pays -1';
+is payout( 37, 11 ), -1, 'Spin 37 (00), bet 11 pays -1';
+is payout( 37, 12 ), -1, 'Spin 37 (00), bet 12 pays -1';
+is payout( 37, 13 ), -1, 'Spin 37 (00), bet 13 pays -1';
+is payout( 37, 14 ), -1, 'Spin 37 (00), bet 14 pays -1';
+is payout( 37, 15 ), -1, 'Spin 37 (00), bet 15 pays -1';
+is payout( 37, 16 ), -1, 'Spin 37 (00), bet 16 pays -1';
+is payout( 37, 17 ), -1, 'Spin 37 (00), bet 17 pays -1';
+is payout( 37, 18 ), -1, 'Spin 37 (00), bet 18 pays -1';
+is payout( 37, 19 ), -1, 'Spin 37 (00), bet 19 pays -1';
+is payout( 37, 20 ), -1, 'Spin 37 (00), bet 20 pays -1';
+is payout( 37, 21 ), -1, 'Spin 37 (00), bet 21 pays -1';
+is payout( 37, 22 ), -1, 'Spin 37 (00), bet 22 pays -1';
+is payout( 37, 23 ), -1, 'Spin 37 (00), bet 23 pays -1';
+is payout( 37, 24 ), -1, 'Spin 37 (00), bet 24 pays -1';
+is payout( 37, 25 ), -1, 'Spin 37 (00), bet 25 pays -1';
+is payout( 37, 26 ), -1, 'Spin 37 (00), bet 26 pays -1';
+is payout( 37, 27 ), -1, 'Spin 37 (00), bet 27 pays -1';
+is payout( 37, 28 ), -1, 'Spin 37 (00), bet 28 pays -1';
+is payout( 37, 29 ), -1, 'Spin 37 (00), bet 29 pays -1';
+is payout( 37, 30 ), -1, 'Spin 37 (00), bet 30 pays -1';
+is payout( 37, 31 ), -1, 'Spin 37 (00), bet 31 pays -1';
+is payout( 37, 32 ), -1, 'Spin 37 (00), bet 32 pays -1';
+is payout( 37, 33 ), -1, 'Spin 37 (00), bet 33 pays -1';
+is payout( 37, 34 ), -1, 'Spin 37 (00), bet 34 pays -1';
+is payout( 37, 35 ), -1, 'Spin 37 (00), bet 35 pays -1';
+is payout( 37, 36 ), -1, 'Spin 37 (00), bet 36 pays -1';
+is payout( 37, 37 ), -1, 'Spin 37 (00), bet 37 pays -1';
+is payout( 37, 38 ), -1, 'Spin 37 (00), bet 38 pays -1';
+is payout( 37, 39 ), -1, 'Spin 37 (00), bet 39 pays -1';
+is payout( 37, 40 ), -1, 'Spin 37 (00), bet 40 pays -1';
+is payout( 37, 41 ), -1, 'Spin 37 (00), bet 41 pays -1';
+is payout( 37, 42 ), -1, 'Spin 37 (00), bet 42 pays -1';
+is payout( 37, 43 ), -1, 'Spin 37 (00), bet 43 pays -1';
+is payout( 37, 44 ), -1, 'Spin 37 (00), bet 44 pays -1';
+is payout( 37, 45 ), -1, 'Spin 37 (00), bet 45 pays -1';
+is payout( 37, 46 ), -1, 'Spin 37 (00), bet 46 pays -1';
+is payout( 37, 47 ), -1, 'Spin 37 (00), bet 47 pays -1';
+is payout( 37, 48 ), -1, 'Spin 37 (00), bet 48 pays -1';
+is payout( 37, 49 ), -1, 'Spin 37 (00), bet 49 pays -1';
+is payout( 37, 50 ), 35, 'Spin 37 (00), bet 50 pays 35';
+
+done_testing;
+
+1;
+
+# ex: set textwidth=72 :
diff --git a/75_Roulette/perl/roulette.pl b/75_Roulette/perl/roulette.pl
new file mode 100755
index 00000000..ad3ce965
--- /dev/null
+++ b/75_Roulette/perl/roulette.pl
@@ -0,0 +1,319 @@
+#!/usr/bin/env perl
+
+use 5.010; # To get 'state' and 'say'
+
+use strict; # Require explicit declaration of variables
+use warnings; # Enable optional compiler warnings
+
+use English; # Use more friendly names for Perl's magic variables
+use POSIX qw{ strftime }; # Time formatting
+use Term::ReadLine; # Prompt and return user input
+
+our $VERSION = '0.000_01';
+
+# A main() function is not usual in Perl scripts. I have installed one
+# here to make the script into a "modulino." The next line executes
+# main() if and only if caller() returns false. It will do this if we
+# were loaded by another Perl script but not otherwise. This was done so
+# I could test the payout and spin formatting logic.
+main() unless caller;
+
+sub main {
+
+ print <<'EOD';
+ ROULETTE
+ Creative Computing Morristown, New Jersey
+
+
+
+
+Welcome to the roulette table.
+
+EOD
+
+ if ( get_yes_no( 'Do you want instructions' ) ) {
+ print <<'EOD';
+
+This is the betting layout
+ (*=red)
+
+ 1* 2 3*
+ 4 5* 6
+ 7* 8 9*
+10 11 12*
+---------------
+13 14* 15
+16* 17 18*
+19* 20 21*
+22 23* 24
+---------------
+25* 26 27*
+28 29 30*
+31 32* 33
+34* 35 36*
+---------------
+ 00 0
+
+Types of bets:
+
+The numbers 1 to 36 signify a straight bet
+on that number.
+These pay off 35:1
+
+The 2:1 bets are:
+ 37) 1-12 40) first column
+ 38) 13-24 41) second column
+ 39) 25-36 42) third column
+
+The even money bets are:
+ 43) 1-18 46) odd
+ 44) 19-36 47) red
+ 45) even 48) black
+
+ 49) 0 and 50) 00 pay off 35:1
+ Note: 0 and 00 do not count under any
+ bets except their own.
+
+When I ask for each bet, type the number
+and the amount, separated by a comma.
+For example: to bet $500 on black, type 48,500
+when I ask for a bet.
+
+The minimum bet is $5, the maximum is $500.
+
+EOD
+}
+
+ my $P = 1000;
+ my $D = 100000.;
+
+ while ( 1 ) { # Iterate indefinitely
+
+ my $Y = get_input( 'How many bets? ',
+ sub { m/ \A [0-9]+ \z /smx && $ARG > 0 && $ARG <= 50 },
+ "Please enter a positive integer no greater than 50\n",
+ );
+ my @B;
+ my @T;
+ foreach my $C ( 1 .. $Y ) {
+ my ( $X, $Z ) = split qr< , >smx, get_input(
+ "Number $C: ",
+ sub { m/ \A ( [0-9]+ ) , ( [0-9]+ ) \z /smx
+ && $1 > 0 && $1 <= 50 && $2 >= 5 && $2 <= 500 },
+ "Please enter two comma-separated positive numbers\n",
+ );
+ if ( $B[$X] ) {
+ say 'You made that bet once already, dum-dum.';
+ redo;
+ }
+ $B[$X] = $Z; # BASIC does $B[$C] = $Z
+ $T[$C] = $X;
+ }
+
+ print <<'EOD';
+
+ Spinning ...
+
+EOD
+ my $S = int rand 38; # Zero-based, versus 1-based in BASIC
+
+ say format_spin( $S );
+
+ say '';
+
+ foreach my $C ( 1 .. $Y ) {
+ my $X = $T[$C];
+ my $payout = payout( $S, $X ) * $B[$X];
+ $D -= $payout;
+ $P += $payout;
+ if ( $payout > 0 ) {
+ say "You win $payout dollars on bet $C";
+ } else {
+ $payout = -$payout;
+ say "You lose $payout dollars on bet $C";
+ }
+ }
+ say "Totals\tMe\tYou";
+ say "\t$D\t$P";
+ say '';
+
+
+ last unless get_yes_no( 'Again' );
+ }
+
+ say '';
+
+ if ( $P > 0 ) {
+ my $B = get_input(
+ 'To whom shall I make out the check? ',
+ );
+ my $check_number = 1000 + int rand 9000;
+ my $todays_date = strftime( '%B %d, %Y', localtime );
+ print <<"EOD";
+
+------------------------------------------------------------ Check number $check_number
+
+ $todays_date
+
+Pay to the order of ------ $B ----- \$$P
+
+ The Memory Bank of New York
+
+ The Computer
+ ---------X-----
+
+-------------------------------------------------------------------------------
+
+Come back soon!
+EOD
+ } else {
+ print <<'EOD';
+Thanks for your money.
+I'll use it to buy a solid gold roulette wheel
+EOD
+ }
+}
+
+{
+ # Define the kind of each possible spin. 0 is '0' or '00', 1 is
+ # black, and 2 is red. We assign the values in a BEGIN block because
+ # execution never actually reaches this point in the script.
+ my @kind;
+ BEGIN {
+ @kind = ( 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1,
+ 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 0,
+ 0 );
+ }
+
+ # Convert the spin (0-37) to its name on the wheel.
+ sub format_spin {
+ my ( $number ) = @_;
+ state $format = [
+ sub { '0' x ( $_[0] - 35 ) },
+ sub { sprintf '%s Black', $_[0] + 1 },
+ sub { sprintf '%s Red', $_[0] + 1 },
+ ];
+ return $format->[$kind[$number]]( $number );
+ }
+
+ # Compute the payout given the spin (0-37) and the bet (1-50).
+ sub payout {
+ my ( $number, $bet ) = @_;
+ # We compute the payout on '0' and '00' directly, since under
+ # our rules they are only eligible for the 35-to-1 bet.
+ $kind[$number]
+ or return $number == $bet - 49 + 36 ? 35 : -1;
+ --$bet; # #bet is 1-based coming in
+ # Dispatch table for computing the payout for spins 0-36.
+ state $payout = [
+ ( sub { $_[0] == $_[1] ? 35 : -1 } ) x 36,
+ ( sub { int( $_[0] / 12 ) == $_[1] - 36 ? 2 : -1 } ) x 3,
+ ( sub { $_[0] % 3 == $_[1] - 39 ? 2 : -1 } ) x 3,
+ ( sub { int( $_[0] / 18 ) == $_[1] - 42 ? 1 : -1 } ) x 2,
+ ( sub { $_[0] % 2 == 45 - $_[1] ? 1 : -1 } ) x 2,
+ ( sub { $kind[$_[0]] == 48 - $_[1] ? 1 : -1 } ) x 2,
+ ( sub { -1 } ) x 2, # Bet on '0' or '00' loses
+ ];
+ return $payout->[$bet]->( $number, $bet );
+ }
+}
+
+# Get input from the user. The arguments are:
+# * The prompt
+# * A reference to validation code. This code receives the response in
+# $ARG and returns true for a valid response.
+# * A warning to print if the response is not valid. This must end in a
+# return.
+# The first valid response is returned. An end-of-file terminates the
+# script.
+sub get_input {
+ my ( $prompt, $validate, $warning ) = @ARG;
+
+ # If no validator is passed, default to one that always returns
+ # true.
+ $validate ||= sub { 1 };
+
+ # Create the readline object. The 'state' causes the variable to be
+ # initialized only once, no matter how many times this subroutine is
+ # called. The do { ... } is a compound statement used because we
+ # need to tweak the created object before we store it.
+ state $term = do {
+ my $obj = Term::ReadLine->new( 'reverse' );
+ $obj->ornaments( 0 );
+ $obj;
+ };
+
+ while ( 1 ) { # Iterate indefinitely
+
+ # Read the input into the topic variable, localized to prevent
+ # Spooky Action at a Distance. We exit on undef, which signals
+ # end-of-file.
+ exit unless defined( local $ARG = $term->readline( $prompt ) );
+
+ # Return the input if it is valid.
+ return $ARG if $validate->();
+
+ # Issue the warning, and go around the merry-go-round again.
+ warn $warning;
+ }
+}
+
+# Get a yes-or-no answer. The argument is the prompt, which will have
+# '? [y/n]: ' appended. The donkey work is done by get_input(), which is
+# requested to validate the response as beginning with 'y' or 'n',
+# case-insensitive. The return is a true value for 'y' and a false value
+# for 'n'.
+sub get_yes_no {
+ my ( $prompt ) = @ARG;
+ state $map_answer = {
+ n => 0,
+ y => 1,
+ };
+ my $resp = lc get_input(
+ "$prompt? [y/n]: ",
+ sub { m/ \A [yn] /smxi },
+ "Please respond 'y' or 'n'\n",
+ );
+ return $map_answer->{ substr $resp, 0, 1 };
+}
+
+__END__
+
+=head1 TITLE
+
+roulette - Play the game 'Roulette' from Basic Computer Games
+
+=head1 SYNOPSIS
+
+ roulette.pl
+
+=head1 DETAILS
+
+This Perl script is a port of roulette, which is the 75th
+entry in Basic Computer Games.
+
+The main internal changes are converting the roulette slot numbering to
+0-based and replacing most of the payout logic with a dispatch table.
+These changes were tested for correctness against the original BASIC.
+
+=head1 PORTED BY
+
+Thomas R. Wyant, III F
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2022 by Thomas R. Wyant, III
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl 5.10.0. For more details, see the Artistic
+License 1.0 at
+L, and/or the
+Gnu GPL at L.
+
+This program is distributed in the hope that it will be useful, but
+without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
+
+=cut
+
+# ex: set expandtab tabstop=4 textwidth=72 :
diff --git a/75_Roulette/python/roulette.py b/75_Roulette/python/roulette.py
new file mode 100644
index 00000000..c839adc7
--- /dev/null
+++ b/75_Roulette/python/roulette.py
@@ -0,0 +1,213 @@
+from datetime import date
+import random
+
+global RED_NUMBERS
+RED_NUMBERS = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36]
+
+def print_instructions():
+ print("""
+THIS IS THE BETTING LAYOUT
+ (*=RED)
+
+ 1* 2 3*
+ 4 5* 6
+ 7* 8 9*
+10 11 12*
+---------------
+13 14* 15
+16* 17 18*
+19* 20 21*
+22 23* 24
+---------------
+25* 26 27*
+28 29 30*
+31 32* 33
+34* 35 36*
+---------------
+ 00 0
+
+TYPES OF BETS
+
+THE NUMBERS 1 TO 36 SIGNIFY A STRAIGHT BET
+ON THAT NUMBER.
+THESE PAY OFF 35:1
+
+THE 2:1 BETS ARE:
+37) 1-12 40) FIRST COLUMN
+38) 13-24 41) SECOND COLUMN
+39) 25-36 42) THIRD COLUMN
+
+THE EVEN MONEY BETS ARE:
+43) 1-18 46) ODD
+44) 19-36 47) RED
+45) EVEN 48) BLACK
+
+ 49)0 AND 50)00 PAY OFF 35:1
+NOTE: 0 AND 00 DO NOT COUNT UNDER ANY
+ BETS EXCEPT THEIR OWN.
+
+WHEN I ASK FOR EACH BET, TYPE THE NUMBER
+AND THE AMOUNT, SEPARATED BY A COMMA.
+FOR EXAMPLE: TO BET $500 ON BLACK, TYPE 48,500
+WHEN I ASK FOR A BET.
+
+THE MINIMUM BET IS $5, THE MAXIMUM IS $500.
+
+ """)
+
+def query_bets():
+ """Queries the user to input their bets"""
+ betCount = -1
+ while betCount <= 0:
+ try:
+ betCount = int(input("HOW MANY BETS? "))
+ except:
+ ...
+
+ bet_IDs = [-1] * betCount
+ bet_Values = [0] * betCount
+
+ for i in range(betCount):
+ while(bet_IDs[i] == -1):
+ try:
+ inString = input("NUMBER " + str(i + 1) + "? ").split(',')
+ id,val = int(inString[0]),int(inString[1])
+
+ # check other bet_IDs
+ for j in range(i):
+ if id != -1 and bet_IDs[j] == id:
+ id = -1
+ print("YOU ALREADY MADE THAT BET ONCE, DUM-DUM")
+ break
+
+ if id > 0 and id <= 50 and val >= 5 and val <= 500:
+ bet_IDs[i] = id
+ bet_Values[i] = val
+ except:
+ ...
+ return bet_IDs,bet_Values
+
+def bet_results(bet_IDs,bet_Values,result):
+ """Computes the results, prints them, and returns the total net winnings"""
+ total_winnings = 0
+ def get_modifier(id,num):
+ if id == 37 and num <= 12:
+ return 2
+ elif id == 38 and num > 12 and num <= 24:
+ return 2
+ elif id == 39 and num > 24 and num < 37:
+ return 2
+ elif id == 40 and num < 37 and num % 3 == 1:
+ return 2
+ elif id == 41 and num < 37 and num % 3 == 2:
+ return 2
+ elif id == 42 and num < 37 and num % 3 == 0:
+ return 2
+ elif id == 43 and num <= 18:
+ return 1
+ elif id == 44 and num > 18 and num <= 36:
+ return 1
+ elif id == 45 and num % 2 == 0:
+ return 1
+ elif id == 46 and num % 2 == 1:
+ return 1
+ elif id == 47 and num in RED_NUMBERS:
+ return 1
+ elif id == 48 and num not in RED_NUMBERS:
+ return 1
+ elif id < 37 and id == num:
+ return 35
+ else:
+ return -1
+
+ for i in range(len(bet_IDs)):
+ winnings = bet_Values[i] * get_modifier(bet_IDs[i],result)
+ total_winnings += winnings
+
+ if winnings >= 0:
+ print("YOU WIN " + str(winnings) + " DOLLARS ON BET " + str(i + 1))
+ else:
+ print("YOU LOSE " + str(winnings * -1) + " DOLLARS ON BET " + str(i + 1))
+
+ return winnings
+
+def print_check(amount):
+ """Prints a check of a given amount"""
+ name = input("TO WHOM SHALL I MAKE THE CHECK? ")
+
+ print("-" * 72)
+ print()
+ print(" " * 40 + "CHECK NO. " + str(random.randint(0,100)))
+ print(" " * 40 + str(date.today()))
+ print()
+ print("PAY TO THE ORDER OF -----" + name + "----- $" + str(amount))
+ print()
+ print(" " * 40 + "THE MEMORY BANK OF NEW YORK")
+ print(" " * 40 + "THE COMPUTER")
+ print(" " * 40 + "----------X-----")
+ print("-" * 72)
+
+def main():
+ player_balance = 1000
+ host_balance = 100000
+
+ print(" " * 32 + "ROULETTE")
+ print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
+ print()
+ print()
+ print()
+
+ if stringtobool(input("DO YOU WANT INSTRUCTIONS? ")):
+ print_instructions()
+
+ while True:
+ bet_IDs,bet_Values = query_bets()
+
+ print("SPINNING")
+ print()
+ print()
+
+ val = random.randint(0,38)
+ if val == 38:
+ print("0")
+ elif val == 37:
+ print("00")
+ elif val in RED_NUMBERS:
+ print(str(val) + " RED")
+ else:
+ print(str(val) + " BLACK")
+
+ print()
+ total_winnings = bet_results(bet_IDs,bet_Values,val)
+ player_balance += total_winnings
+ host_balance -= total_winnings
+
+ print()
+ print("TOTALS:\tME\t\tYOU")
+ print("\t\t" + str(host_balance) + "\t" + str(player_balance))
+
+ if player_balance <= 0:
+ print("OOPS! YOU JUST SPENT YOUR LAST DOLLAR!")
+ break
+ elif host_balance <= 0:
+ print("YOU BROKE THE HOUSE!")
+ player_balance = 101000
+ break
+ if not stringtobool(input("PLAY AGAIN? ")):
+ break
+
+
+ if player_balance <= 0:
+ print("THANKS FOR YOUR MONEY")
+ print("I'LL USE IT TO BUY A SOLID GOLD ROULETTE WHEEL")
+ else:
+ print_check(player_balance)
+ print("COME BACK SOON!")
+
+
+def stringtobool(string):
+ """Converts a string to a bool"""
+ return string.lower() in ("yes","y","true","t","yes")
+
+if __name__ == '__main__':
+ main()
diff --git a/76 Russian Roulette/perl/russianroulette.pl b/76 Russian Roulette/perl/russianroulette.pl
new file mode 100644
index 00000000..0fa0cff7
--- /dev/null
+++ b/76 Russian Roulette/perl/russianroulette.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+#use strict;
+# Automatic converted by bas2perl.pl
+
+print ' 'x28 . "RUSSIAN ROULETTE\n";
+print ' 'x15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
+print "\n"; print "\n"; print "\n";
+print "THIS IS A GAME OF >>>>>>>>>>RUSSIAN ROULETTE.\n";
+Line10:
+print "\n"; print "HERE IS A REVOLVER.\n";
+Line20:
+print "TYPE '1' TO SPIN CHAMBER AND PULL TRIGGER.\n";
+print "TYPE '2' TO GIVE UP.\n";
+print "GO";
+$N=0;
+Line30:
+print "? "; chomp($I = );
+if ($I ne 2) { goto Line35; }
+print " CHICKEN!!!!!\n";
+goto Line72;
+Line35:
+$N=$N+1;
+if (rand(1)>.833333) { goto Line70; }
+if ($N>10) { goto Line80; }
+print "- CLICK -\n";
+print "\n"; goto Line30;
+Line70:
+print " BANG!!!!! YOU'RE DEAD!\n";
+print "CONDOLENCES WILL BE SENT TO YOUR RELATIVES.\n";
+Line72:
+print "\n"; print "\n"; print "\n";
+print "...NEXT VICTIM...\n"; goto Line20;
+Line80:
+print "YOU WIN!!!!!\n";
+print "LET SOMEONE ELSE BLOW HIS BRAINS OUT.\n";
+goto Line10;
+exit;
+
+
diff --git a/80_Slots/ruby/slots.rb b/80_Slots/ruby/slots.rb
new file mode 100644
index 00000000..a4c3e7ef
--- /dev/null
+++ b/80_Slots/ruby/slots.rb
@@ -0,0 +1,152 @@
+$pot = 0
+
+def greeting
+ puts "SLOTS".center(80)
+ puts "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY".center(80)
+ puts "\n\n"
+
+ # PRODUCED BY FRED MIRABELLE AND BOB HARPER ON JAN 29, 1973
+ # IT SIMULATES THE SLOT MACHINE.
+
+ puts "You are in the H&M Casino, in front of one of our"
+ puts "one-arm bandits. You can bet from $1 to $100."
+ puts "To pull the arm, punch the return key after making your bet."
+ puts "\nBet zero to end the game."
+end
+
+def overLimit
+ puts "House Limit is $100"
+end
+
+def underMinimum
+ puts "Minimum Bet is $1"
+end
+
+# bells don't work on my machine. YMMV
+# I'm displaying dashes between the reels
+
+def tenBells
+ 10.times do
+ # beep if you can
+ print "-"
+ end
+end
+
+def fiveBells
+ "-----"
+end
+
+def goodbye
+ puts "\n\n\n"
+ # end the game
+ exit
+end
+
+def payUp
+ puts "PAY UP! PLEASE LEAVE YOUR MONEY ON THE TERMINAL."
+end
+
+def brokeEven
+ puts "HEY, YOU BROKE EVEN."
+end
+
+def collectWinnings
+ puts "COLLECT YOUR WINNINGS FROM THE H&M CASHIER."
+end
+
+def win winType, bet
+ case winType
+ when "jackpot"
+ winMessage = "***JACKPOT***"
+ winnings = 101
+ when "topDollar"
+ winMessage = "**TOP DOLLAR**"
+ winnings = 11
+ when "doubleBar"
+ winMessage = "*DOUBLE BAR!!*"
+ winnings = 6
+ when "double"
+ winMessage = "DOUBLE!!"
+ winnings = 3
+ end
+ puts "\nYou won: " + winMessage
+ $pot += (winnings * bet)
+end
+
+greeting
+
+#$pot = 0
+while true
+ reelArray = ["BAR","BELL","ORANGE","LEMON","PLUM","CHERRY"]
+ print "\nYOUR BET? "
+ # get input, remove leading and trailing whitespace, cast to integer
+ bet = gets.strip.to_i
+
+ if bet == 0 then
+ goodbye
+ elsif bet > 100 then
+ overLimit # error if more than $100
+ elsif bet < 1 then
+ underMinimum # have to bet at least a dollar
+ else
+ # valid bet, continue
+ tenBells # ding
+
+ # assign a random value from the array to each of the three reels
+ reel1 = reelArray[rand(5)]
+ reel2 = reelArray[rand(5)]
+ reel3 = reelArray[rand(5)]
+
+ # print the slot machine reels
+ puts "\n\n" + reel1 + fiveBells + reel2 + fiveBells + reel3
+
+ # see if we have a match in the first two reels
+ if reel1 == reel2 then
+ if reel2 == reel3 then
+ if reel3 == "BAR" then
+ # all three reels are "BAR"
+ win "jackpot", bet
+ else
+ # all three reels match but aren't "BAR"
+ win "topDollar", bet
+ end
+ elsif reel2 == "BAR" then
+ # reels 1 and 2 are both "BAR"
+ win "doubleBar", bet
+ else
+ # reels 1 and 2 match but aren't "BAR"
+ win "double", bet
+ end
+ # otherwise see if there's a match in the remaining reels
+ elsif reel1 == reel3 or reel2 == reel3 then
+ if reel3 == "BAR" then
+ # two reels match, both "BAR"
+ win "doubleBar", bet
+ else
+ # two reels match, but not "BAR"
+ win "double", bet
+ end
+ else
+ # bad news - no matches
+ puts "\nYou lost"
+ # decrement your standings by the bet amount
+ $pot -= bet
+ end
+
+ puts "Your standings are: " + $pot.to_s
+ print "\nAgain? " # YES to continue
+ # get input, remove leading and trailing whitespace, make uppercase
+ again = gets.strip.upcase
+ if again != "Y" && again != "YES" then
+ # that's enough... evaluate the pot and quit
+ if $pot < 0 then
+ payUp
+ elsif $pot == 0 then
+ brokeEven
+ else # yay!
+ collectWinnings
+ end
+ goodbye
+ end
+ end
+end
diff --git a/README.md b/README.md
index 266aaac8..6ed7cb51 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,8 @@ Each project has subfolders corresponding to the languages we'd like to see the
- Delphi / Object Pascal
- Perl
+
+
### Project goals
Feel free to begin converting these classic games into the above list of modern, memory safe languages. But first, a few guidelines:
@@ -39,6 +41,16 @@ Feel free to begin converting these classic games into the above list of modern,
- **Don't get _too_ fancy**. Definitely use the most recent versions and features of the target language, but also try to keep the code samples simple and explainable -- the goal is to teach programming in the target language, not necessarily demonstrate the cleverest one-line tricks.
+### Emulation and Bugfixes
+
+We want the general behavior of the original programs to be preserved, _however_, we also want to update them, specifically:
+
+- allow both UPPERCASE and lowercase input and display
+- incorporate any bugfixes to the original programs; see the `readme.md` in the game folder
+- improved error handling for bad or erroneous input
+
+Please note that on the back of the Basic Computer Games book it says **Microsoft 8K Basic, Rev 4.0 was the version David Ahl used to test**, so that is the level of compatibility we are looking for. QBasic on the DOS emulation is a later version of Basic but one that retains downwards compatibility so far in our testing. We're working on a recommended emulation to verify behavior.
+
### Have fun!
Thank you for taking part in this project to update a classic programming book -- one of the most influential programming books in computing history -- for 2022 and beyond!
diff --git a/buildJvm/README.md b/buildJvm/README.md
index a5169abd..e2b9da0b 100644
--- a/buildJvm/README.md
+++ b/buildJvm/README.md
@@ -7,7 +7,7 @@ We should be using version 17 anyway, because anything less than 17 is deprecate
Build all the games:
```shell
cd buildJvm
- ./gradlew -q assemble installDist distributeBin distributeLib
+ ./gradlew -q clean assemble installDist distributeBin distributeLib
```
Then, run a game