# Graph Poker Hand Distributions

Last time I showed how to Calculate Poker hand probabilities with code that showed how often a particular hand occurred, such as a Pair or a Full House.
Now that we can calculate these probabilities, lets graph them.

You’ll need to add references to some assemblies: System.Windows.Forms, System.Windows.Forms.DataVisualization, WindowsFormsIntegration

First we’ll accumulate the results of dealing 5 card hands into a Dictionary<string, int>, where entries might be “Pair” = 223, “Straight”=3

Windows Forms has a nice graphing assembly System.Windows.Forms.DataVisualization, which calls a graph a “Chart.
Because the charting code is written for Windows Forms, and the Poker code uses Windows Presentation Foundation, we need to host the Windows Forms chart as a child of a WindowsFormsHost control.
We’ll create a ChartArea and add it to the chart.
Set the chart DataSource property to the dictionary. We’ll add a Series to the chart, with the type “Bar” for a bar chart. (Try changing it to a Line chart for fun)

Here’s a screenshot of 14 million deals, indicating visually that a Pair occurs just a little less than “Nothing” (42% vs 50% of the deals).

<code>

``````using System;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Windows.Forms.DataVisualization.Charting;
using System.Windows.Forms.Integration;
using System.Threading.Tasks;
using System.Text;

/*
File->new->Project->C#->WPF App "Poker"
download cards.dll from https://onedrive.live.com/redir?resid=D69F3552CEFC21!74629&authkey=!AGaX84aRcmB1fB4&ithint=file%2cDll
Solution->Add Existing Item Cards.dll (Properties: Copy to Output Directory=Copy If Newer)
Add Project->Add Reference to System.Drawing, WindowsFormsIntegration, System.Windows.Forms, System.Windows.Forms.DataVisualizetion
*
* * */
namespace Poker
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public int NumDealsPerGroup { get; set; } = 1000;
int hghtCard = 100;
int wdthCard = 80;
public MainWindow()
{
InitializeComponent();
Width = 1100;
Height = 800;
WindowState = WindowState.Maximized;
Title = "CardDist";
this.Loaded += MainWindow_Loaded;
}
void AddStatusMsg(string msg, params object[] args)
{
if (_txtStatus != null)
{
// we want to read the threadid
//and time immediately on current thread
var dt = string.Format("[{0}],{1,2},",
DateTime.Now.ToString("hh:mm:ss:fff"),
Thread.CurrentThread.ManagedThreadId);
_txtStatus.Dispatcher.BeginInvoke(
new Action(() =>
{
// this action executes on main thread
var str = string.Format(dt + msg + "\r\n", args);
_txtStatus.AppendText(str);
_txtStatus.ScrollToEnd();
}));
}
}

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
try
{
this.DataContext = this;
var sp = new StackPanel() { Orientation = Orientation.Vertical };
sp.Children.Add(new Label() { Content = "Card Dealing Program." });
var spControls = new StackPanel() { Orientation = Orientation.Horizontal };
sp.Children.Add(spControls);
spControls.Children.Add(new Label() { Content = "nDeals" });
var txtnDeals = new TextBox()
{
Width = 100,
ToolTip = ""
};
txtnDeals.SetBinding(TextBox.TextProperty, nameof(NumDealsPerGroup));
spControls.Children.Add(txtnDeals);

_txtStatus = new TextBox()
{
IsReadOnly = true,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
IsUndoEnabled = false,
FontFamily = new FontFamily("Courier New"),
FontSize = 10,
Height = 200,
MaxHeight = 200,
HorizontalContentAlignment = HorizontalAlignment.Left
};

var dictHandValues = new Dictionary<string, int>(); // Hand value, cnt. Like "Pair" = 4
foreach (var k in Enum.GetValues(typeof(PokerHand.HandValues)))
{
dictHandValues[k.ToString()] = 0;
}
var chart = new Chart();
//                chart.Height = 500;
chart.Width = 200;
chart.Dock = System.Windows.Forms.DockStyle.Fill;
var chartArea = new ChartArea("ChartArea");
chart.ChartAreas.Add(chartArea);
var wfh = new WindowsFormsHost();
wfh.Height = 600;
wfh.Child = chart;
sp.Children.Add(wfh);

var dictPairDist = new Dictionary<int, int>(); // #hands that are a Pair, count
var series1 = new Series();
chart.Series.Add(series1);

/* add a "/" at the beginning of this line to use alternate code
// insert code here later
/*/
chart.DataSource = dictHandValues;
series1.ChartType = SeriesChartType.Bar;
series1.XValueMember = "Key";
series1.YValueMembers = "Value";
chartArea.AxisX.Interval = 1;
chartArea.AxisY.LabelStyle.Format = "#,#";
//*/

var canvas = new Canvas() { Height = hghtCard + 3 };
sp.Children.Add(canvas);
sp.Children.Add(_txtStatus);

this.Content = sp;

var deck = new Card[52];
for (var suit = 0; suit < 4; suit++)
{
for (var denom = 0; denom < 13; denom++)
{
var card = CardDeck.GetCard((CardDeck.Suit)suit, denom);
deck[suit * 13 + denom] = card;
}
}
for (int i = 0; i < PokerHand.HandSize; i++)
{
var img = new Image()
{
Height = hghtCard
};
// add it to the canvas
canvas.Children.Add(img);
// set it's position on the canvas
Canvas.SetLeft(img, i * wdthCard);
//                    Canvas.SetTop(img, suit * (1 + hghtCard));
}
var numHands = 0;
var rand = new Random(1);
PokerHand hand = null;
Action UpdateUI = () =>
{
series1.ToolTip = \$"Num deals = {numHands:n0} {_txtStatus.Text}";
// draw the cards
for (int i = 0; i < PokerHand.HandSize; i++)
{
var img = (Image)canvas.Children[i];
img.Source = hand.Cards[i].bmpSource;
}
chart.DataBind();
var sb = new StringBuilder();
foreach (var kvp in dictHandValues)
{
sb.Append(\$" {kvp.Key}={kvp.Value * 100.0 / numHands,9:f6}");
}
_txtStatus.Text = \$@"{hand.PokerValue().ToString(),-15}";
_txtStatus.AppendText(\$"\r\nN={numHands:n0} {sb.ToString()}");
};
Action<int> ShuffleAndDeal = (nDeals) =>
{
int nPairs = 0;
for (int nDeal = 0; nDeal < nDeals; nDeal++)
{
numHands++;

// shuffle
for (int n = 0; n < 52; n++)
{
//get a random number 0-51
var tempNdx = rand.Next(52);
var tmp = deck[tempNdx];
deck[tempNdx] = deck[n];
deck[n] = tmp;
}
//var newdeck = new Card[52];
//for (int i = 0; i < 26; i++)
//{
//    newdeck[2 * i] = deck[i];
//    newdeck[2 * i + 1] = deck[i + 26];
//}

//for (int n = 0; n < 52; n++)
//{
//    deck[n] = newdeck[n];
//}
// deal
hand = new PokerHand(deck.Take(5).ToList());
var val = hand.PokerValue();
if (val == PokerHand.HandValues.Pair)
{
nPairs++;
}
dictHandValues[val.ToString()]++;
}
if (!dictPairDist.ContainsKey(nPairs))
{
dictPairDist[nPairs] = 1;
}
else
{
dictPairDist[nPairs]++;
}
};

var btnGo = new Button()
{
Content = "_Go",
Width = 20
};
spControls.Children.Add(btnGo);
bool fIsGoing = false;
btnGo.Click += async (ob, eb) =>
{
fIsGoing = !fIsGoing;
if (fIsGoing)
{
dictPairDist.Clear();
while (fIsGoing)
{
await Task.Run(() =>
{
ShuffleAndDeal(NumDealsPerGroup);
});
UpdateUI();
}
}
};
this.MouseUp += (om, em) =>
{
if (!fIsGoing)
{
ShuffleAndDeal(1);
UpdateUI();
}
};
btnGo.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, null));
}
catch (Exception ex)
{
this.Content = ex.ToString();
}
}

