From e5c7625101ab8dcd1dce20d70d5beab7d633d16e Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sun, 2 Jan 2022 22:31:41 +0000 Subject: [PATCH 01/33] Typo vs. original listing screenshot --- 27_Civil_War/civilwar.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 6c9e90752fc251dced4389e3b8eeb4812bb43494 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sun, 2 Jan 2022 22:37:49 +0000 Subject: [PATCH 02/33] Create .gitignore --- 27_Civil_War/java/.gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 27_Civil_War/java/.gitignore 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 From 518562645afb24e66d0c5e00024d5fa2ad39d6c7 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Wed, 5 Jan 2022 00:00:36 +0000 Subject: [PATCH 03/33] Initial --- 27_Civil_War/java/src/CivilWar.java | 610 ++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 27_Civil_War/java/src/CivilWar.java diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java new file mode 100644 index 00000000..fa6cd27a --- /dev/null +++ b/27_Civil_War/java/src/CivilWar.java @@ -0,0 +1,610 @@ +import java.io.PrintStream; +import java.util.List; +import java.util.Scanner; + +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 BattleState currentBattle; + private int R1; + private int R2; + private final int[] strategies; + private int numGenerals; + private final int[] B; + private final int[] F; + private final int[] H; + private final int[] D; + private final double[] O; + private int battleNumber; + private int Y; + private int Y2; + private final ArmyPair totalExpectedCasualties; + private final ArmyPair totalCasualties; + private int excessiveConfederateLosses; + private int excessiveUnionLosses; + private int L; + private int W; + private int M3; + private int M4; + private int Q1; + private int Q2; + private double C6; + private double E2; + private int W0; + private int confedTroops; // M5 + private int unionTroops; // M6 + private boolean confedSurrender; + private boolean unionSurrender; + private int I1; + private int I2; + private double R; + private boolean wantBattleDescriptions; + + /** + * 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? "); + + var terminalInput = new Scanner(System.in); + String s = terminalInput.nextLine(); + switch (s) { + case "Y" -> { + x.showHelp(); + x.gameLoop(); + } + case "N" -> x.gameLoop(); + default -> System.out.println("YES OR NO -- "); + } + } + + private void gameLoop() { + out.println(); + out.println(); + out.println(); + out.print("ARE THERE TWO GENERALS PRESENT (ANSWER YES OR NO)? "); + + var terminalInput = new Scanner(System.in); + String twoGeneralsAnswer = terminalInput.nextLine(); + switch (twoGeneralsAnswer) { + case "YES" -> this.numGenerals = 2; + case "NO" -> { + this.numGenerals = 1; + out.println(); + out.println("YOU ARE THE CONFEDERACY. GOOD LUCK!"); + out.println(); + } + default -> { + // FIXME Retry! + } + } + + 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)? "); + String descriptionsAnswer = terminalInput.nextLine(); + this.wantBattleDescriptions = descriptionsAnswer.equals("YES"); + // FIXME Retry if not "YES" or "NO" + + while (true) { + var battle = startBattle(); + if (battle == null) { + break; + } + + this.currentBattle = battle; + + offensiveLogic(battle.data); + + unionStrategy(); + + simulatedLosses(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 terminalInput = new Scanner(System.in); + var battleNumber = terminalInput.nextInt(); + + if (battleNumber == 0 && this.currentBattle != null) { + out.println(this.currentBattle.data.name + " INSTANT REPLAY"); + return this.currentBattle; + } + + if (battleNumber <= 0 || battleNumber > this.data.size()) { + return null; + } + + this.battleNumber = battleNumber; + + var battle = this.data.get(this.battleNumber - 1); + var battleState = new BattleState(battle); + + + excessiveConfederateLosses = 0; + + // INFLATION CALC + // REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% + I1 = 10 + (L - W) * 2; + I2 = 10 + (W - L) * 2; + + // MONEY AVAILABLE + this.D[0] = 100 * (int) Math.floor((battle.troops.confederate * (100.0 - I1) / 2000) * (1 + (R1 - Q1) / (R1 + 1.0)) + .5); + + // MEN AVAILABLE + this.confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (M3 + 1.0))); + this.unionTroops = (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (M4 + 1.0))); + battleState.F1 = 5 * battle.troops.confederate / 6.0; + + if (this.numGenerals == 2) { + this.D[1] = 100 * (int) Math.floor((battle.troops.union * (100.0 - I2) / 2000) * (1 + (R2 - Q2) / (R2 + 1.0)) + .5); + } else { + this.D[1] = 100 * (int) Math.floor(battle.troops.union * (100.0 - I2) / 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 " + confedTroops + " " + unionTroops); + out.println("MONEY $ " + this.D[0] + " $ " + this.D[1]); + out.println("INFLATION " + (I1 + 15) + "% " + I2 + "%"); + + // ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% + // IF TWO GENERALS, INPUT CONFED. FIRST + + for (int i = 0; i < numGenerals; i++) { + out.println(); + + if (this.numGenerals == 1 || i == 0) { + out.print("CONFEDERATE GENERAL --- "); + } else { + out.print("UNION GENERAL --- "); + } + + out.println("HOW MUCH DO YOU WISH TO SPEND FOR"); + out.print("- FOOD...... ? "); + var F = terminalInput.nextInt(); + if (F == 0) { + if (this.R1 != 0) { + out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS"); + out.println(); + } + } + + this.F[i] = F; + + out.print("- SALARIES.. ? "); + this.H[i] = terminalInput.nextInt(); + + out.print("- AMMUNITION ? "); + this.B[i] = terminalInput.nextInt(); // FIXME Retry if -ve + + if (this.F[i] + this.H[i] + this.B[i] > this.D[i]) { + out.println("THINK AGAIN! YOU HAVE ONLY $" + this.D[i]); + // FIXME Redo inputs from Food + } + } + + out.println(); + + // Record Morale + out.println(range(0, numGenerals).mapToObj(i -> moraleForArmy(battleState, i)).collect(joining(", "))); + + out.println(); + + return battleState; + } + + private String moraleForArmy(BattleState battleState, int armyIdx) { + var builder = new StringBuilder(); + + if (this.numGenerals == 1 || armyIdx == 0) { + builder.append("CONFEDERATE "); + } else { + builder.append("UNION "); + } + + // FIND MORALE + this.O[armyIdx] = (2 * Math.pow(F[armyIdx], 2) + Math.pow(H[armyIdx], 2)) / Math.pow(battleState.F1, 2) + 1; + if (this.O[armyIdx] >= 10) { + builder.append("MORALE IS HIGH"); + } else if (this.O[armyIdx] >= 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 ? "); + } + + var terminalInput = new Scanner(System.in); + Y = terminalInput.nextInt(); + if (Math.abs(Y - 3) >= 3) { + out.println("STRATEGY " + Y + " NOT ALLOWED."); + // FIXME Proper numeric check!! Not abs + // FIXME Retry Y input + } + + if (Y == 5) { // 1970 + confedSurrender = true; + } + } + + // 2070 REM : SIMULATED LOSSES-NORTH + private void simulatedLosses(HistoricalDatum battle) { + C6 = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 1))); + C6 = C6 * (1.28 + (5.0 * battle.troops.union / 6) / (B[1] + 1)); + C6 = Math.floor(C6 * (1 + 1 / O[1]) + 0.5); + // IF LOSS > MEN PRESENT, RESCALE LOSSES + E2 = 100 / O[1]; + if (Math.floor(C6 + E2) >= unionTroops) { + C6 = Math.floor(13.0 * unionTroops / 20); + E2 = 7 * C6 / 13; + excessiveUnionLosses = 1; + } + } + + // 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(Y2 - Y) + 1))); + C5 = (int) Math.floor(C5 * (1 + 1.0 / this.O[0]) * (1.28 + battle.F1 / (this.B[0] + 1.0)) + .5); + var E = 100 / O[0]; + + if (C5 + 100 / O[0] >= battle.data.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (M3 + 1.0))) { + C5 = (int) Math.floor(13.0 * battle.data.troops.confederate / 20 * (1 + (totalExpectedCasualties.union - totalCasualties.confederate) / (M3 + 1.0))); + E = 7 * C5 / 13.0; + excessiveConfederateLosses = 1; + } + + ///// 2270 + + if (this.numGenerals == 1) { + C6 = (int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)); + E2 = 5 * O[0]; + } + + out.println("CASUALTIES: " + rightAlignInt(C5) + " " + rightAlignInt(C6)); + out.println("DESERTIONS: " + rightAlignInt(E) + " " + rightAlignInt(E2)); + 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 * (C6 / (double) battle.data.expectedCasualties.union) + 0.5) + " % OF THE ORIGINAL"); + + out.println(); + + // REM - 1 WHO WON + var winner = findWinner(C5 + E, C6 + E2); + switch (winner) { + case UNION -> { + out.println("THE UNION WINS " + battle.data.name); + L++; + } + case CONFED -> { + out.println("THE CONFEDERACY WINS " + battle.data.name); + W++; + } + case INDECISIVE -> { + out.println("BATTLE OUTCOME UNRESOLVED"); + this.W0++; + } + } + } 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 == 1) { + out.println("YOU LOSE " + battle.data.name); + + if (this.battleNumber != 0) { + L++; + } + } else { + out.println("YOU WIN " + battle.data.name); + // CUMULATIVE BATTLE FACTORS WHICH ALTER HISTORICAL RESOURCES AVAILABLE.IF A REPLAY DON'T UPDATE. + W++; + } + } + + if (this.battleNumber != 0) { + totalCasualties.confederate += (int) (C5 + E); + totalCasualties.union += (int) (C6 + E2); + totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate; + totalExpectedCasualties.union += battle.data.expectedCasualties.union; + Q1 += F[0] + H[0] + B[0]; + Q2 += F[1] + H[1] + B[1]; + R1 += battle.data.troops.confederate * (100 - I1) / 20; + R2 += battle.data.troops.union * (100 - I2) / 20; + M3 += battle.data.troops.confederate; + M4 += battle.data.troops.union; + + updateStrategies(this.Y); + } + } + + // 2790 + private void reset() { + excessiveConfederateLosses = excessiveUnionLosses = 0; + + 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 " + this.W + " BATTLES AND LOST " + this.L); + + if (this.Y2 == 5) { + out.println("THE CONFEDERACY HAS WON THE WAR"); + } + + if (this.Y == 5 || this.W <= this.L) { + out.println("THE UNION HAS WON THE WAR"); + } + + out.println(); + + // FIXME 2960 IF R1=0 THEN 3100 + + out.println("FOR THE " + (W + L + W0) + " BATTLES FOUGHT (EXCLUDING RERUNS)"); +// out.println(" ", " ", " "); + 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 == 1 && this.excessiveUnionLosses == 1) { + return Winner.INDECISIVE; + } + + if (this.excessiveConfederateLosses == 1) { + return Winner.UNION; + } + + if (this.excessiveUnionLosses == 1 || 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() { + if (this.battleNumber != 0) { + out.print("UNION STRATEGY ? "); + var terminalInput = new Scanner(System.in); + Y2 = terminalInput.nextInt(); + if (Y2 < 0) { + out.println("ENTER 1, 2, 3, OR 4 (USUALLY PREVIOUS UNION STRATEGY)"); + // FIXME Retry Y2 input !!! + } + + if (Y2 < 5) { // 3155 + return; + } + } + + var S0 = 0; + + this.R = 100 * Math.random(); + + for (Y2 = 0; Y2 < 4; Y2++) { + S0 += this.strategies[Y2]; + // 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 " + Y2); + } + + public CivilWar(PrintStream out) { + this.out = out; + + this.totalCasualties = new ArmyPair<>(0, 0); + this.totalExpectedCasualties = new ArmyPair<>(0, 0); + + // UNION INFO ON LIKELY CONFEDERATE STRATEGY + this.strategies = new int[]{25, 25, 25, 25}; + + this.F = new int[]{0, 0}; + this.H = new int[]{0, 0}; + this.B = new int[]{0, 0}; + this.D = new int[]{0, 0}; + this.O = new double[]{0, 0}; + + // 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 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 record HistoricalDatum(String name, ArmyPair troops, + ArmyPair expectedCasualties, + OffensiveStatus offensiveStatus, String[] blurb) { + } +} \ No newline at end of file From cf55a5f63756691ce3145636cbaf43606fc61831 Mon Sep 17 00:00:00 2001 From: Bernard Cooke Date: Fri, 14 Jan 2022 01:28:30 +0000 Subject: [PATCH 04/33] 12. Port Bombs Away to Python --- 12_Bombs_Away/python/bombs_away.py | 177 +++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 12_Bombs_Away/python/bombs_away.py diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py new file mode 100644 index 00000000..894f968c --- /dev/null +++ b/12_Bombs_Away/python/bombs_away.py @@ -0,0 +1,177 @@ +""" +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: + 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? ")) + + 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.keys(), + ) + + # 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? + enemy_gunner_accuracy = float( + input("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ") + ) + + 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.keys(), + ) + + 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.keys(), + ) + + 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() + + if random.random() > 0.65: + return mission_success() + return 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.keys(), + ) + + 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.keys(), + ) + 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 From 11ffe9cf90d7b7f61c7fe576b1ad7b61f451daee Mon Sep 17 00:00:00 2001 From: Bernard Cooke Date: Fri, 14 Jan 2022 01:31:27 +0000 Subject: [PATCH 05/33] Tidy up 'Another mission?' message --- 12_Bombs_Away/python/bombs_away.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py index 894f968c..a7c4ccd5 100644 --- a/12_Bombs_Away/python/bombs_away.py +++ b/12_Bombs_Away/python/bombs_away.py @@ -174,4 +174,4 @@ if __name__ == "__main__": again = True while again: play_game() - again = True if input("ANOTHER MISSION (Y OR N)").upper() == "Y" else False + again = True if input("ANOTHER MISSION? (Y OR N): ").upper() == "Y" else False From f29fa1792ce2dc0b7f161ae631a3cd63854cdf3f Mon Sep 17 00:00:00 2001 From: Bernard Cooke Date: Fri, 14 Jan 2022 01:53:41 +0000 Subject: [PATCH 06/33] Tidy logic with inline conditionals for clarity --- 12_Bombs_Away/python/bombs_away.py | 52 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py index a7c4ccd5..b3133ae3 100644 --- a/12_Bombs_Away/python/bombs_away.py +++ b/12_Bombs_Away/python/bombs_away.py @@ -8,7 +8,7 @@ import random from typing import Iterable -def _stdin_choice(*, prompt: str, choices: Iterable[str]) -> str: +def _stdin_choice(prompt: str, *, choices: Iterable[str]) -> str: ret = input(prompt) while ret not in choices: print("TRY AGAIN...") @@ -43,12 +43,22 @@ def death_with_chance(p_death: float) -> bool: def commence_non_kamikazi_attack() -> None: - nmissions = int(input("HOW MANY MISSIONS HAVE YOU FLOWN? ")) + 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? ")) + 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 + else: + break if nmissions >= 100: print("THAT'S PUSHING THE ODDS!") @@ -73,7 +83,7 @@ def mission_failure() -> None: print() enemy_weapons = _stdin_choice( prompt="DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)? ", - choices=weapons_choices.keys(), + choices=weapons_choices, ) # If there are no gunners (i.e. weapon choice 2) then @@ -83,9 +93,17 @@ def mission_failure() -> None: enemy_gunner_accuracy = 0.0 if enemy_weapons != "2": # If the enemy has guns, how accurate are the gunners? - enemy_gunner_accuracy = float( - input("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ") - ) + 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...") @@ -109,7 +127,7 @@ def play_italy() -> None: } target = _stdin_choice( prompt="YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)", - choices=targets_to_messages.keys(), + choices=targets_to_messages, ) print(targets_to_messages[target]) @@ -125,7 +143,7 @@ def play_allies() -> None: } aircraft = _stdin_choice( prompt="AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4): ", - choices=aircraft_to_message.keys(), + choices=aircraft_to_message, ) print(aircraft_to_message[aircraft]) @@ -137,10 +155,7 @@ def play_japan() -> None: first_mission = input("YOUR FIRST KAMIKAZE MISSION? (Y OR N): ") if first_mission.lower() == "n": return player_death() - - if random.random() > 0.65: - return mission_success() - return player_death() + return mission_success() if random.random() > 0.65 else player_death() def play_germany() -> None: @@ -152,7 +167,7 @@ def play_germany() -> None: } target = _stdin_choice( prompt="A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\nENGLAND(2), OR FRANCE(3)? ", - choices=targets_to_messages.keys(), + choices=targets_to_messages, ) print(targets_to_messages[target]) @@ -164,8 +179,7 @@ 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.keys(), + prompt="WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4): ", choices=sides ) return sides[side]() From 62af4c0ab2a4c64633ec39e4f7da6ec5971392a0 Mon Sep 17 00:00:00 2001 From: Bernard Cooke Date: Fri, 14 Jan 2022 08:47:21 +0000 Subject: [PATCH 07/33] Correct looping/breaking in number of missions dialogue --- 12_Bombs_Away/python/bombs_away.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/12_Bombs_Away/python/bombs_away.py b/12_Bombs_Away/python/bombs_away.py index b3133ae3..5c591f44 100644 --- a/12_Bombs_Away/python/bombs_away.py +++ b/12_Bombs_Away/python/bombs_away.py @@ -51,14 +51,12 @@ def commence_non_kamikazi_attack() -> None: 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 + break except ValueError: # In the BASIC implementation this # wasn't accounted for print("TRY AGAIN...") continue - else: - break if nmissions >= 100: print("THAT'S PUSHING THE ODDS!") From 8245d7badba8fdf3fe5c0123759a9e9220315238 Mon Sep 17 00:00:00 2001 From: Joe Nellis Date: Fri, 14 Jan 2022 01:42:44 -0800 Subject: [PATCH 08/33] Breakdown of the original BASIC code for re-implementers of this game. Sample code in BASIC showing distribution of right and wrong dice rolls. --- 29_Craps/README.md | 208 +++++++++++++++++++++++++++++++++++++ 29_Craps/distributions.bas | 24 +++++ 2 files changed, 232 insertions(+) create mode 100644 29_Craps/distributions.bas 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 From b4c8bfc9c8ae609713bdfa0c6024bcf6908e9ce8 Mon Sep 17 00:00:00 2001 From: Joe Nellis Date: Fri, 14 Jan 2022 01:43:24 -0800 Subject: [PATCH 09/33] Java port of BASIC Craps --- 29_Craps/java/src/Craps.java | 125 +++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 29_Craps/java/src/Craps.java 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("> "); + } + } +} From 3b082b4db98b178f5db65c4e97dc66b35f451779 Mon Sep 17 00:00:00 2001 From: Alessandro Tegner Date: Fri, 14 Jan 2022 09:32:16 -0300 Subject: [PATCH 10/33] Chief in Ruby --- 25_Chief/ruby/chief.rb | 98 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 25_Chief/ruby/chief.rb 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 From f9a9a791e2e82f576cea7ef7dedb9845bfd902c5 Mon Sep 17 00:00:00 2001 From: markbernard Date: Fri, 14 Jan 2022 09:54:49 -0500 Subject: [PATCH 11/33] Add Notepad++ syntax colouring for Vintage BASIC. --- 00_Utilities/VintageBASIC.xml | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 00_Utilities/VintageBASIC.xml 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f6c6065bdd51f4ebbbc8222b9b02f083aa15be64 Mon Sep 17 00:00:00 2001 From: Nezumi Ronin Date: Fri, 14 Jan 2022 11:31:02 -0600 Subject: [PATCH 12/33] Create gomoko.pl Original "intelligent move" it's awful. Seems it only move below user. --- 40_Gomoko/perl/gomoko.pl | 121 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 40_Gomoko/perl/gomoko.pl 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; + } + + From 697697228d38520bec7bb71993d8272a4793283b Mon Sep 17 00:00:00 2001 From: imiro Date: Fri, 14 Jan 2022 12:41:42 -0600 Subject: [PATCH 13/33] 67-One Check on Python --- 67_One_Check/python/onecheck.py | 115 ++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 67_One_Check/python/onecheck.py 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() From cb2aeed07cef61000fc5d15234666241bcd40e1c Mon Sep 17 00:00:00 2001 From: Joe Nellis Date: Fri, 14 Jan 2022 12:36:11 -0800 Subject: [PATCH 14/33] Fix dice roll computation. Remove obscure input prompt for random number generation scrambling. --- 29_Craps/javascript/craps.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) 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"); From db0b1ffe4e976a5b18bcf9e6d7862756b8d0835d Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 01:48:34 +1100 Subject: [PATCH 15/33] Add bug descriptions discovered by Jack Boyce in Super Star Trek to the main game description. --- 84_Super_Star_Trek/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md index 77fae3d8..28bd3c03 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. + +Jack Boyce has discovered and fixed a number of bugs in 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): From 1085d282fff2f6d369ed302b278b4e15c4113eba Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 16:32:12 +0000 Subject: [PATCH 16/33] Clean up YES/NO input and validation --- 27_Civil_War/java/src/CivilWar.java | 70 ++++++++++++++++++----------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index fa6cd27a..bdf1bff6 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -1,6 +1,7 @@ import java.io.PrintStream; import java.util.List; import java.util.Scanner; +import java.util.function.Predicate; import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; @@ -46,6 +47,9 @@ public class CivilWar { private double R; private boolean wantBattleDescriptions; + 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 @@ -58,16 +62,11 @@ public class CivilWar { System.out.print("DO YOU WANT INSTRUCTIONS? "); - var terminalInput = new Scanner(System.in); - String s = terminalInput.nextLine(); - switch (s) { - case "Y" -> { - x.showHelp(); - x.gameLoop(); - } - case "N" -> x.gameLoop(); - default -> System.out.println("YES OR NO -- "); + if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) { + x.showHelp(); } + + x.gameLoop(); } private void gameLoop() { @@ -76,19 +75,13 @@ public class CivilWar { out.println(); out.print("ARE THERE TWO GENERALS PRESENT (ANSWER YES OR NO)? "); - var terminalInput = new Scanner(System.in); - String twoGeneralsAnswer = terminalInput.nextLine(); - switch (twoGeneralsAnswer) { - case "YES" -> this.numGenerals = 2; - case "NO" -> { - this.numGenerals = 1; - out.println(); - out.println("YOU ARE THE CONFEDERACY. GOOD LUCK!"); - out.println(); - } - default -> { - // FIXME Retry! - } + 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"); @@ -101,9 +94,8 @@ public class CivilWar { out.println(); out.print("AFTER REQUESTING A BATTLE, DO YOU WISH BATTLE DESCRIPTIONS (ANSWER YES OR NO)? "); - String descriptionsAnswer = terminalInput.nextLine(); - this.wantBattleDescriptions = descriptionsAnswer.equals("YES"); - // FIXME Retry if not "YES" or "NO" + + this.wantBattleDescriptions = isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER)); while (true) { var battle = startBattle(); @@ -584,6 +576,34 @@ public class CivilWar { return rightAlignInt((int) Math.floor(number)); } + private static String inputString(Predicate validator, String reminder) { + var terminalInput = new Scanner(System.in); + + while (true) { + var input = terminalInput.nextLine(); + if (validator.test(input)) { + return input; + } + System.out.println(reminder); + } + } + + 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; From df95ff0eff9d90310fe376f6bf6e01502199c32f Mon Sep 17 00:00:00 2001 From: Mark Wieder Date: Sat, 15 Jan 2022 13:16:28 -0800 Subject: [PATCH 17/33] Cube in ruby --- 30_Cube/ruby/cube.rb | 261 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 30_Cube/ruby/cube.rb 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 + From 2e82f08efddbda677b161d2d0cf7ee250f561525 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 22:11:45 +0000 Subject: [PATCH 18/33] Improve encapsulation --- 27_Civil_War/java/src/CivilWar.java | 117 +++++++++++++++------------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index bdf1bff6..ea667890 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -13,15 +13,9 @@ public class CivilWar { private final List data; private BattleState currentBattle; - private int R1; - private int R2; private final int[] strategies; private int numGenerals; - private final int[] B; - private final int[] F; - private final int[] H; - private final int[] D; - private final double[] O; + private final ArmyPair resources; private int battleNumber; private int Y; private int Y2; @@ -31,10 +25,9 @@ public class CivilWar { private int excessiveUnionLosses; private int L; private int W; - private int M3; - private int M4; - private int Q1; - private int Q2; + private final ArmyPair revenue; + private final ArmyPair totalExpenditure; + private final ArmyPair totalTroops; private double C6; private double E2; private int W0; @@ -42,8 +35,7 @@ public class CivilWar { private int unionTroops; // M6 private boolean confedSurrender; private boolean unionSurrender; - private int I1; - private int I2; + private final ArmyPair inflation; private double R; private boolean wantBattleDescriptions; @@ -152,21 +144,21 @@ public class CivilWar { // INFLATION CALC // REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% - I1 = 10 + (L - W) * 2; - I2 = 10 + (W - L) * 2; + inflation.confederate = 10 + (L - W) * 2; + inflation.union = 10 + (W - L) * 2; // MONEY AVAILABLE - this.D[0] = 100 * (int) Math.floor((battle.troops.confederate * (100.0 - I1) / 2000) * (1 + (R1 - Q1) / (R1 + 1.0)) + .5); + 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 - this.confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (M3 + 1.0))); - this.unionTroops = (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (M4 + 1.0))); + this.confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); + this.unionTroops = (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0))); battleState.F1 = 5 * battle.troops.confederate / 6.0; if (this.numGenerals == 2) { - this.D[1] = 100 * (int) Math.floor((battle.troops.union * (100.0 - I2) / 2000) * (1 + (R2 - Q2) / (R2 + 1.0)) + .5); + 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 { - this.D[1] = 100 * (int) Math.floor(battle.troops.union * (100.0 - I2) / 2000 + .5); + resources.union.budget = 100 * (int) Math.floor(battle.troops.union * (100.0 - inflation.union) / 2000 + .5); } out.println(); @@ -185,8 +177,8 @@ public class CivilWar { out.println(); out.println(" CONFEDERACY UNION"); out.println("MEN " + confedTroops + " " + unionTroops); - out.println("MONEY $ " + this.D[0] + " $ " + this.D[1]); - out.println("INFLATION " + (I1 + 15) + "% " + I2 + "%"); + 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 @@ -194,32 +186,36 @@ public class CivilWar { 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; } out.println("HOW MUCH DO YOU WISH TO SPEND FOR"); out.print("- FOOD...... ? "); var F = terminalInput.nextInt(); if (F == 0) { - if (this.R1 != 0) { + if (this.revenue.confederate != 0) { out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS"); out.println(); } } - this.F[i] = F; + currentResources.food = F; out.print("- SALARIES.. ? "); - this.H[i] = terminalInput.nextInt(); + currentResources.salaries = terminalInput.nextInt(); out.print("- AMMUNITION ? "); - this.B[i] = terminalInput.nextInt(); // FIXME Retry if -ve + currentResources.ammunition = terminalInput.nextInt(); // FIXME Retry if -ve - if (this.F[i] + this.H[i] + this.B[i] > this.D[i]) { - out.println("THINK AGAIN! YOU HAVE ONLY $" + this.D[i]); + if (currentResources.getTotal() > currentResources.budget) { + out.println("THINK AGAIN! YOU HAVE ONLY $" + currentResources.budget); // FIXME Redo inputs from Food } } @@ -237,17 +233,21 @@ public class CivilWar { 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 - this.O[armyIdx] = (2 * Math.pow(F[armyIdx], 2) + Math.pow(H[armyIdx], 2)) / Math.pow(battleState.F1, 2) + 1; - if (this.O[armyIdx] >= 10) { + 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 (this.O[armyIdx] >= 5) { + } else if (currentResources.morale >= 5) { builder.append("MORALE IS FAIR"); } else { builder.append("MORALE IS POOR"); @@ -295,10 +295,10 @@ public class CivilWar { // 2070 REM : SIMULATED LOSSES-NORTH private void simulatedLosses(HistoricalDatum battle) { C6 = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 1))); - C6 = C6 * (1.28 + (5.0 * battle.troops.union / 6) / (B[1] + 1)); - C6 = Math.floor(C6 * (1 + 1 / O[1]) + 0.5); + C6 = C6 * (1.28 + (5.0 * battle.troops.union / 6) / (resources.union.ammunition + 1)); + C6 = Math.floor(C6 * (1 + 1 / resources.union.morale) + 0.5); // IF LOSS > MEN PRESENT, RESCALE LOSSES - E2 = 100 / O[1]; + E2 = 100 / resources.union.morale; if (Math.floor(C6 + E2) >= unionTroops) { C6 = Math.floor(13.0 * unionTroops / 20); E2 = 7 * C6 / 13; @@ -313,11 +313,11 @@ public class CivilWar { out.println(" CONFEDERACY UNION"); var C5 = (2 * battle.data.expectedCasualties.confederate / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 1))); - C5 = (int) Math.floor(C5 * (1 + 1.0 / this.O[0]) * (1.28 + battle.F1 / (this.B[0] + 1.0)) + .5); - var E = 100 / O[0]; + 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 / O[0] >= battle.data.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (M3 + 1.0))) { - C5 = (int) Math.floor(13.0 * battle.data.troops.confederate / 20 * (1 + (totalExpectedCasualties.union - totalCasualties.confederate) / (M3 + 1.0))); + 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 = 1; } @@ -326,7 +326,7 @@ public class CivilWar { if (this.numGenerals == 1) { C6 = (int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)); - E2 = 5 * O[0]; + E2 = 5 * resources.confederate.morale; } out.println("CASUALTIES: " + rightAlignInt(C5) + " " + rightAlignInt(C6)); @@ -379,12 +379,12 @@ public class CivilWar { totalCasualties.union += (int) (C6 + E2); totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate; totalExpectedCasualties.union += battle.data.expectedCasualties.union; - Q1 += F[0] + H[0] + B[0]; - Q2 += F[1] + H[1] + B[1]; - R1 += battle.data.troops.confederate * (100 - I1) / 20; - R2 += battle.data.troops.union * (100 - I2) / 20; - M3 += battle.data.troops.confederate; - M4 += battle.data.troops.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.Y); } @@ -420,7 +420,6 @@ public class CivilWar { // FIXME 2960 IF R1=0 THEN 3100 out.println("FOR THE " + (W + L + W0) + " BATTLES FOUGHT (EXCLUDING RERUNS)"); -// out.println(" ", " ", " "); 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)); @@ -494,16 +493,17 @@ public class CivilWar { 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}; - this.F = new int[]{0, 0}; - this.H = new int[]{0, 0}; - this.B = new int[]{0, 0}; - this.D = new int[]{0, 0}; - this.O = new double[]{0, 0}; - // 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."})); @@ -623,6 +623,19 @@ public class CivilWar { } } + 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) { From 32efeeb4fe6a5bc75938ba725486abc1f2c7596a Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 22:40:17 +0000 Subject: [PATCH 19/33] Refactor --- 27_Civil_War/java/src/CivilWar.java | 103 ++++++++++++++++------------ 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index ea667890..8e13b760 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -21,21 +21,16 @@ public class CivilWar { private int Y2; private final ArmyPair totalExpectedCasualties; private final ArmyPair totalCasualties; - private int excessiveConfederateLosses; - private int excessiveUnionLosses; - private int L; - private int W; + private boolean excessiveConfederateLosses; + private boolean excessiveUnionLosses; + private final BattleResults results; private final ArmyPair revenue; + private final ArmyPair inflation; private final ArmyPair totalExpenditure; private final ArmyPair totalTroops; - private double C6; - private double E2; - private int W0; - private int confedTroops; // M5 private int unionTroops; // M6 private boolean confedSurrender; private boolean unionSurrender; - private final ArmyPair inflation; private double R; private boolean wantBattleDescriptions; @@ -101,7 +96,6 @@ public class CivilWar { unionStrategy(); - simulatedLosses(battle.data); calcLosses(battle); reset(); @@ -140,18 +134,18 @@ public class CivilWar { var battleState = new BattleState(battle); - excessiveConfederateLosses = 0; + excessiveConfederateLosses = false; // INFLATION CALC // REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% - inflation.confederate = 10 + (L - W) * 2; - inflation.union = 10 + (W - L) * 2; + 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 - this.confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); + var confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); this.unionTroops = (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0))); battleState.F1 = 5 * battle.troops.confederate / 6.0; @@ -293,17 +287,20 @@ public class CivilWar { } // 2070 REM : SIMULATED LOSSES-NORTH - private void simulatedLosses(HistoricalDatum battle) { - C6 = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 1))); - C6 = C6 * (1.28 + (5.0 * battle.troops.union / 6) / (resources.union.ammunition + 1)); - C6 = Math.floor(C6 * (1 + 1 / resources.union.morale) + 0.5); + private UnionLosses simulateUnionLosses(HistoricalDatum battle) { + var losses = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 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 - E2 = 100 / resources.union.morale; - if (Math.floor(C6 + E2) >= unionTroops) { - C6 = Math.floor(13.0 * unionTroops / 20); - E2 = 7 * C6 / 13; - excessiveUnionLosses = 1; + var moraleFactor = 100 / resources.union.morale; + + if (Math.floor(losses + moraleFactor) >= unionTroops) { + losses = Math.floor(13.0 * unionTroops / 20); + moraleFactor = 7 * losses / 13; + excessiveUnionLosses = true; } + + return new UnionLosses((int) losses, (int) Math.floor(moraleFactor)); } // 2170: CALCULATE SIMULATED LOSSES @@ -319,41 +316,44 @@ public class CivilWar { 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 = 1; + excessiveConfederateLosses = true; } ///// 2270 + final UnionLosses unionLosses; + if (this.numGenerals == 1) { - C6 = (int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)); - E2 = 5 * resources.confederate.morale; + 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(C6)); - out.println("DESERTIONS: " + rightAlignInt(E) + " " + rightAlignInt(E2)); + 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 * (C6 / (double) battle.data.expectedCasualties.union) + 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, C6 + E2); + var winner = findWinner(C5 + E, unionLosses.losses + unionLosses.desertions); switch (winner) { case UNION -> { out.println("THE UNION WINS " + battle.data.name); - L++; + results.union++; } case CONFED -> { out.println("THE CONFEDERACY WINS " + battle.data.name); - W++; + results.confederate++; } case INDECISIVE -> { out.println("BATTLE OUTCOME UNRESOLVED"); - this.W0++; + results.indeterminate++; } } } else { @@ -361,22 +361,22 @@ public class CivilWar { // FIND WHO WON - if (excessiveConfederateLosses == 1) { + if (excessiveConfederateLosses) { out.println("YOU LOSE " + battle.data.name); if (this.battleNumber != 0) { - L++; + results.union++; } } else { out.println("YOU WIN " + battle.data.name); // CUMULATIVE BATTLE FACTORS WHICH ALTER HISTORICAL RESOURCES AVAILABLE.IF A REPLAY DON'T UPDATE. - W++; + results.confederate++; } } if (this.battleNumber != 0) { totalCasualties.confederate += (int) (C5 + E); - totalCasualties.union += (int) (C6 + E2); + totalCasualties.union += unionLosses.losses + unionLosses.desertions; totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate; totalExpectedCasualties.union += battle.data.expectedCasualties.union; totalExpenditure.confederate += resources.confederate.getTotal(); @@ -392,7 +392,7 @@ public class CivilWar { // 2790 private void reset() { - excessiveConfederateLosses = excessiveUnionLosses = 0; + excessiveConfederateLosses = excessiveUnionLosses = false; out.println("---------------"); } @@ -405,13 +405,13 @@ public class CivilWar { out.println(); out.println(); out.println(); - out.println("THE CONFEDERACY HAS WON " + this.W + " BATTLES AND LOST " + this.L); + out.println("THE CONFEDERACY HAS WON " + results.confederate + " BATTLES AND LOST " + results.union); if (this.Y2 == 5) { out.println("THE CONFEDERACY HAS WON THE WAR"); } - if (this.Y == 5 || this.W <= this.L) { + if (this.Y == 5 || results.confederate <= results.union) { out.println("THE UNION HAS WON THE WAR"); } @@ -419,7 +419,7 @@ public class CivilWar { // FIXME 2960 IF R1=0 THEN 3100 - out.println("FOR THE " + (W + L + W0) + " BATTLES FOUGHT (EXCLUDING RERUNS)"); + 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)); @@ -435,15 +435,15 @@ public class CivilWar { } private Winner findWinner(double confLosses, double unionLosses) { - if (this.excessiveConfederateLosses == 1 && this.excessiveUnionLosses == 1) { + if (this.excessiveConfederateLosses && this.excessiveUnionLosses) { return Winner.INDECISIVE; } - if (this.excessiveConfederateLosses == 1) { + if (this.excessiveConfederateLosses) { return Winner.UNION; } - if (this.excessiveUnionLosses == 1 || confLosses < unionLosses) { + if (this.excessiveUnionLosses || confLosses < unionLosses) { return Winner.CONFED; } @@ -491,6 +491,8 @@ public class CivilWar { 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); @@ -623,6 +625,16 @@ public class CivilWar { } } + 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; @@ -640,4 +652,7 @@ public class CivilWar { ArmyPair expectedCasualties, OffensiveStatus offensiveStatus, String[] blurb) { } + + private record UnionLosses(int losses, int desertions) { + } } \ No newline at end of file From 48edb2c5d7b0b2a2c724dff3c1184deded10b909 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 22:43:28 +0000 Subject: [PATCH 20/33] Make more readable --- 27_Civil_War/java/src/CivilWar.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index 8e13b760..ad909f2c 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -508,7 +508,22 @@ public class CivilWar { // 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."})); + 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() { From c256a7b0fb085c77eb520e991451cf33a1d3c497 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 22:54:13 +0000 Subject: [PATCH 21/33] Fix budget validation; restore "keep same allocations" --- 27_Civil_War/java/src/CivilWar.java | 38 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index ad909f2c..7b8d8b4c 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -190,27 +190,31 @@ public class CivilWar { currentResources = resources.union; } - out.println("HOW MUCH DO YOU WISH TO SPEND FOR"); - out.print("- FOOD...... ? "); - var F = terminalInput.nextInt(); - if (F == 0) { - if (this.revenue.confederate != 0) { - out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS"); - out.println(); + 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; } - } - currentResources.food = F; + out.print("- SALARIES.. ? "); + currentResources.salaries = terminalInput.nextInt(); - out.print("- SALARIES.. ? "); - currentResources.salaries = terminalInput.nextInt(); + out.print("- AMMUNITION ? "); + currentResources.ammunition = terminalInput.nextInt(); // FIXME Retry if -ve - 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); - // FIXME Redo inputs from Food + if (currentResources.getTotal() > currentResources.budget) { + out.println("THINK AGAIN! YOU HAVE ONLY $" + currentResources.budget); + } else { + validInputs = true; + } } } From b53005727860f312e22fd71db01a77a9e94c84b2 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 23:34:11 +0000 Subject: [PATCH 22/33] More robust validation --- 27_Civil_War/civilwar.bas | 44 +++++++++++++++++++++++------ 27_Civil_War/java/src/CivilWar.java | 26 +++++++++++++---- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/27_Civil_War/civilwar.bas b/27_Civil_War/civilwar.bas index f8fe9701..f9cb256b 100644 --- a/27_Civil_War/civilwar.bas +++ b/27_Civil_War/civilwar.bas @@ -172,23 +172,47 @@ 1840 PRINT 1850 REM - CHOOSE STRATEGIES 1860 IF B$ <> "YES" THEN 1910 -1870 FOR I=1 TO 2 -1880 ON I GOTO 1890,1920 + + +== 2 +1890 PRINT "CONFEDERATE STRATEGY "; +1920 INPUT Y +1930 IF ABS(Y-3)<3 THEN 1960 +1940 PRINT "STRATEGY";Y;"NOT ALLOWED." +1950 GOTO 1910 +2010 LET Y1=Y + +2020 PRINT "UNION STRATEGY "; +1920 INPUT Y +1930 IF ABS(Y-3)<3 THEN 1960 +1940 PRINT "STRATEGY";Y;"NOT ALLOWED." +1950 GOTO 1910 + + +== 1 1890 PRINT "CONFEDERATE STRATEGY "; -1900 GOTO 1920 -1910 PRINT "YOUR STRATEGY "; 1920 INPUT Y 1930 IF ABS(Y-3)<3 THEN 1960 1940 PRINT "STRATEGY";Y;"NOT ALLOWED." 1950 GOTO 1910 -1960 IF B$="YES" THEN 2000 1970 IF Y=5 THEN 2830 1980 GOSUB 3110 1990 GOTO 2170 -2000 IF I=2 THEN 2040 2010 LET Y1=Y -2020 PRINT "UNION STRATEGY "; -2030 NEXT I + +# 2020 PRINT "UNION STRATEGY "; +# 1920 INPUT Y +# 1930 IF ABS(Y-3)<3 THEN 1960 +# 1940 PRINT "STRATEGY";Y;"NOT ALLOWED." +# 1950 GOTO 1910 +# 1970 IF Y=5 THEN 2830 +# 1980 GOSUB 3110 +# 1990 GOTO 2170 + + + + + 2040 LET Y2=Y 2050 LET Y=Y1 2060 IF Y2=5 THEN 2020 @@ -298,6 +322,7 @@ 3080 PRINT S(1);S(2);S(3);S(4) 3090 REM--------------------------------- 3100 STOP + 3110 REM - UNION STRATEGY IS COMPUTER CHOSEN 3120 PRINT "UNION STRATEGY IS "; 3130 IF A <> 0 THEN 3180 @@ -318,6 +343,9 @@ 3270 LET Y2=I 3280 PRINT Y2 3290 RETURN + + + 3300 REM LEARN PRESENT STRATEGY, START FORGETTING OLD ONES 3310 REM - PRESENT STRATEGY OF SOUTH GAINS 3*S, OTHERS LOSE S 3320 REM PROBABILITY POINTS, UNLESS A STRATEGY FALLS BELOW 5%. diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index 7b8d8b4c..099a9183 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -598,17 +598,33 @@ public class CivilWar { } private static String inputString(Predicate validator, String reminder) { - var terminalInput = new Scanner(System.in); - while (true) { - var input = terminalInput.nextLine(); - if (validator.test(input)) { - return input; + 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; From 416033b8e0e68e2c067746f3b76f19f2898d7cfe Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 23:46:21 +0000 Subject: [PATCH 23/33] Improve battle number validation --- 27_Civil_War/java/src/CivilWar.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index 099a9183..7733fb7f 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -1,6 +1,8 @@ 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; @@ -116,15 +118,14 @@ public class CivilWar { out.println(); out.print("WHICH BATTLE DO YOU WISH TO SIMULATE ? "); - var terminalInput = new Scanner(System.in); - var battleNumber = terminalInput.nextInt(); + var battleNumber = inputInt(i -> i >= 1 || (i == 0 && this.currentBattle != null), i -> "BATTLE " + i + " NOT ALLOWED."); - if (battleNumber == 0 && this.currentBattle != null) { + if (battleNumber == 0) { out.println(this.currentBattle.data.name + " INSTANT REPLAY"); return this.currentBattle; } - if (battleNumber <= 0 || battleNumber > this.data.size()) { + if (battleNumber > this.data.size()) { // TYPE ANY OTHER NUMBER TO END THE SIMULATION return null; } @@ -177,6 +178,8 @@ public class CivilWar { // 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(); From 7fc9a1d8dc9024901c51895b965946ed981c379f Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 23:52:09 +0000 Subject: [PATCH 24/33] Changed in error --- 27_Civil_War/civilwar.bas | 44 +++++++-------------------------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/27_Civil_War/civilwar.bas b/27_Civil_War/civilwar.bas index f9cb256b..f8fe9701 100644 --- a/27_Civil_War/civilwar.bas +++ b/27_Civil_War/civilwar.bas @@ -172,47 +172,23 @@ 1840 PRINT 1850 REM - CHOOSE STRATEGIES 1860 IF B$ <> "YES" THEN 1910 - - -== 2 -1890 PRINT "CONFEDERATE STRATEGY "; -1920 INPUT Y -1930 IF ABS(Y-3)<3 THEN 1960 -1940 PRINT "STRATEGY";Y;"NOT ALLOWED." -1950 GOTO 1910 -2010 LET Y1=Y - -2020 PRINT "UNION STRATEGY "; -1920 INPUT Y -1930 IF ABS(Y-3)<3 THEN 1960 -1940 PRINT "STRATEGY";Y;"NOT ALLOWED." -1950 GOTO 1910 - - -== 1 +1870 FOR I=1 TO 2 +1880 ON I GOTO 1890,1920 1890 PRINT "CONFEDERATE STRATEGY "; +1900 GOTO 1920 +1910 PRINT "YOUR STRATEGY "; 1920 INPUT Y 1930 IF ABS(Y-3)<3 THEN 1960 1940 PRINT "STRATEGY";Y;"NOT ALLOWED." 1950 GOTO 1910 +1960 IF B$="YES" THEN 2000 1970 IF Y=5 THEN 2830 1980 GOSUB 3110 1990 GOTO 2170 +2000 IF I=2 THEN 2040 2010 LET Y1=Y - -# 2020 PRINT "UNION STRATEGY "; -# 1920 INPUT Y -# 1930 IF ABS(Y-3)<3 THEN 1960 -# 1940 PRINT "STRATEGY";Y;"NOT ALLOWED." -# 1950 GOTO 1910 -# 1970 IF Y=5 THEN 2830 -# 1980 GOSUB 3110 -# 1990 GOTO 2170 - - - - - +2020 PRINT "UNION STRATEGY "; +2030 NEXT I 2040 LET Y2=Y 2050 LET Y=Y1 2060 IF Y2=5 THEN 2020 @@ -322,7 +298,6 @@ 3080 PRINT S(1);S(2);S(3);S(4) 3090 REM--------------------------------- 3100 STOP - 3110 REM - UNION STRATEGY IS COMPUTER CHOSEN 3120 PRINT "UNION STRATEGY IS "; 3130 IF A <> 0 THEN 3180 @@ -343,9 +318,6 @@ 3270 LET Y2=I 3280 PRINT Y2 3290 RETURN - - - 3300 REM LEARN PRESENT STRATEGY, START FORGETTING OLD ONES 3310 REM - PRESENT STRATEGY OF SOUTH GAINS 3*S, OTHERS LOSE S 3320 REM PROBABILITY POINTS, UNLESS A STRATEGY FALLS BELOW 5%. From 373905adb2a84c524b7aa2e4097a52d83a38b208 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 23:59:42 +0000 Subject: [PATCH 25/33] Refactor / rework Union strategy for simulation --- 27_Civil_War/java/src/CivilWar.java | 51 +++++++++++++++-------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index 7733fb7f..d65601de 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -19,8 +19,8 @@ public class CivilWar { private int numGenerals; private final ArmyPair resources; private int battleNumber; - private int Y; - private int Y2; + private int confedStrategy; + private int unionStrategy; private final ArmyPair totalExpectedCasualties; private final ArmyPair totalCasualties; private boolean excessiveConfederateLosses; @@ -96,8 +96,6 @@ public class CivilWar { offensiveLogic(battle.data); - unionStrategy(); - calcLosses(battle); reset(); @@ -280,22 +278,26 @@ public class CivilWar { out.print("YOUR STRATEGY ? "); } - var terminalInput = new Scanner(System.in); - Y = terminalInput.nextInt(); - if (Math.abs(Y - 3) >= 3) { - out.println("STRATEGY " + Y + " NOT ALLOWED."); - // FIXME Proper numeric check!! Not abs - // FIXME Retry Y input + confedStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED."); + if (confedStrategy == 5) { // 1970 + confedSurrender = true; } - if (Y == 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(Y2 - Y) + 1))); + 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 @@ -316,7 +318,7 @@ public class CivilWar { out.println(); out.println(" CONFEDERACY UNION"); - var C5 = (2 * battle.data.expectedCasualties.confederate / 5) * (1 + 1.0 / (2 * (Math.abs(Y2 - Y) + 1))); + 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; @@ -393,7 +395,7 @@ public class CivilWar { totalTroops.confederate += battle.data.troops.confederate; totalTroops.union += battle.data.troops.union; - updateStrategies(this.Y); + updateStrategies(this.confedStrategy); } } @@ -414,11 +416,11 @@ public class CivilWar { out.println(); out.println("THE CONFEDERACY HAS WON " + results.confederate + " BATTLES AND LOST " + results.union); - if (this.Y2 == 5) { + if (this.unionStrategy == 5) { out.println("THE CONFEDERACY HAS WON THE WAR"); } - if (this.Y == 5 || results.confederate <= results.union) { + if (this.confedStrategy == 5 || results.confederate <= results.union) { out.println("THE UNION HAS WON THE WAR"); } @@ -466,16 +468,17 @@ public class CivilWar { } private void unionStrategy() { - if (this.battleNumber != 0) { + // 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); - Y2 = terminalInput.nextInt(); - if (Y2 < 0) { + unionStrategy = terminalInput.nextInt(); + if (unionStrategy < 0) { out.println("ENTER 1, 2, 3, OR 4 (USUALLY PREVIOUS UNION STRATEGY)"); // FIXME Retry Y2 input !!! } - if (Y2 < 5) { // 3155 + if (unionStrategy < 5) { // 3155 return; } } @@ -484,15 +487,15 @@ public class CivilWar { this.R = 100 * Math.random(); - for (Y2 = 0; Y2 < 4; Y2++) { - S0 += this.strategies[Y2]; + 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 " + Y2); + out.println("UNION STRATEGY IS " + unionStrategy); } public CivilWar(PrintStream out) { From 32f506a9c78c90760d68dbf47ffa34cca755a0ff Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sun, 16 Jan 2022 00:01:21 +0000 Subject: [PATCH 26/33] R => local --- 27_Civil_War/java/src/CivilWar.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index d65601de..a3992492 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -33,7 +33,6 @@ public class CivilWar { private int unionTroops; // M6 private boolean confedSurrender; private boolean unionSurrender; - private double R; private boolean wantBattleDescriptions; private final static String YES_NO_REMINDER = "(ANSWER YES OR NO)"; @@ -484,13 +483,12 @@ public class CivilWar { } var S0 = 0; - - this.R = 100 * Math.random(); + 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) { + if (r < S0) { break; } } From e43300a23ec8ef8ea1d14559479480574f173266 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sun, 16 Jan 2022 00:10:40 +0000 Subject: [PATCH 27/33] Refactor --- 27_Civil_War/java/src/CivilWar.java | 34 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java index a3992492..042126f6 100644 --- a/27_Civil_War/java/src/CivilWar.java +++ b/27_Civil_War/java/src/CivilWar.java @@ -14,26 +14,30 @@ public class CivilWar { private final PrintStream out; private final List data; + private final BattleResults results; + private BattleState currentBattle; - private final int[] strategies; private int numGenerals; - private final ArmyPair resources; 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 boolean excessiveConfederateLosses; - private boolean excessiveUnionLosses; - private final BattleResults results; private final ArmyPair revenue; private final ArmyPair inflation; private final ArmyPair totalExpenditure; private final ArmyPair totalTroops; - private int unionTroops; // M6 + + private boolean excessiveConfederateLosses; + private boolean excessiveUnionLosses; + private boolean confedSurrender; private boolean unionSurrender; - private boolean wantBattleDescriptions; private final static String YES_NO_REMINDER = "(ANSWER YES OR NO)"; private final static Predicate YES_NO_CHECKER = i -> isYes(i) || isNo(i); @@ -143,8 +147,6 @@ public class CivilWar { 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 - var confedTroops = (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); - this.unionTroops = (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0))); battleState.F1 = 5 * battle.troops.confederate / 6.0; if (this.numGenerals == 2) { @@ -168,7 +170,7 @@ public class CivilWar { out.println(); out.println(" CONFEDERACY UNION"); - out.println("MEN " + confedTroops + " " + unionTroops); + out.println("MEN " + getConfedTroops(battle) + " " + getUnionTroops(battle)); out.println("MONEY $ " + resources.confederate.budget + " $ " + resources.union.budget); out.println("INFLATION " + (inflation.confederate + 15) + "% " + inflation.union + "%"); @@ -228,6 +230,14 @@ public class CivilWar { 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(); @@ -302,8 +312,8 @@ public class CivilWar { // IF LOSS > MEN PRESENT, RESCALE LOSSES var moraleFactor = 100 / resources.union.morale; - if (Math.floor(losses + moraleFactor) >= unionTroops) { - losses = Math.floor(13.0 * unionTroops / 20); + if (Math.floor(losses + moraleFactor) >= getUnionTroops(battle)) { + losses = Math.floor(13.0 * getUnionTroops(battle) / 20); moraleFactor = 7 * losses / 13; excessiveUnionLosses = true; } From 5b1aaadac1c6e929c40892394832e0f8bf44ee92 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 12:30:34 +1100 Subject: [PATCH 28/33] Update with link to give credit where it is due, @jkboyce --- 84_Super_Star_Trek/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md index 28bd3c03..e47c90b7 100644 --- a/84_Super_Star_Trek/README.md +++ b/84_Super_Star_Trek/README.md @@ -93,7 +93,7 @@ The relation between the Historical and Standard nomenclatures is shown in the s Many of the programs in this book and this collection have bugs in the original code. -Jack Boyce has discovered and fixed a number of bugs in his [python implementation](python/superstartrek.py), which should be noted by other implementers: +@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 From c4f9b15db0f033373aa533969ea9274cd6ad26e0 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 13:09:47 +1100 Subject: [PATCH 29/33] Name should have an uppercase class name, and the correct file suffix. --- 63_Name/java/{main.class => Name.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename 63_Name/java/{main.class => Name.java} (98%) 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(" "); } From 18574bd155f20af30aa2789520d86526230a7ad1 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 13:24:12 +1100 Subject: [PATCH 30/33] Stop ignoring buildJvm and build.gradle files in gitignore! Add build files for some of the new games Basketball java Battle java Craps java Cube java High IQ java Name java --- .gitignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3412076c..64e591f9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,8 @@ .vscode/ .gradle/ node_modules/ -buildJvm/ - -build.gradle +buildJvm/bin +buildJvm/*/build/ .classpath .project From 1f018419a74aa836e94437ae48fdf52044fc1ff7 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 13:31:53 +1100 Subject: [PATCH 31/33] Second version of number has the wrong file name, and class names clashed. Added buildscript for Number1 --- 66_Number/java/{main.class => Number1.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename 66_Number/java/{main.class => Number1.java} (99%) diff --git a/66_Number/java/main.class b/66_Number/java/Number1.java similarity index 99% rename from 66_Number/java/main.class rename to 66_Number/java/Number1.java index c3df4ebf..c1d71bda 100644 --- a/66_Number/java/main.class +++ b/66_Number/java/Number1.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.Random; import java.util.Scanner; -public class Number { +public class Number1 { public static int points = 0; From 7278f607750bec360ede755cfda88f3b4fba8a9d Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 16 Jan 2022 13:45:52 +1100 Subject: [PATCH 32/33] Change buildscript for two versions of Number --- 66_Number/java/{Number1.java => 1/Number.java} | 2 +- 66_Number/java/{ => 2}/Number.java | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename 66_Number/java/{Number1.java => 1/Number.java} (99%) rename 66_Number/java/{ => 2}/Number.java (100%) diff --git a/66_Number/java/Number1.java b/66_Number/java/1/Number.java similarity index 99% rename from 66_Number/java/Number1.java rename to 66_Number/java/1/Number.java index c1d71bda..c3df4ebf 100644 --- a/66_Number/java/Number1.java +++ b/66_Number/java/1/Number.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.Random; import java.util.Scanner; -public class Number1 { +public class Number { public static int points = 0; 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 From e51103148bc9b9183dcdd4ca2174be28bd191dca Mon Sep 17 00:00:00 2001 From: RibTips <36372030+ribtips@users.noreply.github.com> Date: Sun, 16 Jan 2022 00:34:02 -0500 Subject: [PATCH 33/33] The game of craps in perl 29_Craps --- 29_Craps/perl/craps.pl | 113 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 29_Craps/perl/craps.pl 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"; +}