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); + } + } +}