diff --git a/29 Craps/csharp/Craps/.gitignore b/29 Craps/csharp/Craps/.gitignore
new file mode 100644
index 00000000..c1cb0dce
--- /dev/null
+++ b/29 Craps/csharp/Craps/.gitignore
@@ -0,0 +1,5 @@
+.vs
+TestResults
+bin
+obj
+
diff --git a/29 Craps/csharp/Craps/Craps.sln b/29 Craps/csharp/Craps/Craps.sln
new file mode 100644
index 00000000..f6ab76ec
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31112.23
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Craps", "Craps\Craps.csproj", "{783A49D7-DADE-477A-9973-D9457258573B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrapsTester", "CrapsTester\CrapsTester.csproj", "{44DFE8DB-715F-428E-992D-A97C34D47B98}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {783A49D7-DADE-477A-9973-D9457258573B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {783A49D7-DADE-477A-9973-D9457258573B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {783A49D7-DADE-477A-9973-D9457258573B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {783A49D7-DADE-477A-9973-D9457258573B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44DFE8DB-715F-428E-992D-A97C34D47B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {44DFE8DB-715F-428E-992D-A97C34D47B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44DFE8DB-715F-428E-992D-A97C34D47B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {44DFE8DB-715F-428E-992D-A97C34D47B98}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {871679FF-B86C-468B-960E-DFC625CDB5D0}
+ EndGlobalSection
+EndGlobal
diff --git a/29 Craps/csharp/Craps/Craps/Craps.csproj b/29 Craps/csharp/Craps/Craps/Craps.csproj
new file mode 100644
index 00000000..c73e0d16
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps/Craps.csproj
@@ -0,0 +1,8 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
diff --git a/29 Craps/csharp/Craps/Craps/CrapsGame.cs b/29 Craps/csharp/Craps/Craps/CrapsGame.cs
new file mode 100644
index 00000000..71a3931f
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps/CrapsGame.cs
@@ -0,0 +1,73 @@
+namespace Craps
+{
+ public enum Result
+ {
+ // It's not used in this program but it's often a good idea to include a "none"
+ // value in an enum so that you can set an instance of the enum to "invalid" or
+ // initialise it to "none of the valid values".
+ noResult,
+ naturalWin,
+ snakeEyesLoss,
+ naturalLoss,
+ pointLoss,
+ pointWin,
+ };
+
+ class CrapsGame
+ {
+ private readonly UserInterface ui;
+ private Dice dice1 = new Dice();
+ private Dice dice2 = new Dice();
+
+ public CrapsGame(ref UserInterface ui)
+ {
+ this.ui = ui;
+ }
+
+ public Result Play(out int diceRoll)
+ {
+ diceRoll = dice1.Roll() + dice2.Roll();
+
+ if (Win(diceRoll))
+ {
+ return Result.naturalWin;
+ }
+ else if (Lose(diceRoll))
+ {
+ return (diceRoll == 2) ? Result.snakeEyesLoss : Result.naturalLoss;
+ }
+ else
+ {
+ var point = diceRoll;
+ ui.Point(point);
+
+ while (true)
+ {
+ var newRoll = dice1.Roll() + dice2.Roll();
+ if (newRoll == point)
+ {
+ diceRoll = newRoll;
+ return Result.pointWin;
+ }
+ else if (newRoll == 7)
+ {
+ diceRoll = newRoll;
+ return Result.pointLoss;
+ }
+
+ ui.NoPoint(newRoll);
+ }
+ }
+ }
+
+ private bool Lose(int diceRoll)
+ {
+ return diceRoll == 2 || diceRoll == 3 || diceRoll == 12;
+ }
+
+ private bool Win(int diceRoll)
+ {
+ return diceRoll == 7 || diceRoll == 11;
+ }
+ }
+}
diff --git a/29 Craps/csharp/Craps/Craps/Dice.cs b/29 Craps/csharp/Craps/Craps/Dice.cs
new file mode 100644
index 00000000..43186026
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps/Dice.cs
@@ -0,0 +1,23 @@
+using System;
+
+
+namespace Craps
+{
+ public class Dice
+ {
+ private Random rand = new Random();
+ public readonly int sides;
+
+ public Dice()
+ {
+ sides = 6;
+ }
+
+ public Dice(int sides)
+ {
+ this.sides = sides;
+ }
+
+ public int Roll() => rand.Next(1, sides + 1);
+ }
+}
diff --git a/29 Craps/csharp/Craps/Craps/Program.cs b/29 Craps/csharp/Craps/Craps/Program.cs
new file mode 100644
index 00000000..d0aa5961
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps/Program.cs
@@ -0,0 +1,51 @@
+using System.Diagnostics;
+
+
+
+namespace Craps
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var ui = new UserInterface();
+ var game = new CrapsGame(ref ui);
+ int winnings = 0;
+
+ ui.Intro();
+
+ do
+ {
+ var bet = ui.PlaceBet();
+ var result = game.Play(out int diceRoll);
+
+ switch (result)
+ {
+ case Result.naturalWin:
+ winnings += bet;
+ break;
+
+ case Result.naturalLoss:
+ case Result.snakeEyesLoss:
+ case Result.pointLoss:
+ winnings -= bet;
+ break;
+
+ case Result.pointWin:
+ winnings += (2 * bet);
+ break;
+
+ // Include a default so that we will be warned if the values of the enum
+ // ever change and we forget to add code to handle the new value.
+ default:
+ Debug.Assert(false); // We should never get here.
+ break;
+ }
+
+ ui.ShowResult(result, diceRoll, bet);
+ } while (ui.PlayAgain(winnings));
+
+ ui.GoodBye(winnings);
+ }
+ }
+}
diff --git a/29 Craps/csharp/Craps/Craps/UserInterface.cs b/29 Craps/csharp/Craps/Craps/UserInterface.cs
new file mode 100644
index 00000000..149a3ecc
--- /dev/null
+++ b/29 Craps/csharp/Craps/Craps/UserInterface.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Diagnostics;
+
+
+
+namespace Craps
+{
+ public class UserInterface
+ {
+ public void Intro()
+ {
+ Console.WriteLine(" CRAPS");
+ Console.WriteLine(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n");
+ Console.WriteLine("2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.");
+
+ // In the original game a random number would be generated and then thrown away for as many
+ // times as the number the user entered. This is presumably something to do with ensuring
+ // that different random numbers will be generated each time the program is run.
+ //
+ // This is not necessary in C#; the random number generator uses the current time as a seed
+ // so the results will always be different every time it is run.
+ //
+ // So that the game exactly matches the original game we ask the question but then ignore
+ // the answer.
+ Console.Write("PICK A NUMBER AND INPUT TO ROLL DICE ");
+ GetInt();
+ }
+
+ public int PlaceBet()
+ {
+ Console.Write("INPUT THE AMOUNT OF YOUR WAGER. ");
+ int n = GetInt();
+ Console.WriteLine("I WILL NOW THROW THE DICE");
+
+ return n;
+ }
+
+ public bool PlayAgain(int winnings)
+ {
+ // Goodness knows why we have to enter 5 to play
+ // again but that's what the original game asked.
+ Console.Write("IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2 ");
+
+ bool playAgain = (GetInt() == 5);
+
+ if (winnings < 0)
+ {
+ Console.WriteLine($"YOU ARE NOW UNDER ${-winnings}");
+ }
+ else if (winnings > 0)
+ {
+ Console.WriteLine($"YOU ARE NOW OVER ${winnings}");
+ }
+ else
+ {
+ Console.WriteLine($"YOU ARE NOW EVEN AT ${winnings}");
+ }
+
+ return playAgain;
+ }
+
+ public void GoodBye(int winnings)
+ {
+ if (winnings < 0)
+ {
+ Console.WriteLine("TOO BAD, YOU ARE IN THE HOLE. COME AGAIN.");
+ }
+ else if (winnings > 0)
+ {
+ Console.WriteLine("CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!");
+ }
+ else
+ {
+ Console.WriteLine("CONGRATULATIONS---YOU CAME OUT EVEN, NOT BAD FOR AN AMATEUR");
+ }
+ }
+
+ public void NoPoint(int diceRoll)
+ {
+ Console.WriteLine($"{diceRoll} - NO POINT. I WILL ROLL AGAIN ");
+ }
+
+ public void Point(int point)
+ {
+ Console.WriteLine($"{point} IS THE POINT. I WILL ROLL AGAIN");
+ }
+
+ public void ShowResult(Result result, int diceRoll, int bet)
+ {
+ switch (result)
+ {
+ case Result.naturalWin:
+ Console.WriteLine($"{diceRoll} - NATURAL....A WINNER!!!!");
+ Console.WriteLine($"{diceRoll} PAYS EVEN MONEY, YOU WIN {bet} DOLLARS");
+ break;
+
+ case Result.naturalLoss:
+ Console.WriteLine($"{diceRoll} - CRAPS...YOU LOSE.");
+ Console.WriteLine($"YOU LOSE {bet} DOLLARS.");
+ break;
+
+ case Result.snakeEyesLoss:
+ Console.WriteLine($"{diceRoll} - SNAKE EYES....YOU LOSE.");
+ Console.WriteLine($"YOU LOSE {bet} DOLLARS.");
+ break;
+
+ case Result.pointLoss:
+ Console.WriteLine($"{diceRoll} - CRAPS. YOU LOSE.");
+ Console.WriteLine($"YOU LOSE ${bet}");
+ break;
+
+ case Result.pointWin:
+ Console.WriteLine($"{diceRoll} - A WINNER.........CONGRATS!!!!!!!!");
+ Console.WriteLine($"AT 2 TO 1 ODDS PAYS YOU...LET ME SEE... {2 * bet} DOLLARS");
+ break;
+
+ // Include a default so that we will be warned if the values of the enum
+ // ever change and we forget to add code to handle the new value.
+ default:
+ Debug.Assert(false); // We should never get here.
+ break;
+ }
+ }
+
+ private int GetInt()
+ {
+ while (true)
+ {
+ string input = Console.ReadLine();
+ if (int.TryParse(input, out int n))
+ {
+ return n;
+ }
+ else
+ {
+ Console.Write("ENTER AN INTEGER ");
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/29 Craps/csharp/Craps/CrapsTester/CrapsTester.csproj b/29 Craps/csharp/Craps/CrapsTester/CrapsTester.csproj
new file mode 100644
index 00000000..748e7da4
--- /dev/null
+++ b/29 Craps/csharp/Craps/CrapsTester/CrapsTester.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/29 Craps/csharp/Craps/CrapsTester/CrapsTests.cs b/29 Craps/csharp/Craps/CrapsTester/CrapsTests.cs
new file mode 100644
index 00000000..576c2b82
--- /dev/null
+++ b/29 Craps/csharp/Craps/CrapsTester/CrapsTests.cs
@@ -0,0 +1,96 @@
+using Craps;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+
+
+namespace CrapsTester
+{
+ [TestClass]
+ public class DiceTests
+ {
+ [TestMethod]
+ public void SixSidedDiceReturnsValidRolls()
+ {
+ var dice = new Dice();
+ for (int i = 0; i < 100000; i++)
+ {
+ var roll = dice.Roll();
+ Assert.IsTrue(roll >= 1 && roll <= dice.sides);
+ }
+ }
+
+ [TestMethod]
+ public void TwentySidedDiceReturnsValidRolls()
+ {
+ var dice = new Dice(20);
+ for (int i = 0; i < 100000; i++)
+ {
+ var roll = dice.Roll();
+ Assert.IsTrue(roll >= 1 && roll <= dice.sides);
+ }
+ }
+
+ [TestMethod]
+ public void DiceRollsAreRandom()
+ {
+ // Roll 600,000 dice and count how many rolls there are for each side.
+
+ var dice = new Dice();
+
+ int numOnes = 0;
+ int numTwos = 0;
+ int numThrees = 0;
+ int numFours = 0;
+ int numFives = 0;
+ int numSixes = 0;
+ int numErrors = 0;
+
+ for (int i = 0; i < 600000; i++)
+ {
+ switch (dice.Roll())
+ {
+ case 1:
+ numOnes++;
+ break;
+
+ case 2:
+ numTwos++;
+ break;
+
+ case 3:
+ numThrees++;
+ break;
+
+ case 4:
+ numFours++;
+ break;
+
+ case 5:
+ numFives++;
+ break;
+
+ case 6:
+ numSixes++;
+ break;
+
+ default:
+ numErrors++;
+ break;
+ }
+ }
+
+ // We'll assume that a variation of 10% in rolls for the different numbers is random enough.
+ // Perfectly random rolling would produce 100000 rolls per side, +/- 5% of this gives the
+ // range 90000..110000.
+ const int minRolls = 95000;
+ const int maxRolls = 105000;
+ Assert.IsTrue(numOnes >= minRolls && numOnes <= maxRolls);
+ Assert.IsTrue(numTwos >= minRolls && numTwos <= maxRolls);
+ Assert.IsTrue(numThrees >= minRolls && numThrees <= maxRolls);
+ Assert.IsTrue(numFours >= minRolls && numFours <= maxRolls);
+ Assert.IsTrue(numFives >= minRolls && numFives <= maxRolls);
+ Assert.IsTrue(numSixes >= minRolls && numSixes <= maxRolls);
+ Assert.AreEqual(numErrors, 0);
+ }
+ }
+}