// http://www.math.hawaii.edu/~ramsey/Probability/PokerHands.html
internal class PokerHand
{
public static int HandSize = 5;
public enum HandValues
{
Nothing,
Pair, // .047539
TwoPair, // .021128
ThreeOfAKind,
Straight,
Flush,
FullHouse,
FourOfAKind,
StraightFlush,
RoyalFlush
}
public List<Card> Cards;

public PokerHand(List<Card> cards)
{
this.Cards = cards;
}
public HandValues PokerValue()
{
// using Linq is probably not the most efficient
var value = HandValues.Nothing;
var groupsBySuits = Cards.GroupBy(c => c.suit);
var groupsByRank = Cards.OrderBy(c => c.denom).GroupBy(c => c.denom);
if (groupsBySuits.Count() == 1) // only one suit= Flush. See if it's a straight
{
if (IsStraight(groupsByRank))
{
if (groupsByRank.First().Min().denom == 8) //it's a ten
{
value = HandValues.RoyalFlush;
}
else
{
value = HandValues.StraightFlush;
}
}
else
{
value = HandValues.Flush;
}
}
else
{
// if the hand has none of a kind: e.g. a 3,5,6,9,J, then there are 5 groups by denom
// if there's a pair, then there will be 4 groups
// 2 pair: there will be 3
// if there's a triple, then there will be 2 (full house) or 3 groups
// 4 of a kind: 2 groups
switch (groupsByRank.Count())
{
case 5: // there are 5 groups of denoms, so can only be straight
if (IsStraight(groupsByRank))
{
value = HandValues.Straight;
}
break;
case 4:
value = HandValues.Pair;
break;
case 3: // 2 pair or triple
{
var nn = groupsByRank.OrderByDescending(g => g.Count());
int nMaxCount = nn.First().Count();
if (nMaxCount == 3)
{
value = HandValues.ThreeOfAKind;
}
else
{
value = HandValues.TwoPair;
}
}
break;
case 2: // full house or 4 of a kind
{
var nn = groupsByRank.OrderByDescending(g => g.Count());
int nMaxCount = nn.First().Count();
if (nMaxCount == 4)
{
value = HandValues.FourOfAKind;
}
else
{
value = HandValues.FullHouse;
}
}
break;
}
}
return value;
}

private bool IsStraight(IEnumerable<IGrouping<int, Card>> groupsByRank)
{
bool isStraight = false;
if (groupsByRank.Count() == HandSize)
{
// the groups are sorted
var first = groupsByRank.First().First().denom;
var last = groupsByRank.Last().First().denom;
if (first + HandSize - 1 == last)
{
isStraight = true;
}
else
{ // special case: Ace low
if (last == 12) // Ace
{
int ndx = 0;
foreach (var g in groupsByRank)
{
var c = g.First().denom;
if (ndx++ != c)
{
break;
}
}
if (ndx == HandSize)
{
isStraight = true;
}
}
}
}

return isStraight;
}
}

