Introduction

Today we're going to build a simple online poker bot—the simplest possible online poker bot—in just a few hundred lines of code.

But first: have you ever tinkered around with an erector set?

You remember. Colorful cardboard boxes with see-through fronts. Girders and gears, nuts and bolts, even the occasional electric motor.

A classic brand with over 90 years of building experience, Erector introduces an incredible 50-Model building set. It has a whopping 605 pieces for building 50 different models, including a helicopter, a construction truck with crane, a scooter and more. A powerful 6V motor brings life to your creations, and special mechanical functions create unique and exciting movements. Set includes an assortment of construction materials and fasteners, plus detailed, step-by-step instructions with photographs -- no reading required. Requires 3 "AA" batteries, not included.

The thing that always appealed to me about Erector Sets? Ingredients. Dozens of components, each with a specific, and yet generic, purpose. Not to mention the recipes: instructions for assembling those components. With a little time and some careful screwdriver work, you could build just about anything.

Charles Babbage, eat your heart out.

But what do erector sets have to do with poker, programming, and poker botting?

Well, a lot of people have written me or left comments to the effect of: show us the code, buddy. And for the past couple weeks I've struggled with how to do that.

First, there's a lot of code. We're talking a sizeable production code base, with various versions and branches, and a lot of half-finished one-off tools. Utility libraries, hand evaluators, history analyzers, DLL injectors, you name it.

Second, I don't have complete control over said code. Before too long, I'll introduce my poker buddy, botting partner, former client, and general co-conspirator, Mr. Anonymous. He has a stake in all this, too.

Third, the code is quite valuable on its face. Not because I'm such a superb programmer, or poker player—I've made more than my share of mistakes in both arenas—but because it's had the benefit of thousands of hours of work.

And as we start to publicize it, that value, that potential energy, depreciates.

So what to do?

Sell it? Open-source it? Discuss it?

And that's when a friend sent me a link to the above picture and I thought: aha!

Erector sets.

Self-Contained Experimental Botting Goodness

Programmers, like poker players, have a behavioral profile. They prefer a certain language, a certain methodology, a certain environment. My favorite languages are C# and C++. Yours might be Java and PHP.

When it comes to botting (or software automation in general) we're all hoping to take something different away from the table. Some come to learn. Some want to make money. Some have a product they'd like to build, or a built product they'd like to automate. Some are interested in poker, others spend their time on World of Warcraft, and still others invest their energy in the stock market. Some want specific code they can download, build, and use. Others just want the techniques.

So rather than posting isolated code samples without any context, and rather than posting a hundred thousand source lines of code without any focus, we'll take the middle ground: the software erector set. That's a self-contained package of ingredients—source code, tools, information—together with instructions for turning those ingredients into something real and, as we progress further into the series, something useful.

That means various flavors of bots:

FoldBots

TrashBots

Autofolders

ShortBots

DeepBots

General (non-poker) software automation tools

Various poker-related tools:

Simulators

Hand Evaluators

History Analyzers

Poker Calculators

And some other stuff as well.

Obviously this is a little much to handle in a single series—you'd end up with 400 articles, and I'd have to start saying things like: "In Part 267, I pointed out, for the fifty-sixth time, that DLL Injection isn't the only way to build a bot."

The poker botting series will continue indefinitely. But software automation and human emulation, poker-related and otherwise, will now be a primary content line on Coding the Wheel.

After all, that's kind of what the name means.

Or did you think I just woke up one day and decided to start blogging about poker bots?

The Simplest of All Possible Bots: Introducing the FoldBot

The first code sample/erector set I'd like to present is what happens when you start to apply iterative, incremental development to online poker botting. Before we tackle generic poker A.I., let's see if we can get a proof of concept up and running. Let's see if we can create a bot which:

Reads our hole cards

