mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
38
13_Bounce/csharp/Bounce.cs
Normal file
38
13_Bounce/csharp/Bounce.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
namespace Bounce;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the bounce of the ball, calculating duration, height and position in time.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All calculations are derived from the equation for projectile motion: s = vt + 0.5at^2
|
||||||
|
/// </remarks>
|
||||||
|
internal class Bounce
|
||||||
|
{
|
||||||
|
private const float _acceleration = -32; // feet/s^2
|
||||||
|
|
||||||
|
private readonly float _velocity;
|
||||||
|
|
||||||
|
internal Bounce(float velocity)
|
||||||
|
{
|
||||||
|
_velocity = velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Duration => -2 * _velocity / _acceleration;
|
||||||
|
|
||||||
|
public float MaxHeight =>
|
||||||
|
(float)Math.Round(-_velocity * _velocity / 2 / _acceleration, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
public float Plot(Graph graph, float startTime)
|
||||||
|
{
|
||||||
|
var time = 0f;
|
||||||
|
for (; time <= Duration; time += graph.TimeIncrement)
|
||||||
|
{
|
||||||
|
var height = _velocity * time + _acceleration * time * time / 2;
|
||||||
|
graph.Plot(startTime + time, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return startTime + time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bounce Next(float elasticity) => new Bounce(_velocity * elasticity);
|
||||||
|
}
|
||||||
@@ -6,4 +6,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources\*.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
38
13_Bounce/csharp/Game.cs
Normal file
38
13_Bounce/csharp/Game.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using static Bounce.Resources.Resource;
|
||||||
|
|
||||||
|
namespace Bounce;
|
||||||
|
|
||||||
|
internal class Game
|
||||||
|
{
|
||||||
|
private readonly IReadWrite _io;
|
||||||
|
|
||||||
|
public Game(IReadWrite io)
|
||||||
|
{
|
||||||
|
_io = io;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(Func<bool> playAgain)
|
||||||
|
{
|
||||||
|
_io.Write(Streams.Title);
|
||||||
|
_io.Write(Streams.Instructions);
|
||||||
|
|
||||||
|
while (playAgain.Invoke())
|
||||||
|
{
|
||||||
|
var timeIncrement = _io.ReadParameter("Time increment (sec)");
|
||||||
|
var velocity = _io.ReadParameter("Velocity (fps)");
|
||||||
|
var elasticity = _io.ReadParameter("Coefficient");
|
||||||
|
|
||||||
|
var bounce = new Bounce(velocity);
|
||||||
|
var bounceCount = (int)(Graph.Row.Width * timeIncrement / bounce.Duration);
|
||||||
|
var graph = new Graph(bounce.MaxHeight, timeIncrement);
|
||||||
|
|
||||||
|
var time = 0f;
|
||||||
|
for (var i = 0; i < bounceCount; i++, bounce = bounce.Next(elasticity))
|
||||||
|
{
|
||||||
|
time = bounce.Plot(graph, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
_io.WriteLine(graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
13_Bounce/csharp/Graph.cs
Normal file
116
13_Bounce/csharp/Graph.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Bounce;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides support for plotting a graph of height vs time, and rendering it to a string.
|
||||||
|
/// </summary>
|
||||||
|
internal class Graph
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, Row> _rows;
|
||||||
|
|
||||||
|
public Graph(float maxHeight, float timeIncrement)
|
||||||
|
{
|
||||||
|
// 1 row == 1/2 foot + 1 row for zero
|
||||||
|
var rowCount = 2 * (int)Math.Round(maxHeight, MidpointRounding.AwayFromZero) + 1;
|
||||||
|
_rows = Enumerable.Range(0, rowCount)
|
||||||
|
.ToDictionary(x => x, x => new Row(x % 2 == 0 ? $" {x / 2} " : ""));
|
||||||
|
TimeIncrement = timeIncrement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float TimeIncrement { get; }
|
||||||
|
public float MaxTimePlotted { get; private set; }
|
||||||
|
|
||||||
|
public void Plot(float time, float height)
|
||||||
|
{
|
||||||
|
var rowIndex = (int)Math.Round(height * 2, MidpointRounding.AwayFromZero);
|
||||||
|
var colIndex = (int)(time / TimeIncrement) + 1;
|
||||||
|
if (_rows.TryGetValue(rowIndex, out var row))
|
||||||
|
{
|
||||||
|
row[colIndex] = '0';
|
||||||
|
}
|
||||||
|
MaxTimePlotted = Math.Max(time, MaxTimePlotted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder().AppendLine("Feet").AppendLine();
|
||||||
|
foreach (var (_, row) in _rows.OrderByDescending(x => x.Key))
|
||||||
|
{
|
||||||
|
sb.Append(row).AppendLine();
|
||||||
|
}
|
||||||
|
sb.Append(new Axis(MaxTimePlotted, TimeIncrement));
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Row
|
||||||
|
{
|
||||||
|
public const int Width = 70;
|
||||||
|
|
||||||
|
private readonly char[] _chars = new char[Width + 2];
|
||||||
|
private int nextColumn = 0;
|
||||||
|
|
||||||
|
public Row(string label)
|
||||||
|
{
|
||||||
|
Array.Fill(_chars, ' ');
|
||||||
|
Array.Copy(label.ToCharArray(), _chars, label.Length);
|
||||||
|
nextColumn = label.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char this[int column]
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (column >= _chars.Length) { return; }
|
||||||
|
if (column < nextColumn) { column = nextColumn; }
|
||||||
|
_chars[column] = value;
|
||||||
|
nextColumn = column + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => new string(_chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Axis
|
||||||
|
{
|
||||||
|
private readonly int _maxTimeMark;
|
||||||
|
private readonly float _timeIncrement;
|
||||||
|
private readonly Labels _labels;
|
||||||
|
|
||||||
|
internal Axis(float maxTimePlotted, float timeIncrement)
|
||||||
|
{
|
||||||
|
_maxTimeMark = (int)Math.Ceiling(maxTimePlotted);
|
||||||
|
_timeIncrement = timeIncrement;
|
||||||
|
|
||||||
|
_labels = new Labels();
|
||||||
|
for (var i = 1; i <= _maxTimeMark; i++)
|
||||||
|
{
|
||||||
|
_labels.Add((int)(i / _timeIncrement), $" {i} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> new StringBuilder()
|
||||||
|
.Append(' ').Append('.', (int)(_maxTimeMark / _timeIncrement) + 1).AppendLine()
|
||||||
|
.Append(_labels).AppendLine()
|
||||||
|
.Append(' ', (int)(_maxTimeMark / _timeIncrement / 2 - 2)).AppendLine("Seconds")
|
||||||
|
.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Labels : Row
|
||||||
|
{
|
||||||
|
public Labels()
|
||||||
|
: base(" 0")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(int column, string label)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < label.Length; i++)
|
||||||
|
{
|
||||||
|
this[column + i] = label[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
13_Bounce/csharp/IReadWriteExtensions.cs
Normal file
11
13_Bounce/csharp/IReadWriteExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Bounce;
|
||||||
|
|
||||||
|
internal static class IReadWriteExtensions
|
||||||
|
{
|
||||||
|
internal static float ReadParameter(this IReadWrite io, string parameter)
|
||||||
|
{
|
||||||
|
var value = io.ReadNumber(parameter);
|
||||||
|
io.WriteLine();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
13_Bounce/csharp/Program.cs
Normal file
6
13_Bounce/csharp/Program.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
global using Games.Common.IO;
|
||||||
|
global using Games.Common.Numbers;
|
||||||
|
|
||||||
|
using Bounce;
|
||||||
|
|
||||||
|
new Game(new ConsoleIO()).Play(() => true);
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
# Bounce
|
||||||
|
|
||||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
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/)
|
Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)
|
||||||
|
|
||||||
|
## Conversion notes
|
||||||
|
|
||||||
|
### Mode of Operation
|
||||||
|
|
||||||
|
This conversion performs the same function as the original, and provides the same experience, but does it in a different
|
||||||
|
way.
|
||||||
|
|
||||||
|
The original BASIC code builds the graph as it writes to the screen, scanning each line for points that need to be
|
||||||
|
plotted.
|
||||||
|
|
||||||
|
This conversion steps through time, calculating the position of the ball at each instant, building the graph in memory.
|
||||||
|
It then writes the graph to the output in one go.
|
||||||
|
|
||||||
|
### Failure Modes
|
||||||
|
|
||||||
|
The original BASIC code performs no validation of the input parameters. Some combinations of parameters produce no
|
||||||
|
output, others crash the program.
|
||||||
|
|
||||||
|
In the spirit of the original this conversion also performs no validation of the parameters, but it does not attempt to
|
||||||
|
replicate the original's failure modes. It fails quite happily in its own way.
|
||||||
|
|||||||
8
13_Bounce/csharp/Resources/Instructions.txt
Normal file
8
13_Bounce/csharp/Resources/Instructions.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
This simulation lets you specify the initial velocity
|
||||||
|
of a ball thrown straight up, and the coefficient of
|
||||||
|
elasticity of the ball. Please use a decimal fraction
|
||||||
|
coefficiency (less than 1).
|
||||||
|
|
||||||
|
You also specify the time increment to be used in
|
||||||
|
'strobing' the ball's flight (try .1 initially).
|
||||||
|
|
||||||
17
13_Bounce/csharp/Resources/Resource.cs
Normal file
17
13_Bounce/csharp/Resources/Resource.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Bounce.Resources;
|
||||||
|
|
||||||
|
internal static class Resource
|
||||||
|
{
|
||||||
|
internal static class Streams
|
||||||
|
{
|
||||||
|
public static Stream Instructions => GetStream();
|
||||||
|
public static Stream Title => GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream GetStream([CallerMemberName] string? name = null)
|
||||||
|
=> Assembly.GetExecutingAssembly().GetManifestResourceStream($"Bounce.Resources.{name}.txt")
|
||||||
|
?? throw new ArgumentException($"Resource stream {name} does not exist", nameof(name));
|
||||||
|
}
|
||||||
5
13_Bounce/csharp/Resources/Title.txt
Normal file
5
13_Bounce/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Bounce
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user