mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-26 12:51:29 -08:00
25
28 Combat/csharp/Combat.sln
Normal file
25
28 Combat/csharp/Combat.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31321.278
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game", "Game.csproj", "{054A1718-1B7D-4954-81A7-EEA390713439}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{054A1718-1B7D-4954-81A7-EEA390713439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{054A1718-1B7D-4954-81A7-EEA390713439}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{054A1718-1B7D-4954-81A7-EEA390713439}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{054A1718-1B7D-4954-81A7-EEA390713439}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1EBA7488-1DA6-4B0B-8234-F10A65E96BDB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
6
28 Combat/csharp/Game.csproj
Normal file
6
28 Combat/csharp/Game.csproj
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,3 +1,7 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
|
||||
|
||||
The original BASIC code has a surprising number of bugs for such a small program.
|
||||
For the sake of preserving the original behaviour, I've left them in place and
|
||||
commented the ones I noticed.
|
||||
|
||||
42
28 Combat/csharp/src/ArmedForces.cs
Normal file
42
28 Combat/csharp/src/ArmedForces.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the armed forces for a country.
|
||||
/// </summary>
|
||||
public record ArmedForces
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of men and women in the army.
|
||||
/// </summary>
|
||||
public int Army { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of men and women in the navy.
|
||||
/// </summary>
|
||||
public int Navy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of men and women in the air force.
|
||||
/// </summary>
|
||||
public int AirForce { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of troops in the armed forces.
|
||||
/// </summary>
|
||||
public int TotalTroops => Army + Navy + AirForce;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of men and women in the given branch.
|
||||
/// </summary>
|
||||
public int this[MilitaryBranch branch] =>
|
||||
branch switch
|
||||
{
|
||||
MilitaryBranch.Army => Army,
|
||||
MilitaryBranch.Navy => Navy,
|
||||
MilitaryBranch.AirForce => AirForce,
|
||||
_ => throw new ArgumentException("INVALID BRANCH")
|
||||
};
|
||||
}
|
||||
}
|
||||
60
28 Combat/csharp/src/Ceasefire.cs
Normal file
60
28 Combat/csharp/src/Ceasefire.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of the game after reaching a ceasefire.
|
||||
/// </summary>
|
||||
public sealed class Ceasefire : WarState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether the player achieved absolute victory.
|
||||
/// </summary>
|
||||
public override bool IsAbsoluteVictory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the outcome of the war.
|
||||
/// </summary>
|
||||
public override WarResult? FinalOutcome
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsAbsoluteVictory || PlayerForces.TotalTroops > 3 / 2 * ComputerForces.TotalTroops)
|
||||
return WarResult.PlayerVictory;
|
||||
else
|
||||
if (PlayerForces.TotalTroops < 2 / 3 * ComputerForces.TotalTroops)
|
||||
return WarResult.ComputerVictory;
|
||||
else
|
||||
return WarResult.PeaceTreaty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Ceasefire class.
|
||||
/// </summary>
|
||||
/// <param name="computerForces">
|
||||
/// The computer's forces.
|
||||
/// </param>
|
||||
/// <param name="playerForces">
|
||||
/// The player's forces.
|
||||
/// </param>
|
||||
/// <param name="absoluteVictory">
|
||||
/// Indicates whether the player acheived absolute victory (defeating
|
||||
/// the computer without destroying its military).
|
||||
/// </param>
|
||||
public Ceasefire(ArmedForces computerForces, ArmedForces playerForces, bool absoluteVictory = false)
|
||||
: base(computerForces, playerForces)
|
||||
{
|
||||
IsAbsoluteVictory = absoluteVictory;
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithArmy(int attackSize) =>
|
||||
throw new InvalidOperationException("THE WAR IS OVER");
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithNavy(int attackSize) =>
|
||||
throw new InvalidOperationException("THE WAR IS OVER");
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithAirForce(int attackSize) =>
|
||||
throw new InvalidOperationException("THE WAR IS OVER");
|
||||
}
|
||||
}
|
||||
102
28 Combat/csharp/src/Controller.cs
Normal file
102
28 Combat/csharp/src/Controller.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for interacting with the user.
|
||||
/// </summary>
|
||||
public class Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player's initial armed forces distribution.
|
||||
/// </summary>
|
||||
/// <param name="computerForces">
|
||||
/// The computer's initial armed forces.
|
||||
/// </param>
|
||||
public static ArmedForces GetInitialForces(ArmedForces computerForces)
|
||||
{
|
||||
var playerForces = default(ArmedForces);
|
||||
|
||||
// BUG: This loop allows the player to assign negative values to
|
||||
// some branches, leading to strange results.
|
||||
do
|
||||
{
|
||||
View.ShowDistributeForces();
|
||||
|
||||
View.PromptArmySize(computerForces.Army);
|
||||
var army = InputInteger();
|
||||
|
||||
View.PromptNavySize(computerForces.Navy);
|
||||
var navy = InputInteger();
|
||||
|
||||
View.PromptAirForceSize(computerForces.AirForce);
|
||||
var airForce = InputInteger();
|
||||
|
||||
playerForces = new ArmedForces
|
||||
{
|
||||
Army = army,
|
||||
Navy = navy,
|
||||
AirForce = airForce
|
||||
};
|
||||
}
|
||||
while (playerForces.TotalTroops > computerForces.TotalTroops);
|
||||
|
||||
return playerForces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the military branch for the user's next attack.
|
||||
/// </summary>
|
||||
public static MilitaryBranch GetAttackBranch(WarState state, bool isFirstTurn)
|
||||
{
|
||||
if (isFirstTurn)
|
||||
View.PromptFirstAttackBranch();
|
||||
else
|
||||
View.PromptNextAttackBranch(state.ComputerForces, state.PlayerForces);
|
||||
|
||||
// If the user entered an invalid branch number in the original
|
||||
// game, the code fell through to the army case. We'll preserve
|
||||
// that behaviour here.
|
||||
return Console.ReadLine() switch
|
||||
{
|
||||
"2" => MilitaryBranch.Navy,
|
||||
"3" => MilitaryBranch.AirForce,
|
||||
_ => MilitaryBranch.Army
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a valid attack size from the player for the given branch
|
||||
/// of the armed forces.
|
||||
/// </summary>
|
||||
/// <param name="troopsAvailable">
|
||||
/// The number of troops available.
|
||||
/// </param>
|
||||
public static int GetAttackSize(int troopsAvailable)
|
||||
{
|
||||
var attackSize = 0;
|
||||
|
||||
do
|
||||
{
|
||||
View.PromptAttackSize();
|
||||
attackSize = InputInteger();
|
||||
}
|
||||
while (attackSize < 0 || attackSize > troopsAvailable);
|
||||
|
||||
return attackSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an integer value from the user.
|
||||
/// </summary>
|
||||
public static int InputInteger()
|
||||
{
|
||||
var value = default(int);
|
||||
|
||||
while (!Int32.TryParse(Console.ReadLine(), out value))
|
||||
View.PromptValidInteger();
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
28 Combat/csharp/src/FinalCampaign.cs
Normal file
121
28 Combat/csharp/src/FinalCampaign.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of the game during the final campaign of the war.
|
||||
/// </summary>
|
||||
public sealed class FinalCampaign : WarState
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the FinalCampaign class.
|
||||
/// </summary>
|
||||
/// <param name="computerForces">
|
||||
/// The computer's forces.
|
||||
/// </param>
|
||||
/// <param name="playerForces">
|
||||
/// The player's forces.
|
||||
/// </param>
|
||||
public FinalCampaign(ArmedForces computerForces, ArmedForces playerForces)
|
||||
: base(computerForces, playerForces)
|
||||
{
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithArmy(int attackSize)
|
||||
{
|
||||
if (attackSize < ComputerForces.Army / 2)
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army - attackSize
|
||||
}),
|
||||
"I WIPED OUT YOUR ATTACK!"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces with
|
||||
{
|
||||
Army = 0
|
||||
},
|
||||
PlayerForces),
|
||||
"YOU DESTROYED MY ARMY!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithNavy(int attackSize)
|
||||
{
|
||||
if (attackSize < ComputerForces.Navy / 2)
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army / 4,
|
||||
Navy = PlayerForces.Navy / 2
|
||||
}),
|
||||
"I SUNK TWO OF YOUR BATTLESHIPS, AND MY AIR FORCE\n" +
|
||||
"WIPED OUT YOUR UNGAURDED CAPITOL."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces with
|
||||
{
|
||||
AirForce = 2 * ComputerForces.AirForce / 3,
|
||||
Navy = ComputerForces.Navy / 2
|
||||
},
|
||||
PlayerForces),
|
||||
"YOUR NAVY SHOT DOWN THREE OF MY XIII PLANES,\n" +
|
||||
"AND SUNK THREE BATTLESHIPS."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithAirForce(int attackSize)
|
||||
{
|
||||
// BUG? Usually, larger attacks lead to better outcomes.
|
||||
// It seems odd that the logic is suddenly reversed here,
|
||||
// but this could be intentional.
|
||||
if (attackSize > ComputerForces.AirForce / 2)
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army / 3,
|
||||
Navy = PlayerForces.Navy / 3,
|
||||
AirForce = PlayerForces.AirForce / 3
|
||||
}),
|
||||
"MY NAVY AND AIR FORCE IN A COMBINED ATTACK LEFT\n" +
|
||||
"YOUR COUNTRY IN SHAMBLES."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
(
|
||||
new Ceasefire(
|
||||
ComputerForces,
|
||||
PlayerForces,
|
||||
absoluteVictory: true),
|
||||
"ONE OF YOUR PLANES CRASHED INTO MY HOUSE. I AM DEAD.\n" +
|
||||
"MY COUNTRY FELL APART."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
187
28 Combat/csharp/src/InitialCampaign.cs
Normal file
187
28 Combat/csharp/src/InitialCampaign.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of the game during the initial campaign of the war.
|
||||
/// </summary>
|
||||
public sealed class InitialCampaign : WarState
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the InitialCampaign class.
|
||||
/// </summary>
|
||||
/// <param name="computerForces">
|
||||
/// The computer's forces.
|
||||
/// </param>
|
||||
/// <param name="playerForces">
|
||||
/// The player's forces.
|
||||
/// </param>
|
||||
public InitialCampaign(ArmedForces computerForces, ArmedForces playerForces)
|
||||
: base(computerForces, playerForces)
|
||||
{
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithArmy(int attackSize)
|
||||
{
|
||||
// BUG: Why are we comparing attack size to the size of our own
|
||||
// military? This leads to some truly absurd results if our
|
||||
// army is tiny.
|
||||
if (attackSize < PlayerForces.Army / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army - attackSize
|
||||
}),
|
||||
$"YOU LOST {attackSize} MEN FROM YOUR ARMY."
|
||||
);
|
||||
}
|
||||
else
|
||||
if (attackSize < 2 * PlayerForces.Army / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
// BUG: Clearly not what we claim below...
|
||||
Army = 0
|
||||
},
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army - attackSize / 3
|
||||
}),
|
||||
$"YOU LOST {attackSize / 3} MEN, BUT I LOST {2 * ComputerForces.Army / 3}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// BUG? This is identical to the third outcome when attacking
|
||||
// with the navy. It seems unlikely that this was the
|
||||
// intent. Probably line 115 in the original source was
|
||||
// supposed to say "GOTO 170" instead of "GOTO 270".
|
||||
// (Line 170 is conspicuously absent.)
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
Navy = 2 * ComputerForces.Navy / 3
|
||||
},
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army / 3,
|
||||
AirForce = PlayerForces.AirForce / 3
|
||||
}),
|
||||
"YOU SUNK ONE OF MY PATROL BOATS, BUT I WIPED OUT TWO\n" +
|
||||
"OF YOUR AIR FORCE BASES AND 3 ARMY BASES."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithNavy(int attackSize)
|
||||
{
|
||||
if (attackSize < ComputerForces.Navy / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
Navy = PlayerForces.Navy - attackSize
|
||||
}),
|
||||
"YOUR ATTACK WAS STOPPED!"
|
||||
);
|
||||
}
|
||||
else
|
||||
if (attackSize < 2 * ComputerForces.Navy / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
Navy = ComputerForces.Navy / 3
|
||||
},
|
||||
PlayerForces),
|
||||
$"YOU DESTROYED {2 * ComputerForces.Navy / 3} OF MY ARMY."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
Navy = 2 * ComputerForces.Navy / 3
|
||||
},
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army / 3,
|
||||
AirForce = PlayerForces.AirForce / 3
|
||||
}),
|
||||
"YOU SUNK ONE OF MY PATROL BOATS, BUT I WIPED OUT TWO\n" +
|
||||
"OF YOUR AIR FORCE BASES AND 3 ARMY BASES."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override (WarState nextState, string message) AttackWithAirForce(int attackSize)
|
||||
{
|
||||
// BUG: Why are we comparing the attack size to the size of
|
||||
// our own air force? Surely we meant to compare to the
|
||||
// computer's air force.
|
||||
if (attackSize < PlayerForces.AirForce / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces,
|
||||
PlayerForces with
|
||||
{
|
||||
AirForce = PlayerForces.AirForce - attackSize
|
||||
}),
|
||||
"YOUR ATTACK WAS WIPED OUT."
|
||||
);
|
||||
}
|
||||
else
|
||||
if (attackSize < 2 * PlayerForces.AirForce / 3)
|
||||
{
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
Army = 2 * ComputerForces.Army / 3,
|
||||
Navy = ComputerForces.Navy / 3,
|
||||
AirForce = ComputerForces.AirForce / 3
|
||||
},
|
||||
PlayerForces),
|
||||
"WE HAD A DOGFIGHT. YOU WON - AND FINISHED YOUR MISSION."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return
|
||||
(
|
||||
new FinalCampaign(
|
||||
ComputerForces with
|
||||
{
|
||||
Army = 2 * ComputerForces.Army / 3
|
||||
},
|
||||
PlayerForces with
|
||||
{
|
||||
Army = PlayerForces.Army / 4,
|
||||
Navy = PlayerForces.Navy / 3
|
||||
}),
|
||||
"YOU WIPED OUT ONE OF MY ARMY PATROLS, BUT I DESTROYED" +
|
||||
"TWO NAVY BASES AND BOMBED THREE ARMY BASES."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
28 Combat/csharp/src/MilitaryBranch.cs
Normal file
12
28 Combat/csharp/src/MilitaryBranch.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different branches of the military.
|
||||
/// </summary>
|
||||
public enum MilitaryBranch
|
||||
{
|
||||
Army,
|
||||
Navy,
|
||||
AirForce
|
||||
}
|
||||
}
|
||||
31
28 Combat/csharp/src/Program.cs
Normal file
31
28 Combat/csharp/src/Program.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Game
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
View.ShowBanner();
|
||||
View.ShowInstructions();
|
||||
|
||||
var computerForces = new ArmedForces { Army = 30000, Navy = 20000, AirForce = 22000 };
|
||||
var playerForces = Controller.GetInitialForces(computerForces);
|
||||
|
||||
var state = (WarState) new InitialCampaign(computerForces, playerForces);
|
||||
var isFirstTurn = true;
|
||||
|
||||
while (!state.FinalOutcome.HasValue)
|
||||
{
|
||||
var branch = Controller.GetAttackBranch(state, isFirstTurn);
|
||||
var attackSize = Controller.GetAttackSize(state.PlayerForces[branch]);
|
||||
|
||||
var (nextState, message) = state.LaunchAttack(branch, attackSize);
|
||||
View.ShowMessage(message);
|
||||
|
||||
state = nextState;
|
||||
isFirstTurn = false;
|
||||
}
|
||||
|
||||
View.ShowResult(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
28 Combat/csharp/src/View.cs
Normal file
110
28 Combat/csharp/src/View.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for displaying information to the user.
|
||||
/// </summary>
|
||||
public static class View
|
||||
{
|
||||
public static void ShowBanner()
|
||||
{
|
||||
Console.WriteLine(" COMBAT");
|
||||
Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
}
|
||||
|
||||
public static void ShowInstructions()
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("I AM AT WAR WITH YOU.");
|
||||
Console.WriteLine("WE HAVE 72000 SOLDIERS APIECE.");
|
||||
}
|
||||
|
||||
public static void ShowDistributeForces()
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("DISTRIBUTE YOUR FORCES.");
|
||||
Console.WriteLine("\tME\t YOU");
|
||||
}
|
||||
|
||||
public static void ShowMessage(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public static void ShowResult(WarState finalState)
|
||||
{
|
||||
if (!finalState.IsAbsoluteVictory)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("FROM THE RESULTS OF BOTH OF YOUR ATTACKS,");
|
||||
}
|
||||
|
||||
switch (finalState.FinalOutcome)
|
||||
{
|
||||
case WarResult.ComputerVictory:
|
||||
Console.WriteLine("YOU LOST-I CONQUERED YOUR COUNTRY. IT SERVES YOU");
|
||||
Console.WriteLine("RIGHT FOR PLAYING THIS STUPID GAME!!!");
|
||||
break;
|
||||
case WarResult.PlayerVictory:
|
||||
Console.WriteLine("YOU WON, OH! SHUCKS!!!!");
|
||||
break;
|
||||
case WarResult.PeaceTreaty:
|
||||
Console.WriteLine("THE TREATY OF PARIS CONCLUDED THAT WE TAKE OUR");
|
||||
Console.WriteLine("RESPECTIVE COUNTRIES AND LIVE IN PEACE.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void PromptArmySize(int computerArmySize)
|
||||
{
|
||||
Console.Write($"ARMY\t{computerArmySize}\t? ");
|
||||
}
|
||||
|
||||
public static void PromptNavySize(int computerNavySize)
|
||||
{
|
||||
Console.Write($"NAVY\t{computerNavySize}\t? ");
|
||||
}
|
||||
|
||||
public static void PromptAirForceSize(int computerAirForceSize)
|
||||
{
|
||||
Console.Write($"A. F.\t{computerAirForceSize}\t? ");
|
||||
}
|
||||
|
||||
public static void PromptFirstAttackBranch()
|
||||
{
|
||||
Console.WriteLine("YOU ATTACK FIRST. TYPE (1) FOR ARMY; (2) FOR NAVY;");
|
||||
Console.WriteLine("AND (3) FOR AIR FORCE.");
|
||||
Console.Write("? ");
|
||||
}
|
||||
|
||||
public static void PromptNextAttackBranch(ArmedForces computerForces, ArmedForces playerForces)
|
||||
{
|
||||
// BUG: More of a nit-pick really, but the order of columns in the
|
||||
// table is reversed from what we showed when distributing troops.
|
||||
// The tables should be consistent.
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("\tYOU\tME");
|
||||
Console.WriteLine($"ARMY\t{playerForces.Army}\t{computerForces.Army}");
|
||||
Console.WriteLine($"NAVY\t{playerForces.Navy}\t{computerForces.Navy}");
|
||||
Console.WriteLine($"A. F.\t{playerForces.AirForce}\t{computerForces.AirForce}");
|
||||
|
||||
Console.WriteLine("WHAT IS YOUR NEXT MOVE?");
|
||||
Console.WriteLine("ARMY=1 NAVY=2 AIR FORCE=3");
|
||||
Console.Write("? ");
|
||||
}
|
||||
|
||||
public static void PromptAttackSize()
|
||||
{
|
||||
Console.WriteLine("HOW MANY MEN");
|
||||
Console.Write("? ");
|
||||
}
|
||||
|
||||
public static void PromptValidInteger()
|
||||
{
|
||||
Console.WriteLine("ENTER A VALID INTEGER VALUE");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
28 Combat/csharp/src/WarResult.cs
Normal file
14
28 Combat/csharp/src/WarResult.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the possible outcomes of the war.
|
||||
/// </summary>
|
||||
public enum WarResult
|
||||
{
|
||||
ComputerVictory,
|
||||
|
||||
PlayerVictory,
|
||||
|
||||
PeaceTreaty
|
||||
}
|
||||
}
|
||||
101
28 Combat/csharp/src/WarState.cs
Normal file
101
28 Combat/csharp/src/WarState.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the current state of the war.
|
||||
/// </summary>
|
||||
public abstract class WarState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the computer's armed forces.
|
||||
/// </summary>
|
||||
public ArmedForces ComputerForces { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player's armed forces.
|
||||
/// </summary>
|
||||
public ArmedForces PlayerForces { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether this state represents absolute
|
||||
/// victory for the player.
|
||||
/// </summary>
|
||||
public virtual bool IsAbsoluteVictory => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the final outcome of the war.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the war is ongoing, this property will be null.
|
||||
/// </remarks>
|
||||
public virtual WarResult? FinalOutcome => null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the state class.
|
||||
/// </summary>
|
||||
/// <param name="computerForces">
|
||||
/// The computer's forces.
|
||||
/// </param>
|
||||
/// <param name="playerForces">
|
||||
/// The player's forces.
|
||||
/// </param>
|
||||
public WarState(ArmedForces computerForces, ArmedForces playerForces) =>
|
||||
(ComputerForces, PlayerForces) = (computerForces, playerForces);
|
||||
|
||||
/// <summary>
|
||||
/// Launches an attack.
|
||||
/// </summary>
|
||||
/// <param name="branch">
|
||||
/// The branch of the military to use for the attack.
|
||||
/// </param>
|
||||
/// <param name="attackSize">
|
||||
/// The number of men and women to use for the attack.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The new state of the game resulting from the attack and a message
|
||||
/// describing the result.
|
||||
/// </returns>
|
||||
public (WarState nextState, string message) LaunchAttack(MilitaryBranch branch, int attackSize) =>
|
||||
branch switch
|
||||
{
|
||||
MilitaryBranch.Army => AttackWithArmy(attackSize),
|
||||
MilitaryBranch.Navy => AttackWithNavy(attackSize),
|
||||
MilitaryBranch.AirForce => AttackWithAirForce(attackSize),
|
||||
_ => throw new ArgumentException("INVALID BRANCH")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Conducts an attack with the player's army.
|
||||
/// </summary>
|
||||
/// <param name="attackSize">
|
||||
/// The number of men and women used in the attack.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The new game state and a message describing the result.
|
||||
/// </returns>
|
||||
protected abstract (WarState nextState, string message) AttackWithArmy(int attackSize);
|
||||
|
||||
/// <summary>
|
||||
/// Conducts an attack with the player's navy.
|
||||
/// </summary>
|
||||
/// <param name="attackSize">
|
||||
/// The number of men and women used in the attack.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The new game state and a message describing the result.
|
||||
/// </returns>
|
||||
protected abstract (WarState nextState, string message) AttackWithNavy(int attackSize);
|
||||
|
||||
/// <summary>
|
||||
/// Conducts an attack with the player's air force.
|
||||
/// </summary>
|
||||
/// <param name="attackSize">
|
||||
/// The number of men and women used in the attack.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The new game state and a message describing the result.
|
||||
/// </returns>
|
||||
protected abstract (WarState nextState, string message) AttackWithAirForce(int attackSize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user