diff --git a/.gitignore b/.gitignore
index 078ff6f9..fea38bce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,8 @@
.vscode/
.gradle/
node_modules/
-buildJvm/
-
-build.gradle
+buildJvm/bin
+buildJvm/*/build/
.classpath
.project
diff --git a/00_Utilities/VintageBASIC.xml b/00_Utilities/VintageBASIC.xml
new file mode 100644
index 00000000..4b49b888
--- /dev/null
+++ b/00_Utilities/VintageBASIC.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+ 00REM 01 02 03 04
+
+
+
+
+
+
+
+ - + ^ * / = <> < > <= >=
+
+
+
+
+
+
+
+
+
+
+ DATA DEF FN DIM END FOR GOSUB GOTO IF THEN INPUT LET NEXT ON PRINT RANDOMIZE REM RESTORE RETURN STOP TO
+ ABS ASC ATN CHR$ COS EXP INT LEFT$ LEN LOG MID$ RIGHT$ RND SGN SIN SPC SQR STR TAB TAN VAL
+ NOT AND OR
+
+
+
+
+
+ 00" 01 02" 03( 04 05) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py
new file mode 100644
index 00000000..5c591f44
--- /dev/null
+++ b/12_Bombs_Away/python/bombs_away.py
@@ -0,0 +1,189 @@
+"""
+Bombs away
+
+Ported from BASIC to Python3 by Bernard Cooke (bernardcooke53)
+Tested with Python 3.8.10, formatted with Black and type checked with mypy.
+"""
+import random
+from typing import Iterable
+
+
+def _stdin_choice(prompt: str, *, choices: Iterable[str]) -> str:
+ ret = input(prompt)
+ while ret not in choices:
+ print("TRY AGAIN...")
+ ret = input(prompt)
+ return ret
+
+
+def player_survived() -> None:
+ print("YOU MADE IT THROUGH TREMENDOUS FLAK!!")
+
+
+def player_death() -> None:
+ print("* * * * BOOM * * * *")
+ print("YOU HAVE BEEN SHOT DOWN.....")
+ print("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR")
+ print("LAST TRIBUTE...")
+
+
+def mission_success() -> None:
+ print(f"DIRECT HIT!!!! {int(100 * random.random())} KILLED.")
+ print("MISSION SUCCESSFUL.")
+
+
+def death_with_chance(p_death: float) -> bool:
+ """
+ Takes a float between 0 and 1 and returns a boolean
+ if the player has survived (based on random chance)
+
+ Returns True if death, False if survived
+ """
+ return p_death > random.random()
+
+
+def commence_non_kamikazi_attack() -> None:
+ while True:
+ try:
+ nmissions = int(input("HOW MANY MISSIONS HAVE YOU FLOWN? "))
+
+ while nmissions >= 160:
+ print("MISSIONS, NOT MILES...")
+ print("150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS")
+ nmissions = int(input("NOW THEN, HOW MANY MISSIONS HAVE YOU FLOWN? "))
+ break
+ except ValueError:
+ # In the BASIC implementation this
+ # wasn't accounted for
+ print("TRY AGAIN...")
+ continue
+
+ if nmissions >= 100:
+ print("THAT'S PUSHING THE ODDS!")
+
+ if nmissions < 25:
+ print("FRESH OUT OF TRAINING, EH?")
+
+ print()
+ return (
+ mission_success() if nmissions >= 160 * random.random() else mission_failure()
+ )
+
+
+def mission_failure() -> None:
+ weapons_choices = {
+ "1": "GUNS",
+ "2": "MISSILES",
+ "3": "BOTH",
+ }
+ print(f"MISSED TARGET BY {int(2 + 30 * random.random())} MILES!")
+ print("NOW YOU'RE REALLY IN FOR IT !!")
+ print()
+ enemy_weapons = _stdin_choice(
+ prompt="DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)? ",
+ choices=weapons_choices,
+ )
+
+ # If there are no gunners (i.e. weapon choice 2) then
+ # we say that the gunners have 0 accuracy for the purposes
+ # of calculating probability of player death
+
+ enemy_gunner_accuracy = 0.0
+ if enemy_weapons != "2":
+ # If the enemy has guns, how accurate are the gunners?
+ while True:
+ try:
+ enemy_gunner_accuracy = float(
+ input("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ")
+ )
+ break
+ except ValueError:
+ # In the BASIC implementation this
+ # wasn't accounted for
+ print("TRY AGAIN...")
+ continue
+
+ if enemy_gunner_accuracy < 10:
+ print("YOU LIE, BUT YOU'LL PAY...")
+ return player_death()
+
+ missile_threat_weighting = 0 if enemy_weapons == "1" else 35
+
+ death = death_with_chance(
+ p_death=(enemy_gunner_accuracy + missile_threat_weighting) / 100
+ )
+
+ return player_survived() if not death else player_death()
+
+
+def play_italy() -> None:
+ targets_to_messages = {
+ # 1 - ALBANIA, 2 - GREECE, 3 - NORTH AFRICA
+ "1": "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE.",
+ "2": "BE CAREFUL!!!",
+ "3": "YOU'RE GOING FOR THE OIL, EH?",
+ }
+ target = _stdin_choice(
+ prompt="YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)",
+ choices=targets_to_messages,
+ )
+
+ print(targets_to_messages[target])
+ return commence_non_kamikazi_attack()
+
+
+def play_allies() -> None:
+ aircraft_to_message = {
+ "1": "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI.",
+ "2": "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA.",
+ "3": "YOU'RE CHASING THE BISMARK IN THE NORTH SEA.",
+ "4": "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.",
+ }
+ aircraft = _stdin_choice(
+ prompt="AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4): ",
+ choices=aircraft_to_message,
+ )
+
+ print(aircraft_to_message[aircraft])
+ return commence_non_kamikazi_attack()
+
+
+def play_japan() -> None:
+ print("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.")
+ first_mission = input("YOUR FIRST KAMIKAZE MISSION? (Y OR N): ")
+ if first_mission.lower() == "n":
+ return player_death()
+ return mission_success() if random.random() > 0.65 else player_death()
+
+
+def play_germany() -> None:
+ targets_to_messages = {
+ # 1 - RUSSIA, 2 - ENGLAND, 3 - FRANCE
+ "1": "YOU'RE NEARING STALINGRAD.",
+ "2": "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR.",
+ "3": "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.",
+ }
+ target = _stdin_choice(
+ prompt="A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\nENGLAND(2), OR FRANCE(3)? ",
+ choices=targets_to_messages,
+ )
+
+ print(targets_to_messages[target])
+
+ return commence_non_kamikazi_attack()
+
+
+def play_game() -> None:
+ print("YOU ARE A PILOT IN A WORLD WAR II BOMBER.")
+ sides = {"1": play_italy, "2": play_allies, "3": play_japan, "4": play_germany}
+ side = _stdin_choice(
+ prompt="WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4): ", choices=sides
+ )
+ return sides[side]()
+
+
+if __name__ == "__main__":
+ again = True
+ while again:
+ play_game()
+ again = True if input("ANOTHER MISSION? (Y OR N): ").upper() == "Y" else False
diff --git a/25_Chief/ruby/chief.rb b/25_Chief/ruby/chief.rb
new file mode 100644
index 00000000..c60b2519
--- /dev/null
+++ b/25_Chief/ruby/chief.rb
@@ -0,0 +1,98 @@
+def tab(size)
+ str = ''
+ size.times do
+ str += ' '
+ end
+
+ str
+end
+
+def input
+ gets.chomp
+end
+
+def bye
+ print "BYE!!!\n"
+end
+
+def main
+ print tab(30), "CHIEF\n"
+ print tab(15), "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"
+ print "\n"
+ print "\n"
+ print "\n"
+ print "I AM CHIEF NUMBERS FREEK, THE GREAT INDIAN MATH GOD.\n"
+ print "ARE YOU READY TO TAKE THE TEST YOU CALLED ME OUT FOR\n"
+
+ a = input
+ if a != 'YES'
+ print "SHUT UP, PALE FACE WITH WISE TONGUE.\n"
+ end
+
+ print " TAKE A NUMBER AND ADD 3. DIVIDE THIS NUMBER BY 5 AND\n"
+ print "MULTIPLY BY 8. DIVIDE BY 5 AND ADD THE SAME. SUBTRACT 1.\n"
+ print " WHAT DO YOU HAVE\n"
+
+ b = input.to_f
+ c = ((b + 1 - 5) * 5 / 8 * 5 -3).to_f
+ print "I BET YOUR NUMBER WAS #{c}. AM I RIGHT\n"
+
+ d = input
+ if d == 'YES'
+ return bye
+ end
+
+ print "WHAT WAS YOUR ORIGINAL NUMBER\n"
+
+ k = input.to_f
+ f = k + 3
+ g = f / 5
+ h = g * 8
+ i = h / 5 + 5
+ j = i - 1
+
+ print "SO YOU THINK YOU'RE SO SMART, EH?\n"
+ print "NOW WATCH.\n"
+ print k, " PLUS 3 EQUALS ", f, ". THIS DIVIDED BY 5 EQUALS ", g, ";\n"
+ print "THIS TIMES 8 EQUALS ", h, ". IF WE DIVIDE BY 5 AND ADD 5,\n"
+ print "WE GET ", i, ", WHICH, MINUS 1, EQUALS ", j, ".\n"
+ print "NOW DO YOU BELIEVE ME\n"
+
+ z = input
+ if z == 'YES'
+ return bye
+ end
+
+ print "YOU HAVE MADE ME MAD!!!\n"
+ print "THERE MUST BE A GREAT LIGHTNING BOLT!\n"
+ print "\n"
+ print "\n"
+
+ x = 30
+ while x >= 22
+ print tab(x), "X X\n"
+ x -= 1
+ end
+
+ print tab(21), "X XXX\n"
+ print tab(20), "X X\n"
+ print tab(19), "XX X\n"
+
+ y = 20
+ while y >= 13
+ print tab(y), "X X\n"
+ y -= 1
+ end
+
+ print tab(12), "XX\n"
+ print tab(11), "X\n"
+ print tab(10), "*\n"
+
+ print "\n"
+ print "#########################\n"
+ print "\n"
+ print "I HOPE YOU BELIEVE ME NOW, FOR YOUR SAKE!!\n"
+ return bye
+end
+
+main
diff --git a/27_Civil_War/civilwar.bas b/27_Civil_War/civilwar.bas
index 63597944..f8fe9701 100644
--- a/27_Civil_War/civilwar.bas
+++ b/27_Civil_War/civilwar.bas
@@ -216,7 +216,7 @@
2280 PRINT "CASUALTIES",C5,C6
2290 PRINT "DESERTIONS",INT(E),INT(E2)
2300 PRINT
-2310 IF B$ <> "YES" THEN 2350
+2310 IF B$ <> "YES" THEN 2530
2320 PRINT "COMPARED TO THE ACTUAL CASUALTIES AT "C$
2330 PRINT "CONFEDERATE:"INT(100*(C5/C1)+.5)"% OF THE ORIGINAL"
2340 PRINT "UNION: "INT(100*(C6/C2)+.5)"% OF THE ORIGINAL"
diff --git a/27_Civil_War/java/.gitignore b/27_Civil_War/java/.gitignore
new file mode 100644
index 00000000..a87dc8b2
--- /dev/null
+++ b/27_Civil_War/java/.gitignore
@@ -0,0 +1,3 @@
+*.class
+*.iml
+.idea/
\ No newline at end of file
diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java
new file mode 100644
index 00000000..042126f6
--- /dev/null
+++ b/27_Civil_War/java/src/CivilWar.java
@@ -0,0 +1,707 @@
+import java.io.PrintStream;
+import java.util.InputMismatchException;
+import java.util.List;
+import java.util.Scanner;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.IntStream.range;
+
+@SuppressWarnings("SpellCheckingInspection")
+public class CivilWar {
+
+ private final PrintStream out;
+ private final List data;
+
+ private final BattleResults results;
+
+ private BattleState currentBattle;
+ private int numGenerals;
+ private int battleNumber;
+ private boolean wantBattleDescriptions;
+ private final int[] strategies;
+
+ private int confedStrategy;
+ private int unionStrategy;
+
+ private final ArmyPair resources;
+ private final ArmyPair totalExpectedCasualties;
+ private final ArmyPair totalCasualties;
+ private final ArmyPair revenue;
+ private final ArmyPair inflation;
+ private final ArmyPair totalExpenditure;
+ private final ArmyPair totalTroops;
+
+ private boolean excessiveConfederateLosses;
+ private boolean excessiveUnionLosses;
+
+ private boolean confedSurrender;
+ private boolean unionSurrender;
+
+ private final static String YES_NO_REMINDER = "(ANSWER YES OR NO)";
+ private final static Predicate YES_NO_CHECKER = i -> isYes(i) || isNo(i);
+
+ /**
+ * ORIGINAL GAME DESIGN: CRAM, GOODIE, HIBBARD LEXINGTON H.S.
+ * MODIFICATIONS: G. PAUL, R. HESS (TIES), 1973
+ */
+ public static void main(String[] args) {
+ var x = new CivilWar(System.out);
+ x.showCredits();
+
+ // LET D=RND(-1) ???
+
+ System.out.print("DO YOU WANT INSTRUCTIONS? ");
+
+ if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) {
+ x.showHelp();
+ }
+
+ x.gameLoop();
+ }
+
+ private void gameLoop() {
+ out.println();
+ out.println();
+ out.println();
+ out.print("ARE THERE TWO GENERALS PRESENT (ANSWER YES OR NO)? ");
+
+ if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) {
+ this.numGenerals = 2;
+ } else {
+ this.numGenerals = 1;
+ out.println();
+ out.println("YOU ARE THE CONFEDERACY. GOOD LUCK!");
+ out.println();
+ }
+
+ out.println("SELECT A BATTLE BY TYPING A NUMBER FROM 1 TO 14 ON");
+ out.println("REQUEST. TYPE ANY OTHER NUMBER TO END THE SIMULATION.");
+ out.println("BUT '0' BRINGS BACK EXACT PREVIOUS BATTLE SITUATION");
+ out.println("ALLOWING YOU TO REPLAY IT");
+ out.println();
+ out.println("NOTE: A NEGATIVE FOOD$ ENTRY CAUSES THE PROGRAM TO ");
+ out.println("USE THE ENTRIES FROM THE PREVIOUS BATTLE");
+ out.println();
+
+ out.print("AFTER REQUESTING A BATTLE, DO YOU WISH BATTLE DESCRIPTIONS (ANSWER YES OR NO)? ");
+
+ this.wantBattleDescriptions = isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER));
+
+ while (true) {
+ var battle = startBattle();
+ if (battle == null) {
+ break;
+ }
+
+ this.currentBattle = battle;
+
+ offensiveLogic(battle.data);
+
+ calcLosses(battle);
+
+ reset();
+
+ if (this.confedSurrender) {
+ out.println("THE CONFEDERACY HAS SURRENDERED");
+ } else if (unionSurrender) { // FIXME Is this actually possible? 2850
+ out.println("THE UNION HAS SURRENDERED.");
+ }
+ }
+
+ complete();
+ }
+
+ private BattleState startBattle() {
+ out.println();
+ out.println();
+ out.println();
+ out.print("WHICH BATTLE DO YOU WISH TO SIMULATE ? ");
+
+ var battleNumber = inputInt(i -> i >= 1 || (i == 0 && this.currentBattle != null), i -> "BATTLE " + i + " NOT ALLOWED.");
+
+ if (battleNumber == 0) {
+ out.println(this.currentBattle.data.name + " INSTANT REPLAY");
+ return this.currentBattle;
+ }
+
+ if (battleNumber > this.data.size()) { // TYPE ANY OTHER NUMBER TO END THE SIMULATION
+ return null;
+ }
+
+ this.battleNumber = battleNumber;
+
+ var battle = this.data.get(this.battleNumber - 1);
+ var battleState = new BattleState(battle);
+
+
+ excessiveConfederateLosses = false;
+
+ // INFLATION CALC
+ // REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15%
+ inflation.confederate = 10 + (results.union - results.confederate) * 2;
+ inflation.union = 10 + (results.confederate - results.union) * 2;
+
+ // MONEY AVAILABLE
+ resources.confederate.budget = 100 * (int) Math.floor((battle.troops.confederate * (100.0 - inflation.confederate) / 2000) * (1 + (revenue.confederate - totalExpenditure.confederate) / (revenue.confederate + 1.0)) + .5);
+
+ // MEN AVAILABLE
+ battleState.F1 = 5 * battle.troops.confederate / 6.0;
+
+ if (this.numGenerals == 2) {
+ resources.union.budget = 100 * (int) Math.floor((battle.troops.union * (100.0 - inflation.union) / 2000) * (1 + (revenue.union - totalExpenditure.union) / (revenue.union + 1.0)) + .5);
+ } else {
+ resources.union.budget = 100 * (int) Math.floor(battle.troops.union * (100.0 - inflation.union) / 2000 + .5);
+ }
+
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println("THIS IS THE BATTLE OF " + battle.name);
+
+ if (this.wantBattleDescriptions) {
+ for (var eachLine : battle.blurb) {
+ out.println(eachLine);
+ }
+ }
+
+ out.println();
+ out.println(" CONFEDERACY UNION");
+ out.println("MEN " + getConfedTroops(battle) + " " + getUnionTroops(battle));
+ out.println("MONEY $ " + resources.confederate.budget + " $ " + resources.union.budget);
+ out.println("INFLATION " + (inflation.confederate + 15) + "% " + inflation.union + "%");
+
+ // ONLY IN PRINTOUT IS CONFED INFLATION = I1+15%
+ // IF TWO GENERALS, INPUT CONFED. FIRST
+
+ var terminalInput = new Scanner(System.in);
+
+ for (int i = 0; i < numGenerals; i++) {
+ out.println();
+
+ ArmyResources currentResources;
+
+ if (this.numGenerals == 1 || i == 0) {
+ out.print("CONFEDERATE GENERAL --- ");
+ currentResources = resources.confederate;
+ } else {
+ out.print("UNION GENERAL --- ");
+ currentResources = resources.union;
+ }
+
+ var validInputs = false;
+ while (!validInputs) {
+ out.println("HOW MUCH DO YOU WISH TO SPEND FOR");
+ out.print("- FOOD...... ? ");
+ var food = terminalInput.nextInt();
+ if (food == 0) {
+ if (this.revenue.confederate != 0) {
+ out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS");
+ out.println();
+ }
+ } else {
+ currentResources.food = food;
+ }
+
+ out.print("- SALARIES.. ? ");
+ currentResources.salaries = terminalInput.nextInt();
+
+ out.print("- AMMUNITION ? ");
+ currentResources.ammunition = terminalInput.nextInt(); // FIXME Retry if -ve
+
+ if (currentResources.getTotal() > currentResources.budget) {
+ out.println("THINK AGAIN! YOU HAVE ONLY $" + currentResources.budget);
+ } else {
+ validInputs = true;
+ }
+ }
+ }
+
+ out.println();
+
+ // Record Morale
+ out.println(range(0, numGenerals).mapToObj(i -> moraleForArmy(battleState, i)).collect(joining(", ")));
+
+ out.println();
+
+ return battleState;
+ }
+
+ private int getUnionTroops(HistoricalDatum battle) {
+ return (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0)));
+ }
+
+ private int getConfedTroops(HistoricalDatum battle) {
+ return (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0)));
+ }
+
+ private String moraleForArmy(BattleState battleState, int armyIdx) {
+ var builder = new StringBuilder();
+
+ ArmyResources currentResources;
+
+ if (this.numGenerals == 1 || armyIdx == 0) {
+ builder.append("CONFEDERATE ");
+ currentResources = resources.confederate;
+ } else {
+ builder.append("UNION ");
+ currentResources = resources.union;
+ }
+
+ // FIND MORALE
+ currentResources.morale = (2 * Math.pow(currentResources.food, 2) + Math.pow(currentResources.salaries, 2)) / Math.pow(battleState.F1, 2) + 1;
+ if (currentResources.morale >= 10) {
+ builder.append("MORALE IS HIGH");
+ } else if (currentResources.morale >= 5) {
+ builder.append("MORALE IS FAIR");
+ } else {
+ builder.append("MORALE IS POOR");
+ }
+
+ return builder.toString();
+ }
+
+ private enum OffensiveStatus {
+ DEFENSIVE("YOU ARE ON THE DEFENSIVE"), OFFENSIVE("YOU ARE ON THE OFFENSIVE"), BOTH_OFFENSIVE("BOTH SIDES ARE ON THE OFFENSIVE");
+
+ private final String label;
+
+ OffensiveStatus(String label) {
+ this.label = label;
+ }
+ }
+
+ private void offensiveLogic(HistoricalDatum battle) {
+ out.print("CONFEDERATE GENERAL---");
+ // ACTUAL OFF/DEF BATTLE SITUATION
+ out.println(battle.offensiveStatus.label);
+
+ // CHOOSE STRATEGIES
+
+ if (numGenerals == 2) {
+ out.print("CONFEDERATE STRATEGY ? ");
+ } else {
+ out.print("YOUR STRATEGY ? ");
+ }
+
+ confedStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED.");
+ if (confedStrategy == 5) { // 1970
+ confedSurrender = true;
+ }
+
+ if (numGenerals == 2) {
+ out.print("UNION STRATEGY ? ");
+
+ unionStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED.");
+ if (unionStrategy == 5) { // 1970
+ unionSurrender = true;
+ }
+ } else {
+ unionStrategy();
+ }
+ }
+
+ // 2070 REM : SIMULATED LOSSES-NORTH
+ private UnionLosses simulateUnionLosses(HistoricalDatum battle) {
+ var losses = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1)));
+ losses = losses * (1.28 + (5.0 * battle.troops.union / 6) / (resources.union.ammunition + 1));
+ losses = Math.floor(losses * (1 + 1 / resources.union.morale) + 0.5);
+ // IF LOSS > MEN PRESENT, RESCALE LOSSES
+ var moraleFactor = 100 / resources.union.morale;
+
+ if (Math.floor(losses + moraleFactor) >= getUnionTroops(battle)) {
+ losses = Math.floor(13.0 * getUnionTroops(battle) / 20);
+ moraleFactor = 7 * losses / 13;
+ excessiveUnionLosses = true;
+ }
+
+ return new UnionLosses((int) losses, (int) Math.floor(moraleFactor));
+ }
+
+ // 2170: CALCULATE SIMULATED LOSSES
+ private void calcLosses(BattleState battle) {
+ // 2190
+ out.println();
+ out.println(" CONFEDERACY UNION");
+
+ var C5 = (2 * battle.data.expectedCasualties.confederate / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1)));
+ C5 = (int) Math.floor(C5 * (1 + 1.0 / resources.confederate.morale) * (1.28 + battle.F1 / (resources.confederate.ammunition + 1.0)) + .5);
+ var E = 100 / resources.confederate.morale;
+
+ if (C5 + 100 / resources.confederate.morale >= battle.data.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))) {
+ C5 = (int) Math.floor(13.0 * battle.data.troops.confederate / 20 * (1 + (totalExpectedCasualties.union - totalCasualties.confederate) / (totalTroops.confederate + 1.0)));
+ E = 7 * C5 / 13.0;
+ excessiveConfederateLosses = true;
+ }
+
+ ///// 2270
+
+ final UnionLosses unionLosses;
+
+ if (this.numGenerals == 1) {
+ unionLosses = new UnionLosses((int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)), (int) Math.floor(5 * resources.confederate.morale));
+ } else {
+ unionLosses = simulateUnionLosses(battle.data);
+ }
+
+ out.println("CASUALTIES: " + rightAlignInt(C5) + " " + rightAlignInt(unionLosses.losses));
+ out.println("DESERTIONS: " + rightAlignInt(E) + " " + rightAlignInt(unionLosses.desertions));
+ out.println();
+
+ if (numGenerals == 2) {
+ out.println("COMPARED TO THE ACTUAL CASUALTIES AT " + battle.data.name);
+ out.println("CONFEDERATE: " + (int) Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + " % OF THE ORIGINAL");
+ out.println("UNION: " + (int) Math.floor(100 * (unionLosses.losses / (double) battle.data.expectedCasualties.union) + 0.5) + " % OF THE ORIGINAL");
+
+ out.println();
+
+ // REM - 1 WHO WON
+ var winner = findWinner(C5 + E, unionLosses.losses + unionLosses.desertions);
+ switch (winner) {
+ case UNION -> {
+ out.println("THE UNION WINS " + battle.data.name);
+ results.union++;
+ }
+ case CONFED -> {
+ out.println("THE CONFEDERACY WINS " + battle.data.name);
+ results.confederate++;
+ }
+ case INDECISIVE -> {
+ out.println("BATTLE OUTCOME UNRESOLVED");
+ results.indeterminate++;
+ }
+ }
+ } else {
+ out.println("YOUR CASUALTIES WERE " + Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + "% OF THE ACTUAL CASUALTIES AT " + battle.data.name);
+
+ // FIND WHO WON
+
+ if (excessiveConfederateLosses) {
+ out.println("YOU LOSE " + battle.data.name);
+
+ if (this.battleNumber != 0) {
+ results.union++;
+ }
+ } else {
+ out.println("YOU WIN " + battle.data.name);
+ // CUMULATIVE BATTLE FACTORS WHICH ALTER HISTORICAL RESOURCES AVAILABLE.IF A REPLAY DON'T UPDATE.
+ results.confederate++;
+ }
+ }
+
+ if (this.battleNumber != 0) {
+ totalCasualties.confederate += (int) (C5 + E);
+ totalCasualties.union += unionLosses.losses + unionLosses.desertions;
+ totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate;
+ totalExpectedCasualties.union += battle.data.expectedCasualties.union;
+ totalExpenditure.confederate += resources.confederate.getTotal();
+ totalExpenditure.union += resources.union.getTotal();
+ revenue.confederate += battle.data.troops.confederate * (100 - inflation.confederate) / 20;
+ revenue.union += battle.data.troops.union * (100 - inflation.union) / 20;
+ totalTroops.confederate += battle.data.troops.confederate;
+ totalTroops.union += battle.data.troops.union;
+
+ updateStrategies(this.confedStrategy);
+ }
+ }
+
+ // 2790
+ private void reset() {
+ excessiveConfederateLosses = excessiveUnionLosses = false;
+
+ out.println("---------------");
+ }
+
+ // 2820 REM------FINISH OFF
+ private void complete() {
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println("THE CONFEDERACY HAS WON " + results.confederate + " BATTLES AND LOST " + results.union);
+
+ if (this.unionStrategy == 5) {
+ out.println("THE CONFEDERACY HAS WON THE WAR");
+ }
+
+ if (this.confedStrategy == 5 || results.confederate <= results.union) {
+ out.println("THE UNION HAS WON THE WAR");
+ }
+
+ out.println();
+
+ // FIXME 2960 IF R1=0 THEN 3100
+
+ out.println("FOR THE " + results.getTotal() + " BATTLES FOUGHT (EXCLUDING RERUNS)");
+ out.println(" CONFEDERACY UNION");
+ out.println("HISTORICAL LOSSES " + (int) Math.floor(totalExpectedCasualties.confederate + .5) + " " + (int) Math.floor(totalExpectedCasualties.union + .5));
+ out.println("SIMULATED LOSSES " + (int) Math.floor(totalCasualties.confederate + .5) + " " + (int) Math.floor(totalCasualties.union + .5));
+ out.println();
+ out.println(" % OF ORIGINAL " + (int) Math.floor(100 * ((double) totalCasualties.confederate / totalExpectedCasualties.confederate) + .5) + " " + (int) Math.floor(100 * ((double) totalCasualties.union / totalExpectedCasualties.union) + .5));
+
+ if (this.numGenerals == 1) {
+ out.println();
+ out.println("UNION INTELLIGENCE SUGGESTS THAT THE SOUTH USED ");
+ out.println("STRATEGIES 1, 2, 3, 4 IN THE FOLLOWING PERCENTAGES");
+ out.println(this.strategies[0] + "," + this.strategies[1] + "," + this.strategies[2] + "," + this.strategies[3]);
+ }
+ }
+
+ private Winner findWinner(double confLosses, double unionLosses) {
+ if (this.excessiveConfederateLosses && this.excessiveUnionLosses) {
+ return Winner.INDECISIVE;
+ }
+
+ if (this.excessiveConfederateLosses) {
+ return Winner.UNION;
+ }
+
+ if (this.excessiveUnionLosses || confLosses < unionLosses) {
+ return Winner.CONFED;
+ }
+
+ if (confLosses == unionLosses) {
+ return Winner.INDECISIVE;
+ }
+
+ return Winner.UNION; // FIXME Really? 2400-2420 ?
+ }
+
+ private enum Winner {
+ CONFED, UNION, INDECISIVE
+ }
+
+ private void unionStrategy() {
+ // 3130 ... so you can only input / override Union strategy on re-run??
+ if (this.battleNumber == 0) {
+ out.print("UNION STRATEGY ? ");
+ var terminalInput = new Scanner(System.in);
+ unionStrategy = terminalInput.nextInt();
+ if (unionStrategy < 0) {
+ out.println("ENTER 1, 2, 3, OR 4 (USUALLY PREVIOUS UNION STRATEGY)");
+ // FIXME Retry Y2 input !!!
+ }
+
+ if (unionStrategy < 5) { // 3155
+ return;
+ }
+ }
+
+ var S0 = 0;
+ var r = 100 * Math.random();
+
+ for (unionStrategy = 1; unionStrategy <= 4; unionStrategy++) {
+ S0 += this.strategies[unionStrategy - 1];
+ // IF ACTUAL STRATEGY INFO IS IN PROGRAM DATA STATEMENTS THEN R-100 IS EXTRA WEIGHT GIVEN TO THAT STATEGY.
+ if (r < S0) {
+ break;
+ }
+ }
+ // IF ACTUAL STRAT. IN,THEN HERE IS Y2= HIST. STRAT.
+ out.println("UNION STRATEGY IS " + unionStrategy);
+ }
+
+ public CivilWar(PrintStream out) {
+ this.out = out;
+
+ this.results = new BattleResults();
+
+ this.totalCasualties = new ArmyPair<>(0, 0);
+ this.totalExpectedCasualties = new ArmyPair<>(0, 0);
+ this.totalExpenditure = new ArmyPair<>(0, 0);
+ this.totalTroops = new ArmyPair<>(0, 0);
+
+ this.revenue = new ArmyPair<>(0, 0);
+ this.inflation = new ArmyPair<>(0, 0);
+
+ this.resources = new ArmyPair<>(new ArmyResources(), new ArmyResources());
+
+ // UNION INFO ON LIKELY CONFEDERATE STRATEGY
+ this.strategies = new int[]{25, 25, 25, 25};
+
+ // READ HISTORICAL DATA.
+ // HISTORICAL DATA...CAN ADD MORE (STRAT.,ETC) BY INSERTING DATA STATEMENTS AFTER APPRO. INFO, AND ADJUSTING READ
+ this.data = List.of(
+ new HistoricalDatum("BULL RUN", new ArmyPair<>(18000, 18500), new ArmyPair<>(1967, 2708), OffensiveStatus.DEFENSIVE, new String[]{"JULY 21, 1861. GEN. BEAUREGARD, COMMANDING THE SOUTH, MET", "UNION FORCES WITH GEN. MCDOWELL IN A PREMATURE BATTLE AT", "BULL RUN. GEN. JACKSON HELPED PUSH BACK THE UNION ATTACK."}),
+ new HistoricalDatum("SHILOH", new ArmyPair<>(40000, 44894), new ArmyPair<>(10699, 13047), OffensiveStatus.OFFENSIVE, new String[]{"APRIL 6-7, 1862. THE CONFEDERATE SURPRISE ATTACK AT", "SHILOH FAILED DUE TO POOR ORGANIZATION."}),
+ new HistoricalDatum("SEVEN DAYS", new ArmyPair<>(95000, 115000), new ArmyPair<>(20614, 15849), OffensiveStatus.OFFENSIVE, new String[]{"JUNE 25-JULY 1, 1862. GENERAL LEE (CSA) UPHELD THE", "OFFENSIVE THROUGHOUT THE BATTLE AND FORCED GEN. MCCLELLAN", "AND THE UNION FORCES AWAY FROM RICHMOND."}),
+ new HistoricalDatum("SECOND BULL RUN", new ArmyPair<>(54000, 63000), new ArmyPair<>(10000, 14000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"AUG 29-30, 1862. THE COMBINED CONFEDERATE FORCES UNDER", " LEE", "AND JACKSON DROVE THE UNION FORCES BACK INTO WASHINGTON."}),
+ new HistoricalDatum("ANTIETAM", new ArmyPair<>(40000, 50000), new ArmyPair<>(10000, 12000), OffensiveStatus.OFFENSIVE, new String[]{"SEPT 17, 1862. THE SOUTH FAILED TO INCORPORATE MARYLAND", "INTO THE CONFEDERACY."}),
+ new HistoricalDatum("FREDERICKSBURG", new ArmyPair<>(75000, 120000), new ArmyPair<>(5377, 12653), OffensiveStatus.DEFENSIVE, new String[]{"DEC 13, 1862. THE CONFEDERACY UNDER LEE SUCCESSFULLY", "REPULSED AN ATTACK BY THE UNION UNDER GEN. BURNSIDE."}),
+ new HistoricalDatum("MURFREESBORO", new ArmyPair<>(38000, 45000), new ArmyPair<>(11000, 12000), OffensiveStatus.DEFENSIVE, new String[]{"DEC 31, 1862. THE SOUTH UNDER GEN. BRAGG WON A CLOSE BATTLE."}),
+ new HistoricalDatum("CHANCELLORSVILLE", new ArmyPair<>(32000, 90000), new ArmyPair<>(13000, 17197), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 1-6, 1863. THE SOUTH HAD A COSTLY VICTORY AND LOST", "ONE OF THEIR OUTSTANDING GENERALS, 'STONEWALL' JACKSON."}),
+ new HistoricalDatum("VICKSBURG", new ArmyPair<>(50000, 70000), new ArmyPair<>(12000, 19000), OffensiveStatus.DEFENSIVE, new String[]{"JULY 4, 1863. VICKSBURG WAS A COSTLY DEFEAT FOR THE SOUTH", "BECAUSE IT GAVE THE UNION ACCESS TO THE MISSISSIPPI."}),
+ new HistoricalDatum("GETTYSBURG", new ArmyPair<>(72500, 85000), new ArmyPair<>(20000, 23000), OffensiveStatus.OFFENSIVE, new String[]{"JULY 1-3, 1863. A SOUTHERN MISTAKE BY GEN. LEE AT GETTYSBURG", "COST THEM ONE OF THE MOST CRUCIAL BATTLES OF THE WAR."}),
+ new HistoricalDatum("CHICKAMAUGA", new ArmyPair<>(66000, 60000), new ArmyPair<>(18000, 16000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"SEPT. 15, 1863. CONFUSION IN A FOREST NEAR CHICKAMAUGA LED", "TO A COSTLY SOUTHERN VICTORY."}),
+ new HistoricalDatum("CHATTANOOGA", new ArmyPair<>(37000, 60000), new ArmyPair<>(36700, 5800), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"NOV. 25, 1863. AFTER THE SOUTH HAD SIEGED GEN. ROSENCRANS'", "ARMY FOR THREE MONTHS, GEN. GRANT BROKE THE SIEGE."}),
+ new HistoricalDatum("SPOTSYLVANIA", new ArmyPair<>(62000, 110000), new ArmyPair<>(17723, 18000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 5, 1864. GRANT'S PLAN TO KEEP LEE ISOLATED BEGAN TO", "FAIL HERE, AND CONTINUED AT COLD HARBOR AND PETERSBURG."}),
+ new HistoricalDatum("ATLANTA", new ArmyPair<>(65000, 100000), new ArmyPair<>(8500, 3700), OffensiveStatus.DEFENSIVE, new String[]{"AUGUST, 1864. SHERMAN AND THREE VETERAN ARMIES CONVERGED", "ON ATLANTA AND DEALT THE DEATH BLOW TO THE CONFEDERACY."})
+ );
+ }
+
+ private void showCredits() {
+ out.println(" ".repeat(26) + "CIVIL WAR");
+ out.println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ out.println();
+ out.println();
+ out.println();
+ }
+
+ private void updateStrategies(int strategy) {
+ // REM LEARN PRESENT STRATEGY, START FORGETTING OLD ONES
+ // REM - PRESENT STRATEGY OF SOUTH GAINS 3*S, OTHERS LOSE S
+ // REM PROBABILITY POINTS, UNLESS A STRATEGY FALLS BELOW 5%.
+
+ var S = 3;
+ var S0 = 0;
+ for (int i = 0; i < 4; i++) {
+ if (this.strategies[i] <= 5) {
+ continue;
+ }
+
+ this.strategies[i] -= S;
+ S0 += S;
+
+ }
+ this.strategies[strategy - 1] += S0;
+
+ }
+
+ private void showHelp() {
+ out.println();
+ out.println();
+ out.println();
+ out.println();
+ out.println("THIS IS A CIVIL WAR SIMULATION.");
+ out.println("TO PLAY TYPE A RESPONSE WHEN THE COMPUTER ASKS.");
+ out.println("REMEMBER THAT ALL FACTORS ARE INTERRELATED AND THAT YOUR");
+ out.println("RESPONSES COULD CHANGE HISTORY. FACTS AND FIGURES USED ARE");
+ out.println("BASED ON THE ACTUAL OCCURRENCE. MOST BATTLES TEND TO RESULT");
+ out.println("AS THEY DID IN THE CIVIL WAR, BUT IT ALL DEPENDS ON YOU!!");
+ out.println();
+ out.println("THE OBJECT OF THE GAME IS TO WIN AS MANY BATTLES AS ");
+ out.println("POSSIBLE.");
+ out.println();
+ out.println("YOUR CHOICES FOR DEFENSIVE STRATEGY ARE:");
+ out.println(" (1) ARTILLERY ATTACK");
+ out.println(" (2) FORTIFICATION AGAINST FRONTAL ATTACK");
+ out.println(" (3) FORTIFICATION AGAINST FLANKING MANEUVERS");
+ out.println(" (4) FALLING BACK");
+ out.println(" YOUR CHOICES FOR OFFENSIVE STRATEGY ARE:");
+ out.println(" (1) ARTILLERY ATTACK");
+ out.println(" (2) FRONTAL ATTACK");
+ out.println(" (3) FLANKING MANEUVERS");
+ out.println(" (4) ENCIRCLEMENT");
+ out.println("YOU MAY SURRENDER BY TYPING A '5' FOR YOUR STRATEGY.");
+ }
+
+ private static final int MAX_NUM_LENGTH = 6;
+
+ private String rightAlignInt(int number) {
+ var s = String.valueOf(number);
+ return " ".repeat(MAX_NUM_LENGTH - s.length()) + s;
+ }
+
+ private String rightAlignInt(double number) {
+ return rightAlignInt((int) Math.floor(number));
+ }
+
+ private static String inputString(Predicate validator, String reminder) {
+ while (true) {
+ try {
+ var input = new Scanner(System.in).nextLine();
+ if (validator.test(input)) {
+ return input;
+ }
+ } catch (InputMismatchException e) {
+ // Ignore
+ }
+ System.out.println(reminder);
+ }
+ }
+
+ private static int inputInt(Predicate validator, Function reminder) {
+ while (true) {
+ try {
+ var input = new Scanner(System.in).nextInt();
+ if (validator.test(input)) {
+ return input;
+ }
+ System.out.println(reminder.apply(input));
+ } catch (InputMismatchException e) {
+ System.out.println(reminder.apply(0));
+ }
+ }
+ }
+
+ private static boolean isYes(String s) {
+ if (s == null) {
+ return false;
+ }
+ var uppercase = s.toUpperCase();
+ return uppercase.equals("Y") || uppercase.equals("YES");
+ }
+
+ private static boolean isNo(String s) {
+ if (s == null) {
+ return false;
+ }
+ var uppercase = s.toUpperCase();
+ return uppercase.equals("N") || uppercase.equals("NO");
+ }
+
+ private static class BattleState {
+ private final HistoricalDatum data;
+ private double F1;
+
+ public BattleState(HistoricalDatum data) {
+ this.data = data;
+ }
+ }
+
+ private static class ArmyPair {
+ private T confederate;
+ private T union;
+
+ public ArmyPair(T confederate, T union) {
+ this.confederate = confederate;
+ this.union = union;
+ }
+ }
+
+ private static class BattleResults {
+ private int confederate;
+ private int union;
+ private int indeterminate;
+
+ public int getTotal() {
+ return confederate + union + indeterminate;
+ }
+ }
+
+ private static class ArmyResources {
+ private int food;
+ private int salaries;
+ private int ammunition;
+ private int budget;
+
+ private double morale; // TODO really here?
+
+ public int getTotal() {
+ return this.food + this.salaries + this.ammunition;
+ }
+ }
+
+ private record HistoricalDatum(String name, ArmyPair troops,
+ ArmyPair expectedCasualties,
+ OffensiveStatus offensiveStatus, String[] blurb) {
+ }
+
+ private record UnionLosses(int losses, int desertions) {
+ }
+}
\ No newline at end of file
diff --git a/29_Craps/README.md b/29_Craps/README.md
index 7372b99f..3c45c62b 100644
--- a/29_Craps/README.md
+++ b/29_Craps/README.md
@@ -17,3 +17,211 @@ As published in Basic Computer Games (1978):
Downloaded from Vintage Basic at
http://www.vintage-basic.net/games.html
+
+### Comments on the BASIC code for re-implementers.
+
+ 15 LET R=0
+`R` is a variable that tracks winnings and losings. Unlike other games that
+start out with a lump sum of cash to spend this game assumes the user has as
+much money as they want and we only track how much they lost or won.
+
+ 21 LET T=1
+ 22 PRINT "PICK A NUMBER AND INPUT TO ROLL DICE";
+ 23 INPUT Z
+ 24 LET X=(RND(0))
+ 25 LET T =T+1
+ 26 IF T<=Z THEN 24
+This block of code does nothing other than try to scramble the random number
+generator. Random number generation is not random, they are generated from the
+previous generated number. Because of the slow speed of these systems back then,
+gaming random number generators was a concern, mostly for gameplay quality.
+If you could know the "seed value" to the generator then you could effectively
+know how to get the exact same dice rolls to happen and change your bet to
+maximize your winnings and minimize your losses.
+
+The first reason this is an example of bad coding practice is the user is asked
+to input a number but no clue is given as to the use of this number. This number
+has no bearing on the game and as we'll see only has bearing on the internal
+implementation of somehow trying to get an un-game-able seed for the random number
+generator (since all future random numbers generated are based off this seed value.)
+
+The `RND(1)` command generates a number from a seed value that is always
+the same, everytime, from when the machine is booted up (old C64 behavior). In
+order to avoid the same dice rolls being generated, a special call to `RND(-TI)`
+would initialize the random generator with something else. But RND(-TI) is not
+a valid command on all systems. So `RND(0)`, which generates a random number
+from the system clock is used. But technically this could be gamed because the
+system clock was driven by the bootup time, there wasn't a BIOS battery on these
+systems that kept an internal real time clock going even when the system was
+turned off, unlike your regular PC. Therefore, in order to ensure as true
+randomness as possible, insert human reaction time by asking for human input.
+
+But a human could just be holding down the enter key on bootup and that would
+just skip any kind of multi-millisecond variance assigned by a natural human
+reaction time. So, paranoia being a great motivator, a number is asked of the
+user to avoid just holding down the enter key which negates the timing variance
+of a human reaction.
+
+What comes next is a bit of nonsense. The block of code loops a counter, recalling
+the `RND(0)` function (and thus reseeding it with the system clock value)
+and then comparing the counter to the user's number input
+in order to bail out of the loop. Because the `RND(0)` function is based off the
+system clock and the loop of code has no branching other than the bailout
+condition, the loop also takes a fixed amount of time to execute, thus making
+repeated calls to `RND(0)` predictive and this scheming to get a better random
+number is pointless. Furthermore, the loop is based on the number the user inputs
+so a huge number like ten million causes a very noticable delay and leaves the
+user wondering if the program has errored. The author could have simply called
+`RND(0)` once and used a prompt that made more sense like asking for the users
+name and then using that name in the game's replies.
+
+It is advised that you use whatever your languages' random number generator
+provides and simply skip trying to recreate this bit of nonsense including
+the user input.
+
+ 27 PRINT"INPUT THE AMOUNT OF YOUR WAGER.";
+ 28 INPUT F
+ 30 PRINT "I WILL NOW THROW THE DICE"
+ 40 LET E=INT(7*RND(1))
+ 41 LET S=INT(7*RND(1))
+ 42 LET X=E+S
+ .... a bit later ....
+ 60 IF X=1 THEN 40
+ 65 IF X=0 THEN 40
+
+
+`F` is a variable that represents the users wager for this betting round.
+`E` and `S` represent the two individual and random dice being rolled.
+This code is actually wrong because it returns a value between 0 and 6.
+`X` is the sum of these dice rolls. As you'll see though further down in the
+code, if `X` is zero or one it re-rolls the dice to maintain a potential
+outcome of the sum of two dice between 2 and 12. This skews the normal distribution
+of dice values to favor lower numbers because it does not consider that `E`
+could be zero and `S` could be 2 or higher. To show this skewing of values
+you can run the `distribution.bas` program which creates a histogram of the
+distribution of the bad dice throw code and proper dice throw code.
+
+Here are the results:
+
+ DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)
+ THE INT(7*RND(1)) DISTRIBUTION:
+ 2 3 4 5 6 7 8 9 10 11 12
+ 6483 8662 10772 13232 15254 13007 10746 8878 6486 4357 2123
+ THE INT(6*RND(1)+1) DISTRIBUTION
+ 2 3 4 5 6 7 8 9 10 11 12
+ 2788 5466 8363 11072 13947 16656 13884 11149 8324 5561 2790
+If the dice rolls are fair then we should see the largest occurrence be a 7 and
+the smallest should be 2 and 12. Furthermore the occurrences should be
+symetrical meaning there should be roughly the same amount of 2's as 12's, the
+same amount of 3's as 11's, 4's as 10's and so on until you reach the middle, 7.
+But notice in the skewed dice roll, 6 is the most rolled number not 7, and the
+rest of the numbers are not symetrical, there are many more 2's than 12's.
+So the lesson is test your code.
+
+The proper way to model a dice throw, in almost every language is
+ `INT(6*RND(1)+1)` or `INT(6*RND(1))+1`
+
+SideNote: `X` was used already in the
+previous code block discussed but its value was never used. This is another
+poor coding practice: **Don't reuse variable names for different purposes.**
+
+ 50 IF X=7 THEN 180
+ 55 IF X=11 THEN 180
+ 60 IF X=1 THEN 40
+ 62 IF X=2 THEN 195
+ 65 IF X=0 THEN 40
+ 70 IF X=2 THEN 200
+ 80 IF X=3 THEN 200
+ 90 IF X=12 THEN 200
+ 125 IF X=5 THEN 220
+ 130 IF X =6 THEN 220
+ 140 IF X=8 THEN 220
+ 150 IF X=9 THEN 220
+ 160 IF X =10 THEN 220
+ 170 IF X=4 THEN 220
+
+This bit of code determines the routing of where to go for payout, or loss.
+Of course, line 60 and 65 are pointless as we've just shown and should be removed
+as long as the correct dice algorithm is also changed.
+
+ 62 IF X=2 THEN 195
+ ....
+ 70 IF X=2 THEN 200
+The check for a 2 has already been made and the jump is done. Line 70 is
+therefore redundant and can be left out. The purpose of line 62 is only to
+print a special output, "SNAKE EYES!" which we'll see in the next block creates
+duplicate code.
+
+Lines 125-170 are also pointlessly checked because we know previous values have
+been ruled out, only these last values must remain, and they are all going to
+the same place, line 220. Line 125-170 could have simply been replaced with
+`GOTO 220`
+
+
+
+ 180 PRINT X "- NATURAL....A WINNER!!!!"
+ 185 PRINT X"PAYS EVEN MONEY, YOU WIN"F"DOLLARS"
+ 190 GOTO 210
+ 195 PRINT X"- SNAKE EYES....YOU LOSE."
+ 196 PRINT "YOU LOSE"F "DOLLARS."
+ 197 LET F=0-F
+ 198 GOTO 210
+ 200 PRINT X " - CRAPS...YOU LOSE."
+ 205 PRINT "YOU LOSE"F"DOLLARS."
+ 206 LET F=0-F
+ 210 LET R= R+F
+ 211 GOTO 320
+
+This bit of code manages instant wins or losses due to 7,11 or 2,3,12. As
+mentioned previously, lines 196 and 197 are essentially the same as lines
+205 and 206. A simpler code would be just to jump after printing the special
+message of "SNAKE EYES!" to line 205.
+
+Lines 197 and 206 just negate the wager by subtracting it from zero. Just saying
+`F = -F` would have sufficed. Line 210 updates your running total of winnings
+or losses with this bet.
+
+ 220 PRINT X "IS THE POINT. I WILL ROLL AGAIN"
+ 230 LET H=INT(7*RND(1))
+ 231 LET Q=INT(7*RND(1))
+ 232 LET O=H+Q
+ 240 IF O=1 THEN 230
+ 250 IF O=7 THEN 290
+ 255 IF O=0 THEN 230
+
+This code sets the point, the number you must re-roll to win without rolling
+a 7, the most probable number to roll. Except in this case again, it has the
+same incorrect dice rolling code and therefore 6 is the most probable number
+to roll. The concept of DRY (don't repeat yourself) is a coding practice which
+encourages non-duplication of code because if there is an error in the code, it
+can be fixed in one place and not multiple places like in this code. The scenario
+might be that a programmer sees some wrong code, fixes it, but neglects to
+consider that there might be duplicates of the same wrong code elsewhere. If
+you practice DRY then you never worry much about behaviors in your code diverging
+due to duplicate code snippets.
+
+ 260 IF O=X THEN 310
+ 270 PRINT O " - NO POINT. I WILL ROLL AGAIN"
+ 280 GOTO 230
+ 290 PRINT O "- CRAPS. YOU LOSE."
+ 291 PRINT "YOU LOSE $"F
+ 292 F=0-F
+ 293 GOTO 210
+ 300 GOTO 320
+ 310 PRINT X"- A WINNER.........CONGRATS!!!!!!!!"
+ 311 PRINT X "AT 2 TO 1 ODDS PAYS YOU...LET ME SEE..."2*F"DOLLARS"
+ 312 LET F=2*F
+ 313 GOTO 210
+
+This is the code to keep rolling until the point is made or a seven is rolled.
+Again we see the negated `F` wager and lose message duplicated. This code could
+have been reorganized using a subroutine, or in BASIC, the GOSUB command, but
+in your language its most likely just known as a function or method. You can
+do a `grep -r 'GOSUB'` from the root directory to see other BASIC programs in
+this set that use GOSUB.
+
+The rest of the code if fairly straight forward, replay the game or end with
+a report of your winnings or losings.
+
+
+
diff --git a/29_Craps/distributions.bas b/29_Craps/distributions.bas
new file mode 100644
index 00000000..a963b228
--- /dev/null
+++ b/29_Craps/distributions.bas
@@ -0,0 +1,24 @@
+10 PRINT "DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)"
+20 DIM A(12)
+30 DIM B(12)
+100 FOR X = 1 TO 100000 : REM CHOOSE A LARGE NUMBER TO GET A FINER GRAINED HISTOGRAM
+140 REM GET A NUMBER FROM 0 TO 6 INCLUSIVE WITH THE INTENT TO THROW AWAY ZEROES.
+150 LET D1 = INT(7*RND(1))
+155 LET D2 = INT(7*RND(1))
+160 LET S1 = D1+D2
+165 REM IF THIS SUM IS LESS THAN TWO THEN TRY AGAIN.
+170 IF S1<2 THEN 150
+199 REM GET A NUMBER FROM 0 TO 5 THEN ADD 1 TO IT TO MAKE IT 1 TO 6
+200 LET D3 = INT(6*RND(1))+1
+210 LET D4 = INT(6*RND(1))+1
+220 LET S2 = D3+D4
+245 REM USE OUR ARRAY AS A HISTOGRAM, COUNTING EACH OCCURRENCE OF DICE ROLL
+250 A(S1) = A(S1) + 1
+260 B(S2) = B(S2) + 1
+290 NEXT X
+300 PRINT "THE INT(7*RND(1)) DISTRIBUTION:"
+310 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
+320 FOR I = 2 TO 12 :PRINT A(I),:NEXT:PRINT
+325 PRINT "THE INT(6*RND(1)+1) DISTRIBUTION"
+330 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
+340 FOR I = 2 TO 12 :PRINT B(I),:NEXT:PRINT
diff --git a/29_Craps/java/src/Craps.java b/29_Craps/java/src/Craps.java
new file mode 100644
index 00000000..483c16a9
--- /dev/null
+++ b/29_Craps/java/src/Craps.java
@@ -0,0 +1,125 @@
+import java.util.Random;
+import java.util.Scanner;
+
+/**
+ * Port of Craps from BASIC to Java 17.
+ */
+public class Craps {
+ public static final Random random = new Random();
+
+ public static void main(String[] args) {
+ System.out.println("""
+ CRAPS
+ CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
+
+
+ 2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.
+ """);
+ double winnings = 0.0;
+ do {
+ winnings = playCraps(winnings);
+ } while (stillInterested(winnings));
+ winningsReport(winnings);
+ }
+
+ public static double playCraps(double winnings) {
+ double wager = getWager();
+ System.out.println("I WILL NOW THROW THE DICE");
+ int roll = rollDice();
+ double payout = switch (roll) {
+ case 7, 11 -> naturalWin(roll, wager);
+ case 2, 3, 12 -> lose(roll, wager);
+ default -> setPoint(roll, wager);
+ };
+ return winnings + payout;
+ }
+
+ public static int rollDice() {
+ return random.nextInt(1, 7) + random.nextInt(1, 7);
+ }
+
+ private static double setPoint(int point, double wager) {
+ System.out.printf("%1$ d IS THE POINT. I WILL ROLL AGAIN%n",point);
+ return makePoint(point, wager);
+ }
+
+ private static double makePoint(int point, double wager) {
+ int roll = rollDice();
+ if (roll == 7)
+ return lose(roll, wager);
+ if (roll == point)
+ return win(roll, wager);
+ System.out.printf("%1$ d - NO POINT. I WILL ROLL AGAIN%n", roll);
+ return makePoint(point, wager); // recursive
+ }
+
+ private static double win(int roll, double wager) {
+ double payout = 2 * wager;
+ System.out.printf("%1$ d - A WINNER.........CONGRATS!!!!!!!!%n", roll);
+ System.out.printf("%1$ d AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$%2$3.2f%n",
+ roll, payout);
+ return payout;
+ }
+
+ private static double lose(int roll, double wager) {
+ String msg = roll == 2 ? "SNAKE EYES.":"CRAPS";
+ System.out.printf("%1$ d - %2$s...YOU LOSE.%n", roll, msg);
+ System.out.printf("YOU LOSE $%3.2f%n", wager);
+ return -wager;
+ }
+
+ public static double naturalWin(int roll, double wager) {
+ System.out.printf("%1$ d - NATURAL....A WINNER!!!!%n", roll);
+ System.out.printf("%1$ d PAYS EVEN MONEY, YOU WIN $%2$3.2f%n", roll, wager);
+ return wager;
+ }
+
+ public static void winningsUpdate(double winnings) {
+ System.out.println(switch ((int) Math.signum(winnings)) {
+ case 1 -> "YOU ARE NOW AHEAD $%3.2f".formatted(winnings);
+ case 0 -> "YOU ARE NOW EVEN AT 0";
+ default -> "YOU ARE NOW UNDER $%3.2f".formatted(-winnings);
+ });
+ }
+
+ public static void winningsReport(double winnings) {
+ System.out.println(
+ switch ((int) Math.signum(winnings)) {
+ case 1 -> "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!";
+ case 0 -> "CONGRATULATIONS---YOU CAME OUT EVEN, NOT BAD FOR AN AMATEUR";
+ default -> "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN.";
+ }
+ );
+ }
+
+ public static boolean stillInterested(double winnings) {
+ System.out.print(" IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2 ");
+ int fiveOrTwo = (int)getInput();
+ winningsUpdate(winnings);
+ return fiveOrTwo == 5;
+ }
+
+ public static double getWager() {
+ System.out.print("INPUT THE AMOUNT OF YOUR WAGER. ");
+ return getInput();
+ }
+
+ public static double getInput() {
+ Scanner scanner = new Scanner(System.in);
+ System.out.print("> ");
+ while (true) {
+ try {
+ return scanner.nextDouble();
+ } catch (Exception ex) {
+ try {
+ scanner.nextLine(); // flush whatever this non number stuff is.
+ } catch (Exception ns_ex) { // received EOF (ctrl-d or ctrl-z if windows)
+ System.out.println("END OF INPUT, STOPPING PROGRAM.");
+ System.exit(1);
+ }
+ }
+ System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
+ System.out.print("> ");
+ }
+ }
+}
diff --git a/29_Craps/javascript/craps.js b/29_Craps/javascript/craps.js
index 93351011..108f6067 100644
--- a/29_Craps/javascript/craps.js
+++ b/29_Craps/javascript/craps.js
@@ -42,6 +42,11 @@ function tab(space)
return str;
}
+function roll()
+{
+ return Math.floor(6 * Math.random())+1 + Math.floor(6 * Math.random())+1;
+}
+
// Main program
async function main()
{
@@ -52,22 +57,11 @@ async function main()
print("\n");
r = 0;
print("2,3,12 ARE LOSERS: 4,5,6,8,9,10 ARE POINTS: 7,11 ARE NATURAL WINNERS.\n");
- t = 1;
- print("PICK A NUMBER AND INPUT TO ROLL DICE");
- z = parseInt(await input());
- do {
- x = Math.random();
- t++;
- } while (t <= z) ;
while (1) {
print("INPUT THE AMOUNT OF YOUR WAGER.");
f = parseInt(await input());
print("I WILL NOW THROW THE DICE\n");
- do {
- e = Math.floor(7 * Math.random());
- s = Math.floor(7 * Math.random());
- x = e + s;
- } while (x == 0 || x == 1) ;
+ x = roll();
if (x == 7 || x == 11) {
print(x + " - NATURAL....A WINNER!!!!\n");
print(x + " PAYS EVEN MONEY, YOU WIN " + f + " DOLLARS\n");
@@ -83,11 +77,7 @@ async function main()
} else {
print(x + " IS THE POINT. I WILL ROLL AGAIN\n");
while (1) {
- do {
- h = Math.floor(7 * Math.random());
- q = Math.floor(7 * Math.random());
- o = h + q;
- } while (o == 0 || o == 1) ;
+ o = roll();
if (o == 7) {
print(o + " - CRAPS, YOU LOSE.\n");
print("YOU LOSE $" + f + "\n");
diff --git a/29_Craps/perl/craps.pl b/29_Craps/perl/craps.pl
new file mode 100644
index 00000000..7dd39856
--- /dev/null
+++ b/29_Craps/perl/craps.pl
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+#
+
+my $bank = 0;
+
+&main;
+
+sub main {
+ &print_intro;
+ my $continue=5;
+ until ($continue != 5) {
+ $continue=&game_play;
+ &print_bank;
+ }
+ &final_bank;
+}
+
+sub game_play {
+ my $point = 0;
+ my $continue = 1;
+ print "INPUT THE AMOUNT OF YOUR WAGER.\n";
+ chomp(my $wager=);
+ print "I WILL NOW THROW THE DICE\n";
+ until ($continue == 0) {
+ my $roll = &roll_dice;
+ $continue = &check_value($roll,$wager);
+ }
+ print "IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2\n";
+ chomp(my $ans=);
+ return $ans;
+}
+
+sub print_bank {
+ if ($bank < 0) {
+ print "YOU ARE NOW UNDER \$$bank\n";
+ }
+ elsif ($bank > 0) {
+ print "YOU ARE NOW AHEAD \$$bank\n";
+ }
+ else {
+ print "YOU ARE EVEN AT 0\n";
+ }
+}
+
+sub final_bank {
+ if ($bank < 0) {
+ print "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN\n";
+ }
+ elsif ($bank > 0) {
+ print "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!\n";
+ }
+ else {
+ print "CONGRATULATIONS---YOU CAME OUT EVEN. NOT BAD FOR AN AMATEUR!\n";
+ }
+}
+
+sub check_value {
+ my $roll = shift;
+ my $wager = shift;
+ if ($roll == 7 || $roll == 11) {
+ print "$roll - NATURAL....A WINNER!!!!\n";
+ print "$roll PAYS EVEN MONEY, YOU WIN $wager DOLLARS\n";
+ $bank += $wager;
+ return 0;
+ }
+ elsif ($roll == 2 || $roll == 3 || $roll == 12) {
+ if ($roll == 2) {
+ print "$roll - SNAKE EYES....YOU LOSE.\n";
+ print "YOU LOSE $wager DOLLARS.\n";
+ }
+ else {
+ print "$roll - CRAPS...YOU LOSE.\n";
+ print "YOU LOSE $wager DOLLARS.\n";
+ }
+ $bank -= $wager;
+ return 0;
+ }
+ else {
+ my $point = $roll;
+ print "$point IS THE POINT. I WILL ROLL AGAIN\n";
+ until (1==2) {
+ $roll = &roll_dice;
+ if ($roll == 7) {
+ print "$roll YOU LOSE $wager\n";
+ $bank -= $wager;
+ return 0;
+ }
+ elsif ($roll == $point) {
+ print "$roll - A WINNER..........CONGRATS!!!!!!!!\n";
+ my $payout = $wager * 2;
+ print "$roll AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$payout DOLLARS\n";
+ $bank += $payout;
+ return 0;
+ }
+ else {
+ print "$roll - NO POINT. I WILL ROLL AGAIN\n";
+ sleep(1);
+ }
+ }
+ }
+}
+
+sub roll_dice {
+ my $die1 = 1+int rand(6);
+ my $die2 = 1+int rand(6);
+ return ($die1 + $die2);
+}
+
+sub print_intro {
+ print ' ' x 33 . "CRAPS\n";
+ print ' ' x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
+ print "2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.\n";
+}
diff --git a/30_Cube/ruby/cube.rb b/30_Cube/ruby/cube.rb
new file mode 100644
index 00000000..a881986e
--- /dev/null
+++ b/30_Cube/ruby/cube.rb
@@ -0,0 +1,261 @@
+$landmines = Array.new
+
+$currentLocation = "111"
+
+$standings = 500 # starting amount
+
+def getYesOrNoResponseTo prompt
+ print prompt
+ # strip leading and trailing whitespace from entry
+ yesno = gets.strip.upcase[0]
+ yesno == "Y"
+end
+
+def greeting
+ puts "CUBE".center(80)
+ puts "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY".center(80)
+ puts "\n\n\n"
+ response = getYesOrNoResponseTo "Do you want to see the INSTRUCTIONS?"
+ if response
+ puts "This is a game in which you will be playing against the"
+ puts "random decision of the computer. The field of play is a"
+ puts "cube of size 3. Any of your 27 locations can be designated"
+ puts "by inputting three numbers such as 231."
+ puts "At the start you are automatically at location 1,1,1.\n"
+ puts "The object of the game is to get to location 3,3,3.\n"
+
+ puts "\nONE MINOR DETAIL:"
+ puts "The computer will pick five random locations at which it will"
+ puts "plant land mines. if you hit one of these locations you lose.\n"
+
+ puts "\nONE OTHER DETAIL:"
+ puts "You may move only one space in one direction each move."
+ puts "For example: from 1,1,2 you may move to 2,1,2 or 1,1,3."
+ puts "You may not change more than one number on the same move."
+ puts "If you make an illegal move, you lose and the computer takes"
+ puts "the money you may have bet on that round."
+ puts ""
+ puts "When stating the amount of a wager, enter only the number"
+ puts "of dollars (example: 250) You are automatically started with"
+ puts "500 dollars in your account."
+ puts
+ puts "Good luck!"
+ end
+end
+
+def landMindStringFrom x, y, z
+ landMine = x.to_s + y.to_s + z.to_s
+ return landMine
+end
+
+def assignLandMines
+ $landmines.clear
+
+ # put five unique entries into the landmines array
+ while $landmines.size < 5 do
+ a = rand(3)+1
+ b = rand(3)+1
+ c = rand(3)+1
+ landmine = landMindStringFrom(a, b, c)
+ if !$landmines.include?(landmine) && landmine != "333"
+# puts landmine # debugging
+ $landmines.push landmine
+ end
+ end
+ $currentLocation = "111"
+end
+
+def initializePot
+ $standings = 500 # starting amount
+end
+
+def startGame
+ assignLandMines
+ displayStandings
+ response = getYesOrNoResponseTo "WANT TO MAKE A WAGER? "
+ if response
+ print "HOW MUCH? "
+ while true do
+ wager = gets.strip.tr('^0-9', '').to_i
+ if $standings < wager
+ puts "TRIED TO FOOL ME; BET AGAIN ";
+ else
+ break
+ end
+ end
+ else
+ wager = 0
+ end
+
+ # start at location 1,1,1
+ $currentLocation = "111"
+ return wager
+end
+
+def goodbye
+ puts "TOUGH LUCK!"
+ puts ""
+ puts "GOODBYE."
+ exit
+end
+
+def bust
+ puts "YOU BUST."
+ goodbye
+end
+
+def tryAgain
+ again = getYesOrNoResponseTo "WANT TO TRY AGAIN? "
+ if not again
+ exit
+ end
+end
+
+def isLegalMove? newLocation
+ # test for illegal moves
+ # can only change one variable per move
+ # newLocation is the proposed new position
+ # can only move one space from the current position
+
+ moveX = newLocation[0].to_i
+ moveY = newLocation[1].to_i
+ moveZ = newLocation[2].to_i
+
+ # currentX, currentY, currentZ contains the current position
+ currentX = $currentLocation[0].to_i
+ currentY = $currentLocation[1].to_i
+ currentZ = $currentLocation[2].to_i
+
+ isLegalMove = true
+ errorString = ""
+ # ensure we're not moving off the cube
+ if not (moveX.between?(1,3) && moveY.between?(1,3) && moveZ.between?(1,3))
+ errorString = "You moved off the cube!"
+ return errorString
+ end
+
+ # test for only one move from current position
+ if not moveX.between?(currentX-1,currentX+1)
+ isLegalMove = false
+ end
+ if not moveY.between?(currentY-1,currentY+1)
+ isLegalMove = false
+ end
+ if not moveZ.between?(currentZ-1,currentZ+1)
+ isLegalMove = false
+ end
+ if not isLegalMove
+ errorString = "You've gone too far"
+ end
+
+ # only allow change to one variable at a time
+ if isLegalMove
+ if moveX != currentX
+ if moveY != currentY or moveZ != currentZ
+ isLegalMove = false
+ end
+ end
+ if moveY != currentY
+ if moveX != currentX or moveZ != currentZ
+ isLegalMove = false
+ end
+ end
+ if moveZ != currentZ
+ if moveY != currentY or moveX != currentX
+ isLegalMove = false
+ end
+ end
+ if not isLegalMove
+ errorString = "You made too many changes"
+ end
+ end
+
+ return errorString
+end
+
+def displayStandings
+ print "You now have " + $standings.to_s
+ if $standings > 1
+ puts " dollars"
+ else
+ puts " dollar"
+ end
+end
+
+def didWin? location
+ location == "333"
+end
+
+def youWin amount
+ $standings += amount
+ puts "*** You win " + amount.to_s + " dollars! ***\n\n"
+ displayStandings
+ tryAgain
+ assignLandMines
+ puts "*** new cube ***"
+ puts "different landmine locations"
+ puts "starting over at location 111"
+end
+
+def youLose amount
+ # subtract the bet amount from the standings
+ if amount > 0
+ puts "You lose " + amount.to_s + " dollars!\n\n"
+ $standings -= amount
+ end
+ if $standings <= 0
+ # no money left, so end the game
+ bust
+ else
+ displayStandings
+ end
+ tryAgain
+ $currentLocation = "111"
+ puts "starting over at location 111"
+end
+
+def landMine betAmount
+ puts "******BANG******"
+ puts "You hit a land mine at " + $currentLocation + "!"
+ youLose betAmount
+end
+
+def gameLoop betAmount
+ while true do
+ puts ""
+ print "IT'S YOUR MOVE: "
+ # allow only integers: strip anything else from input
+ moveToLocation = gets.strip.tr('^0-9', '')
+
+ # test for illegal moves
+ # can only change one variable per move
+ # moveToLocation is the proposed new position
+
+ error = isLegalMove?(moveToLocation)
+ if error == ""
+ # assign the new position
+ $currentLocation = moveToLocation
+
+ # test for win
+ if didWin?(moveToLocation)
+ youWin betAmount
+ end
+
+ # haven't won yet, test the land mines
+ if $landmines.include? moveToLocation
+ landMine betAmount
+ end
+
+ else
+ puts "Illegal move: " + error
+ youLose betAmount
+ end
+
+ end
+end
+
+
+greeting
+initializePot
+gameLoop startGame
+
diff --git a/40_Gomoko/perl/gomoko.pl b/40_Gomoko/perl/gomoko.pl
new file mode 100644
index 00000000..9bd2f3db
--- /dev/null
+++ b/40_Gomoko/perl/gomoko.pl
@@ -0,0 +1,121 @@
+#!/usr/bin/perl
+use strict;
+
+
+print ' 'x 33 . "GOMOKO\n";
+print ' 'x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
+print "\n"; print "\n"; print "\n";
+#my @A;
+print "WELCOME TO THE ORIENTAL GAME OF GOMOKO.\n";
+print "\n"; print "THE GAME IS PLAYED ON AN N BY N GRID OF A SIZE\n";
+print "THAT YOU SPECIFY. DURING YOUR PLAY, YOU MAY COVER ONE GRID\n";
+print "INTERSECTION WITH A MARKER. THE OBJECT OF THE GAME IS TO GET\n";
+print "5 ADJACENT MARKERS IN A ROW -- HORIZONTALLY, VERTICALLY, OR\n";
+print "DIAGONALLY. ON THE BOARD DIAGRAM, YOUR MOVES ARE MARKED\n";
+print "WITH A '1' AND THE COMPUTER MOVES WITH A '2'.\n";
+print "\n"; print "THE COMPUTER DOES NOT KEEP TRACK OF WHO HAS WON.\n";
+print "TO END THE GAME, TYPE -1,-1 FOR YOUR MOVE.\n"; print "\n";
+
+
+my $Ret;
+my $I;
+my $J;
+
+my @Board;
+my $Size= 0;
+
+
+while (1) {
+
+ do {
+ $Size= 0;
+ print "WHAT IS YOUR BOARD SIZE (MIN 7/ MAX 19)"; print "? "; chomp($Size = uc());
+ if ($Size<7 || $Size>19) {
+ $Size=0;
+ print "I SAID, THE MINIMUM IS 7, THE MAXIMUM IS 19.\n";
+ }
+ } until ($Size);
+
+ #==> Reset Board to zeroes...
+ for (my $I=1; $I<=$Size; $I++) {
+ for (my $J=1; $J<=$Size; $J++) {
+ $Board[$I][$J]= 0;
+ }
+ }
+
+ print "\n"; print "WE ALTERNATE MOVES. YOU GO FIRST...\n"; print "\n";
+
+ while (1) {
+ do {
+ print "YOUR PLAY (I,J)"; print "? "; chomp(my $Inp = uc());
+ ($I, $J)= split(",", $Inp);
+ print "\n";
+ if ($I==-1) { last; }
+ $Ret= &ValidMove($I, $J, 1);
+ } until ($Ret==1);
+ if ($I==-1) { last; }
+ $Board[$I][$J]= 1;
+
+ my $X;
+ my $Y;
+ my $Found=0;
+ # REM *** COMPUTER TRIES AN INTELLIGENT MOVE ***
+ #==> Too complex, original basic code seems only move below user.
+ $Ret= &ValidMove($I+1, $J);
+ if ($Ret==1) {
+ $Found=1;
+ $X= $I+1;
+ $Y= $J;
+ }
+
+ while($Found==0) {
+ # REM *** COMPUTER TRIES A RANDOM MOVE ***
+ $X= int($Size*rand(1)+1);
+ $Y= int($Size*rand(1)+1);
+ $Ret= &ValidMove($X, $Y, 2);
+ if ($Ret==1) { $Found= 1; }
+ };
+ $Board[$X][$Y]=2;
+
+ &ShowBoard();
+ }
+
+ print "\n"; print "THANKS FOR THE GAME!!\n";
+ print "PLAY AGAIN (1 FOR YES, 0 FOR NO)"; print "? "; chomp(my $Q = uc());
+ if ($Q==0) { last; }
+ }
+
+
+
+exit;
+
+
+sub ShowBoard {
+ for (my $I=1; $I<=$Size; $I++) {
+ print " ";
+ for (my $J=1; $J<=$Size; $J++) {
+ print "$Board[$I][$J] ";
+ }
+ print "\n";
+ }
+ print "\n";
+ return;
+ }
+
+
+sub ValidMove {
+ my ($X, $Y, $Val)= @_;
+ if ($X<1 || $X>$Size || $Y<1 || $Y>$Size) {
+ if ($Val==1) { print "ILLEGAL MOVE. TRY AGAIN...\n"; }
+ return 0;
+ }
+ if ($Board[$X][$Y]!=0) {
+ if ($Val==1) { print "SQUARE OCCUPIED. TRY AGAIN...\n"; }
+ return 0;
+ }
+
+ #$Board[$X][$Y]= $Val;
+ return 1;
+ }
+
+
diff --git a/63_Name/java/main.class b/63_Name/java/Name.java
similarity index 98%
rename from 63_Name/java/main.class
rename to 63_Name/java/Name.java
index db01b014..7b67f489 100644
--- a/63_Name/java/main.class
+++ b/63_Name/java/Name.java
@@ -1,7 +1,7 @@
import java.util.Arrays;
import java.util.Scanner;
-public class main {
+public class Name {
public static void printempty() { System.out.println(" "); }
diff --git a/66_Number/java/main.class b/66_Number/java/1/Number.java
similarity index 100%
rename from 66_Number/java/main.class
rename to 66_Number/java/1/Number.java
diff --git a/66_Number/java/Number.java b/66_Number/java/2/Number.java
similarity index 100%
rename from 66_Number/java/Number.java
rename to 66_Number/java/2/Number.java
diff --git a/67_One_Check/python/onecheck.py b/67_One_Check/python/onecheck.py
new file mode 100644
index 00000000..b3d828b1
--- /dev/null
+++ b/67_One_Check/python/onecheck.py
@@ -0,0 +1,115 @@
+# ONE CHECK
+
+# Port to python by imiro
+
+def tab(x):
+ return ' '*x
+
+def main():
+
+ # Initial instructions
+ print(tab(30) + "ONE CHECK");
+ print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
+ print();
+ print();
+ print();
+ print("SOLITAIRE CHECKER PUZZLE BY DAVID AHL");
+ print();
+ print("48 CHECKERS ARE PLACED ON THE 2 OUTSIDE SPACES OF A");
+ print("STANDARD 64-SQUARE CHECKERBOARD. THE OBJECT IS TO");
+ print("REMOVE AS MANY CHECKERS AS POSSIBLE BY DIAGONAL JUMPS");
+ print("(AS IN STANDARD CHECKERS). USE THE NUMBERED BOARD TO");
+ print("INDICATE THE SQUARE YOU WISH TO JUMP FROM AND TO. ON");
+ print("THE BOARD PRINTED OUT ON EACH TURN '1' INDICATES A");
+ print("CHECKER AND '0' AN EMPTY SQUARE. WHEN YOU HAVE NO");
+ print("POSSIBLE JUMPS REMAINING, INPUT A '0' IN RESPONSE TO");
+ print("QUESTION 'JUMP FROM ?'");
+ print();
+ print("HERE IS THE NUMERICAL BOARD:");
+ print();
+
+ while(True):
+ for j in range(1,64,8):
+ for i in range(j,j+7):
+ print(i, end=(' '*(3 if i < 10 else 2)))
+ print(j+7)
+ print()
+ print("AND HERE IS THE OPENING POSITION OF THE CHECKERS.")
+ print()
+
+ (jumps, left) = play_game()
+
+ print()
+ print("YOU MADE " + jumps + " JUMPS AND HAD " + left + " PIECES")
+ print("REMAINING ON THE BOARD.")
+ print()
+
+ if not(try_again()):
+ break
+
+ print()
+ print("O.K. HOPE YOU HAD FUN!!")
+
+def play_game():
+ # Initialize board
+ # Give more than 64 elements to accomodate 1-based indexing
+ board = [1]*70
+ for j in range(19,44,8):
+ for i in range(j,j+4):
+ board[i] = 0
+ jumps = 0
+ while True:
+ # print board
+ for j in range(1,64,8):
+ for i in range(j,j+7):
+ print(board[i], end=' ')
+ print(board[j+7])
+ print()
+
+ while True:
+ print("JUMP FROM", end=' ')
+ f = input()
+ f = int(f)
+ if f == 0:
+ break
+ print("TO", end=' ')
+ t = input()
+ t = int(t)
+ print()
+
+ # Check legality of move
+ f1 = ((f-1) // 8)
+ f2 = f - 8 * f1
+ t1 = ((t-1) // 8)
+ t2 = t - 8 * t1
+ if (f1 > 7 or t1 > 7 or f2 > 8 or t2 > 8 or abs(f1 - t1) != 2 or
+ abs(f2 - t2) != 2 or board[(t + f) // 2] == 0 or
+ board[f] == 0 or board[t] == 1):
+ print("ILLEGAL MOVE. TRY AGAIN...")
+ continue
+ break
+
+ if(f == 0):
+ break
+ board[t] = 1
+ board[f] = 0
+ board[(t+f) // 2] = 0
+ jumps = jumps + 1
+
+ left = 0
+ for i in range(1,64+1):
+ left = left + board[i]
+ return (str(jumps), str(left))
+
+def try_again():
+ print("TRY AGAIN", end=' ')
+ answer = input()
+ if (answer.upper() == "YES"):
+ return True
+ elif (answer.upper() == "NO"):
+ return False
+ print("PLEASE ANSWER 'YES' OR 'NO'.")
+ try_again()
+
+if __name__ == '__main__':
+ main()
diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md
index 77fae3d8..e47c90b7 100644
--- a/84_Super_Star_Trek/README.md
+++ b/84_Super_Star_Trek/README.md
@@ -88,6 +88,17 @@ The relation between the Historical and Standard nomenclatures is shown in the s
15. This version of Star Trek was created for a Data General Nova 800 system with 32K or core. So that it would fit, the instructions are separated from the main program via a CHAIN. For conversion to DEC BASIC-PLUS, Statement 160 (Randomize) should be moved after the return from the chained instructions, say to Statement 245. For Altair BASIC, Randomize and the chain instructions should be eliminated.
+---
+#### Bugs
+
+Many of the programs in this book and this collection have bugs in the original code.
+
+@jkboyce has done a great job of discovering and fixing a number of bugs in the [original code](superstartrek.bas), as part of his [python implementation](python/superstartrek.py), which should be noted by other implementers:
+
+- line `4410` : `D(7)` should be `D(6)`
+- lines `8310`,`8330`,`8430`,`8450` : Division by zero is possible
+- line `440` : `B9` should be initialised to 0, not 2
+
---
As published in Basic Computer Games (1978):