diff --git a/27_Civil_War/civilwar.bas b/27_Civil_War/civilwar.bas index 63597944..f8fe9701 100644 --- a/27_Civil_War/civilwar.bas +++ b/27_Civil_War/civilwar.bas @@ -216,7 +216,7 @@ 2280 PRINT "CASUALTIES",C5,C6 2290 PRINT "DESERTIONS",INT(E),INT(E2) 2300 PRINT -2310 IF B$ <> "YES" THEN 2350 +2310 IF B$ <> "YES" THEN 2530 2320 PRINT "COMPARED TO THE ACTUAL CASUALTIES AT "C$ 2330 PRINT "CONFEDERATE:"INT(100*(C5/C1)+.5)"% OF THE ORIGINAL" 2340 PRINT "UNION: "INT(100*(C6/C2)+.5)"% OF THE ORIGINAL" diff --git a/27_Civil_War/java/.gitignore b/27_Civil_War/java/.gitignore new file mode 100644 index 00000000..a87dc8b2 --- /dev/null +++ b/27_Civil_War/java/.gitignore @@ -0,0 +1,3 @@ +*.class +*.iml +.idea/ \ No newline at end of file diff --git a/27_Civil_War/java/src/CivilWar.java b/27_Civil_War/java/src/CivilWar.java new file mode 100644 index 00000000..042126f6 --- /dev/null +++ b/27_Civil_War/java/src/CivilWar.java @@ -0,0 +1,707 @@ +import java.io.PrintStream; +import java.util.InputMismatchException; +import java.util.List; +import java.util.Scanner; +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; + +@SuppressWarnings("SpellCheckingInspection") +public class CivilWar { + + private final PrintStream out; + private final List data; + + private final BattleResults results; + + private BattleState currentBattle; + private int numGenerals; + private int battleNumber; + private boolean wantBattleDescriptions; + private final int[] strategies; + + private int confedStrategy; + private int unionStrategy; + + private final ArmyPair resources; + private final ArmyPair totalExpectedCasualties; + private final ArmyPair totalCasualties; + private final ArmyPair revenue; + private final ArmyPair inflation; + private final ArmyPair totalExpenditure; + private final ArmyPair totalTroops; + + private boolean excessiveConfederateLosses; + private boolean excessiveUnionLosses; + + private boolean confedSurrender; + private boolean unionSurrender; + + private final static String YES_NO_REMINDER = "(ANSWER YES OR NO)"; + private final static Predicate YES_NO_CHECKER = i -> isYes(i) || isNo(i); + + /** + * ORIGINAL GAME DESIGN: CRAM, GOODIE, HIBBARD LEXINGTON H.S. + * MODIFICATIONS: G. PAUL, R. HESS (TIES), 1973 + */ + public static void main(String[] args) { + var x = new CivilWar(System.out); + x.showCredits(); + + // LET D=RND(-1) ??? + + System.out.print("DO YOU WANT INSTRUCTIONS? "); + + if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) { + x.showHelp(); + } + + x.gameLoop(); + } + + private void gameLoop() { + out.println(); + out.println(); + out.println(); + out.print("ARE THERE TWO GENERALS PRESENT (ANSWER YES OR NO)? "); + + if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) { + this.numGenerals = 2; + } else { + this.numGenerals = 1; + out.println(); + out.println("YOU ARE THE CONFEDERACY. GOOD LUCK!"); + out.println(); + } + + out.println("SELECT A BATTLE BY TYPING A NUMBER FROM 1 TO 14 ON"); + out.println("REQUEST. TYPE ANY OTHER NUMBER TO END THE SIMULATION."); + out.println("BUT '0' BRINGS BACK EXACT PREVIOUS BATTLE SITUATION"); + out.println("ALLOWING YOU TO REPLAY IT"); + out.println(); + out.println("NOTE: A NEGATIVE FOOD$ ENTRY CAUSES THE PROGRAM TO "); + out.println("USE THE ENTRIES FROM THE PREVIOUS BATTLE"); + out.println(); + + out.print("AFTER REQUESTING A BATTLE, DO YOU WISH BATTLE DESCRIPTIONS (ANSWER YES OR NO)? "); + + this.wantBattleDescriptions = isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER)); + + while (true) { + var battle = startBattle(); + if (battle == null) { + break; + } + + this.currentBattle = battle; + + offensiveLogic(battle.data); + + calcLosses(battle); + + reset(); + + if (this.confedSurrender) { + out.println("THE CONFEDERACY HAS SURRENDERED"); + } else if (unionSurrender) { // FIXME Is this actually possible? 2850 + out.println("THE UNION HAS SURRENDERED."); + } + } + + complete(); + } + + private BattleState startBattle() { + out.println(); + out.println(); + out.println(); + out.print("WHICH BATTLE DO YOU WISH TO SIMULATE ? "); + + var battleNumber = inputInt(i -> i >= 1 || (i == 0 && this.currentBattle != null), i -> "BATTLE " + i + " NOT ALLOWED."); + + if (battleNumber == 0) { + out.println(this.currentBattle.data.name + " INSTANT REPLAY"); + return this.currentBattle; + } + + if (battleNumber > this.data.size()) { // TYPE ANY OTHER NUMBER TO END THE SIMULATION + return null; + } + + this.battleNumber = battleNumber; + + var battle = this.data.get(this.battleNumber - 1); + var battleState = new BattleState(battle); + + + excessiveConfederateLosses = false; + + // INFLATION CALC + // REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% + inflation.confederate = 10 + (results.union - results.confederate) * 2; + inflation.union = 10 + (results.confederate - results.union) * 2; + + // MONEY AVAILABLE + resources.confederate.budget = 100 * (int) Math.floor((battle.troops.confederate * (100.0 - inflation.confederate) / 2000) * (1 + (revenue.confederate - totalExpenditure.confederate) / (revenue.confederate + 1.0)) + .5); + + // MEN AVAILABLE + battleState.F1 = 5 * battle.troops.confederate / 6.0; + + if (this.numGenerals == 2) { + resources.union.budget = 100 * (int) Math.floor((battle.troops.union * (100.0 - inflation.union) / 2000) * (1 + (revenue.union - totalExpenditure.union) / (revenue.union + 1.0)) + .5); + } else { + resources.union.budget = 100 * (int) Math.floor(battle.troops.union * (100.0 - inflation.union) / 2000 + .5); + } + + out.println(); + out.println(); + out.println(); + out.println(); + out.println(); + out.println("THIS IS THE BATTLE OF " + battle.name); + + if (this.wantBattleDescriptions) { + for (var eachLine : battle.blurb) { + out.println(eachLine); + } + } + + out.println(); + out.println(" CONFEDERACY UNION"); + out.println("MEN " + getConfedTroops(battle) + " " + getUnionTroops(battle)); + out.println("MONEY $ " + resources.confederate.budget + " $ " + resources.union.budget); + out.println("INFLATION " + (inflation.confederate + 15) + "% " + inflation.union + "%"); + + // ONLY IN PRINTOUT IS CONFED INFLATION = I1+15% + // IF TWO GENERALS, INPUT CONFED. FIRST + + var terminalInput = new Scanner(System.in); + + for (int i = 0; i < numGenerals; i++) { + out.println(); + + ArmyResources currentResources; + + if (this.numGenerals == 1 || i == 0) { + out.print("CONFEDERATE GENERAL --- "); + currentResources = resources.confederate; + } else { + out.print("UNION GENERAL --- "); + currentResources = resources.union; + } + + var validInputs = false; + while (!validInputs) { + out.println("HOW MUCH DO YOU WISH TO SPEND FOR"); + out.print("- FOOD...... ? "); + var food = terminalInput.nextInt(); + if (food == 0) { + if (this.revenue.confederate != 0) { + out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS"); + out.println(); + } + } else { + currentResources.food = food; + } + + out.print("- SALARIES.. ? "); + currentResources.salaries = terminalInput.nextInt(); + + out.print("- AMMUNITION ? "); + currentResources.ammunition = terminalInput.nextInt(); // FIXME Retry if -ve + + if (currentResources.getTotal() > currentResources.budget) { + out.println("THINK AGAIN! YOU HAVE ONLY $" + currentResources.budget); + } else { + validInputs = true; + } + } + } + + out.println(); + + // Record Morale + out.println(range(0, numGenerals).mapToObj(i -> moraleForArmy(battleState, i)).collect(joining(", "))); + + out.println(); + + return battleState; + } + + private int getUnionTroops(HistoricalDatum battle) { + return (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0))); + } + + private int getConfedTroops(HistoricalDatum battle) { + return (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); + } + + private String moraleForArmy(BattleState battleState, int armyIdx) { + var builder = new StringBuilder(); + + ArmyResources currentResources; + + if (this.numGenerals == 1 || armyIdx == 0) { + builder.append("CONFEDERATE "); + currentResources = resources.confederate; + } else { + builder.append("UNION "); + currentResources = resources.union; + } + + // FIND MORALE + currentResources.morale = (2 * Math.pow(currentResources.food, 2) + Math.pow(currentResources.salaries, 2)) / Math.pow(battleState.F1, 2) + 1; + if (currentResources.morale >= 10) { + builder.append("MORALE IS HIGH"); + } else if (currentResources.morale >= 5) { + builder.append("MORALE IS FAIR"); + } else { + builder.append("MORALE IS POOR"); + } + + return builder.toString(); + } + + private enum OffensiveStatus { + DEFENSIVE("YOU ARE ON THE DEFENSIVE"), OFFENSIVE("YOU ARE ON THE OFFENSIVE"), BOTH_OFFENSIVE("BOTH SIDES ARE ON THE OFFENSIVE"); + + private final String label; + + OffensiveStatus(String label) { + this.label = label; + } + } + + private void offensiveLogic(HistoricalDatum battle) { + out.print("CONFEDERATE GENERAL---"); + // ACTUAL OFF/DEF BATTLE SITUATION + out.println(battle.offensiveStatus.label); + + // CHOOSE STRATEGIES + + if (numGenerals == 2) { + out.print("CONFEDERATE STRATEGY ? "); + } else { + out.print("YOUR STRATEGY ? "); + } + + confedStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED."); + if (confedStrategy == 5) { // 1970 + confedSurrender = true; + } + + if (numGenerals == 2) { + out.print("UNION STRATEGY ? "); + + unionStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED."); + if (unionStrategy == 5) { // 1970 + unionSurrender = true; + } + } else { + unionStrategy(); + } + } + + // 2070 REM : SIMULATED LOSSES-NORTH + private UnionLosses simulateUnionLosses(HistoricalDatum battle) { + var losses = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1))); + losses = losses * (1.28 + (5.0 * battle.troops.union / 6) / (resources.union.ammunition + 1)); + losses = Math.floor(losses * (1 + 1 / resources.union.morale) + 0.5); + // IF LOSS > MEN PRESENT, RESCALE LOSSES + var moraleFactor = 100 / resources.union.morale; + + if (Math.floor(losses + moraleFactor) >= getUnionTroops(battle)) { + losses = Math.floor(13.0 * getUnionTroops(battle) / 20); + moraleFactor = 7 * losses / 13; + excessiveUnionLosses = true; + } + + return new UnionLosses((int) losses, (int) Math.floor(moraleFactor)); + } + + // 2170: CALCULATE SIMULATED LOSSES + private void calcLosses(BattleState battle) { + // 2190 + out.println(); + out.println(" CONFEDERACY UNION"); + + var C5 = (2 * battle.data.expectedCasualties.confederate / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1))); + C5 = (int) Math.floor(C5 * (1 + 1.0 / resources.confederate.morale) * (1.28 + battle.F1 / (resources.confederate.ammunition + 1.0)) + .5); + var E = 100 / resources.confederate.morale; + + if (C5 + 100 / resources.confederate.morale >= battle.data.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))) { + C5 = (int) Math.floor(13.0 * battle.data.troops.confederate / 20 * (1 + (totalExpectedCasualties.union - totalCasualties.confederate) / (totalTroops.confederate + 1.0))); + E = 7 * C5 / 13.0; + excessiveConfederateLosses = true; + } + + ///// 2270 + + final UnionLosses unionLosses; + + if (this.numGenerals == 1) { + unionLosses = new UnionLosses((int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)), (int) Math.floor(5 * resources.confederate.morale)); + } else { + unionLosses = simulateUnionLosses(battle.data); + } + + out.println("CASUALTIES: " + rightAlignInt(C5) + " " + rightAlignInt(unionLosses.losses)); + out.println("DESERTIONS: " + rightAlignInt(E) + " " + rightAlignInt(unionLosses.desertions)); + out.println(); + + if (numGenerals == 2) { + out.println("COMPARED TO THE ACTUAL CASUALTIES AT " + battle.data.name); + out.println("CONFEDERATE: " + (int) Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + " % OF THE ORIGINAL"); + out.println("UNION: " + (int) Math.floor(100 * (unionLosses.losses / (double) battle.data.expectedCasualties.union) + 0.5) + " % OF THE ORIGINAL"); + + out.println(); + + // REM - 1 WHO WON + var winner = findWinner(C5 + E, unionLosses.losses + unionLosses.desertions); + switch (winner) { + case UNION -> { + out.println("THE UNION WINS " + battle.data.name); + results.union++; + } + case CONFED -> { + out.println("THE CONFEDERACY WINS " + battle.data.name); + results.confederate++; + } + case INDECISIVE -> { + out.println("BATTLE OUTCOME UNRESOLVED"); + results.indeterminate++; + } + } + } else { + out.println("YOUR CASUALTIES WERE " + Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + "% OF THE ACTUAL CASUALTIES AT " + battle.data.name); + + // FIND WHO WON + + if (excessiveConfederateLosses) { + out.println("YOU LOSE " + battle.data.name); + + if (this.battleNumber != 0) { + results.union++; + } + } else { + out.println("YOU WIN " + battle.data.name); + // CUMULATIVE BATTLE FACTORS WHICH ALTER HISTORICAL RESOURCES AVAILABLE.IF A REPLAY DON'T UPDATE. + results.confederate++; + } + } + + if (this.battleNumber != 0) { + totalCasualties.confederate += (int) (C5 + E); + totalCasualties.union += unionLosses.losses + unionLosses.desertions; + totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate; + totalExpectedCasualties.union += battle.data.expectedCasualties.union; + totalExpenditure.confederate += resources.confederate.getTotal(); + totalExpenditure.union += resources.union.getTotal(); + revenue.confederate += battle.data.troops.confederate * (100 - inflation.confederate) / 20; + revenue.union += battle.data.troops.union * (100 - inflation.union) / 20; + totalTroops.confederate += battle.data.troops.confederate; + totalTroops.union += battle.data.troops.union; + + updateStrategies(this.confedStrategy); + } + } + + // 2790 + private void reset() { + excessiveConfederateLosses = excessiveUnionLosses = false; + + out.println("---------------"); + } + + // 2820 REM------FINISH OFF + private void complete() { + out.println(); + out.println(); + out.println(); + out.println(); + out.println(); + out.println(); + out.println("THE CONFEDERACY HAS WON " + results.confederate + " BATTLES AND LOST " + results.union); + + if (this.unionStrategy == 5) { + out.println("THE CONFEDERACY HAS WON THE WAR"); + } + + if (this.confedStrategy == 5 || results.confederate <= results.union) { + out.println("THE UNION HAS WON THE WAR"); + } + + out.println(); + + // FIXME 2960 IF R1=0 THEN 3100 + + out.println("FOR THE " + results.getTotal() + " BATTLES FOUGHT (EXCLUDING RERUNS)"); + out.println(" CONFEDERACY UNION"); + out.println("HISTORICAL LOSSES " + (int) Math.floor(totalExpectedCasualties.confederate + .5) + " " + (int) Math.floor(totalExpectedCasualties.union + .5)); + out.println("SIMULATED LOSSES " + (int) Math.floor(totalCasualties.confederate + .5) + " " + (int) Math.floor(totalCasualties.union + .5)); + out.println(); + out.println(" % OF ORIGINAL " + (int) Math.floor(100 * ((double) totalCasualties.confederate / totalExpectedCasualties.confederate) + .5) + " " + (int) Math.floor(100 * ((double) totalCasualties.union / totalExpectedCasualties.union) + .5)); + + if (this.numGenerals == 1) { + out.println(); + out.println("UNION INTELLIGENCE SUGGESTS THAT THE SOUTH USED "); + out.println("STRATEGIES 1, 2, 3, 4 IN THE FOLLOWING PERCENTAGES"); + out.println(this.strategies[0] + "," + this.strategies[1] + "," + this.strategies[2] + "," + this.strategies[3]); + } + } + + private Winner findWinner(double confLosses, double unionLosses) { + if (this.excessiveConfederateLosses && this.excessiveUnionLosses) { + return Winner.INDECISIVE; + } + + if (this.excessiveConfederateLosses) { + return Winner.UNION; + } + + if (this.excessiveUnionLosses || confLosses < unionLosses) { + return Winner.CONFED; + } + + if (confLosses == unionLosses) { + return Winner.INDECISIVE; + } + + return Winner.UNION; // FIXME Really? 2400-2420 ? + } + + private enum Winner { + CONFED, UNION, INDECISIVE + } + + private void unionStrategy() { + // 3130 ... so you can only input / override Union strategy on re-run?? + if (this.battleNumber == 0) { + out.print("UNION STRATEGY ? "); + var terminalInput = new Scanner(System.in); + unionStrategy = terminalInput.nextInt(); + if (unionStrategy < 0) { + out.println("ENTER 1, 2, 3, OR 4 (USUALLY PREVIOUS UNION STRATEGY)"); + // FIXME Retry Y2 input !!! + } + + if (unionStrategy < 5) { // 3155 + return; + } + } + + var S0 = 0; + var r = 100 * Math.random(); + + for (unionStrategy = 1; unionStrategy <= 4; unionStrategy++) { + S0 += this.strategies[unionStrategy - 1]; + // IF ACTUAL STRATEGY INFO IS IN PROGRAM DATA STATEMENTS THEN R-100 IS EXTRA WEIGHT GIVEN TO THAT STATEGY. + if (r < S0) { + break; + } + } + // IF ACTUAL STRAT. IN,THEN HERE IS Y2= HIST. STRAT. + out.println("UNION STRATEGY IS " + unionStrategy); + } + + public CivilWar(PrintStream out) { + this.out = out; + + this.results = new BattleResults(); + + this.totalCasualties = new ArmyPair<>(0, 0); + this.totalExpectedCasualties = new ArmyPair<>(0, 0); + this.totalExpenditure = new ArmyPair<>(0, 0); + this.totalTroops = new ArmyPair<>(0, 0); + + this.revenue = new ArmyPair<>(0, 0); + this.inflation = new ArmyPair<>(0, 0); + + this.resources = new ArmyPair<>(new ArmyResources(), new ArmyResources()); + + // UNION INFO ON LIKELY CONFEDERATE STRATEGY + this.strategies = new int[]{25, 25, 25, 25}; + + // READ HISTORICAL DATA. + // HISTORICAL DATA...CAN ADD MORE (STRAT.,ETC) BY INSERTING DATA STATEMENTS AFTER APPRO. INFO, AND ADJUSTING READ + this.data = List.of( + new HistoricalDatum("BULL RUN", new ArmyPair<>(18000, 18500), new ArmyPair<>(1967, 2708), OffensiveStatus.DEFENSIVE, new String[]{"JULY 21, 1861. GEN. BEAUREGARD, COMMANDING THE SOUTH, MET", "UNION FORCES WITH GEN. MCDOWELL IN A PREMATURE BATTLE AT", "BULL RUN. GEN. JACKSON HELPED PUSH BACK THE UNION ATTACK."}), + new HistoricalDatum("SHILOH", new ArmyPair<>(40000, 44894), new ArmyPair<>(10699, 13047), OffensiveStatus.OFFENSIVE, new String[]{"APRIL 6-7, 1862. THE CONFEDERATE SURPRISE ATTACK AT", "SHILOH FAILED DUE TO POOR ORGANIZATION."}), + new HistoricalDatum("SEVEN DAYS", new ArmyPair<>(95000, 115000), new ArmyPair<>(20614, 15849), OffensiveStatus.OFFENSIVE, new String[]{"JUNE 25-JULY 1, 1862. GENERAL LEE (CSA) UPHELD THE", "OFFENSIVE THROUGHOUT THE BATTLE AND FORCED GEN. MCCLELLAN", "AND THE UNION FORCES AWAY FROM RICHMOND."}), + new HistoricalDatum("SECOND BULL RUN", new ArmyPair<>(54000, 63000), new ArmyPair<>(10000, 14000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"AUG 29-30, 1862. THE COMBINED CONFEDERATE FORCES UNDER", " LEE", "AND JACKSON DROVE THE UNION FORCES BACK INTO WASHINGTON."}), + new HistoricalDatum("ANTIETAM", new ArmyPair<>(40000, 50000), new ArmyPair<>(10000, 12000), OffensiveStatus.OFFENSIVE, new String[]{"SEPT 17, 1862. THE SOUTH FAILED TO INCORPORATE MARYLAND", "INTO THE CONFEDERACY."}), + new HistoricalDatum("FREDERICKSBURG", new ArmyPair<>(75000, 120000), new ArmyPair<>(5377, 12653), OffensiveStatus.DEFENSIVE, new String[]{"DEC 13, 1862. THE CONFEDERACY UNDER LEE SUCCESSFULLY", "REPULSED AN ATTACK BY THE UNION UNDER GEN. BURNSIDE."}), + new HistoricalDatum("MURFREESBORO", new ArmyPair<>(38000, 45000), new ArmyPair<>(11000, 12000), OffensiveStatus.DEFENSIVE, new String[]{"DEC 31, 1862. THE SOUTH UNDER GEN. BRAGG WON A CLOSE BATTLE."}), + new HistoricalDatum("CHANCELLORSVILLE", new ArmyPair<>(32000, 90000), new ArmyPair<>(13000, 17197), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 1-6, 1863. THE SOUTH HAD A COSTLY VICTORY AND LOST", "ONE OF THEIR OUTSTANDING GENERALS, 'STONEWALL' JACKSON."}), + new HistoricalDatum("VICKSBURG", new ArmyPair<>(50000, 70000), new ArmyPair<>(12000, 19000), OffensiveStatus.DEFENSIVE, new String[]{"JULY 4, 1863. VICKSBURG WAS A COSTLY DEFEAT FOR THE SOUTH", "BECAUSE IT GAVE THE UNION ACCESS TO THE MISSISSIPPI."}), + new HistoricalDatum("GETTYSBURG", new ArmyPair<>(72500, 85000), new ArmyPair<>(20000, 23000), OffensiveStatus.OFFENSIVE, new String[]{"JULY 1-3, 1863. A SOUTHERN MISTAKE BY GEN. LEE AT GETTYSBURG", "COST THEM ONE OF THE MOST CRUCIAL BATTLES OF THE WAR."}), + new HistoricalDatum("CHICKAMAUGA", new ArmyPair<>(66000, 60000), new ArmyPair<>(18000, 16000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"SEPT. 15, 1863. CONFUSION IN A FOREST NEAR CHICKAMAUGA LED", "TO A COSTLY SOUTHERN VICTORY."}), + new HistoricalDatum("CHATTANOOGA", new ArmyPair<>(37000, 60000), new ArmyPair<>(36700, 5800), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"NOV. 25, 1863. AFTER THE SOUTH HAD SIEGED GEN. ROSENCRANS'", "ARMY FOR THREE MONTHS, GEN. GRANT BROKE THE SIEGE."}), + new HistoricalDatum("SPOTSYLVANIA", new ArmyPair<>(62000, 110000), new ArmyPair<>(17723, 18000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 5, 1864. GRANT'S PLAN TO KEEP LEE ISOLATED BEGAN TO", "FAIL HERE, AND CONTINUED AT COLD HARBOR AND PETERSBURG."}), + new HistoricalDatum("ATLANTA", new ArmyPair<>(65000, 100000), new ArmyPair<>(8500, 3700), OffensiveStatus.DEFENSIVE, new String[]{"AUGUST, 1864. SHERMAN AND THREE VETERAN ARMIES CONVERGED", "ON ATLANTA AND DEALT THE DEATH BLOW TO THE CONFEDERACY."}) + ); + } + + private void showCredits() { + out.println(" ".repeat(26) + "CIVIL WAR"); + out.println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + out.println(); + out.println(); + out.println(); + } + + private void updateStrategies(int strategy) { + // REM LEARN PRESENT STRATEGY, START FORGETTING OLD ONES + // REM - PRESENT STRATEGY OF SOUTH GAINS 3*S, OTHERS LOSE S + // REM PROBABILITY POINTS, UNLESS A STRATEGY FALLS BELOW 5%. + + var S = 3; + var S0 = 0; + for (int i = 0; i < 4; i++) { + if (this.strategies[i] <= 5) { + continue; + } + + this.strategies[i] -= S; + S0 += S; + + } + this.strategies[strategy - 1] += S0; + + } + + private void showHelp() { + out.println(); + out.println(); + out.println(); + out.println(); + out.println("THIS IS A CIVIL WAR SIMULATION."); + out.println("TO PLAY TYPE A RESPONSE WHEN THE COMPUTER ASKS."); + out.println("REMEMBER THAT ALL FACTORS ARE INTERRELATED AND THAT YOUR"); + out.println("RESPONSES COULD CHANGE HISTORY. FACTS AND FIGURES USED ARE"); + out.println("BASED ON THE ACTUAL OCCURRENCE. MOST BATTLES TEND TO RESULT"); + out.println("AS THEY DID IN THE CIVIL WAR, BUT IT ALL DEPENDS ON YOU!!"); + out.println(); + out.println("THE OBJECT OF THE GAME IS TO WIN AS MANY BATTLES AS "); + out.println("POSSIBLE."); + out.println(); + out.println("YOUR CHOICES FOR DEFENSIVE STRATEGY ARE:"); + out.println(" (1) ARTILLERY ATTACK"); + out.println(" (2) FORTIFICATION AGAINST FRONTAL ATTACK"); + out.println(" (3) FORTIFICATION AGAINST FLANKING MANEUVERS"); + out.println(" (4) FALLING BACK"); + out.println(" YOUR CHOICES FOR OFFENSIVE STRATEGY ARE:"); + out.println(" (1) ARTILLERY ATTACK"); + out.println(" (2) FRONTAL ATTACK"); + out.println(" (3) FLANKING MANEUVERS"); + out.println(" (4) ENCIRCLEMENT"); + out.println("YOU MAY SURRENDER BY TYPING A '5' FOR YOUR STRATEGY."); + } + + private static final int MAX_NUM_LENGTH = 6; + + private String rightAlignInt(int number) { + var s = String.valueOf(number); + return " ".repeat(MAX_NUM_LENGTH - s.length()) + s; + } + + private String rightAlignInt(double number) { + return rightAlignInt((int) Math.floor(number)); + } + + private static String inputString(Predicate validator, String reminder) { + while (true) { + try { + var input = new Scanner(System.in).nextLine(); + if (validator.test(input)) { + return input; + } + } catch (InputMismatchException e) { + // Ignore + } + System.out.println(reminder); + } + } + + private static int inputInt(Predicate validator, Function reminder) { + while (true) { + try { + var input = new Scanner(System.in).nextInt(); + if (validator.test(input)) { + return input; + } + System.out.println(reminder.apply(input)); + } catch (InputMismatchException e) { + System.out.println(reminder.apply(0)); + } + } + } + + private static boolean isYes(String s) { + if (s == null) { + return false; + } + var uppercase = s.toUpperCase(); + return uppercase.equals("Y") || uppercase.equals("YES"); + } + + private static boolean isNo(String s) { + if (s == null) { + return false; + } + var uppercase = s.toUpperCase(); + return uppercase.equals("N") || uppercase.equals("NO"); + } + + private static class BattleState { + private final HistoricalDatum data; + private double F1; + + public BattleState(HistoricalDatum data) { + this.data = data; + } + } + + private static class ArmyPair { + private T confederate; + private T union; + + public ArmyPair(T confederate, T union) { + this.confederate = confederate; + this.union = union; + } + } + + private static class BattleResults { + private int confederate; + private int union; + private int indeterminate; + + public int getTotal() { + return confederate + union + indeterminate; + } + } + + private static class ArmyResources { + private int food; + private int salaries; + private int ammunition; + private int budget; + + private double morale; // TODO really here? + + public int getTotal() { + return this.food + this.salaries + this.ammunition; + } + } + + private record HistoricalDatum(String name, ArmyPair troops, + ArmyPair expectedCasualties, + OffensiveStatus offensiveStatus, String[] blurb) { + } + + private record UnionLosses(int losses, int desertions) { + } +} \ No newline at end of file