Implement Blackjack (game 10) in C#

This commit is contained in:
Jesse McDowell
2021-02-20 21:56:58 -08:00
parent f24b72e73c
commit aa346ee36c
8 changed files with 522 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>Blackjack</RootNamespace>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

View 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;
}
}
}
}

View 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
View 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);
}
}
}

View 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;
}
}

View 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; }
}
}

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

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