Merge pull request #840 from drewjcooper/csharp-75-roulette

C# 75 roulette
This commit is contained in:
Jeff Atwood
2023-02-16 03:06:46 -08:00
committed by GitHub
27 changed files with 442 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
namespace Roulette;
internal record struct Bet(BetType Type, int Number, int Wager)
{
public int Payout => Wager * Type.Payout;
}

View File

@@ -0,0 +1,13 @@
namespace Roulette;
internal record struct BetType(int Value)
{
public static implicit operator BetType(int value) => new(value);
public int Payout => Value switch
{
<= 36 or >= 49 => 35,
<= 42 => 2,
<= 48 => 1
};
}

View File

@@ -0,0 +1,41 @@
namespace Roulette;
internal class Croupier
{
private const int _initialHouse = 100_000;
private const int _initialPlayer = 1_000;
private int _house = _initialHouse;
private int _player = _initialPlayer;
public string Totals => Strings.Totals(_house, _player);
public bool PlayerIsBroke => _player <= 0;
public bool HouseIsBroke => _house <= 0;
internal string Pay(Bet bet)
{
_house -= bet.Payout;
_player += bet.Payout;
if (_house <= 0)
{
_player = _initialHouse + _initialPlayer;
}
return Strings.Win(bet);
}
internal string Take(Bet bet)
{
_house += bet.Wager;
_player -= bet.Wager;
return Strings.Lose(bet);
}
public void CutCheck(IReadWrite io, IRandom random)
{
var name = io.ReadString(Prompts.Check);
io.Write(Strings.Check(random, name, _player));
}
}

View File

@@ -0,0 +1,42 @@
namespace Roulette;
internal class Game
{
private readonly IReadWrite _io;
private readonly IRandom _random;
private readonly Table _table;
private readonly Croupier _croupier;
public Game(IReadWrite io, IRandom random)
{
_io = io;
_random = random;
_croupier = new();
_table = new(_croupier, io, random);
}
public void Play()
{
_io.Write(Streams.Title);
if (!_io.ReadString(Prompts.Instructions).ToLowerInvariant().StartsWith('n'))
{
_io.Write(Streams.Instructions);
}
while (_table.Play());
if (_croupier.PlayerIsBroke)
{
_io.Write(Streams.LastDollar);
_io.Write(Streams.Thanks);
return;
}
if (_croupier.HouseIsBroke)
{
_io.Write(Streams.BrokeHouse);
}
_croupier.CutCheck(_io, _random);
}
}

View File

@@ -0,0 +1,34 @@
namespace Roulette;
internal static class IOExtensions
{
internal static int ReadBetCount(this IReadWrite io)
{
while (true)
{
var betCount = io.ReadNumber(Prompts.HowManyBets);
if (betCount.IsValidInt(1)) { return (int)betCount; }
}
}
internal static Bet ReadBet(this IReadWrite io, int number)
{
while (true)
{
var (type, amount) = io.Read2Numbers(Prompts.Bet(number));
if (type.IsValidInt(1, 50) && amount.IsValidInt(5, 500))
{
return new()
{
Type = (int)type,
Number = number,
Wager = (int)amount
};
}
}
}
internal static bool IsValidInt(this float value, int minValue, int maxValue = int.MaxValue)
=> value == (int)value && value >= minValue && value <= maxValue;
}

View File

@@ -0,0 +1,6 @@
global using Games.Common.IO;
global using Games.Common.Randomness;
global using static Roulette.Resources.Resource;
using Roulette;
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();

View File

@@ -0,0 +1 @@
Again

View File

@@ -0,0 +1 @@
You made that bet once already,dum-dum

View File

@@ -0,0 +1 @@
Number {0}

View File

@@ -0,0 +1 @@
You broke the house!

View File

@@ -0,0 +1,16 @@
------------------------------------------------------------------------Check No. {0}
{1:MMMM d',' yyyy}
Pay to the order of-----{2}-----$ {3}
The Memory Bank of New YORK
The Computer
----------X-----
--------------------------------------------------------------Come back soon!

View File

@@ -0,0 +1 @@
To whom shall I make the check

View File

@@ -0,0 +1 @@
How many bets

View File

