From b6c333b29358194d49c648de92d06597e4598e57 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Sun, 21 Feb 2021 05:02:05 +1000 Subject: [PATCH] initial implementation fur trader in csharp --- 38 Fur Trader/csharp/FurTrader.csproj | 8 + 38 Fur Trader/csharp/FurTrader.sln | 25 ++ 38 Fur Trader/csharp/Game.cs | 506 ++++++++++++++++++++++++++ 38 Fur Trader/csharp/GameState.cs | 42 +++ 38 Fur Trader/csharp/Program.cs | 20 + 5 files changed, 601 insertions(+) create mode 100644 38 Fur Trader/csharp/FurTrader.csproj create mode 100644 38 Fur Trader/csharp/FurTrader.sln create mode 100644 38 Fur Trader/csharp/Game.cs create mode 100644 38 Fur Trader/csharp/GameState.cs create mode 100644 38 Fur Trader/csharp/Program.cs diff --git a/38 Fur Trader/csharp/FurTrader.csproj b/38 Fur Trader/csharp/FurTrader.csproj new file mode 100644 index 00000000..c73e0d16 --- /dev/null +++ b/38 Fur Trader/csharp/FurTrader.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/38 Fur Trader/csharp/FurTrader.sln b/38 Fur Trader/csharp/FurTrader.sln new file mode 100644 index 00000000..54a1760c --- /dev/null +++ b/38 Fur Trader/csharp/FurTrader.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FurTrader", "FurTrader.csproj", "{1FB826B9-8794-4DB7-B676-B51F177B7B87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1FB826B9-8794-4DB7-B676-B51F177B7B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FB826B9-8794-4DB7-B676-B51F177B7B87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FB826B9-8794-4DB7-B676-B51F177B7B87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FB826B9-8794-4DB7-B676-B51F177B7B87}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DDB24448-50EB-47C6-BDB9-465896A81779} + EndGlobalSection +EndGlobal diff --git a/38 Fur Trader/csharp/Game.cs b/38 Fur Trader/csharp/Game.cs new file mode 100644 index 00000000..fc1aae25 --- /dev/null +++ b/38 Fur Trader/csharp/Game.cs @@ -0,0 +1,506 @@ +using System; + +namespace FurTrader +{ + public class Game + { + /// + /// random number generator; no seed to be faithful to original implementation + /// + private Random Rnd { get; } = new Random(); + + /// + /// Generate a price for pelts based off a factor and baseline value + /// + /// Multiplier for the price + /// The baseline price + /// A randomised price for pelts + internal double RandomPriceGenerator(double factor, double baseline) + { + var price = (Convert.ToInt32((factor * Rnd.NextDouble() + baseline) * 100d) + 5) / 100d; + return price; + } + + /// + /// Main game loop function. This will play the game endlessly until the player chooses to quit or a GameOver event occurs + /// + /// + /// General structure followed from Adam Dawes (@AdamDawes575) implementation of Acey Ducey."); + /// + internal void GameLoop() + { + // display instructions to the player + DisplayIntroText(); + + var state = new GameState(); + + // loop for each turn until the player decides not to continue (or has a Game Over event) + while ((!state.GameOver) && ContinueGame()) + { + // clear display at start of each turn + Console.Clear(); + + // play the next turn; pass game state for details and updates from the turn + PlayTurn(state); + } + + // end screen; show some statistics to the player + Console.Clear(); + Console.WriteLine("Thanks for playing!"); + Console.WriteLine(""); + Console.WriteLine($"Total Expeditions: {state.ExpeditionCount}"); + Console.WriteLine($"Final Amount: {state.Savings.ToString("c")}"); + } + + /// + /// Display instructions on how to play the game and wait for the player to press a key. + /// + private void DisplayIntroText() + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Fur Trader."); + Console.WriteLine("Creating Computing, Morristown, New Jersey."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine("Originally published in 1978 in the book 'Basic Computer Games' by David Ahl."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine("You are the leader of a French fur trading expedition in 1776 leaving the Lake Ontario area to sell furs and get supplies for the next year."); + Console.WriteLine(""); + Console.WriteLine("You have a choice of three forts at which you may trade. The cost of supplies and the amount you receive for your furs will depend on the fort that you choose."); + Console.WriteLine(""); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Press any key start the game."); + Console.ReadKey(true); + + } + + /// + /// Prompt the player to try again, and wait for them to press Y or N. + /// + /// Returns true if the player wants to try again, false if they have finished playing. + private bool ContinueGame() + { + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine("Do you wish to trade furs? "); + Console.Write("Answer (Y)es or (N)o "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("> "); + + char pressedKey; + // Keep looping until we get a recognised input + do + { + // Read a key, don't display it on screen + ConsoleKeyInfo key = Console.ReadKey(true); + // Convert to upper-case so we don't need to care about capitalisation + pressedKey = Char.ToUpper(key.KeyChar); + // Is this a key we recognise? If not, keep looping + } while (pressedKey != 'Y' && pressedKey != 'N'); + + // Display the result on the screen + Console.WriteLine(pressedKey); + + // Return true if the player pressed 'Y', false for anything else. + return (pressedKey == 'Y'); + } + + /// + /// Play a turn + /// + /// The current game state + private void PlayTurn(GameState state) + { + state.UnasignedFurCount = 190; /// start with 190 furs each turn + + // provide current status to user + Console.WriteLine(new string('_', 70)); + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(""); + Console.WriteLine($"You have {state.Savings.ToString("c")} savings and {state.UnasignedFurCount} furs to begin the expedition."); + Console.WriteLine(""); + Console.WriteLine($"Your {state.UnasignedFurCount} furs are distributed among the following kinds of pelts: Mink, Beaver, Ermine, and Fox"); + Console.WriteLine(""); + + // get input on number of pelts + Console.ForegroundColor = ConsoleColor.White; + Console.Write("How many Mink pelts do you have? "); + state.MinkPelts = GetPelts(state.UnasignedFurCount); + state.UnasignedFurCount -= state.MinkPelts; + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine($"You have {state.UnasignedFurCount} furs remaining for distribution"); + Console.Write("How many Beaver pelts do you have? "); + state.BeaverPelts = GetPelts(state.UnasignedFurCount); + state.UnasignedFurCount -= state.BeaverPelts; + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine($"You have {state.UnasignedFurCount} furs remaining for distribution"); + Console.Write("How many Ermine pelts do you have? "); + state.ErminePelts = GetPelts(state.UnasignedFurCount); + state.UnasignedFurCount -= state.ErminePelts; + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine($"You have {state.UnasignedFurCount} furs remaining for distribution"); + Console.Write("How many Fox pelts do you have? "); + state.FoxPelts = GetPelts(state.UnasignedFurCount); + state.UnasignedFurCount -= state.FoxPelts; + + // get input on which fort to trade with; user gets an opportunity to evaluate and re-select fort after selection until user confirms selection + do + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(""); + Console.WriteLine("Do you want to trade your furs at Fort 1, Fort 2, or Fort 3"); + Console.WriteLine("Fort 1 is Fort Hochelaga (Montreal) and is under the protection of the French army."); + Console.WriteLine("Fort 2 is Fort Stadacona (Quebec) and is under the protection of the French army. However, you must make a portage and cross the Lachine rapids."); + Console.WriteLine("Fort 3 is Fort New York and is under Dutch control. You must cross through Iroquois land."); + Console.WriteLine(""); + state.SelectedFort = GetSelectedFort(); + + DisplaySelectedFortInformation(state.SelectedFort); + + } while (TradeAtAnotherFort()); + + // process the travel to the fort + ProcessExpeditionOutcome(state); + + // display results of expedition (savings change) to the user + Console.ForegroundColor = ConsoleColor.White; + Console.Write("You now have "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write($"{state.Savings.ToString("c")}"); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(" including your previous savings."); + + // update the turn count now that another turn has been played + state.ExpeditionCount += 1; + } + + /// + /// Method to show the expedition costs to the player with some standard formatting + /// + /// The name of the fort traded with + /// The cost of the supplies at the fort + /// The travel expenses for the expedition + internal void DisplayCosts(string fortname, double supplies, double expenses) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Supplies at {fortname} cost".PadLeft(55)); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"{supplies.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Your travel expenses to {fortname} were".PadLeft(55)); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"{expenses.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + } + + /// + /// Process the results of the expedition + /// + /// the game state + private void ProcessExpeditionOutcome(GameState state) + { + var beaverPrice = RandomPriceGenerator(0.25d, 1.00d); + var foxPrice = RandomPriceGenerator(0.2d , 0.80d); + var erminePrice = RandomPriceGenerator(0.15d, 0.95d); + var minkPrice = RandomPriceGenerator(0.2d , 0.70d); + + var fortname = String.Empty; + var suppliesCost = 0.0d; + var travelExpenses = 0.0d; + + // create a random value 1 to 10 for the different outcomes at each fort + var p = ((int)(10 * Rnd.NextDouble())) + 1; + Console.WriteLine(""); + + switch (state.SelectedFort) + { + case 1: // outcome for expedition to Fort Hochelaga + beaverPrice = RandomPriceGenerator(0.2d, 0.75d); + foxPrice = RandomPriceGenerator(0.2d, 0.80d); + erminePrice = RandomPriceGenerator(0.2d, 0.65d); + minkPrice = RandomPriceGenerator(0.2d, 0.70d); + fortname = "Fort Hochelaga"; + suppliesCost = 150.0d; + travelExpenses = 10.0d; + break; + case 2: // outcome for expedition to Fort Stadacona + beaverPrice = RandomPriceGenerator(0.2d , 0.90d); + foxPrice = RandomPriceGenerator(0.2d , 0.80d); + erminePrice = RandomPriceGenerator(0.15d, 0.80d); + minkPrice = RandomPriceGenerator(0.3d , 0.85d); + fortname = "Fort Stadacona"; + suppliesCost = 125.0d; + travelExpenses = 15.0d; + if (p <= 2) + { + state.BeaverPelts = 0; + Console.WriteLine("Your beaver were to heavy to carry across the portage."); + Console.WriteLine("You had to leave the pelts but found them stolen when you returned"); + } + else if (p <= 6) + { + Console.WriteLine("You arrived safely at Fort Stadacona"); + } + else if (p <= 8) + { + state.BeaverPelts = 0; + state.FoxPelts = 0; + state.ErminePelts = 0; + state.MinkPelts = 0; + Console.WriteLine("Your canoe upset in the Lachine Rapids."); + Console.WriteLine("Your lost all your furs"); + } + else if (p <= 10) + { + state.FoxPelts = 0; + Console.WriteLine("Your fox pelts were not cured properly."); + Console.WriteLine("No one will buy them."); + } + else + { + throw new Exception($"Unexpected Outcome p = {p}"); + } + break; + case 3: // outcome for expedition to Fort New York + beaverPrice = RandomPriceGenerator(0.2d , 1.00d); + foxPrice = RandomPriceGenerator(0.25d, 1.10d); + erminePrice = RandomPriceGenerator(0.2d , 0.95d); + minkPrice = RandomPriceGenerator(0.15d, 1.05d); + fortname = "Fort New York"; + suppliesCost = 80.0d; + travelExpenses = 25.0d; + if (p <= 2) + { + state.BeaverPelts = 0; + state.FoxPelts = 0; + state.ErminePelts = 0; + state.MinkPelts = 0; + suppliesCost = 0.0d; + travelExpenses = 0.0d; + Console.WriteLine("You were attacked by a party of Iroquois."); + Console.WriteLine("All people in your trading group were killed."); + Console.WriteLine("This ends the game (press any key)."); + Console.ReadKey(true); + state.GameOver = true; + } + else if (p <= 6) + { + Console.WriteLine("You were lucky. You arrived safely at Fort New York."); + } + else if (p <= 8) + { + state.BeaverPelts = 0; + state.FoxPelts = 0; + state.ErminePelts = 0; + state.MinkPelts = 0; + Console.WriteLine("You narrowly escaped an Iroquois raiding party."); + Console.WriteLine("However, you had to leave all your furs behind."); + } + else if (p <= 10) + { + beaverPrice = beaverPrice / 2; + minkPrice = minkPrice / 2; + Console.WriteLine("Your mink and beaver were damaged on your trip."); + Console.WriteLine("You receive only half the current price for these furs."); + } + else + { + throw new Exception($"Unexpected Outcome p = {p}"); + } + break; + default: + break; + } + + var beaverSale = state.BeaverPelts * beaverPrice; + var foxSale = state.FoxPelts * foxPrice; + var ermineSale = state.ErminePelts * erminePrice; + var minkSale = state.MinkPelts * minkPrice; + var profit = beaverSale + foxSale + ermineSale + minkSale - suppliesCost - travelExpenses; + state.Savings += profit; + + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Your {state.BeaverPelts.ToString().PadLeft(3, ' ')} beaver pelts sold @ {beaverPrice.ToString("c")} per pelt for a total"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"{beaverSale.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Your {state.FoxPelts.ToString().PadLeft(3, ' ')} fox pelts sold @ {foxPrice.ToString("c")} per pelt for a total"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"{foxSale.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Your {state.ErminePelts.ToString().PadLeft(3, ' ')} ermine pelts sold @ {erminePrice.ToString("c")} per pelt for a total"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"{ermineSale.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Your {state.MinkPelts.ToString().PadLeft(3, ' ')} mink pelts sold @ {minkPrice.ToString("c")} per pelt for a total"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"{minkSale.ToString("c").PadLeft(10)}"); + Console.WriteLine(""); + DisplayCosts(fortname, suppliesCost, travelExpenses); + Console.WriteLine(""); + Console.Write($"Profit / Loss".PadLeft(55)); + Console.ForegroundColor = profit >= 0.0d ? ConsoleColor.Green : ConsoleColor.Red; + Console.WriteLine($"{profit.ToString("c").PadLeft(10)}"); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(""); + } + + private void DisplaySelectedFortInformation(int selectedFort) + { + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + + switch (selectedFort) + { + case 1: // selected fort details for Fort Hochelaga + Console.WriteLine("You have chosen the easiest route."); + Console.WriteLine("However, the fort is far from any seaport."); + Console.WriteLine("The value you receive for your furs will be low."); + Console.WriteLine("The cost of supplies will be higher than at Forts Stadacona or New York"); + break; + case 2: // selected fort details for Fort Stadacona + Console.WriteLine("You have chosen a hard route."); + Console.WriteLine("It is, in comparsion, harder than the route to Hochelaga but easier than the route to New York."); + Console.WriteLine("You will receive an average value for your furs."); + Console.WriteLine("The cost of your supplies will be average."); + break; + case 3: // selected fort details for Fort New York + Console.WriteLine("You have chosen the most difficult route."); + Console.WriteLine("At Fort New York you will receive the higest value for your furs."); + Console.WriteLine("The cost of your supplies will be lower than at all the other forts."); + break; + default: + break; + } + } + + private bool TradeAtAnotherFort() + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(""); + Console.WriteLine("Do you want to trade at another fort?"); + Console.Write("Answer (Y)es or (N)o "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("> "); + + char pressedKey; + // Keep looping until we get a recognised input + do + { + // Read a key, don't display it on screen + ConsoleKeyInfo key = Console.ReadKey(true); + // Convert to upper-case so we don't need to care about capitalisation + pressedKey = Char.ToUpper(key.KeyChar); + // Is this a key we recognise? If not, keep looping + } while (pressedKey != 'Y' && pressedKey != 'N'); + + // Display the result on the screen + Console.WriteLine(pressedKey); + + // Return true if the player pressed 'Y', false for anything else. + return (pressedKey == 'Y'); + } + + /// + /// Get an amount of pelts from the user + /// + /// The total pelts available + /// Returns the amount the player selects + private int GetPelts(int furCount) + { + int peltCount; + + // loop until the user enters a valid value + do + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("> "); + string input = Console.ReadLine(); + + // parse user information to check if it is a valid entry + if (!int.TryParse(input, out peltCount)) + { + // invalid entry; message back to user + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Sorry, I didn't understand. Please enter the number of pelts."); + + // continue looping + continue; + } + + // check if plet amount is more than the available pelts + if (peltCount > furCount) + { + // too many pelts selected + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"You may not have that many furs. Do not try to cheat. I can add."); + + // continue looping + continue; + } + + // valid pelt amount entered + break; + } while (true); + + // return pelt count to the user + return peltCount; + } + + /// + /// Prompt the user for their selected fort + /// + /// returns the fort the user has selected + private int GetSelectedFort() + { + int selectedFort; + + // loop until the user enters a valid value + do + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write("Answer 1, 2, or 3 "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("> "); + string input = Console.ReadLine(); + + // is the user entry valid + if (!int.TryParse(input, out selectedFort)) + { + // no, invalid data + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Sorry, I didn't understand. Please answer 1, 2, or 3."); + + // continue looping + continue; + } + + // is the selected fort an option (one, two or three only) + if (selectedFort != 1 && selectedFort != 2 && selectedFort != 3) + { + // no, invalid for selected + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Please answer 1, 2, or 3."); + + // continue looping + continue; + } + + // valid fort selected, stop looping + break; + } while (true); + + // return the players selected fort + return selectedFort; + } + } +} diff --git a/38 Fur Trader/csharp/GameState.cs b/38 Fur Trader/csharp/GameState.cs new file mode 100644 index 00000000..4e174040 --- /dev/null +++ b/38 Fur Trader/csharp/GameState.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FurTrader +{ + internal class GameState + { + internal bool GameOver { get; set; } + + internal double Savings { get; set; } + + internal int ExpeditionCount { get; set; } + + internal int UnasignedFurCount { get; set; } + + internal int[] Pelts { get; private set; } + + internal int MinkPelts { get { return this.Pelts[0]; } set { this.Pelts[0] = value; } } + internal int BeaverPelts { get { return this.Pelts[0]; } set { this.Pelts[0] = value; } } + internal int ErminePelts { get { return this.Pelts[0]; } set { this.Pelts[0] = value; } } + internal int FoxPelts { get { return this.Pelts[0]; } set { this.Pelts[0] = value; } } + + internal int SelectedFort { get; set; } + + internal GameState() + { + this.Savings = 600; + this.ExpeditionCount = 0; + this.UnasignedFurCount = 190; + this.Pelts = new int[4]; + this.SelectedFort = 0; + } + + internal void StartTurn() + { + this.SelectedFort = 0; // reset to a default value + this.UnasignedFurCount = 190; // each turn starts with 190 furs + this.Pelts = new int[4]; // reset pelts on each turn + } + } +} diff --git a/38 Fur Trader/csharp/Program.cs b/38 Fur Trader/csharp/Program.cs new file mode 100644 index 00000000..438d5c4c --- /dev/null +++ b/38 Fur Trader/csharp/Program.cs @@ -0,0 +1,20 @@ +using System; + +namespace FurTrader +{ + public class Program + { + /// + /// This function will be called automatically when the application begins + /// + /// + public static void Main(string[] args) + { + // Create an instance of our main Game class + var game = new Game(); + + // Call its GameLoop function. This will play the game endlessly in a loop until the player chooses to quit. + game.GameLoop(); + } + } +}