diff --git a/PuzzlePlayer/BlackJack.cs b/PuzzlePlayer/BlackJack.cs index c9dc833fe0ed4134e1c4c92f3050f225d3b30489..d0235b75c3b9a9bdbd505b142f309ce232097151 100644 --- a/PuzzlePlayer/BlackJack.cs +++ b/PuzzlePlayer/BlackJack.cs @@ -1,63 +1,101 @@ -using Microsoft.VisualBasic.Devices; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; +using System.Drawing.Drawing2D; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace PuzzlePlayer_Namespace { - enum BJSTATE // the different states the game can be in + enum BJRESULTS // the different results after a round { - Setup, // choose how much money the player deploys - Game, // play the game - Result, // see te result and then restart + Lost, + Won, + Blackjack, // player scored 21 points + Draw // same score } struct Card { - string name; // for drawing the card, needs to be in format num_of_kind - int value; // value of the card - int value2; // some cards have two values + 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 { - BJSTATE state; + 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; + Button RestartButton; - List<Card> cards; + 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 object Int { get; private set; } - public BlackJack() { - state = BJSTATE.Setup; + result = BJRESULTS.Lost; //init WindowState = FormWindowState.Maximized; + DoubleBuffered = true; BackColor = Color.DarkGreen; - Paint += BlackJack_Paint; + + 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; @@ -65,23 +103,25 @@ namespace PuzzlePlayer_Namespace private void SetupUI() { - //Setup + //setup #region SetupPanel SetupPanel = new Panel(); - SetupPanel.Size = new Size(screen.Width/2,screen.Height); - SetupPanel.Location = new Point(screen.Width / 4, 0); + 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 - for(int j = 1; j <= 2; j++) - for(int i = 0; i < 4; i++) + 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 / 4, SetupPanel.Height / 6); // /2 - b.Location = new Point(i * b.Size.Width, SetupPanel.Height - b.Size.Height*j); + 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]; @@ -89,7 +129,7 @@ namespace PuzzlePlayer_Namespace b.BackColor = chip.Item3; b.Font = new Font(BJFont, b.Size.Width / 5); - b.Click += moneyButtonClick; + b.Click += MoneyButtonClick; SetupPanel.Controls.Add(b); goofyIndex++; @@ -102,44 +142,65 @@ namespace PuzzlePlayer_Namespace 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 += (object o, EventArgs e) => - { - if (deployedMoney == 0) - return; - SetupPanel.Hide(); - GamePanel.Show(); - state = BJSTATE.Game; - }; + dealButton.Click += DealButtonClick; SetupPanel.Controls.Add(dealButton); Controls.Add(SetupPanel); #endregion - //Game + //game #region GamePanel GamePanel = new Panel(); GamePanel.Paint += Game_Paint; - GamePanel.Size = new Size((int)(screen.Width / 1.5), screen.Height); - GamePanel.Location = new Point((screen.Width - GamePanel.Size.Width)/2, 0); - GamePanel.BackColor = Color.Red; //DEBUG - - + 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 - - //Result - #region ResultPanel - ResultPanel = new Panel(); - ResultPanel.Hide(); //Hide until the result state is reached - Controls.Add(ResultPanel); - #endregion } - - - - private void moneyButtonClick(object sender, EventArgs e) + //button logic + private void MoneyButtonClick(object sender, EventArgs e) { Button b = (Button) sender; (int, string, Color) chip = chipInfo[int.Parse(b.Name)]; @@ -152,36 +213,135 @@ namespace PuzzlePlayer_Namespace Invalidate(true); } - private void BlackJack_Paint(object sender, PaintEventArgs e) + private void DealButtonClick(object sender, EventArgs e) { - Graphics g = e.Graphics; + 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 - //default draw - Font moneyFont = new Font(BJFont, screen.Width/50, FontStyle.Bold); - g.DrawString("Money: " + money.ToString(), moneyFont, Brushes.Black, new PointF(0,0)); + GamePanel.Show(); + result = BJRESULTS.Won; + } + + private void RestartButtonClick(object sender, EventArgs e) //reset everything + { + result = BJRESULTS.Lost; + deployedMoney = 0; + roundFinished = false; + resultReady = false; - /* - switch(state) // paint according to the current state + 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 BJSTATE.Setup: - //Setup_Paint(g); + case BJRESULTS.Lost: break; - case BJSTATE.Game: - //Game_Paint(g); + case BJRESULTS.Won: + case BJRESULTS.Blackjack: + money += deployedMoney * 2; break; - case BJSTATE.Result: - //Result_Paint(g); + 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 - string s = "Deployd Money: " + deployedMoney.ToString(); - Font deplMoneyFont = new Font(BJFont, SetupPanel.Width / 20, FontStyle.Bold); + // 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); @@ -190,14 +350,108 @@ namespace PuzzlePlayer_Namespace private void Game_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; - DrawCard(g, new Point(0, 0), 200, "9_of_spades"); + 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 @@ -226,7 +480,55 @@ namespace PuzzlePlayer_Namespace 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 * 1.4)); //img is 500x726 + 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; } } } diff --git a/PuzzlePlayer/PuzzlePlayer.csproj b/PuzzlePlayer/PuzzlePlayer.csproj index 761b306d36a073e000909f7e90e49e2e9c23ab57..ff26550e41b347c6dd217155ec82fde08d1972fe 100644 --- a/PuzzlePlayer/PuzzlePlayer.csproj +++ b/PuzzlePlayer/PuzzlePlayer.csproj @@ -53,6 +53,7 @@ <None Remove="Resources\BlackJack\cards\ace_of_diamonds.png" /> <None Remove="Resources\BlackJack\cards\ace_of_hearts.png" /> <None Remove="Resources\BlackJack\cards\ace_of_spades.png" /> + <None Remove="Resources\BlackJack\cards\back.png" /> <None Remove="Resources\BlackJack\cards\jack_of_clubs.png" /> <None Remove="Resources\BlackJack\cards\jack_of_diamonds.png" /> <None Remove="Resources\BlackJack\cards\jack_of_hearts.png" /> @@ -115,6 +116,7 @@ <EmbeddedResource Include="Resources\BlackJack\cards\ace_of_diamonds.png" /> <EmbeddedResource Include="Resources\BlackJack\cards\ace_of_hearts.png" /> <EmbeddedResource Include="Resources\BlackJack\cards\ace_of_spades.png" /> + <EmbeddedResource Include="Resources\BlackJack\cards\back.png" /> <EmbeddedResource Include="Resources\BlackJack\cards\jack_of_clubs.png" /> <EmbeddedResource Include="Resources\BlackJack\cards\jack_of_diamonds.png" /> <EmbeddedResource Include="Resources\BlackJack\cards\jack_of_hearts.png" /> diff --git a/PuzzlePlayer/Resources/BlackJack/cards/back.png b/PuzzlePlayer/Resources/BlackJack/cards/back.png new file mode 100644 index 0000000000000000000000000000000000000000..07fb311e50ec4b3110dfea36947a89eb90e19138 Binary files /dev/null and b/PuzzlePlayer/Resources/BlackJack/cards/back.png differ