@@ -0,0 +1,48 @@
This is the betting layout
(*=RED)
1* 2 3*
4 5* 6
7* 8 9*
10 11 12*
---------------
13 14* 15
16* 17 18*
19* 20 21*
22 23* 24
---------------
25* 26 27*
28 29 30*
31 32* 33
34* 35 36*
---------------
00 0
Types of bets
The numbers 1 to 36 signify a straight bet
on that number.
These pay off 35:1
The 2:1 bets are:
37) 1-12 40) First column
38) 13-24 41) Second column
39) 25-36 42) Third column
The even money bets are:
43) 1-18 46) Odd
44) 19-36 47) Red
45) Even 48) Black
49)0 and 50)00 pay off 35:1
Note: 0 and 00 do not count under any
bets except their own.
When I ask for each bet, type the number
and the amount, separated by a comma.
For example: to bet $500 on Black, type 48,500
when I ask for a bet.
The minimum bet is $5, the maximum is $500.

View File

@@ -0,0 +1 @@
Do you want instructions

View File

@@ -0,0 +1 @@
Oops! you just spent your last dollar!

View File

@@ -0,0 +1 @@
You {0} {1} dollars on bet {2}

View File

@@ -0,0 +1,59 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Games.Common.Randomness;
namespace Roulette.Resources;
internal static class Resource
{
internal static class Streams
{
public static Stream Title => GetStream();
public static Stream Instructions => GetStream();
public static Stream BetAlready => GetStream();
public static Stream Spinning => GetStream();
public static Stream LastDollar => GetStream();
public static Stream BrokeHouse => GetStream();
public static Stream Thanks => GetStream();
}
internal static class Strings
{
public static string Black(int number) => Slot(number);
public static string Red(int number) => Slot(number);
private static string Slot(int number, [CallerMemberName] string? colour = null)
=> string.Format(GetString(), number, colour);
public static string Lose(Bet bet) => Outcome(bet.Wager, bet.Number);
public static string Win(Bet bet) => Outcome(bet.Payout, bet.Number);
private static string Outcome(int amount, int number, [CallerMemberName] string? winlose = null)
=> string.Format(GetString(), winlose, amount, number);
public static string Totals(int me, int you) => string.Format(GetString(), me, you);
public static string Check(IRandom random, string payee, int amount)
=> string.Format(GetString(), random.Next(100), DateTime.Now, payee, amount);
}
internal static class Prompts
{
public static string Instructions => GetPrompt();
public static string HowManyBets => GetPrompt();
public static string Bet(int number) => string.Format(GetPrompt(), number);
public static string Again => GetPrompt();
public static string Check => GetPrompt();
}
private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt");
private static string GetString([CallerMemberName] string? name = null)
{
using var stream = GetStream(name);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
private static Stream GetStream([CallerMemberName] string? name = null) =>
Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt")
?? throw new Exception($"Could not find embedded resource stream '{name}'.");
}

View File

@@ -0,0 +1,2 @@
{0} {1}

View File

@@ -0,0 +1,3 @@
Spinning

View File

@@ -0,0 +1,3 @@
Thanks for you money.
I'll use it to buy a solid gold roulette WHEEL

View File

@@ -0,0 +1,7 @@
Roulette
Creative Computing Morristown, New Jersey
Welcome to the roulette table

View File

@@ -0,0 +1,3 @@
Totals Me You
{0,-14}{1}

View File

@@ -1,9 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources/*.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System.Collections.Immutable;
namespace Roulette;
internal class Slot
{
private readonly ImmutableHashSet<BetType> _coveringBets;
public Slot (string name, params BetType[] coveringBets)
{
Name = name;
_coveringBets = coveringBets.ToImmutableHashSet();
}
public string Name { get; }
public bool IsCoveredBy(Bet bet) => _coveringBets.Contains(bet.Type);
}

View File

@@ -0,0 +1,71 @@
namespace Roulette;
internal class Table
{
private readonly IReadWrite _io;
private readonly Wheel _wheel;
private readonly Croupier _croupier;
public Table(Croupier croupier, IReadWrite io, IRandom random)
{
_croupier = croupier;
_io = io;
_wheel = new(random);
}
public bool Play()
{
var bets = AcceptBets();
var slot = SpinWheel();
SettleBets(bets, slot);
_io.Write(_croupier.Totals);
if (_croupier.PlayerIsBroke || _croupier.HouseIsBroke) { return false; }
return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y');
}
private Slot SpinWheel()
{
_io.Write(Streams.Spinning);
var slot = _wheel.Spin();
_io.Write(slot.Name);
return slot;
}
private IReadOnlyList<Bet> AcceptBets()
{
var betCount = _io.ReadBetCount();
var betTypes = new HashSet<BetType>();
var bets = new List<Bet>();
for (int i = 1; i <= betCount; i++)
{
while (!TryAdd(_io.ReadBet(i)))
{
_io.Write(Streams.BetAlready);
}
}
return bets.AsReadOnly();
bool TryAdd(Bet bet)
{
if (betTypes.Add(bet.Type))
{
bets.Add(bet);
return true;
}
return false;
}
}
private void SettleBets(IReadOnlyList<Bet> bets, Slot slot)
{
foreach (var bet in bets)
{
_io.Write(slot.IsCoveredBy(bet) ? _croupier.Pay(bet) : _croupier.Take(bet));
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Immutable;
namespace Roulette;
internal class Wheel
{
private static readonly ImmutableArray<Slot> _slots = ImmutableArray.Create(
new Slot(Strings.Red(1), 1, 37, 40, 43, 46, 47),
new Slot(Strings.Black(2), 2, 37, 41, 43, 45, 48),
new Slot(Strings.Red(3), 3, 37, 42, 43, 46, 47),
new Slot(Strings.Black(4), 4, 37, 40, 43, 45, 48),
new Slot(Strings.Red(5), 5, 37, 41, 43, 46, 47),
new Slot(Strings.Black(6), 6, 37, 42, 43, 45, 48),
new Slot(Strings.Red(7), 7, 37, 40, 43, 46, 47),
new Slot(Strings.Black(8), 8, 37, 41, 43, 45, 48),
new Slot(Strings.Red(9), 9, 37, 42, 43, 46, 47),
new Slot(Strings.Black(10), 10, 37, 40, 43, 45, 48),
new Slot(Strings.Black(11), 11, 37, 41, 43, 46, 48),
new Slot(Strings.Red(12), 12, 37, 42, 43, 45, 47),
new Slot(Strings.Black(13), 13, 38, 40, 43, 46, 48),
new Slot(Strings.Red(14), 14, 38, 41, 43, 45, 47),
new Slot(Strings.Black(15), 15, 38, 42, 43, 46, 48),
new Slot(Strings.Red(16), 16, 38, 40, 43, 45, 47),
new Slot(Strings.Black(17), 17, 38, 41, 43, 46, 48),
new Slot(Strings.Red(18), 18, 38, 42, 43, 45, 47),
new Slot(Strings.Red(19), 19, 38, 40, 44, 46, 47),
new Slot(Strings.Black(20), 20, 38, 41, 44, 45, 48),
new Slot(Strings.Red(21), 21, 38, 42, 44, 46, 47),
new Slot(Strings.Black(22), 22, 38, 40, 44, 45, 48),
new Slot(Strings.Red(23), 23, 38, 41, 44, 46, 47),
new Slot(Strings.Black(24), 24, 38, 42, 44, 45, 48),
new Slot(Strings.Red(25), 25, 39, 40, 44, 46, 47),
new Slot(Strings.Black(26), 26, 39, 41, 44, 45, 48),
new Slot(Strings.Red(27), 27, 39, 42, 44, 46, 47),
new Slot(Strings.Black(28), 28, 39, 40, 44, 45, 48),
new Slot(Strings.Black(29), 29, 39, 41, 44, 46, 48),
new Slot(Strings.Red(30), 30, 39, 42, 44, 45, 47),
new Slot(Strings.Black(31), 31, 39, 40, 44, 46, 48),
new Slot(Strings.Red(32), 32, 39, 41, 44, 45, 47),
new Slot(Strings.Black(33), 33, 39, 42, 44, 46, 48),
new Slot(Strings.Red(34), 34, 39, 40, 44, 45, 47),
new Slot(Strings.Black(35), 35, 39, 41, 44, 46, 48),
new Slot(Strings.Red(36), 36, 39, 42, 44, 45, 47),
new Slot("0", 49),
new Slot("00", 50));
private readonly IRandom _random;
public Wheel(IRandom random) => _random = random;
public Slot Spin() => _slots[_random.Next(_slots.Length)];
}