For the community cards, by searching for the tops of the cards within the community cards window its possible to determine the amount of cards that are in play (3,4, or 5). This will make searching faster because the program will know how many cards to look for.
The fourth image is searched for within your hand window to determine if you have any cards in your hand at the current time. This will save a search through 52 cards if you are currently folded or out of play.
I've included all the bitmaps for the 52 cards and the four images above in the zip file below:
Bitmaps.zip
Since the program will be searching for these Bitmaps quite often, it will be beneficial to load them into memory at program start up. To do this, you're going to need to put that unzipped Bitmaps Folder directly into your C# Project's Debug Directory
Default:(C:\Users\YOURNAME\Documents\Visual Studio 201X\Projects\YOUR_PROJECT_NAME\YOUR_PROJECT_NAME\bin\Debug)
As we import the bitmaps we want to keep the filenames attached with them for later identification.
IE) So you know that if the S7.bmp was found within your hand region window on the screen then you know the Seven of Spades was one of the cards in your hand.
To do this, in the CardDetection.cs we will add a new Struct called NamedBitmap:
public struct NamedBitmap { public Bitmap bitmap; public string name; }
Now we will add two seperate Lists of NamedBitmaps to the CardDetection.cs. One list for the Card Bitmaps, and another List for the other four bitmaps shown above:
private static List<namedbitmap> cardPics = new List<namedbitmap>(); private static List<namedbitmap> tablePics = new List<namedbitmap>();
When the Program starts we want to populate those lists with all the Bitmap and filename data, so in the CardDetection.cs class, we will create an Initialize function. This function will only be called once from the main Form's On_Load Event to get all the Bitmap information into memory for quick comparison later. Here is the Initialize function:
public static void Initialize() { //Set Path of the Bitmap folder string bitmapsPath = Application.StartupPath + "\\Bitmaps\\"; //Load the Bitmaps into carPics, and tablePics Lists foreach (string s in Directory.GetFiles(bitmapsPath)) { if (s.ToUpper().EndsWith(".BMP")) { string[] splitter = s.Split('\\'); string fileName = splitter[splitter.Length - 1]; string[] splitter2 = fileName.Split('.'); //if filename Length is 2 or 3. if (splitter2[0].Length == 2 || splitter2[0].Length == 3) { //File name was 2 or 3 chars long, its a card bitmap NamedBitmap b = new NamedBitmap(); b.name = splitter2[0]; b.bitmap = new Bitmap(s); cardPics.Add(b); } else { //File name was not 2 chars long, its a table bitmap NamedBitmap b = new NamedBitmap(); b.name = splitter2[0]; b.bitmap = new Bitmap(s); tablePics.Add(b); } } } }
Basically it searches through the Bitmaps folder, and puts any files that have filenames 2 or 3 characters long into the cardPics list, and anything else into the tablePics list.
Now we will create a basic function to fetch a bitmap out of the cardList or tableList with a given name. You'll see why we're creating this function shortly:
private static Bitmap RetrieveBitmap(string name) { //Check cardPics List foreach (NamedBitmap nb in cardPics) { if (nb.name.ToUpper() == name.ToUpper()) { return nb.bitmap; } } //Check tablePics List foreach (NamedBitmap nb in tablePics) { if (nb.name.ToUpper() == name.ToUpper()) { return nb.bitmap; } } return null; }
Also let's create a few functions that allow us to convert from the card filename strings (S7, DQ, H2, etc....) into the Suit, and Rank enums that we defined in Part 3, because that is the more common form of card data that we will store throughout the rest of the program. Here are two functions for doing that:
// Converts a string "S", "C", "H", or "D" into the coresponding Suit Enum private static Suit ConvertStringToSuit(string stringSuit) { Suit cardSuit; //Figure out which suit the found card was switch (stringSuit) { case "S": cardSuit = Suit.Spades; break; case "C": cardSuit = Suit.Clubs; break; case "H": cardSuit = Suit.Hearts; break; case "D": cardSuit = Suit.Diamonds; break; default: cardSuit = Suit.NotFound; break; } return cardSuit; } // Converts a string "2", "3", "4"... "J", "Q", "K", or "A" into the coresponding Rank Enum private static Rank ConvertStringToRank(string stringRank) { Rank cardRank; //Figure out which rank the found card was switch (stringRank) { case "2": cardRank = Rank.Two; break; case "3": cardRank = Rank.Three; break; case "4": cardRank = Rank.Four; break; case "5": cardRank = Rank.Five; break; case "6": cardRank = Rank.Six; break; case "7": cardRank = Rank.Seven; break; case "8": cardRank = Rank.Eight; break; case "9": cardRank = Rank.Nine; break; case "10": cardRank = Rank.Ten; break; case "J": cardRank = Rank.Jack; break; case "Q": cardRank = Rank.Queen; break; case "K": cardRank = Rank.King; break; case "A": cardRank = Rank.Ace; break; default: cardRank = Rank.NotFound; break; } return cardRank; }
Now, finally coming around to the main goal we set out to do for this portion of the code. Lets make one function that will return a Card[] array of two Cards that were found in your hand, and another function that will return a Card[] array of either 3, 4 or 5 cards that are community cards. Both functions will return null if no cards were found in those places.
The function for returning the Card[] in your hand is here:
public static Card[] RetieveYourHand() { Bitmap currentHand = CaptureYourHandImage(); //First check to see if there are no cards in your hand to avoid unnessicary searching. if (SearchBitmap(currentHand, RetrieveBitmap("NoHand"))) { return null; } else { int foundCount = 0; int cardsToSearch = 2; Card[] hand = new Card[2]; foreach (NamedBitmap cardImage in cardPics) { if (SearchBitmap(currentHand, cardImage.bitmap)) { //Split the Bitmap filenames up into two seperate strings. EX) filename == S7, then stringSuit = "S", and stringRank = "7" string stringSuit = cardImage.name.Substring(0, 1); string stringRank = cardImage.name.Substring(1); //Get the Bitmap filenames into Suit and Rank Enums. EX) filename == S7, then cardSuit = Suit.Spades, and cardRank = Rank.Seven Suit cardSuit = ConvertStringToSuit(stringSuit); Rank cardRank = ConvertStringToRank(stringRank); hand[foundCount] = new Card(cardSuit, cardRank); foundCount++; //If you found all the cards, stop searching if (foundCount == cardsToSearch) { break; } } } return hand; } }
The function for returning the Card[] array for community cards is here:
public static Card[] RetieveCommunityCards() { Bitmap currentCommunityCards = CaptureCommunityCardsImage(); int foundCount = 0; int cardsToSearch; Card[] hand; //Check to see how many cards you need to find, if no cards are on the table return null. if (SearchBitmap(currentCommunityCards, RetrieveBitmap("3cards"))) { cardsToSearch = 3; hand = new Card[3]; } else if (SearchBitmap(currentCommunityCards, RetrieveBitmap("4cards"))) { cardsToSearch = 4; hand = new Card[4]; } else if (SearchBitmap(currentCommunityCards, RetrieveBitmap("5cards"))) { cardsToSearch = 5; hand = new Card[5]; } else { return null; } foreach (NamedBitmap cardImage in cardPics) { if (SearchBitmap(currentCommunityCards, cardImage.bitmap)) { //Split the Bitmap filenames up into two seperate strings. EX) filename == S7, then stringSuit = "S", and stringRank = "7" string stringSuit = cardImage.name.Substring(0, 1); string stringRank = cardImage.name.Substring(1); //Get the Bitmap filenames into Suit and Rank Enums. EX) filename == S7, then cardSuit = Suit.Spades, and cardRank = Rank.Seven Suit cardSuit = ConvertStringToSuit(stringSuit); Rank cardRank = ConvertStringToRank(stringRank); hand[foundCount] = new Card(cardSuit, cardRank); foundCount++; //If you found all the cards, stop searching if (foundCount == cardsToSearch) { break; } } } return hand; }
These methods are very similar. Basically they are putting together everything that we've done up to this point into two neat functions that can be used later on. The functions begin by determining how many cards / if any at all are on the table. Then they loop through the cardPics list that we filled with our card bitmaps searching the specified card window (using the CaptureYourHandImage(), and CaptureCommunityCardsImage() functions we made in Part3) for each card until it finds the amount of cards its looking for. It converts the Bitmap filenames which were located into Suits and Ranks that are turned into Card objects which are passed out of the function as a Card[] array.
To test that these functions are working properly you can add some quick test code on a button in the main Form. Also, don't forget to call the CardDetection.Initiailize() function in your Form's onLoad event so that the bitMap Lists populate:
private void Form1_Load(object sender, EventArgs e) { CardDetection.Initialize(); } private void button1_Click(object sender, EventArgs e) { Card[] hand = CardDetection.RetieveYourHand(); Card[] comm = CardDetection.RetieveCommunityCards(); }
Add a breakpoint to the end of the button click function, and you can see whats inside the hand, and comm Card[] arrays. For example, I clicked the button when this was showing on the Pokerstars Screen:
and everything worked because I saw this in the Visual Studio Debug local variables window, when the code was finished and hit the breakpoint I placed at the end:
That pretty much wraps things up for this portion of the code. Now our bot has some eyes, no brains yet, but eyes are good. We may come back to some more image detection later on to get things like the pot and player chip counts to help our bots make more informed decisions, but for now this is a good starting point and in the next posts we can start to touch on the AI side of things and start building our brain.