mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 07:29:02 -08:00
Merge pull request #840 from drewjcooper/csharp-75-roulette
C# 75 roulette
This commit is contained in:
6
75_Roulette/csharp/Bet.cs
Normal file
6
75_Roulette/csharp/Bet.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Roulette;
|
||||
|
||||
internal record struct Bet(BetType Type, int Number, int Wager)
|
||||
{
|
||||
public int Payout => Wager * Type.Payout;
|
||||
}
|
||||
13
75_Roulette/csharp/BetType.cs
Normal file
13
75_Roulette/csharp/BetType.cs
Normal 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
|
||||
};
|
||||
}
|
||||
41
75_Roulette/csharp/Croupier.cs
Normal file
41
75_Roulette/csharp/Croupier.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
42
75_Roulette/csharp/Game.cs
Normal file
42
75_Roulette/csharp/Game.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
34
75_Roulette/csharp/IOExtensions.cs
Normal file
34
75_Roulette/csharp/IOExtensions.cs
Normal 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;
|
||||
}
|
||||
6
75_Roulette/csharp/Program.cs
Normal file
6
75_Roulette/csharp/Program.cs
Normal 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();
|
||||
1
75_Roulette/csharp/Resources/AgainPrompt.txt
Normal file
1
75_Roulette/csharp/Resources/AgainPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Again
|
||||
1
75_Roulette/csharp/Resources/BetAlready.txt
Normal file
1
75_Roulette/csharp/Resources/BetAlready.txt
Normal file
@@ -0,0 +1 @@
|
||||
You made that bet once already,dum-dum
|
||||
1
75_Roulette/csharp/Resources/BetPrompt.txt
Normal file
1
75_Roulette/csharp/Resources/BetPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Number {0}
|
||||
1
75_Roulette/csharp/Resources/BrokeHouse.txt
Normal file
1
75_Roulette/csharp/Resources/BrokeHouse.txt
Normal file
@@ -0,0 +1 @@
|
||||
You broke the house!
|
||||
16
75_Roulette/csharp/Resources/Check.txt
Normal file
16
75_Roulette/csharp/Resources/Check.txt
Normal 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!
|
||||
|
||||
1
75_Roulette/csharp/Resources/CheckPrompt.txt
Normal file
1
75_Roulette/csharp/Resources/CheckPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
To whom shall I make the check
|
||||
1
75_Roulette/csharp/Resources/HowManyBetsPrompt.txt
Normal file
1
75_Roulette/csharp/Resources/HowManyBetsPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
How many bets
|
||||
48
75_Roulette/csharp/Resources/Instructions.txt
Normal file
48
75_Roulette/csharp/Resources/Instructions.txt
Normal 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.
|
||||
|
||||
1
75_Roulette/csharp/Resources/InstructionsPrompt.txt
Normal file
1
75_Roulette/csharp/Resources/InstructionsPrompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
Do you want instructions
|
||||
1
75_Roulette/csharp/Resources/LastDollar.txt
Normal file
1
75_Roulette/csharp/Resources/LastDollar.txt
Normal file
@@ -0,0 +1 @@
|
||||
Oops! you just spent your last dollar!
|
||||
1
75_Roulette/csharp/Resources/Outcome.txt
Normal file
1
75_Roulette/csharp/Resources/Outcome.txt
Normal file
@@ -0,0 +1 @@
|
||||
You {0} {1} dollars on bet {2}
|
||||
59
75_Roulette/csharp/Resources/Resource.cs
Normal file
59
75_Roulette/csharp/Resources/Resource.cs
Normal 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}'.");
|
||||
}
|
||||
2
75_Roulette/csharp/Resources/Slot.txt
Normal file
2
75_Roulette/csharp/Resources/Slot.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
{0} {1}
|
||||
|
||||
3
75_Roulette/csharp/Resources/Spinning.txt
Normal file
3
75_Roulette/csharp/Resources/Spinning.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Spinning
|
||||
|
||||
|
||||
3
75_Roulette/csharp/Resources/Thanks.txt
Normal file
3
75_Roulette/csharp/Resources/Thanks.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Thanks for you money.
|
||||
I'll use it to buy a solid gold roulette WHEEL
|
||||
|
||||
7
75_Roulette/csharp/Resources/Title.txt
Normal file
7
75_Roulette/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Roulette
|
||||
Creative Computing Morristown, New Jersey
|
||||
|
||||
|
||||
|
||||
Welcome to the roulette table
|
||||
|
||||
3
75_Roulette/csharp/Resources/Totals.txt
Normal file
3
75_Roulette/csharp/Resources/Totals.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
Totals Me You
|
||||
{0,-14}{1}
|
||||
@@ -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>
|
||||
|
||||
18
75_Roulette/csharp/Slot.cs
Normal file
18
75_Roulette/csharp/Slot.cs
Normal 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);
|
||||
}
|
||||
71
75_Roulette/csharp/Table.cs
Normal file
71
75_Roulette/csharp/Table.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
52
75_Roulette/csharp/Wheel.cs
Normal file
52
75_Roulette/csharp/Wheel.cs
Normal 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)];
|
||||
}
|
||||
Reference in New Issue
Block a user