Merge branch 'coding-horror:main' into main

This commit is contained in:
Nezumi Ronin
2022-01-12 16:43:14 -06:00
committed by GitHub
2058 changed files with 90598 additions and 1006 deletions

35
.gitignore vendored
View File

@@ -0,0 +1,35 @@
.local/
.vscode/
.gradle/
node_modules/
buildJvm/
build.gradle
.classpath
.project
.settings
.metadata
*.iml
*.ipr
*.class
*/.vs
*.suo
bin/
obj/
.idea
*.iws
*.iml
*.ipr
out/
*.py[co]
Pipfile
.DS_Store
/.vs/basic-computer-games/v16
/.vs

View File

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

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)

View File

@@ -1,11 +0,0 @@
### Acey Ducey
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=2
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
#### Other languages:
- [Pascal/Object Pascal](https://github.com/gcarreno/basic-computer-games-in-pascal/tree/main/01%20Acey%20Ducey)

View File

@@ -1,135 +0,0 @@
// ACEY DUCEY
//
// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
//
function print(str)
{
document.getElementById("output").appendChild(document.createTextNode(str));
}
function input()
{
var input_element;
var input_str;
return new Promise(function (resolve) {
input_element = document.createElement("INPUT");
print("? ");
input_element.setAttribute("type", "text");
input_element.setAttribute("length", "50");
document.getElementById("output").appendChild(input_element);
input_element.focus();
input_str = undefined;
input_element.addEventListener("keydown", function (event) {
if (event.keyCode == 13) {
input_str = input_element.value;
document.getElementById("output").removeChild(input_element);
print(input_str);
print("\n");
resolve(input_str);
}
});
});
}
function tab(space)
{
var str = "";
while (space-- > 0)
str += " ";
return str;
}
print(tab(26) + "ACEY DUCEY CARD GAME\n");
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n");
print("\n");
print("\n");
print("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER\n");
print("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP\n");
print("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING\n");
print("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE\n");
print("A VALUE BETWEEN THE FIRST TWO.\n");
print("IF YOU DO NOT WANT TO BET, INPUT A 0\n");
function show_card(card)
{
if (card < 11)
print(card + "\n");
else if (card == 11)
print("JACK\n");
else if (card == 12)
print("QUEEN\n");
else if (card == 13)
print("KING\n");
else
print("ACE\n");
}
// Main program
async function main()
{
q = 100;
while (1) {
print("YOU NOW HAVE " + q + " DOLLARS.\n");
print("\n");
do {
print("HERE ARE YOUR NEXT TWO CARDS: \n");
do {
a = Math.floor(Math.random() * 13 + 2);
b = Math.floor(Math.random() * 13 + 2);
} while (a >= b) ;
show_card(a);
show_card(b);
print("\n");
while (1) {
print("\n");
print("WHAT IS YOUR BET");
m = parseInt(await input());
if (m > 0) {
if (m > q) {
print("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.\n");
print("YOU HAVE ONLY " + q + "DOLLARS TO BET.\n");
continue;
}
break;
}
m = 0;
print("CHICKEN!!\n");
print("\n");
break;
}
} while (m == 0) ;
c = Math.floor(Math.random() * 13 + 2);
show_card(c);
if (c > a && c < b) {
print("YOU WIN!!!\n");
q = q + m;
} else {
print("SORRY, YOU LOSE\n");
if (m >= q) {
print("\n");
print("\n");
print("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.\n");
print("\n");
print("\n");
print("TRY AGAIN (YES OR NO)");
a = await input();
print("\n");
print("\n");
if (a == "YES") {
q = 100;
} else {
print("O.K., HOPE YOU HAD FUN!");
break;
}
} else {
q = q - m;
}
}
}
}
main();

20
01_Acey_Ducey/README.md Normal file
View File

@@ -0,0 +1,20 @@
### Acey Ducey
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
#### 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

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
01_Acey_Ducey/d/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.exe
*.obj

29
01_Acey_Ducey/d/README.md Normal file
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
01_Acey_Ducey/d/aceyducey.d Normal file
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;
}

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!");
}

View File

@@ -0,0 +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.

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");
}
}

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.
*/
}

View File

@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true
}

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>ACEY DUCEY</title>
<pre id="output" style="font-size: 12pt"></pre>
<script src="aceyducey.js"></script>

View File

@@ -0,0 +1,220 @@
// UTILITY VARIABLES
// By default:
// — Browsers have a window object
// — Node.js does not
// Checking for an undefined window object is a loose check
// to enable browser and Node.js support
const isRunningInBrowser = typeof window !== 'undefined';
// To easily validate input strings with utility functions
const validLowerCaseYesStrings = ['yes', 'y'];
const validLowerCaseNoStrings = ['no', 'n'];
const validLowerCaseYesAndNoStrings = [
...validLowerCaseYesStrings,
...validLowerCaseNoStrings,
];
// UTILITY VARIABLES
// Function to get a random number (card) 2-14 (ACE is 14)
function getRandomCard() {
// In our game, the value of ACE is greater than face cards;
// instead of having the value of ACE be 1, well have it be 14.
// So, we want to shift the range of random numbers from 1-13 to 2-14
let min = 2;
let max = 14;
// Return random integer between two values, inclusive
return Math.floor(Math.random() * (max - min + 1) + min);
}
function newGameCards() {
let cardOne = getRandomCard();
let cardTwo = getRandomCard();
let cardThree = getRandomCard();
// We want:
// 1. cardOne and cardTwo to be different cards
// 2. cardOne to be lower than cardTwo
// So, while cardOne is greater than or equal too cardTwo
// we will continue to generate random cards.
while (cardOne >= cardTwo) {
cardOne = getRandomCard();
cardTwo = getRandomCard();
}
return [cardOne, cardTwo, cardThree];
}
// Function to get card value
function getCardValue(card) {
let faceOrAce = {
11: 'JACK',
12: 'QUEEN',
13: 'KING',
14: 'ACE',
};
// If card value matches a key in faceOrAce, use faceOrAce value;
// Else, return undefined and handle with the Nullish Coalescing Operator (??)
// and default to card value.
let cardValue = faceOrAce[card] ?? card;
return cardValue;
}
print(spaces(26) + 'ACEY DUCEY CARD GAME');
print(spaces(15) + 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n');
print('ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER');
print('THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP');
print('YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING');
print('ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE');
print('A VALUE BETWEEN THE FIRST TWO.');
print("IF YOU DO NOT WANT TO BET, INPUT '0'");
main();
async function main() {
let bet;
let availableDollars = 100;
// Loop game forever
while (true) {
let [cardOne, cardTwo, cardThree] = newGameCards();
print(`YOU NOW HAVE ${availableDollars} DOLLARS.\n`);
print('HERE ARE YOUR NEXT TWO CARDS: ');
print(getCardValue(cardOne));
print(getCardValue(cardTwo));
print('');
// Loop until receiving a valid bet
let validBet = false;
while (!validBet) {
print('\nWHAT IS YOUR BET? ');
bet = parseInt(await input(), 10);
let minimumRequiredBet = 0;
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;
}
}
}
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));
// Determine if player won or lost
if (cardThree > cardOne && cardThree < cardTwo) {
print('YOU WIN!!!');
availableDollars = availableDollars + bet;
} else {
print('SORRY, YOU LOSE');
if (bet >= availableDollars) {
print('');
print('');
print('SORRY, FRIEND, BUT YOU BLEW YOUR WAD.');
print('');
print('');
print('TRY AGAIN (YES OR NO)');
let tryAgainInput = await input();
print('');
print('');
if (isValidYesString(tryAgainInput)) {
availableDollars = 100;
} else {
print('O.K., HOPE YOU HAD FUN!');
break;
}
} else {
availableDollars = availableDollars - bet;
}
}
}
}
// UTILITY FUNCTIONS
function isValidYesNoString(string) {
return validLowerCaseYesAndNoStrings.includes(string.toLowerCase());
}
function isValidYesString(string) {
return validLowerCaseYesStrings.includes(string.toLowerCase());
}
function isValidNoString(string) {
return validLowerCaseNoStrings.includes(string.toLowerCase());
}
function print(string) {
if (isRunningInBrowser) {
// Adds trailing newline to match console.log behavior
document
.getElementById('output')
.appendChild(document.createTextNode(string + '\n'));
} else {
console.log(string);
}
}
function input() {
if (isRunningInBrowser) {
// Accept input from the browser DOM input
return new Promise((resolve) => {
const outputElement = document.querySelector('#output');
const inputElement = document.createElement('input');
outputElement.append(inputElement);
inputElement.focus();
inputElement.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
const result = inputElement.value;
inputElement.remove();
print(result);
print('');
resolve(result);
}
});
});
} else {
// Accept input from the command line in Node.js
// See: https://nodejs.dev/learn/accept-input-from-the-command-line-in-nodejs
return new Promise(function (resolve) {
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
readline.question('', function (input) {
resolve(input);
readline.close();
});
});
}
}
function printInline(string) {
if (isRunningInBrowser) {
document
.getElementById('output')
.appendChild(document.createTextNode(string));
} else {
process.stdout.write(string);
}
}
function spaces(numberOfSpaces) {
return ' '.repeat(numberOfSpaces);
}
// UTILITY FUNCTIONS

36
01_Acey_Ducey/pascal/.gitattributes vendored Normal file
View File

@@ -0,0 +1,36 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.inc text
*.pas text
*.pp text
*.lpk text
*.lpi text
*.lps text
*.lpr text
*.def text
*.css text
*.html text
*.xml text
*.sql text
# Declare files that will always have CRLF line endings on checkout.
*.dpk text eol=crlf
*.dproj text eol=crlf
# Declare files that will always have LF line endings on checkout.
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.exe binary
*.res binary
*.ico binary
*.dll binary
# Keep these files from archive/exports, mainly from production.
.gitignore export-ignore
.gitattributes export-ignore

63
01_Acey_Ducey/pascal/.gitignore vendored Normal file
View File

@@ -0,0 +1,63 @@
# Basic Computer Programs project specific
aceyducey
aceyducey.exe
# Compiled l10n files: .mo should be ignored
*.mo
# Ghostwriter backups
*.backup
# nano editor backup files
*.swp
# Uncomment these types if you want even more clean repository. But be careful.
# It can make harm to an existing project source. Read explanations below.
#
# Resource files are binaries containing manifest, project icon and version info.
# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.
*.res
# Delphi/Lazarus compiler-generated binaries (safe to delete)
*.exe
*.dll
*.bpl
*.bpi
*.dcp
*.so
*.apk
*.drc
*.map
*.dres
*.rsm
*.tds
*.dcu
*.lib
*.[ao]
*.or
*.ppu
*.dbg
*.compiled
# Delphi autogenerated files (duplicated info)
*.cfg
*Resource.rc
# Delphi local files (user-specific info)
*.local
*.identcache
*.projdata
*.tvsconfig
*.dsk
# Delphi history and backups
__history/
*.~*
# Lazarus history, backups and session
backup/
*.bak
*.lps
# Castalia statistics file
*.stat

