Poker Real Time Odds on Mac OS X: Part 3

The continuing story of creating a tool called SeeingStars, offering real time odds for PokerStars. Read part 1 and part 2.

Now the tricky stuff starts: having found the PokerStars table window, how do we determine the cards? This is not an easy programming problem. To make it easier, I’ll add some constraints. For the purpose of creating proof that real time odds is possible, here are the constraints I’m imposing:

  1. Heads-up only, so that we know there are always exactly two players in the hand.
  2. Maximum window size only, which is 1320 pixels by 932 pixels.
  3. Using PokerStars’ Nova theme, with green felt, 4-color deck, using the modern-style deck:
    Screen Shot 2012 08 23 at 3 10 14 PM

These constraints can be relaxed later, once I get the basics working.

Using the window id obtained in part 2 of this series we can get the screenshot:

- (NSBitmapImageRep *)createSingleWindowShot:(CGWindowID)windowID {
CGImageRef cgImageRef = CGWindowListCreateImage(
CGRectNull,
kCGWindowListOptionIncludingWindow,
windowID,
kCGWindowImageBoundsIgnoreFraming);
NSImage *const nsImage = [[NSImage alloc] initWithCGImage:cgImageRef size:NSZeroSize];
return [[NSBitmapImageRep alloc] initWithData:[nsImage TIFFRepresentation]];
}

From the screen image we can determine the suit of a card by sampling the colour of one pixel. I used stackoverflow.com for information on finding the nearest colour to a pixel, and then map that colour to a suit.  Some reading and experimentation shows the colour’s hue is a good method for differentiating between colours:


- (NSString *)getSuit:(NSBitmapImageRep *)bitmapImageRep x:(NSInteger)x y:(NSInteger)y {
NSDictionary *hues = @{
@0.57f:@"d",
@1.0f:@"d",
@0.29f:@"c",
@0.0f:@"s"
};
CGFloat pixelHue = ([bitmapImageRep colorAtX:x + 2 y:y + 4]).hueComponent;
double smallestDiff = fabs(0.38 - pixelHue); // 0.38 is the hue of the table felt
NSString *match = nil;
for (NSNumber *hue in hues.allKeys) {
const double diff = fabs((hue.floatValue) - pixelHue);
if (diff < smallestDiff) {
smallestDiff = diff;
match = [hues objectForKey:hue];
}
}
return match;
}

What about a card’s rank? This is harder. From experience I know that “thresholding” is helpful. Thresholding turns an image into a one-bit image: every pixel becomes either black or white, on or off, according to a threshold algorithm.

I started up Pixelmator and opened a PokerStars screenshot. Using Pixelmator’s threshold effect, I found that a setting of 80% worked well.

Screen Shot 2012 08 23 at 3 46 12 PM

Most detail is gone, but the ranks of the cards are visible.

I turned this into Objective-C code:


- (BOOL)threshold:(NSColor *)color {
float value = 0.8f;
return color.redComponent > value &&
color.blueComponent > value &&
color.greenComponent > value;
}

- (void)logOneBitImage:(NSBitmapImageRep *)bitmapImageRep startX:(NSInteger)startX startY:(NSInteger)startY {
int width = 26;
int height = 30;
int ary[width * height];
for (int y = 0; y < height; y++) {
NSString *row = @"";
for (int x = 0; x < width; x++) {
NSColor *color = [bitmapImageRep colorAtX:x + startX y:y + startY];
ary[x * height + y] = [self threshold:color] ? 1 : 0;
row = [row stringByAppendingString:(ary[x * height + y]) == 0 ? @"0" : @"1"];
}
NSLog(@"row = %@", row);
}
NSLog(@" ");
}

This gives output in which you can clearly see the rank:

