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):