From aa346ee36c94f7823d2de008c8b550ac0089fe3e Mon Sep 17 00:00:00 2001
From: Jesse McDowell <384601+jessemcdowell@users.noreply.github.com>
Date: Sat, 20 Feb 2021 21:56:58 -0800
Subject: [PATCH] Implement Blackjack (game 10) in C#
---
10 Blackjack/csharp/Blackjack.csproj | 9 ++
10 Blackjack/csharp/Card.cs | 32 ++++
10 Blackjack/csharp/Deck.cs | 56 +++++++
10 Blackjack/csharp/Game.cs | 232 +++++++++++++++++++++++++++
10 Blackjack/csharp/Hand.cs | 67 ++++++++
10 Blackjack/csharp/Player.cs | 27 ++++
10 Blackjack/csharp/Program.cs | 41 +++++
10 Blackjack/csharp/Prompt.cs | 58 +++++++
8 files changed, 522 insertions(+)
create mode 100644 10 Blackjack/csharp/Blackjack.csproj
create mode 100644 10 Blackjack/csharp/Card.cs
create mode 100644 10 Blackjack/csharp/Deck.cs
create mode 100644 10 Blackjack/csharp/Game.cs
create mode 100644 10 Blackjack/csharp/Hand.cs
create mode 100644 10 Blackjack/csharp/Player.cs
create mode 100644 10 Blackjack/csharp/Program.cs
create mode 100644 10 Blackjack/csharp/Prompt.cs
diff --git a/10 Blackjack/csharp/Blackjack.csproj b/10 Blackjack/csharp/Blackjack.csproj
new file mode 100644
index 00000000..796a6731
--- /dev/null
+++ b/10 Blackjack/csharp/Blackjack.csproj
@@ -0,0 +1,9 @@
+
+
+
+ Exe
+ Blackjack
+ net5.0
+
+
+
diff --git a/10 Blackjack/csharp/Card.cs b/10 Blackjack/csharp/Card.cs
new file mode 100644
index 00000000..a2b6b6bd
--- /dev/null
+++ b/10 Blackjack/csharp/Card.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/10 Blackjack/csharp/Deck.cs b/10 Blackjack/csharp/Deck.cs
new file mode 100644
index 00000000..21b1e9ef
--- /dev/null
+++ b/10 Blackjack/csharp/Deck.cs
@@ -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 _cards = new List(52);
+ private readonly List _discards = new List(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 cards)
+ {
+ _discards.AddRange(cards);
+ }
+ }
+}
diff --git a/10 Blackjack/csharp/Game.cs b/10 Blackjack/csharp/Game.cs
new file mode 100644
index 00000000..7bb16350
--- /dev/null
+++ b/10 Blackjack/csharp/Game.cs
@@ -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);
+ }
+ }
+}
diff --git a/10 Blackjack/csharp/Hand.cs b/10 Blackjack/csharp/Hand.cs
new file mode 100644
index 00000000..49b945bb
--- /dev/null
+++ b/10 Blackjack/csharp/Hand.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+
+namespace Blackjack
+{
+ public class Hand
+ {
+ private readonly List _cards = new List(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 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;
+ }
+}
diff --git a/10 Blackjack/csharp/Player.cs b/10 Blackjack/csharp/Player.cs
new file mode 100644
index 00000000..58664f1d
--- /dev/null
+++ b/10 Blackjack/csharp/Player.cs
@@ -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; }
+ }
+}
diff --git a/10 Blackjack/csharp/Program.cs b/10 Blackjack/csharp/Program.cs
new file mode 100644
index 00000000..dac7da86
--- /dev/null
+++ b/10 Blackjack/csharp/Program.cs
@@ -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'.");
+ }
+ }
+}
diff --git a/10 Blackjack/csharp/Prompt.cs b/10 Blackjack/csharp/Prompt.cs
new file mode 100644
index 00000000..9f798965
--- /dev/null
+++ b/10 Blackjack/csharp/Prompt.cs
@@ -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.");
+ }
+ }
+}