2
\$\begingroup\$

This is the second pass on this.
pass one

A single player versus a dealer. It does not perform bets - it only track wins and losses and has no splitting. Other than that I tried to get it all in.

I take it most people know Blackjack. Number are the number, face cards are 10, and ace is 1 or 11. Player has no rules about hitting (take a card). Dealer has to hit to 17. Closest to 21 without going over wins. Dealer wins a tie.

class Program
{
    static void Main(string[] args)
    {
        BlackJack blackJack = new BlackJack();
        blackJack.PlayGame();
    }
    public class Card
    {
        public enum CardRank { Two = 0, Three = 1, Four = 2, Five = 3, Six = 4, Seven = 5, Eight = 6, Nine = 7, Ten = 8, Jack = 9, Queen = 10, King = 11, Ace = 12 }
        public enum CardSuit { Spade = 0, Club = 1, Heart = 2, Diamond = 3 }
        private int i;

        public CardRank Rank { get { return (CardRank)(i % 13); } }
        public CardSuit Suit { get { return (CardSuit)(i / 13); } }
        public int Value
        {
            get
            {
                int value;
                if (i % 13 == 12)
                {
                    value = 11;
                }
                else
                {
                    value = i % 13 + 2;
                    if (value > 10)
                    {
                        value = 10;
                    }
                }
                return value;
            }
        }

        public override string ToString() { return $"{Rank} {Suit}"; }

        public Card(int I)
        {
            i = I;
        }
    }
    public abstract class Seat
    {
        public List<Card> Cards { get; private set; } = new List<Card>(); 
        public bool Busted { get { return Total > 21; } }
        public bool HasBlackJack { get { return (Total == 21 && Cards.Count == 2); } }
        public int Total
        {
            get
            {
                int total = 0;
                int aceCount = 0;
                foreach (Card c in Cards)
                {
                    total += c.Value;
                    if (c.Rank == Card.CardRank.Ace)  
                    {
                        aceCount++;
                    }
                }
                for (int i = 0; i < aceCount && total > 21; i++)
                {
                    total -= 10;
                }
                return total;
            }
        }
        public abstract bool CanHit { get; }

        public virtual string DisplayCards
        {
            get
            {
                string cards = string.Join(", ", Cards);
                return $"{cards} {Total}";
            }
        }

        public void HitMe(Card C)
        {
            Cards.Add(C);
        }          
    }
    public class Dealer : Seat
    {
        public override bool CanHit { get { return Total <= 17; } }
        public override string DisplayCards
        {
            get
            {
                if(Cards.Count == 2  & !HasBlackJack)
                {
                    return $"{Cards[0].ToString()} {Cards[0].Value}";
                }
                else
                {
                    return base.DisplayCards;
                }
            }
        }
    }
    public class Player : Seat
    {
        public override bool CanHit { get { return Total < 21; } }
        public int Wins { get; set; } = 0;
        public int Loss { get; set; } = 0;
        public string WinLoss { get { return $"Win {Wins} Loss {Loss}"; } }
    }
    public class BlackJack
    {
        private Random rand = new Random();
        private List<Card> Deck;
        private int deckPos = -1;
        private Dealer dealer = new Dealer();
        private Player player = new Player();

