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