mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-02 08:10:45 -08:00
Merge pull request #281 from rbamforth/main
Rewrite of 29 Craps into C#
This commit is contained in:
5
29 Craps/csharp/Craps/.gitignore
vendored
Normal file
5
29 Craps/csharp/Craps/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vs
|
||||
TestResults
|
||||
bin
|
||||
obj
|
||||
|
||||
31
29 Craps/csharp/Craps/Craps.sln
Normal file
31
29 Craps/csharp/Craps/Craps.sln
Normal file
@@ -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
|
||||
8
29 Craps/csharp/Craps/Craps/Craps.csproj
Normal file
8
29 Craps/csharp/Craps/Craps/Craps.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
73
29 Craps/csharp/Craps/Craps/CrapsGame.cs
Normal file
73
29 Craps/csharp/Craps/Craps/CrapsGame.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
29 Craps/csharp/Craps/Craps/Dice.cs
Normal file
23
29 Craps/csharp/Craps/Craps/Dice.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
51
29 Craps/csharp/Craps/Craps/Program.cs
Normal file
51
29 Craps/csharp/Craps/Craps/Program.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
143
29 Craps/csharp/Craps/Craps/UserInterface.cs
Normal file
143
29 Craps/csharp/Craps/Craps/UserInterface.cs
Normal file
@@ -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 ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
29 Craps/csharp/Craps/CrapsTester/CrapsTester.csproj
Normal file
20
29 Craps/csharp/Craps/CrapsTester/CrapsTester.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Craps\Craps.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
96
29 Craps/csharp/Craps/CrapsTester/CrapsTests.cs
Normal file
96
29 Craps/csharp/Craps/CrapsTester/CrapsTests.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user