00000000000000000000000000
00000000011111000000000000
00000001110011110000000000
00000111100000111000000000
00001111000000111100000000
00001110000000011110000000
00011110000000011111000000
00011110000000001111000000
00011100000000001111000000
00111100000000001111000000
00111100000000001111000000
00111100000000001111100000
00111100000000000111100000
00111100000000001111000000
00111110000000001111000000
00011110000000001111000000
00011110000000001111000000
00001110000000001110000000
00001111000000011110000000
00000111100000011100000000
00000011110000111000000000
00000000111111100000000000
00000000001111000000000000
00000000001111000000000000
00000000001111100000000000
00000000001111110000000000
00000000000111111000000000
00000000000011111111000000
00000000000000011100000000
00000000000000000000000000

In the next article in this series, I’ll show how that set of 1’s and 0’s can be turned in a ‘Q’, in a reasonably fault-tolerant way.

Poker Copilot 3.17 Now Available

This update is important for you if you play on the Merge Network. Poker Copilot crashes with Merge Network since their update overnight. This update fixes that.

What’s changed:

  • Fix for bug introduced in PartyPoker’s update
  • Fix for change introduced in Merge Network’s update
  • HUD now working for PokerStars “Ante” cash game tables
  • Fixed aggression factor summary, which was 100 times too big (thanks to loyal customer Jon P for spotting this)
  • Amended Betsafe Poker support to use new name “BetsafePokerRed”
  • Added PokerIdol (Ongame Network) support
  • Added BetfairPokerDK (Denmark) support
  • Fixed obscure issue with Zoom Poker HUD not showing for some opponents
  • Fixed rounding error in “buyins won/tournament”

Known problem:

  • Lock Poker’s new Mac OS X software is not supported by Poker Copilot.
  • PartyPoker hands can’t be reliably imported into Poker Copilot. There are several problems in PartyPoker’s hand history file on Mac OS X.

(And if you don’t own it yet, click here to buy Poker Copilot, securely and instantly.)

<!– This update needs to update your database. –>

Poker Real Time Odds on Mac OS X: Part 2

The continuing story of creating a tool called SeeingStars, offering real time odds for PokerStars. Read part 1 here.

If you are an Objective-C programmer, I’m going to give you enough code to get the basics working in this series of articles. If you are not a programmer at all, you probably won’t enjoy much of what follows. Important to note is that I seldom code in Objective-C, so my code isn’t always idiomatic for Objective-C and may have obvious bugs. Please point this out in the comments so I can learn.

Here’s the basic strategy:

  1. Find the top-most PokerStars poker table window, if any
  2. If no such window exists go to step 7
  3. Grab a screenshot of the window
  4. Determine the player’s hole cards and the community cards using some form of screen scraping/OCR
  5. Calculate the % chance of winning, assuming the villains have random hands
  6. Display the % chance of winning
  7. Wait a second or so
  8. Go back to step 1

The hardest bit for me will be step 4, which is determining the player’s hole cards and the community cards. I already have a fair idea how to do the other steps.

First, a timer will achieve the “wait a second or so” of step 7 and 8:

- (IBAction)startRepeatingTimer:sender {
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self selector:@selector(targetMethod:)
userInfo:[self userInfo] repeats:YES];
}

- (void)targetMethod:(NSTimer *)theTimer {
NSDictionary *windowDictionary = [[PokerRoomController alloc] findPokerStarsTableWindows];
if (windowDictionary != nil) {
NSString *title = [windowDictionary objectForKey:@"kCGWindowName"];
NSLog(@"title = %@", title);
}
}

Is one second a reasonable time, that balances snappy updates with low CPU load? I don’t know. I’ll know later in the story. But for now, it is an adequate starting point. I tend to go for “good enough” solutions until I have evidence that the solution is not good. I’ll address performance issues as they arise.

Now for step 1 – find the top-most PokerStars window. There’s a handy function to find all the windows open on your Mac, introduced in Mac OS X 10.5 (Leopard). It is called CGWindowListCopyWindowInfo. It is part of Quartz Window Services. It returns a key-value dictionary of information for each window. Conveniently it returns windows ordered from top to bottom; the front-most window is first in the array of dictionaries. “Normal” windows have a layer of 0.


