Tuesday, April 14, 2015

Creating a PokerBot using a C# WinForms Application Part4

Hello again. In this part we will work with the image windows we captured last time, and loading and comparing bitmaps of the 52 cards to see which cards are in play/your hand. I'm also going to include a few bitmaps that will help save time searching shown below:
 







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.

Sunday, April 5, 2015

Creating a PokerBot using a C# WinForms Application Part3

Before we start diving into too much more code I want to take a step back and give the code a little more structure. Building a good foundation will make it easier as things move forward and the bot becomes more complex.

I'll start by creating a new class called CardDetection.cs. I'm going to put any functions and variables related to the bot's Card Detection into this class. This class is going to be static because there is never going to be a need to create multiple instances of Card Detection. I'm going to move the SearchBitmap and CaptureApplication functions that were shown earlier into this class. So far in the CardDetection.cs class we have:


static class CardDetection
{
     public static Bitmap CaptureApplication(string procName)

     public static bool SearchBitmap(Bitmap bigBmp, Bitmap smallBmp)

     private class User32      //Used by the Capture Application Function
}

My ultimate goal for this class is to have functions that can be called by the main Form to return the information of what cards are currently in play. To reduce all the complexity of capturing bitmaps, searching through them, and matching appropriate cards to their comparisons to a single line of code that simply returns the cards that are on the table.

So first off, I'd like to create a Card.cs class. This will be a simple class that contains a Suit and a Rank variable, but will be used to pass Card Information around the program whenever it is needed. Unlike the CardDetection class, this class will not be static, as there will need to be multiple card variables in use by the program. In addition to the Card class, I'll create some Enums:

public enum Suit
{
    NotFound = 0,
    Diamonds = 1,
    Hearts = 2,
    Clubs = 3,
    Spades = 4,
}

public enum Rank
{
    NotFound = 0,
    Two = 2,
    Three = 3,
    Four = 4,
    Five = 5,
    Six = 6,
    Seven = 7,
    Eight = 8,
    Nine = 9,
    Ten = 10,
    Jack = 11,
    Queen = 12,
    King = 13,
    Ace = 14,
}

The Card.cs Class looks like this:

class Card
{
    public Suit Suit { get; private set; }
    public Rank Rank { get; private set; }

    public Card(Suit suit, Rank rank)
    {
        Suit = suit;
        Rank = rank;
    }
}

Here is an example of how the Card variable can now be used in the program:

//Define your hand, an array of 2 Cards.
Card[] YourHand = new Card[2];

//Define the first Card in your hand, the Ace of Spaces
YourHand[0] = new Card(Suit.Spades, Rank.Ace);

//Define the second Card in your hand, the Ace of Diamonds
YourHand[1] = new Card(Suit.Diamonds, Rank.Ace);

Now that we have the Card class setup, we can continue working on the CardDetection class. I mentioned earilier it will be better to narrow down the areas on the table where the program is looking for cards to make the searching quicker and more efficient. To do this we will put a function in to return a Bitmap of a partial area of the Poker Stars screen defined by a rectagle. The function is shown below:

private static Bitmap CopyPartialBitmap(Bitmap srcBitmap, Rectangle section)
{
     // Create the new bitmap and associated graphics object
     Bitmap bmp = new Bitmap(section.Width, section.Height);
     Graphics g = Graphics.FromImage(bmp);

     // Draw the specified section of the source bitmap to the new one
     g.DrawImage(srcBitmap, 0, 0, section, GraphicsUnit.Pixel);

     // Clean up
     g.Dispose();

     // Return the bitmap
     return bmp;
}

Now we would need to define some rectangles on the Pokerstars window to narrow where we are going to search for cards. The picture below shows on orange rectangle defining where to look for your cards, and a red rectangle defining where to look for community cards:

I created two functions in the CardDetection class that capture a bitmap of the two rectangles above:

private static Bitmap CaptureYourHandImage()
{
    return CopyPartialBitmap(CaptureApplication("PokerStars"), new Rectangle(352, 365, 90, 53));
}

private static Bitmap CaptureCommunityCardsImage()
{
    return CopyPartialBitmap(CaptureApplication("PokerStars"), new Rectangle(262, 204, 282, 74));
}


Next post we will look at combining the elements laid out in this post and create functions that simply return Card[] by searching through the newly defined card windows against all 52 card bitmaps to determine which cards (if any) are there.