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; Button RestartButton; 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; money = ReadMoney(); cardsLeft = getAllCards(); SetupUI(); } List<Card> getAllCards() // returns a list with all the 52 classic cards { 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") // an ace is worth either 1 or 11, the logic for this is handled in GetValue() 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 = HitButton.Size; StandButton.Location = new Point(GamePanel.Width - GamePanel.Width/4 - StandButton.Width/2, HitButton.Location.Y); StandButton.Font = new Font(BJFont, StandButton.Width / 6,FontStyle.Bold); GamePanel.Controls.Add(StandButton); RestartButton = new Button(); RestartButton.Text = "Next Round"; RestartButton.Click += RestartButtonClick; RestartButton.BackColor = Color.DarkCyan; RestartButton.Size = HitButton.Size; RestartButton.Location = new Point(GamePanel.Width / 2 - RestartButton.Width / 2, HitButton.Location.Y); RestartButton.Font = new Font(BJFont, RestartButton.Width / 6, FontStyle.Bold); RestartButton.Hide(); // hide until game state is reached GamePanel.Controls.Add(RestartButton); GamePanel.Hide(); //Hide until the game state is reached Controls.Add(GamePanel); #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; } private void RestartButtonClick(object sender, EventArgs e) //reset everything { result = BJRESULTS.Lost; deployedMoney = 0; roundFinished = false; resultReady = false; playerCards.Clear(); dealerCards.Clear(); RestartButton.Hide(); GamePanel.Hide(); SetupPanel.Show(); } // end of the game, fancy async methode private async void EndGame() { if(roundFinished) return; //only run it once 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 switch (result) { case BJRESULTS.Lost: break; case BJRESULTS.Won: case BJRESULTS.Blackjack: money += deployedMoney * 2; break; case BJRESULTS.Draw: money += deployedMoney; break; } RestartButton.Show(); 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()); // the dealer must hit until it has 17 or more points // if the dealer has 17 points and a ace in hand then the dealer must also hit (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) || DealerHasSoft17()) { 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; }); } private bool DealerHasSoft17() // fancy blackjack rule { List<Card> cards = getVisableDealerCards(); if (getValue(cards) != 17) return false; foreach (Card card in cards) { if(card.value == 11) // check if the dealer has a ace return true; } return false; } // paint events for all the different states 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 = $"Playing for ${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 = ""; string moneyText = ""; Color c = Color.Red; Font resFont = new Font(BJFont, screen.Width / 10,FontStyle.Bold); Font moneyFont = new Font(BJFont, screen.Width / 20, FontStyle.Underline); switch (result) { case BJRESULTS.Lost: resultText = "YOU LOSE"; moneyText = $"You lost -${deployedMoney}"; break; case BJRESULTS.Won: resultText = "YOU WINNN"; moneyText = $"You won ${deployedMoney * 2}"; c = Color.LimeGreen; break; case BJRESULTS.Blackjack: resultText = "!BLACKJACK!"; moneyText = $"You won ${deployedMoney * 2}"; c = Color.Gold; break; case BJRESULTS.Draw: resultText = "Draw"; moneyText = $"You got your ${deployedMoney} back"; c = Color.Gray; break; } SizeF resSize = g.MeasureString(resultText, resFont); Point resPos = new Point(GamePanel.Width / 2 - (int)(resSize.Width / 2), GamePanel.Height / 5 - (int)(resSize.Height / 2)); SizeF moneySize = g.MeasureString (moneyText, moneyFont); Point moneyPos = new Point(GamePanel.Width / 2 - (int)(moneySize.Width / 2), GamePanel.Height / 3 - (int)(moneySize.Height /2)); //crazy outline GraphicsPath p = new GraphicsPath(); p.AddString(resultText, BJFont, (int)FontStyle.Bold, g.DpiY * screen.Width / 10 / 72, resPos, new StringFormat()); Pen pen = new Pen(Color.Black, screen.Width / 60); g.DrawPath(pen, p); //actual text SolidBrush brush = new SolidBrush(c); g.DrawString(resultText,resFont,brush, resPos); g.DrawString(moneyText,moneyFont,brush, moneyPos); } // 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 } // methodes for interacting with the cards 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; } } }