public class Card : Image, IComparable
{
public CardDeck.Suit suit;
public int denom; // 0-12
public int Value => (int)suit * 13 + denom;// 0-51
public BitmapSource bmpSource { get; private set; }
/// <summary>
/// Create a new card
/// </summary>
/// <param name="suit"></param>
/// <param name="denom"> 12=A, 11=K, Q=10, J=9,... 0=2</param>
/// <param name="bmpSource">can be null</param>
public Card(CardDeck.Suit suit, int denom, BitmapSource bmpSource = null)
{
this.suit = suit;
this.denom = denom;
this.bmpSource = bmpSource;
}
public string GetDenomString()
{
var result = (denom + 2).ToString();
switch (denom)
{
case 9:
result = "J";
break;
case 10:
result = "Q";
break;
case 11:
result = "K";
break;
case 12:
result = "A";
break;

}
return result;
}
public override string ToString()
{
return \$"{GetDenomString()} of {suit}";
}

public int CompareTo(object obj)
{
if (obj is Card)
{
var c = (Card)obj;
if (c.suit == this.suit)
{
return c.denom.CompareTo(this.denom);
}
return ((int)c.suit).CompareTo((int)(this.suit));
}
throw new InvalidOperationException();
}
}
public class CardDeck
{
public enum Suit
{
Clubs = 0,
Diamonds = 1,
Hearts = 2,
Spades = 3
}
// put cards in 2 d array, suit, rank (0-12 => 2-A)
public Card[,] _Cards;
public BitmapSource[] _bitmapCardBacks;
private static CardDeck _instance;

public static int NumCardBacks => _instance._bitmapCardBacks.Length;

public CardDeck()
{
_Cards = new Card[4, 13];
var hmodCards = LoadLibraryEx("cards.dll", IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);
if (hmodCards == IntPtr.Zero)
{
throw new FileNotFoundException("Couldn't find cards.dll");
}
// the cards are resources from 1 - 52.
// here is a func to load an int rsrc and return it as a BitmapSource
Func<int, BitmapSource> GetBmpSrc = (rsrc) =>
{
// we first load the bitmap as a native resource, and get a ptr to it
var bmRsrc = LoadBitmap(hmodCards, rsrc);
// now we create a System.Drawing.Bitmap from the native bitmap
var bmp = System.Drawing.Bitmap.FromHbitmap(bmRsrc);
// we can now delete the LoadBitmap
DeleteObject(bmRsrc);
// now we get a handle to a GDI System.Drawing.Bitmap
var hbmp = bmp.GetHbitmap();
// we can create a WPF Bitmap source now
var bmpSrc = Imaging.CreateBitmapSourceFromHBitmap(
hbmp,
palette: IntPtr.Zero,
sourceRect: Int32Rect.Empty,
sizeOptions: BitmapSizeOptions.FromEmptyOptions());

// we're done with the GDI bmp
DeleteObject(hbmp);
return bmpSrc;
};
// now we call our function for the cards and the backs
for (Suit suit = Suit.Clubs; suit <= Suit.Spades; suit++)
{
for (int denom = 0; denom < 13; denom++)
{
// 0 -12 => 2,3,...j,q,k,a
int ndx = 1 + 13 * (int)suit + (denom == 12 ? 0 : denom + 1);
_Cards[(int)suit, denom] = new Card(suit, denom, GetBmpSrc(ndx));
}
}
//The card backs are from 53 - 65
_bitmapCardBacks = new BitmapSource[65 - 53 + 1];
for (int i = 53; i <= 65; i++)
{
_bitmapCardBacks[i - 53] = GetBmpSrc(i);
}
}
public static double MeasureRandomness(Card[] _cards)
{
var dist = 0.0;
for (int suit = 0; suit < 4; suit++)
{
for (int denom = 0; denom < 13; denom++)
{
var ndx = suit * 13 + denom;
int curval = _cards[ndx].Value;
dist += Math.Pow((ndx - curval), 2);
}
}
return dist;
}
/// <summary>
/// Return a card
/// </summary>
/// <param name="nSuit"></param>
/// <param name="nDenom">1-13 = A, 2,3,4,J,Q,K</param>
/// <returns></returns>
public static Card GetCard(Suit nSuit, int nDenom)
{
if (_instance == null)
{
_instance = new CardDeck();
}
if (nDenom < 0 || nDenom > 12)
{
throw new ArgumentOutOfRangeException();
}
return _instance._Cards[(int)nSuit, nDenom];
}

internal static ImageSource GetCardBack(int i)
{
return _instance._bitmapCardBacks[i];
}
}

public const int LOAD_LIBRARY_AS_DATAFILE = 2;
private TextBox _txtStatus;

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFileReserved, uint dwFlags);

[DllImport("User32.dll")]
public static extern IntPtr LoadBitmap(IntPtr hInstance, int uID);

[DllImport("gdi32")]
static extern int DeleteObject(IntPtr o);
}

}``````

</code>

Tags

Comments (0)
Skip to main content