        private void Shuffle()
        {
            deckPos = -1;
            int swap;
            Card temp;
            for (int i = Deck.Count - 1; i > 0; i--)
            {
                swap = rand.Next(i + 1);  //.net rand is not inclusive
                if (swap != i)
                {
                    temp = Deck[i];
                    Deck[i] = Deck[swap];
                    Deck[swap] = temp;
                }
            }
        }
        private Card Pop()
        {
            deckPos++;
            return (Deck[deckPos]);
        }
        private Card Peek()
        {
            return (Deck[deckPos + 1]);
        }
        private void PlayHand()
        {
            if (deckPos > 30)
            {
                Shuffle();
            }

            dealer.Cards.Clear();
            player.Cards.Clear();

            Console.WriteLine();
            Console.WriteLine();

            Card card = Pop();
            dealer.HitMe(card);
            dealer.HitMe(Pop());
            Console.WriteLine("Dealer");
            Console.WriteLine(dealer.DisplayCards);
            if (dealer.HasBlackJack)
            {
                Console.WriteLine("Dealer has blackjack");
                player.Loss++;
            }
            else
            {
                Console.WriteLine("Player H to hit S to Stay");
                player.HitMe(Pop());
                player.HitMe(Pop());
                Console.WriteLine(player.DisplayCards);
                if (!player.HasBlackJack)
                {
                    while (true)
                    {
                        string p = Console.ReadLine();
                        if (p == "H" || p == "h")
                        {
                            if (player.CanHit)
                            {
                                player.HitMe(Pop());
                                Console.WriteLine(player.DisplayCards);
                                if (player.Busted || player.Total == 21)
                                {
                                    break;
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                        else if (p == "S" || p == "s")
                        {
                            break;
                        }
                    }
                }
                if (player.Busted)
                {
                    Console.WriteLine("Player busted out dealer and wins");
                    player.Loss++;
                }
                else if (player.HasBlackJack)
                {
                    player.Wins++;
                    Console.WriteLine("Player has Blackjack and wins");
                }
                else
                {
                    Console.WriteLine("Dealer");
                    while (dealer.CanHit)
                    {
                        dealer.HitMe(Pop());
                        Console.WriteLine(dealer.DisplayCards);
                        if (dealer.Busted)
                        {
                            break;
                        }
                    }                      
                    if (dealer.Busted)
                    {
                        Console.WriteLine("Dealer busted out and player wins");
                        player.Wins++;
                    }
                    else if (dealer.Total >= player.Total)
                    {
                        Console.WriteLine("Dealer wins");
                        player.Loss++;
                    }
                    else
                    {
                        Console.WriteLine("Player wins");
                        player.Wins++;
                    }
                }
            }
            Console.WriteLine(player.WinLoss);
        }
        public void PlayGame()
        {           
            while (true)
            {
                Console.WriteLine("C to continue Q to quit");
                string p = Console.ReadLine();
                if (p == "C" || p == "c")
                {
                    PlayHand();
                }
                else if (p == "Q" || p == "q")
                {
                    Console.WriteLine("Bye");
                    break;
                }
            }
        }

        public BlackJack()
        {
            Deck = new List<Card>();
            //deck will always have 52 cards  
            //card constuctor counts on that
            for (int i = 0; i < 52; i++)
            {
                Card card = new Card(i);
                Console.WriteLine(card);
                Deck.Add(card);
            }
            Shuffle();
            Console.WriteLine();               
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

You didn't move every class to separate file. You didn't define all your magic numbers like 21 and 52 as constants.

Also there are blocks of code that can be significantly reduced. For example this code

if (!player.HasBlackJack)
{
    while (true)
    {
        string p = Console.ReadLine();
        if (p == "H" || p == "h")
        {
            if (player.CanHit)
            {
                player.HitMe(Pop());
                Console.WriteLine(player.DisplayCards);
                if (player.Busted || player.Total == 21)
                {
                    break;
                }
            }
            else
            {
                break;
            }
        }
        else if (p == "S" || p == "s")
        {
            break;
        }
    }
}

can be rewritten as

string command = null;
while ((command = Console.ReadLine().ToLower()) != StayCommand /* const for "s" */ &&
       !player.HasBlackJack &&
       player.CanHit)
{
    if (command != HitCommand /* const for "h" */)
        continue;

    player.HitMe(Pop());
    Console.WriteLine(player.DisplayCards);
    if (player.Busted || player.Total == BlackjackScore /* const for 21 */)
        break;
}

In these lines

return (Deck[deckPos]);
return (Deck[deckPos + 1]);

parentheses are useless, they just add noise to code.

Instead of this

public override bool CanHit { get { return Total <= 17; } }
public string WinLoss { get { return $"Win {Wins} Loss {Loss}"; } }

you can write

public override bool CanHit => Total <= 17;
public string WinLoss => $"Win {Wins} Loss {Loss}";

This code

if(Cards.Count == 2  & !HasBlackJack)
{
    return $"{Cards[0].ToString()} {Cards[0].Value}";
}
else
{
    return base.DisplayCards;
}

can be replaced with

return Cards.Count == BlackjackCardsCount /* const for 2 */  & !HasBlackJack
    ? $"{Cards[0]} {Cards[0].Value}"
    : base.DisplayCards;
\$\endgroup\$
  • \$\begingroup\$ Thanks good feedback. I don't consider 52 and 21 a magic numbers. Cards in a deck and score for blackjack are not going to change. \$\endgroup\$ – paparazzo Jul 20 '17 at 7:42
  • 1
    \$\begingroup\$ Yes and they should be moved to constants. From Wiki: magic number are unique values with unexplained meaning or multiple occurrences which could (preferably) be replaced with named constants. Getting rid of magic constants is the basics of programming. It improves readability and maintainability. \$\endgroup\$ – Maxim Jul 20 '17 at 7:55
  • \$\begingroup\$ Ok I agree they could (preferably) be replaced \$\endgroup\$ – paparazzo Jul 20 '17 at 7:58
1
\$\begingroup\$

I know that the purpose of playing is to get 21 and beat the dealer, but something that you might want to consider is that a player can hit on 21 and take a loss against the dealer in real life, depending on where they are playing (rules can vary by establishment and locale as some gambling establishments may not allow you to hit on 21 because you may be taking a higher card for the next player and allowing them to win).

so your code:

public class Player : Seat
{
    public override bool CanHit { get { return Total < 21; } }
    public int Wins { get; set; } = 0;
    public int Loss { get; set; } = 0;
    public string WinLoss { get { return $"Win {Wins} Loss {Loss}"; } }
}

where the CanHit {get {return Total < 21;}} should also be a less than or equal to situation. unless the establishment has a rule against it. most places allow for it because they would let someone lose money if they want.

Some games of 21 have a dealer rule for hard/soft 17's. Meaning that a dealer may have to hit when they have a soft 17 which is normally a 6 and an Ace because if they hit and get a 10 it is still 17. (can also be two 3's and an Ace) This is a little deeper into the game and may be something for the next iteration, and will probably change the way that you calculate your Total, with Aces, for the Dealer Seat.

Keep in mind also that some games extend the Hard/Soft rule to more than just 17. some places might force the dealer to hit on any number that is considered soft up to 21.

All this depends on the rules that you want to have for your game.

\$\endgroup\$
  • \$\begingroup\$ Never seen a game that let you hit on 21. I have seen dealer can hit on a soft 17. \$\endgroup\$ – paparazzo Apr 19 '18 at 18:56
  • \$\begingroup\$ it's been a while since I played. but I think a dealer will let you, if you really want to. they won't stop you from losing \$\endgroup\$ – Malachi Apr 19 '18 at 18:57
  • \$\begingroup\$ A reason a casino would not do that. Let say a cheat is on the game - they somehow know the cards. A low better may take a card to push the right card to accomplice(s) and or dealer. This was a scam used by that team that was a book and movie. They would get a peak at the bottom card and then cut to know where that card was. Push that card to the last position or dealer. \$\endgroup\$ – paparazzo Apr 19 '18 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.