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/Bounce.csproj b/13_Bounce/csharp/Bounce.csproj
index d3fe4757..e377b904 100644
--- a/13_Bounce/csharp/Bounce.csproj
+++ b/13_Bounce/csharp/Bounce.csproj
@@ -6,4 +6,12 @@
enable
enable
+
+
+
+
+
+
+
+
diff --git a/13_Bounce/csharp/Game.cs b/13_Bounce/csharp/Game.cs
new file mode 100644
index 00000000..155bdf48
--- /dev/null
+++ b/13_Bounce/csharp/Game.cs
@@ -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 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);
+ }
+ }
+}
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
diff --git a/13_Bounce/csharp/Program.cs b/13_Bounce/csharp/Program.cs
new file mode 100644
index 00000000..86af3365
--- /dev/null
+++ b/13_Bounce/csharp/Program.cs
@@ -0,0 +1,6 @@
+global using Games.Common.IO;
+global using Games.Common.Numbers;
+
+using Bounce;
+
+new Game(new ConsoleIO()).Play(() => true);
\ No newline at end of file
diff --git a/13_Bounce/csharp/README.md b/13_Bounce/csharp/README.md
index 4daabb5c..6323d9d1 100644
--- a/13_Bounce/csharp/README.md
+++ b/13_Bounce/csharp/README.md
@@ -1,3 +1,26 @@
+# Bounce
+
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 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.
diff --git a/13_Bounce/csharp/Resources/Instructions.txt b/13_Bounce/csharp/Resources/Instructions.txt
new file mode 100644
index 00000000..d2901d6e
--- /dev/null
+++ b/13_Bounce/csharp/Resources/Instructions.txt
@@ -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).
+
diff --git a/13_Bounce/csharp/Resources/Resource.cs b/13_Bounce/csharp/Resources/Resource.cs
new file mode 100644
index 00000000..68bebee0
--- /dev/null
+++ b/13_Bounce/csharp/Resources/Resource.cs
@@ -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));
+}
\ No newline at end of file
diff --git a/13_Bounce/csharp/Resources/Title.txt b/13_Bounce/csharp/Resources/Title.txt
new file mode 100644
index 00000000..5bcd0496
--- /dev/null
+++ b/13_Bounce/csharp/Resources/Title.txt
@@ -0,0 +1,5 @@
+ Bounce
+ Creative Computing Morristown, New Jersey
+
+
+