mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
@@ -221,7 +221,7 @@
|
||||
2330 PRINT "CONFEDERATE:"INT(100*(C5/C1)+.5)"% OF THE ORIGINAL"
|
||||
2340 PRINT "UNION: "INT(100*(C6/C2)+.5)"% OF THE ORIGINAL"
|
||||
2350 PRINT
|
||||
2360 REM - 1 WHO ONE
|
||||
2360 REM - 1 WHO WON
|
||||
2370 IF U <> 1 THEN 2380
|
||||
2375 IF U2=1 THEN 2460
|
||||
2380 IF U=1 THEN 2420
|
||||
|
||||
25
27 Civil War/csharp/CivilWar/CivilWar.sln
Normal file
25
27 Civil War/csharp/CivilWar/CivilWar.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31005.135
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CivilWar", "CivilWar\CivilWar.csproj", "{09C22BBE-8480-4B8C-9A07-E2DAA24B692B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{09C22BBE-8480-4B8C-9A07-E2DAA24B692B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09C22BBE-8480-4B8C-9A07-E2DAA24B692B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09C22BBE-8480-4B8C-9A07-E2DAA24B692B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{09C22BBE-8480-4B8C-9A07-E2DAA24B692B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {59BCA2DE-D5C7-40F7-BB99-B5C8D2416D8B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
261
27 Civil War/csharp/CivilWar/CivilWar/Army.cs
Normal file
261
27 Civil War/csharp/CivilWar/CivilWar/Army.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CivilWar
|
||||
{
|
||||
public class Army
|
||||
{
|
||||
private enum Resource
|
||||
{
|
||||
Food,
|
||||
Salaries,
|
||||
Ammunition
|
||||
}
|
||||
|
||||
public Army(Side side)
|
||||
{
|
||||
Side = side;
|
||||
}
|
||||
|
||||
public Side Side { get; }
|
||||
|
||||
// Cumulative
|
||||
public int Wins { get; private set; } // W, L
|
||||
public int Losses { get; private set; } // L, W
|
||||
public int Draws { get; private set; } // W0
|
||||
public int BattlesFought => Wins + Draws + Losses;
|
||||
public bool Surrendered { get; private set; } // Y, Y2 == 5
|
||||
|
||||
public int CumulativeHistoricCasualties { get; private set; } // P1, P2
|
||||
public int CumulativeSimulatedCasualties { get; private set; } // T1, T2
|
||||
public int CumulativeHistoricMen { get; private set; } // M3, M4
|
||||
|
||||
private int income; // R1, R2
|
||||
private int moneySpent; // Q1, Q2
|
||||
|
||||
private bool IsFirstBattle => income == 0;
|
||||
|
||||
// This battle
|
||||
private int historicMen; // M1, M2
|
||||
public int HistoricCasualties { get; private set; }
|
||||
|
||||
public int Money { get; private set; } // D(n)
|
||||
public int Men { get; private set; } // M5, M6
|
||||
public int Inflation { get; private set; } // I1, I2
|
||||
public int InflationDisplay => Side == Side.Confederate ? Inflation + 15 : Inflation; // Confederate inflation is shown with 15 added - no idea why!
|
||||
|
||||
private readonly Dictionary<Resource, int> allocations = new(); // F(n), H(n), B(n) for food, salaries, ammunition
|
||||
|
||||
public int Strategy { get; protected set; } // Y1, Y2
|
||||
|
||||
public double Morale => (2.0 * allocations[Resource.Food] * allocations[Resource.Food] + allocations[Resource.Salaries] * allocations[Resource.Salaries]) / (reducedAvailableMen * reducedAvailableMen + 1); // O, O2
|
||||
|
||||
public int Casualties { get; protected set; } // C5, C6
|
||||
public int Desertions { get; protected set; } // E, E2
|
||||
public int MenLost => Casualties + Desertions;
|
||||
public bool AllLost { get; private set; } // U, U2
|
||||
|
||||
private double reducedAvailableMen; // F1
|
||||
|
||||
protected virtual double FractionUnspent => (income - moneySpent) / (income + 1.0);
|
||||
|
||||
public void PrepareBattle(int men, int casualties)
|
||||
{
|
||||
historicMen = men;
|
||||
HistoricCasualties = casualties;
|
||||
Inflation = 10 + (Losses - Wins) * 2;
|
||||
Money = 100 * (int)(men * (100 - Inflation) / 2000.0 * (1 + FractionUnspent) + 0.5);
|
||||
Men = (int)(men * 1 + (CumulativeHistoricCasualties - CumulativeSimulatedCasualties) / (CumulativeHistoricMen + 1.0));
|
||||
reducedAvailableMen = men * 5.0 / 6.0;
|
||||
}
|
||||
|
||||
public virtual void AllocateResources()
|
||||
{
|
||||
Console.WriteLine($"{Side} General ---\nHow much do you wish to spend for");
|
||||
while (true)
|
||||
{
|
||||
foreach (Resource resource in Enum.GetValues<Resource>())
|
||||
{
|
||||
if (EnterResource(resource))
|
||||
break;
|
||||
}
|
||||
if (allocations.Values.Sum() <= Money)
|
||||
return;
|
||||
Console.WriteLine($"Think again! You have only ${Money}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnterResource(Resource resource)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine($" - {resource}");
|
||||
switch ((int.TryParse(Console.ReadLine(), out int val), val))
|
||||
{
|
||||
case (false, _):
|
||||
Console.WriteLine("Not a valid number");
|
||||
break;
|
||||
case (_, < 0):
|
||||
Console.WriteLine("Negative values not allowed");
|
||||
break;
|
||||
case (_, 0) when IsFirstBattle:
|
||||
Console.WriteLine("No previous entries");
|
||||
break;
|
||||
case (_, 0):
|
||||
Console.WriteLine("Assume you want to keep same allocations");
|
||||
return true;
|
||||
case (_, > 0):
|
||||
allocations[resource] = val;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void DisplayMorale()
|
||||
{
|
||||
Console.WriteLine($"{Side} morale is {Morale switch { < 5 => "Poor", < 10 => "Fair", _ => "High" }}");
|
||||
}
|
||||
|
||||
public virtual bool ChooseStrategy(bool isReplay) => EnterStrategy(true, "(1-5)");
|
||||
|
||||
protected bool EnterStrategy(bool canSurrender, string hint)
|
||||
{
|
||||
Console.WriteLine($"{Side} strategy {hint}");
|
||||
while (true)
|
||||
{
|
||||
switch ((int.TryParse(Console.ReadLine(), out int val), val))
|
||||
{
|
||||
case (false, _):
|
||||
Console.WriteLine("Not a valid number");
|
||||
break;
|
||||
case (_, 5) when canSurrender:
|
||||
Surrendered = true;
|
||||
Console.WriteLine($"The {Side} general has surrendered");
|
||||
return true;
|
||||
case (_, < 1 or >= 5):
|
||||
Console.WriteLine($"Strategy {val} not allowed.");
|
||||
break;
|
||||
default:
|
||||
Strategy = val;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void CalculateLosses(Army opponent)
|
||||
{
|
||||
AllLost = false;
|
||||
int stratFactor = 2 * (Math.Abs(Strategy - opponent.Strategy) + 1);
|
||||
Casualties = (int)Math.Round(HistoricCasualties * 0.4 * (1 + 1.0 / stratFactor) * (1 + 1 / Morale) * (1.28 + reducedAvailableMen / (allocations[Resource.Ammunition] + 1)));
|
||||
Desertions = (int)Math.Round(100 / Morale);
|
||||
|
||||
// If losses > men present, rescale losses
|
||||
if (MenLost > Men)
|
||||
{
|
||||
Casualties = 13 * Men / 20;
|
||||
Desertions = Men - Casualties;
|
||||
AllLost = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordResult(Side winner)
|
||||
{
|
||||
if (winner == Side)
|
||||
Wins++;
|
||||
else if (winner == Side.Both)
|
||||
Draws++;
|
||||
else
|
||||
Losses++;
|
||||
|
||||
CumulativeSimulatedCasualties += MenLost;
|
||||
CumulativeHistoricCasualties += HistoricCasualties;
|
||||
moneySpent += allocations.Values.Sum();
|
||||
income += historicMen * (100 - Inflation) / 20;
|
||||
CumulativeHistoricMen += historicMen;
|
||||
|
||||
LearnStrategy();
|
||||
}
|
||||
|
||||
protected virtual void LearnStrategy() { }
|
||||
|
||||
public void DisplayWarResult(Army opponent)
|
||||
{
|
||||
Console.WriteLine("\n\n\n\n");
|
||||
Console.WriteLine($"The {Side} general has won {Wins} battles and lost {Losses}");
|
||||
Side winner = (Surrendered, opponent.Surrendered, Wins < Losses) switch
|
||||
{
|
||||
(_, true, _) => Side,
|
||||
(true, _, _) or (_, _, true) => opponent.Side,
|
||||
_ => Side
|
||||
};
|
||||
Console.WriteLine($"The {winner} general has won the war\n");
|
||||
}
|
||||
|
||||
public virtual void DisplayStrategies() { }
|
||||
}
|
||||
|
||||
class ComputerArmy : Army
|
||||
{
|
||||
public int[] StrategyProb { get; } = { 25, 25, 25, 25 }; // S(n)
|
||||
private readonly Random strategyRng = new();
|
||||
|
||||
public ComputerArmy(Side side) : base(side) { }
|
||||
|
||||
protected override double FractionUnspent => 0.0;
|
||||
|
||||
public override void AllocateResources() { }
|
||||
|
||||
public override void DisplayMorale() { }
|
||||
|
||||
public override bool ChooseStrategy(bool isReplay)
|
||||
{
|
||||
if (isReplay)
|
||||
return EnterStrategy(false, $"(1-4; usually previous {Side} strategy)");
|
||||
|
||||
// Basic code comments say "If actual strategy info is in data then r-100 is extra weight given to that strategy" but there's no data or code to do it.
|
||||
int strategyChosenProb = strategyRng.Next(100); // 0-99
|
||||
int sumProbs = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
sumProbs += StrategyProb[i];
|
||||
if (strategyChosenProb < sumProbs)
|
||||
{
|
||||
Strategy = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"{Side} strategy is {Strategy}");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void LearnStrategy()
|
||||
{
|
||||
// Learn present strategy, start forgetting old ones
|
||||
// - present strategy gains 3 * s, others lose s probability points, unless a strategy falls below 5 %.
|
||||
const int s = 3;
|
||||
int presentGain = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (StrategyProb[i] >= 5)
|
||||
{
|
||||
StrategyProb[i] -= s;
|
||||
presentGain += s;
|
||||
}
|
||||
}
|
||||
StrategyProb[Strategy - 1] += presentGain;
|
||||
}
|
||||
|
||||
public override void CalculateLosses(Army opponent)
|
||||
{
|
||||
Casualties = (int)(17.0 * HistoricCasualties * opponent.HistoricCasualties / (opponent.Casualties * 20));
|
||||
Desertions = (int)(5 * opponent.Morale);
|
||||
}
|
||||
|
||||
public override void DisplayStrategies()
|
||||
{
|
||||
ConsoleUtils.WriteWordWrap($"\nIntelligence suggests that the {Side} general used strategies 1, 2, 3, 4 in the following percentages:");
|
||||
Console.WriteLine(string.Join(", ", StrategyProb));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
27 Civil War/csharp/CivilWar/CivilWar/Battle.cs
Normal file
40
27 Civil War/csharp/CivilWar/CivilWar/Battle.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CivilWar
|
||||
{
|
||||
public enum Side { Confederate, Union, Both }
|
||||
public enum Option { Battle, Replay, Quit }
|
||||
|
||||
public record Battle(string Name, int[] Men, int[] Casualties, Side Offensive, string Description)
|
||||
{
|
||||
public static readonly List<Battle> Historic = new()
|
||||
{
|
||||
new("Bull Run", new[] { 18000, 18500 }, new[] { 1967, 2708 }, Side.Union, "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("Shiloh", new[] { 40000, 44894 }, new[] { 10699, 13047 }, Side.Both, "April 6-7, 1862. The confederate surprise attack at Shiloh failed due to poor organization."),
|
||||
new("Seven Days", new[] { 95000, 115000 }, new[] { 20614, 15849 }, Side.Both, "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("Second Bull Run", new[] { 54000, 63000 }, new[] { 10000, 14000 }, Side.Confederate, "Aug 29-30, 1862. The combined confederate forces under Lee and Jackson drove the union forces back into Washington."),
|
||||
new("Antietam", new[] { 40000, 50000 }, new[] { 10000, 12000 }, Side.Both, "Sept 17, 1862. The south failed to incorporate Maryland into the confederacy."),
|
||||
new("Fredericksburg", new[] { 75000, 120000 }, new[] { 5377, 12653 }, Side.Union, "Dec 13, 1862. The confederacy under Lee successfully repulsed an attack by the union under Gen. Burnside."),
|
||||
new("Murfreesboro", new[] { 38000, 45000 }, new[] { 11000, 12000 }, Side.Union, "Dec 31, 1862. The south under Gen. Bragg won a close battle."),
|
||||
new("Chancellorsville", new[] { 32000, 90000 }, new[] { 13000, 17197 }, Side.Confederate, "May 1-6, 1863. The south had a costly victory and lost one of their outstanding generals, 'stonewall' Jackson."),
|
||||
new("Vicksburg", new[] { 50000, 70000 }, new[] { 12000, 19000 }, Side.Union, "July 4, 1863. Vicksburg was a costly defeat for the south because it gave the union access to the Mississippi."),
|
||||
new("Gettysburg", new[] { 72500, 85000 }, new[] { 20000, 23000 }, Side.Both, "July 1-3, 1863. A southern mistake by Gen. Lee at Gettysburg cost them one of the most crucial battles of the war."),
|
||||
new("Chickamauga", new[] { 66000, 60000 }, new[] { 18000, 16000 }, Side.Confederate, "Sept. 15, 1863. Confusion in a forest near Chickamauga led to a costly southern victory."),
|
||||
new("Chattanooga", new[] { 37000, 60000 }, new[] { 36700, 5800 }, Side.Confederate, "Nov. 25, 1863. After the south had sieged Gen. Rosencrans’ army for three months, Gen. Grant broke the siege."),
|
||||
new("Spotsylvania", new[] { 62000, 110000 }, new[] { 17723, 18000 }, Side.Confederate, "May 5, 1864. Grant's plan to keep Lee isolated began to fail here, and continued at Cold Harbor and Petersburg."),
|
||||
new("Atlanta", new[] { 65000, 100000 }, new[] { 8500, 3700 }, Side.Union, "August, 1864. Sherman and three veteran armies converged on Atlanta and dealt the death blow to the confederacy."),
|
||||
};
|
||||
|
||||
public static (Option, Battle?) SelectBattle()
|
||||
{
|
||||
Console.WriteLine("\n\n\nWhich battle do you wish to simulate?");
|
||||
return int.Parse(Console.ReadLine() ?? "") switch
|
||||
{
|
||||
0 => (Option.Replay, null),
|
||||
>0 and <15 and int n => (Option.Battle, Historic[n-1]),
|
||||
_ => (Option.Quit, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
27 Civil War/csharp/CivilWar/CivilWar/CivilWar.csproj
Normal file
9
27 Civil War/csharp/CivilWar/CivilWar/CivilWar.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
105
27 Civil War/csharp/CivilWar/CivilWar/ConsoleUtils.cs
Normal file
105
27 Civil War/csharp/CivilWar/CivilWar/ConsoleUtils.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CivilWar
|
||||
{
|
||||
static class ConsoleUtils
|
||||
{
|
||||
public static bool InputYesOrNo()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var answer = Console.ReadLine();
|
||||
switch (answer?.ToLower())
|
||||
{
|
||||
case "no":
|
||||
return false;
|
||||
case "yes":
|
||||
return true;
|
||||
default:
|
||||
Console.WriteLine("(Answer Yes or No)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteWordWrap(string text)
|
||||
{
|
||||
var line = new StringBuilder(Console.WindowWidth);
|
||||
foreach (var paragraph in text.Split(Environment.NewLine))
|
||||
{
|
||||
line.Clear();
|
||||
foreach (var word in paragraph.Split(' '))
|
||||
{
|
||||
if (line.Length + word.Length < Console.WindowWidth)
|
||||
{
|
||||
if (line.Length > 0)
|
||||
line.Append(' ');
|
||||
line.Append(word);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(line.ToString());
|
||||
line.Clear().Append(word);
|
||||
}
|
||||
}
|
||||
Console.WriteLine(line.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteTable<T>(ICollection<T> items, List<TableRow<T>> rows, bool transpose = false)
|
||||
{
|
||||
int cols = items.Count + 1;
|
||||
var content = rows.Select(r => r.Format(items)).ToList();
|
||||
if (transpose)
|
||||
{
|
||||
content = Enumerable.Range(0, cols).Select(col => content.Select(r => r[col]).ToArray()).ToList();
|
||||
cols = rows.Count;
|
||||
}
|
||||
var colWidths = Enumerable.Range(0, cols).Select(col => content.Max(c => c[col].Length)).ToArray();
|
||||
|
||||
foreach (var row in content)
|
||||
{
|
||||
for (int col = 0; col < cols; col++)
|
||||
{
|
||||
var space = new string(' ', colWidths[col] - row[col].Length);
|
||||
row[col] = col == 0 ? row[col] + space : space + row[col]; // left-align first col; right-align other cols
|
||||
}
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var horizBars = colWidths.Select(w => new string('═', w)).ToArray();
|
||||
|
||||
void OneRow(string[] cells, char before, char between, char after)
|
||||
{
|
||||
sb.Append(before);
|
||||
sb.AppendJoin(between, cells);
|
||||
sb.Append(after);
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
OneRow(horizBars, '╔', '╦', '╗');
|
||||
bool first = true;
|
||||
foreach (var row in content)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
OneRow(horizBars, '╠', '╬', '╣');
|
||||
OneRow(row, '║', '║', '║');
|
||||
}
|
||||
OneRow(horizBars, '╚', '╩', '╝');
|
||||
|
||||
Console.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
public record TableRow<T>(string Name, Func<T, object> Data, string Before = "", string After = "")
|
||||
{
|
||||
private string FormatItem(T item) => $" {Before}{Data(item)}{After} ";
|
||||
|
||||
public string[] Format(IEnumerable<T> items) => items.Select(FormatItem).Prepend($" {Name} ").ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
55
27 Civil War/csharp/CivilWar/CivilWar/GameOptions.cs
Normal file
55
27 Civil War/csharp/CivilWar/CivilWar/GameOptions.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
using static CivilWar.ConsoleUtils;
|
||||
|
||||
namespace CivilWar
|
||||
{
|
||||
public record GameOptions(bool TwoPlayers, bool ShowDescriptions)
|
||||
{
|
||||
public static GameOptions Input()
|
||||
{
|
||||
Console.WriteLine(
|
||||
@" Civil War
|
||||
Creative Computing, Morristown, New Jersey
|
||||
|
||||
|
||||
Do you want instructions?");
|
||||
|
||||
const string instructions = @"This is a civil war simulation.
|
||||
To play type a response when the computer asks.
|
||||
Remember that all factors are interrelated and that your responses could change history. Facts and figures used are based on the actual occurrence. Most battles tend to result as they did in the civil war, but it all depends on you!!
|
||||
|
||||
The object of the game is to win as many battles as possible.
|
||||
|
||||
Your choices for defensive strategy are:
|
||||
(1) artillery attack
|
||||
(2) fortification against frontal attack
|
||||
(3) fortification against flanking maneuvers
|
||||
(4) falling back
|
||||
Your choices for offensive strategy are:
|
||||
(1) artillery attack
|
||||
(2) frontal attack
|
||||
(3) flanking maneuvers
|
||||
(4) encirclement
|
||||
You may surrender by typing a '5' for your strategy.";
|
||||
|
||||
if (InputYesOrNo())
|
||||
WriteWordWrap(instructions);
|
||||
|
||||
Console.WriteLine("\n\nAre there two generals present?");
|
||||
bool twoPlayers = InputYesOrNo();
|
||||
if (!twoPlayers)
|
||||
Console.WriteLine("\nYou are the confederacy. Good luck!\n");
|
||||
|
||||
WriteWordWrap(
|
||||
@"Select a battle by typing a number from 1 to 14 on request. Type any other number to end the simulation. But '0' brings back exact previous battle situation allowing you to replay it.
|
||||
|
||||
Note: a negative Food$ entry causes the program to use the entries from the previous battle
|
||||
|
||||
After requesting a battle, do you wish battle descriptions (answer yes or no)");
|
||||
bool showDescriptions = InputYesOrNo();
|
||||
|
||||
return new GameOptions(twoPlayers, showDescriptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
27 Civil War/csharp/CivilWar/CivilWar/Program.cs
Normal file
119
27 Civil War/csharp/CivilWar/CivilWar/Program.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CivilWar;
|
||||
|
||||
var options = GameOptions.Input();
|
||||
var armies = new List<Army> { new Army(Side.Confederate), options.TwoPlayers ? new Army(Side.Union) : new ComputerArmy(Side.Union) };
|
||||
|
||||
Battle? battle = null;
|
||||
while (OneBattle(ref battle)) { }
|
||||
DisplayResult();
|
||||
|
||||
bool OneBattle(ref Battle? previous)
|
||||
{
|
||||
var (option, selected) = Battle.SelectBattle();
|
||||
var (battle, isReplay, quit) = option switch
|
||||
{
|
||||
Option.Battle => (selected!, false, false),
|
||||
Option.Replay when previous != null => (previous, true, false), // can't replay if no previous battle
|
||||
_ => (null!, false, true),
|
||||
};
|
||||
if (quit)
|
||||
return false;
|
||||
|
||||
if (!isReplay)
|
||||
{
|
||||
Console.WriteLine($"This is the battle of {battle.Name}.");
|
||||
if (options.ShowDescriptions)
|
||||
ConsoleUtils.WriteWordWrap(battle.Description);
|
||||
armies.ForEach(a => a.PrepareBattle(battle.Men[(int)a.Side], battle.Casualties[(int)a.Side]));
|
||||
}
|
||||
|
||||
ConsoleUtils.WriteTable(armies, new()
|
||||
{
|
||||
new("", a => a.Side),
|
||||
new("Men", a => a.Men),
|
||||
new("Money", a => a.Money, Before: "$"),
|
||||
new("Inflation", a => a.InflationDisplay, After: "%")
|
||||
});
|
||||
|
||||
armies.ForEach(a => a.AllocateResources());
|
||||
armies.ForEach(a => a.DisplayMorale());
|
||||
|
||||
string offensive = battle.Offensive switch
|
||||
{
|
||||
Side.Confederate => "You are on the offensive",
|
||||
Side.Union => "You are on the defensive",
|
||||
_ => "Both sides are on the offensive"
|
||||
};
|
||||
Console.WriteLine($"Confederate general---{offensive}");
|
||||
|
||||
if (armies.Any(a => a.ChooseStrategy(isReplay)))
|
||||
{
|
||||
return false; // someone surrendered
|
||||
}
|
||||
armies[0].CalculateLosses(armies[1]);
|
||||
armies[1].CalculateLosses(armies[0]);
|
||||
|
||||
ConsoleUtils.WriteTable(armies, new()
|
||||
{
|
||||
new("", a => a.Side),
|
||||
new("Casualties", a => a.Casualties),
|
||||
new("Desertions", a => a.Desertions),
|
||||
});
|
||||
if (options.TwoPlayers)
|
||||
{
|
||||
var oneDataCol = new[] { 1 };
|
||||
Console.WriteLine($"Compared to the actual casualties at {battle.Name}");
|
||||
ConsoleUtils.WriteTable(oneDataCol, armies.Select(a => new ConsoleUtils.TableRow<int>(
|
||||
a.Side.ToString(),
|
||||
_ => $"{(double)a.Casualties / battle.Casualties[(int)a.Side]}", After: "% of the original")
|
||||
).ToList());
|
||||
}
|
||||
|
||||
Side winner;
|
||||
switch (armies[0].AllLost, armies[1].AllLost, armies[0].MenLost - armies[1].MenLost)
|
||||
{
|
||||
case (true, true, _) or (false, false, 0):
|
||||
Console.WriteLine("Battle outcome unresolved");
|
||||
winner = Side.Both; // Draw
|
||||
break;
|
||||
case (false, true, _) or (false, false, < 0):
|
||||
Console.WriteLine($"The Confederacy wins {battle.Name}");
|
||||
winner = Side.Confederate;
|
||||
break;
|
||||
case (true, false, _) or (false, false, > 0):
|
||||
Console.WriteLine($"The Union wins {battle.Name}");
|
||||
winner = Side.Union;
|
||||
break;
|
||||
}
|
||||
if (!isReplay)
|
||||
{
|
||||
armies.ForEach(a => a.RecordResult(winner));
|
||||
}
|
||||
Console.WriteLine("---------------");
|
||||
previous = battle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayResult()
|
||||
{
|
||||
armies[0].DisplayWarResult(armies[1]);
|
||||
|
||||
int battles = armies[0].BattlesFought;
|
||||
if (battles > 0)
|
||||
{
|
||||
Console.WriteLine($"For the {battles} battles fought (excluding reruns)");
|
||||
|
||||
ConsoleUtils.WriteTable(armies, new()
|
||||
{
|
||||
new("", a => a.Side),
|
||||
new("Historical Losses", a => a.CumulativeHistoricCasualties),
|
||||
new("Simulated Losses", a => a.CumulativeSimulatedCasualties),
|
||||
new(" % of original", a => ((double)a.CumulativeSimulatedCasualties / a.CumulativeHistoricCasualties).ToString("p2"))
|
||||
}, transpose: true);
|
||||
|
||||
armies[1].DisplayStrategies();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user