Clicks a button (it doesn't matter which)

This is what you might call the Ground Zero of software automation. It's where everything begins: software with the ability to see and touch. (And when I say "see" I don't mean image recognition, or at least, I don't mean image recognition exclusively. For a poker bot, "seeing" might be the ability to extract text from a window by snooping on window memory, sending a WM_GETTEXT, etc. But the end result is the same.)

Here's a picture of the FoldBot in action:

Simple. The FoldBot is the simplest of all possible online poker bots. It doesn't understand, want to understand, or need to understand, poker. It doesn't understand online poker. It doesn't even know how to recognize a poker hand. It only knows how to do three things:

INPUT: Extract your hole cards and other table information from one or more online poker tables.

PROCESSING: Decide to fold every hand (yes, even pocket Aces).

OUTPUT: Click the Fold button when it's your turn to act.

That's the simplest possible Input stage, the simplest possible Processing stage, and the simplest possible Output stage.

And yet, with some additional work, the FoldBot can become a TrashBot (a bot intended to fold garbage hands) which in turn becomes an Autofolder (a bot intended to automate preflop play) which in turn becomes a ShortBot, which in turn becomes a DeepBot capable of playing deep-stacked poker on all rounds of betting, and so forth. We'll explore all of these at some point, and we'll follow the long-established practice of giving short, often cute or cryptic, names to our bots.

Which Online Poker Venues Does It Support?

The FoldBot is specialized for the PokerTime online poker venue (http://www.pokertime.com) so you'll need to download the (free) software and set up a (play-money) account in order to see it in action.

Why are we using PokerTime, instead of a more popular venue like PokerStars or Full Tilt?

Because it makes our Input stage very simple.

But don't worry: we'll add support for Poker Stars, Full Tilt, and other major poker venues, shortly—in case they should feel left out of the botting extravaganza. Extracting text from Poker Stars is slightly more difficult than PokerTime, but only slightly. Botting rules apply here:

Botting Rule #68: It's virtually impossible for a program to display human-readable text on the screen without exposing that text to other programs running on the machine. You might as well not even try.



This is especially the case when the text has to be easy to read, as it does in online poker, where 4x and 8x multi-tabling is commonplace. But even if we allow the text to be difficult to read, well, we're fast approaching the time where if a human can read it, software can too. As Jeff Atwood points out in CAPTCHA Is Dead, Long Live CAPTCHA!, even CAPTCHAs which were once thought to be unbreakable—Google, Yahoo, Hotmail—have since been broken.

2008 is shaping up to be a very bad year indeed for CAPTCHAs: Jan 17: InformationWeek reports Yahoo CAPTCHA broken

Feb 6: Websense reports Hotmail CAPTCHA broken

Feb 22: Websense reports Google CAPTCHA broken Which means I am now 0 for 3. Understand that I am no fan of CAPTCHA. I view them as a necessary and important evil, one of precious few things separating average internet users from a torrential deluge of email, comment, and forum spam.

In fact, I believe the text-only CAPTCHA is doomed to failure, as a long-term mechanism for separating the humans from the bots.

But I digress. On to the code.

About the Code

Download the FoldBot Source Code (C++/Windows 145KB).

The FoldBot code consists of two small projects, both implemented in C++.

The purpose of each project is straightforward:

XPokerBot.MfcView is the (simple and minimalistic) GUI. This is a standard Windows executable (.EXE).

XPokerBot.Hook is the payload containing the meat of the bot's "intelligence". This is a standard Windows dynamic-link library (.DLL).

In order to build them, you'll need:

Microsoft Visual Studio 2003, 2005, or 2008. VS2005 and VS2008 are both available as fully-functional, 90-day trial editions.

The C++ Boost libraries (for regular expression support).

This version of the bot has only been tested on Windows XP, so if you're running Vista or Windows 2000, you might have to do a little tweaking.

But far and away the most difficult part of building the source code is dealing with the Boost libraries. For step-by-step instructions, check out Boost: Getting Started on Windows. If you run into specific problems, you can try posting to the Boost Configuration forums on Nabble. And if all else fails, why, you can simply comment out the Boost code. It's only used in a couple places, for regular expression support. Rewriting that code to use plain vanilla string manipulation should be straightforward.

How It Works

The FoldBot, while simple, makes use of some interesting techniques:

Windows Hooks

DLL Injection

Window Subclassing

Rich Text Interception

Interprocess Communication (IPC)

Regular Expressions

Input Simulation

Here's what it does in a nutshell:

Inject the botting DLL into the poker client's address space. Respond to (poker table) window create notifications via the CBT hook. Subclass the poker table's (RichEdit control) chat window. Intercept chat window text by listening for and parsing EM_STREAMIN messages. Parse the text using simple regular expressions. Simulate input (clicking the Fold button) at the right time, based on those messages. Transmit hole card and table information to the poker bot GUI via simple IPC.

Not too bad. No rocket science here. Now let's look at the individual steps.

Step 1: Inject the DLL

The first thing the bot does is inject a DLL (XPokerBot.Hook.dll) into the poker client process, using the same CBT Hook injection technique I mentioned in Part 1:

bool XPOKERBOTHOOK_API InstallHook ( )

{

g_hHook = SetWindowsHookEx ( WH_CBT, ( HOOKPROC ) CBTProc, g_hInstance, 0 ) ;



return g_hHook ! = NULL;

}

That causes the DLL to be mapped into the address space of every process on the machine, including the poker client. Normally we might be concerned about overhead, but this DLL is fairly small. More to the point, it's almost completely inert when instantiated in an irrelevant (non-poker) process. As we start adding functionality to the DLL and it grows larger, we can switch to the two-stage injection approach:

But for now it's not necessary.

Step 2: Detect the opening and closing of poker windows

Another benefit of the CBT Hook approach is that it gives us a handy way to detect the opening and closing of poker table windows, and any other top-level windows of interest:

LRESULT CALLBACK CBTProc ( int nCode, WPARAM wParam, LPARAM lParam )

{

if ( nCode < 0 )

return CallNextHookEx ( g_hHook, nCode, wParam, lParam ) ;

else if ( ! g_pClient )

return 0 ;



// Window was activated. We currently use this to detect window creation also.

// Why not use HCBT_CREATE? Because often the window doesn't yet have a caption

// when HCBT_CREATE is called, and our sample window-detection logic looks

// at the window caption to determine the type.

HWND hWnd = ( HWND ) wParam;

if ( ! hWnd )

return 0 ;



if ( nCode == HCBT_ACTIVATE )

{

if ( ! g_pClient - > IsRegisteredWindow ( hWnd ) )

g_pClient - > TryRegisterWindow ( hWnd, NULL ) ;

}

else if ( nCode == HCBT_DESTROYWND )

{

if ( g_pClient - > IsRegisteredWindow ( hWnd ) )

g_pClient - > UnregisterWindow ( hWnd ) ;

}



// Return 0 to allow window creation/destruction/activation to proceed as normal.

return 0 ;

}

Here we can guess that that the HCBT_ACTIVATE message is sent when a given top-level window is activated, and that the HCBT_DESTROYWND message is sent when a given top-level window is closed. And we have some simple code to store or remove the window to or from the list of "managed" windows in each case.

The only thing to note here is that we can't really use HCBT_CREATEWND notification when we're answering the "is this a poker table window?" question by examining the window caption. That's because the window caption is usually empty at the time the HCBT_CREATEWND notification is sent. There are ways around this such as delayed creation but we don't need to go to such lengths, certainly not for this incarnation of the bot.

Step 3: Subclass the Chat Window

Now that we're able to detect when poker table windows are opened and closed, we need to extract text from the table chat window. The technique I'm describing here will only work on certain windows, such as the Windows rich edit control, so it may or may not be appropriate for a particular poker client or other gaming application.

Basically we want to subclass the chat window, listen for the EM_STREAMIN message, and replace the callback function provided by the poker client with one of our own. In this sample, I'm subclassing the window when it's first recruited into the poker botting runtime, but strictly speaking, the subclass doesn't have to be installed until the user/bot actually sits down, or even until the user issues a command saying, "okay, automate this table for me."

For each newly-created poker table window, the OnlinePokerClient::TryRegisterWindow method creates a corresponding OnlineTableWindow-derived object—in this case, a PokerTimeTableWindow object. During the construction of that object, we subclass the table's chat window, allowing us to intercept messages sent (by the PokerTime application) to this window:

PokerTimeTableWindow :: PokerTimeTableWindow ( HWND hWnd, PokerTimePokerClient * client ) :

OnlineTableWindow ( hWnd, client )

{

// Find the chat box...

HWND hwndChat = :: FindWindowEx ( hWnd, NULL , _T ( "RichEdit20W" ) , NULL ) ;

if ( hwndChat )

{

// And subclass it!

PokerTimeTableWindow :: OldRichWndProc = ( WNDPROC ) :: GetWindowLongPtr ( hwndChat, GWL_WNDPROC ) ;

:: SetWindowLongPtr ( hwndChat, GWL_WNDPROC, ( LONG_PTR ) PokerTimeTableWindow :: MyRichWndProc ) ;

}

}

Step 4: Intercept chat window text

Technically speaking, the chat text window (on PokerTime) is a Rich Edit control. In order to fill this window with text, the Poker Time client sends it a standard EM_STREAMIN message, which our code intercepts:

LRESULT PokerTimeTableWindow :: MyRichWndProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

{

EDITSTREAM * es = ( EDITSTREAM * ) lParam;



if ( msg == EM_STREAMIN )

{

// Store the original EDITSTREAMCALLBACK and replace it with our own..

PokerTimeTableWindow :: OldRichEditCB = es - > pfnCallback;

es - > pfnCallback = PokerTimeTableWindow :: MyEditStreamCallback ;

// Hack - keep track of current window

PokerTimeTableWindow :: CurrentChatWindow = hWnd;

}



// Call the original window procedure, with our new EDITSTREAMCALLBACK in place

LRESULT lRet = :: CallWindowProc ( PokerTimeTableWindow :: OldRichWndProc , hWnd, msg, wParam, lParam ) ;



if ( msg == EM_STREAMIN )

{

// Restore the original EDITSTREAMCALLBACK function

es - > pfnCallback = PokerTimeTableWindow :: OldRichEditCB ;

}



return lRet;

}

If you've ever worked with EM_STREAMIN before, you know that you have to provide it with a callback function—specifically, an EDITSTREAMCALLBACK function. That's just what the PokerTime client is doing. What we want to do is somehow replace that callback function (which we'll call A) with our own (which we'll call B). We want Windows to call our callback (B), which will in turn call the PokerTime callback (A) to retrieve the text. That's the purpose of these two lines of code:

// Store the original EDITSTREAMCALLBACK and replace it with our own..

PokerTimeTableWindow :: OldRichEditCB = es - > pfnCallback;

es - > pfnCallback = PokerTimeTableWindow :: MyEditStreamCallback ;



We're actually overwriting the EDITSTREAMCALLBACK member of the EDITSTREAM structure passed in by the client. So as far as Windows is concerned, it's processing a perfectly normal EM_STREAMIN message. And as far as the PokerTime client is concerned, it's sending a perfectly normal EM_STREAMIN message which it expects to be handled by Windows.

Step 5: Parse the Chat Text

In the meantime, we get a chance to parse the text generated by the poker client using a couple simple regular expressions:

DWORD CALLBACK PokerTimeTableWindow :: MyEditStreamCallback ( DWORD_PTR dwCookie, LPBYTE pbBuff, LONG numberOfBytes, LONG * actualBytes )

{

// Call the original (PokerTime-provided) callback to get the text..

DWORD dwRet = PokerTimeTableWindow :: OldRichEditCB ( dwCookie, pbBuff, numberOfBytes, actualBytes ) ;

if ( 0 == dwRet && actualBytes && * actualBytes > 0 )

{

//

// do some busy work...

//



// We're looking for the line (in the PokerTime text window) that looks like this:

//> Dealing Hole Cards(8d Kc )

boost :: smatch what;

if ( boost :: regex_match ( line, what, regHoleCards, boost :: match_default | boost :: match_single_line ) && what. size ( ) == 3 )

{

string sCard1 = what [ 1 ] ;

string sCard2 = what [ 2 ] ;



ApplicationProxy :: TransmitHoleCards ( ( sCard1 + sCard2 ) . c_str ( ) , hPokerTable ) ;

}

//> JohnDoe, you have 10 seconds to respond

//> JohnDoe, you have 5 seconds to respond

else if ( boost :: regex_match ( line, what, regWakeUp, boost :: match_default | boost :: match_single_line ) && what. size ( ) == 3 )

{

string theActor = what [ 1 ] ;

if ( theActor == g_pClient - > LoggedInAs )

OnlinePokerExecutor :: PerformAction ( hPokerTable ) ;

}



Specifically, we're looking for two pieces of text:

"Dealing Hole Cards(Ah Ad)"

"JohnDoe, you have 10 seconds to respond"

The equivalent regular expressions, stripped of the redundant C++ backslash escapes (really makes you long for the world of verbatim @ strings such as we enjoy in C#, doesn't it?), look like this:

^\s*>\s*Dealing\ Hole\ Cards(?:\((.{2,3})\ (.{2,3})\ \))?\s*$

^\s*>\s*(.*),\ you\ have\ (\d+)\ seconds\ to\ respond\s*$

There are better regular expressions out there, but these are just an example. Ultimately we'll want to create a generic regular expression engine which is powerful enough to analyze text from multiple poker venues, using named capture groups. But that's a topic for another post.

Step 6: Simulate User Input

The "JohnDoe, you have 10 seconds to respond" message is our v1.0 method of detecting when it's our turn to act. We're obviously going to have to improve on that before we turn the bot loose, but for now, it's an easy way to queue the logic to click the Fold button. Once we know it's our turn to act, we leverage our bot's impressive A.I...

Fold all hands without exception.

...and invoke our not-too-imaginatively named OnlinePokerExecutor object to perform the actual click of the Fold button.

//> JohnDoe, you have 10 seconds to respond

//> JohnDoe, you have 5 seconds to respond

else if ( boost :: regex_match ( line, what, regWakeUp, boost :: match_default | boost :: match_single_line ) && what. size ( ) == 3 )

{

string theActor = what [ 1 ] ;

if ( theActor == g_pClient - > LoggedInAs )

OnlinePokerExecutor :: PerformAction ( hPokerTable ) ;

}



The implementation of that, unfortunately, is a little messy. No matter; we can abstract the details of input simulation away into an easy-to-use class. For now, here's a hard-coded, somewhat kludgy attempt:

void OnlinePokerExecutor :: PerformAction ( HWND hPokerTable )

{

// Coordinates of our click spot (relative to poker table client area)

POINT ptCoords;

ptCoords. x = 290 ;

ptCoords. y = 513 ;

//ptCoords.x = 450;

//ptCoords.y = 354;



// Convert client coords to screen

:: ClientToScreen ( hPokerTable, & ptCoords ) ;



// Get the screen resolution

HDC hdc = :: GetDC ( NULL ) ;

int screenWidth = :: GetDeviceCaps ( hdc, HORZRES ) ;

int screenHeight = :: GetDeviceCaps ( hdc, VERTRES ) ;

:: ReleaseDC ( NULL , hdc ) ;



// Convert our screen coordinates to world coordinates

double temp1 = 65535 * ptCoords. x ;

double dX = temp1 / screenWidth;

temp1 = 65535 * ptCoords. y ;

double dY = temp1 / screenHeight;



// Now lets create our mouse inputs..

INPUT input [ 4 ] ;

MOUSEINPUT mouseInput;



// Move the mouse to the button...

input [ 0 ] . type = INPUT_MOUSE;

mouseInput. dx = ( int ) dX;

mouseInput. dy = ( int ) dY;

mouseInput. mouseData = NULL;

mouseInput. dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;

mouseInput. time = 0 ;

mouseInput. dwExtraInfo = 1001 ;

input [ 0 ] . mi = mouseInput;



// Left-click the mouse down

input [ 1 ] . type = INPUT_MOUSE;

mouseInput. dx = ( int ) dX;

mouseInput. dy = ( int ) dY;

mouseInput. mouseData = NULL;

mouseInput. dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;

mouseInput. time = 0 ;

mouseInput. dwExtraInfo = 1001 ;

input [ 1 ] . mi = mouseInput;



// ..and release it

input [ 2 ] . type = INPUT_MOUSE;

mouseInput. dx = ( int ) dX;

mouseInput. dy = ( int ) dY;

mouseInput. mouseData = NULL;

mouseInput. dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;

mouseInput. time = 0 ;

mouseInput. dwExtraInfo = 1001 ;

input [ 2 ] . mi = mouseInput;



// Move the mouse to where it was before

input [ 3 ] . type = INPUT_MOUSE;

mouseInput. dx = ( int ) dX;

mouseInput. dy = ( int ) dY;

mouseInput. mouseData = NULL;

mouseInput. dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;

mouseInput. time = 0 ;

mouseInput. dwExtraInfo = 1001 ;

//input[3].mi = mouseInput;



int numberOfInputs = 3 ;



// Remember which window has the focus, so we can restore it to that window.

GUITHREADINFO info;

:: ZeroMemory ( & info, sizeof ( info ) ) ;

info. cbSize = sizeof ( info ) ;

:: GetGUIThreadInfo ( NULL , & info ) ;



// Also get the current mouse position, so we can restore that.

POINT ptCursor;

if ( :: GetCursorPos ( & ptCursor ) )

{

numberOfInputs ++ ;



temp1 = 65535 * ptCursor. x ;

mouseInput. dx = ( int ) ( temp1 / screenWidth ) ;

temp1 = 65535 * ptCursor. y ;

mouseInput. dy = ( int ) ( temp1 / screenHeight ) ;

input [ 3 ] . mi = mouseInput;

}



// Make sure this window isn't obscured behind some other window

:: BringWindowToTop ( hPokerTable ) ;



// Now actually send the mouse input

:: SendInput ( numberOfInputs, input, sizeof ( INPUT ) ) ;



// Reset the focus to the previous window

if ( info. hwndFocus )

:: SetFocus ( info. hwndFocus ) ;

}

Another problem with the above input simulation is that the mouse coordinates (button locations) are hard-coded. This is really the type of data you want to "file and forget" in an external XML or other settings file—which is just what we'll do, in future versions of the bot. We'll also want to add random (or normal distribution) input timing along with a few other features. And of course, SendInput is just one way of several ways to simulate user input.

Step 7: Update the GUI

The last piece is to somehow get this data (our hole cards and open tables) to display in the bot's UI. The problem is that the bot user interface lives in one process, and our hook DLL lives in another. So whatever we do is going to involve some sort of inter-process communication or IPC. (Note, however, that you can create a full botting GUI, living entirely in the poker client's process, without a separate .EXE at all.)

For now, let's throw some "poor man's IPC" at the problem by manhandling the data into a WM_COPYDATA message. We'll look at cleaner and more robust approaches as needed.

void ApplicationProxy :: TransmitHoleCards ( LPCSTR cards, HWND hPokerTable )

{

HWND hWnd = :: FindWindow ( NULL , _T ( "FoldBot v1.0a" ) ) ;

if ( hWnd )

{

COPYDATASTRUCT cds;

:: ZeroMemory ( & cds, sizeof ( COPYDATASTRUCT ) ) ;

cds. dwData = ( DWORD ) hPokerTable;

cds. lpData = ( PVOID ) cards;

cds. cbData = strlen ( cards ) + 1 ;

:: SendMessage ( hWnd, WM_COPYDATA, ( WPARAM ) hPokerTable, ( LPARAM ) & cds ) ;

}

}

Meanwhile, the code sitting on the other side of that message can implement a WM_COPYDATA handler, extract the data, and display it appropriately.

Revisiting the Choice of Language

In Part One of this series, I suggested that the ideal language for bot-building was C++. A lot of people took that as a sort of language elitism. So I'd would now like to qualify that statement.

Build the bot in whatever language you're most comfortable with. When it comes to DLL injection, API hooking, log file snooping, stealth, and other "close to the metal" techniques, you'll find these are easier in C and C++ than in other languages (the greatest weakness of C/C++ also happens to be its greatest strength). It saves you the considerable trouble of accomplishing these things through P/Invoke or Interop or other virtualization layers. But the bot's domain modelling and A.I. aspects can and should be built in whatever langage you prefer. It depends on your level of comfort with your current language, with C/C++, and with the Windows API.

I used C++ (and later, C#) exclusively in the creation of the bot. That's C++ for the close-to-the-metal components, and C# for domain modelling and A.I. So the code samples I publish, today and in the future, will more or less reflect that. If you'd rather see these techniques in another language: what are you waiting for? The code isn't going to walk out of the compiler and migrate itself.

Disclaimer

Like many programmers, I have an almost superhuman ability to produce ugly, buggy, downright evil code. While the "production" botting source code has had the benefit of constant use and refactoring, the same can't be said for all of the samples I'll be publishing. In order to highlight particular techniques without burying innocent bystanders under an avalanche of sloc, I've had to manhandle the source in various ways:

By removing error handling.

By collapsing objects or object hierarchies into global variables or removing them entirely.

By occasionally introducing classes, methods, etc., which we'll end up throwing away as we put together more powerful abstractions.

By relying on quick hacks and naive solutions when necessary to keep the code samples relevant.

By severing important dependencies.

By ignoring bugs when they're irrelevant to the topic at hand.

And so forth. So it should go without saying that the code is intended to illustrate practical techniques, rather than proper coding style.

Using the FoldBot

The instructions for use are bundled with the software, but here are the basic steps.

Build the FoldBot. Download the PokerTime client. Create a play-money account. Launch the FoldBot. Launch the PokerTime client. Open one or more poker table windows. The table name(s) should appear in the Fold Bot GUI. Sit down at one or more tables and wait for a hand. Your hole cards should appear in the FoldBot GUI. When it's your turn to act, don't do anything. Wait. Continue waiting until the PokerTime client prompts you with the message "so-and-so, you have 10 seconds to respond". Your hand should now be folded. Leave the table(s). The table(s) should disappear from the FoldBot GUI.

The acid test for whether or not the FoldBot is actually working, is whether it displays PokerTime tables as you open them.

Look Ma! No Hands!

Even if you shut the FoldBot application (.EXE) down, it will continue folding your hands on any table which you opened up when the FoldBot was running. Any tables opened after you close the FoldBot GUI won't be folded.

This has to do with the fact that, although we "pin" the Hook DLL in the poker client's address space via a redundant call to LoadLibrary, the Windows CBT hook itself dies when the FoldBot GUI shuts down. So the Hook DLL is still mapped, but you no longer receive CBT notifications, which means new windows don't get subclassed. This behavior is pretty easy to change but it's good to see an EXE-less, GUI-less bot in practice.

Conclusion

Believe it or not, we covered a lot of ground today. Although this code isn't going to be too useful for established botters, except perhaps as a way to compare notes, it's illustrative. We'll take these techniques and add them to our arsenal for the next incarnation of the bot: the TrashBot, a.k.a., the first "useful" bot.

We'll also reintroduce Detours, with a twist. We'll talk about what poker has to do with the Fastest Gun in the West. And I might even tell you about how the Twelve of Spades changed my life, though I have a hard and fast rule to never, ever tell a bad beat story over the web. And at some point we'll have to take a stroll through the underbelly of poker, before too long. Unscrupulous business practices; routinely poor quality of service; and the fact that yes, sometimes your opponents really CAN see your hole cards.

Until then: thanks for reading, as always, and good luck to you in your poker and programming struggles.