Merge branch 'coding-horror:main' into main

This commit is contained in:
Andrew Regan
2022-01-15 22:40:47 +00:00
committed by GitHub
323 changed files with 24354 additions and 754 deletions
+19
View File
@@ -1,3 +1,19 @@
.local/
.vscode/
.gradle/
node_modules/
buildJvm/
build.gradle
.classpath
.project
.settings
.metadata
*.iml
*.ipr
*.class
*/.vs
*.suo
@@ -11,6 +27,9 @@ obj/
out/
*.py[co]
Pipfile
.DS_Store
/.vs/basic-computer-games/v16
/.vs
+168
View File
@@ -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
+25
View File
@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetUtils", "DotnetUtils\DotnetUtils.csproj", "{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFDF93C2-4FB7-4838-AFDF-E7B5F83C3F00}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {30FCF56E-4E83-42F8-AB43-A52C86C7C9B4}
EndGlobalSection
EndGlobal
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
namespace DotnetUtils;
public static class Extensions {
public static IEnumerable<TResult> SelectT<T1, T2, TResult>(this IEnumerable<(T1, T2)> src, Func<T1, T2, TResult> selector) =>
src.Select(x => selector(x.Item1, x.Item2));
public static IEnumerable<TResult> SelectT<T1, T2, T3, TResult>(this IEnumerable<(T1, T2, T3)> src, Func<T1, T2, T3, TResult> selector) =>
src.Select(x => selector(x.Item1, x.Item2, x.Item3));
public static IEnumerable<(T1, T2, int)> WithIndex<T1, T2>(this IEnumerable<(T1, T2)> src) => src.Select((x, index) => (x.Item1, x.Item2, index));
public static bool 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 /
}
}
@@ -0,0 +1,8 @@
namespace DotnetUtils;
public static class Globals {
public static readonly Dictionary<string, (string codefileExtension, string projExtension)> LangData = new() {
{ "csharp", ("cs", "csproj") },
{ "vbnet", ("vb", "vbproj") }
};
}
@@ -0,0 +1,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)
);
}
}
@@ -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;
}
@@ -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}");
}
+64
View File
@@ -0,0 +1,64 @@
<NotepadPlus>
<UserLang name="Vintage BASIC" ext="bas" udlVersion="2.1">
<Settings>
<Global caseIgnored="no" allowFoldOfComments="no" foldCompact="no" forcePureLC="0" decimalSeparator="0" />
<Prefix Keywords1="no" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="no" Keywords7="no" Keywords8="no" />
</Settings>
<KeywordLists>
<Keywords name="Comments">00REM 01 02 03 04</Keywords>
<Keywords name="Numbers, prefix1"></Keywords>
<Keywords name="Numbers, prefix2"></Keywords>
<Keywords name="Numbers, extras1"></Keywords>
<Keywords name="Numbers, extras2"></Keywords>
<Keywords name="Numbers, suffix1"></Keywords>
<Keywords name="Numbers, suffix2"></Keywords>
<Keywords name="Numbers, range"></Keywords>
<Keywords name="Operators1">- + ^ * / = &lt;&gt; &lt; &gt; &lt;= &gt;=</Keywords>
<Keywords name="Operators2"></Keywords>
<Keywords name="Folders in code1, open"></Keywords>
<Keywords name="Folders in code1, middle"></Keywords>
<Keywords name="Folders in code1, close"></Keywords>
<Keywords name="Folders in code2, open"></Keywords>
<Keywords name="Folders in code2, middle"></Keywords>
<Keywords name="Folders in code2, close"></Keywords>
<Keywords name="Folders in comment, open"></Keywords>
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">DATA DEF FN DIM END FOR GOSUB GOTO IF THEN INPUT LET NEXT ON PRINT RANDOMIZE REM RESTORE RETURN STOP TO</Keywords>
<Keywords name="Keywords2">ABS ASC ATN CHR$ COS EXP INT LEFT$ LEN LOG MID$ RIGHT$ RND SGN SIN SPC SQR STR TAB TAN VAL</Keywords>
<Keywords name="Keywords3">NOT AND OR</Keywords>
<Keywords name="Keywords4"></Keywords>
<Keywords name="Keywords5"></Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>
<Keywords name="Keywords8"></Keywords>
<Keywords name="Delimiters">00&quot; 01 02&quot; 03( 04 05) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
</KeywordLists>
<Styles>
<WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="Courier New" fontStyle="0" fontSize="14" nesting="0" />
<WordsStyle name="COMMENTS" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="LINE COMMENTS" fgColor="00FF00" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="NUMBERS" fgColor="FF0000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS1" fgColor="8000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS2" fgColor="0080C0" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS3" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="OPERATORS" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN CODE1" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS1" fgColor="0000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS2" fgColor="FF8040" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS3" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
</Styles>
</UserLang>
</NotepadPlus>
@@ -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;
+50
View File
@@ -0,0 +1,50 @@
import os
lang_pos = {
"csharp": 1, "java": 2, "javascript": 3,
"pascal": 4, "perl": 5, "python": 6, "ruby": 7, "vbnet": 8
}
write_string = "# TODO list \n game | csharp | java | javascript | pascal | perl | python | ruby | vbnet \n --- | --- | --- | --- | --- | --- | --- | --- | --- \n"
# Set the directory you want to start from
rootDir = '..'
strings_done = []
checklist = ["game", "csharp", "java", "javascript",
"pascal", "perl", "python", "ruby", "vbnet"]
prev_game = ""
for dirName, subdirList, fileList in os.walk(rootDir):
split_dir = dirName.split("/")
if len(split_dir) == 2 and not split_dir[1] in ['.git', '00_Utilities']:
if prev_game == "":
prev_game = split_dir[1]
checklist[0] = split_dir[1]
if prev_game != split_dir[1]:
# it's a new dir
strings_done.append(checklist)
checklist = [split_dir[1], "csharp", "java", "javascript",
"pascal", "perl", "python", "ruby", "vbnet"]
prev_game = split_dir[1]
elif len(split_dir) == 3 and split_dir[1] != '.git':
if split_dir[2] in lang_pos.keys():
if len(fileList) > 1 or len(subdirList) > 0:
# there is more files than the readme
checklist[lang_pos[split_dir[2]]] = ""
else:
checklist[lang_pos[split_dir[2]]] = "⬜️"
sorted_strings = list(map(lambda l: " | ".join(l) + "\n",
sorted(strings_done, key=lambda x: x[0])))
write_string += ''.join(sorted_strings)
with open("README.md", "w") as f:
f.write(write_string)
+14 -3
View File
@@ -1,9 +1,20 @@
### Acey Ducey
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=2
This is a simulation of the Acey Ducey card game. In the game, the dealer (the computer) deals two cards face up. You have an option to bet or not to bet depending on whether or not you feel the next card dealt will have a value between the first two.
Your initial money is set to $100; you may want to alter this value if you want to start with more or less than $100. The game keeps going on until you lose all your money or interrupt the program.
The original program author was Bill Palmby of Prairie View, Illinois.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=2)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=17)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
A Common Lisp port is [here](https://github.com/koalahedron/lisp-computer-games/blob/master/01%20Acey%20Ducey/common-lisp/acey-deucy.lisp).
#### External Links
- Common Lisp: https://github.com/koalahedron/lisp-computer-games/blob/master/01%20Acey%20Ducey/common-lisp/acey-deucy.lisp
- PowerShell: https://github.com/eweilnau/basic-computer-games-powershell/blob/main/AceyDucey.ps1
+1 -1
View File
@@ -10,7 +10,7 @@
80 PRINT"IF YOU DO NOT WANT TO BET, INPUT A 0"
100 N=100
110 Q=100
120 PRINT "YOU NOW HAVE";Q;"DOLLARS."
120 PRINT "YOU NOW HAVE ";Q;" DOLLARS."
130 PRINT
140 GOTO 260
210 Q=Q+M
+2
View File
@@ -0,0 +1,2 @@
*.exe
*.obj
+29
View File
@@ -0,0 +1,29 @@
Original source downloaded from [Vintage Basic](http://www.vintage-basic.net/games.html)
Converted to [D](https://dlang.org/) by [Bastiaan Veelo](https://github.com/veelo).
Two versions are supplied that are functionally equivalent, but differ in source layout:
<dl>
<dt><tt>aceyducey_literal.d</tt></dt>
<dd>A largely literal transcription of the original Basic source. All unnecessary uglyness is preserved.</dd>
<dt><tt>aceyducey.d</tt></dt>
<dd>An idiomatic D refactoring of the original, with a focus on increasing the readability and robustness.
Memory-safety <A href="https://dlang.org/spec/memory-safe-d.html">is ensured by the language</a>, thanks to the
<tt>@safe</tt> annotation.</dd>
</dl>
## Running the code
Assuming the reference [dmd](https://dlang.org/download.html#dmd) compiler:
```shell
dmd -run aceyducey.d
```
[Other compilers](https://dlang.org/download.html) also exist.
Note that there are compiler switches related to memory-safety (`-preview=dip25` and `-preview=dip1000`) that are not
used here because they are unnecessary in this case. What these do is to make the analysis more thorough, so that with
them some code that needed to be `@system` can then be inferred to be in fact `@safe`. [Code that compiles without
these switches is just as safe as when compiled with them]
(https://forum.dlang.org/post/dftgjalswvwfjpyushgn@forum.dlang.org).
+131
View File
@@ -0,0 +1,131 @@
@safe: // Make @safe the default for this file, enforcing memory-safety.
void main()
{
import std.stdio : write, writeln;
import std.string : center, toUpper, wrap;
import std.exception : ifThrown;
enum width = 80;
writeln(center("Acey Ducey Card Game", width));
writeln(center("(After Creative Computing Morristown, New Jersey)\n", width));
writeln(wrap("Acey-Ducey is played in the following manner: The dealer (computer) deals two cards face up. " ~
"You have an option to bet or not bet depending on whether or not you feel the third card will " ~
"have a value between the first two. If you do not want to bet, input a 0.", width));
enum Hand {low, middle, high}
Card[Hand.max + 1] cards; // Three cards.
bool play = true;
while (play)
{
int cash = 100;
while (cash > 0)
{
writeln("\nYou now have ", cash, " dollars.");
int bet = 0;
while (bet <= 0)
{
do // Draw new cards, until the first card has a smaller value than the last card.
{
foreach (ref card; cards)
card.drawNew;
} while (cards[Hand.low] >= cards[Hand.high]);
writeln("Here are your next two cards:\n", cards[Hand.low], "\n", cards[Hand.high]);
int askBet() // A nested function.
{
import std.conv : to;
write("\nWhat is your bet? ");
int answer = readString.to!int.
ifThrown!Exception(askBet); // Try again when answer does not convert to int.
if (answer <= cash)
return answer;
writeln("Sorry, my friend, but you bet too much.\nYou have only ", cash, " dollars to bet.");
return askBet; // Recurse: Ask again.
}
bet = askBet;
if (bet <= 0) // Negative bets are interpreted as 0.
writeln("CHICKEN!!");
} // bet is now > 0.
writeln(cards[Hand.middle]);
if (cards[Hand.low] < cards[Hand.middle] && cards[Hand.middle] < cards[Hand.high])
{
writeln("YOU WIN!!!");
cash += bet;
}
else
{
writeln("Sorry, you lose.");
cash -= bet;
if (cash <= 0)
{
writeln("\n\nSorry, friend, but you blew your wad.");
write("\n\nTry again (Yes or No)? ");
play = readString.toUpper == "YES";
}
}
}
}
writeln("O.K., hope you had fun!");
}
struct Card
{
int value = 2;
alias value this; // Enables Card to stand in as an int, so that cards can be compared as ints.
invariant
{
assert(2 <= value && value <= 14); // Ensure cards always have a valid value.
}
/// Adopt a new value.
void drawNew()
{
import std.random : uniform;
value = uniform!("[]", int, int)(2, 14); // A random int between inclusive bounds.
}
/// Called for implicit conversion to string.
string toString() const pure
{
import std.conv : text;
switch (value)
{
case 11: return "Jack";
case 12: return "Queen";
case 13: return "King";
case 14: return "Ace";
default: return text(" ", value); // Basic prepends a space.
}
}
}
/// Read a string from standard input, stripping newline and other enclosing whitespace.
string readString() nothrow
{
import std.string : strip;
try
return trustedReadln.strip;
catch (Exception) // readln throws on I/O and Unicode errors, which we handle here.
return "";
}
/** An @trusted wrapper around readln.
*
* This is the only function that formally requires manual review for memory-safety.
* [Arguably readln should be safe already](https://forum.dlang.org/post/rab398$1up$1@digitalmars.com)
* which would remove the need to have any @trusted code in this program.
*/
string trustedReadln() @trusted
{
import std.stdio : readln;
return readln;
}
+104
View File
@@ -0,0 +1,104 @@
void main()
{
import std;
L10: writef("%26s", ' '); writeln("ACEY DUCEY CARD GAME");
L20: writef("%15s", ' '); writeln("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
L21: writeln;
L22: writeln;
L30: writeln("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ");
L40: writeln("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP");
L50: writeln("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING");
L60: writeln("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE");
L70: writeln("A VALUE BETWEEN THE FIRST TWO.");
L80: writeln("IF YOU DO NOT WANT TO BET, INPUT A 0");
L100: int N=100;
L110: int Q=100, M;
L120: writeln("YOU NOW HAVE ",Q," DOLLARS.");
L130: writeln;
L140: goto L260;
L210: Q=Q+M;
L220: goto L120;
L240: Q=Q-M;
L250: goto L120;
L260: writeln("HERE ARE YOUR NEXT TWO CARDS: ");
L270: auto A=to!int(14*uniform01)+2;
L280: if (A<2) goto L270;
L290: if (A>14) goto L270;
L300: auto B=to!int(14*uniform01)+2;
L310: if (B<2) goto L300;
L320: if (B>14) goto L300;
L330: if (A>=B) goto L270;
L350: if (A<11) goto L400;
L360: if (A==11) goto L420;
L370: if (A==12) goto L440;
L380: if (A==13) goto L460;
L390: if (A==14) goto L480;
L400: writefln("%2d", A);
L410: goto L500;
L420: writeln("JACK");
L430: goto L500;
L440: writeln("QUEEN");
L450: goto L500;
L460: writeln("KING");
L470: goto L500;
L480: writeln("ACE");
L500: if (B<11) goto L550;
L510: if (B==11) goto L570;
L520: if (B==12) goto L590;
L530: if (B==13) goto L610;
L540: if (B==14) goto L630;
L550: writefln("%2d", B);
L560: goto L650;
L570: writeln("JACK");
L580: goto L650;
L590: writeln("QUEEN");
L600: goto L650;
L610: writeln("KING");
L620: goto L650;
L630: writeln("ACE");
L640: writeln;
L650: writeln;
L660: write("WHAT IS YOUR BET? "); M = stdin.readln.strip.to!int;
L670: if (M!=0) goto L680;
L675: writeln("CHICKEN!!");
L676: writeln;
L677: goto L260;
L680: if (M<=Q) goto L730;
L690: writeln("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.");
L700: writeln("YOU HAVE ONLY ",Q," DOLLARS TO BET.");
L710: goto L650;
L730: auto C=to!int(14*uniform01)+2;
L740: if (C<2) goto L730;
L750: if (C>14) goto L730;
L760: if (C<11) goto L810;
L770: if (C==11) goto L830;
L780: if (C==12) goto L850;
L790: if (C==13) goto L870;
L800: if (C==14) goto L890;
L810: writeln(C);
L820: goto L910;
L830: writeln("JACK");
L840: goto L910;
L850: writeln("QUEEN");
L860: goto L910;
L870: writeln("KING");
L880: goto L910;
L890: writeln( "ACE");
L900: writeln;
L910: if (C>A) goto L930;
L920: goto L970;
L930: if (C>=B) goto L970;
L950: writeln("YOU WIN!!!");
L960: goto L210;
L970: writeln("SORRY, YOU LOSE");
L980: if (M<Q) goto L240;
L990: writeln;
L1000: writeln;
L1010: writeln("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.");
L1015: writeln;writeln;
L1020: write("TRY AGAIN (YES OR NO)? "); auto AS=stdin.readln;
L1025: writeln;writeln;
L1030: if (AS.strip.toUpper=="YES") goto L110;
L1040: writeln("O.K., HOPE YOU HAD FUN!");
}
+6
View File
@@ -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.
+1 -1
View File
@@ -167,6 +167,6 @@ public class AceyDucey {
System.out.println("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING");
System.out.println("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE");
System.out.println("A VALUE BETWEEN THE FIRST TWO.");
System.out.println("IF YOU DO NOT WANT TO BET, INPUT A 0");
System.out.println("IF YOU DO NOT WANT TO BET, INPUT: 0");
}
}
+201
View File
@@ -0,0 +1,201 @@
import java.util.Random;
import java.util.Scanner;
/**
* A modern version (JDK17) of ACEY DUCEY using post Java 8 features. Notes
* regarding new java features or differences in the original basic
* implementation are numbered and at the bottom of this code.
* The goal is to recreate the exact look and feel of the original program
* minus a large glaring bug in the original code that lets you cheat.
*/
public class AceyDucey17 {
public static void main(String[] args) {
// notes [1]
System.out.println("""
ACEY DUCEY CARD GAME
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER
THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP
YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING
ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE
A VALUE BETWEEN THE FIRST TWO.
IF YOU DO NOT WANT TO BET, INPUT A 0""");
do {
playGame();
} while (stillInterested());
System.out.println("O.K., HOPE YOU HAD FUN!");
}
public static void playGame() {
int cashOnHand = 100; // our only mutable variable note [11]
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");// note [6]
while (cashOnHand > 0) {
System.out.println();
System.out.println("HERE ARE YOUR NEXT TWO CARDS:");
final Card lowCard = Card.getRandomCard(2, Card.KING); //note [3]
System.out.println(lowCard);
final Card highCard = Card.getRandomCard(lowCard.rank() + 1, Card.ACE);
System.out.println(highCard);
final int bet = getBet(cashOnHand);
final int winnings = determineWinnings(lowCard,highCard,bet);
cashOnHand += winnings;
if(winnings != 0 || cashOnHand != 0){ //note [2]
System.out.println("YOU NOW HAVE "+ cashOnHand +" DOLLARS.");//note [6]
}
}
}
public static int determineWinnings(Card lowCard, Card highCard, int bet){
if (bet <= 0) { // note [5]
System.out.println("CHICKEN!!");
return 0;
}
Card nextCard = Card.getRandomCard(2, Card.ACE);
System.out.println(nextCard);
if(nextCard.between(lowCard,highCard)){
System.out.println("YOU WIN!!!");
return bet;
}
System.out.println("SORRY, YOU LOSE");
return -bet;
}
public static boolean stillInterested(){
System.out.println();
System.out.println();
System.out.println("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.");
System.out.println();
System.out.println();
System.out.print("TRY AGAIN (YES OR NO)? ");
Scanner input = new Scanner(System.in);
boolean playAgain = input.nextLine()
.toUpperCase()
.startsWith("Y"); // note [9]
System.out.println();
System.out.println();
return playAgain;
}
public static int getBet(int cashOnHand){
int bet;
do{
System.out.println();
System.out.print("WHAT IS YOUR BET? ");
bet = inputNumber();
if (bet > cashOnHand) {
System.out.println("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.");
System.out.println("YOU HAVE ONLY "+cashOnHand+" DOLLARS TO BET.");
}
}while(bet > cashOnHand);
return bet;
}
public static int inputNumber() {
final Scanner input = new Scanner(System.in);
// set to negative to mark as not entered yet in case of input error.
int number = -1;
while (number < 0) {
try {
number = input.nextInt();
} catch(Exception ex) { // note [7]
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
System.out.print("? ");
try{
input.nextLine();
}
catch(Exception ns_ex){ // received EOF (ctrl-d or ctrl-z if windows)
System.out.println("END OF INPUT, STOPPING PROGRAM.");
System.exit(1);
}
}
}
return number;
}
record Card(int rank){
// Some constants to describe face cards.
public static final int JACK = 11, QUEEN = 12, KING = 13, ACE = 14;
private static final Random random = new Random();
public static Card getRandomCard(int from, int to){
return new Card(random.nextInt(from, to+1)); // note [4]
}
public boolean between(Card lower, Card higher){
return lower.rank() < this.rank() && this.rank() < higher.rank();
}
@Override
public String toString() { // note [13]
return switch (rank) {
case JACK -> "JACK";
case QUEEN -> "QUEEN";
case KING -> "KING";
case ACE -> "ACE\n"; // note [10]
default -> " "+rank+" "; // note [6]
};
}
}
/*
Notes:
1. Multiline strings, a.k.a. text blocks, were added in JDK15.
2. The original game only displays the players balance if it changed,
which it does not when the player chickens out and bets zero.
It also doesn't display the balance when it becomes zero because it has
a more appropriate message: Sorry, You Lose.
3. To pick two cards to show, the original BASIC implementation has a
bug that could cause a race condition if the RND function never chose
a lower number first and higher number second. It loops infinitely
re-choosing random numbers until the condition is met of the first
one being lower. The logic is changed a bit here so that the first
card picked is anything but an ACE, the highest possible card,
and then the second card is between the just picked first card upto
and including the ACE.
4. Random.nextInt(origin, bound) was added in JDK17, and allows to
directly pick a range for a random integer to be generated. The second
parameter is exclusive of the range and thus why they are stated with
+1's to the face card.
5. The original BASIC implementation has a bug that allows negative value
bets. Since you can't bet MORE cash than you have you can always bet
less including a very, very large negative value. You would do this when
the chances of winning are slim or zero since losing a hand SUBTRACTS
your bet from your cash; subtracting a negative number actually ADDS
to your cash, potentially making you an instant billionaire.
This loophole is now closed.
6. The subtle behavior of the BASIC PRINT command causes a space to be
printed before all positive numbers as well as a trailing space. Any
place a non-face card or the players balance is printed has extra space
to mimic this behavior.
7. Errors on input were probably specific to the interpreter. This program
tries to match the Vintage Basic interpreter's error messages. The final
input.nextLine() command exists to clear the blockage of whatever
non-number input was entered. But even that could fail if the user
types Ctrl-D (windows Ctrl-Z), signifying an EOF (end of file) and thus
the closing of STDIN channel. The original program on an EOF signal prints
"END OF INPUT IN LINE 660" and thus we cover it roughly the same way.
All of this is necessary to avoid a messy stack trace from being
printed as the program crashes.
9. The original game only accepted a full upper case "YES" to continue
playing if bankrupted. This program is more lenient and will accept
any input that starts with the letter 'y', uppercase or not.
10. The original game prints an extra blank line if the card is an ACE. There
is seemingly no rationale for this.
11. Modern java best practices are edging toward a more functional paradigm
and as such, mutating state is discouraged. All other variables besides
the cashOnHand are final and initialized only once.
12. Refactoring of the concept of a card is done with a record. Records were
introduced in JDK14. Card functionality is encapsulated in this example
of a record. An enum could be a better alternative since there are
technically only 13 cards possible.
13. Switch expressions were introduced as far back as JDK12 but continue to
be refined for clarity, exhaustiveness. As of JDK17 pattern matching
for switch expressions can be accessed by enabling preview features.
*/
}
+10 -6
View File
@@ -90,19 +90,23 @@ async function main() {
print('\nWHAT IS YOUR BET? ');
bet = parseInt(await input(), 10);
let minimumRequiredBet = 0;
if (bet > minimumRequiredBet) {
if (bet >= minimumRequiredBet) {
if (bet > availableDollars) {
print('SORRY, MY FRIEND, BUT YOU BET TOO MUCH.');
print(`YOU HAVE ONLY ${availableDollars} DOLLARS TO BET.`);
} else {
validBet = true;
}
} else {
// Does not meet minimum required bet
print('CHICKEN!!');
print('');
}
}
if (bet == 0)
{
// User chose not to bet.
print('CHICKEN!!');
print('');
// Don't draw a third card, draw a new set of 2 cards.
continue;
}
print('\n\nHERE IS THE CARD WE DREW: ');
print(getCardValue(cardThree));
@@ -127,7 +131,7 @@ async function main() {
print('');
print('');
if (isValidYesNoString(tryAgainInput)) {
if (isValidYesString(tryAgainInput)) {
availableDollars = 100;
} else {
print('O.K., HOPE YOU HAD FUN!');
+1 -1
View File
@@ -85,6 +85,7 @@ end;
constructor TGame.Create;
begin
Randomize;
FDeck:= TDeck.Create;
end;
@@ -99,7 +100,6 @@ begin
ClrScr;
PrintGreeting;
repeat
Randomize;
FStash:= 100;
repeat
PrintBalance;
+1 -1
View File
@@ -118,10 +118,10 @@ begin
end;
begin
Randomize;
ClrScr;
PrintGreeting;
repeat
Randomize;
Stash:= 100;
repeat
PrintBalance;
+53 -70
View File
@@ -28,17 +28,15 @@ If you do not want to bet, input a 0. If you want to quit, input a -1.
END_INSTRUCTIONS
my @cards = (1 .. 13); # That is, Ace through King.
my @cards = ( 1 .. 13 ); # That is, Ace through King.
my $keepPlaying = 1;
GAME:
while ($keepPlaying)
{
my $playerBalance = 100; # The player starts with $100
while ($keepPlaying) {
my $playerBalance = 100; # The player starts with $100
HAND:
while (1)
{
HAND:
while (1) {
print "\nYou now have $playerBalance dollars.\n\n";
# We'll create a new array that is a shuffled version of the deck.
@@ -48,19 +46,17 @@ while ($keepPlaying)
# that those will be unique. This way we don't have to keep drawing
# if we get, say, two queens. We sort them as we pull them to make
# sure that the first card is lower than the second one.
my ($firstCard, $secondCard) = sort { $a <=> $b } @shuffledDeck[ 0 .. 1 ];
my ( $firstCard, $secondCard ) = sort { $a <=> $b } @shuffledDeck[ 0 .. 1 ];
print "I drew ", nameOfCard($firstCard), " and ", nameOfCard($secondCard), ".\n";
my $bet = getValidBet($playerBalance);
if ($bet == 0)
{
if ( $bet == 0 ) {
print "Chicken!\n\n";
next HAND;
}
if ($bet < 0)
{
if ( $bet < 0 ) {
last GAME;
}
@@ -72,19 +68,16 @@ while ($keepPlaying)
print "I drew ", nameOfCard($thirdCard), "!\n";
if (($firstCard < $thirdCard) && ($thirdCard < $secondCard))
{
if ( ( $firstCard < $thirdCard ) && ( $thirdCard < $secondCard ) ) {
print "You win!\n\n";
$playerBalance += $bet;
}
else
{
else {
print "You lose!\n\n";
$playerBalance -= $bet;
}
if ($playerBalance <= 0)
{
if ( $playerBalance <= 0 ) {
print "Sorry, buddy, you blew your wad!\n\n";
last HAND;
}
@@ -96,49 +89,43 @@ while ($keepPlaying)
print "Thanks for playing!\n";
###############
sub getValidBet
{
sub getValidBet {
my $maxBet = shift;
print "\nWhat's your bet? ";
my $input = <STDIN>;
chomp $input;
# This regular expression will validate that the player entered an integer.
# The !~ match operate *negates* the match, so if the player did NOT enter
# an integer, they'll be given an error and prompted again.
if ($input !~ /^ # Match the beginning of the string
[+-]? # Optional plus or minus...
\d+ # followed by one more more digits...
$ # and then the end of the string
/x # The x modifier ignores whitespace in this regex...
)
INPUT:
{
print "Sorry, numbers only!\n";
$input = getValidBet($maxBet);
}
print "\nWhat's your bet? ";
if ($input > $maxBet)
{
print "Sorry, my friend, you can't bet more money than you have.\n";
print "You only have $maxBet dollars to spend!\n";
$input = getValidBet($maxBet);
}
chomp( my $input = <STDIN> );
if ($input != int($input))
{
print "Sorry, you must bet in whole dollars. No change!\n";
$input = getValidBet($maxBet);
}
# This regular expression will validate that the player entered an integer.
# The !~ match operate *negates* the match, so if the player did NOT enter
# an integer, they'll be given an error and prompted again.
if (
$input !~ /^ # Match the beginning of the string
[+-]? # Optional plus or minus...
\d+ # followed by one more more digits...
$ # and then the end of the string
/x # The x modifier ignores whitespace in this regex...
)
{
print "Sorry, numbers only!\n";
redo INPUT;
}
return $input;
if ( $input > $maxBet ) {
print "Sorry, my friend, you can't bet more money than you have.\n";
print "You only have $maxBet dollars to spend!\n";
redo INPUT;
}
return $input;
}
}
# Since arrays in Perl are 0-based, we need to convert the value that we drew from
# the array to its proper position in the deck.
sub nameOfCard
{
sub nameOfCard {
my $value = shift;
# Note that the Joker isn't used in this game, but since arrays in Perl are
@@ -150,25 +137,21 @@ sub nameOfCard
return $cardlist[$value];
}
sub promptUserToKeepPlaying
{
print "Try again (Y/N)? ";
my $input = <STDIN>;
chomp $input;
sub promptUserToKeepPlaying {
YESNO:
{
print "Try again (Y/N)? ";
my $keepGoing;
if (uc($input) eq 'Y')
{
$keepGoing = 1;
}
elsif (uc($input) eq 'N')
{
$keepGoing = 0;
}
else
{
$keepGoing = promptUserToKeepPlaying();
}
chomp( my $input = uc <STDIN> );
return $keepGoing;
if ( $input eq 'Y' ) {
return 1;
}
elsif ( $input eq 'N' ) {
return 0;
}
else {
redo YESNO;
}
}
}
+5 -2
View File
@@ -36,7 +36,7 @@ class Deck:
def build(self):
for suit in ['\u2665', '\u2666', '\u2663', '\u2660']:
for rank in range(1, 14):
for rank in range(2, 15):
self.cards.append(Card(suit, rank))
def shuffle(self):
@@ -95,11 +95,14 @@ class Game:
self.not_done = False
break
if len(self.deck.cards) <= 1:
if len(self.deck.cards) <= 3:
print('You ran out of cards. Game over.')
self.not_done = False
break
self.card_a = self.deck.deal()
self.card_b = self.deck.deal()
if self.money == 0:
self.not_done = False
+13 -2
View File
@@ -1,7 +1,18 @@
### Amazing
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=3
This program will print out a different maze every time it is run and guarantees only one path through. You can choose the dimensions of the maze — i.e. the number of squares wide and long.
The original program author was Jack Hauber of Windsor, Connecticut.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=3)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=18)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
---
**2022-01-04:** patched original source in [#400](https://github.com/coding-horror/basic-computer-games/pull/400) to fix a minor bug where a generated maze may be missing an exit, particularly at small maze sizes.
+9 -4
View File
@@ -117,10 +117,15 @@
975 V(R,S)=3:Q=0:GOTO 1000
980 V(R,S)=1:Q=0:R=1:S=1:GOTO 250
1000 GOTO 210
1010 FOR J=1 TO V
1011 PRINT "I";
1012 FOR I=1 TO H
1013 IF V(I,J)<2 THEN 1030
1010 IF Z=1 THEN 1015
1011 X=INT(RND(1)*H+1)
1012 IF V(X,V)=0 THEN 1014
1013 V(X,V)=3: GOTO 1015
1014 V(X,V)=1
1015 FOR J=1 TO V
1016 PRINT "I";
1017 FOR I=1 TO H
1018 IF V(I,J)<2 THEN 1030
1020 PRINT " ";
1021 GOTO 1040
1030 PRINT " I";
+159
View File
@@ -0,0 +1,159 @@
#! /usr/bin/perl
use strict;
use warnings;
# Translated from BASIC by Alex Kapranoff
use feature qw/say/;
# width and height of the maze
my ($width, $height) = input_dimensions();
# wall masks for all cells
my @walls;
# flags of previous visitation for all cells
my %is_visited;
# was the path out of the maze found?
my $path_found = 0;
# column of entry to the maze in the top line
my $entry_col = int(rand($width));
# cell coordinates for traversal
my $col = $entry_col;
my $row = 0;
$is_visited{$row, $col} = 1;
# looping until we visit every cell
while (keys %is_visited < $width * $height) {
if (my @dirs = get_possible_directions()) {
my $dir = $dirs[rand @dirs];
# modify current cell wall if needed
$walls[$row]->[$col] |= $dir->[2];
# move the position
$row += $dir->[0];
$col += $dir->[1];
# we found the exit!
if ($row == $height) {
$path_found = 1;
--$row;
if ($walls[$row]->[$col] == 1) {
($row, $col) = get_next_branch(0, 0);
}
}
else {
# modify the new cell wall if needed
$walls[$row]->[$col] |= $dir->[3];
$is_visited{$row, $col} = 1;
}
}
else {
($row, $col) = get_next_branch($row, $col);
}
}
unless ($path_found) {
$walls[-1]->[rand $width] |= 1;
}
print_maze();
sub input_dimensions {
# Print the banner and returns the dimensions as two integers > 1.
# The integers are parsed from the first line of standard input.
say ' ' x 28, 'AMAZING PROGRAM';
say ' ' x 15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY';
print "\n" x 4;
my ($w, $h) = (0, 0);
while ($w <= 1 || $h <= 1) {
print 'WHAT ARE YOUR WIDTH AND LENGTH? ';
($w, $h) = <STDIN> =~ / \d+ /xg;
if ($w < 1 || $h < 1) {
say "MEANINGLESS DIMENSIONS. TRY AGAIN."
}
}
print "\n" x 4;
return ($w, $h);
}
sub get_possible_directions {
# Returns a list of all directions that are available to go to
# from the current coordinates. "Down" is available on the last line
# until we go there once and mark it as the path through the maze.
#
# Each returned direction element contains changes to the coordindates and to
# the wall masks of the previous and next cell after the move.
my @rv;
# up
if ($row > 0 && !$is_visited{$row - 1, $col}) {
push @rv, [-1, 0, 0, 1];
}
# left
if ($col > 0 && !$is_visited{$row, $col - 1}) {
push @rv, [0, -1, 0, 2];
}
# right
if ($col < $width - 1 && !$is_visited{$row, $col + 1}) {
push @rv, [0, 1, 2, 0];
}
# down
if ($row < $height - 1 && !$is_visited{$row + 1, $col}
|| $row == $height - 1 && !$path_found
) {
push @rv, [1, 0, 1, 0];
}
return @rv;
}
sub get_next_branch {
# Returns the cell coordinates to start a new maze branch from.
# It looks for a visited cell starting from passed position and
# going down in the natural traversal order incrementing column and
# rows with a rollover to start at the bottom right corner.
my ($y, $x) = @_;
do {
if ($x < $width - 1) {
++$x;
} elsif ($y < $height - 1) {
($y, $x) = ($y + 1, 0);
} else {
($y, $x) = (0, 0);
}
} while (!$is_visited{$y, $x});
return ($y, $x);
}
sub print_maze {
# Print the full maze based on wall masks.
# For each cell, we mark the absense of the wall to the right with
# bit 2 and the absense of the wall down with bit 1. Full table:
# 0 -> both walls are present
# 1 -> wall down is absent
# 2 -> wall to the right is absent
# 3 -> both walls are absent
say join('.', '', map { $_ == $entry_col ? ' ' : '--' } 0 .. $width - 1), '.';
for my $row (@walls) {
say join(' ', map { $_ & 2 ? ' ' : 'I' } 0, @$row);
say join(':', '', map { $_ & 1 ? ' ' : '--' } @$row), '.';
}
return;
}
+295
View File
@@ -0,0 +1,295 @@
Imports System
Module Program
Enum Directions
SolveAndReset = 0
Left = 1
Up = 2
Right = 3
Down = 4
End Enum
'Program State
Dim Width As Integer = 0, Height As Integer = 0, Q As Integer = 0, CellsVisited As Integer = 2, curCol As Integer, curRow As Integer = 1
Dim SolutionCompleted As Boolean = False
Dim CellVisitHistory(,) As Integer
Dim CellState(,) As Integer
Dim rnd As New Random()
Public ReadOnly Property BlockedLeft As Boolean
Get
Return curCol - 1 = 0 OrElse CellVisitHistory(curCol - 1, curRow) <> 0
End Get
End Property
Public ReadOnly Property BlockedAbove As Boolean
Get
Return curRow - 1 = 0 OrElse CellVisitHistory(curCol, curRow - 1) <> 0
End Get
End Property
Public ReadOnly Property BlockedRight As Boolean
Get
Return curCol = Width OrElse CellVisitHistory(curCol + 1, curRow) <> 0
End Get
End Property
'Note: "BlockedBelow" does NOT include checking if we have a solution!
Public ReadOnly Property BlockedBelow As Boolean
Get
Return curRow = Height OrElse CellVisitHistory(curCol, curRow + 1) <> 0
End Get
End Property
Public ReadOnly Property OnBottomRow As Boolean
Get
Return curRow.Equals(Height)
End Get
End Property
Sub Main(args As String())
Const header As String =
" AMAZING PROGRAM
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
"
Console.WriteLine(header)
While Width <= 1 OrElse Height <= 1
Console.Write("WHAT ARE YOUR WIDTH AND LENGTH? ")
'We no longer have the old convenient INPUT command, so need to parse out the inputs
Dim parts = Console.ReadLine().Split(","c).Select(Function(s) Convert.ToInt32(s.Trim())).ToList()
Width = parts(0)
Height = parts(1)
If Width <= 1 OrElse Height <= 1 Then Console.WriteLine($"MEANINGLESS DIMENSIONS. TRY AGAIN.{vbCrLf}")
End While
ReDim CellVisitHistory(Width, Height), CellState(Width, Height)
Console.WriteLine("
")
curCol = rnd.Next(1, Width + 1) 'Starting X position
CellVisitHistory(curCol, 1) = 1
Dim startXPos As Integer = curCol 'we need to know this at the end to print opening line
Dim keepGoing As Boolean = True
While keepGoing
If BlockedLeft Then
keepGoing = ChoosePath_BlockedToTheLeft()
ElseIf BlockedAbove Then
keepGoing = ChoosePath_BlockedAbove()
ElseIf BlockedRight Then
keepGoing = ChoosePath_BlockedToTheRight()
Else
keepGoing = SelectRandomDirection(Directions.Left, Directions.Up, Directions.Right) 'Go anywhere but down
End If
End While
PrintFinalResults(startXPos)
End Sub
Public Sub ResetCurrentPosition()
Do
If curCol <> Width Then 'not at the right edge
curCol += 1
ElseIf curRow <> Height Then 'not at the bottom
curCol = 1
curRow += 1
Else
curCol = 1
curRow = 1
End If
Loop While CellVisitHistory(curCol, curRow) = 0
End Sub
Dim methods() As Func(Of Boolean) = {AddressOf MarkSolvedAndResetPosition, AddressOf GoLeft, AddressOf GoUp, AddressOf GoRight, AddressOf GoDown}
Public Function SelectRandomDirection(ParamArray possibles() As Directions) As Boolean
Dim x As Integer = rnd.Next(0, possibles.Length)
Return methods(possibles(x))()
End Function
Public Function ChoosePath_BlockedToTheLeft() As Boolean
If BlockedAbove Then
If BlockedRight Then
If curRow <> Height Then
If CellVisitHistory(curCol, curRow + 1) <> 0 Then ' Can't go down, but not at the edge...blocked. Reset and try again
ResetCurrentPosition()
Return True
Else
Return GoDown()
End If
ElseIf SolutionCompleted Then 'Can't go Down (there's already another solution)
ResetCurrentPosition()
Return True
Else 'Can't go LEFT, UP, RIGHT, or DOWN, but we're on the bottom and there's no solution yet
Return MarkSolvedAndResetPosition()
End If
ElseIf BlockedBelow Then
Return GoRight()
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Right, Directions.Down)
ElseIf SolutionCompleted Then 'Can only go right, and we're at the bottom
Return GoRight()
Else 'Can only go right, we're at the bottom, and there's not a solution yet
Return SelectRandomDirection(Directions.Right, Directions.SolveAndReset)
End If
'== Definitely can go Up ==
ElseIf BlockedRight Then
If BlockedBelow Then
Return GoUp()
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Up, Directions.Down)
ElseIf SolutionCompleted Then 'We're on the bottom row, can only go up
Return GoUp()
Else 'We're on the bottom row, can only go up, but there's no solution
Return SelectRandomDirection(Directions.Up, Directions.SolveAndReset)
End If
'== Definitely can go Up and Right ==
ElseIf BlockedBelow Then
Return SelectRandomDirection(Directions.Up, Directions.Right)
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Up, Directions.Right, Directions.Down)
ElseIf SolutionCompleted Then 'at the bottom, but already have a solution
Return SelectRandomDirection(Directions.Up, Directions.Right)
Else
Return SelectRandomDirection(Directions.Up, Directions.Right, Directions.SolveAndReset)
End If
End Function
Public Function ChoosePath_BlockedAbove() As Boolean
'No need to check the left side, only called from the "keepGoing" loop where LEFT is already cleared
If BlockedRight Then
If BlockedBelow Then
Return GoLeft()
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Left, Directions.Down)
ElseIf SolutionCompleted Then 'Can't go down because there's already a solution
Return GoLeft()
Else 'At the bottom, no solution yet...
Return SelectRandomDirection(Directions.Left, Directions.SolveAndReset)
End If
ElseIf BlockedBelow Then
Return SelectRandomDirection(Directions.Left, Directions.Right)
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Left, Directions.Right, Directions.Down)
ElseIf SolutionCompleted Then
Return SelectRandomDirection(Directions.Left, Directions.Right)
Else
Return SelectRandomDirection(Directions.Left, Directions.Right, Directions.SolveAndReset)
End If
End Function
Public Function ChoosePath_BlockedToTheRight() As Boolean
'No need to check Left or Up, only called from the "keepGoing" loop where LEFT and UP are already cleared
If BlockedRight Then 'Can't go Right -- why? we knew this when calling the function
If BlockedBelow Then
Return SelectRandomDirection(Directions.Left, Directions.Up)
ElseIf Not OnBottomRow Then
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.Down)
ElseIf SolutionCompleted Then
Return SelectRandomDirection(Directions.Left, Directions.Up)
Else
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.SolveAndReset)
End If
Else 'Should never get here
Return SelectRandomDirection(Directions.Left, Directions.Up, Directions.Right) 'Go Left, Up, or Right (but path is blocked?)
End If
End Function
Public Sub PrintFinalResults(startPos As Integer)
For i As Integer = 0 To Width - 1
If i = startPos Then Console.Write(". ") Else Console.Write(".--")
Next
Console.WriteLine(".")
If Not SolutionCompleted Then 'Pick a random exit
Dim X As Integer = rnd.Next(1, Width + 1)
If CellState(X, Height) = 0 Then
CellState(X, Height) = 1
Else
CellState(X, Height) = 3
End If
End If
For j As Integer = 1 To Height
Console.Write("I")
For i As Integer = 1 To Width
If CellState(i, j) < 2 Then
Console.Write(" I")
Else
Console.Write(" ")
End If
Next
Console.WriteLine()
For i As Integer = 1 To Width
If CellState(i, j) = 0 OrElse CellState(i, j) = 2 Then
Console.Write(":--")
Else
Console.Write(": ")
End If
Next
Console.WriteLine(".")
Next
End Sub
Public Function GoLeft() As Boolean
curCol -= 1
CellVisitHistory(curCol, curRow) = CellsVisited
CellsVisited += 1
CellState(curCol, curRow) = 2
If CellsVisited > Width * Height Then Return False
Q = 0
Return True
End Function
Public Function GoUp() As Boolean
curRow -= 1
CellVisitHistory(curCol, curRow) = CellsVisited
CellsVisited += 1
CellState(curCol, curRow) = 1
If CellsVisited > Width * Height Then Return False
Q = 0
Return True
End Function
Public Function GoRight() As Boolean
CellVisitHistory(curCol + 1, curRow) = CellsVisited
CellsVisited += 1
If CellState(curCol, curRow) = 0 Then CellState(curCol, curRow) = 2 Else CellState(curCol, curRow) = 3
curCol += 1
If CellsVisited > Width * Height Then Return False
Return ChoosePath_BlockedToTheLeft()
End Function
Public Function GoDown() As Boolean
If Q = 1 Then Return MarkSolvedAndResetPosition()
CellVisitHistory(curCol, curRow + 1) = CellsVisited
CellsVisited += 1
If CellState(curCol, curRow) = 0 Then CellState(curCol, curRow) = 1 Else CellState(curCol, curRow) = 3
curRow += 1
If CellsVisited > Width * Height Then Return False
Return True
End Function
Public Function MarkSolvedAndResetPosition() As Boolean
' AlWAYS returns true
SolutionCompleted = True
Q = 1
If CellState(curCol, curRow) = 0 Then
CellState(curCol, curRow) = 1
curCol = 1
curRow = 1
If CellVisitHistory(curCol, curRow) = 0 Then ResetCurrentPosition()
Else
CellState(curCol, curRow) = 3
ResetCurrentPosition()
End If
Return True
End Function
End Module
+16 -1
View File
@@ -1,7 +1,22 @@
### Animal
Unlike other computer games in which the computer picks a number or letter and you must guess what it is, in this game _you_ think of an animal and the _computer_ asks you questions and tries to guess the name of your animal. If the computer guesses incorrectly, it will ask you for a question that differentiates the animal you were thinking of. In this way the computer “learns” new animals. Questions to differentiate new animals should be input without a question mark.
This version of the game does not have a SAVE feature. If your system allows, you may modify the program to save and reload the array when you want to play the game again. This way you can save what the computer learns over a series of games.
At any time if you reply “LIST” to the question “ARE YOU THINKING OF AN ANIMAL,” the computer will tell you all the animals it knows so far.
The program starts originally by knowing only FISH and BIRD. As you build up a file of animals you should use broad, general questions first and then narrow down to more specific ones with later animals. For example, if an elephant was to be your first animal, the computer would ask for a question to distinguish an elephant from a bird. Naturally, there are hundreds of possibilities, however, if you plan to build a large file of animals a good question would be “IS IT A MAMMAL.”
This program can be easily modified to deal with categories of things other than animals by simply modifying the initial data and the dialogue references to animals. In an educational environment, this would be a valuable program to teach the distinguishing characteristics of many classes of objects — rock formations, geography, marine life, cell structures, etc.
Originally developed by Arthur Luehrmann at Dartmouth College, Animal was subsequently shortened and modified by Nathan Teichholtz at DEC and Steve North at Creative Computing.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=4
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=4)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=19)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+119
View File
@@ -0,0 +1,119 @@
/**
* ANIMAL
*
*
* Converted from BASIC to Kotlin by John Long (@patimen)
*
* Animal is basically a perfect example of a binary tree. Implement it
* as such, with the QuestionNode either having an answer if it is a terminal node
* or a Question
*/
fun main() {
printIntro()
val rootQuestionNode =
QuestionOrAnswer(question = Question("DOES IT SWIM", QuestionOrAnswer("FISH"), QuestionOrAnswer("BIRD")))
while (true) {
val choice = ask("ARE YOU THINKING OF AN ANIMAL")
when {
choice == "LIST" -> printKnownAnimals(rootQuestionNode)
choice.startsWith("Q") -> return
choice.startsWith("Y") -> {
// A wrong answer means it's a new animal!
val wrongAnswer = rootQuestionNode.getWrongAnswer()
if (wrongAnswer == null) {
// The computer got the right answer!
println("WHY NOT TRY ANOTHER ANIMAL?")
} else {
// Get a new question to ask next time
wrongAnswer.askForInformationAndSave()
}
}
}
}
}
// Takes care of asking a question (on the same line) and getting
// an answer or a blank string
fun ask(question: String): String {
print("$question? ")
return readln().uppercase() ?: ""
}
// Special case for a "yes or no" question, returns true of yes
fun askYesOrNo(question: String): Boolean {
return generateSequence {
print("$question? ")
readln()
}.firstNotNullOf { yesOrNo(it) }
}
// If neither Y (true) or N (false), return null, so the above sequence
// will just keep executing until it gets the answer
private fun yesOrNo(string: String): Boolean? =
when (string.uppercase().firstOrNull()) {
'Y' -> true
'N' -> false
else -> null
}
private fun printKnownAnimals(question: QuestionOrAnswer) {
println("\nANIMALS I ALREADY KNOW ARE:")
val animals = question.getAnswers().chunked(4)
animals.forEach { line ->
// The '*' in front of line.toTypedArray() "spreads" the array as a list of parameters instead
System.out.printf("%-15s".repeat(line.size), *line.toTypedArray())
println()
}
}
private fun printIntro() {
println(" ANIMAL")
println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
println("\n\n")
println("PLAY 'GUESS THE ANIMAL'")
println("\n")
println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.")
}
class QuestionOrAnswer(private var answer: String? = null, var question: Question? = null) {
fun getAnswers(): List<String> = answer?.let { listOf(it) } ?: question!!.getAnswers()
fun getWrongAnswer(): QuestionOrAnswer? {
if (answer != null) {
// "takeUnless" will return null if the answer is "yes". In this case
// we will return the "wrong answer", aka the terminal answer that was incorrect
return this.takeUnless { askYesOrNo("IS IT A $answer") }
}
return question?.getWrongAnswer()
}
fun askForInformationAndSave() {
//Failed to get it right and ran out of questions
//Let's ask the user for the new information
val newAnimal = ask("THE ANIMAL YOU WERE THINKING OF WAS A")
val newQuestion = ask("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A \n$newAnimal FROM A $answer\n")
val newAnswer = askYesOrNo("FOR A $newAnimal THE ANSWER WOULD BE")
val trueAnswer = if (newAnswer) newAnimal else answer
val falseAnswer = if (newAnswer) answer else newAnimal
// Replace our answer with null and set the question with the data we just got
// This makes it a question instead of an answer
this.answer = null
this.question = Question(newQuestion, QuestionOrAnswer(trueAnswer), QuestionOrAnswer(falseAnswer))
}
}
class Question(
private val question: String,
private val trueAnswer: QuestionOrAnswer,
private val falseAnswer: QuestionOrAnswer
) {
fun getAnswers(): List<String> = trueAnswer.getAnswers() + falseAnswer.getAnswers()
fun getWrongAnswer(): QuestionOrAnswer? =
if (askYesOrNo(question)) {
trueAnswer.getWrongAnswer()
} else {
falseAnswer.getWrongAnswer()
}
}
+3
View File
@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Kotlin](https://kotlinlang.org/)
+223
View File
@@ -0,0 +1,223 @@
#!/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 Term::ReadLine; # Prompt and return user input
our $VERSION = '0.000_01';
# The Perl ref() built-in returns 'HASH' for a hash reference. But we
# make it a manifest constant just to avoid typos.
use constant REF_HASH => ref {};
print <<'EOD';
ANIMAL
Creative Computing Morristown, New Jersey
Play 'Guess the Animal'
Think of an animal and the computer will try to guess it.
EOD
# We keep the accumulated data in a tree structure, initialized here. As
# we accumulate animals, we replace the 'yes' or 'no' keys with new hash
# references.
my $database = {
question => 'Does it swim', # Initial question
yes => 'fish', # Result of answering 'y'
no => 'bird', # Result of answering 'n'
};
while ( 1 ) {
my $resp = get_input(
'Are you thinking of an an animal? [y/n/list]: '
);
if ( $resp =~ m/ \A y /smxi ) {
# If we got an answer beginning with 'y', walk the database
walk_tree( $database );
} elsif ( $resp =~ m/ \A list \z /smxi ) {
# If we got 'list', list the currently-known animals.
say '';
say 'Animals I already know are:';
say " $_" for sort( list_animals( $database ) );
}
}
# 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( 'animal' );
$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 };
}
# Recurse through the database, returning the names of all animals in
# it, in an undefined order.
sub list_animals {
my ( $node ) = @ARG;
return $node unless REF_HASH eq ref $node;
return( map { list_animals( $node->{$_} ) } qw{ yes no } );
}
# Find or create the desired animal.
# Ask the question stored in the node given in its argument. If the key
# selected by the answer ('yes' or 'no') is another node, recurse. If it
# is an animal name, confirm it, or add a new animal as appropriate.
sub walk_tree {
my ( $node ) = @ARG;
# Ask the question associated with this node. Turn the true/false
# response into 'yes' or 'no', since those are the names of the
# respective keys.
my $resp = get_yes_no ( $node->{question} ) ? 'yes' : 'no';
# Chose the datum for the response.
my $choice = $node->{ $resp };
# If the datum is a hash reference
if ( REF_HASH eq ref $choice ) {
# Recurse into it
walk_tree( $choice );
# Otherwise it is an actual animal (i.e. terminal node). Check it.
} else {
# If this is not the animal the player was thinking of
unless ( get_yes_no( "Is it a $choice" ) ) {
# Find out what animal the player was thinking of
my $animal = lc get_input(
'The animal you were thinking of was a ',
);
# Get a yes/no question that distinguishes the animal the
# player was thinking of from the animal we found in the
# tree.
say 'Please type in a question that would distinguish a';
my $question = get_input( "$animal from a $choice: " );
# Find out whether the new animal is selected by 'yes' or
# 'no'. If 'no', swap the original animal with the new one
# for convenience.
( $choice, $animal ) = ( $animal, $choice ) if get_yes_no(
"For a $animal the answer would be",
);
# Replace the animal we originally found by a new node
# giving the original animal, the new animal, and the
# question that distinguishes them.
$node->{ $resp } = {
question => $question,
no => $animal,
yes => $choice,
};
}
# Find out if the player wants to play again. If not, exit. If
# so, just return.
say '';
exit unless get_yes_no( 'Why not try another animal' );
return;
}
}
__END__
=head1 TITLE
animal.pl - Play the game 'animal' from Basic Computer Games
=head1 SYNOPSIS
animal.pl
=head1 DETAILS
This Perl script is a port of C<animal>, which is the 3ed entry in Basic
Computer Games.
The original BASIC was greatly complicated by the need to emulate a
binary tree with an array. The implementation using hashes as nodes in
an actual binary tree is much simpler.
=head1 PORTED BY
Thomas R. Wyant, III F<wyant at cpan dot org>
=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<https://www.perlfoundation.org/artistic-license-10.html>, and/or the
Gnu GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
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 :
+32 -1
View File
@@ -1,7 +1,38 @@
### Awari
Awari is an ancient African game played with seven sticks and thirty-six stones or beans laid out as shown above. The board is divided into six compartments or pits on each side. In addition, there are two special home pits at the ends.
A move is made by taking all the beans from any (non-empty) pit on your own side. Starting from the pit to the right of this one, these beans are sown one in each pit working around the board anticlockwise.
A turn consists of one or two moves. If the last bean of your move is sown in your own home you may take a second move.
If the last bean sown in a move lands in an empty pit, provided that the opposite pit is not empty, all the beans in the opposite pit, together with the last bean sown are captured and moved to the players home.
When either side is empty, the game is finished. The player with the most beans in his home has won.
In the computer version, the board is printed as 14 numbers representing the 14 pits.
```
3 3 3 3 3 3
0 0
3 3 3 3 3 3
```
The pits on your (lower) side are numbered 1-6 from left to right. The pits on my (the computers) side are numbered from my left (your right).
To make a move you type in the number of a pit. If the last bean lands in your home, the computer types AGAIN? and then you type in your second move.
The computers move is typed, followed by a diagram of the board in its new state. The computer always offers you the first move. This is considered to be a slight advantage.
There is a learning mechanism in the program that causes the play of the computer to improve as it playes more games.
The original version of Awari is adopted from one originally written by Geoff Wyvill of Bradford, Yorkshire, England.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=6
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=6)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=21)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use List::Util 'none';
# our board will be represented with an array of 14 slots, from 0 to 13.
# Positions 6 and 13 represent the "home pit" for the human and the
# computer, respectively.
use constant PLAYER_HOME => 6;
use constant COMPUTER_HOME => 13;
use constant FIRST => 0;
use constant AGAIN => 1;
exit main(@ARGV);
sub main {
$|++; # disable buffering on standard output, every print will be
# done immediately
welcome(); # startup message
# this array will keep track of computer-side failures, defined as
# "the computer did not win". Whenever the computer loses or draws, the
# specific sequence of moves will be saved and then used to drive
# the search for a (hopefully) optimal move.
my $failures = [];
while ('enjoying') {
# a new game starts, let's reset the board to the initial condition
my $board = [ (3) x 6, 0, (3) x 6, 0 ];
# this string will keep track of all moves performed
my $moves = '/';
# the human player starts
my $turn = 'player';
say "\n";
print_board($board);
while (not is_game_over($board)) {
my $move; # this will collect the move in this turn
if ($turn eq 'player') { # "first" move for player
# player_move(...) does the move selected by the player,
# returning both the selected move as well as the pit id
# where the last seed landed
($move, my $landing) = player_move($board);
# if we landed on the Player's Home Pit we get another move
$turn = $landing == PLAYER_HOME ? 'player-again' : 'computer';
}
elsif ($turn eq 'player-again') { # "second" move for player
# here we call player_move making it clear that it's the
# second move, to get the right prompt eventually. We only
# care for the $move as the result, so we ignore the other.
($move) = player_move($board, AGAIN);
$turn = 'computer';
}
else {
# the computer_move(...) function analyzes the $board as well
# as adapting the strategy based on past "failures" (i.e.
# matches where the computer did not win). For this it's
# important to pass the log of these failures, as well as the
# full record of moves in this specific match.
($move, my $landing) = computer_move($board, $failures, $moves);
print "\nMY MOVE IS ", $move - 6;
# do the second move in the turn if conditions apply
if ($landing == COMPUTER_HOME && ! is_game_over($board)) {
# save the first move before doing the second one!
$moves .= "$move/";
my ($move) = computer_move($board, $failures, $moves);
print ',', $move - 6;
}
$turn = 'player';
}
# append the last selected move by either party, to track this
# specific match (useful for computer's AI and ML)
$moves .= "$move/";
print_board($board);
}
# assess_victory() returns the difference between player's and
# computer's seeds, so a negative value is a win for the computer.
my $computer_won = assess_victory($board) < 0;
# if this last match was a "failure" (read: not a win for the
# computer), then record it for future memory.
push $failures->@*, $moves unless $computer_won;
}
return 0;
}
# calculate the difference between the two home pits. Negative values mean
# that the computer won, 0 is a draw, positive values is a player's win.
# The difference is also returned back, in case of need.
sub assess_victory ($board) {
say "\nGAME OVER";
my $difference = $board->[PLAYER_HOME] - $board->[COMPUTER_HOME];
if ($difference < 0) {
say 'I WIN BY ', -$difference, ' POINTS';
}
else {
say $difference ? "YOU WIN BY $difference POINTS" : 'DRAWN GAME';
}
return $difference;
}
# move the seeds from $pit and take into account possible bonuses
sub move_seeds ($board, $pit) {
# get the seeds from the selected pit $pit
my $seeds = $board->[$pit];
$board->[$pit] = 0;
# $landing will be our "moving cursor" to place seeds around
my $landing = $pit;
while ($seeds > 0) {
$landing = ($landing + 1) % 14; # 12 --> 13 -[wrap]-> 0 --> 1
--$seeds;
++$board->[$landing];
}
# check for "stealing seeds" condition. This cannot happen in home pits
if ($landing != PLAYER_HOME && $landing != COMPUTER_HOME
&& $board->[$landing] == 1 && $board->[12 - $landing] > 0) {
my $home = $pit < 7 ? PLAYER_HOME : COMPUTER_HOME;
$board->[$home] += 1 + $board->[12 - $landing];
$board->@[$landing, 12 - $landing] = (0, 0);
}
return ($pit, $landing);
}
sub get_player_move ($board, $prompt) {
print "\n$prompt? ";
while (defined(my $move = <STDIN>)) {
chomp($move); # remove newline
return $move - 1 if $move =~ m{\A[1-6]\z}mxs && $board->[$move - 1];
print 'ILLEGAL MOVE\nAGAIN? ';
}
die "goodbye\n";
}
sub player_move ($board, $stage = FIRST) {
my $prompt = $stage == FIRST ? 'YOUR MOVE' : 'AGAIN';
my $selected_move = get_player_move($board, $prompt);
return move_seeds($board, $selected_move);
}
sub computer_move ($board, $failures, $moves) {
# we will go through all possible moves for the computer and all
# possible responses by the player, collecting the "best" move in terms
# of reasonable outcome (assuming that each side wants to maximize their
# outcome. $best_move will eventually contain the best move for the
# computer, and $best_difference the best difference in scoring (as
# seen from the computer).
my ($best_move, $best_difference);
for my $c_move (7 .. 12) {
next unless $board->[$c_move]; # only consider pits with seeds inside
# we work on a copy of the board to do all our trial-and-errors
my $copy = [ $board->@* ];
move_seeds($copy, $c_move);
# it's time to "think like a player" and see what's the "best" move
# for the player in this situation. This heuristic is "not perfect"
# but it seems OK anyway.
my $best_player_score = 0;
for my $p_move (0 .. 5) {
next unless $copy->[$p_move]; # only pits with seeds inside
my $landing = $copy->[$p_move] + $p_move;
# the player's score for this move, calculated as additional seeds
# placed in the player's pit. The original algorithm sets this to
# 1 only if the $landing position is greater than 13, which can
# be obtained by setting the ORIGINAL environment variable to a
# "true" value (in Perl terms). Otherwise it is calculated
# according to the real rules for the game.
my $p_score = $ENV{ORIGINAL} ? $landing > 13
: ($landing + 1) % 14 > 6;
# whatever, the landing position must be within the bounds
$landing %= 14;
# if the conditions apply, the player's move might win additional
# seeds, which we have to to take into account.
$p_score += $copy->[12 - $landing]
if $copy->[$landing] == 0
&& $landing != PLAYER_HOME && $landing != COMPUTER_HOME;
# let's compare this move's score against the best collected
# so far (as a response to a specific computer's move).
$best_player_score = $p_score if $p_score > $best_player_score;
}
# the overall score for the player is the additional seeds we just
# calculated into $best_player_score plus the seeds that were already
# in the player's pit
$best_player_score += $copy->[PLAYER_HOME];
# the best difference we can aim for with this computer's move must
# assume that the player will try its best
my $difference = $copy->[COMPUTER_HOME] - $best_player_score;
# now it's time to check this computer's move against the history
# of failed matches. $candidate_moves will be the "candidate" list
# of moves if we accept this one.
my $candidate_moves = $moves . $c_move . '/';
for my $failure ($failures->@*) {
# index(.) returns 0 if and only if $candidate_moves appears at
# the very beginning of $failure, i.e. it matches a previous
# behaviour.
next if index($failure, $candidate_moves) != 0;
# same sequence of moves as before... assign a penalty
$difference -= 2;
}
# update $best_move and $best_difference if they need to
($best_move, $best_difference) = ($c_move, $difference)
if (! defined $best_move) || ($best_difference < $difference);
}
# apply the selected move and return
return move_seeds($board, $best_move);
}
sub welcome {
say ' ' x 34, 'AWARI';
say ' ' x 15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY';
}
sub print_board ($board) {
my $template = '
%2d %2d %2d %2d %2d %2d
%2d %d
%2d %2d %2d %2d %2d %2d
';
printf $template, $board->@[12, 11, 10, 9, 8 , 7, 13, 6, 0 .. 5];
return;
}
sub is_game_over ($board) {
# game over if the player's side is empty
return 1 if none { $_ } $board->@[0 .. 5];
# game over if the computers' side is empty
return 1 if none { $_ } $board->@[7 .. 12];
# not game over
return 0;
}
+1 -1
View File
@@ -1,3 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Ruby](https://www.ruby-lang.org/en/)
Conversion to [Ruby](https://www.ruby-lang.org/en/) by [Alex Scown](https://github.com/TheScown)
+311
View File
@@ -0,0 +1,311 @@
require 'strscan'
# Prints a number according to Vintage Basic's PRINT statement
# @param n The number to print
def print_number(n)
# PRINT adds padding after a number and before a positive number
print ' ' if n >= 0
print n.to_s
print ' '
end
# Mimic the INPUT statement using Vintage Basic as a reference
# @param prompt The prompt to show to the user
# @return An array of strings representing the inputted values
def input(prompt)
prompt_suffix = '? '
print "#{prompt}#{prompt_suffix}"
input = gets.chomp.strip
scanner = StringScanner.new(input)
input_values = []
until scanner.eos?
scanner.scan(/\s+/)
if scanner.check(/"/)
scanner.scan(/"/)
next_string = scanner.scan_until(/"/)
if next_string
# Remove the trailing close quote
next_string.chomp!('"')
else
# No close quote Vintage Basic crashes in this case
raise 'Unmatched quotes in input'
end
elsif scanner.exist?(/,/)
next_string = scanner.scan_until(/,/).chomp(',')
else
next_string = scanner.scan_until(/\s+|$/).rstrip
end
input_values << next_string
end
input_values << '' if input_values.empty?
input_values
end
class Game
def initialize(history, non_win_count)
@beans = Array.new(13, 3)
@beans[6] = 0
@beans[13] = 0
@turn_counter = 0
@history = history
@non_win_count = non_win_count
end
# @return [Boolean] True if the computer did not win the game
def play
while true
print_beans
move = get_move("YOUR MOVE")
home_pit = 6
computer_home_pit = 13
last_pit = perform_move(move, home_pit)
print_beans
break if game_over
if home_pit == last_pit
second_move = get_move("AGAIN")
perform_move(second_move, home_pit)
print_beans
break if game_over
end
computer_move, computer_last_pit = get_computer_move
print "MY MOVE IS #{computer_move - 6}"
break if game_over
if computer_last_pit == computer_home_pit
second_computer_move, _ = get_computer_move
print ",#{second_computer_move - 6}"
break if game_over
end
end
end_game
end
private
def game_over
@beans[0...6].all? { |b| b == 0 } || @beans[7...13].all? { |b| b == 0 }
end
# @return [Boolean] True if the computer did not win
def end_game
puts
puts "GAME OVER"
difference = @beans[6] - @beans[13]
if difference < 0
puts "I WIN BY #{-difference} POINTS"
return
end
puts "YOU WIN BY #{difference} POINTS" if difference > 0
puts "DRAWN GAME" if difference == 0
difference >= 0
end
# @param [Integer] move
# @param [Integer] home_pit
def perform_move(move, home_pit)
last_pit = distribute_beans(move, home_pit)
update_history(move)
last_pit
end
def update_history(current_move)
k = current_move % 7
@turn_counter += 1
# Add the move to the history
@history[@non_win_count] = @history[@non_win_count] * 6 + k if @turn_counter < 9
end
def print_beans
puts
# Print computer beans
print ' ' * 3
@beans[7...13].reverse.each { |bean_count| print_bean(bean_count) }
puts
# Print home beans
print_bean(@beans[13])
print ' ' * 23
print_number(@beans[6]) # This is not print_bean in line with the original version
puts
# Print player beans
print ' ' * 3
@beans[0...6].each { |bean_count| print_bean(bean_count) }
puts
puts
end
def get_move(prompt)
move = get_integer_input(prompt)
while move < 1 || move > 6 || @beans[move - 1] == 0
puts "ILLEGAL MOVE"
move = get_integer_input("AGAIN")
end
move - 1
end
def distribute_beans(start_pit, home_pit, beans = @beans)
beans_to_distribute = beans[start_pit]
beans[start_pit] = 0
current_pit = start_pit
(0...beans_to_distribute).each do
current_pit = (current_pit + 1) % beans.size
beans[current_pit] += 1
end
# If the last pit was empty before we put a bean in it (and it's not a scoring pit), add beans to score
if beans[current_pit] == 1 && current_pit != 6 && current_pit != 13 && beans[12 - current_pit] != 0
beans[home_pit] = beans[home_pit] + beans[12 - current_pit] + 1
beans[current_pit] = 0
beans[12 - current_pit] = 0
end
current_pit
end
def print_bean(bean_count)
print ' ' if bean_count < 10
print_number(bean_count)
end
def get_integer_input(prompt)
integer_value = nil
input_values = input(prompt)
while integer_value.nil?
print '!EXTRA INPUT IGNORED' if (input_values.size > 1)
value = input_values.first
begin
integer_value = Integer(value)
rescue
puts '!NUMBER EXPECTED - RETRY INPUT LINE'
input_values = input('')
end
end
integer_value
end
def get_computer_move
d = -99
home_pit = 13
chosen_move = 7
# Test all possible moves
(7...13).each do |move_under_test|
# Create a copy of the beans to test against
beans_copy = @beans.dup
# If the move is not legal, skip it
next if beans_copy[move_under_test] == 0
# Determine the best response the player may make to this move
player_max_score = 0
# Make the move under test against the copy
distribute_beans(move_under_test, home_pit, beans_copy)
# Test every player response
(0...6).each do |i|
# Skip the move if it would be illegal
next if beans_copy[i] == 0
# Determine the last
landing_with_overflow = beans_copy[i] + i
# If landing > 13 the player has put a bean in both home pits
player_move_score = (landing_with_overflow > 14) ? 1 : 0
# Find the actual pit
landing = landing_with_overflow % 14
# If the landing pit is empty, the player will steal beans
if beans_copy[landing] == 0 && landing != 6 && landing != 13
player_move_score = beans_copy[12 - landing] + player_move_score
end
# Update the max score if this move is the best player move
player_max_score = player_move_score if player_move_score > player_max_score
end
# Final score for move is computer score, minus the player's score and any player gains from their best move
final_score = beans_copy[13] - beans_copy[6] - player_max_score
if @turn_counter < 9
k = move_under_test % 7
(0...@non_win_count).each do |i|
# Penalise move if it was used in a losing game
final_score = final_score - 2 if @history[@non_win_count] * 6 + k == ((Float(@history[i]) / 6 ** (7 - @turn_counter)) + 0.1).floor
end
end
# Choose the move if it is the best move found so far
if final_score >= d
chosen_move = move_under_test
d = final_score
end
end
last_pit = perform_move(chosen_move, home_pit)
[chosen_move, last_pit]
end
end
puts 'AWARI'.center(80)
puts 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'.center(80)
# Initialise stable variables
history = Array.new(50)
non_win_count = 0
# APPLICATION LOOP
while true
puts
puts
history[non_win_count] = 0
game = Game.new(history, non_win_count)
computer_didnt_win = game.play
non_win_count += 1 if computer_didnt_win
end
+14 -1
View File
@@ -1,7 +1,20 @@
### Bagels
In this game, the computer picks a 3-digit secret number using the digits 0 to 9 and you attempt to guess what it is. You are allowed up to twenty guesses. No digit is repeated. After each guess the computer will give you clues about your guess as follows:
- PICO One digit is correct, but in the wrong place
- FERMI One digit is in the correct place
- BAGELS No digit is correct
You will learn to draw inferences from the clues and, with practice, youll learn to improve your score. There are several good strategies for playing Bagels. After you have found a good strategy, see if you can improve it. Or try a different strategy altogether to see if it is any better. While the program allows up to twenty guesses, if you use a good strategy it should not take more than eight guesses to get any number.
The original authors of this program are D. Resek and P. Rowe of the Lawrence Hall of Science, Berkeley, California.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=9
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=9)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=21)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+8 -1
View File
@@ -1,7 +1,14 @@
### Banner
This program creates a large banner on a terminal of any message you input. The letters may be any dimension of you wish although the letter height plus distance from left-hand side should not exceed 6 inches. Experiment with the height and width until you get a pleasing effect on whatever terminal you are using.
This program was written by Leonard Rosendust of Brooklyn, New York.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=10
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=10)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=25)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+24 -1
View File
@@ -1,7 +1,30 @@
### Basketball
This program simulates a game of basketball between Dartmouth College and an opponent of your choice. You are the Dartmouth captain and control the type of shot and defense during the course of the game.
There are four types of shots:
1. Long Jump Shot (30ft)
2. Short Jump Shot (15ft)
3. Lay Up
4. Set Shot
Both teams use the same defense, but you may call it:
- Enter (6): Press
- Enter (6.5): Man-to-man
- Enter (7): Zone
- Enter (7.5): None
To change defense, type “0” as your next shot.
Note: The game is biased slightly in favor of Dartmouth. The average probability of a Dartmouth shot being good is 62.95% compared to a probability of 61.85% for their opponent. (This makes the sample run slightly remarkable in that Cornell won by a score of 45 to 42 Hooray for the Big Red!)
Charles Bacheller of Dartmouth College was the original author of this game.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=12
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=12)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=27)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+469
View File
@@ -0,0 +1,469 @@
import java.lang.Math;
import java.util.*;
import java.util.Scanner;
/* The basketball class is a computer game that allows you to play as
Dartmouth College's captain and playmaker
The game uses set probabilites to simulate outcomes of each posession
You are able to choose your shot types as well as defensive formations */
public class Basketball {
int time = 0;
int[] score = {0, 0};
double defense = -1;
List<Double> defense_choices = Arrays.asList(6.0, 6.5, 7.0, 7.5);
int shot = -1;
List<Integer> shot_choices = Arrays.asList(0, 1, 2, 3, 4);
double opponent_chance = 0;
String opponent = null;
public Basketball() {
// Explains the keyboard inputs
System.out.println("\t\t\t Basketball");
System.out.println("\t Creative Computing Morristown, New Jersey\n\n\n");
System.out.println("This is Dartmouth College basketball. ");
System.out.println("Υou will be Dartmouth captain and playmaker.");
System.out.println("Call shots as follows:");
System.out.println("1. Long (30ft.) Jump Shot; 2. Short (15 ft.) Jump Shot; "
+ "3. Lay up; 4. Set Shot");
System.out.println("Both teams will use the same defense. Call Defense as follows:");
System.out.println("6. Press; 6.5 Man-to-Man; 7. Zone; 7.5 None.");
System.out.println("To change defense, just type 0 as your next shot.");
System.out.print("Your starting defense will be? ");
Scanner scanner = new Scanner(System.in); // creates a scanner
// takes input for a defense
if (scanner.hasNextDouble()) {
defense = scanner.nextDouble();
}
else {
scanner.next();
}
// makes sure that input is legal
while (!defense_choices.contains(defense)) {
System.out.print("Your new defensive allignment is? ");
if (scanner.hasNextDouble()) {
defense = scanner.nextDouble();
}
else {
scanner.next();
continue;
}
}
// takes input for opponent's name
System.out.print("\nChoose your opponent? ");
opponent = scanner.next();
start_of_period();
}
// adds points to the score
// team can take 0 or 1, for opponent or Dartmouth, respectively
private void add_points(int team, int points) {
score[team] += points;
print_score();
}
private void ball_passed_back() {
System.out.print("Ball passed back to you. ");
dartmouth_ball();
}
// change defense, called when the user enters 0 for their shot
private void change_defense() {
defense = -1;
Scanner scanner = new Scanner(System.in); // creates a scanner
while (!defense_choices.contains(defense)) {
System.out.println("Your new defensive allignment is? ");
if (scanner.hasNextDouble()) {
defense = (double)(scanner.nextDouble());
}
else {
continue;
}
}
dartmouth_ball();
}
// simulates two foul shots for a player and adds the points
private void foul_shots(int team) {
System.out.println("Shooter fouled. Two shots.");
if (Math.random() > .49) {
if (Math.random() > .75) {
System.out.println("Both shots missed.");
}
else {
System.out.println("Shooter makes one shot and misses one.");
score[team] += 1;
}
}
else {
System.out.println("Shooter makes both shots.");
score[team] += 2;
}
print_score();
}
// called when time = 50, starts a new period
private void halftime() {
System.out.println("\n ***** End of first half *****\n");
print_score();
start_of_period();
}
// prints the current score
private void print_score() {
System.out.println("Score: " + score[1] + " to " + score[0] + "\n");
}
// simulates a center jump for posession at the beginning of a period
private void start_of_period() {
System.out.println("Center jump");
if (Math.random() > .6) {
System.out.println("Dartmouth controls the tap.\n");
dartmouth_ball();
}
else {
System.out.println(opponent + " controls the tap.\n");
opponent_ball();
}
}
// called when t = 92
private void two_minute_warning() {
System.out.println(" *** Two minutes left in the game ***");
}
// called when the user enters 1 or 2 for their shot
private void dartmouth_jump_shot() {
time ++;
if (time == 50) {
halftime();
}
else if (time == 92) {
two_minute_warning();
}
System.out.println("Jump Shot.");
// simulates chances of different possible outcomes
if (Math.random() > .341 * defense / 8) {
if (Math.random() > .682 * defense / 8) {
if (Math.random() > .782 * defense / 8) {
if (Math.random() > .843 * defense / 8) {
System.out.println("Charging foul. Dartmouth loses ball.\n");
opponent_ball();
}
else {
// player is fouled
foul_shots(1);
opponent_ball();
}
}
else {
if (Math.random() > .5) {
System.out.println("Shot is blocked. Ball controlled by " +
opponent + ".\n");
opponent_ball();
}
else {
System.out.println("Shot is blocked. Ball controlled by Dartmouth.");
dartmouth_ball();
}
}
}
else {
System.out.println("Shot is off target.");
if (defense / 6 * Math.random() > .45) {
System.out.println("Rebound to " + opponent + "\n");
opponent_ball();
}
else {
System.out.println("Dartmouth controls the rebound.");
if (Math.random() > .4) {
if (defense == 6 && Math.random() > .6) {
System.out.println("Pass stolen by " + opponent
+ ", easy lay up");
add_points(0, 2);
dartmouth_ball();
}
else {
// ball is passed back to you
ball_passed_back();
}
}
else {
System.out.println("");
dartmouth_non_jump_shot();
}
}
}
}
else {
System.out.println("Shot is good.");
add_points(1, 2);
opponent_ball();
}
}
// called when the user enters 0, 3, or 4
// lay up, set shot, or defense change
private void dartmouth_non_jump_shot() {
time ++;
if (time == 50) {
halftime();
}
else if (time == 92) {
two_minute_warning();
}
if (shot == 4) {
System.out.println("Set shot.");
}
else if (shot == 3) {
System.out.println("Lay up.");
}
else if (shot == 0) {
change_defense();
}
// simulates different outcomes after a lay up or set shot
if (7/defense*Math.random() > .4) {
if (7/defense*Math.random() > .7) {
if (7/defense*Math.random() > .875) {
if (7/defense*Math.random() > .925) {
System.out.println("Charging foul. Dartmouth loses the ball.\n");
opponent_ball();
}
else {
System.out.println("Shot blocked. " + opponent + "'s ball.\n");
opponent_ball();
}
}
else {
foul_shots(1);
opponent_ball();
}
}
else {
System.out.println("Shot is off the rim.");
if (Math.random() > 2/3) {
System.out.println("Dartmouth controls the rebound.");
if (Math.random() > .4) {
System.out.println("Ball passed back to you.\n");
dartmouth_ball();
}
else {
dartmouth_non_jump_shot();
}
}
else {
System.out.println(opponent + " controls the rebound.\n");
opponent_ball();
}
}
}
else {
System.out.println("Shot is good. Two points.");
add_points(1, 2);
opponent_ball();
}
}
// plays out a Dartmouth posession, starting with your choice of shot
private void dartmouth_ball() {
Scanner scanner = new Scanner(System.in); // creates a scanner
System.out.print("Your shot? ");
shot = -1;
if (scanner.hasNextInt()) {
shot = scanner.nextInt();
}
else {
System.out.println("");
scanner.next();
}
while (!shot_choices.contains(shot)) {
System.out.print("Incorrect answer. Retype it. Your shot?");
if (scanner.hasNextInt()) {
shot = scanner.nextInt();
}
else {
System.out.println("");
scanner.next();
}
}
if (time < 100 || Math.random() < .5) {
if (shot == 1 || shot == 2) {
dartmouth_jump_shot();
}
else {
dartmouth_non_jump_shot();
}
}
else {
if (score[0] != score[1]) {
System.out.println("\n ***** End Of Game *****");
System.out.println("Final Score: Dartmouth: " + score[1] + " "
+ opponent + ": " + score[0]);
System.exit(0);
}
else {
System.out.println("\n ***** End Of Second Half *****");
System.out.println("Score at end of regulation time:");
System.out.println(" Dartmouth: " + score[1] + " " +
opponent + ": " + score[0]);
System.out.println("Begin two minute overtime period");
time = 93;
start_of_period();
}
}
}
// simulates the opponents jumpshot
private void opponent_jumpshot() {
System.out.println("Jump Shot.");
if (8/defense*Math.random() > .35) {
if (8/defense*Math.random() > .75) {
if (8/defense*Math.random() > .9) {
System.out.println("Offensive foul. Dartmouth's ball.\n");
dartmouth_ball();
}
else {
foul_shots(0);
dartmouth_ball();
}
}
else {
System.out.println("Shot is off the rim.");
if (defense/6*Math.random() > .5) {
System.out.println(opponent + " controls the rebound.");
if (defense == 6) {
if (Math.random() > .75) {
System.out.println("Ball stolen. Easy lay up for Dartmouth.");
add_points(1, 2);
opponent_ball();
}
else {
if (Math.random() > .5) {
System.out.println("");
opponent_non_jumpshot();
}
else {
System.out.println("Pass back to " + opponent +
" guard.\n");
opponent_ball();
}
}
}
else {
if (Math.random() > .5) {
opponent_non_jumpshot();
}
else {
System.out.println("Pass back to " + opponent +
" guard.\n");
opponent_ball();
}
}
}
else {
System.out.println("Dartmouth controls the rebound.\n");
dartmouth_ball();
}
}
}
else {
System.out.println("Shot is good.");
add_points(0, 2);
dartmouth_ball();
}
}
// simulates opponents lay up or set shot
private void opponent_non_jumpshot() {
if (opponent_chance > 3) {
System.out.println("Set shot.");
}
else {
System.out.println("Lay up");
}
if (7/defense*Math.random() > .413) {
System.out.println("Shot is missed.");
if (defense/6*Math.random() > .5) {
System.out.println(opponent + " controls the rebound.");
if (defense == 6) {
if (Math.random() > .75) {
System.out.println("Ball stolen. Easy lay up for Dartmouth.");
add_points(1, 2);
opponent_ball();
}
else {
if (Math.random() > .5) {
System.out.println("");
opponent_non_jumpshot();
}
else {
System.out.println("Pass back to " + opponent +
" guard.\n");
opponent_ball();
}
}
}
else {
if (Math.random() > .5) {
System.out.println("");
opponent_non_jumpshot();
}
else {
System.out.println("Pass back to " + opponent + " guard\n");
opponent_ball();
}
}
}
else {
System.out.println("Dartmouth controls the rebound.\n");
dartmouth_ball();
}
}
else {
System.out.println("Shot is good.");
add_points(0, 2);
dartmouth_ball();
}
}
// simulates an opponents possesion
// #randomly picks jump shot or lay up / set shot.
private void opponent_ball() {
time ++;
if (time == 50) {
halftime();
}
opponent_chance = 10/4*Math.random()+1;
if (opponent_chance > 2) {
opponent_non_jumpshot();
}
else {
opponent_jumpshot();
}
}
public static void main(String[] args) {
Basketball new_game = new Basketball();
}
}
+343
View File
@@ -0,0 +1,343 @@
import random
# The basketball class is a computer game that allows you to play as
# Dartmouth College's captain and playmaker
# The game uses set probabilites to simulate outcomes of each posession
# You are able to choose your shot types as well as defensive formations
class Basketball():
def __init__(self):
self.time = 0
self.score = [0, 0] # first value is opponents score, second is home
self.defense = None
self.defense_choices = [6, 6.5, 7, 7.5]
self.shot = None
self.shot_choices = [0, 1, 2, 3, 4]
self.z1 = None
# Explains the keyboard inputs
print("\t\t\t Basketball")
print("\t Creative Computing Morristown, New Jersey\n\n\n")
print("This is Dartmouth College basketball. ")
print("Υou will be Dartmouth captain and playmaker.")
print("Call shots as follows:")
print("1. Long (30ft.) Jump Shot; 2. Short (15 ft.) Jump Shot; "
+ "3. Lay up; 4. Set Shot")
print("Both teams will use the same defense. Call Defense as follows:")
print("6. Press; 6.5 Man-to-Man; 7. Zone; 7.5 None.")
print("To change defense, just type 0 as your next shot.")
print("Your starting defense will be? ", end='')
# takes input for a defense
try:
self.defense = float(input())
except ValueError:
self.defense = None
# if the input wasn't a valid defense, takes input again
while self.defense not in self.defense_choices:
print("Your new defensive allignment is? ", end='')
try:
self.defense = float(input())
except ValueError:
continue
# takes input for opponent's name
print("\nChoose your opponent? ", end='')
self.opponent = input()
self.start_of_period()
# adds points to the score
# team can take 0 or 1, for opponent or Dartmouth, respectively
def add_points(self, team, points):
self.score[team] += points
self.print_score()
def ball_passed_back(self):
print("Ball passed back to you. ", end='')
self.dartmouth_ball()
# change defense, called when the user enters 0 for their shot
def change_defense(self):
self.defense = None
while self.defense not in self.defense_choices:
print("Your new defensive allignment is? ")
try:
self.defense = float(input())
except ValueError:
continue
self.dartmouth_ball()
# simulates two foul shots for a player and adds the points
def foul_shots(self, team):
print("Shooter fouled. Two shots.")
if random.random() > .49:
if random.random() > .75:
print("Both shots missed.")
else:
print("Shooter makes one shot and misses one.")
self.score[team] += 1
else:
print("Shooter makes both shots.")
self.score[team] += 2
self.print_score()
# called when t = 50, starts a new period
def halftime(self):
print("\n ***** End of first half *****\n")
self.print_score()
self.start_of_period()
# prints the current score
def print_score(self):
print("Score: " + str(self.score[1])
+ " to " + str(self.score[0]) + "\n")
# simulates a center jump for posession at the beginning of a period
def start_of_period(self):
print("Center jump")
if random.random() > .6:
print("Dartmouth controls the tap.\n")
self.dartmouth_ball()
else:
print(self.opponent + " controls the tap.\n")
self.opponent_ball()
# called when t = 92
def two_minute_warning(self):
print(" *** Two minutes left in the game ***")
# called when the user enters 1 or 2 for their shot
def dartmouth_jump_shot(self):
self.time += 1
if self.time == 50:
self.halftime()
elif self.time == 92:
self.two_minute_warning()
print("Jump Shot.")
# simulates chances of different possible outcomes
if random.random() > .341 * self.defense / 8:
if random.random() > .682 * self.defense / 8:
if random.random() > .782 * self.defense / 8:
if random.random() > .843 * self.defense / 8:
print("Charging foul. Dartmouth loses ball.\n")
self.opponent_ball()
else:
# player is fouled
self.foul_shots(1)
self.opponent_ball()
else:
if random.random() > .5:
print("Shot is blocked. Ball controlled by " +
self.opponent + ".\n")
self.opponent_ball()
else:
print("Shot is blocked. Ball controlled by Dartmouth.")
self.dartmouth_ball()
else:
print("Shot is off target.")
if self.defense / 6 * random.random() > .45:
print("Rebound to " + self.opponent + "\n")
self.opponent_ball()
else:
print("Dartmouth controls the rebound.")
if random.random() > .4:
if self.defense == 6 and random.random() > .6:
print("Pass stolen by " + self.opponent
+ ", easy lay up")
self.add_points(0, 2)
self.dartmouth_ball()
else:
# ball is passed back to you
self.ball_passed_back()
else:
print("")
self.dartmouth_non_jump_shot()
else:
print("Shot is good.")
self.add_points(1, 2)
self.opponent_ball()
# called when the user enters 0, 3, or 4
# lay up, set shot, or defense change
def dartmouth_non_jump_shot(self):
self.time += 1
if self.time == 50:
self.halftime()
elif self.time == 92:
self.two_minute_warning()
if self.shot == 4:
print("Set shot.")
elif self.shot == 3:
print("Lay up.")
elif self.shot == 0:
self.change_defense()
# simulates different outcomes after a lay up or set shot
if 7/self.defense*random.random() > .4:
if 7/self.defense*random.random() > .7:
if 7/self.defense*random.random() > .875:
if 7/self.defense*random.random() > .925:
print("Charging foul. Dartmouth loses the ball.\n")
self.opponent_ball()
else:
print("Shot blocked. " + self.opponent + "'s ball.\n")
self.opponent_ball()
else:
self.foul_shots(1)
self.opponent_ball()
else:
print("Shot is off the rim.")
if random.random() > 2/3:
print("Dartmouth controls the rebound.")
if random.random() > .4:
print("Ball passed back to you.\n")
self.dartmouth_ball()
else:
self.dartmouth_non_jump_shot()
else:
print(self.opponent + " controls the rebound.\n")
self.opponent_ball()
else:
print("Shot is good. Two points.")
self.add_points(1, 2)
self.opponent_ball()
# plays out a Dartmouth posession, starting with your choice of shot
def dartmouth_ball(self):
print("Your shot? ", end='')
self.shot = None
try:
self.shot = int(input())
except ValueError:
self.shot = None
while self.shot not in self.shot_choices:
print("Incorrect answer. Retype it. Your shot? ", end='')
try:
self.shot = int(input())
except:
continue
if self.time < 100 or random.random() < .5:
if self.shot == 1 or self.shot == 2:
self.dartmouth_jump_shot()
else:
self.dartmouth_non_jump_shot()
else:
if self.score[0] != self.score[1]:
print("\n ***** End Of Game *****")
print("Final Score: Dartmouth: " + str(self.score[1]) + " "
+ self.opponent + ": " + str(self.score[0]))
else:
print("\n ***** End Of Second Half *****")
print("Score at end of regulation time:")
print(" Dartmouth: " + str(self.score[1]) + " " +
self.opponent + ": " + str(self.score[0]))
print("Begin two minute overtime period")
self.time = 93
self.start_of_period()
# simulates the opponents jumpshot
def opponent_jumpshot(self):
print("Jump Shot.")
if 8/self.defense*random.random() > .35:
if 8/self.defense*random.random() > .75:
if 8/self.defense*random.random() > .9:
print("Offensive foul. Dartmouth's ball.\n")
self.dartmouth_ball()
else:
self.foul_shots(0)
self.dartmouth_ball()
else:
print("Shot is off the rim.")
if self.defense/6*random.random() > .5:
print(self.opponent + " controls the rebound.")
if self.defense == 6:
if random.random() > .75:
print("Ball stolen. Easy lay up for Dartmouth.")
self.add_points(1, 2)
self.opponent_ball()
else:
if random.random() > .5:
print("")
self.opponent_non_jumpshot()
else:
print("Pass back to " + self.opponent +
" guard.\n")
self.opponent_ball()
else:
if random.random() > .5:
self.opponent_non_jumpshot()
else:
print("Pass back to " + self.opponent +
" guard.\n")
self.opponent_ball()
else:
print("Dartmouth controls the rebound.\n")
self.dartmouth_ball()
else:
print("Shot is good.")
self.add_points(0, 2)
self.dartmouth_ball()
# simulates opponents lay up or set shot
def opponent_non_jumpshot(self):
if self.z1 > 3:
print("Set shot.")
else:
print("Lay up")
if 7/self.defense*random.random() > .413:
print("Shot is missed.")
if self.defense/6*random.random() > .5:
print(self.opponent + " controls the rebound.")
if self.defense == 6:
if random.random() > .75:
print("Ball stolen. Easy lay up for Dartmouth.")
self.add_points(1, 2)
self.opponent_ball()
else:
if random.random() > .5:
print("")
self.opponent_non_jumpshot()
else:
print("Pass back to " + self.opponent +
" guard.\n")
self.opponent_ball()
else:
if random.random() > .5:
print("")
self.opponent_non_jumpshot()
else:
print("Pass back to " + self.opponent + " guard\n")
self.opponent_ball()
else:
print("Dartmouth controls the rebound.\n")
self.dartmouth_ball()
else:
print("Shot is good.")
self.add_points(0, 2)
self.dartmouth_ball()
# simulates an opponents possesion
# #randomly picks jump shot or lay up / set shot.
def opponent_ball(self):
self.time += 1
if self.time == 50:
self.halftime()
self.z1 = 10/4*random.random()+1
if self.z1 > 2:
self.opponent_non_jumpshot()
else:
self.opponent_jumpshot()
new_game = Basketball()
+15 -2
View File
@@ -1,7 +1,20 @@
### Batnum
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=14
The game starts with an imaginary pile of objects, coins for example. You and your opponent (the computer) alternately remove objects from the pile. You specify in advance the minimum and maximum number of objects that can be taken on each turn. You also specify in advance how winning is defined:
1. To take the last object
2. To avoid taking the last object
You may also determine whether you or the computer go first.
The strategy of this game is based on modulo arithmetic. If the maximum number of objects a player may remove in a turn is M, then to gain a winning position a player at the end of his turn must leave a stack of 1 modulo (M+1) coins. If you dont understand this, play the game 23 Matches first, then BATNUM, and have fun!
BATNUM is a generalized version of a great number of manual remove-the-object games. The original computer version was written by one of the two originators of the BASIC language, John Kemeny of Dartmouth College.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=14)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=29)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+21 -1
View File
@@ -1,7 +1,27 @@
### Battle
BATTLE is based on the popular game Battleship which is primarily played to familiarize people with the location and designation of points on a coordinate plane.
BATTLE first randomly sets up the bad guys fleet disposition on a 6 by 6 matrix or grid. The fleet consists of six ships:
- Two destroyers (ships number 1 and 2) which are two units long
- Two cruisers (ships number 3 and 4) which are three units long
- Two aircraft carriers (ships number 5 and 6) which are four units long
The program then prints out this fleet disposition in a coded or disguised format (see the sample computer print-out). You then proceed to sink the various ships by typing in the coordinates (two digits. each from 1 to 6, separated by a comma) of the place where you want to drop a bomb, if youll excuse the expression. The computer gives the appropriate response (splash, hit, etc.) which you should record on a 6 by 6 matrix. You are thus building a representation of the actual fleet disposition which you will hopefully use to decode the coded fleet disposition printed out by the computer. Each time a ship is sunk, the computer prints out which ships have been sunk so far and also gives you a “SPLASH/HIT RATIO.”
The first thing you should learn is how to locate and designate positions on the matrix, and specifically the difference between “3,4” and “4,3.” Our method corresponds to the location of points on the coordinate plane rather than the location of numbers in a standard algebraic matrix: the first number gives the column counting from left to right and the second number gives the row counting from bottom to top.
The second thing you should learn about is the splash/hit ratio. “What is a ratio?” A good reply is “Its a fraction or quotient.” Specifically, the spash/hit ratio is the number of splashes divided by the number of hits. If you had 9 splashes and 15 hits, the ratio would be 9/15 or 3/5, both of which are correct. The computer would give this splash/hit ratio as .6.
The main objective and primary education benefit of BATTLE comes from attempting to decode the bas guys fleet disposition code. To do this, you must make a comparison between the coded matrix and the actual matrix which you construct as you play the game.
The original author of both the program and these descriptive notes is Ray Westergard of Lawrence Hall of Science, Berkeley, California.
---
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=15
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=15)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=30)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+1
View File
@@ -0,0 +1 @@
*~
+168
View File
@@ -0,0 +1,168 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
import java.util.function.Predicate;
import java.text.NumberFormat;
/* This class holds the game state and the game logic */
public class Battle {
/* parameters of the game */
private int seaSize;
private int[] sizes;
private int[] counts;
/* The game setup - the ships and the sea */
private ArrayList<Ship> ships;
private Sea sea;
/* game state counts */
private int[] losses; // how many of each type of ship have been sunk
private int hits; // how many hits the player has made
private int misses; // how many misses the player has made
// Names of ships of each size. The game as written has ships of size 3, 4 and 5 but
// can easily be modified. It makes no sense to have a ship of size zero though.
private static String NAMES_BY_SIZE[] = {
"error",
"size1",
"destroyer",
"cruiser",
"aircraft carrier",
"size5" };
// Entrypoint
public static void main(String args[]) {
Battle game = new Battle(6, // Sea is 6 x 6 tiles
new int[] { 2, 3, 4 }, // Ships are of sizes 2, 3, and 4
new int[] { 2, 2, 2 }); // there are two ships of each size
game.play();
}
public Battle(int scale, int[] shipSizes, int[] shipCounts) {
seaSize = scale;
sizes = shipSizes;
counts = shipCounts;
// validate parameters
if (seaSize < 4) throw new RuntimeException("Sea Size " + seaSize + " invalid, must be at least 4");
for (int sz : sizes) {
if ((sz < 1) || (sz > seaSize))
throw new RuntimeException("Ship has invalid size " + sz);
}
if (counts.length != sizes.length) {
throw new RuntimeException("Ship counts must match");
}
// Initialize game state
sea = new Sea(seaSize); // holds what ship if any occupies each tile
ships = new ArrayList<Ship>(); // positions and states of all the ships
losses = new int[counts.length]; // how many ships of each type have been sunk
// Build up the list of all the ships
int shipNumber = 1;
for (int type = 0; type < counts.length; ++type) {
for (int i = 0; i < counts[i]; ++i) {
ships.add(new Ship(shipNumber++, sizes[type]));
}
}
// When we put the ships in the sea, we put the biggest ones in first, or they might
// not fit
ArrayList<Ship> largestFirst = new ArrayList<>(ships);
Collections.sort(largestFirst, Comparator.comparingInt((Ship ship) -> ship.size()).reversed());
// place each ship into the sea
for (Ship ship : largestFirst) {
ship.placeRandom(sea);
}
}
public void play() {
System.out.println("The following code of the bad guys' fleet disposition\nhas been captured but not decoded:\n");
System.out.println(sea.encodedDump());
System.out.println("De-code it and use it if you can\nbut keep the de-coding method a secret.\n");
int lost = 0;
System.out.println("Start game");
Input input = new Input(seaSize);
try {
while (lost < ships.size()) { // the game continues while some ships remain unsunk
if (! input.readCoordinates()) { // ... unless there is no more input from the user
return;
}
// The computer thinks of the sea as a grid of rows, from top to bottom.
// However, the user will use X and Y coordinates, with Y going bottom to top
int row = seaSize - input.y();
int col = input.x() - 1;
if (sea.isEmpty(col, row)) {
++misses;
System.out.println("Splash! Try again.");
} else {
Ship ship = ships.get(sea.get(col, row) - 1);
if (ship.isSunk()) {
++misses;
System.out.println("There used to be a ship at that point, but you sunk it.");
System.out.println("Splash! Try again.");
} else if (ship.wasHit(col, row)) {
++misses;
System.out.println("You already put a hole in ship number " + ship.id());
System.out.println("Splash! Try again.");
} else {
ship.hit(col, row);
++hits;
System.out.println("A direct hit on ship number " + ship.id());
// If a ship was hit, we need to know whether it was sunk.
// If so, tell the player and update our counts
if (ship.isSunk()) {
++lost;
System.out.println("And you sunk it. Hurrah for the good guys.");
System.out.print("So far, the bad guys have lost ");
ArrayList<String> typeDescription = new ArrayList<>();
for (int i = 0 ; i < sizes.length; ++i) {
if (sizes[i] == ship.size()) {
++losses[i];
}
StringBuilder sb = new StringBuilder();
sb.append(losses[i]);
sb.append(" ");
sb.append(NAMES_BY_SIZE[sizes[i]]);
if (losses[i] != 1)
sb.append("s");
typeDescription.add(sb.toString());
}
System.out.println(String.join(", ", typeDescription));
double ratioNum = ((double)misses)/hits;
String ratio = NumberFormat.getInstance().format(ratioNum);
System.out.println("Your current splash/hit ratio is " + ratio);
if (lost == ships.size()) {
System.out.println("You have totally wiped out the bad guys' fleet");
System.out.println("With a final splash/hit ratio of " + ratio);
if (misses == 0) {
System.out.println("Congratulations - A direct hit every time.");
}
System.out.println("\n****************************\n");
}
}
}
}
}
}
catch (IOException e) {
// This should not happen running from console, but java requires us to check for it
System.err.println("System error.\n" + e);
}
}
}
+67
View File
@@ -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]; }
}
+60
View File
@@ -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;
}
}
+170
View File
@@ -0,0 +1,170 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
import java.util.function.Predicate;
/** A single ship, with its position and where it has been hit */
class Ship {
// These are the four directions that ships can be in
public static final int ORIENT_E=0; // goes East from starting position
public static final int ORIENT_SE=1; // goes SouthEast from starting position
public static final int ORIENT_S=2; // goes South from starting position
public static final int ORIENT_SW=3; // goes SouthWest from starting position
private int id; // ship number
private int size; // how many tiles it occupies
private boolean placed; // whether this ship is in the sea yet
private boolean sunk; // whether this ship has been sunk
private ArrayList<Boolean> hits; // which tiles of the ship have been hit
private int startX; // starting position coordinates
private int startY;
private int orientX; // x and y deltas from each tile occupied to the next
private int orientY;
public Ship(int i, int sz) {
id = i; size = sz;
sunk = false; placed = false;
hits = new ArrayList<>(Collections.nCopies(size, false));
}
/** @returns the ship number */
public int id() { return id; }
/** @returns the ship size */
public int size() { return size; }
/* record the ship as having been hit at the given coordinates */
public void hit(int x, int y) {
// need to work out how many tiles from the ship's starting position the hit is at
// that can be worked out from the difference between the starting X coord and this one
// unless the ship runs N-S, in which case use the Y coord instead
int offset;
if (orientX != 0) {
offset = (x - startX) / orientX;
} else {
offset = (y - startY) / orientY;
}
hits.set(offset, true);
// if every tile of the ship has been hit, the ship is sunk
sunk = hits.stream().allMatch(Predicate.isEqual(true));
}
public boolean isSunk() { return sunk; }
// whether the ship has already been hit at the given coordinates
public boolean wasHit(int x, int y) {
int offset;
if (orientX != 0) {
offset = (x - startX) / orientX;
} else {
offset = (y - startY) / orientY;
}
return hits.get(offset);
};
// Place the ship in the sea.
// choose a random starting position, and a random direction
// if that doesn't fit, keep picking different positions and directions
public void placeRandom(Sea s) {
Random random = new Random();
for (int tries = 0 ; tries < 1000 ; ++tries) {
int x = random.nextInt(s.size());
int y = random.nextInt(s.size());
int orient = random.nextInt(4);
if (place(s, x, y, orient)) return;
}
throw new RuntimeException("Could not place any more ships");
}
// Attempt to fit the ship into the sea, starting from a given position and
// in a given direction
// This is by far the most complicated part of the program.
// It will start at the position provided, and attempt to occupy tiles in the
// requested direction. If it does not fit, either because of the edge of the
// sea, or because of ships already in place, it will try to extend the ship
// in the opposite direction instead. If that is not possible, it fails.
public boolean place(Sea s, int x, int y, int orient) {
if (placed) {
throw new RuntimeException("Program error - placed ship " + id + " twice");
}
switch(orient) {
case ORIENT_E: // east is increasing X coordinate
orientX = 1; orientY = 0;
break;
case ORIENT_SE: // southeast is increasing X and Y
orientX = 1; orientY = 1;
break;
case ORIENT_S: // south is increasing Y
orientX = 0; orientY = 1;
break;
case ORIENT_SW: // southwest is increasing Y but decreasing X
orientX = -1; orientY = 1;
break;
default:
throw new RuntimeException("Invalid orientation " + orient);
}
if (!s.isEmpty(x, y)) return false; // starting position is occupied - placing fails
startX = x; startY = y;
int tilesPlaced = 1;
int nextX = startX;
int nextY = startY;
while (tilesPlaced < size) {
if (extendShip(s, nextX, nextY, nextX + orientX, nextY + orientY)) {
// It is clear to extend the ship forwards
tilesPlaced += 1;
nextX = nextX + orientX;
nextY = nextY + orientY;
} else {
int backX = startX - orientX;
int backY = startY - orientY;
if (extendShip(s, startX, startY, backX, backY)) {
// We can move the ship backwards, so it can be one tile longer
tilesPlaced +=1;
startX = backX;
startY = backY;
} else {
// Could not make it longer or move it backwards
return false;
}
}
}
// Mark in the sea which tiles this ship occupies
for (int i = 0; i < size; ++i) {
int sx = startX + i * orientX;
int sy = startY + i * orientY;
s.set(sx, sy, id);
}
placed = true;
return true;
}
// Check whether a ship which already occupies the "from" coordinates,
// can also occupy the "to" coordinates.
// They must be within the sea area, empty, and not cause the ship to cross
// over another ship
private boolean extendShip(Sea s, int fromX, int fromY, int toX, int toY) {
if (!s.isEmpty(toX, toY)) return false; // no space
if ((fromX == toX)||(fromY == toY)) return true; // horizontal or vertical
// we can extend the ship without colliding, but we are going diagonally
// and it should not be possible for two ships to cross each other on
// opposite diagonals.
// check the two tiles that would cross us here - if either is empty, we are OK
// if they both contain different ships, we are OK
// but if they both contain the same ship, we are crossing!
int corner1 = s.get(fromX, toY);
int corner2 = s.get(toX, fromY);
if ((corner1 == 0) || (corner1 != corner2)) return true;
return false;
}
}
+11 -2
View File
@@ -1,7 +1,16 @@
### Blackjack
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=18
This is a simulation of the card game of Blackjack or 21, Las Vegas style. This rather comprehensive version allows for up to seven players. On each hand a player may get another card (a hit), stand, split a hand in the event two identical cards were received or double down. Also, the dealer will ask for an insurance bet if he has an exposed ace.
Cards are automatically reshuffled as the 51st card is reached. For greater realism, you may wish to change this to the 41st card. Actually, fanatical purists will want to modify the program so it uses three decks of cards instead of just one.
This program originally surfaced at Digital Equipment Corp.; the author is unknown.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=18)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=33)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+11 -2
View File
@@ -1,7 +1,16 @@
### Bombardment
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=22
BOMBARDMENT is played on two, 5x5 grids or boards with 25 outpost locations numbered 1 to 25. Both you and the computer have four platoons of troops that can be located at any four outposts on your respective grids.
At the start of the game, you locate (or hide) your four platoons on your grid. The computer does the same on its grid. You then take turns firing missiles or bombs at each others outposts trying to destroy all four platoons. The one who finds all four opponents platoons first, wins.
This program was slightly modified from the original written by Martin Burdash of Parlin, New Jersey.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=22)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=37)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+183
View File
@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
namespace Bombardment
{
// <summary>
// Game of Bombardment
// Based on the Basic game of Bombardment here
// https://github.com/coding-horror/basic-computer-games/blob/main/11%20Bombardment/bombardment.bas
// Note: The idea was to create a version of the 1970's Basic game in C#, without introducing
// new features - no additional text, error checking, etc has been added.
// </summary>
internal class Bombardment
{
private static int MAX_GRID_SIZE = 25;
private static int MAX_PLATOONS = 4;
private static Random random = new Random();
private List<int> computerPositions = new List<int>();
private List<int> playerPositions = new List<int>();
private List<int> computerGuesses = new List<int>();
private void PrintStartingMessage()
{
Console.WriteLine("{0}BOMBARDMENT", new string(' ', 33));
Console.WriteLine("{0}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", new string(' ', 15));
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("YOU ARE ON A BATTLEFIELD WITH 4 PLATOONS AND YOU");
Console.WriteLine("HAVE 25 OUTPOSTS AVAILABLE WHERE THEY MAY BE PLACED.");
Console.WriteLine("YOU CAN ONLY PLACE ONE PLATOON AT ANY ONE OUTPOST.");
Console.WriteLine("THE COMPUTER DOES THE SAME WITH ITS FOUR PLATOONS.");
Console.WriteLine();
Console.WriteLine("THE OBJECT OF THE GAME IS TO FIRE MISSLES AT THE");
Console.WriteLine("OUTPOSTS OF THE COMPUTER. IT WILL DO THE SAME TO YOU.");
Console.WriteLine("THE ONE WHO DESTROYS ALL FOUR OF THE ENEMY'S PLATOONS");
Console.WriteLine("FIRST IS THE WINNER.");
Console.WriteLine();
Console.WriteLine("GOOD LUCK... AND TELL US WHERE YOU WANT THE BODIES SENT!");
Console.WriteLine();
Console.WriteLine("TEAR OFF MATRIX AND USE IT TO CHECK OFF THE NUMBERS.");
// As an alternative to repeating the call to WriteLine(),
// we can print the new line character five times.
Console.Write(new string('\n', 5));
// Print a sample board (presumably the game was originally designed to be
// physically printed on paper while played).
for (var i = 1; i <= 25; i += 5)
{
// The token replacement can be padded by using the format {tokenPosition, padding}
// Negative values for the padding cause the output to be left-aligned.
Console.WriteLine("{0,-3}{1,-3}{2,-3}{3,-3}{4,-3}", i, i + 1, i + 2, i + 3, i + 4);
}
Console.WriteLine("\n");
}
// Generate 5 random positions for the computer's platoons.
private void PlaceComputerPlatoons()
{
do
{
var nextPosition = random.Next(1, MAX_GRID_SIZE);
if (!computerPositions.Contains(nextPosition))
{
computerPositions.Add(nextPosition);
}
} while (computerPositions.Count < MAX_PLATOONS);
}
private void StoreHumanPositions()
{
Console.WriteLine("WHAT ARE YOUR FOUR POSITIONS");
// The original game assumed that the input would be five comma-separated values, all on one line.
// For example: 12,22,1,4,17
var input = Console.ReadLine();
var playerPositionsAsStrings = input.Split(",");
foreach (var playerPosition in playerPositionsAsStrings) {
playerPositions.Add(int.Parse(playerPosition));
}
}
private void HumanTurn()
{
Console.WriteLine("WHERE DO YOU WISH TO FIRE YOUR MISSLE");
var input = Console.ReadLine();
var humanGuess = int.Parse(input);
if(computerPositions.Contains(humanGuess))
{
Console.WriteLine("YOU GOT ONE OF MY OUTPOSTS!");
computerPositions.Remove(humanGuess);
switch(computerPositions.Count)
{
case 3:
Console.WriteLine("ONE DOWN, THREE TO GO.");
break;
case 2:
Console.WriteLine("TWO DOWN, TWO TO GO.");
break;
case 1:
Console.WriteLine("THREE DOWN, ONE TO GO.");
break;
case 0:
Console.WriteLine("YOU GOT ME, I'M GOING FAST.");
Console.WriteLine("BUT I'LL GET YOU WHEN MY TRANSISTO&S RECUP%RA*E!");
break;
}
}
else
{
Console.WriteLine("HA, HA YOU MISSED. MY TURN NOW:");
}
}
private int GenerateComputerGuess()
{
int computerGuess;
do
{
computerGuess = random.Next(1, 25);
}
while(computerGuesses.Contains(computerGuess));
computerGuesses.Add(computerGuess);
return computerGuess;
}
private void ComputerTurn()
{
var computerGuess = GenerateComputerGuess();
if (playerPositions.Contains(computerGuess))
{
Console.WriteLine("I GOT YOU. IT WON'T BE LONG NOW. POST {0} WAS HIT.", computerGuess);
playerPositions.Remove(computerGuess);
switch(playerPositions.Count)
{
case 3:
Console.WriteLine("YOU HAVE ONLY THREE OUTPOSTS LEFT.");
break;
case 2:
Console.WriteLine("YOU HAVE ONLY TWO OUTPOSTS LEFT.");
break;
case 1:
Console.WriteLine("YOU HAVE ONLY ONE OUTPOST LEFT.");
break;
case 0:
Console.WriteLine("YOU'RE DEAD. YOUR LAST OUTPOST WAS AT {0}. HA, HA, HA.", computerGuess);
Console.WriteLine("BETTER LUCK NEXT TIME.");
break;
}
}
else
{
Console.WriteLine("I MISSED YOU, YOU DIRTY RAT. I PICKED {0}. YOUR TURN:", computerGuess);
}
}
public void Play()
{
PrintStartingMessage();
PlaceComputerPlatoons();
StoreHumanPositions();
while (playerPositions.Count > 0 && computerPositions.Count > 0)
{
HumanTurn();
if (computerPositions.Count > 0)
{
ComputerTurn();
}
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>
+13
View File
@@ -0,0 +1,13 @@
using System;
namespace Bombardment
{
class Program
{
static void Main(string[] args)
{
var bombardment = new Bombardment();
bombardment.Play();
}
}
}
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/perl
use strict;
use warnings;
#GLOBAL
my %player_bases;
my %computer_bases;
my %player_choices;
my %computer_choices;
&main;
sub main {
&print_intro;
&display_field;
&populate_computer_bases;
&populate_player_bases;
&game_play;
}
sub game_play {
until (keys %computer_bases == 0 || keys %player_bases == 0) {
&player_turn;
if (keys %computer_bases == 0) {
exit;
}
&computer_turn;
}
exit;
}
sub computer_turn {
# There is logic in here to ensure that the computer doesn't try to pick a target it has already picked
my $valid_choice = 0;
until ($valid_choice == 1) {
my $target = int(rand(25)+1);
if (exists $computer_choices{$target}) {
$valid_choice = 0;
}
else {
$valid_choice = 1;
$computer_choices{$target}=1;
if (exists $player_bases{$target}) {
delete($player_bases{$target});
my $size = keys %player_bases;
if ($size > 0) {
print "I GOT YOU. IT WON'T BE LONG NOW. POST $target WAS HIT.\n";
if ($size == 3) { print "YOU HAVE ONLY THREE OUTPOSTS LEFT.\n"};
if ($size == 2) { print "YOU HAVE ONLY TWO OUTPOSTS LEFT.\n"};
if ($size == 1) { print "YOU HAVE ONLY ONE OUTPOSTS LEFT.\n"};
}
else {
print "YOU'RE DEAD. YOUR LAST OUTPOST WAS AT $target. HA, HA, HA.\nBETTER LUCK NEXT TIME\n";
}
}
else {
print "I MISSED YOU, YOU DIRTY RAT. I PICKED $target. YOUR TURN:\n";
}
}
}
}
sub player_turn {
print "WHERE DO YOU WISH TO FIRE YOUR MISSILE\n";
chomp(my $target=<STDIN>);
if (exists $computer_bases{$target}) {
print "YOU GOT ONE OF MY OUTPOSTS!\n";
delete($computer_bases{$target});
my $size = keys %computer_bases;
if ($size == 3) { print "ONE DOWN, THREE TO GO.\n"};
if ($size == 2) { print "TWO DOWN, TWO TO GO.\n"};
if ($size == 1) { print "THREE DOWN, ONE TO GO.\n"};
if ($size == 0) { print "YOU GOT ME, I'M GOING FAST. BUT I'LL GET YOU WHEN\nMY TRANSISTO&S RECUP%RA*E!\n"};
}
else {
print "HA, HA YOU MISSED. MY TURN NOW:\n";
}
}
sub populate_player_bases {
print "WHAT ARE YOUR FOUR POSITIONS\n";
my $positions=<STDIN>;
chomp($positions);
my @positions = split/ /,$positions;
foreach my $base (@positions) {
$player_bases{$base}=0;
}
}
sub display_field {
for my $num (1..25) {
if (length($num) < 2) {
$num = " $num";
}
print "$num ";
if ($num % 5 == 0) {
print "\n";
}
}
}
sub populate_computer_bases {
my $size = 0;
until ($size == 4) {
my $base = int(rand(25)+1);
$computer_bases{$base}=0;
$size = keys %computer_bases;
}
}
sub print_intro {
print " " x 33, "BOMBARDMENT\n";
print " " x 15, " CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
print "\n\n";
print "YOU ARE ON A BATTLEFIELD WITH 4 PLATOONS AND YOU\n";
print "HAVE 25 OUTPOSTS AVAILABLE WHERE THEY MAY BE PLACED.\n";
print "YOU CAN ONLY PLACE ONE PLATOON AT ANY ONE OUTPOST.\n";
print "THE COMPUTER DOES THE SAME WITH ITS FOUR PLATOONS.\n\n";
print "THE OBJECT OF THE GAME IS TO FIRE MISSLES AT THE\n";
print "OUTPOSTS OF THE COMPUTER. IT WILL DO THE SAME TO YOU.\n";
print "THE ONE WHO DESTROYS ALL FOUR OF THE ENEMY'S PLATOONS\n";
print "FIRST IS THE WINNER.\n\n";
print "GOOD LUCK... AND TELL US WHERE YOU WANT THE BODIES SENT!\n\n";
print "TEAR OFF MATRIX AND USE IT TO CHECK OFF THE NUMBERS.\n";
print "\n\n\n\n";
}
+9 -2
View File
@@ -1,7 +1,14 @@
### Bombs Away
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=24
In this program, you fly a World War II bomber for one of the four protagonists of the war. You then pick your target or the type of plane you are flying. Depending on your flying experience and the quality of enemy defenders, you then may accomplish your mission, get shot down, or make it back through enemy fire. In any case, you get a chance to fly again.
David Ahl modified the original program which was created by David Sherman while a student at Curtis Jr. High School, Sudbury, Massachusetts.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=24)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=39)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+31
View File
@@ -0,0 +1,31 @@
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}") = "BombsAwayConsole", "BombsAwayConsole\BombsAwayConsole.csproj", "{D80015FA-423C-4A16-AA2B-16669245AD59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BombsAwayGame", "BombsAwayGame\BombsAwayGame.csproj", "{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D80015FA-423C-4A16-AA2B-16669245AD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D80015FA-423C-4A16-AA2B-16669245AD59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D80015FA-423C-4A16-AA2B-16669245AD59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D80015FA-423C-4A16-AA2B-16669245AD59}.Release|Any CPU.Build.0 = Release|Any CPU
{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F57AEC18-FEE9-4F08-9F20-DFC56EFE6BFC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {39B2ECFB-037D-4335-BBD2-64892E953DD4}
EndGlobalSection
EndGlobal
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BombsAwayGame\BombsAwayGame.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,134 @@
namespace BombsAwayConsole;
/// <summary>
/// Implements <see cref="BombsAwayGame.IUserInterface"/> by writing to and reading from <see cref="Console"/>.
/// </summary>
internal class ConsoleUserInterface : BombsAwayGame.IUserInterface
{
/// <summary>
/// Write message to console.
/// </summary>
/// <param name="message">Message to display.</param>
public void Output(string message)
{
Console.WriteLine(message);
}
/// <summary>
/// Write choices with affixed indexes, allowing the user to choose by index.
/// </summary>
/// <param name="message">Message to display.</param>
/// <param name="choices">Choices to display.</param>
/// <returns>Choice that user picked.</returns>
public int Choose(string message, IList<string> choices)
{
IEnumerable<string> choicesWithIndexes = choices.Select((choice, index) => $"{choice}({index + 1})");
string choiceText = string.Join(", ", choicesWithIndexes);
Output($"{message} -- {choiceText}");
ISet<ConsoleKey> allowedKeys = ConsoleKeysFromList(choices);
ConsoleKey? choice;
do
{
choice = ReadChoice(allowedKeys);
if (choice is null)
{
Output("TRY AGAIN...");
}
}
while (choice is null);
return ListIndexFromConsoleKey(choice.Value);
}
/// <summary>
/// Convert the given list to its <see cref="ConsoleKey"/> equivalents. This generates keys that map
/// the first element to <see cref="ConsoleKey.D1"/>, the second element to <see cref="ConsoleKey.D2"/>,
/// and so on, up to the last element of the list.
/// </summary>
/// <param name="list">List whose elements will be converted to <see cref="ConsoleKey"/> equivalents.</param>
/// <returns><see cref="ConsoleKey"/> equivalents from <paramref name="list"/>.</returns>
private ISet<ConsoleKey> ConsoleKeysFromList(IList<string> list)
{
IEnumerable<int> indexes = Enumerable.Range((int)ConsoleKey.D1, list.Count);
return new HashSet<ConsoleKey>(indexes.Cast<ConsoleKey>());
}
/// <summary>
/// Convert the given console key to its list index equivalent. This assumes the key was generated from
/// <see cref="ConsoleKeysFromList(IList{string})"/>
/// </summary>
/// <param name="key">Key to convert to its list index equivalent.</param>
/// <returns>List index equivalent of key.</returns>
private int ListIndexFromConsoleKey(ConsoleKey key)
{
return key - ConsoleKey.D1;
}
/// <summary>
/// Read a key from the console and return it if it is in the given allowed keys.
/// </summary>
/// <param name="allowedKeys">Allowed keys.</param>
/// <returns>Key read from <see cref="Console"/>, if it is in <paramref name="allowedKeys"/>; null otherwise./></returns>
private ConsoleKey? ReadChoice(ISet<ConsoleKey> allowedKeys)
{
ConsoleKeyInfo keyInfo = ReadKey();
return allowedKeys.Contains(keyInfo.Key) ? keyInfo.Key : null;
}
/// <summary>
/// Read key from <see cref="Console"/>.
/// </summary>
/// <returns>Key read from <see cref="Console"/>.</returns>
private ConsoleKeyInfo ReadKey()
{
ConsoleKeyInfo result = Console.ReadKey(intercept: false);
// Write a blank line to the console so the displayed key is on its own line.
Console.WriteLine();
return result;
}
/// <summary>
/// Allow user to choose 'Y' or 'N' from <see cref="Console"/>.
/// </summary>
/// <param name="message">Message to display.</param>
/// <returns>True if user chose 'Y', false if user chose 'N'.</returns>
public bool ChooseYesOrNo(string message)
{
Output(message);
ConsoleKey? choice;
do
{
choice = ReadChoice(new HashSet<ConsoleKey>(new[] { ConsoleKey.Y, ConsoleKey.N }));
if (choice is null)
{
Output("ENTER Y OR N");
}
}
while (choice is null);
return choice.Value == ConsoleKey.Y;
}
/// <summary>
/// Get integer by reading a line from <see cref="Console"/>.
/// </summary>
/// <returns>Integer read from <see cref="Console"/>.</returns>
public int InputInteger()
{
bool resultIsValid;
int result;
do
{
string? integerText = Console.ReadLine();
resultIsValid = int.TryParse(integerText, out result);
if (!resultIsValid)
{
Output("PLEASE ENTER A NUMBER");
}
}
while (!resultIsValid);
return result;
}
}
@@ -0,0 +1,26 @@
using BombsAwayConsole;
using BombsAwayGame;
/// Create and play <see cref="Game"/>s using a <see cref="ConsoleUserInterface"/>.
PlayGameWhileUserWantsTo(new ConsoleUserInterface());
void PlayGameWhileUserWantsTo(ConsoleUserInterface ui)
{
do
{
new Game(ui).Play();
}
while (UserWantsToPlayAgain(ui));
}
bool UserWantsToPlayAgain(IUserInterface ui)
{
bool result = ui.ChooseYesOrNo("ANOTHER MISSION (Y OR N)?");
if (!result)
{
Console.WriteLine("CHICKEN !!!");
}
return result;
}
@@ -0,0 +1,22 @@
namespace BombsAwayGame;
/// <summary>
/// Allies protagonist. Can fly missions in a Liberator, B-29, B-17, or Lancaster.
/// </summary>
internal class AlliesSide : MissionSide
{
public AlliesSide(IUserInterface ui)
: base(ui)
{
}
protected override string ChooseMissionMessage => "AIRCRAFT";
protected override IList<Mission> AllMissions => new Mission[]
{
new("LIBERATOR", "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI."),
new("B-29", "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA."),
new("B-17", "YOU'RE CHASING THE BISMARK IN THE NORTH SEA."),
new("LANCASTER", "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.")
};
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,8 @@
namespace BombsAwayGame;
/// <summary>
/// Represents enemy artillery.
/// </summary>
/// <param name="Name">Name of artillery type.</param>
/// <param name="Accuracy">Accuracy of artillery. This is the `T` variable in the original BASIC.</param>
internal record class EnemyArtillery(string Name, int Accuracy);
@@ -0,0 +1,58 @@
namespace BombsAwayGame;
/// <summary>
/// Plays the Bombs Away game using a supplied <see cref="IUserInterface"/>.
/// </summary>
public class Game
{
private readonly IUserInterface _ui;
/// <summary>
/// Create game instance using the given UI.
/// </summary>
/// <param name="ui">UI to use for game.</param>
public Game(IUserInterface ui)
{
_ui = ui;
}
/// <summary>
/// Play game. Choose a side and play the side's logic.
/// </summary>
public void Play()
{
_ui.Output("YOU ARE A PILOT IN A WORLD WAR II BOMBER.");
Side side = ChooseSide();
side.Play();
}
/// <summary>
/// Represents a <see cref="Side"/>.
/// </summary>
/// <param name="Name">Name of side.</param>
/// <param name="CreateSide">Create instance of side that this descriptor represents.</param>
private record class SideDescriptor(string Name, Func<Side> CreateSide);
/// <summary>
/// Choose side and return a new instance of that side.
/// </summary>
/// <returns>New instance of side that was chosen.</returns>
private Side ChooseSide()
{
SideDescriptor[] sides = AllSideDescriptors;
string[] sideNames = sides.Select(a => a.Name).ToArray();
int index = _ui.Choose("WHAT SIDE", sideNames);
return sides[index].CreateSide();
}
/// <summary>
/// All side descriptors.
/// </summary>
private SideDescriptor[] AllSideDescriptors => new SideDescriptor[]
{
new("ITALY", () => new ItalySide(_ui)),
new("ALLIES", () => new AlliesSide(_ui)),
new("JAPAN", () => new JapanSide(_ui)),
new("GERMANY", () => new GermanySide(_ui)),
};
}
@@ -0,0 +1,21 @@
namespace BombsAwayGame;
/// <summary>
/// Germany protagonist. Can fly missions to Russia, England, and France.
/// </summary>
internal class GermanySide : MissionSide
{
public GermanySide(IUserInterface ui)
: base(ui)
{
}
protected override string ChooseMissionMessage => "A NAZI, EH? OH WELL. ARE YOU GOING FOR";
protected override IList<Mission> AllMissions => new Mission[]
{
new("RUSSIA", "YOU'RE NEARING STALINGRAD."),
new("ENGLAND", "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR."),
new("FRANCE", "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.")
};
}
@@ -0,0 +1,38 @@
namespace BombsAwayGame;
/// <summary>
/// Represents an interface for supplying data to the game.
/// </summary>
/// <remarks>
/// Abstracting the UI allows us to concentrate its concerns in one part of our code and to change UI behavior
/// without creating any risk of changing the game logic. It also allows us to supply an automated UI for tests.
/// </remarks>
public interface IUserInterface
{
/// <summary>
/// Display the given message.
/// </summary>
/// <param name="message">Message to display.</param>
void Output(string message);
/// <summary>
/// Choose an item from the given choices.
/// </summary>
/// <param name="message">Message to display.</param>
/// <param name="choices">Choices to choose from.</param>
/// <returns>Index of choice in <paramref name="choices"/> that user chose.</returns>
int Choose(string message, IList<string> choices);
/// <summary>
/// Allow user to choose Yes or No.
/// </summary>
/// <param name="message">Message to display.</param>
/// <returns>True if user chose Yes, false if user chose No.</returns>
bool ChooseYesOrNo(string message);
/// <summary>
/// Get integer from user.
/// </summary>
/// <returns>Integer supplied by user.</returns>
int InputInteger();
}
@@ -0,0 +1,21 @@
namespace BombsAwayGame;
/// <summary>
/// Italy protagonist. Can fly missions to Albania, Greece, and North Africa.
/// </summary>
internal class ItalySide : MissionSide
{
public ItalySide(IUserInterface ui)
: base(ui)
{
}
protected override string ChooseMissionMessage => "YOUR TARGET";
protected override IList<Mission> AllMissions => new Mission[]
{
new("ALBANIA", "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE."),
new("GREECE", "BE CAREFUL!!!"),
new("NORTH AFRICA", "YOU'RE GOING FOR THE OIL, EH?")
};
}
@@ -0,0 +1,38 @@
namespace BombsAwayGame;
/// <summary>
/// Japan protagonist. Flies a kamikaze mission, which has a different logic from <see cref="MissionSide"/>s.
/// </summary>
internal class JapanSide : Side
{
public JapanSide(IUserInterface ui)
: base(ui)
{
}
/// <summary>
/// Perform a kamikaze mission. If first kamikaze mission, it will succeed 65% of the time. If it's not
/// first kamikaze mission, perform an enemy counterattack.
/// </summary>
public override void Play()
{
UI.Output("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.");
bool isFirstMission = UI.ChooseYesOrNo("YOUR FIRST KAMIKAZE MISSION(Y OR N)?");
if (!isFirstMission)
{
// LINE 207 of original BASIC: hitRatePercent is initialized to 0,
// but R, the type of artillery, is not initialized at all. Setting
// R = 1, which is to say EnemyArtillery = Guns, gives the same result.
EnemyCounterattack(Guns, hitRatePercent: 0);
}
else if (RandomFrac() > 0.65)
{
MissionSucceeded();
}
else
{
MissionFailed();
}
}
}
@@ -0,0 +1,8 @@
namespace BombsAwayGame;
/// <summary>
/// Represents a mission that can be flown by a <see cref="MissionSide"/>.
/// </summary>
/// <param name="Name">Name of mission.</param>
/// <param name="Description">Description of mission.</param>
internal record class Mission(string Name, string Description);
@@ -0,0 +1,208 @@
namespace BombsAwayGame;
/// <summary>
/// Represents a protagonist that chooses a standard (non-kamikaze) mission.
/// </summary>
internal abstract class MissionSide : Side
{
/// <summary>
/// Create instance using the given UI.
/// </summary>
/// <param name="ui">UI to use.</param>
public MissionSide(IUserInterface ui)
: base(ui)
{
}
/// <summary>
/// Reasonable upper bound for missions flown previously.
/// </summary>
private const int MaxMissionCount = 160;
/// <summary>
/// Choose a mission and attempt it. If attempt fails, perform an enemy counterattack.
/// </summary>
public override void Play()
{
Mission mission = ChooseMission();
UI.Output(mission.Description);
int missionCount = MissionCountFromUI();
CommentOnMissionCount(missionCount);
AttemptMission(missionCount);
}
/// <summary>
/// Choose a mission.
/// </summary>
/// <returns>Mission chosen.</returns>
private Mission ChooseMission()
{
IList<Mission> missions = AllMissions;
string[] missionNames = missions.Select(a => a.Name).ToArray();
int index = UI.Choose(ChooseMissionMessage, missionNames);
return missions[index];
}
/// <summary>
/// Message to display when choosing a mission.
/// </summary>
protected abstract string ChooseMissionMessage { get; }
/// <summary>
/// All aviailable missions to choose from.
/// </summary>
protected abstract IList<Mission> AllMissions { get; }
/// <summary>
/// Get mission count from UI. If mission count exceeds a reasonable maximum, ask UI again.
/// </summary>
/// <returns>Mission count from UI.</returns>
private int MissionCountFromUI()
{
const string HowManyMissions = "HOW MANY MISSIONS HAVE YOU FLOWN?";
string inputMessage = HowManyMissions;
bool resultIsValid;
int result;
do
{
UI.Output(inputMessage);
result = UI.InputInteger();
if (result < 0)
{
UI.Output($"NUMBER OF MISSIONS CAN'T BE NEGATIVE.");
resultIsValid = false;
}
else if (result > MaxMissionCount)
{
resultIsValid = false;
UI.Output($"MISSIONS, NOT MILES...{MaxMissionCount} MISSIONS IS HIGH EVEN FOR OLD-TIMERS.");
inputMessage = "NOW THEN, " + HowManyMissions;
}
else
{
resultIsValid = true;
}
}
while (!resultIsValid);
return result;
}
/// <summary>
/// Display a message about the given mission count, if it is unusually high or low.
/// </summary>
/// <param name="missionCount">Mission count to comment on.</param>
private void CommentOnMissionCount(int missionCount)
{
if (missionCount >= 100)
{
UI.Output("THAT'S PUSHING THE ODDS!");
}
else if (missionCount < 25)
{
UI.Output("FRESH OUT OF TRAINING, EH?");
}
}
/// <summary>
/// Attempt mission.
/// </summary>
/// <param name="missionCount">Number of missions previously flown. Higher mission counts will yield a higher probability of success.</param>
private void AttemptMission(int missionCount)
{
if (missionCount < RandomInteger(0, MaxMissionCount))
{
MissedTarget();
}
else
{
MissionSucceeded();
}
}
/// <summary>
/// Display message indicating that target was missed. Choose enemy artillery and perform a counterattack.
/// </summary>
private void MissedTarget()
{
UI.Output("MISSED TARGET BY " + (2 + RandomInteger(0, 30)) + " MILES!");
UI.Output("NOW YOU'RE REALLY IN FOR IT !!");
// Choose enemy and counterattack.
EnemyArtillery enemyArtillery = ChooseEnemyArtillery();
if (enemyArtillery == Missiles)
{
EnemyCounterattack(enemyArtillery, hitRatePercent: 0);
}
else
{
int hitRatePercent = EnemyHitRatePercentFromUI();
if (hitRatePercent < MinEnemyHitRatePercent)
{
UI.Output("YOU LIE, BUT YOU'LL PAY...");
MissionFailed();
}
else
{
EnemyCounterattack(enemyArtillery, hitRatePercent);
}
}
}
/// <summary>
/// Choose enemy artillery from UI.
/// </summary>
/// <returns>Artillery chosen.</returns>
private EnemyArtillery ChooseEnemyArtillery()
{
EnemyArtillery[] artilleries = new EnemyArtillery[] { Guns, Missiles, Both };
string[] artilleryNames = artilleries.Select(a => a.Name).ToArray();
int index = UI.Choose("DOES THE ENEMY HAVE", artilleryNames);
return artilleries[index];
}
/// <summary>
/// Minimum allowed hit rate percent.
/// </summary>
private const int MinEnemyHitRatePercent = 10;
/// <summary>
/// Maximum allowed hit rate percent.
/// </summary>
private const int MaxEnemyHitRatePercent = 50;
/// <summary>
/// Get the enemy hit rate percent from UI. Value must be between zero and <see cref="MaxEnemyHitRatePercent"/>.
/// If value is less than <see cref="MinEnemyHitRatePercent"/>, mission fails automatically because the user is
/// assumed to be untruthful.
/// </summary>
/// <returns>Enemy hit rate percent from UI.</returns>
private int EnemyHitRatePercentFromUI()
{
UI.Output($"WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS ({MinEnemyHitRatePercent} TO {MaxEnemyHitRatePercent})");
bool resultIsValid;
int result;
do
{
result = UI.InputInteger();
// Let them enter a number below the stated minimum, as they will be caught and punished.
if (0 <= result && result <= MaxEnemyHitRatePercent)
{
resultIsValid = true;
}
else
{
resultIsValid = false;
UI.Output($"NUMBER MUST BE FROM {MinEnemyHitRatePercent} TO {MaxEnemyHitRatePercent}");
}
}
while (!resultIsValid);
return result;
}
}
@@ -0,0 +1,97 @@
namespace BombsAwayGame;
/// <summary>
/// Represents a protagonist in the game.
/// </summary>
internal abstract class Side
{
/// <summary>
/// Create instance using the given UI.
/// </summary>
/// <param name="ui">UI to use.</param>
public Side(IUserInterface ui)
{
UI = ui;
}
/// <summary>
/// Play this side.
/// </summary>
public abstract void Play();
/// <summary>
/// User interface supplied to ctor.
/// </summary>
protected IUserInterface UI { get; }
/// <summary>
/// Random-number generator for this play-through.
/// </summary>
private readonly Random _random = new();
/// <summary>
/// Gets a random floating-point number greater than or equal to zero, and less than one.
/// </summary>
/// <returns>Random floating-point number greater than or equal to zero, and less than one.</returns>
protected double RandomFrac() => _random.NextDouble();
/// <summary>
/// Gets a random integer in a range.
/// </summary>
/// <param name="minValue">The inclusive lower bound of the number returned.</param>
/// <param name="maxValue">The exclusive upper bound of the number returned.</param>
/// <returns>Random integer in a range.</returns>
protected int RandomInteger(int minValue, int maxValue) => _random.Next(minValue: minValue, maxValue: maxValue);
/// <summary>
/// Display messages indicating the mission succeeded.
/// </summary>
protected void MissionSucceeded()
{
UI.Output("DIRECT HIT!!!! " + RandomInteger(0, 100) + " KILLED.");
UI.Output("MISSION SUCCESSFUL.");
}
/// <summary>
/// Gets the Guns type of enemy artillery.
/// </summary>
protected EnemyArtillery Guns { get; } = new("GUNS", 0);
/// <summary>
/// Gets the Missiles type of enemy artillery.
/// </summary>
protected EnemyArtillery Missiles { get; } = new("MISSILES", 35);
/// <summary>
/// Gets the Both Guns and Missiles type of enemy artillery.
/// </summary>
protected EnemyArtillery Both { get; } = new("BOTH", 35);
/// <summary>
/// Perform enemy counterattack using the given artillery and hit rate percent.
/// </summary>
/// <param name="artillery">Enemy artillery to use.</param>
/// <param name="hitRatePercent">Hit rate percent for enemy.</param>
protected void EnemyCounterattack(EnemyArtillery artillery, int hitRatePercent)
{
if (hitRatePercent + artillery.Accuracy > RandomInteger(0, 100))
{
MissionFailed();
}
else
{
UI.Output("YOU MADE IT THROUGH TREMENDOUS FLAK!!");
}
}
/// <summary>
/// Display messages indicating the mission failed.
/// </summary>
protected void MissionFailed()
{
UI.Output("* * * * BOOM * * * *");
UI.Output("YOU HAVE BEEN SHOT DOWN.....");
UI.Output("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR");
UI.Output("LAST TRIBUTE...");
}
}
+35 -18
View File
@@ -4,10 +4,16 @@ import java.util.Scanner;
* Game of Bombs Away
*
* Based on the Basic game of Bombs Away here
* https://github.com/coding-horror/basic-computer-games/blob/main/12%20Bombs%20Away/bombsaway.bas
* https://github.com/coding-horror/basic-computer-games/blob/main/12_Bombs_Away/bombsaway.bas
*
* Note: The idea was to create a version of the 1970's Basic game in Java, without adding new features.
* Obvious bugs where found have been fixed, but the playability and overlook and feel
* of the game have been faithfully reproduced.
*
* Modern Java coding conventions have been employed and JDK 11 used for maximum compatibility.
*
* Java port by https://github.com/journich
*
* Note: The idea was to create a version of the 1970's Basic game in Java, without introducing
* new features - no additional text, error checking, etc has been added.
*/
public class BombsAway {
@@ -116,8 +122,9 @@ public class BombsAway {
private int missions;
private int chanceToHit;
private int chanceToBeHit;
private int percentageHitRateOfGunners;
private boolean liar;
public BombsAway() {
@@ -139,8 +146,9 @@ public class BombsAway {
// Show an introduction the first time the game is played.
case START:
intro();
chanceToHit = 0;
chanceToBeHit = 0;
percentageHitRateOfGunners = 0;
liar = false;
gameState = GAME_STATE.CHOOSE_SIDE;
break;
@@ -267,39 +275,48 @@ public class BombsAway {
break;
case CHOOSE_ENEMY_DEFENCES:
boolean bothWeapons = true;
percentageHitRateOfGunners = 0;
ENEMY_DEFENCES enemyDefences = getEnemyDefences("DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3) ? ");
if(enemyDefences == null) {
System.out.println("TRY AGAIN...");
} else {
chanceToBeHit = 35;
switch(enemyDefences) {
case MISSILES:
case GUNS:
bothWeapons = false;
// MISSILES... An extra 35 but cannot specify percentage hit rate for gunners
break;
// fall through on purpose to BOTH since its pretty much identical code other than the chance to hit
// increasing if both weapons are part of the defence.
case GUNS:
// GUNS... No extra 35 but can specify percentage hit rate for gunners
chanceToBeHit = 0;
// fall through (no break) on purpose because remaining code is applicable
// for both GUNS and BOTH options.
case BOTH:
// BOTH... An extra 35 and percentage hit rate for gunners can be specified.
percentageHitRateOfGunners = getNumberFromKeyboard("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ");
if(percentageHitRateOfGunners < 10) {
System.out.println("YOU LIE, BUT YOU'LL PAY...");
}
if(bothWeapons) {
chanceToHit = 35;
liar = true;
}
break;
}
}
gameState = GAME_STATE.PROCESS_FLAK;
// If player didn't lie when entering percentage hit rate of gunners continue with game
// Otherwise shoot down the player.
if(!liar) {
gameState = GAME_STATE.PROCESS_FLAK;
} else {
gameState = GAME_STATE.SHOT_DOWN;
}
break;
// Determine if the players airplan makes it through the Flak.
// Determine if the player's airplane makes it through the Flak.
case PROCESS_FLAK:
double calc = (CHANCE_OF_BEING_SHOT_DOWN_BASE * randomNumber(1));
if ((chanceToHit + percentageHitRateOfGunners) > calc) {
if ((chanceToBeHit + percentageHitRateOfGunners) > calc) {
gameState = GAME_STATE.SHOT_DOWN;
} else {
gameState = GAME_STATE.MADE_IT_THROUGH_FLAK;
@@ -462,7 +479,7 @@ public class BombsAway {
/**
* Check whether a string equals one of a variable number of values
* Useful to check for Y or YES for example
* Comparison is case insensitive.
* Comparison is case-insensitive.
*
* @param text source string
* @param values a range of values to compare against the source string
+2
View File
@@ -45,6 +45,8 @@ function tab(space)
// Main program
async function main()
{
s = 0;
t = 0;
while (1) {
print("YOU ARE A PILOT IN A WORLD WAR II BOMBER.\n");
while (1) {
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
exit main(@ARGV);
sub main {
$|++;
my $mission = 'y';
# first-level choices will allow us to select the "right" callback
# function to start each mission
my @choices = (\&italy, \&allies, \&japan, \&germany);
# to support being case-insensitive "the right way" we apply the fc()
# function (i.e. "fold case"). This is slightly overkill in this case
# but it's better to stick to good habits.
while (fc($mission // 'n') eq fc('y')) {
say 'YOU ARE A PILOT IN A WORLD WAR II BOMBER.';
my $side = choose(
'WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4)', 4);
# arrays start from 0 in Perl, so our starting-from-1 side value
# has to be offset by 1.
$choices[$side - 1]->();
$mission = get_input("\n\n\nANOTHER MISSION (Y OR N)");
}
__exit();
}
# unified exit function, make sure to shame the desertor!
sub __exit ($prefix = '') {
say $prefix, "CHICKEN !!!\n";
exit 0;
}
# unified input gathering. Checks if the input is closed (e.g. because the
# player hit CTRL-D) and __exit()s in case. Gets a prompt for asking a
# question, returns whatever is input (except spaces).
sub get_input ($prompt) {
print "$prompt? ";
defined(my $input = <STDIN>) or __exit("\n");
# remove spaces from the input (including newlines), they are not used
$input =~ s{\s+}{}gmxs;
return $input;
}
# structured choosing function, gets a $prompt for asking a question and
# will iterate asking until the input is a number between 1 and $n_max.
sub choose ($prompt, $n_max) {
while ('necessary') {
my $side = get_input($prompt);
return $side if $side =~ m{\A [1-9]\d* \z}mxs && $side <= $n_max;
say 'TRY AGAIN...';
}
}
# Italy mission has the same structure as Allies and Germany, so it's been
# refactored into a single "multiple()" (pun intended) function, providing
# the right messaging.
sub italy {
return multiple(
'YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)',
q{SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE.},
'BE CAREFUL!!!',
q{YOU'RE GOING FOR THE OIL, EH?},
);
}
# Allies mission has the same structure as Italy and Germany, so it's been
# refactored into a single "multiple()" (pun intended) function, providing
# the right messaging.
sub allies {
return multiple(
'AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4)',
q{YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI.},
q{YOU'RE DUMPING THE A-BOMB ON HIROSHIMA.},
q{YOU'RE CHASING THE BISMARK IN THE NORTH SEA.},
q{YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.},
);
}
# Japan mission is different from the other three and is coded...
# differently. The end game phases are the same as other missions though,
# hence the calls to "direct_hit()" and "endgame()" functions.
sub japan {
say q{YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.};
my $is_first_kamikaze = get_input(q{YOUR FIRST KAMIKAZE MISSION(Y OR N)});
if (fc($is_first_kamikaze) eq fc('n')) {
our $guns_hit_rate = 0;
say '';
return endgame();
}
return direct_hit() if rand(1) > 0.65;
return endgame('fail');
}
# Germany mission has the same structure as Italy and Allies, so it's been
# refactored into a single "multiple()" (pun intended) function, providing
# the right messaging.
sub germany {
return multiple(
"A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\n"
. 'ENGLAND(2), OR FRANCE(3)',
q{YOU'RE NEARING STALINGRAD.},
q{NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR.},
q{NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.}
);
}
# This function implements the workhorse for Italy, Allies and Germany
# missions, which all have the same structure. It starts with a $question
# and a few @comments, each commenting every different answer to the
# $question.
sub multiple ($question, @comments) {
my $target = choose($question, scalar @comments);
say "\n", $comments[$target - 1], "\n";
# we gather the number of missions flown so far so that we can
# use it to figure out if *this* mission will be successful. The more
# the missions flown, the higher the probability of success.
my $missions;
while ('necessary') {
$missions = get_input('HOW MANY MISSIONS HAVE YOU FLOWN');
last if $missions < 160;
print 'MISSIONS, NOT MILES...
150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS.
NOW THEN, ';
}
say '';
# a little intermediate comment based on the value of $missions
if ($missions < 25) { say "FRESH OUT OF TRANING, EH?\n" }
elsif ($missions >= 100) { say "THAT'S PUSHING THE ODDS!\n" }
# let's roll a 160-faced die and compare to the missions flown so far,
# player might not even have to engage in combat!
return direct_hit() if $missions >= rand(160);
# player didn't get a direct hit on the target, so we provide a
# feedback about how much it was apart. This is part of the story.
my $miss = 2 + int rand(30);
say "MISSED TARGET BY $miss MILES!";
say "NOW YOU'RE REALLY IN FOR IT !!\n";
# here is where the game shows a little "weakness", although it might
# have been done on purpose. We use "our" variables $missiles_hit_rate
# and $guns_hit_rate here because the original BASIC code did not reset
# the associated variables (respectively T and S) at every mission, thus
# leaking state from one mission to the following ones.
#
# In particular, both are leaked to the Japan mission(s), and
# $guns_hit_rate is leaked to future "multiple()" missions that have
# missiles only.
#
# This is what you get when your language only has global variables.
#
# Of course, this might have been done on purpose, and we'll replicate
# this behaviour here because it adds some randomness to the game.
our $missiles_hit_rate = 0;
my $response = choose(
'DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)', 3);
# Apply Gun damage for responses 1 and 3
if ($response != 2) { # there's some guns involved, ask more
say '';
# see comment above as to why we have a "our" variable here
our $guns_hit_rate =
get_input(q{WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)});
# let's normalize the input a bit
$guns_hit_rate = 0 unless $guns_hit_rate =~ m{\A [1-9]\d* \z}mxs;
# a hit rate this low is not reasonable and is immediately punished!
if ($guns_hit_rate < 10) {
say q{YOU LIE, BUT YOU'LL PAY...};
# function endgame() provides the... end game messaging, which is
# also used by the Japan mission, so it's been factored out.
# Passing 'fail' (or any true value) makes sure that is' a
# failure.
return endgame('fail'); # sure failure
}
say '';
}
# Apply missile damage for responses 2 and 3
if ($response > 1 ) {
$missiles_hit_rate = 35; # remember... this is a global variable
}
# hand control over to the "endgame()" refactored function (also shared
# by the Japan mission).
return endgame();
}
sub direct_hit {
my $killed = int rand(100);
say "DIRECT HIT!!!! $killed KILLED.\nMISSION SUCCESSFUL";
return;
}
# This function provides the end game randomization and messages, shared
# across all missions. If passed a true value $fail, it will make sure that
# the outcome is... a failure. This allows coping with a few ad-hoc
# GOTO:s in the original BASIC code, while still preserving a refactored
# code.
sub endgame ($fail = 0) {
our $missiles_hit_rate //= 0;
our $guns_hit_rate //= 0;
$fail ||= ($missiles_hit_rate + $guns_hit_rate) > rand(100);
if ($fail) {
say '* * * * BOOM * * * *
YOU HAVE BEEN SHOT DOWN.....
DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR
LAST TRIBUTE...';
}
else {
say 'YOU MADE IT THROUGH TREMENDOUS FLAK!!';
}
return;
}
+189
View File
@@ -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
+11 -2
View File
@@ -1,7 +1,16 @@
### Bounce
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=25
This program plots a bouncing ball. Most computer plots run along the paper in the terminal (top to bottom); however, this plot is drawn horizontally on the paper (left to right).
You may specify the initial velocity of the ball and the coefficient of elasticity of the ball (a superball is about 0.85 — other balls are much less). You also specify the time increment to be used in “strobing” the flight of the ball. In other words, it is as though the ball is thrown up in a darkened room and you flash a light at fixed time intervals and photograph the progress of the ball.
The program was originally written by Val Skalabrin while he was at DEC.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=25)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=40)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+184
View File
@@ -0,0 +1,184 @@
## Global constants
# Gravity accelaration (F/S^2) ~= 32
G = 32
# Used to indent the plotting of ball positions
# so that the height digits don't affect
# where we start plotting ball positions
BALL_PLOT_INDENT = "\t"
# The deviation between current plotted height and the actual
# height of the ball that we will accept to plot the ball in
# that plotted height
BALL_PLOT_DEVIATION = 0.25
# The step we will take as we move down vertically while
# plotting ball positions
BALL_PLOT_HEIGHT_STEP = 0.5
## Helper functions
# Calculates the bounce speed (up) of the ball for a given
# bounce number and coefficient
def calc_velocity_for_bounce(v0, bounce, coefficient)
v = v0 * coefficient**bounce
end
# Check https://physics.stackexchange.com/a/333436 for nice explanation
def calc_bounce_total_time(v0, bounce, coefficient)
v = calc_velocity_for_bounce(v0, bounce, coefficient)
t = 2 * v / G
end
# Check https://physics.stackexchange.com/a/333436 for nice explanation
def calc_ball_height(v0, bounce, coefficient, t)
v = calc_velocity_for_bounce(v0, bounce, coefficient)
h = v * t - 0.5 * G * t**2
end
def heighest_position_in_next_bounce(time_in_bounce, v0, i, c)
time_in_next_bounce = time_in_bounce[i+1]
return -1 if time_in_next_bounce.nil?
return calc_ball_height(v0, i, c, time_in_next_bounce / 2) unless time_in_next_bounce.nil?
end
def intro
puts <<~INSTRUCTIONS
BOUNCE
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY
OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF
ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION
COEFFICIENCY (LESS THAN 1).
YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN
'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).
INSTRUCTIONS
end
## Plottin functions
def plot_header
puts
puts "FEET"
end
def plot_bouncing_ball(strobbing_time, v0, c)
## Initializing helper values
# How many bounces we want to plot
# original BASIC version is 70 / (V / (16 * S2))
# 70 is assumed to be an arbitrary number higher than 2G and 16 is 1/2G
bounces_to_plot = (G**2 / (v0 / strobbing_time)).to_i
# Holds the total time the ball spends in the air in every bounce
time_in_bounce = bounces_to_plot.times.map { |i| calc_bounce_total_time v0, i, c }
plot_width = 0
# Calculate the highest position for the ball after the very first bounce
plotted_height = (calc_ball_height(v0, 0, c, v0/G) + 0.5).to_i
## Plotting bouncing ball
while plotted_height >= 0 do
# We will print only whole-number heights
print plotted_height.to_i if plotted_height.to_i === plotted_height
print BALL_PLOT_INDENT
bounces_to_plot.times { |i|
(0..time_in_bounce[i]).step(strobbing_time) { |t|
ball_pos = calc_ball_height v0, i, c, t
# If the ball is within the acceptable deviation
# from the current height, we will plot it
if (plotted_height - ball_pos).abs <= BALL_PLOT_DEVIATION then
print "0"
else
print " "
end
# Increment the plot width when we are plotting height = 0
# which will definitely be the longest since it never gets
# skipped by line 98
plot_width += 1 if plotted_height == 0
}
if heighest_position_in_next_bounce(time_in_bounce, v0, i, c) < plotted_height then
# If we got no more ball positions at or above current height in the next bounce,
# we can skip the rest of the bounces and move down to the next height to plot
puts
break
end
}
plotted_height -= BALL_PLOT_HEIGHT_STEP
end
# Return plot_width to be used by the plot_footer
plot_width
end
def plot_footer (plot_width, strobbing_time)
# Dotted separator line
puts
print BALL_PLOT_INDENT
(plot_width).times { |_| print "." }
puts
# Time values line
print BALL_PLOT_INDENT
points_in_sec = (1 / strobbing_time).to_i
plot_width.times { |i|
if i % points_in_sec == 0 then
print (i / points_in_sec).to_i
else
print " "
end
}
puts
# Time unit line
print BALL_PLOT_INDENT
(plot_width / 2 - 4).to_i.times { |_| print " " }
puts "SECONDS"
puts
end
def game_loop
# Read strobing, velocity and coefficient parameters from user input
puts "TIME INCREMENT (SEC)"
strobbing_time = gets.to_f
puts "VELOCITY (FPS)"
v0 = gets.to_f
puts "COEFFICIENT"
c = gets.to_f
# Plotting
plot_header
plot_width = plot_bouncing_ball strobbing_time, v0, c
plot_footer plot_width, strobbing_time
end
## Entry point
begin
intro
loop do
game_loop
end
rescue SystemExit, Interrupt
exit
rescue => exception
p exception
end
+13 -2
View File
@@ -1,7 +1,18 @@
### Bowling
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=26
This is a simulated bowling game for up to four players. You play 10 frames. To roll the ball, you simply type “ROLL.” After each roll, the computer will show you a diagram of the remaining pins (“0” means the pin is down, “+” means it is still standing), and it will give you a roll analysis:
- GUTTER
- STRIKE
- SPARE
- ERROR (on second ball if pins still standing)
Bowling was written by Paul Peraino while a student at Woodrow Wilson High School, San Francisco, California.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=26)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=41)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+11 -2
View File
@@ -1,7 +1,16 @@
### Boxing
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=28
This program simulates a three-round Olympic boxing match. The computer coaches one of the boxers and determines his punches and defences, while you do the same for your boxer. At the start of the match, you may specify your mans best punch and his vulnerability.
There are approximately seven major punches per round, although this may be varied. The best out if three rounds wins.
Jesse Lynch of St. Paul, Minnesota created this program.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=28)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=43)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+48
View File
@@ -0,0 +1,48 @@
namespace Boxing;
public abstract class AttackStrategy
{
protected const int KnockoutDamageThreshold = 35;
protected readonly Boxer Other;
protected readonly Stack<Action> Work;
private readonly Action _notifyGameEnded;
public AttackStrategy(Boxer other, Stack<Action> work, Action notifyGameEnded)
{
Other = other;
Work = work;
_notifyGameEnded = notifyGameEnded;
}
public void Attack()
{
var punch = GetPunch();
if (punch.IsBestPunch)
{
Other.DamageTaken += 2;
}
Work.Push(punch.Punch switch
{
Punch.FullSwing => FullSwing,
Punch.Hook => Hook,
Punch.Uppercut => Uppercut,
_ => Jab
});
}
protected abstract AttackPunch GetPunch();
protected abstract void FullSwing();
protected abstract void Hook();
protected abstract void Uppercut();
protected abstract void Jab();
protected void RegisterKnockout(string knockoutMessage)
{
Work.Clear();
_notifyGameEnded();
Console.WriteLine(knockoutMessage);
}
protected record AttackPunch(Punch Punch, bool IsBestPunch);
}
+45
View File
@@ -0,0 +1,45 @@
namespace Boxing;
public class Boxer
{
private int _wins;
private string Name { get; set; } = string.Empty;
public Punch BestPunch { get; set; }
public Punch Vulnerability { get; set; }
public void SetName(string prompt)
{
Console.WriteLine(prompt);
string? name;
do
{
name = Console.ReadLine();
} while (string.IsNullOrWhiteSpace(name));
Name = name;
}
public int DamageTaken { get; set; }
public void ResetForNewRound() => DamageTaken = 0;
public void RecordWin() => _wins += 1;
public bool IsWinner => _wins >= 2;
public override string ToString() => Name;
}
public class Opponent : Boxer
{
public void SetRandomPunches()
{
do
{
BestPunch = (Punch) GameUtils.Roll(4); // B1
Vulnerability = (Punch) GameUtils.Roll(4); // D1
} while (BestPunch == Vulnerability);
}
}
+10
View File
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boxing", "Boxing.csproj", "{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52A7BDE5-3085-4F58-AC57-2BA4E65212D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+115
View File
@@ -0,0 +1,115 @@
using static Boxing.GameUtils;
using static System.Console;
namespace Boxing;
public class OpponentAttackStrategy : AttackStrategy
{
private readonly Opponent _opponent;
public OpponentAttackStrategy(Opponent opponent, Boxer player, Action notifyGameEnded, Stack<Action> work) : base(player, work, notifyGameEnded)
{
_opponent = opponent;
}
protected override AttackPunch GetPunch()
{
var punch = (Punch)Roll(4);
return new AttackPunch(punch, punch == _opponent.BestPunch);
}
protected override void FullSwing() // 720
{
Write($"{_opponent} TAKES A FULL SWING AND");
if (Other.Vulnerability == Punch.FullSwing)
{
ScoreFullSwing();
}
else
{
if (RollSatisfies(60, x => x < 30))
{
WriteLine(" IT'S BLOCKED!");
}
else
{
ScoreFullSwing();
}
}
void ScoreFullSwing()
{
WriteLine(" POW!!!!! HE HITS HIM RIGHT IN THE FACE!");
if (Other.DamageTaken > KnockoutDamageThreshold)
{
Work.Push(RegisterOtherKnockedOut);
}
Other.DamageTaken += 15;
}
}
protected override void Hook() // 810
{
Write($"{_opponent} GETS {Other} IN THE JAW (OUCH!)");
Other.DamageTaken += 7;
WriteLine("....AND AGAIN!");
Other.DamageTaken += 5;
if (Other.DamageTaken > KnockoutDamageThreshold)
{
Work.Push(RegisterOtherKnockedOut);
}
}
protected override void Uppercut() // 860
{
Write($"{Other} IS ATTACKED BY AN UPPERCUT (OH,OH)...");
if (Other.Vulnerability == Punch.Uppercut)
{
ScoreUppercut();
}
else
{
if (RollSatisfies(200, x => x > 75))
{
WriteLine($" BLOCKS AND HITS {_opponent} WITH A HOOK.");
_opponent.DamageTaken += 5;
}
else
{
ScoreUppercut();
}
}
void ScoreUppercut()
{
WriteLine($"AND {_opponent} CONNECTS...");
Other.DamageTaken += 8;
}
}
protected override void Jab() // 640
{
Write($"{_opponent} JABS AND ");
if (Other.Vulnerability == Punch.Jab)
{
ScoreJab();
}
else
{
if (RollSatisfies(7, x => x > 4))
{
WriteLine("BLOOD SPILLS !!!");
ScoreJab();
}
else
{
WriteLine("IT'S BLOCKED!");
}
}
void ScoreJab() => Other.DamageTaken += 5;
}
private void RegisterOtherKnockedOut()
=> RegisterKnockout($"{Other} IS KNOCKED COLD AND {_opponent} IS THE WINNER AND CHAMP!");
}
+121
View File
@@ -0,0 +1,121 @@
using static Boxing.GameUtils;
using static System.Console;
namespace Boxing;
public class PlayerAttackStrategy : AttackStrategy
{
private readonly Boxer _player;
public PlayerAttackStrategy(Boxer player, Opponent opponent, Action notifyGameEnded, Stack<Action> work)
: base(opponent, work, notifyGameEnded) => _player = player;
protected override AttackPunch GetPunch()
{
var punch = GameUtils.GetPunch($"{_player}'S PUNCH");
return new AttackPunch(punch, punch == _player.BestPunch);
}
protected override void FullSwing() // 340
{
Write($"{_player} SWINGS AND ");
if (Other.Vulnerability == Punch.FullSwing)
{
ScoreFullSwing();
}
else
{
if (RollSatisfies(30, x => x < 10))
{
ScoreFullSwing();
}
else
{
WriteLine("HE MISSES");
}
}
void ScoreFullSwing()
{
WriteLine("HE CONNECTS!");
if (Other.DamageTaken > KnockoutDamageThreshold)
{
Work.Push(() => RegisterKnockout($"{Other} IS KNOCKED COLD AND {_player} IS THE WINNER AND CHAMP!"));
}
Other.DamageTaken += 15;
}
}
protected override void Uppercut() // 520
{
Write($"{_player} TRIES AN UPPERCUT ");
if (Other.Vulnerability == Punch.Uppercut)
{
ScoreUpperCut();
}
else
{
if (RollSatisfies(100, x => x < 51))
{
ScoreUpperCut();
}
else
{
WriteLine("AND IT'S BLOCKED (LUCKY BLOCK!)");
}
}
void ScoreUpperCut()
{
WriteLine("AND HE CONNECTS!");
Other.DamageTaken += 4;
}
}
protected override void Hook() // 450
{
Write($"{_player} GIVES THE HOOK... ");
if (Other.Vulnerability == Punch.Hook)
{
ScoreHookOnOpponent();
}
else
{
if (RollSatisfies(2, x => x == 1))
{
WriteLine("BUT IT'S BLOCKED!!!!!!!!!!!!!");
}
else
{
ScoreHookOnOpponent();
}
}
void ScoreHookOnOpponent()
{
WriteLine("CONNECTS...");
Other.DamageTaken += 7;
}
}
protected override void Jab()
{
WriteLine($"{_player} JABS AT {Other}'S HEAD");
if (Other.Vulnerability == Punch.Jab)
{
ScoreJabOnOpponent();
}
else
{
if (RollSatisfies(8, x => x < 4))
{
WriteLine("IT'S BLOCKED.");
}
else
{
ScoreJabOnOpponent();
}
}
void ScoreJabOnOpponent() => Other.DamageTaken += 3;
}
}
+29
View File
@@ -0,0 +1,29 @@
using Boxing;
using static Boxing.GameUtils;
using static System.Console;
WriteLine(new string('\t', 33) + "BOXING");
WriteLine(new string('\t', 15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
WriteLine("{0}{0}{0}BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS){0}", Environment.NewLine);
var opponent = new Opponent();
opponent.SetName("WHAT IS YOUR OPPONENT'S NAME"); // J$
var player = new Boxer();
player.SetName("INPUT YOUR MAN'S NAME"); // L$
PrintPunchDescription();
player.BestPunch = GetPunch("WHAT IS YOUR MANS BEST"); // B
player.Vulnerability = GetPunch("WHAT IS HIS VULNERABILITY"); // D
opponent.SetRandomPunches();
WriteLine($"{opponent}'S ADVANTAGE IS {opponent.BestPunch.ToFriendlyString()} AND VULNERABILITY IS SECRET.");
for (var i = 1; i <= 3; i ++) // R
{
var round = new Round(player, opponent, i);
round.Start();
round.CheckOpponentWin();
round.CheckPlayerWin();
if (round.GameEnded) break;
}
WriteLine("{0}{0}AND NOW GOODBYE FROM THE OLYMPIC ARENA.{0}", Environment.NewLine);
+9
View File
@@ -0,0 +1,9 @@
namespace Boxing;
public enum Punch
{
FullSwing = 1,
Hook = 2,
Uppercut = 3,
Jab = 4
}
+96
View File
@@ -0,0 +1,96 @@
namespace Boxing;
class Round
{
private readonly Boxer _player;
private readonly Boxer _opponent;
private readonly int _round;
private Stack<Action> _work = new();
private readonly PlayerAttackStrategy _playerAttackStrategy;
private readonly OpponentAttackStrategy _opponentAttackStrategy;
public bool GameEnded { get; private set; }
public Round(Boxer player, Opponent opponent, int round)
{
_player = player;
_opponent = opponent;
_round = round;
_work.Push(ResetPlayers);
_work.Push(CheckOpponentWin);
_work.Push(CheckPlayerWin);
void NotifyGameEnded() => GameEnded = true;
_playerAttackStrategy = new PlayerAttackStrategy(player, opponent, NotifyGameEnded, _work);
_opponentAttackStrategy = new OpponentAttackStrategy(opponent, player, NotifyGameEnded, _work);
}
public void Start()
{
while (_work.Count > 0)
{
var action = _work.Pop();
// This delay does not exist in the VB code but it makes a bit easier to follow the game.
// I assume the computers at the time were slow enough
// so that they did not need this delay...
Thread.Sleep(300);
action();
}
}
public void CheckOpponentWin()
{
if (_opponent.IsWinner)
{
Console.WriteLine($"{_opponent} WINS (NICE GOING, {_opponent}).");
GameEnded = true;
}
}
public void CheckPlayerWin()
{
if (_player.IsWinner)
{
Console.WriteLine($"{_player} AMAZINGLY WINS!!");
GameEnded = true;
}
}
private void ResetPlayers()
{
_player.ResetForNewRound();
_opponent.ResetForNewRound();
_work.Push(RoundBegins);
}
private void RoundBegins()
{
Console.WriteLine();
Console.WriteLine($"ROUND {_round} BEGINS...");
_work.Push(CheckRoundWinner);
for (var i = 0; i < 7; i++)
{
_work.Push(DecideWhoAttacks);
}
}
private void CheckRoundWinner()
{
if (_opponent.DamageTaken > _player.DamageTaken)
{
Console.WriteLine($"{_player} WINS ROUND {_round}");
_player.RecordWin();
}
else
{
Console.WriteLine($"{_opponent} WINS ROUND {_round}");
_opponent.RecordWin();
}
}
private void DecideWhoAttacks()
{
_work.Push( GameUtils.RollSatisfies(10, x => x > 5) ? _opponentAttackStrategy.Attack : _playerAttackStrategy.Attack );
}
}
+35
View File
@@ -0,0 +1,35 @@
namespace Boxing;
public static class GameUtils
{
private static readonly Random Rnd = new((int) DateTime.UtcNow.Ticks);
public static void PrintPunchDescription() =>
Console.WriteLine($"DIFFERENT PUNCHES ARE: {PunchDesc(Punch.FullSwing)}; {PunchDesc(Punch.Hook)}; {PunchDesc(Punch.Uppercut)}; {PunchDesc(Punch.Jab)}.");
private static string PunchDesc(Punch punch) => $"({(int)punch}) {punch.ToFriendlyString()}";
public static Punch GetPunch(string prompt)
{
Console.WriteLine(prompt);
Punch result;
while (!Enum.TryParse(Console.ReadLine(), out result) || !Enum.IsDefined(typeof(Punch), result))
{
PrintPunchDescription();
}
return result;
}
public static Func<int, int> Roll { get; } = upperLimit => (int) (upperLimit * Rnd.NextSingle()) + 1;
public static bool RollSatisfies(int upperLimit, Predicate<int> predicate) => predicate(Roll(upperLimit));
public static string ToFriendlyString(this Punch punch)
=> punch switch
{
Punch.FullSwing => "FULL SWING",
Punch.Hook => "HOOK",
Punch.Uppercut => "UPPERCUT",
Punch.Jab => "JAB",
_ => throw new ArgumentOutOfRangeException(nameof(punch), punch, null)
};
}
+13 -2
View File
@@ -1,7 +1,18 @@
### Bug
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=30
The object of this game is to finish your drawing of a bug before the computer finishes.
You and the computer roll a die alternately with each number standing for a part of the bug. You must add the parts in the right order; in other words, you cannot have a neck until you have a body, you cannot have a head until you have a neck, and so on. After each new part has been added, you have the option of seeing pictures of the two bugs.
If you elect to see all the pictures, this program has the ability of consuming well over six feet of terminal paper per run. We can only suggest recycling the paper by using the other side.
Brian Leibowitz wrote this program while in the 7th grade at Harrison Jr-Se High School in Harrison, New York.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=30)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=45)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+20 -2
View File
@@ -1,7 +1,25 @@
### Bullfight
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=32
In this simulated bullfight, you are the matador — i.e., the one with the principle role and the one who must kill the bull or be killed (or run from the ring).
On each pass of the bull, you may try:
- 0: Veronica (dangerous inside move of the cape)
- 1: Less dangerous outside move of the cape
- 2: Ordinary swirl of the cape
Or you may try to kill the bull:
- 4: Over the horns
- 5: In the chest
The crowd will determine what award you deserve, posthumously if necessary. The braver you are, the better the reward you receive. Its nice to stay alive too. The better the job the picadores and toreadores do, the better your chances.
David Sweet of Dartmouth wrote the original version of this program. It was then modified by students at Lexington High School and finally by Steve North of Creative Computing.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=32)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=47)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+31 -2
View File
@@ -1,7 +1,36 @@
### Bullseye
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=34
In this game, up to 20 players throw darts at a target with 10-, 20-, 30-, and 40-point zones. The objective is to get 200 points.
You have a choice of three methods of throwing:
| Throw | Description | Probable Score |
|-------|--------------------|---------------------------|
| 1 | Fast overarm | Bullseye or complete miss |
| 2 | Controlled overarm | 10, 20, or 30 points |
| 3 | Underarm | Anything |
You will find after playing a while that different players will swear by different strategies. However, considering the expected score per throw by always using throw 3:
| Score (S) | Probability (P) | S x P |
|-----------|-----------------|-------|
| 40 | 1.00-.95 = .05 | 2 |
| 30 | .95-.75 = .20 | 6 |
| 30 | .75-.45 = .30 | 6 |
| 10 | .45-.05 = .40 | 4 |
| 0 | .05-.00 = .05 | 0 |
Expected score per throw = 18
Calculate the expected score for the other throws and you may be surprised!
The program was written by David Ahl of Creative Computing.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=34)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=49)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+3 -2
View File
@@ -1,7 +1,8 @@
### Bunny
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=35
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=35)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=50)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+9 -2
View File
@@ -1,7 +1,14 @@
### Buzzword
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=36
This program is an invaluable aid for preparing speeches and briefings about educational technology. This buzzword generator provides sets of three highly-acceptable words to work into your material. Your audience will never know that the phrases dont really mean much of anything because they sound so great! Full instructions for running are given in the program.
This version of Buzzword was written by David Ahl.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=36)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=51)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+10 -34
View File
@@ -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!" );
}
}
@@ -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<String> {
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)];
}
}
+64
View File
@@ -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<String> 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<String> 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!");
}
}
+18 -2
View File
@@ -1,7 +1,23 @@
### Calendar
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=37
This program prints out a calendar for any year. You must specify the starting day of the week of the year:
- 0: Sunday
- -1: Monday
- -2: Tuesday
- -3: Wednesday
- -4: Thursday
- -5: Friday
- -6: Saturday
You can determine this by using the program WEEKDAY. You must also make two changes for leap years. The program listing describes the necessary changes. Running the program produces a nice 12-month calendar.
The program was written by Geoffrey Chase of the Abbey, Portsmouth, Rhode Island.
---
As published in Basic Computer Games (1978):
- [Atari Archives](https://www.atariarchives.org/basicgames/showpage.php?page=37)
- [Annarchive](https://annarchive.com/files/Basic_Computer_Games_Microcomputer_Edition.pdf#page=52)
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_21_calendar</RootNamespace>
</PropertyGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More