- (NSDictionary *)findFrontNormalWindow {
NSArray *windowList = (__bridge NSArray *)
CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSDictionary *dictionary in windowList) {
NSNumber *windowLayer = [dictionary objectForKey:@"kCGWindowLayer"];
if ([windowLayer isEqualToNumber:[NSNumber numberWithInt:0]]) {
return dictionary;
}
}
return nil;
}

Now we need to make sure the front-most window is a PokerStars table window. For Poker Copilot, I’ve created a set of heuristics to determine this as follows. If all the following are true, then this is a PokerStars table window:

  • Does the window owner name start with PokerStars? This “start with” clause allows this approach to work with PokerStarsES, PokerStarsFR, PokerStarsEU, etc?
  • Does the window name NOT include “Tournament Buy-in”?
  • Does the window name NOT include “Chat”?
  • Does the window name NOT include “Cashier”?
  • Does the window name NOT include “PokerStars Lobby”?
  • Does the window name include at least one hyphens?
  • If the window name contains exactly two hyphens, Is the window’s aspect ratio (width/height) NOT 1.45 +/- 0.01? (That excludes the hand replayer window).
  • Does the window name include “Logged In as”?

I’ve sneakily left out a couple of obscure edge cases from that list. Hey, it’s taken four years to build up this knowledge for Poker Copilot; I’m not going to give all my secrets away!

In code form, most of the above looks like this:

- (BOOL)isTableWindow:(NSString *)title bounds:(CGRect)bounds {
BOOL buyinWindow = [title rangeOfString:@"Tournament Buy-in"].location == 0;
BOOL chatWindow = [title rangeOfString:@"Chat"].location == 0;
BOOL cashierWindow = [title rangeOfString:@"Cashier"].location == 0;
BOOL lobbyWindow = [title rangeOfString:@"PokerStars Lobby"].location == 0;
BOOL hasHyphens = [[title componentsSeparatedByString:@"-"] count] - 1 > 1;
double aspectRatio = bounds.size.width / (bounds.size.height - 22);
double epsilon = fabs(1.45 - aspectRatio);
NSUInteger count = [[title componentsSeparatedByString:@"-"] count] - 1;
BOOL isReplayerWindow = count == 2 && epsilon < 0.01;
BOOL hasLoggedIn = ([title rangeOfString:@"Logged In as"].location != NSNotFound);
return !buyinWindow &&
!chatWindow &&
!lobbyWindow &&
!cashierWindow &&
!isReplayerWindow &&
hasHyphens &&
hasLoggedIn;
}

Put it all together, and the PokerRoomController  is as follows:


@implementation PokerRoomController {
}

- (NSDictionary *)findPokerStarsTableWindows {
NSDictionary *windowDictionary = [self findFrontNormalWindow];
if (windowDictionary == nil) {
return nil;
}
NSString *windowOwnerName = [windowDictionary objectForKey:@"kCGWindowOwnerName"];
if ([windowOwnerName rangeOfString:@"PokerStars"].location != 0) {
return nil;
}
NSString *title = [windowDictionary objectForKey:@"kCGWindowName"];
CGRect bounds;
CGRectMakeWithDictionaryRepresentation(
(__bridge CFDictionaryRef)
([windowDictionary objectForKey:@"kCGWindowBounds"]), &bounds);
if (!([self isTableWindow:title bounds:bounds])) {
return nil;
}
return windowDictionary;
}

- (NSDictionary *)findFrontNormalWindow {
NSArray *windowList = (__bridge NSArray *)
CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSDictionary *dictionary in windowList) {
NSNumber *windowLayer = [dictionary objectForKey:@"kCGWindowLayer"];
if ([windowLayer isEqualToNumber:[NSNumber numberWithInt:0]]) {
return dictionary;
}
}
return nil;
}

