diff --git a/13_Bounce/csharp/Bounce.cs b/13_Bounce/csharp/Bounce.cs new file mode 100644 index 00000000..c250e7df --- /dev/null +++ b/13_Bounce/csharp/Bounce.cs @@ -0,0 +1,38 @@ +namespace Bounce; + +/// +/// Represents the bounce of the ball, calculating duration, height and position in time. +/// +/// +/// All calculations are derived from the equation for projectile motion: s = vt + 0.5at^2 +/// +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); +} diff --git a/13_Bounce/csharp/Game.cs b/13_Bounce/csharp/Game.cs index 1bf8236d..8d2baf8b 100644 --- a/13_Bounce/csharp/Game.cs +++ b/13_Bounce/csharp/Game.cs @@ -1,13 +1,9 @@ -using System.Text; using static Bounce.Resources.Resource; namespace Bounce; internal class Game { - private const int _chartWidth = 70; - private const float _acceleration = -32; // feet/s^2 - private readonly IReadWrite _io; public Game(IReadWrite io) @@ -20,130 +16,20 @@ internal class Game _io.Write(Streams.Title); _io.Write(Streams.Instructions); - var increment = _io.ReadParameter("Time increment (sec)"); + var timeIncrement = _io.ReadParameter("Time increment (sec)"); var velocity = _io.ReadParameter("Velocity (fps)"); var elasticity = _io.ReadParameter("Coefficient"); - var timeToFirstBounce = -2 * velocity / _acceleration; - var bounceCount = (int)(Graph.Row.Width * increment / timeToFirstBounce); + var bounce = new Bounce(velocity); + var bounceCount = (int)(Graph.Row.Width * timeIncrement / bounce.Duration); + var graph = new Graph(bounce.MaxHeight, timeIncrement); - var maxHeight = (float)Math.Round(-velocity * velocity / 2 / _acceleration, MidpointRounding.AwayFromZero); - - var graph = new Graph(maxHeight, increment); - - var totalTime = 0f; - for (var bounce = 0; bounce < bounceCount; bounce++, velocity *= elasticity) + var time = 0f; + for (var i = 0; i < bounceCount; i++, bounce = bounce.Next(elasticity)) { - var bounceDuration = -2 * velocity / _acceleration; - for (var time = 0f; time <= bounceDuration; totalTime += increment, time += increment) - { - var height = velocity * time + _acceleration * time * time / 2; - graph.Plot(totalTime, height); - } + time = bounce.Plot(graph, time); } _io.WriteLine(graph); } } - -internal class Graph -{ - private readonly Dictionary _rows; - private readonly float _increment; - private float _maxTimePlotted; - - public Graph(float maxHeight, float increment) - { - // 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} " : "")); - _increment = increment; - } - - public void Plot(float time, float height) - { - var rowIndex = (int)Math.Round(height * 2, MidpointRounding.AwayFromZero); - var colIndex = (int)(time / _increment) + 1; - if (_rows.TryGetValue(rowIndex, out var row)) - { - row[colIndex] = '0'; - } - _maxTimePlotted = Math.Max(time, _maxTimePlotted); - } - - public override string ToString() - { - var sb = new StringBuilder(); - foreach (var (_, row) in _rows.OrderByDescending(x => x.Key)) - { - sb.Append(row).AppendLine(); - } - var maxTimeMark = (int)Math.Ceiling(_maxTimePlotted); - sb.Append(' ').Append('.', (int)(maxTimeMark/_increment) + 1).AppendLine(); - - var timeLabels = new Labels(); - for (var i = 1; i <= maxTimeMark; i++) - { - timeLabels.Add((int)(i / _increment), $" {i} "); - } - sb.Append(timeLabels).AppendLine(); - sb.Append(' ', (int)((int)(_maxTimePlotted + 1)/_increment/2 - 2)).AppendLine("Seconds"); - - 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 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]; - } - } - } -} - -internal static class IReadWriteExtensions -{ - internal static float ReadParameter(this IReadWrite io, string parameter) - { - var value = io.ReadNumber(parameter); - io.WriteLine(); - return value; - } -} \ No newline at end of file diff --git a/13_Bounce/csharp/Graph.cs b/13_Bounce/csharp/Graph.cs new file mode 100644 index 00000000..608b5bda --- /dev/null +++ b/13_Bounce/csharp/Graph.cs @@ -0,0 +1,116 @@ +using System.Text; + +namespace Bounce; + +/// +/// Provides support for plotting a graph of height vs time, and rendering it to a string. +/// +internal class Graph +{ + private readonly Dictionary _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]; + } + } + } +} diff --git a/13_Bounce/csharp/IReadWriteExtensions.cs b/13_Bounce/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..bafeee42 --- /dev/null +++ b/13_Bounce/csharp/IReadWriteExtensions.cs @@ -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; + } +} \ No newline at end of file