mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
Implement Blackjack (game 10) in C#
This commit is contained in:
9
10 Blackjack/csharp/Blackjack.csproj
Normal file
9
10 Blackjack/csharp/Blackjack.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Blackjack</RootNamespace>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
32
10 Blackjack/csharp/Card.cs
Normal file
32
10 Blackjack/csharp/Card.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Blackjack
|
||||
{
|
||||
public class Card
|
||||
{
|
||||
private static readonly string[] _names = new[] {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
|
||||
|
||||
public Card(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public int Index { get; private set; }
|
||||
|
||||
public string Name => _names[Index];
|
||||
|
||||
public string IndefiniteArticle => (Index == 0 || Index == 7) ? "an" : "a";
|
||||
|
||||
public bool IsAce => Index == 0;
|
||||
|
||||
public int Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsAce)
|
||||
return 11;
|
||||
if (Index > 8)
|
||||
return 10;
|
||||
return Index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
10 Blackjack/csharp/Deck.cs
Normal file
56
10 Blackjack/csharp/Deck.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Blackjack
|
||||
{
|
||||
public class Deck
|
||||
{
|
||||
private static readonly Random _random = new Random();
|
||||
|
||||
private readonly List<Card> _cards = new List<Card>(52);
|
||||
private readonly List<Card> _discards = new List<Card>(52);
|
||||
|
||||
public Deck()
|
||||
{
|
||||
for (var index = 0; index < 12; index++)
|
||||
{
|
||||
for (var suit = 0; suit < 4; suit++)
|
||||
{
|
||||
_discards.Add(new Card(index));
|
||||
}
|
||||
}
|
||||
Reshuffle();
|
||||
}
|
||||
|
||||
private void Reshuffle()
|
||||
{
|
||||
Console.WriteLine("Reshuffling");
|
||||
|
||||
_cards.AddRange(_discards);
|
||||
_discards.Clear();
|
||||
|
||||
for (var index1 = _cards.Count - 1; index1 > 0; index1--)
|
||||
{
|
||||
var index2 = _random.Next(0, index1);
|
||||
var swapCard = _cards[index1];
|
||||
_cards[index1] = _cards[index2];
|
||||
_cards[index2] = swapCard;
|
||||
}
|
||||
}
|
||||
|
||||
public Card DrawCard()
|
||||
{
|
||||
if (_cards.Count < 2)
|
||||
Reshuffle();
|
||||
|
||||
var card = _cards[_cards.Count - 1];
|
||||
_cards.RemoveAt(_cards.Count - 1);
|
||||
return card;
|
||||
}
|
||||
|
||||
public void Discard(IEnumerable<Card> cards)
|
||||
{
|
||||
_discards.AddRange(cards);
|
||||
}
|
||||
}
|
||||
}
|
||||
232
10 Blackjack/csharp/Game.cs
Normal file
232
10 Blackjack/csharp/Game.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Blackjack
|
||||
{
|
||||
public class Game
|
||||
{
|
||||
private readonly Deck _deck = new Deck();
|
||||
private readonly int _numberOfPlayers;
|
||||
private readonly Player[] _players;
|
||||
private readonly Hand _dealerHand;
|
||||
|
||||
public Game(int numberOfPlayers)
|
||||
{
|
||||
_numberOfPlayers = numberOfPlayers;
|
||||
_players = new Player[_numberOfPlayers];
|
||||
for (var playerIndex = 0; playerIndex < _numberOfPlayers; playerIndex++)
|
||||
_players[playerIndex] = new Player(playerIndex);
|
||||
_dealerHand = new Hand();
|
||||
}
|
||||
|
||||
public void PlayGame()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
PlayRound();
|
||||
TallyResults();
|
||||
ResetRoundState();
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayRound()
|
||||
{
|
||||
GetPlayerBets();
|
||||
|
||||
DealHands();
|
||||
|
||||
// Test for insurance
|
||||
var dealerIsShowingAce = _dealerHand.Cards[0].IsAce;
|
||||
if (dealerIsShowingAce && Prompt.ForYesNo("Any insurance?"))
|
||||
{
|
||||
Console.WriteLine("Insurance bets");
|
||||
var insuranceBets = new int[_numberOfPlayers];
|
||||
foreach (var player in _players)
|
||||
insuranceBets[player.Index] = Prompt.ForInteger($"# {player.Index + 1} ?", 0, player.RoundBet / 2);
|
||||
|
||||
var insuranceEffectMultiplier = _dealerHand.IsBlackjack ? 2 : -1;
|
||||
foreach (var player in _players)
|
||||
player.RoundWinnings += insuranceBets[player.Index] * insuranceEffectMultiplier;
|
||||
}
|
||||
|
||||
// Test for dealer blackjack
|
||||
var concealedCard = _dealerHand.Cards[0];
|
||||
if (_dealerHand.IsBlackjack)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Dealer has {0} {1} in the hole for blackjack.", concealedCard.IndefiniteArticle, concealedCard.Name);
|
||||
return;
|
||||
}
|
||||
else if (dealerIsShowingAce)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("No dealer blackjack.");
|
||||
}
|
||||
|
||||
foreach (var player in _players)
|
||||
PlayHand(player);
|
||||
|
||||
// Dealer hand
|
||||
var allPlayersBusted = _players.All(p => p.Hand.IsBusted && (!p.SecondHand.Exists || p.SecondHand.IsBusted));
|
||||
if (allPlayersBusted)
|
||||
Console.WriteLine("Dealer had {0} {1} concealed.", concealedCard.IndefiniteArticle, concealedCard.Name);
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Dealer has {0} {1} concealed for a total of {2}", concealedCard.IndefiniteArticle, concealedCard.Name, _dealerHand.Total);
|
||||
if (_dealerHand.Total < 17)
|
||||
{
|
||||
Console.Write("Draws");
|
||||
while (_dealerHand.Total < 17)
|
||||
{
|
||||
var card = _dealerHand.AddCard(_deck.DrawCard());
|
||||
Console.Write(" {0}", card.Name);
|
||||
}
|
||||
if (_dealerHand.IsBusted)
|
||||
Console.WriteLine(" ...Busted");
|
||||
else
|
||||
Console.WriteLine(" ---Total is {0}", _dealerHand.Total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetPlayerBets()
|
||||
{
|
||||
Console.WriteLine("Bets:");
|
||||
foreach (var player in _players)
|
||||
player.RoundBet = Prompt.ForInteger($"# {player.Name} ?", 1, 500);
|
||||
}
|
||||
|
||||
private void DealHands()
|
||||
{
|
||||
Console.Write("Player ");
|
||||
foreach (var player in _players)
|
||||
Console.Write("{0} ", player.Name);
|
||||
Console.WriteLine("Dealer");
|
||||
|
||||
for (var cardIndex = 0; cardIndex < 2; cardIndex++)
|
||||
{
|
||||
Console.Write(" ");
|
||||
foreach (var player in _players)
|
||||
Console.Write(" {0,-4}", player.Hand.AddCard(_deck.DrawCard()).Name);
|
||||
var dealerCard = _dealerHand.AddCard(_deck.DrawCard());
|
||||
Console.Write(" {0,-4}", (cardIndex == 0) ? "XX" : dealerCard.Name);
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayHand(Player player)
|
||||
{
|
||||
var hand = player.Hand;
|
||||
|
||||
Console.Write("Player {0} ", player.Name);
|
||||
|
||||
var playerCanSplit = hand.Cards[0].Value == hand.Cards[1].Value;
|
||||
var command = Prompt.ForCommandCharacter("?", playerCanSplit ? "HSD/" : "HSD");
|
||||
switch (command)
|
||||
{
|
||||
case "D":
|
||||
player.RoundBet *= 2;
|
||||
goto case "H";
|
||||
|
||||
case "H":
|
||||
while (TakeHit(hand) && PromptForAnotherHit())
|
||||
{ }
|
||||
if (!hand.IsBusted)
|
||||
Console.WriteLine("Total is {0}", hand.Total);
|
||||
break;
|
||||
|
||||
case "S":
|
||||
if (hand.IsBlackjack)
|
||||
{
|
||||
Console.WriteLine("Blackjack!");
|
||||
player.RoundWinnings = (int)(1.5 * player.RoundBet + 0.5);
|
||||
player.RoundBet = 0;
|
||||
}
|
||||
else
|
||||
Console.WriteLine("Total is {0}", hand.Total);
|
||||
break;
|
||||
|
||||
case "/":
|
||||
hand.SplitHand(player.SecondHand);
|
||||
var card = hand.AddCard(_deck.DrawCard());
|
||||
Console.WriteLine("First hand receives {0} {1}", card.IndefiniteArticle, card.Name);
|
||||
card = player.SecondHand.AddCard(_deck.DrawCard());
|
||||
Console.WriteLine("Second hand receives {0} {1}", card.IndefiniteArticle, card.Name);
|
||||
|
||||
for (int handNumber = 1; handNumber <= 2; handNumber++)
|
||||
{
|
||||
hand = (handNumber == 1) ? player.Hand : player.SecondHand;
|
||||
|
||||
Console.Write("Hand {0}", handNumber);
|
||||
while (PromptForAnotherHit() && TakeHit(hand))
|
||||
{ }
|
||||
if (!hand.IsBusted)
|
||||
Console.WriteLine("Total is {0}", hand.Total);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TakeHit(Hand hand)
|
||||
{
|
||||
var card = hand.AddCard(_deck.DrawCard());
|
||||
Console.Write("Received {0,-6}", $"{card.IndefiniteArticle} {card.Name}");
|
||||
if (hand.IsBusted)
|
||||
{
|
||||
Console.WriteLine("...Busted");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool PromptForAnotherHit()
|
||||
{
|
||||
return String.Equals(Prompt.ForCommandCharacter(" Hit?", "HS"), "H");
|
||||
}
|
||||
|
||||
private void TallyResults()
|
||||
{
|
||||
Console.WriteLine();
|
||||
foreach (var player in _players)
|
||||
{
|
||||
player.RoundWinnings += CalculateWinnings(player, player.Hand);
|
||||
if (player.SecondHand.Exists)
|
||||
player.RoundWinnings += CalculateWinnings(player, player.SecondHand);
|
||||
player.TotalWinnings += player.RoundWinnings;
|
||||
|
||||
Console.WriteLine("Player {0} {1,-6} {2,3} Total= {3,5}",
|
||||
player.Name,
|
||||
(player.RoundWinnings > 0) ? "wins" : (player.RoundWinnings) < 0 ? "loses" : "pushes",
|
||||
(player.RoundWinnings != 0) ? Math.Abs(player.RoundWinnings).ToString() : "",
|
||||
player.TotalWinnings);
|
||||
}
|
||||
Console.WriteLine("Dealer's total= {0}", -_players.Sum(p => p.TotalWinnings));
|
||||
}
|
||||
|
||||
private int CalculateWinnings(Player player, Hand hand)
|
||||
{
|
||||
if (hand.IsBusted)
|
||||
return -player.RoundBet;
|
||||
if (hand.Total == _dealerHand.Total)
|
||||
return 0;
|
||||
if (_dealerHand.IsBusted || hand.Total > _dealerHand.Total)
|
||||
return player.RoundBet;
|
||||
return -player.RoundBet;
|
||||
}
|
||||
|
||||
private void ResetRoundState()
|
||||
{
|
||||
foreach (var player in _players)
|
||||
{
|
||||
player.RoundWinnings = 0;
|
||||
player.RoundBet = 0;
|
||||
player.Hand.Discard(_deck);
|
||||
player.SecondHand.Discard(_deck);
|
||||
}
|
||||
_dealerHand.Discard(_deck);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
10 Blackjack/csharp/Hand.cs
Normal file
67
10 Blackjack/csharp/Hand.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Blackjack
|
||||
{
|
||||
public class Hand
|
||||
{
|
||||
private readonly List<Card> _cards = new List<Card>(12);
|
||||
private int _cachedTotal = 0;
|
||||
|
||||
public Card AddCard(Card card)
|
||||
{
|
||||
_cards.Add(card);
|
||||
_cachedTotal = 0;
|
||||
return card;
|
||||
}
|
||||
|
||||
public void Discard(Deck deck)
|
||||
{
|
||||
deck.Discard(_cards);
|
||||
_cards.Clear();
|
||||
_cachedTotal = 0;
|
||||
}
|
||||
|
||||
public void SplitHand(Hand secondHand)
|
||||
{
|
||||
if (Count != 2 || secondHand.Count != 0)
|
||||
throw new InvalidOperationException();
|
||||
secondHand.AddCard(_cards[1]);
|
||||
_cards.RemoveAt(1);
|
||||
_cachedTotal = 0;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Card> Cards => _cards;
|
||||
|
||||
public int Count => _cards.Count;
|
||||
|
||||
public bool Exists => _cards.Count > 0;
|
||||
|
||||
public int Total
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedTotal == 0)
|
||||
{
|
||||
var aceCount = 0;
|
||||
foreach (var card in _cards)
|
||||
{
|
||||
_cachedTotal += card.Value;
|
||||
if (card.IsAce)
|
||||
aceCount++;
|
||||
}
|
||||
while (_cachedTotal > 21 && aceCount > 0)
|
||||
{
|
||||
_cachedTotal -= 10;
|
||||
aceCount--;
|
||||
}
|
||||
}
|
||||
return _cachedTotal;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBlackjack => Total == 21 && Count == 2;
|
||||
|
||||
public bool IsBusted => Total > 21;
|
||||
}
|
||||
}
|
||||
27
10 Blackjack/csharp/Player.cs
Normal file
27
10 Blackjack/csharp/Player.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Blackjack
|
||||
{
|
||||
public class Player
|
||||
{
|
||||
public Player(int index)
|
||||
{
|
||||
Index = index;
|
||||
Name = (index + 1).ToString();
|
||||
Hand = new Hand();
|
||||
SecondHand = new Hand();
|
||||
}
|
||||
|
||||
public int Index { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Hand Hand { get; private set; }
|
||||
|
||||
public Hand SecondHand { get; private set;}
|
||||
|
||||
public int RoundBet { get; set; }
|
||||
|
||||
public int RoundWinnings { get; set; }
|
||||
|
||||
public int TotalWinnings { get; set; }
|
||||
}
|
||||
}
|
||||
41
10 Blackjack/csharp/Program.cs
Normal file
41
10 Blackjack/csharp/Program.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Blackjack
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("{0}BLACK JACK", new string(' ', 31));
|
||||
Console.WriteLine("{0}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", new string(' ', 15));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
|
||||
OfferInstructions();
|
||||
|
||||
var numberOfPlayers = Prompt.ForInteger("Number of players?", 1, 6);
|
||||
var game = new Game(numberOfPlayers);
|
||||
game.PlayGame();
|
||||
}
|
||||
|
||||
private static void OfferInstructions()
|
||||
{
|
||||
if (!Prompt.ForYesNo("Do you want instructions?"))
|
||||
return;
|
||||
|
||||
Console.WriteLine("This is the game of 21. As many as 7 players may play the");
|
||||
Console.WriteLine("game. On each deal, bets will be asked for, and the");
|
||||
Console.WriteLine("players' bets should be typed in. The cards will then be");
|
||||
Console.WriteLine("dealt, and each player in turn plays his hand. The");
|
||||
Console.WriteLine("first response should be either 'D', indicating that the");
|
||||
Console.WriteLine("player is doubling down, 'S', indicating that he is");
|
||||
Console.WriteLine("standing, 'H', indicating he wants another card, or '/',");
|
||||
Console.WriteLine("indicating that he wants to split his cards. After the");
|
||||
Console.WriteLine("initial response, all further responses should be 's' or");
|
||||
Console.WriteLine("'H', unless the cards were split, in which case doubling");
|
||||
Console.WriteLine("down is again permitted. In order to collect for");
|
||||
Console.WriteLine("Blackjack, the initial response should be 'S'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
10 Blackjack/csharp/Prompt.cs
Normal file
58
10 Blackjack/csharp/Prompt.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
|
||||
namespace Blackjack
|
||||
{
|
||||
public static class Prompt
|
||||
{
|
||||
public static bool ForYesNo(string prompt)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
Console.Write("{0} ", prompt);
|
||||
var input = Console.ReadLine();
|
||||
if (input.StartsWith("y", StringComparison.InvariantCultureIgnoreCase))
|
||||
return true;
|
||||
if (input.StartsWith("n", StringComparison.InvariantCultureIgnoreCase))
|
||||
return false;
|
||||
WriteNotUnderstood();
|
||||
}
|
||||
}
|
||||
|
||||
public static int ForInteger(string prompt, int minimum = 1, int maximum = int.MaxValue)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Console.Write("{0} ", prompt);
|
||||
if (!int.TryParse(Console.ReadLine(), out var number))
|
||||
WriteNotUnderstood();
|
||||
else if (number < minimum || number > maximum)
|
||||
Console.WriteLine("Sorry, I need a number between {0} and {1}.", minimum, maximum);
|
||||
else
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ForCommandCharacter(string prompt, string allowedCharacters)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Console.Write("{0} ", prompt);
|
||||
var input = Console.ReadLine();
|
||||
if (input.Length > 0)
|
||||
{
|
||||
var character = input.Substring(0, 1);
|
||||
var characterIndex = allowedCharacters.IndexOf(character, StringComparison.InvariantCultureIgnoreCase);
|
||||
if (characterIndex != -1)
|
||||
return allowedCharacters.Substring(characterIndex, 1);
|
||||
}
|
||||
|
||||
Console.WriteLine("Type one of {0} please", String.Join(", ", allowedCharacters.ToCharArray()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteNotUnderstood()
|
||||
{
|
||||
Console.WriteLine("Sorry, I didn't understand.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user