- (BOOL)isTableWindow:(NSString *)title bounds:(CGRect)bounds {
BOOL buyinWindow = [title rangeOfString:@"Tournament Buy-in"].location == 0;
BOOL chatWindow = [title rangeOfString:@"Chat"].location == 0;
BOOL cashierWindow = [title rangeOfString:@"Cashier"].location == 0;
BOOL lobbyWindow = [title rangeOfString:@"PokerStars Lobby"].location == 0;
BOOL hasHyphens = [[title componentsSeparatedByString:@"-"] count] - 1 > 1;
double aspectRatio = bounds.size.width / (bounds.size.height - 22);
double epsilon = fabs(1.45 - aspectRatio);
NSUInteger count = [[title componentsSeparatedByString:@"-"] count] - 1;
BOOL isReplayerWindow = count == 2 && epsilon < 0.01;
BOOL hasLoggedIn = ([title rangeOfString:@"Logged In as"].location != NSNotFound);
return !buyinWindow &&
!chatWindow &&
!lobbyWindow &&
!cashierWindow &&
!isReplayerWindow &&
hasHyphens &&
hasLoggedIn;
}

@end

So now I’ve got code running once a second that finds the top-most PokerStars table window, if any. This is a key step in creating Poker Copilot’s HUD. However from hereon, the approach I’ll be showing differs from Poker Copilot’s HUD.

In the next part of this blog series, I’ll grab the screenshot and do some processing on it.

Poker Real Time Odds on Mac OS X: Part I

I’ve been playing heads-up sit and go tournaments (HU SNG) over the last couple of weeks. It’s nice to note that Poker Copilot works very well with HU SNG’s, because there is so much screen space to use. I can see a dozen stats, with labels:

Screen Shot 2012 08 19 at 7 37 32 PM

HU SNG’s are a game where you raise a lot pre-flop. Depending on the opponent, when I have position I might  raise 50% of the time, 75% of the time, or even more. In fact when I played a HU SNG today, I noticed my opponent folded to 80% of my pre flop raises, so I raised pre-flop 89% of the time from position.

The information I need to help decide when to raise pre-flop is to know whether I have, for example, a top 50% hand, or with a nitty opponent, a top 75% hand. Looking this up manually in PokerZebra does the job but it is slow and clumsy to do this mid-hand.

Screen Shot 2012 08 19 at 7 56 15 PM

 

That’s why I want a tool that shows in real-time my percentage chance of winning. I’m surprised that no-one has yet created and made available such a tool for Mac OS X, as it is not overly difficult to do for a programmer of some talent. So I’m going to try to create such a tool over the next week or so.

There is a problem though. Full Tilt Poker always banned software that showed real-time odds. As far as I can tell, PokerStars allows it, but it is not clear to me whether this really is the case, and whether it will continue to be the case. Other poker rooms lack any clear policy. If Poker Copilot gets banned by online poker rooms, I’m out of business. So I’m going to create this as a separate tool.

Over the next few days, I’ll describe the steps I take to in an attempt to build this tool, which I’m called SeeingStars.

 

Congratulations to FastSpring for Inc 500 Award

For Poker Copilot’s e-commerce, I use a service called FastSpring. They handle the credit card verification, fraud detection, payment processing, and “thanks for buying” email generation. I’ve been using FastSpring with Poker Copilot since the beginning, when FastSpring was a relative newcomer in a field where many of the competitors had a bad reputation. Many of the incumbents – well, mainly just one incumbent that went by many names – had a reputation for not particularly caring for either their customers nor for their customers’ customers. They tended to make decisions that favoured themselves in the short-term. Other companies weren’t keeping up with the times.

I admire FastSpring for keeping to a policy of excellent customer service, even as they have experienced year after year of rapid growth. In four years of selling Poker Copilot, I’ve never once seriously entertained the idea of moving to another e-commerce partner. Their offering keeps improving. Every customer support issue I’ve had with them has been handled immediately. I’m certainly not one of FastSpring’s large customers, but they always treat me as if I am.

