mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 07:29:02 -08:00
8
83 Stock Market/csharp/Game.csproj
Normal file
8
83 Stock Market/csharp/Game.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
25
83 Stock Market/csharp/StockMarket.sln
Normal file
25
83 Stock Market/csharp/StockMarket.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", "{BADD262D-D540-431F-8803-2A6F80C22033}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BADD262D-D540-431F-8803-2A6F80C22033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BADD262D-D540-431F-8803-2A6F80C22033}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BADD262D-D540-431F-8803-2A6F80C22033}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BADD262D-D540-431F-8803-2A6F80C22033}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C78DBA4A-87E2-4B31-A261-4AEF5E4C3B12}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
20
83 Stock Market/csharp/src/Assets.cs
Normal file
20
83 Stock Market/csharp/src/Assets.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the player's assets.
|
||||
/// </summary>
|
||||
public record Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player's amount of cash.
|
||||
/// </summary>
|
||||
public double Cash { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of stocks owned of each company.
|
||||
/// </summary>
|
||||
public ImmutableArray<int> Portfolio { get; init; }
|
||||
}
|
||||
}
|
||||
60
83 Stock Market/csharp/src/Broker.cs
Normal file
60
83 Stock Market/csharp/src/Broker.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for exchanging assets.
|
||||
/// </summary>
|
||||
public static class Broker
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the given set of transactions to the given set of assets.
|
||||
/// </summary>
|
||||
/// <param name="assets">
|
||||
/// The assets to update.
|
||||
/// </param>
|
||||
/// <param name="transactions">
|
||||
/// The set of stocks to purchase or sell. Positive values indicate
|
||||
/// purchaes and negative values indicate sales.
|
||||
/// </param>
|
||||
/// <param name="companies">
|
||||
/// The collection of companies.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns the sellers new assets and a code indicating the result
|
||||
/// of the transaction.
|
||||
/// </returns>
|
||||
public static (Assets newAssets, TransactionResult result) Apply(Assets assets, IEnumerable<int> transactions, IEnumerable<Company> companies)
|
||||
{
|
||||
var (netCost, transactionSize) = Enumerable.Zip(
|
||||
transactions,
|
||||
companies,
|
||||
(amount, company) => (amount * company.SharePrice))
|
||||
.Aggregate(
|
||||
(netCost: 0.0, transactionSize: 0.0),
|
||||
(accumulated, amount) => (accumulated.netCost + amount, accumulated.transactionSize + Math.Abs(amount)));
|
||||
|
||||
var brokerageFee = 0.01 * transactionSize;
|
||||
|
||||
var newAssets = assets with
|
||||
{
|
||||
Cash = assets.Cash - netCost - brokerageFee,
|
||||
Portfolio = ImmutableArray.CreateRange(Enumerable.Zip(
|
||||
assets.Portfolio,
|
||||
transactions,
|
||||
(sharesOwned, delta) => sharesOwned + delta))
|
||||
};
|
||||
|
||||
if (newAssets.Portfolio.Any(amount => amount < 0))
|
||||
return (newAssets, TransactionResult.Oversold);
|
||||
else
|
||||
if (newAssets.Cash < 0)
|
||||
return (newAssets, TransactionResult.Overspent);
|
||||
else
|
||||
return (newAssets, TransactionResult.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
83 Stock Market/csharp/src/Company.cs
Normal file
23
83 Stock Market/csharp/src/Company.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a company.
|
||||
/// </summary>
|
||||
public record Company
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the company's name.
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the company's three letter stock symbol.
|
||||
/// </summary>
|
||||
public string StockSymbol { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the company's current share price.
|
||||
/// </summary>
|
||||
public double SharePrice { get; init; }
|
||||
}
|
||||
}
|
||||
107
83 Stock Market/csharp/src/Controller.cs
Normal file
107
83 Stock Market/csharp/src/Controller.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
public static class Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the initial interaction with the user.
|
||||
/// </summary>
|
||||
public static void StartGame()
|
||||
{
|
||||
View.ShowBanner();
|
||||
|
||||
var showInstructions = GetYesOrNo(View.PromptShowInstructions);
|
||||
View.ShowSeparator();
|
||||
if (showInstructions)
|
||||
View.ShowInstructions();
|
||||
|
||||
View.ShowSeparator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a yes or no answer from the user.
|
||||
/// </summary>
|
||||
/// <param name="prompt">
|
||||
/// Displays the prompt.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the user answered yes and false if he or she answered no.
|
||||
/// </returns>
|
||||
public static bool GetYesOrNo(Action prompt)
|
||||
{
|
||||
prompt();
|
||||
|
||||
var response = default(char);
|
||||
do
|
||||
{
|
||||
response = Console.ReadKey(intercept: true).KeyChar;
|
||||
}
|
||||
while (response != '0' && response != '1');
|
||||
|
||||
View.ShowChar(response);
|
||||
return response == '1';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a transaction amount for each company in the given collection
|
||||
/// of companies and returns the updated assets.
|
||||
/// </summary>
|
||||
/// <param name="assets">
|
||||
/// The assets to update.
|
||||
/// </param>
|
||||
/// <param name="companies">
|
||||
/// The collection of companies.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated assets.
|
||||
/// </returns>
|
||||
public static Assets UpdateAssets(Assets assets, IEnumerable<Company> companies)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
View.PromptEnterTransactions();
|
||||
|
||||
var result = Broker.Apply (
|
||||
assets,
|
||||
companies.Select(GetTransactionAmount).ToList(),
|
||||
companies);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case (Assets newAssets, TransactionResult.Ok):
|
||||
return newAssets;
|
||||
case (_, TransactionResult.Oversold):
|
||||
View.ShowOversold();
|
||||
break;
|
||||
case (Assets newAssets, TransactionResult.Overspent):
|
||||
View.ShowOverspent(-newAssets.Cash);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a transaction amount for the given company.
|
||||
/// </summary>
|
||||
/// <param name="company">
|
||||
/// The company to buy or sell.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The number of shares to buy or sell.
|
||||
/// </returns>
|
||||
public static int GetTransactionAmount(Company company)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
View.PromptBuySellCompany(company);
|
||||
if (Int32.TryParse(Console.ReadLine(), out var amount))
|
||||
return amount;
|
||||
else
|
||||
View.PromptValidInteger();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
83 Stock Market/csharp/src/Extensions/EnumerableExtensions.cs
Normal file
116
83 Stock Market/csharp/src/Extensions/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides additional methods for the <see cref="IEnumerable{T}"/>
|
||||
/// interface.
|
||||
/// </summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Simultaneously projects each element of a sequence and applies
|
||||
/// the result of the previous projection.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">
|
||||
/// The type of elements in the source sequence.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TResult">
|
||||
/// The type of elements in the result sequence.
|
||||
/// </typeparam>
|
||||
/// <param name="source">
|
||||
/// The source sequence.
|
||||
/// </param>
|
||||
/// <param name="seed">
|
||||
/// The seed value for the aggregation component. This value is
|
||||
/// passed to the first call to <paramref name="selector"/>.
|
||||
/// </param>
|
||||
/// <param name="selector">
|
||||
/// The projection function. This function is supplied with a value
|
||||
/// from the source sequence and the result of the projection on the
|
||||
/// previous value in the source sequence.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The resulting sequence.
|
||||
/// </returns>
|
||||
public static IEnumerable<TResult> SelectAndAggregate<TSource, TResult>(
|
||||
this IEnumerable<TSource> source,
|
||||
TResult seed,
|
||||
Func<TSource, TResult, TResult> selector)
|
||||
{
|
||||
foreach (var element in source)
|
||||
{
|
||||
seed = selector(element, seed);
|
||||
yield return seed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the results of three distinct sequences into a single
|
||||
/// sequence.
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">
|
||||
/// The element type of the first sequence.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="T2">
|
||||
/// The element type of the second sequence.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="T3">
|
||||
/// The element type of the third sequence.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TResult">
|
||||
/// The element type of the resulting sequence.
|
||||
/// </typeparam>
|
||||
/// <param name="first">
|
||||
/// The first source sequence.
|
||||
/// </param>
|
||||
/// <param name="second">
|
||||
/// The second source sequence.
|
||||
/// </param>
|
||||
/// <param name="third">
|
||||
/// The third source sequence.
|
||||
/// </param>
|
||||
/// <param name="resultSelector">
|
||||
/// Function that combines results from each source sequence into a
|
||||
/// final result.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A sequence of combined values.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This function works identically to Enumerable.Zip except that it
|
||||
/// combines three sequences instead of two.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// We have defined this as an extension method for consistency with
|
||||
/// the similar LINQ methods in the <see cref="Enumerable"/> class.
|
||||
/// However, since there is nothing special about the first sequence,
|
||||
/// it is often more clear to call this as a regular function. For
|
||||
/// example:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// EnumerableExtensions.Zip(
|
||||
/// sequence1,
|
||||
/// sequence2,
|
||||
/// sequence3,
|
||||
/// (a, b, c) => GetResult (a, b, c));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static IEnumerable<TResult> Zip<T1, T2, T3, TResult>(
|
||||
this IEnumerable<T1> first,
|
||||
IEnumerable<T2> second,
|
||||
IEnumerable<T3> third,
|
||||
Func<T1, T2, T3, TResult> resultSelector)
|
||||
{
|
||||
using var enumerator1 = first.GetEnumerator();
|
||||
using var enumerator2 = second.GetEnumerator();
|
||||
using var enumerator3 = third.GetEnumerator();
|
||||
|
||||
while (enumerator1.MoveNext() && enumerator2.MoveNext() && enumerator3.MoveNext())
|
||||
yield return resultSelector(enumerator1.Current, enumerator2.Current, enumerator3.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
83 Stock Market/csharp/src/Extensions/RandomExtensions.cs
Normal file
46
83 Stock Market/csharp/src/Extensions/RandomExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides additional methods for the <see cref="Random"/> class.
|
||||
/// </summary>
|
||||
public static class RandomExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates an infinite sequence of random numbers.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="min">
|
||||
/// The inclusive lower bound of the range to generate.
|
||||
/// </param>
|
||||
/// <param name="max">
|
||||
/// The exclusive upper bound of the range to generate.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An infinite sequence of random integers in the range [min, max).
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// We use an exclusive upper bound, even though it's a little
|
||||
/// confusing, for the sake of consistency with Random.Next.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Since the sequence is infinite, a typical usage would be to cap
|
||||
/// the results with a function like Enumerable.Take. For example,
|
||||
/// to sum the results of rolling three six sided dice, we could do:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// random.Integers(1, 7).Take(3).Sum()
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static IEnumerable<int> Integers(this Random random, int min, int max)
|
||||
{
|
||||
while (true)
|
||||
yield return random.Next(min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
83 Stock Market/csharp/src/Program.cs
Normal file
52
83 Stock Market/csharp/src/Program.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the set of companies that will be simulated in the game.
|
||||
/// </summary>
|
||||
private static ImmutableArray<Company> Companies = ImmutableArray.CreateRange(new[]
|
||||
{
|
||||
new Company { Name = "INT. BALLISTIC MISSILES", StockSymbol = "IBM", SharePrice = 100 },
|
||||
new Company { Name = "RED CROSS OF AMERICA", StockSymbol = "RCA", SharePrice = 85 },
|
||||
new Company { Name = "LICHTENSTEIN, BUMRAP & JOKE", StockSymbol = "LBJ", SharePrice = 150 },
|
||||
new Company { Name = "AMERICAN BANKRUPT CO.", StockSymbol = "ABC", SharePrice = 140 },
|
||||
new Company { Name = "CENSURED BOOKS STORE", StockSymbol = "CBS", SharePrice = 110 }
|
||||
});
|
||||
|
||||
static void Main()
|
||||
{
|
||||
var assets = new Assets
|
||||
{
|
||||
Cash = 10000.0,
|
||||
Portfolio = ImmutableArray.CreateRange(Enumerable.Repeat(0, Companies.Length))
|
||||
};
|
||||
|
||||
var previousDay = default(TradingDay);
|
||||
|
||||
Controller.StartGame();
|
||||
|
||||
foreach (var day in StockMarket.Simulate(Companies))
|
||||
{
|
||||
if (previousDay is null)
|
||||
View.ShowCompanies(day.Companies);
|
||||
else
|
||||
View.ShowTradeResults(day, previousDay, assets);
|
||||
|
||||
View.ShowAssets(assets, day.Companies);
|
||||
|
||||
if (previousDay is not null && !Controller.GetYesOrNo(View.PromptContinue))
|
||||
break;
|
||||
|
||||
assets = Controller.UpdateAssets(assets, day.Companies);
|
||||
previousDay = day;
|
||||
}
|
||||
|
||||
View.ShowFarewell();
|
||||
}
|
||||
}
|
||||
}
|
||||
149
83 Stock Market/csharp/src/StockMarket.cs
Normal file
149
83 Stock Market/csharp/src/StockMarket.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Game.Extensions;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a method for simulating a stock market.
|
||||
/// </summary>
|
||||
public static class StockMarket
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulates changes in the stock market over time.
|
||||
/// </summary>
|
||||
/// <param name="companies">
|
||||
/// The collection of companies that will participate in the market.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An infinite sequence of trading days. Each day represents the
|
||||
/// state of the stock market at the start of that day.
|
||||
/// </returns>
|
||||
public static IEnumerable<TradingDay> Simulate(ImmutableArray<Company> companies)
|
||||
{
|
||||
var random = new Random();
|
||||
|
||||
var cyclicParameters = EnumerableExtensions.Zip(
|
||||
Trends(random, 1, 5),
|
||||
PriceSpikes(random, companies.Length, 1, 5),
|
||||
PriceSpikes(random, companies.Length, 1, 5),
|
||||
(trend, company1, company2) => (trend, positiveSpike: company1, negativeSpike: company2));
|
||||
|
||||
return cyclicParameters.SelectAndAggregate(
|
||||
new TradingDay
|
||||
{
|
||||
Companies = companies
|
||||
},
|
||||
(parameters, previousDay) => previousDay with
|
||||
{
|
||||
Companies = ImmutableArray.CreateRange(
|
||||
previousDay.Companies.Select ((company, index) => AdjustSharePrice(
|
||||
random,
|
||||
company,
|
||||
parameters.trend,
|
||||
parameters.positiveSpike == index,
|
||||
parameters.negativeSpike == index)))
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of a company with a randomly adjusted share price,
|
||||
/// based on the given parameters.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="company">
|
||||
/// The company to adjust.
|
||||
/// </param>
|
||||
/// <param name="trend">
|
||||
/// The slope of the overall market price trend.
|
||||
/// </param>
|
||||
/// <param name="positiveSpike">
|
||||
/// True if the function should simulate a positive spike in the
|
||||
/// company's share price.
|
||||
/// </param>
|
||||
/// <param name="negativeSpike">
|
||||
/// True if the function should simulate a negative spike in the
|
||||
/// company's share price.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The adjusted company.
|
||||
/// </returns>
|
||||
private static Company AdjustSharePrice(Random random, Company company, double trend, bool positiveSpike, bool negativeSpike)
|
||||
{
|
||||
var boost = random.Next(4) * 0.25;
|
||||
|
||||
var spikeAmount = 0.0;
|
||||
|
||||
if (positiveSpike)
|
||||
spikeAmount = 10;
|
||||
|
||||
if (negativeSpike)
|
||||
spikeAmount = spikeAmount - 10;
|
||||
|
||||
var priceChange = (int)(trend * company.SharePrice) + boost + (int)(3.5 - (6 * random.NextDouble())) + spikeAmount;
|
||||
|
||||
var newPrice = company.SharePrice + priceChange;
|
||||
if (newPrice < 0)
|
||||
newPrice = 0;
|
||||
|
||||
return company with { SharePrice = newPrice };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an infinite sequence of market trends.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="minDays">
|
||||
/// The minimum number of days each trend should last.
|
||||
/// </param>
|
||||
/// <param name="maxDays">
|
||||
/// The maximum number of days each trend should last.
|
||||
/// </param>
|
||||
public static IEnumerable<double> Trends(Random random, int minDays, int maxDays) =>
|
||||
random.Integers(minDays, maxDays + 1).SelectMany(days => Enumerable.Repeat(GenerateTrend(random), days));
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value for the market trend.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A trend value in the range [-0.1, 0.1].
|
||||
/// </returns>
|
||||
private static double GenerateTrend(Random random) =>
|
||||
((int)(random.NextDouble() * 10 + 0.5) / 100.0) * (random.Next(2) == 0 ? 1 : -1) ;
|
||||
|
||||
/// <summary>
|
||||
/// Generates an infinite sequence of price spikes.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="companyCount">
|
||||
/// The number of companies.
|
||||
/// </param>
|
||||
/// <param name="minDays">
|
||||
/// The minimum number of days in between price spikes.
|
||||
/// </param>
|
||||
/// <param name="maxDays">
|
||||
/// The maximum number of days in between price spikes.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An infinite sequence of random company indexes and null values.
|
||||
/// A non-null value means that the corresponding company should
|
||||
/// experience a price spike.
|
||||
/// </returns>
|
||||
private static IEnumerable<int?> PriceSpikes(Random random, int companyCount, int minDays, int maxDays) =>
|
||||
random.Integers(minDays, maxDays + 1)
|
||||
.SelectMany(
|
||||
days => Enumerable.Range(0, days),
|
||||
(days, dayNumber) => dayNumber == 0 ? random.Next(companyCount) : default(int?));
|
||||
}
|
||||
}
|
||||
24
83 Stock Market/csharp/src/TradingDay.cs
Normal file
24
83 Stock Market/csharp/src/TradingDay.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single trading day.
|
||||
/// </summary>
|
||||
public record TradingDay
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the average share price of all companies in the market this
|
||||
/// day.
|
||||
/// </summary>
|
||||
public double AverageSharePrice =>
|
||||
Companies.Average (company => company.SharePrice);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of public listed companies in the stock market
|
||||
/// this day.
|
||||
/// </summary>
|
||||
public ImmutableArray<Company> Companies { get; init; }
|
||||
}
|
||||
}
|
||||
25
83 Stock Market/csharp/src/TransactionResult.cs
Normal file
25
83 Stock Market/csharp/src/TransactionResult.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the different possible outcomes of applying a transaction.
|
||||
/// </summary>
|
||||
public enum TransactionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The transaction was successful.
|
||||
/// </summary>
|
||||
Ok,
|
||||
|
||||
/// <summary>
|
||||
/// The transaction failed because the seller tried to sell more shares
|
||||
/// than he or she owns.
|
||||
/// </summary>
|
||||
Oversold,
|
||||
|
||||
/// <summary>
|
||||
/// The transaction failed because the net cost was greater than the
|
||||
/// seller's available cash.
|
||||
/// </summary>
|
||||
Overspent
|
||||
}
|
||||
}
|
||||
157
83 Stock Market/csharp/src/View.cs
Normal file
157
83 Stock Market/csharp/src/View.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Game.Extensions;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for displaying information to the user.
|
||||
/// </summary>
|
||||
public static class View
|
||||
{
|
||||
public static void ShowBanner()
|
||||
{
|
||||
Console.WriteLine(" STOCK MARKET");
|
||||
Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowInstructions()
|
||||
{
|
||||
Console.WriteLine("THIS PROGRAM PLAYS THE STOCK MARKET. YOU WILL BE GIVEN");
|
||||
Console.WriteLine("$10,000 AND MAY BUY OR SELL STOCKS. THE STOCK PRICES WILL");
|
||||
Console.WriteLine("BE GENERATED RANDOMLY AND THEREFORE THIS MODEL DOES NOT");
|
||||
Console.WriteLine("REPRESENT EXACTLY WHAT HAPPENS ON THE EXCHANGE. A TABLE");
|
||||
Console.WriteLine("OF AVAILABLE STOCKS, THEIR PRICES, AND THE NUMBER OF SHARES");
|
||||
Console.WriteLine("IN YOUR PORTFOLIO WILL BE PRINTED. FOLLOWING THIS, THE");
|
||||
Console.WriteLine("INITIALS OF EACH STOCK WILL BE PRINTED WITH A QUESTION");
|
||||
Console.WriteLine("MARK. HERE YOU INDICATE A TRANSACTION. TO BUY A STOCK");
|
||||
Console.WriteLine("TYPE +NNN, TO SELL A STOCK TYPE -NNN, WHERE NNN IS THE");
|
||||
Console.WriteLine("NUMBER OF SHARES. A BROKERAGE FEE OF 1% WILL BE CHARGED");
|
||||
Console.WriteLine("ON ALL TRANSACTIONS. NOTE THAT IF A STOCK'S VALUE DROPS");
|
||||
Console.WriteLine("TO ZERO IT MAY REBOUND TO A POSITIVE VALUE AGAIN. YOU");
|
||||
Console.WriteLine("HAVE $10,000 TO INVEST. USE INTEGERS FOR ALL YOUR INPUTS.");
|
||||
Console.WriteLine("(NOTE: TO GET A 'FEEL' FOR THE MARKET RUN FOR AT LEAST");
|
||||
Console.WriteLine("10 DAYS)");
|
||||
Console.WriteLine("-----GOOD LUCK!-----");
|
||||
}
|
||||
|
||||
public static void ShowCompanies(IEnumerable<Company> companies)
|
||||
{
|
||||
var maxNameLength = companies.Max(company => company.Name.Length);
|
||||
|
||||
Console.WriteLine($"{"STOCK".PadRight(maxNameLength)} INITIALS PRICE/SHARE");
|
||||
foreach (var company in companies)
|
||||
Console.WriteLine($"{company.Name.PadRight(maxNameLength)} {company.StockSymbol} {company.SharePrice:0.00}");
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"NEW YORK STOCK EXCHANGE AVERAGE: {companies.Average(company => company.SharePrice):0.00}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowTradeResults(TradingDay day, TradingDay previousDay, Assets assets)
|
||||
{
|
||||
var results = EnumerableExtensions.Zip(
|
||||
day.Companies,
|
||||
previousDay.Companies,
|
||||
assets.Portfolio,
|
||||
(company, previous, shares) =>
|
||||
(
|
||||
stockSymbol: company.StockSymbol,
|
||||
price: company.SharePrice,
|
||||
shares,
|
||||
value: shares * company.SharePrice,
|
||||
change: company.SharePrice - previous.SharePrice
|
||||
)).ToList();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("********** END OF DAY'S TRADING **********");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("STOCK\tPRICE/SHARE\tHOLDINGS\tVALUE\tNET PRICE CHANGE");
|
||||
foreach (var result in results)
|
||||
Console.WriteLine($"{result.stockSymbol}\t{result.price}\t\t{result.shares}\t\t{result.value:0.00}\t\t{result.change:0.00}");
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
|
||||
var averagePrice = day.AverageSharePrice;
|
||||
var averagePriceChange = averagePrice - previousDay.AverageSharePrice;
|
||||
|
||||
Console.WriteLine($"NEW YORK STOCK EXCHANGE AVERAGE: {averagePrice:0.00} NET CHANGE {averagePriceChange:0.00}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowAssets(Assets assets, IEnumerable<Company> companies)
|
||||
{
|
||||
var totalStockValue = Enumerable.Zip(
|
||||
assets.Portfolio,
|
||||
companies,
|
||||
(shares, company) => shares * company.SharePrice).Sum();
|
||||
|
||||
Console.WriteLine($"TOTAL STOCK ASSETS ARE ${totalStockValue:0.00}");
|
||||
Console.WriteLine($"TOTAL CASH ASSETS ARE ${assets.Cash:0.00}");
|
||||
Console.WriteLine($"TOTAL ASSETS ARE ${totalStockValue + assets.Cash:0.00}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowOversold()
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("YOU HAVE OVERSOLD A STOCK; TRY AGAIN.");
|
||||
}
|
||||
|
||||
public static void ShowOverspent(double amount)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"YOU HAVE USED ${amount:0.00} MORE THAN YOU HAVE.");
|
||||
}
|
||||
|
||||
public static void ShowFarewell()
|
||||
{
|
||||
Console.WriteLine("HOPE YOU HAD FUN!!");
|
||||
}
|
||||
|
||||
public static void ShowSeparator()
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowChar(char c)
|
||||
{
|
||||
Console.WriteLine(c);
|
||||
}
|
||||
|
||||
public static void PromptShowInstructions()
|
||||
{
|
||||
Console.Write("DO YOU WANT THE INSTRUCTIONS (YES-TYPE 1, NO-TYPE 0)? ");
|
||||
}
|
||||
|
||||
public static void PromptContinue()
|
||||
{
|
||||
Console.Write("DO YOU WISH TO CONTINUE (YES-TYPE 1, NO-TYPE 0)? ");
|
||||
}
|
||||
|
||||
public static void PromptEnterTransactions()
|
||||
{
|
||||
Console.WriteLine("WHAT IS YOUR TRANSACTION IN");
|
||||
}
|
||||
|
||||
public static void PromptBuySellCompany(Company company)
|
||||
{
|
||||
Console.Write($"{company.StockSymbol}? ");
|
||||
}
|
||||
|
||||
public static void PromptValidInteger()
|
||||
{
|
||||
Console.WriteLine("PLEASE ENTER A VALID INTEGER");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user