Skip to content
Snippets Groups Projects
BlackJack.cs 18.94 KiB
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PuzzlePlayer_Namespace
{
    enum BJRESULTS // the different results after a round
    {
        Lost,
        Won,
        Blackjack,  // player scored 21 points
        Draw        // same score
    }

    struct Card
    {
        public string name;    // for drawing the card, needs to be in format num_of_kind
        public int value;      // value of the card

        public Card(string n, int val)
        {
            name = n;
            value = val;
        }
    }

    internal class BlackJack : Form
    {
        BJRESULTS result;
        bool roundFinished = false;
        bool resultReady = false;
        int money;
        int deployedMoney = 0;
        readonly FontFamily BJFont = FontFamily.GenericSansSerif;
        readonly int defaultFontSize;
        const double cardMultply = 1.4; //double to go from the width of the card to the height
        const int aceValueMax = 11; //the two values of a ace
        const int aceValueMin = 1;
        const int BLACKJACK = 21;   //the value to get blackjack

        Size screen = Screen.PrimaryScreen.WorkingArea.Size;
        
        // visibility depends on game state
        Panel SetupPanel;
        Panel GamePanel;
        Panel ResultPanel;

        List<Card> cardsLeft;
        List<Card> playerCards = new();
        List<(Card,bool)> dealerCards = new(); // boolean is to keep track if the card is visable

        (int,string,Color)[] chipInfo = { (5,"5",Color.Gray), (25, "25", Color.Blue), (50, "50", Color.Green), (100, "100", Color.DarkCyan), (500, "500", Color.Purple), (1000, "1K", Color.Red), (5000, "5K", Color.Orange), (10000, "10K", Color.Gold) };

        public BlackJack()
        {
            result = BJRESULTS.Lost; //init
            WindowState = FormWindowState.Maximized;
            DoubleBuffered = true;
            BackColor = Color.DarkGreen;

            defaultFontSize = screen.Width / 50;

            Paint += BlackJack_Paint;

            money = ReadMoney();
            cardsLeft = getAllCards();

            SetupUI();
        }

        List<Card> getAllCards()
        {
            List<Card> cards = new List<Card>();
            string[] numbers = { "2", "3", "4", "5", "6", "7", "8", "9", "10", "ace", "jack", "king", "queen" };
            string[] kinds = { "clubs", "diamonds", "hearts", "spades" };

            foreach (string kind in kinds)
            {
                foreach (string num in numbers)
                {
                    int val;
                    if (num == "jack" || num == "king" || num == "queen")
                        val = 10;
                    else if (num == "ace") // if it is an ace the player can chose between 1 or 11
                    {
                        val = 11;
                    }
                    else
                        val = int.Parse(num);
                    cards.Add(new Card(num + "_of_" + kind,val));
                }
            }

            cards.Shuffle();
            return cards;
        }

        private int ReadMoney() //TODO read money from local file
        {
            return 1000;
        }

        private void SetupUI()
        {
            //setup
            #region SetupPanel
            SetupPanel = new Panel();
            SetupPanel.Size = screen; //SetupPanel.Size = new Size(screen.Width/2,screen.Height);
            SetupPanel.Location = new Point(0, 0); //SetupPanel.Location = new Point(screen.Width / 4, 0);
            //DefaultPanel.BackColor = Color.Blue; //DEBUG

            SetupPanel.Paint += Setup_Paint;

            //money buttons
            int goofyIndex = 0; //dw
            const int rows = 4;
            const int cols = 2;
            for(int j = 1; j <= cols; j++)
                for(int i = 0; i < rows; i++)
                {
                    Button b = new Button();
                    b.Size = new Size(SetupPanel.Width / 8, SetupPanel.Height / 6); // w/8
                    b.Location = new Point(i * b.Size.Width + rows / 2 * b.Size.Width, SetupPanel.Height - b.Size.Height * j);
                    b.Name = goofyIndex.ToString(); // for getting the index for chipInfo later

                    (int, string, Color) chip = chipInfo[goofyIndex];
                    b.Text = chip.Item2;
                    b.BackColor = chip.Item3;
                    b.Font = new Font(BJFont, b.Size.Width / 5);

                    b.Click += moneyButtonClick;

                    SetupPanel.Controls.Add(b);
                    goofyIndex++;
                }

            // Deal button
            Button dealButton = new Button();
            dealButton.Text = "DEAL";
            dealButton.Size = new Size(SetupPanel.Width/4, SetupPanel.Height / 6);
            dealButton.Location = new Point(SetupPanel.Width /2 - dealButton.Width/2, SetupPanel.Height /4);
            dealButton.Font = new Font(BJFont, dealButton.Size.Width / 5);
            dealButton.BackColor = Color.DodgerBlue;
            dealButton.Click += dealButtonClick;
            SetupPanel.Controls.Add(dealButton);

            Controls.Add(SetupPanel);
            #endregion

            //game
            #region GamePanel
            GamePanel = new Panel();
            GamePanel.Paint += Game_Paint;
            GamePanel.Size = screen; //GamePanel.Size = new Size((int)(screen.Width / 1.5), screen.Height);
            GamePanel.Location = new Point(0, 0); //GamePanel.Location = new Point((screen.Width - GamePanel.Size.Width)/2, 0);
            //GamePanel.BackColor = Color.Red; //DEBUG
            
            //buttons
            Button HitButton = new Button();
            HitButton.Text = "HIT";
            HitButton.Click += (object sender, EventArgs e) =>
            {
                if (roundFinished) // don't let the player draw cards when the round is already finished
                    return;
                playerCards.Add(PopCard());
                Invalidate(true);
                if (getValue(playerCards) >= BLACKJACK) //if it is a bust or blackjack the game is automaticly over
                    EndGame();
            };
            HitButton.BackColor = Color.Red;
            HitButton.Size = new Size(GamePanel.Width/5, GamePanel.Height/5);
            HitButton.Location = new Point(GamePanel.Width/4 - HitButton.Width/2, GamePanel.Height / 2 - HitButton.Size.Height / 2);
            HitButton.Font = new Font(BJFont, HitButton.Size.Width/3, FontStyle.Bold);

            GamePanel.Controls.Add(HitButton);
            // stand button
            Button StandButton = new Button();
            StandButton.Text = "STAND";
            StandButton.Click += (object sender, EventArgs e) => EndGame();
            StandButton.BackColor = Color.Blue;
            StandButton.Size = new Size(GamePanel.Width / 5, GamePanel.Height / 5);
            StandButton.Location = new Point(GamePanel.Width - GamePanel.Width/4 - StandButton.Width/2, GamePanel.Height / 2 - StandButton.Size.Height / 2);
            StandButton.Font = new Font(BJFont, StandButton.Size.Width / 6,FontStyle.Bold);

            GamePanel.Controls.Add(StandButton);

            GamePanel.Hide(); //Hide until the game state is reached
            Controls.Add(GamePanel);
            #endregion

            //result
            #region ResultPanel
            ResultPanel = new Panel();
            ResultPanel.Hide(); //Hide until the result state is reached
            ResultPanel.BackColor = Color.Transparent;
            ResultPanel.Size = screen;

            //ResultPanel.Paint += Result_Paint;

            Controls.Add(ResultPanel);
            #endregion
        }
        //button logic
        private void moneyButtonClick(object sender, EventArgs e)
        {
            Button b = (Button) sender;
            (int, string, Color) chip = chipInfo[int.Parse(b.Name)];

            if(money >= chip.Item1)
            {
                deployedMoney += chip.Item1;
                money -= chip.Item1;
            }
            Invalidate(true);
        }

        private void dealButtonClick(object sender, EventArgs e)
        {
            if (deployedMoney == 0)
                return;
            SetupPanel.Hide();

            playerCards.Add(PopCard()); // give both dealer and player cards
            dealerCards.Add((PopCard(), false)); //hided card
            playerCards.Add(PopCard());
            dealerCards.Add((PopCard(), true));   //visable card

            GamePanel.Show();
            result = BJRESULTS.Won;
        }
        // end of the game, fancy async methode
        private async void EndGame()
        {
            roundFinished = true;

            // check for blackjack or bust (above 21 so player loses)
            int playerScore = getValue(playerCards);
            if (playerScore == BLACKJACK)
                result = BJRESULTS.Blackjack;
            else if (playerScore > BLACKJACK)
                result = BJRESULTS.Lost;
            else // if the player scored below 21 then the dealer must draw cards
                 await DealerAnimation(); // fancy Asynchronous animation function

            resultReady = true;
            Invalidate(true);
        }

        private Task DealerAnimation()
        {
            return Task.Factory.StartNew(() =>
            {

                Thread.Sleep(200); //wait 0.2 sec for the fun
                for (int i = 0; i < dealerCards.Count; i++)  // flip all the cards that where invisable
                    if (!dealerCards[i].Item2)
                        dealerCards[i] = (dealerCards[i].Item1, true);
                Invalidate(true);

                Thread.Sleep(1000); // just a little more
                int playerScore = getValue(playerCards);
                int dealerScore = getValue(getVisableDealerCards());

                // let the dealer hit until it has above 17 points (dealer will hit on soft 17 type shit)
                // also if the dealer has a higher score then the player then the dealer won, so no need to push the luck and draw more cards
                // if the dealer has the same score then it is a draw and the dealer will not draw more cards
                while (dealerScore <= 17 && dealerScore < playerScore)
                {
                    dealerCards.Add((PopCard(), true));
                    dealerScore = getValue(getVisableDealerCards()); // update dealer score
                    Invalidate(true);
                    Thread.Sleep(1000);
                }

                Thread.Sleep(500);

                if (dealerScore > BLACKJACK) // dealer bust
                    result = BJRESULTS.Won;
                else if (dealerScore > playerScore) // dealer has higher score then player
                    result = BJRESULTS.Lost;
                else if (dealerScore == playerScore)
                    result = BJRESULTS.Draw;
                else
                    result = BJRESULTS.Won;

            });
        }

        // paint events for all the different states
        private void BlackJack_Paint(object sender, PaintEventArgs e) //this will always get drawn
        {
            Graphics g = e.Graphics;

            Font moneyFont = new Font(BJFont, defaultFontSize, FontStyle.Bold);
            //g.DrawString("Money: $" + money.ToString(), moneyFont, Brushes.Black, new PointF(0,0));
        }
        private void Setup_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            DrawMoney(g);

            // draw deployd money
            // Might update this to butiful chipss TODO
            string s = "Deployed Money: $" + deployedMoney.ToString();
            Font deplMoneyFont = new Font(BJFont, SetupPanel.Width / 40, FontStyle.Bold);
            SizeF pf = g.MeasureString(s, deplMoneyFont);
            PointF p = new PointF(SetupPanel.Width / 2 - pf.Width / 2, SetupPanel.Height / 2);
            g.DrawString(s, deplMoneyFont, Brushes.Black, p);
        }

        private void Game_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            DrawMoney(g);

            int cardWidth = screen.Width/8; //lucky number
            int cardHeight = (int)(cardWidth * cardMultply);
            // draw cards for player and dealer
            for(int i = 0; i < playerCards.Count; i++)
            {
                int centerOffsetX = GamePanel.Width/2 - playerCards.Count * cardWidth/2; //to get the cards perfectly in the middle

                Point p = new Point(centerOffsetX + i * cardWidth, screen.Height - cardHeight);
                DrawCard(g, p, cardWidth, playerCards[i].name);
            }
            for (int i = 0; i < dealerCards.Count; i++)
            {
                int centerOffsetX = GamePanel.Width / 2 - dealerCards.Count * cardWidth / 2;
                Point p = new Point(centerOffsetX + i * cardWidth, 0);
                if (dealerCards[i].Item2)
                    DrawCard(g, p, cardWidth, dealerCards[i].Item1.name);
                else
                    DrawCard(g, p, cardWidth, "back"); //if not visable draw backside of card
            }

            //draw player and dealer points
            int playerValue = getValue(playerCards);
            int dealerValue = getValue(getVisableDealerCards());
            string playerString = $"Player: {playerValue} points";
            string dealerString = $"Dealer: {dealerValue} points";
            Font f = new Font(BJFont, defaultFontSize,FontStyle.Bold);

            SizeF playerSF = g.MeasureString(playerString, f);
            PointF playerStrPos = new PointF(GamePanel.Width / 2 - playerSF.Width / 2, GamePanel.Height - cardHeight - playerSF.Height);
            g.DrawString(playerString, f, Brushes.Black, playerStrPos);

            SizeF dealerSF = g.MeasureString(dealerString, f);
            PointF dealerStrPos = new PointF(GamePanel.Width / 2 - dealerSF.Width / 2, cardHeight);
            g.DrawString(dealerString, f, Brushes.Black, dealerStrPos);

            //draw deployed money
            string deployedString = $"${deployedMoney}";
            SizeF deployedSF = g.MeasureString(deployedString, f);
            PointF deployedStrPos = new PointF(GamePanel.Width/2 - deployedSF.Width / 2,GamePanel.Height/2 - deployedSF.Height/2);
            g.DrawString(deployedString,f, Brushes.Black, deployedStrPos);


            // draw result text when ready
            if (resultReady)
                Result_Paint(g);
            

        }

        private void DrawMoney(Graphics g)
        {
            Font moneyFont = new Font(BJFont, defaultFontSize, FontStyle.Bold);
            g.DrawString("Money: $" + money.ToString(), moneyFont, Brushes.Black, new PointF(0, 0));
        }

        private void Result_Paint(Graphics g)
        {
            string resultText = "";
            Font f = new Font(BJFont, screen.Width / 10,FontStyle.Bold);

            switch (result)
            {
                case BJRESULTS.Lost:
                    resultText = "YOU LOSE";
                    break;
                case BJRESULTS.Won:
                    resultText = "YOU WINNN";
                    break;
                case BJRESULTS.Blackjack:
                    resultText = "!BLACKJACK!";
                    break;
                case BJRESULTS.Draw:
                    resultText = "draw";
                    break;
            }

            SizeF size = g.MeasureString(resultText, f);
            Point pos = new Point(GamePanel.Width / 2 - (int)(size.Width / 2), GamePanel.Height / 4 - (int)(size.Height / 2));

            GraphicsPath p = new GraphicsPath();
            p.AddString(
                resultText,             // text to draw
                BJFont,  // or any other font family
                (int)FontStyle.Bold,      // font style (bold, italic, etc.)
                g.DpiY * screen.Width / 10 / 72,       // em size
                pos,              // location where to draw text
                new StringFormat());          // set options here (e.g. center alignment)
            Pen pen = new Pen(Color.Black, screen.Width / 50);
            
            g.DrawPath(pen, p);


            
            g.DrawString(resultText,f,Brushes.Red, pos);
        }
        // some draw thingies
        private void DrawChip(Graphics g, Color c, int r, Point pos, string value)
        {
            // Chip
            Brush brush = new SolidBrush(c);
            g.FillEllipse(brush, pos.X - r, pos.Y - r, r * 2, r * 2);

            // White Squares
            int squareSize = r / 2;

            Rectangle rect = new Rectangle(pos.X - squareSize/2, pos.Y - r, squareSize, squareSize);
            g.FillRectangle(Brushes.White, rect);
            rect.Offset(new Point(0, r*2-squareSize));
            g.FillRectangle(Brushes.White, rect);

            rect = new Rectangle(pos.X - r, pos.Y - squareSize/2, squareSize, squareSize);
            g.FillRectangle(Brushes.White, rect);
            rect.Offset(new Point(r * 2 - squareSize, 0));
            g.FillRectangle(Brushes.White, rect);

            //text
            Font f = new Font(BJFont, r/2);
            SizeF fontSize = g.MeasureString(value.ToString(), f);
            g.DrawString(value.ToString(), f, Brushes.Black, new PointF(pos.X-fontSize.Width/2, pos.Y-fontSize.Height/2));
        }

        private void DrawCard(Graphics g, Point pos, int width, string cardName) //cardName needs to be in format: num_of_kind
        {
            Image img = SettingForm.GetEmbeddedImage("BlackJack.cards." + cardName + ".png");
            g.DrawImage(img, pos.X, pos.Y, width, (float)(width * cardMultply)); //img is 500x726
        }
        // interact with the cards methodes
        private Card PopCard()
        {
            if (cardsLeft.Count == 0)
                cardsLeft = getAllCards(); //reshuffle, In the real game the reshuffle is done after the round has ended but i am lazy :)
            Card c = cardsLeft[cardsLeft.Count - 1];
            cardsLeft.RemoveAt(cardsLeft.Count - 1);
            return c;
        }

        private int getValue(List<Card> cardList)
        {
            int result = 0;
            int aceCount = 0;

            foreach(Card c in cardList)
            {
                if (c.name.StartsWith("ace"))
                    aceCount++;
                else
                    result += c.value;
            }

            if(aceCount > 0)
            {
                for (int i = 0; i < aceCount; i++)          //check for each ace if adding 11 will make them go over 21. if not then we can add 21 else we will add 1
                {
                    if (result + aceValueMax > BLACKJACK)
                        result += aceValueMin;
                    else
                        result += aceValueMax;
                }
            }

            return result;
        }

        private List<Card> getVisableDealerCards()
        {
            List<Card> cards = new List<Card>();

            foreach ((Card c,bool b) in dealerCards)
                if(b)
                    cards.Add(c);

            return cards;
        }
    }
}