That’s why I’m happy to see that FastSpring has received its 3rd consecutive annual Inc. 500 award for fastest-growing private US companies.

If you sell software or other digital merchandise online I recommend considering FastSpring as your e-commerce partner.

 

PokerStars new "Theme Chooser"

PokerStars released an update today. The only change I’ve noticed so far is a new theme chooser. This I like. It’s good to see the leading online poker room continue to innovate:

Screen Shot 2012 08 16 at 3 01 14 PM

Poker Copilot 3.16 Now Available

Poker Copilot 3.16 is now available to download.

This update fixed many small bugs and issues.

What’s changed:

  • Hand replayer now anonymises player names
  • Change to PokerStars satellite tournament ticket win descriptions now handled properly
  • PokerStars audit importing was failing on tournament wins greater than $1000/€1000
  • Fixed problem where tournament descriptions longer than 100 characters crashed Poker Copilot (Yes, a ROOKIE MISTAKE – that somehow took FOUR years to surface)
  • Fixed PokerStarsES problem where HUD default layout was incorrect
  • Hand replayer now always shows hero in same position (top centre)
  • Added page up/page down/home/end keyboard shortcuts to the hand replayer
  • Added “Rake” as an optional column in the summaries

Known problem:

  • Lock Poker’s new Mac OS X software is not supported by Poker Copilot.
  • PartyPoker’s latest update is not yet supported by Poker Copilot.
  • Merge Network All-in EV is sometimes calculated incorrectly.

(And if you don’t own it yet, click here to buy Poker Copilot, securely and instantly.)

<!– This update needs to update your database. –>

Update Instructions:

  1. Download the latest version here.
  2. Open the downloaded file.
  3. Drag the Poker Copilot icon to the Applications icon. If prompted to replace an existing version, confirm that you do want to replace.

Now you are done and ready to hit the tables.

Full Tilt Poker returning

I didn’t think this would happen: Full Tilt Poker has been taken over by PokerStars, it is reopening within 90 days, and all existing non-USA Full Tilt Poker customers will have their funds made available. USA customers will eventually be paid by the US Department of Justice (DOJ) using funds paid by PokerStars to the DOJ as part of a very large settlement.

How large is the settlement? Roughly US$700 million. Which shows just how much money there is online poker.

Read more here and here and views and opinions here and the DOJ press release here.

Full Tilt Poker had, in my opinion, the best Mac software in online poker.

And I Thought _I_ Got Some Difficult Support Requests

Carbon Poker doesn’t seem to be working (for me and for some others) with OS X Mountain Lion. A person experiencing this problem is taking it pretty badly:

I have the same problem.

If its not fixed in under 48 hours, I think I’m going to have to commit suicide.

Hope merge has a good tech team.

Carbon’s support guy offers a nice reply:

That seems borderline excessive, but we’re working on it.

I assume everyone is being tongue-in-cheek.

 

Mountain Lion and Poker Copilot get along well

After 24 hours of Mountain Lion, and 24 hours of Poker Copilot being Mountain Lion friendly, I’ve not heard of any problems. This is a huge relief for me. I’ve been running developer previews of Mountain Lion for some months, and it quickly became clear that Mountain Lion was going to cause some grief for small software companies if they didn’t get their software modified for Gatekeeper. Mountain Lion refuses to open many newly downloaded applications. You get messages like this:

Screen Shot 2012 07 26 at 3 14 01 PM

There are work-arounds; if you encounter software like this, you can either:

  • right-click (or ctrl-click) the downloaded application and click Open. You’ll see this:

Screen Shot 2012 07 26 at 3 14 09 PM

  • Turn Gatekeeper off. In System Preferences, go to Security & Privacy, select the General panel, and set “Allow applications downloaded from:” to “Anywhere”:

Screen Shot 2012 07 26 at 3 15 30 PM