View File

@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language)) by Gustavo Carreno [gcarreno@github](https://github.com/gcarreno)

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="aceyducey"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<Units Count="3">
<Unit0>
<Filename Value="aceyducey.pas"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="game.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="Game"/>
</Unit1>
<Unit2>
<Filename Value="deck.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="Deck"/>
</Unit2>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="aceyducey"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,19 @@
program aceyducey;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
uses
Game
, Deck
;
var
Acey_Ducey: TGame;
begin
Acey_Ducey:= TGame.Create;
Acey_Ducey.Run;
end.

View File

@@ -0,0 +1,111 @@
unit Deck;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
interface
uses
Classes
, SysUtils
;
type
{ TDeck }
TDeck = class
private
FDealerLow: Integer;
FDealerHigh: Integer;
FPlayer: Integer;
procedure PrintCard(const ACard: Integer);
protected
public
property DealerLow: Integer
read FDealerLow;
property DealerHigh: Integer
read FDealerHigh;
property Player: Integer
read FPlayer;
procedure DrawCards;
procedure ShowDealerCards;
procedure ShowPlayerCard;
function PlayerWins: Boolean;
published
end;
implementation
{ TDeck }
procedure TDeck.PrintCard(const ACard: Integer);
begin
if ACard < 11 then
begin
Write(ACard);
end;
if ACard = 11 then
begin
Write('JACK');
end;
if ACard = 12 then
begin
Write('QUEEN');
end;
if ACard = 13 then
begin
Write('KING');
end;
if ACard = 14 then
begin
Write('ACE');
end;
end;
procedure TDeck.DrawCards;
var
tmp: Integer;
begin
repeat
FDealerLow:= Random(14) + 2;
until (FDealerLow >= 2) and (FDealerLow <= 14);
repeat
FDealerHigh:= Random(14) + 2;
until (FDealerHigh >= 2) and (FDealerHigh <= 14) and (FDealerLow <> FDealerHigh);
if FDealerLow > FDealerHigh then
begin
tmp:= FDealerHigh;
FDealerHigh:= FDealerLow;
FDealerLow:= tmp;
end;
repeat
FPlayer:= Random(14) + 2;
until (FPlayer >= 2) and (FPlayer <= 14);
end;
procedure TDeck.ShowDealerCards;
begin
Write('HERE ARE YOUR NEXT TWO CARDS: ');
PrintCard(FDealerLow);
Write(' ');
PrintCard(FDealerHigh);
WriteLN;
WriteLN;
end;
procedure TDeck.ShowPlayerCard;
begin
PrintCard(FPlayer);
WriteLN;
WriteLN;
end;
function TDeck.PlayerWins: Boolean;
begin
Result:= (FPlayer > FDealerLow) and (FPlayer < FDealerHigh);
end;
end.

View File

@@ -0,0 +1,136 @@
unit Game;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
interface
uses
Classes
, SysUtils
, Crt
, Deck
;
type
{ TGame }
TGame = class
private
FStash: Integer;
FBet: Integer;
FDeck: TDeck;
procedure PrintGreeting;
procedure PrintBalance;
function GetBet: Integer;
function TryAgain: Boolean;
protected
public
constructor Create;
destructor Destroy; override;
procedure Run;
published
end;
implementation
{ TGame }
procedure TGame.PrintGreeting;
begin
WriteLN(' ':26, 'ACEY DUCEY CARD GAME');
WriteLN(' ':15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY');
WriteLN;
WriteLN;
WriteLN('ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ');
WriteLN('THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP');
WriteLN('YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING');
WriteLN('ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE');
WriteLN('A VALUE BETWEEN THE FIRST TWO.');
WriteLN('IF YOU DO NOT WANT TO BET, INPUT A 0');
WriteLN;
end;
procedure TGame.PrintBalance;
begin
WriteLN('YOU NOW HAVE ', FStash,' DOLLARS.');
WriteLN;
end;
function TGame.GetBet: Integer;
begin
Result:= 0;
repeat
Write('WHAT IS YOUR BET: ');
ReadLN(Result);
if Result > FStash then
begin
WriteLn('SORRY, MY FRIEND, BUT YOU BET TOO MUCH.');
WriteLn('YOU HAVE ONLY ', FStash,' DOLLARS TO BET.');
end;
until (Result >=0) and (Result <= FStash);
end;
function TGame.TryAgain: Boolean;
var
answer: String;
begin
Result:= False;
Write('TRY AGAIN (YES OR NO)');
ReadLn(answer);
Result:= (LowerCase(answer)='yes') or (LowerCase(answer)='y');
end;
constructor TGame.Create;
begin
Randomize;
FDeck:= TDeck.Create;
end;
destructor TGame.Destroy;
begin
FDeck.Free;
inherited Destroy;
end;
procedure TGame.Run;
begin
ClrScr;
PrintGreeting;
repeat
FStash:= 100;
repeat
PrintBalance;
FDeck.DrawCards;
//DrawDealerCards;
FDeck.ShowDealerCards;
FBet:= GetBet;
if FBet = 0 then
begin
WriteLN('CHICKEN!!');
continue;
end;
//DrawPlayerCard;
FDeck.ShowPlayerCard;
//if (FCardC > FCardA) and (FCardC < FCardB) then
if FDeck.PlayerWins then
begin
WriteLN('YOU WIN!!!');
Inc(FStash, FBet)
end
else
begin
WriteLN('SORRY, YOU LOSE');
Dec(FStash, FBet)
end;
until FStash = 0;
WriteLN('SORRY, FRIEND, BUT YOU BLEW YOUR WAD.');
WriteLN;
until not TryAgain;
WriteLN('O.K., HOPE YOU HAD FUN!');
end;
end.

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="aceyducey"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<Units Count="1">
<Unit0>
<Filename Value="aceyducey.pas"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="aceyducey"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,152 @@
program aceyducey;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
uses
Crt;
var
Stash: Integer;
CardA: Integer;
CardB: Integer;
CardC: Integer;
Bet: Integer;
procedure PrintGreeting;
begin
WriteLN(' ':26, 'ACEY DUCEY CARD GAME');
WriteLN(' ':15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY');
WriteLN;
WriteLN;
WriteLN('ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER ');
WriteLN('THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP');
WriteLN('YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING');
WriteLN('ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE');
WriteLN('A VALUE BETWEEN THE FIRST TWO.');
WriteLN('IF YOU DO NOT WANT TO BET, INPUT A 0');
WriteLN;
end;
procedure PrintBalance;
begin
WriteLN('YOU NOW HAVE ', Stash,' DOLLARS.');
WriteLN;
end;
procedure PrintCard(const ACard: Integer);
begin
if ACard < 11 then
begin
Write(ACard);
end;
if ACard = 11 then
begin
Write('JACK');
end;
if ACard = 12 then
begin
Write('QUEEN');
end;
if ACard = 13 then
begin
Write('KING');
end;
if ACard = 14 then
begin
Write('ACE');
end;
end;
procedure DrawDealerCards;
var
tmp: Integer;
begin
Write('HERE ARE YOUR NEXT TWO CARDS: ');
repeat
CardA:= Random(14) + 2;
until (CardA >= 2) and (CardA <= 14);
repeat
CardB:= Random(14) + 2;
until (CardB >= 2) and (CardB <= 14) and (CardA <> CardB);
if CardA > CardB then
begin
tmp:= CardB;
CardB:= CardA;
CardA:= tmp;
end;
PrintCard(CardA);
Write(' ');
PrintCard(CardB);
WriteLN;
WriteLN;
end;
procedure DrawPlayerCard;
begin
repeat
CardC:= Random(14) + 2;
until (CardC >= 2) and (CardC <= 14);
PrintCard(CardC);
WriteLN;
WriteLN;
end;
function GetBet: Integer;
begin
Result:= 0;
repeat
Write('WHAT IS YOUR BET: ');
ReadLN(Result);
if Result > Stash then
begin
WriteLn('SORRY, MY FRIEND, BUT YOU BET TOO MUCH.');
WriteLn('YOU HAVE ONLY ', Stash,' DOLLARS TO BET.');
end;
until (Result >=0) and (Result <= Stash);
end;
function TryAgain: Boolean;
var
answer: String;
begin
Result:= False;
Write('TRY AGAIN (YES OR NO)');
ReadLn(answer);
Result:= (LowerCase(answer)='yes') or (LowerCase(answer)='y');
end;
begin
Randomize;
ClrScr;
PrintGreeting;
repeat
Stash:= 100;
repeat
PrintBalance;
DrawDealerCards;
Bet:= GetBet;
if Bet = 0 then
begin
WriteLN('CHICKEN!!');
continue;
end;
DrawPlayerCard;
if (CardC > CardA) and (CardC < CardB) then
begin
WriteLN('YOU WIN!!!');
Inc(Stash, Bet)
end
else
begin
WriteLN('SORRY, YOU LOSE');
Dec(Stash, Bet)
end;
until Stash = 0;
WriteLN('SORRY, FRIEND, BUT YOU BLEW YOUR WAD.');
WriteLN;
until not TryAgain;
WriteLN('O.K., HOPE YOU HAD FUN!');
end.

View File

@@ -1,3 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Raku](https://raku.org/)
Conversion to [Perl](https://www.perl.org/)

157
01_Acey_Ducey/perl/aceyducey.pl Executable file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/perl
use strict;
use warnings;
# The List::Util module is part of the core Perl distribution. Using this
# means we don't need to re-invent the wheel and create a way to shuffle
# a list.
use List::Util qw(shuffle);
# Rather than put in a number of print (or say) statements here, we use a
# "here document". This is very useful for long strings of text. In this
# case, everything between the end of the "print" line and the line with
# "END_INSTRUCTIONS" on it will be printed verbatim.
print << 'END_INSTRUCTIONS';
Acey-Ducey
Adapted from Creative Computing, Morristown, New Jersey
Acey-Ducey is played as follows. The dealer (computer) deals two cards face up.
You have an option to bet or not bet, depending on whether or not you feel that
the next card drawn will have a value between the first two. Aces are low.
Bets must be in whole-dollar amounts only.
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 $keepPlaying = 1;
GAME:
while ($keepPlaying) {
my $playerBalance = 100; # The player starts with $100
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.
my @shuffledDeck = shuffle(@cards);
# Then, by taking the two "top cards" off the deck, we're guaranteed
# 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 ];
print "I drew ", nameOfCard($firstCard), " and ", nameOfCard($secondCard), ".\n";
my $bet = getValidBet($playerBalance);
if ( $bet == 0 ) {
print "Chicken!\n\n";
next HAND;
}
if ( $bet < 0 ) {
last GAME;
}
# Now we re-shuffle the whole deck again and choose a third card.
# (Note: This is how the odds get stacked in favor of the dealer since
# the third card can be exactly the same as the first or second.)
@shuffledDeck = shuffle(@cards);
my $thirdCard = $shuffledDeck[0];
print "I drew ", nameOfCard($thirdCard), "!\n";
if ( ( $firstCard < $thirdCard ) && ( $thirdCard < $secondCard ) ) {
print "You win!\n\n";
$playerBalance += $bet;
}
else {
print "You lose!\n\n";
$playerBalance -= $bet;
}
if ( $playerBalance <= 0 ) {
print "Sorry, buddy, you blew your wad!\n\n";
last HAND;
}
}
$keepPlaying = promptUserToKeepPlaying();
}
print "Thanks for playing!\n";
###############
sub getValidBet {
my $maxBet = shift;
INPUT:
{
print "\nWhat's your bet? ";
chomp( my $input = <STDIN> );
# 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;
}
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 {
my $value = shift;
# Note that the Joker isn't used in this game, but since arrays in Perl are
# 0-based, it's useful to have something there to represent the "0th"
# position. This way the rest of the elements match their expected values
# (e.g., element 1 is Ace, element 7 is 7, and element 12 is Queen).
my @cardlist = qw(Joker Ace 2 3 4 5 6 7 8 9 10 Jack Queen King);
return $cardlist[$value];
}
sub promptUserToKeepPlaying {
YESNO:
{
print "Try again (Y/N)? ";
chomp( my $input = uc <STDIN> );
if ( $input eq 'Y' ) {
return 1;
}
elsif ( $input eq 'N' ) {
return 0;
}
else {
redo YESNO;
}
}
}

View File

@@ -78,7 +78,7 @@ if __name__ == "__main__":
"""
Acey-Ducey is played in the following manner
The dealer (computer) deals two cards face up
You have an option to be or not bet depending
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

View File

@@ -0,0 +1,128 @@
#
# AceyDuchy
#
# From: BASIC Computer Games (1978)
# Edited by David Ahl
#
# "The original BASIC program author was Bill Palmby
# of Prairie View, Illinois."
#
# Python port by Aviyam Fischer, 2022
#
######################################################
class Card:
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __str__(self):
r = self.rank
if r == 11:
r = 'J'
elif r == 12:
r = 'Q'
elif r == 13:
r = 'K'
elif r == 14:
r = 'A'
return f'{r}{self.suit}'
class Deck:
def __init__(self):
self.cards = []
self.build()
def build(self):
for suit in ['\u2665', '\u2666', '\u2663', '\u2660']:
for rank in range(2, 15):
self.cards.append(Card(suit, rank))
def shuffle(self):
import random
random.shuffle(self.cards)
def deal(self):
return self.cards.pop()
class Game:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
self.card_a = self.deck.deal()
self.card_b = self.deck.deal()
self.money = 100
self.not_done = True
def play(self):
while self.not_done:
while self.money > 0:
card_a = self.card_a
card_b = self.card_b
if card_a.rank > card_b.rank:
card_a, card_b = card_b, card_a
if card_a.rank == card_b.rank:
self.card_b = self.deck.deal()
card_b = self.card_b
print(f'You have:\t ${self.money} ')
print(f'Your cards:\t {card_a} {card_b}')
bet = int(input('What is your bet? '))
player_card = self.deck.deal()
if 0 < bet <= self.money:
print(f'Your deal:\t {player_card}')
if card_a.rank < player_card.rank < card_b.rank:
print('You Win!')
self.money += bet
else:
print('You Lose!')
self.money -= bet
self.not_done = False
else:
print('Chicken!')
print(f'Your deal should have been: {player_card}')
if card_a.rank < player_card.rank < card_b.rank:
print(f'You could have won!')
else:
print(f'You would lose, so it was wise of you to chicken out!')
self.not_done = False
break
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
if __name__ == '__main__':
print('''
Acey Ducey is a card game where you play against the computer.
The Dealer(computer) will deal two cards facing 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
''')
GAME_OVER = False
while not GAME_OVER:
game = Game()
game.play()
print(f'You have ${game.money} left')
print('Would you like to play again? (y/n)')
if input() == 'n':
GAME_OVER = True
print('\nThanks for playing!')

View File

@@ -174,7 +174,7 @@ print("OK Hope you had fun\n")
#
# Give the user the ability to quit the game, perhaps
# by typing "quit" instead of making a bet. Provide a
# final assement based on how much of the original
# final assessment based on how much of the original
# bankroll they have left.
#
# Or have the game run for a set number of rounds or

View File

@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Ruby](https://www.ruby-lang.org/en/) by Christopher Özbek [coezbek@github](https://github.com/coezbek).

View File

@@ -0,0 +1,139 @@
########################################################
#
# Acey Ducey
#
# From: BASIC Computer Games (1978)
# Edited by David Ahl
#
# "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. The game keeps
# going on until you lose all your money or interrupt
# the program.
#
# "The original BASIC program author was Bill Palmby
# of Prairie View, Illinois."
#
# Ruby port by Christopher Oezbek, 2021
#
# This uses the following techniques:
#
# - The programm largely consists of a GAME LOOP,
# which is used to represent one round.
# - The Kernel function rand(Range) is used to get an
# Integer in the (inclusive) Range of 2 to 14.
# - To ensure the user enters a proper Integer
# via the console, an inline 'rescue' statement is
# used.
# - To capture the long text in the introduction, a
# squiggly HEREDOC string declaration <<~ is used.
#
#
########################################################
puts <<~INSTRUCTIONS
🂡 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 IN A ROUND, ENTER 0
INSTRUCTIONS
# The player starts with 100$
stake = 100
while true # Game loop
puts
puts "YOU NOW HAVE #{stake} DOLLARS."
puts
puts "HERE ARE YOUR NEXT TWO CARDS:"
# Randomly draw two cards and make sure the first card is lower in value than the second
# Using array destructuring, this sorted array can be assigned to `first_card` and `second_card`
first_card, second_card = (2...14).to_a.shuffle.pop(2).sort
# Helper method to convert a numeric card into a String for printing
def card_name(card)
case card
when 11
"JACK"
when 12
"QUEEN"
when 13
"KING"
when 14
"ACE"
else
card
end
end
puts card_name(first_card)
puts card_name(second_card)
puts
puts
# Loop until the user enters something sensible
while true
puts "WHAT IS YOUR BET? ENTER 0 IF YOU DON'T WANT TO BET (CTRL+C TO EXIT)"
your_bet = Integer(gets.chomp) rescue nil
if your_bet == nil || your_bet < 0
puts "PLEASE ENTER A POSITIVE NUMBER"
puts
next
end
if your_bet > stake
puts "SORRY, MY FRIEND, BUT YOU BET TOO MUCH."
puts "YOU HAVE ONLY #{stake} DOLLARS TO BET."
puts
next
end
break
end
if your_bet == 0
puts "CHICKEN!!"
next
end
puts "THANK YOU! YOUR BET IS #{your_bet} DOLLARS."
puts ""
puts "THE THIRD CARD IS:"
third_card = rand(2..14)
puts card_name(third_card)
puts
if first_card <= third_card && third_card <= second_card
puts "YOU WIN!!!"
stake += your_bet
else
puts "SORRY, YOU LOSE"
stake -= your_bet
end
if stake == 0
puts
puts "SORRY, FRIEND, BUT YOU BLEW YOUR WAD."
puts
puts "TRY AGAIN? (YES OR NO)"
if gets.chomp.downcase.start_with? 'y'
# Reset cash to 100
stake = 100
else
puts "O.K., HOPE YOU HAD FUN!"
exit
end
end
end

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "AceyDucy", "AceyDucy\AceyDucy.vbproj", "{37496710-B458-4502-ADCB-4C57203866F9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37496710-B458-4502-ADCB-4C57203866F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37496710-B458-4502-ADCB-4C57203866F9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C01D9DAE-644C-455F-8365-E14E49074BC3}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>AceyDucy</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,178 @@
Imports System
''' <summary>
''' This is a modern adapation of Acey Ducey from BASIC Computer Games.
'''
''' The structural changes primarily consist of replacing the many GOTOs with
''' Do/Loop constructs to force the continual execution of the program.
'''
''' Because modern Basic allows multi-line If/Then blocks, many GOTO jumps were
''' able to be eliminated and the logic was able to be moved to more relevant areas,
''' For example, the increment/decrement of the player's balance could be in the same
''' area as the notification of win/loss.
'''
''' Some modern improvements were added, primarily the inclusion of a function, which
''' eliminated a thrice-repeated block of logic to display the card value. The archaic
''' RND function is greatly simplified with the .NET Framework's Random class.
'''
''' Elementary comments are provided for non-programmers or novices.
''' </summary>
Module Program
Sub Main(args As String())
' These are the variables that will hold values during the program's execution
Dim input As String
Dim rnd As New Random ' You can create a new instance of an object during declaration
Dim currentBalance As Integer = 100 ' You can set a initial value at declaration
Dim currentWager As Integer
Dim cardA, cardB, cardC As Integer ' You can specify multiple variables of the same type in one declaration statement
' Display the opening title and instructions
' Use a preceding $ to insert calculated values within the string using {}
Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 10)}ACEY DUCEY CARD GAME")
Console.WriteLine($"{Space((Console.WindowWidth \ 2) - 21)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
Console.WriteLine("")
Console.WriteLine("")
Console.WriteLine("ACEY-DUCEY IS PLAYED IN THE FOLLOWING MANNER")
Console.WriteLine("THE DEALER (COMPUTER) DEALS TWO CARDS FACE UP")
Console.WriteLine("YOU HAVE AN OPTION TO BET OR NOT BET DEPENDING")
Console.WriteLine("ON WHETHER OR NOT YOU FEEL THE CARD WILL HAVE")
Console.WriteLine("A VALUE BETWEEN THE FIRST TWO.")
Console.WriteLine("IF YOU DO NOT WANT TO BET, INPUT A 0")
Do ' This loop continues as long as the player wants to keep playing
Do ' This loop continues as long as the player has money to play
Console.WriteLine("")
Console.WriteLine($"YOU NOW HAVE {currentBalance} DOLLARS.")
Console.WriteLine("")
Console.WriteLine("HERE ARE YOUR NEXT TWO CARDS:")
' We need to ensure that card B is a higher value for our later comparison,
' so we will loop until we have two cards that meet this criteria
Do
cardA = rnd.Next(2, 14)
cardB = rnd.Next(2, 14)
Loop While cardA > cardB
' We use a function to display the text value of the numeric card value
' because we do this 3 times and a function reduces repetition of code
Console.WriteLine(DisplayCard(cardA))
Console.WriteLine(DisplayCard(cardB))
Do ' This loop continues until the player provides a valid wager value
Console.WriteLine("")
Console.WriteLine("WHAT IS YOUR BET")
currentWager = 0
input = Console.ReadLine
' Any input from the console is a string, but we require a number.
' Test the input to make sure it is a numeric value.
If Integer.TryParse(input, currentWager) Then
' Test to ensure the player has not wagered more than their balance
If currentWager > currentBalance Then
Console.WriteLine("SORRY, MY FRIEND, BUT YOU BET TOO MUCH.")
Console.WriteLine($"YOU HAVE ONLY {currentBalance} DOLLARS TO BET.")
Else
' The player has provided a numeric value that is less/equal to their balance,
' exit the loop and continue play
Exit Do
End If ' check player balance
End If ' check numeric input
Loop ' wager loop
' If the player is wagering, draw the third card, otherwise, mock them.
If currentWager > 0 Then
cardC = rnd.Next(2, 14)
Console.WriteLine(DisplayCard(cardC))
' The effort we made to have two cards in numeric order earlier makes this check easier,
' otherwise we would have to have a second check in the opposite direction
If cardC < cardA OrElse cardC >= cardB Then
Console.WriteLine("SORRY, YOU LOSE")
currentBalance -= currentWager ' Shorthand code to decrement a number (currentBalance=currentBalance - currentWager)
Else
Console.WriteLine("YOU WIN!!!")
currentBalance += currentWager ' Shorthand code to increment a number (currentBalance=currentBalance + currentWager)
End If
Else
Console.WriteLine("CHICKEN!!")
Console.WriteLine("")
End If
Loop While currentBalance > 0 ' loop as long as the player has money
' At this point, the player has no money (currentBalance=0). Inform them of such.
Console.WriteLine("")
Console.WriteLine("SORRY, FRIEND, BUT YOU BLEW YOUR WAD.")
Console.WriteLine("")
Console.WriteLine("")
' We will loop to ensure the player provides some answer.
Do
Console.WriteLine("TRY AGAIN (YES OR NO)")
Console.WriteLine("")
input = Console.ReadLine
Loop While String.IsNullOrWhiteSpace(input)
' We will assume that the player wants to play again only if they answer yes.
' (yeah and ya are valid as well, because we only check the first letter)
If input.Substring(0, 1).Equals("y", StringComparison.CurrentCultureIgnoreCase) Then ' This allows upper and lower case to be entered.
currentBalance = 100 ' Reset the players balance before restarting
Else
' Exit the outer loop which will end the game.
Exit Do
End If
Loop ' The full game loop
Console.WriteLine("O.K., HOPE YOU HAD FUN!")
End Sub
' This function is called for each of the 3 cards used in the game.
' The input and the output are both consistent, making it a good candidate for a function.
Private Function DisplayCard(value As Integer) As String
' We check the value of the input and run a block of code for whichever
' evaluation matches
Select Case value
Case 2 To 10 ' Case statements can be ranges of values, also multiple values (Case 2,3,4,5,6,7,8,9,10)
Return value.ToString
Case 11
Return "JACK"
Case 12
Return "QUEEN"
Case 13
Return "KING"
Case 14
Return "ACE"
End Select
' Although we have full knowledge of the program and never plan to send an invalid
' card value, it's important to provide a message for the next developer who won't
Throw New ArgumentOutOfRangeException("Card value must be between 2 and 14")
End Function
End Module

View File

@@ -1,7 +0,0 @@
### Amazing
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=3
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html

18
02_Amazing/README.md Normal file
View File

@@ -0,0 +1,18 @@
### Amazing
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.

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";

View File

@@ -0,0 +1,320 @@
using System;
using System.Collections.Generic;
namespace Amazing
{
class AmazingGame
{
private const int FIRST_COL = 0;
private const int FIRST_ROW = 0;
private const int EXIT_UNSET = 0;
private const int EXIT_DOWN = 1;
private const int EXIT_RIGHT = 2;
private static int GetDelimitedValue(String text, int pos)
{
String[] tokens = text.Split(",");
int val;
if (Int32.TryParse(tokens[pos], out val))
{
return val;
}
return 0;
}
private static String Tab(int spaces)
{
return new String(' ', spaces);
}
public static int Random(int min, int max)
{
Random random = new Random();
return random.Next(max - min) + min;
}
public void Play()
{
Console.WriteLine(Tab(28) + "AMAZING PROGRAM");
Console.WriteLine(Tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
Console.WriteLine();
int width = 0;
int length = 0;
do
{
String range = DisplayTextAndGetInput("WHAT ARE YOUR WIDTH AND LENGTH");
if (range.IndexOf(",") > 0)
{
width = GetDelimitedValue(range, 0);
length = GetDelimitedValue(range, 1);
}
}
while (width < 1 || length < 1);
Grid grid = new Grid(length, width);
int enterCol = grid.SetupEntrance();
int totalWalls = width * length + 1;
int count = 2;
Cell cell = grid.StartingCell();
while (count != totalWalls)
{
List<Direction> possibleDirs = GetPossibleDirs(grid, cell);
if (possibleDirs.Count != 0)
{
cell = SetCellExit(grid, cell, possibleDirs);
cell.Count = count++;
}
else
{
cell = grid.GetFirstUnset(cell);
}
}
grid.SetupExit();
WriteMaze(width, grid, enterCol);
}
private Cell SetCellExit(Grid grid, Cell cell, List<Direction> possibleDirs)
{
Direction direction = possibleDirs[Random(0, possibleDirs.Count)];
if (direction == Direction.GO_LEFT)
{
cell = grid.GetPrevCol(cell);
cell.ExitType = EXIT_RIGHT;
}
else if (direction == Direction.GO_UP)
{
cell = grid.GetPrevRow(cell);
cell.ExitType = EXIT_DOWN;
}
else if (direction == Direction.GO_RIGHT)
{
cell.ExitType = cell.ExitType + EXIT_RIGHT;
cell = grid.GetNextCol(cell);
}
else if (direction == Direction.GO_DOWN)
{
cell.ExitType = cell.ExitType + EXIT_DOWN;
cell = grid.GetNextRow(cell);
}
return cell;
}
private void WriteMaze(int width, Grid grid, int enterCol)
{
// top line
for (int i = 0; i < width; i++)
{
if (i == enterCol) Console.Write(". ");
else Console.Write(".--");
}
Console.WriteLine(".");
for (int i = 0; i < grid.Length; i++)
{
Console.Write("I");
for (int j = 0; j < grid.Width; j++)
{
if (grid.Cells[i,j].ExitType == EXIT_UNSET || grid.Cells[i, j].ExitType == EXIT_DOWN)
Console.Write(" I");
else Console.Write(" ");
}
Console.WriteLine();
for (int j = 0; j < grid.Width; j++)
{
if (grid.Cells[i,j].ExitType == EXIT_UNSET || grid.Cells[i, j].ExitType == EXIT_RIGHT)
Console.Write(":--");
else Console.Write(": ");
}
Console.WriteLine(".");
}
}
private List<Direction> GetPossibleDirs(Grid grid, Cell cell)
{
var possibleDirs = new List<Direction>();
foreach (var val in Enum.GetValues(typeof(Direction)))
{
possibleDirs.Add((Direction)val);
}
if (cell.Col == FIRST_COL || grid.IsPrevColSet(cell))
{
possibleDirs.Remove(Direction.GO_LEFT);
}
if (cell.Row == FIRST_ROW || grid.IsPrevRowSet(cell))
{
possibleDirs.Remove(Direction.GO_UP);
}
if (cell.Col == grid.LastCol || grid.IsNextColSet(cell))
{
possibleDirs.Remove(Direction.GO_RIGHT);
}
if (cell.Row == grid.LastRow || grid.IsNextRowSet(cell))
{
possibleDirs.Remove(Direction.GO_DOWN);
}
return possibleDirs;
}
private String DisplayTextAndGetInput(String text)
{
Console.WriteLine(text);
return Console.ReadLine();
}
private enum Direction
{
GO_LEFT,
GO_UP,
GO_RIGHT,
GO_DOWN,
}
public class Cell
{
public int ExitType { get; set; }
public int Count { get; set; }
public int Col { get; set; }
public int Row { get; set; }
public Cell(int row, int col)
{
ExitType = EXIT_UNSET;
Row = row;
Col = col;
}
}
public class Grid
{
public Cell[,] Cells { get; private set; }
public int LastCol { get; set; }
public int LastRow { get; set; }
public int Width { get; private set; }
public int Length { get; private set; }
private int enterCol;
public Grid(int length, int width)
{
LastCol = width - 1;
LastRow = length - 1;
Width = width;
Length = length;
Cells = new Cell[length,width];
for (int i = 0; i < length; i++)
{
for (int j = 0; j < width; j++)
{
this.Cells[i,j] = new Cell(i, j);
}
}
}
public int SetupEntrance()
{
this.enterCol = Random(0, Width);
Cells[0, enterCol].Count = 1;
return this.enterCol;
}
public void SetupExit()
{
int exit = Random(0, Width - 1);
Cells[LastRow, exit].ExitType += 1;
}
public Cell StartingCell()
{
return Cells[0, enterCol];
}
public bool IsPrevColSet(Cell cell)
{
return 0 != Cells[cell.Row, cell.Col - 1].Count;
}
public bool IsPrevRowSet(Cell cell)
{
return 0 != Cells[cell.Row - 1, cell.Col].Count;
}
public bool IsNextColSet(Cell cell)
{
return 0 != Cells[cell.Row, cell.Col + 1].Count;
}
public bool IsNextRowSet(Cell cell)
{
return 0 != Cells[cell.Row + 1, cell.Col].Count;
}
public Cell GetPrevCol(Cell cell)
{
return Cells[cell.Row, cell.Col - 1];
}
public Cell GetPrevRow(Cell cell)
{
return Cells[cell.Row - 1, cell.Col];
}
public Cell GetNextCol(Cell cell)
{
return Cells[cell.Row, cell.Col + 1];
}
public Cell GetNextRow(Cell cell)
{
return Cells[cell.Row + 1, cell.Col];
}
public Cell GetFirstUnset(Cell cell)
{
int col = cell.Col;
int row = cell.Row;
Cell newCell;
do
{
if (col != this.LastCol)
{
col++;
}
else if (row != this.LastRow)
{
row++;
col = 0;
}
else
{
row = 0;
col = 0;
}
}
while ((newCell = Cells[row, col]).Count == 0);
return newCell;
}
}
}
class Program
{
static void Main(string[] args)
{
new AmazingGame().Play();
}
}
}

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.808.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazing", "Amazing.csproj", "{DD3483B4-F366-493C-8BFE-BAFBFE6F3016}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DD3483B4-F366-493C-8BFE-BAFBFE6F3016}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD3483B4-F366-493C-8BFE-BAFBFE6F3016}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD3483B4-F366-493C-8BFE-BAFBFE6F3016}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD3483B4-F366-493C-8BFE-BAFBFE6F3016}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F8762606-E467-47C1-A28F-BD5C4F7BFF5A}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,262 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
import static java.lang.System.in;
import static java.lang.System.out;
/**
* Core algorithm copied from amazing.py
*/
public class Amazing {
final static int FIRST_COL = 0;
final static int FIRST_ROW = 0;
final static int EXIT_UNSET = 0;
final static int EXIT_DOWN = 1;
final static int EXIT_RIGHT = 2;
private final Scanner kbScanner;
public Amazing() {
kbScanner = new Scanner(in);
}
private static int getDelimitedValue(String text, int pos) {
String[] tokens = text.split(",");
try {
return Integer.parseInt(tokens[pos]);
} catch (Exception ex) {
return 0;
}
}
private static String tab(int spaces) {
char[] spacesTemp = new char[spaces];
Arrays.fill(spacesTemp, ' ');
return new String(spacesTemp);
}
public static int random(int min, int max) {
Random random = new Random();
return random.nextInt(max - min) + min;
}
public void play() {
out.println(tab(28) + "AMAZING PROGRAM");
out.println(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
out.println();
int width = 0;
int length = 0;
do {
String range = displayTextAndGetInput("WHAT ARE YOUR WIDTH AND LENGTH");
if (range.indexOf(",") > 0) {
width = getDelimitedValue(range, 0);
length = getDelimitedValue(range, 1);
}
} while (width < 1 || length < 1);
Grid grid = new Grid(length, width);
int enterCol = grid.setupEntrance();
int totalWalls = width * length + 1;
int count = 2;
Cell cell = grid.startingCell();
while (count != totalWalls) {
ArrayList<Direction> possibleDirs = getPossibleDirs(grid, cell);
if (possibleDirs.size() != 0) {
cell = setCellExit(grid, cell, possibleDirs);
cell.count = count++;
} else {
cell = grid.getFirstUnset(cell);
}
}
grid.setupExit();
writeMaze(width, grid, enterCol);
}
private Cell setCellExit(Grid grid, Cell cell, ArrayList<Direction> possibleDirs) {
Direction direction = possibleDirs.get(random(0, possibleDirs.size()));
if (direction == Direction.GO_LEFT) {
cell = grid.getPrevCol(cell);
cell.exitType = EXIT_RIGHT;
} else if (direction == Direction.GO_UP) {
cell = grid.getPrevRow(cell);
cell.exitType = EXIT_DOWN;
} else if (direction == Direction.GO_RIGHT) {
cell.exitType = cell.exitType + EXIT_RIGHT;
cell = grid.getNextCol(cell);
} else if (direction == Direction.GO_DOWN) {
cell.exitType = cell.exitType + EXIT_DOWN;
cell = grid.getNextRow(cell);
}
return cell;
}
private void writeMaze(int width, Grid grid, int enterCol) {
// top line
for (int i = 0; i < width; i++) {
if (i == enterCol) {
out.print(". ");
} else {
out.print(".--");
}
}
out.println('.');
for (Cell[] rows : grid.cells) {
out.print("I");
for (Cell cell : rows) {
if (cell.exitType == EXIT_UNSET || cell.exitType == EXIT_DOWN) {
out.print(" I");
} else {
out.print(" ");
}
}
out.println();
for (Cell cell : rows) {
if (cell.exitType == EXIT_UNSET || cell.exitType == EXIT_RIGHT) {
out.print(":--");
} else {
out.print(": ");
}
}
out.println(".");
}
}
private ArrayList<Direction> getPossibleDirs(Grid grid, Cell cell) {
ArrayList<Direction> possibleDirs = new ArrayList<>(Arrays.asList(Direction.values()));
if (cell.col == FIRST_COL || grid.isPrevColSet(cell)) {
possibleDirs.remove(Direction.GO_LEFT);
}
if (cell.row == FIRST_ROW || grid.isPrevRowSet(cell)) {
possibleDirs.remove(Direction.GO_UP);
}
if (cell.col == grid.lastCol || grid.isNextColSet(cell)) {
possibleDirs.remove(Direction.GO_RIGHT);
}
if (cell.row == grid.lastRow || grid.isNextRowSet(cell)) {
possibleDirs.remove(Direction.GO_DOWN);
}
return possibleDirs;
}
private String displayTextAndGetInput(String text) {
out.print(text);
return kbScanner.next();
}
enum Direction {
GO_LEFT,
GO_UP,
GO_RIGHT,
GO_DOWN,
}
public static class Cell {
int exitType = EXIT_UNSET;
int count = 0;
int col;
int row;
public Cell(int row, int col) {
this.row = row;
this.col = col;
}
}
public static class Grid {
Cell[][] cells;
int lastCol;
int lastRow;
int width;
int enterCol;
public Grid(int length, int width) {
this.lastCol = width - 1;
this.lastRow = length - 1;
this.width = width;
this.cells = new Cell[length][width];
for (int i = 0; i < length; i++) {
this.cells[i] = new Cell[width];
for (int j = 0; j < width; j++) {
this.cells[i][j] = new Cell(i, j);
}
}
}
public int setupEntrance() {
this.enterCol = random(0, this.width);
cells[0][this.enterCol].count = 1;
return this.enterCol;
}
public void setupExit() {
int exit = random(0, width - 1);
cells[lastRow][exit].exitType += 1;
}
public Cell startingCell() {
return cells[0][enterCol];
}
public boolean isPrevColSet(Cell cell) {
return 0 != cells[cell.row][cell.col - 1].count;
}
public boolean isPrevRowSet(Cell cell) {
return 0 != cells[cell.row - 1][cell.col].count;
}
public boolean isNextColSet(Cell cell) {
return 0 != cells[cell.row][cell.col + 1].count;
}
public boolean isNextRowSet(Cell cell) {
return 0 != cells[cell.row + 1][cell.col].count;
}
public Cell getPrevCol(Cell cell) {
return cells[cell.row][cell.col - 1];
}
public Cell getPrevRow(Cell cell) {
return cells[cell.row - 1][cell.col];
}
public Cell getNextCol(Cell cell) {
return cells[cell.row][cell.col + 1];
}
public Cell getNextRow(Cell cell) {
return cells[cell.row + 1][cell.col];
}
public Cell getFirstUnset(Cell cell) {
int col = cell.col;
int row = cell.row;
Cell newCell;
do {
if (col != this.lastCol) {
col++;
} else if (row != this.lastRow) {
row++;
col = 0;
} else {
row = 0;
col = 0;
}
} while ((newCell = cells[row][col]).count == 0);
return newCell;
}
}
}

View File

@@ -0,0 +1,6 @@
public class AmazingGame {
public static void main(String[] args) {
Amazing amazing = new Amazing();
amazing.play();
}
}

36
02_Amazing/pascal/.gitattributes vendored Normal file
View File

@@ -0,0 +1,36 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.inc text
*.pas text
*.pp text
*.lpk text
*.lpi text
*.lps text
*.lpr text
*.def text
*.css text
*.html text
*.xml text
*.sql text
# Declare files that will always have CRLF line endings on checkout.
*.dpk text eol=crlf
*.dproj text eol=crlf
# Declare files that will always have LF line endings on checkout.
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.exe binary
*.res binary
*.ico binary
*.dll binary
# Keep these files from archive/exports, mainly from production.
.gitignore export-ignore
.gitattributes export-ignore

63
02_Amazing/pascal/.gitignore vendored Normal file
View File

@@ -0,0 +1,63 @@
# Basic Computer Programs project specific
amazing
amazing.exe
# Compiled l10n files: .mo should be ignored
*.mo
# Ghostwriter backups
*.backup
# nano editor backup files
*.swp
# Uncomment these types if you want even more clean repository. But be careful.
# It can make harm to an existing project source. Read explanations below.
#
# Resource files are binaries containing manifest, project icon and version info.
# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.
*.res
# Delphi/Lazarus compiler-generated binaries (safe to delete)
*.exe
*.dll
*.bpl
*.bpi
*.dcp
*.so
*.apk
*.drc
*.map
*.dres
*.rsm
*.tds
*.dcu
*.lib
*.[ao]
*.or
*.ppu
*.dbg
*.compiled
# Delphi autogenerated files (duplicated info)
*.cfg
*Resource.rc
# Delphi local files (user-specific info)
*.local
*.identcache
*.projdata
*.tvsconfig
*.dsk
# Delphi history and backups
__history/
*.~*
# Lazarus history, backups and session
backup/
*.bak
*.lps
# Castalia statistics file
*.stat

View File

@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language)) by Gustavo Carreno [gcarreno@github](https://github.com/gcarreno)

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="12"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<Title Value="amazing"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
</RunParams>
<Units Count="4">
<Unit0>
<Filename Value="amazing.pas"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="amazingapplication.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="AmazingApplication"/>
</Unit1>
<Unit2>
<Filename Value="maze.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="Maze"/>
</Unit2>
<Unit3>
<Filename Value="room.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="Room"/>
</Unit3>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="amazing"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,17 @@
program amazing;
{$IFDEF FPC}
{$mode ObjFPC}{$H+}
{$ENDIF}
uses
AmazingApplication, maze, Room;
var
AmazingApp: TAmazingApplication;
begin
AmazingApp:= TAmazingApplication.Create;
AmazingApp.Run;
end.

View File

@@ -0,0 +1,104 @@
unit AmazingApplication;
{$IFDEF FPC}
{$mode ObjFPC}{$H+}
{$ENDIF}
interface
uses
Classes
, SysUtils
, Crt
, Maze
;
type
{ TAmazingApplication }
TAmazingApplication = class(TObject)
private
FMaze: TMaze;
procedure PrintGreeting;
procedure GetDimensions;
procedure BuildMaze;
procedure PrintMaze;
protected
public
constructor Create;
destructor Destroy; override;
procedure Run;
published
end;
implementation
{ TAmazingApplication }
procedure TAmazingApplication.PrintGreeting;
begin
WriteLN(' ':28, 'AMAZING PROGRAM');
WriteLN(' ':15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY');
WriteLN;
WriteLN;
WriteLN;
WriteLN;
end;
procedure TAmazingApplication.GetDimensions;
var
width: Integer;
length: Integer;
begin
repeat
Write('WHAT ARE YOUR WIDTH AND LENGTH (SPACE IN BETWEEN): ');
ReadLN(width, length);
if (width = 1) or (length = 1) then
begin
WriteLN('MEANINGLESS DIMENSIONS. TRY AGAIN.');
end;
until (width > 1) and (length > 1);
FMaze:= TMaze.Create(width, length);
WriteLN;
WriteLN;
WriteLN;
WriteLN;
end;
procedure TAmazingApplication.BuildMaze;
begin
FMaze.Build;
end;
procedure TAmazingApplication.PrintMaze;
begin
FMaze.Print;
WriteLN;
end;
constructor TAmazingApplication.Create;
begin
//
end;
destructor TAmazingApplication.Destroy;
begin
if Assigned(FMaze) then
begin
FMaze.Free;
end;
inherited Destroy;
end;
procedure TAmazingApplication.Run;
begin
//ClrScr;
PrintGreeting;
GetDimensions;
BuildMaze;
PrintMaze;
end;
end.

View File

@@ -0,0 +1,280 @@
unit Maze;
{$IFDEF FPC}
{$mode ObjFPC}{$H+}
{$ENDIF}
interface
uses
Classes
, SysUtils
, Room
;
type
TDirection = (dUp, dRight, dDown, dLeft);
TDirections = set of TDirection;
{ TMaze }
TMaze = class(TObject)
private
FWidth: Integer;
FLength: Integer;
FEntry: Integer;
FLabyrinth: Array of Array of TRoom;
function GetRandomDirection(const ADirections: TDirections): TDirection;
procedure DebugVisited;
procedure DebugWalls;
protected
public
constructor Create(const AWidth, ALength: Integer);
destructor Destroy; override;
procedure Build;
procedure Print;
published
end;
implementation
const
EXIT_DOWN = 1;
EXIT_RIGHT = 2;
{ TMaze }
function TMaze.GetRandomDirection(const ADirections: TDirections): TDirection;
var
count: Integer;
position: Integer;
directions: array [0..3] of TDirection;
begin
count:= 0;
position:= 0;
if dUp in ADirections then
begin
Inc(count);
directions[position]:= dUp;
Inc(position);
end;
if dRight in ADirections then
begin
Inc(count);
directions[position]:= dRight;
Inc(position);
end;
if dDown in ADirections then
begin
Inc(count);
directions[position]:= dDown;
Inc(position);
end;
if dLeft in ADirections then
begin
Inc(count);
directions[position]:= dLeft;
Inc(position);
end;
Result:= directions[Random(count)];
end;
procedure TMaze.DebugVisited;
var
indexW: Integer;
indexL: Integer;
begin
WriteLN('Visited');
for indexL:= 0 to Pred(FLength) do
begin
for indexW:= 0 to Pred(FWidth) do
begin
Write(FLabyrinth[indexW][indexL].Visited:3,' ');
end;
WriteLN;
end;
WriteLN;
end;
procedure TMaze.DebugWalls;
var
indexW: Integer;
indexL: Integer;
begin
WriteLN('Walls');
for indexL:= 0 to Pred(FLength) do
begin
for indexW:= 0 to Pred(FWidth) do
begin
Write(FLabyrinth[indexW][indexL].Walls:3,' ');
end;
WriteLN;
end;
WriteLN;
end;
constructor TMaze.Create(const AWidth, ALength: Integer);
var
indexW: Integer;
indexL: Integer;
begin
Randomize;
FWidth:= AWidth;
FLength:= ALength;
FEntry:= Random(FWidth);
SetLength(FLabyrinth, FWidth, FLength);
for indexW:= 0 to Pred(FWidth) do
begin
for indexL:= 0 to Pred(FLength) do
begin
FLabyrinth[indexW][indexL]:= TRoom.Create;
end;
end;
end;
destructor TMaze.Destroy;
var
indexW: Integer;
indexL: Integer;
begin
for indexW:= 0 to Pred(FWidth) do
begin
for indexL:= 0 to Pred(FLength) do
begin
if Assigned(FLabyrinth[indexW][indexL]) then
begin
FLabyrinth[indexW][indexL].Free;
end;
end;
end;
inherited Destroy;
end;
procedure TMaze.Build;
var
indexW: Integer;
indexL: Integer;
direction: TDirection;
directions: TDirections;
count: Integer;
begin
FEntry:= Random(FWidth);
indexW:= FEntry;
indexL:= 0;
count:= 1;
FLabyrinth[indexW][indexL].Visited:= count;
Inc(count);
repeat
directions:= [dUp, dRight, dDown, dLeft];
if (indexW = 0) or (FLabyrinth[Pred(indexW)][indexL].Visited <> 0) then
begin
Exclude(directions, dLeft);
end;
if (indexL = 0) or (FLabyrinth[indexW][Pred(indexL)].Visited <> 0) then
begin
Exclude(directions, dUp);
end;
if (indexW = Pred(FWidth)) or (FLabyrinth[Succ(indexW)][indexL].Visited <> 0) then
begin
Exclude(directions, dRight);
end;
if (indexL = Pred(FLength)) or (FLabyrinth[indexW][Succ(indexL)].Visited <> 0) then
begin
Exclude(directions, dDown);
end;
if directions <> [] then
begin
direction:= GetRandomDirection(directions);
case direction of
dLeft:begin
Dec(indexW);
FLabyrinth[indexW][indexL].Walls:= EXIT_RIGHT;
end;
dUp:begin
Dec(indexL);
FLabyrinth[indexW][indexL].Walls:= EXIT_DOWN;
end;
dRight:begin
FLabyrinth[indexW][indexL].Walls:= FLabyrinth[indexW][indexL].Walls + EXIT_RIGHT;
Inc(indexW);
end;
dDown:begin
FLabyrinth[indexW][indexL].Walls:= FLabyrinth[indexW][indexL].Walls + EXIT_DOWN;
Inc(indexL);
end;
end;
FLabyrinth[indexW][indexL].Visited:= count;
Inc(count);
end
else
begin
while True do
begin
if indexW <> Pred(FWidth) then
begin
Inc(indexW);
end
else if indexL <> Pred(FLength) then
begin
Inc(indexL);
indexW:= 0;
end
else
begin
indexW:= 0;
indexL:= 0;
end;
if FLabyrinth[indexW][indexL].Visited <> 0 then
begin
break;
end;
end;
end;
until count = (FWidth * FLength) + 1;
indexW:= Random(FWidth);
indexL:= Pred(FLength);
FLabyrinth[indexW][indexL].Walls:= FLabyrinth[indexW][indexL].Walls + 1;
end;
procedure TMaze.Print;
var
indexW:Integer;
indexL: Integer;
begin
//DebugVisited;
//DebugWalls;
for indexW:= 0 to Pred(FWidth) do
begin
if indexW = FEntry then
begin
Write('. ');
end
else
begin
Write('.--');
end;
end;
WriteLN('.');
for indexL:= 0 to Pred(FLength) do
begin
Write('I');
for indexW:= 0 to Pred(FWidth) do
begin
FLabyrinth[indexW][indexL].PrintRoom;
end;
WriteLN;
for indexW:= 0 to Pred(FWidth) do
begin
FLabyrinth[indexW][indexL].PrintWall;
end;
WriteLN('.');
end;
end;
end.

View File

@@ -0,0 +1,71 @@
unit Room;
{$IFDEF FPC}
{$mode ObjFPC}{$H+}
{$ENDIF}
interface
uses
Classes
, SysUtils
;
type
{ TRoom }
TRoom = class(TObject)
private
FVisited: Integer;
FWalls: Integer;
protected
public
constructor Create;
procedure PrintRoom;
procedure PrintWall;
property Visited: Integer
read FVisited
write FVisited;
property Walls: Integer
read FWalls
write FWalls;
published
end;
implementation
{ TRoom }
constructor TRoom.Create;
begin
FVisited:= 0;
FWalls:= 0;
end;
procedure TRoom.PrintRoom;
begin
if FWalls < 2 then
begin
Write(' I');
end
else
begin
Write(' ');
end;
end;
procedure TRoom.PrintWall;
begin
if (FWalls = 0) or (FWalls = 2) then
begin
Write(':--');
end
else
begin
Write(': ');
end;
end;
end.

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="12"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
<CompatibilityMode Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<Title Value="amazing"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<UseFileFilters Value="True"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
</RunParams>
<Units Count="1">
<Unit0>
<Filename Value="amazing.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="Amazing"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<Target>
<Filename Value="amazing"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@@ -0,0 +1,284 @@
program Amazing;
{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}
uses
Crt;
type
TDirection = (dUp, dRight, dDown, dLeft);
TDirections = set of TDirection;
var
Width: Integer; // H
Length: Integer; // V
Entry: Integer;
MatrixWalls: Array of Array of Integer;
MatrixVisited: Array of Array of Integer;
const
EXIT_DOWN = 1;
EXIT_RIGHT = 2;
procedure PrintGreeting;
begin
WriteLN(' ':28, 'AMAZING PROGRAM');
WriteLN(' ':15, 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY');
WriteLN;
WriteLN;
WriteLN;
WriteLN;
end;
procedure GetDimensions;
begin
repeat
Write('WHAT ARE YOUR WIDTH AND LENGTH (SPACE IN BETWEEN): ');
ReadLN(Width, Length);
if (Width = 1) or (Length = 1) then
begin
WriteLN('MEANINGLESS DIMENSIONS. TRY AGAIN.');
end;
until (Width > 1) and (Length > 1);
WriteLN;
WriteLN;
WriteLN;
WriteLN;
end;
procedure ClearMatrices;
var
indexW: Integer;
indexL: Integer;
begin
SetLength(MatrixWalls, Width, Length);
SetLength(MatrixVisited, Width, Length);
for indexW:= 0 to Pred(Width) do
begin
for indexL:= 0 to Pred(Length) do
begin
MatrixWalls[indexW][indexL]:= 0;
MatrixVisited[indexW][indexL]:= 0;
end;
end;
end;
function GetRandomDirection(const ADirections: TDirections): TDirection;
var
count: Integer;
position: Integer;
directions: array [0..3] of TDirection;
begin
count:= 0;
position:= 0;
if dUp in ADirections then
begin
Inc(count);
directions[position]:= dUp;
Inc(position);
end;
if dRight in ADirections then
begin
Inc(count);
directions[position]:= dRight;
Inc(position);
end;
if dDown in ADirections then
begin
Inc(count);
directions[position]:= dDown;
Inc(position);
end;
if dLeft in ADirections then
begin
Inc(count);
directions[position]:= dLeft;
Inc(position);
end;
Result:= directions[Random(count)];
end;
procedure BuildMaze;
var
indexW: Integer;
indexL: Integer;
direction: TDirection;
directions: TDirections;
count: Integer;
begin
Entry:= Random(Width);
indexW:= Entry;
indexL:= 0;
count:= 1;
MatrixVisited[indexW][indexL]:= count;
Inc(count);
repeat
directions:= [dUp, dRight, dDown, dLeft];
if (indexW = 0) or (MatrixVisited[Pred(indexW)][indexL] <> 0) then
begin
Exclude(directions, dLeft);
end;
if (indexL = 0) or (MatrixVisited[indexW][Pred(indexL)] <> 0) then
begin
Exclude(directions, dUp);
end;
if (indexW = Pred(Width)) or (MatrixVisited[Succ(indexW)][indexL] <> 0) then
begin
Exclude(directions, dRight);
end;
if (indexL = Pred(Length)) or (MatrixVisited[indexW][Succ(indexL)] <> 0) then
begin
Exclude(directions, dDown);
end;
if directions <> [] then
begin
direction:= GetRandomDirection(directions);
case direction of
dLeft:begin
Dec(indexW);
MatrixWalls[indexW][indexL]:= EXIT_RIGHT;
end;
dUp:begin
Dec(indexL);
MatrixWalls[indexW][indexL]:= EXIT_DOWN;
end;
dRight:begin
Inc(MatrixWalls[indexW][indexL], EXIT_RIGHT);
Inc(indexW);
end;
dDown:begin
Inc(MatrixWalls[indexW][indexL], EXIT_DOWN);
Inc(indexL);
end;
end;
MatrixVisited[indexW][indexL]:= count;
Inc(count);
end
else
begin
while True do
begin
if indexW <> Pred(Width) then
begin
Inc(indexW);
end
else if indexL <> Pred(Length) then
begin
Inc(indexL);
indexW:= 0;
end
else
begin
indexW:= 0;
indexL:= 0;
end;
if MatrixVisited[indexW][indexL] <> 0 then
begin
break;
end;
end;
end;
until count = (Width * Length) + 1;
indexW:= Random(Width);
indexL:= Pred(Length);
Inc(MatrixWalls[indexW][indexL]);
end;
procedure DegubVisited;
var
indexW: Integer;
indexL: Integer;
begin
WriteLN('Visited');
for indexL:= 0 to Pred(Length) do
begin
for indexW:= 0 to Pred(Width) do
begin
Write(MatrixVisited[indexW][indexL]:2,' ');
end;
WriteLN;
end;
WriteLN;
end;
procedure DebugWalls;
var
indexW: Integer;
indexL: Integer;
begin
WriteLN('Walls');
for indexL:= 0 to Pred(Length) do
begin
for indexW:= 0 to Pred(Width) do
begin
Write(MatrixWalls[indexW, indexL]:2, ' ');
end;
WriteLN;
end;
WriteLN;
end;
procedure PrintMaze;
var
indexW: Integer;
indexL: Integer;
begin
for indexW:= 0 to Pred(Width) do
begin
if indexW = Entry then
begin
Write('. ');
end
else
begin
Write('.--');
end;
end;
WriteLN('.');
for indexL:= 0 to Pred(Length) do
begin
Write('I');
for indexW:= 0 to Pred(Width) do
begin
if MatrixWalls[indexW, indexL] < 2 then
begin
Write(' I');
end
else
begin
Write(' ');
end;
end;
WriteLN;
for indexW:= 0 to Pred(Width) do
begin
if (MatrixWalls[indexW, indexL] = 0) or (MatrixWalls[indexW, indexL] = 2) then
begin
Write(':--');
end
else
begin
Write(': ');
end;
end;
WriteLN('.');
end;
WriteLN;
end;
begin
Randomize;
ClrScr;
PrintGreeting;
GetDimensions;
ClearMatrices;
BuildMaze;
//DegubVisited;
//DebugWalls;
PrintMaze;
end.

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 [Perl](https://www.perl.org/)

159
02_Amazing/perl/amazing.pl Executable file
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;
}

View File

@@ -0,0 +1,121 @@
import random
# Python translation by Frank Palazzolo - 2/2021
print(' '*28+'AMAZING PROGRAM')
print(' '*15+'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY')
print()
print()
print()
while True:
width, length = input('What are your width and length?').split(',')
width = int(width)
length = int(length)
if width != 1 and length != 1:
break
print('Meaningless dimensions. Try again.')
# Build two 2D arrays
#
# used:
# Initially set to zero, unprocessed cells
# Filled in with consecutive non-zero numbers as cells are processed
#
# walls:
# Initially set to zero, (all paths blocked)
# Remains 0 if there is no exit down or right
# Set to 1 if there is an exit down
# Set to 2 if there is an exit right
# Set to 3 if there are exits down and right
used = []
walls = []
for i in range(length):
used.append([0]*width)
walls.append([0]*width)
# Use direction variables with nice names
GO_LEFT,GO_UP,GO_RIGHT,GO_DOWN=[0,1,2,3]
# Give Exit directions nice names
EXIT_DOWN = 1
EXIT_RIGHT = 2
# Pick a random entrance, mark as used
enter_col=random.randint(0,width-1)
row,col=0,enter_col
count=1
used[row][col]=count
count=count+1
while count!=width*length+1:
# remove possible directions that are blocked or
# hit cells that we have already processed
possible_dirs = [GO_LEFT,GO_UP,GO_RIGHT,GO_DOWN]
if col==0 or used[row][col-1]!=0:
possible_dirs.remove(GO_LEFT)
if row==0 or used[row-1][col]!=0:
possible_dirs.remove(GO_UP)
if col==width-1 or used[row][col+1]!=0:
possible_dirs.remove(GO_RIGHT)
if row==length-1 or used[row+1][col]!=0:
possible_dirs.remove(GO_DOWN)
# If we can move in a direction, move and make opening
if len(possible_dirs)!=0:
direction=random.choice(possible_dirs)
if direction==GO_LEFT:
col=col-1
walls[row][col]=EXIT_RIGHT
elif direction==GO_UP:
row=row-1
walls[row][col]=EXIT_DOWN
elif direction==GO_RIGHT:
walls[row][col]=walls[row][col]+EXIT_RIGHT
col=col+1
elif direction==GO_DOWN:
walls[row][col]=walls[row][col]+EXIT_DOWN
row=row+1
used[row][col]=count
count=count+1
# otherwise, move to the next used cell, and try again
else:
while True:
if col!=width-1:
col=col+1
elif row!=length-1:
row,col=row+1,0
else:
row,col=0,0
if used[row][col]!=0:
break
# Add a random exit
col=random.randint(0,width-1)
row=length-1
walls[row][col]=walls[row][col]+1
# Print the maze
for col in range(width):
if col==enter_col:
print('. ',end='')
else:
print('.--',end='')
print('.')
for row in range(length):
print('I',end='')
for col in range(width):
if walls[row][col]<2:
print(' I',end='')
else:
print(' ',end='')
print()
for col in range(width):
if walls[row][col]==0 or walls[row][col]==2:
print(':--',end='')
else:
print(': ',end='')
print('.')

View File

@@ -0,0 +1,9 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Ruby](https://www.ruby-lang.org/en/)
Converted to Ruby (with tons of inspiration from the Python version) by @marcheiligers
Run `ruby amazing.rb`.
Run `DEBUG=1 ruby amazing.ruby` to see how it works (requires at least Ruby 2.7).

216
02_Amazing/ruby/amazing.rb Normal file
View File

@@ -0,0 +1,216 @@
# frozen_string_literal: true
DEBUG = !ENV['DEBUG'].nil?
require 'io/console' if DEBUG
# BASIC arrays are 1-based, unlike Ruby 0-based arrays,
# and this class simulates that. BASIC arrays are zero-filled,
# which is also done here. While we could easily update the
# algorithm to work with zero-based arrays, this class makes
# the problem easier to reason about, row or col 1 are the
# first row or column.
class BasicArrayTwoD
def initialize(rows, cols)
@val = Array.new(rows) { Array.new(cols, 0) }
end
def [](row, col = nil)
if col
@val[row - 1][col - 1]
else
@val[row - 1]
end
end
def []=(row, col, n)
@val[row - 1][col - 1] = n
end
def to_s(width: max_width, row_hilite: nil, col_hilite: nil)
@val.map.with_index do |row, row_index|
row.map.with_index do |val, col_index|
if row_hilite == row_index + 1 && col_hilite == col_index + 1
"[#{val.to_s.center(width)}]"
else
val.to_s.center(width + 2)
end
end.join
end.join("\n")
end
def max_width
@val.flat_map { |row| row.map { |val| val.to_s.length } }.sort.last
end
end
class Maze
EXIT_DOWN = 1
EXIT_RIGHT = 2
# Set up a constant hash for directions
# The values represent the direction of the move as changes to row, col
# and the type of exit when moving in that direction
DIRECTIONS = {
left: { row: 0, col: -1, exit: EXIT_RIGHT },
up: { row: -1, col: 0, exit: EXIT_DOWN },
right: { row: 0, col: 1, exit: EXIT_RIGHT },
down: { row: 1, col: 0, exit: EXIT_DOWN }
}.freeze
attr_reader :width, :height, :used, :walls, :entry
def initialize(width, height)
@width = width
@height = height
@used = BasicArrayTwoD.new(height, width)
@walls = BasicArrayTwoD.new(height, width)
create
end
def draw
# Print the maze
draw_top(entry, width)
(1..height - 1).each do |row|
draw_row(walls[row])
end
draw_bottom(walls[height])
end
private
def create
# entry represents the location of the opening
@entry = (rand * width).round + 1
# Set up our current row and column, starting at the top and the locations of the opening
row = 1
col = entry
c = 1
used[row, col] = c # This marks the opening in the first row
c += 1
while c != width * height + 1 do
debug walls, row, col
# remove possible directions that are blocked or
# hit cells that we have already processed
possible_dirs = DIRECTIONS.reject do |dir, change|
nrow = row + change[:row]
ncol = col + change[:col]
nrow < 1 || nrow > height || ncol < 1 || ncol > width || used[nrow, ncol] != 0
end.keys
# If we can move in a direction, move and make opening
if possible_dirs.size != 0
direction = possible_dirs.sample
change = DIRECTIONS[direction] # pick a random direction
if %i[left up].include?(direction)
row += change[:row]
col += change[:col]
walls[row, col] = change[:exit]
else
walls[row, col] += change[:exit]
row += change[:row]
col += change[:col]
end
used[row, col] = c
c = c + 1
# otherwise, move to the next used cell, and try again
else
loop do
if col != width
col += 1
elsif row != height
row += 1
col = 1
else
row = col = 1
end
break if used[row, col] != 0
debug walls, row, col
end
end
end
# Add a random exit
walls[height, (rand * width).round] += 1
end
def draw_top(entry, width)
(1..width).each do |i|
if i == entry
print i == 1 ? '┏ ' : '┳ '
else
print i == 1 ? '┏━━' : '┳━━'
end
end
puts '┓'
end
def draw_row(row)
print '┃'
row.each.with_index do |val, col|
print val < 2 ? ' ┃' : ' '
end
puts
row.each.with_index do |val, col|
print val == 0 || val == 2 ? (col == 0 ? '┣━━' : '╋━━') : (col == 0 ? '┃ ' : '┫ ')
end
puts '┫'
end
def draw_bottom(row)
print '┃'
row.each.with_index do |val, col|
print val < 2 ? ' ┃' : ' '
end
puts
row.each.with_index do |val, col|
print val == 0 || val == 2 ? (col == 0 ? '┗━━' : '┻━━') : (col == 0 ? '┗ ' : '┻ ')
end
puts '┛'
end
def debug(walls, row, col)
return unless DEBUG
STDOUT.clear_screen
puts walls.to_s(row_hilite: row, col_hilite: col)
sleep 0.1
end
end
class Amazing
def run
draw_header
width, height = ask_dimensions
while width <= 1 || height <= 1
puts "MEANINGLESS DIMENSIONS. TRY AGAIN."
width, height = ask_dimensions
end
maze = Maze.new(width, height)
puts "\n" * 3
maze.draw
end
def draw_header
puts ' ' * 28 + 'AMAZING PROGRAM'
puts ' ' * 15 + 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'
puts "\n" * 3
end
def ask_dimensions
print 'WHAT ARE YOUR WIDTH AND HEIGHT? '
width = gets.to_i
print '?? '
height = gets.to_i
[width, height]
end
end
Amazing.new.run

295
02_Amazing/vbnet/program.vb Normal file
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

View File

@@ -1,7 +0,0 @@
### Animal
As published in Basic Computer Games (1978)
https://www.atariarchives.org/basicgames/showpage.php?page=4
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html

22
03_Animal/README.md Normal file
View File

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

160
03_Animal/java/Animal.java Normal file
View File

@@ -0,0 +1,160 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
/**
* ANIMAL
* <p>
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
*/
public class Animal {
public static void main(String[] args) {
printIntro();
Scanner scan = new Scanner(System.in);
List<Question> questions = new ArrayList<>();
questions.add(new Question("DOES IT SWIM", "FISH", "BIRD"));
boolean stopGame = false;
while (!stopGame) {
String choice = readMainChoice(scan);
switch (choice) {
case "LIST":
printKnownAnimals(questions);
break;
case "Q":
case "QUIT":
stopGame = true;
break;
default:
if (choice.toUpperCase(Locale.ROOT).startsWith("Y")) {
int k = 0;
boolean correctGuess = false;
while (questions.size() > k && !correctGuess) {
Question question = questions.get(k);
correctGuess = askQuestion(question, scan);
if (correctGuess) {
System.out.println("WHY NOT TRY ANOTHER ANIMAL?");
} else {
k++;
}
}
if (!correctGuess) {
askForInformationAndSave(scan, questions);
}
}
}
}
}
private static void askForInformationAndSave(Scanner scan, List<Question> questions) {
//Failed to get it right and ran out of questions
//Let's ask the user for the new information
System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ");
String animal = scan.nextLine();
System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, questions.get(
questions.size() - 1).falseAnswer);
String newQuestion = scan.nextLine();
System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal);
boolean newAnswer = readYesOrNo(scan);
//Add it to our list
addNewAnimal(questions, animal, newQuestion, newAnswer);
}
private static void addNewAnimal(List<Question> questions, String animal, String newQuestion, boolean newAnswer) {
Question lastQuestion = questions.get(questions.size() - 1);
String lastAnimal = lastQuestion.falseAnswer;
lastQuestion.falseAnswer = null; //remove the false option to indicate that there is a next question
Question newOption;
if (newAnswer) {
newOption = new Question(newQuestion, animal, lastAnimal);
} else {
newOption = new Question(newQuestion, lastAnimal, animal);
}
questions.add(newOption);
}
private static boolean askQuestion(Question question, Scanner scanner) {
System.out.printf("%s ? ", question.question);
boolean chosenAnswer = readYesOrNo(scanner);
if (chosenAnswer) {
if (question.trueAnswer != null) {
System.out.printf("IS IT A %s ? ", question.trueAnswer);
return readYesOrNo(scanner);
}
//else go to the next question
} else {
if (question.falseAnswer != null) {
System.out.printf("IS IT A %s ? ", question.falseAnswer);
return readYesOrNo(scanner);
}
//else go to the next question
}
return false;
}
private static boolean readYesOrNo(Scanner scanner) {
boolean validAnswer = false;
Boolean choseAnswer = null;
while (!validAnswer) {
String answer = scanner.nextLine();
if (answer.toUpperCase(Locale.ROOT).startsWith("Y")) {
validAnswer = true;
choseAnswer = true;
} else if (answer.toUpperCase(Locale.ROOT).startsWith("N")) {
validAnswer = true;
choseAnswer = false;
}
}
return choseAnswer;
}
private static void printKnownAnimals(List<Question> questions) {
System.out.println("\nANIMALS I ALREADY KNOW ARE:");
List<String> animals = new ArrayList<>();
questions.forEach(q -> {
if (q.trueAnswer != null) {
animals.add(q.trueAnswer);
}
if (q.falseAnswer != null) {
animals.add(q.falseAnswer);
}
});
System.out.println(String.join("\t\t", animals));
}
private static String readMainChoice(Scanner scan) {
System.out.print("ARE YOU THINKING OF AN ANIMAL ? ");
return scan.nextLine();
}
private static void printIntro() {
System.out.println(" ANIMAL");
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
System.out.println("\n\n");
System.out.println("PLAY 'GUESS THE ANIMAL'");
System.out.println("\n");
System.out.println("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
}
public static class Question {
String question;
String trueAnswer;
String falseAnswer;
public Question(String question, String trueAnswer, String falseAnswer) {
this.question = question;
this.trueAnswer = trueAnswer;
this.falseAnswer = falseAnswer;
}
}
}

119
03_Animal/kotlin/Animal.kt Normal file
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()
}
}

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 [Kotlin](https://kotlinlang.org/)

View File

@@ -1,3 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
Conversion to [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language))

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 [Perl](https://www.perl.org/)

223
03_Animal/perl/animal.pl Executable file
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 :

192
03_Animal/python/animal.py Normal file
View File

@@ -0,0 +1,192 @@
########################################################
#
# Animal
#
# From: Basic computer Games(1978)
#
# 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 it guessed
# from the one 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 sistem allows, you may modify the program to
# save array A$, then 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 MAMAL".
# This program can be easily modified to deal with categories of
# things other than animals by simply modifying the initial data
# in Line 530 and the dialogue references to animal in Lines 10,
# 40, 50, 130, 230, 240 and 600. In an educational environment, this
# would be a valuable program to teach the distinguishing chacteristics
# 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
#
########################################################
class Node:
"""
Node of the binary tree of questions.
"""
def __init__(self, text, yes_node, no_node):
# the nodes that are leafs have as text the animal's name, otherwise
# a yes/no question
self.text = text
self.yes_node = yes_node
self.no_node = no_node
def update_node(self, new_question, answer_new_ques, new_animal):
# update the leaf with a question
old_animal = self.text
# we replace the animal with a new question
self.text = new_question
if answer_new_ques == 'y':
self.yes_node = Node(new_animal, None, None)
self.no_node = Node(old_animal, None, None)
else:
self.yes_node = Node(old_animal, None, None)
self.no_node = Node(new_animal, None, None)
# the leafs have as children None
def is_leaf(self):
return self.yes_node == None and self.no_node == None
def list_known_animals(root_node):
# Traversing the tree by recursion until we reach the leafs
if root_node == None:
return
if root_node.is_leaf():
print(root_node.text, end=' '*11)
return
if root_node.yes_node:
list_known_animals(root_node.yes_node)
if root_node.no_node:
list_known_animals(root_node.no_node)
def parse_input(message, check_list, root_node):
# only accepts yes or no inputs and recognizes list operation
correct_input = False
while not correct_input:
try:
inp = input(message)
if check_list and inp.lower() == 'list':
print('Animals I already know are:')
list_known_animals(root_node)
print('\n')
token = inp[0].lower()
if token == 'y' or token == 'n':
correct_input = True
except IndexError:
pass
return token
def avoid_void_input(message):
answer = ''
while answer == '':
answer = input(message)
return answer
def initial_message():
print(' '*32 + 'Animal')
print(' '*15 + 'Creative Computing Morristown, New Jersey\n')
print('Play ´Guess the Animal´')
print('Think of an animal and the computer will try to guess it.\n')
# Initial tree
yes_child = Node('Fish', None, None)
no_child = Node('Bird', None, None)
root = Node('Does it swim?', yes_child, no_child)
# Main loop of game
initial_message()
keep_playing = parse_input(
'Are you thinking of an animal? ', True, root) == 'y'
while keep_playing:
keep_asking = True
# Start traversing the tree by the root
actual_node = root
while keep_asking:
if not actual_node.is_leaf():
# we have to keep asking i.e. traversing nodes
answer = parse_input(actual_node.text, False, None)
if answer == 'y':
actual_node = actual_node.yes_node
else:
actual_node = actual_node.no_node
else:
# we have reached a possible answer
answer = parse_input('Is it a {}? '.format(
actual_node.text), False, None)
if answer == 'n':
# add the new animal to the tree
new_animal = avoid_void_input(
'The animal you were thinking of was a ? ')
new_question = avoid_void_input(
'Please type in a question that would distinguish a {} from a {}: '.format(new_animal, actual_node.text))
answer_new_question = parse_input(
'for a {} the answer would be: '.format(new_animal), False, None)
actual_node.update_node(
new_question+'?', answer_new_question, new_animal)
else:
print("Why not try another animal?")
keep_asking = False
keep_playing = parse_input(
'Are you thinking of an animal? ', True, root) == 'y'
########################################################
# Porting Notes
#
# The data structure used for storing questions and
# animals is a binary tree where each non-leaf node
# has a question, while the leafs store the animals.
#
# As the original program, this program doesn't store
# old questions and animals. A good modification would
# be to add a database to store the tree.
# Also as the original program, this one can be easily
# modified to not only make guesses about animals, by
# modyfing the initial data of the tree, the questions
# that are asked to the user and the initial message
# function (Lines 120 to 130, 135, 158, 160, 168, 173)
########################################################

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