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."); + } + } +}