From e5c7625101ab8dcd1dce20d70d5beab7d633d16e Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sun, 2 Jan 2022 22:31:41 +0000 Subject: [PATCH 01/14] 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/14] 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/14] 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 1085d282fff2f6d369ed302b278b4e15c4113eba Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 16:32:12 +0000 Subject: [PATCH 04/14] 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 2e82f08efddbda677b161d2d0cf7ee250f561525 Mon Sep 17 00:00:00 2001 From: Andrew Regan Date: Sat, 15 Jan 2022 22:11:45 +0000 Subject: [PATCH 05/14] 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 06/14] 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 07/14] 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 08/14] 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 09/14] 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 10/14] 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 11/14] 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 12/14] 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 13/14] 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 14/14] 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; }