Онлайн-покер с каждым днем набирает все большую популярность. Это и неудивительно, ведь азартные игры всегда манили людей возможностью быстрой и легкой наживы. В результате — появление на просторах Сети покерных ботов, способных самостоятельно вести игру.

Но, как известно, в последнее время крупнейшие покер-румы начали активно закручивать гайки, выкидывая любителей нечестной игры из-за столов. В такой ситуации разработка покер-ботов превращается в весьма неблагодарное занятие, а порой и просто в пустую трату времени. Казалось бы, овчинка не стоит выделки, и тему можно смело закрывать. Однако не стоит опускать руки раньше времени. Устраивайся поудобнее, сейчас я покажу тебе, как научить собственного бота играть на partypoker.com :).

 

Flop aka вливаемся в игру

Как ты уже понял, речь в статье пойдет о написании покерного бота. Он рассчитан на работу во время твоего сна/отдыха/etc. Представь, что для улучшения своего благосостояния тебе достаточно лишь запустить софтину на забугорном дедике. Прогуливаешься в парке — бот работает, спишь — бот наигрывает кэш. Бот полностью имитирует действия игрока мышью, а всю информацию со стола собирает снимками необходимых областей.

Мы не будем лезть в код клиентского софта покер-рума. Это понижа ет удобство работы взамен на гарантии безопасности и стабильности функционирования бота. Однако все не так сложно, как тебе кажется :).

 

Turn — реализуем бота

Итак, перед запуском бота необходимо произвести следующие действия:

  1. Сливаем официальный бесплатный клиент с partypoker.com.
  2. Запускаем его и регистрируемся в покер-руме.
  3. В настройках клиента ставим четырехцветную колоду карт.
  4. Открываем четыре любых игровых стола и устанавливаем автоматическое расположение окон.

Обрати внимание, что окна должны располагаться именно в автоматическом режиме, иначе бот не сможет ориентироваться в игре. Бот имеет собственную панель управления. Она рассчитана на разрешение экрана 1280х1024, размещается ниже игровых столов и выше панели задач винды, что позволяет одновременно наблюдать за игрой и контролировать работу бота, не мешая ему считывать информацию со столов. Согласно концепции, бот может играть одновременно на четырех столах, для каждого из которых выделена область, активируемая кликом мыши. Таким образом, мы можем выбрать, на каких именно столах будет играть бот.

Подготовка завершена, но запускать бота пока рано, для начала разберемся в начинке софтины. Логика работы бота станет понятна после просмотра сорца Unit3.cpp. Для экономии места похожие строчки кода мы заменим на «…».

Для удобства хранения инфы создадим четыре объекта TABLE, хранящие данные по каждому столу:

TABLE table1;
...

Позиция игрока за столом и предыдущие карты выставляются по-дефолту:

table1.position = "1";
...
table1.last_cards = "start";
...

Забираем из боксов и присваиваем позиции для каждого стола:

table1.position = Form1->Edit1->Text.c_str();
...

Запускаем основной цикл. Задержка в начале цикла выставлена не случайно. Дело в том, что работа с нашими снимками занимает приличное количество времени. Данная задержка оптимальна для рабочей лошадки P4 2800MHZ, 1ГБ ОЗУ.

while(true)
{
Sleep(2000);

Далее следует проверка и прорисовка в окне бота ситуации на столах:

check_situation(table1.situation, table2.situation, table3.situation, table4.situation);
Form1->Label34->Caption = table1.situation.c_str();
Form1->Label35->Caption = table2.situation.c_str();
Form1->Label36->Caption = table3.situation.c_str();
Form1->Label37->Caption = table4.situation.c_str();

И, наконец, обработка каждого стола. Рассмотрим на примере первого. Для начала проверим, требуется ли от бота игра на этом столе. Это удобно, поскольку можно отключать бота от стола и играть вручную:

if (table_1_start == "go") {

Проверяем ситуацию — требуется ли от бота принятие каких-либо решений, или сейчас ходят другие игроки:

if (table1.situation=="check" ||
table1.situation == "call_0.10" ||
table1.situation=="call_0.05" ||
table1.situation=="call_many" ||
table1.situation=="allin") {
Обнуляем параметры стола:
table1.combination = "--";
table1.action = "--";

Проверяем и прорисовываем карты игрока и карты на столе:

check_p_cards( 1, table1.p_card_1, table1.p_card_2);
Form1->Label26->Caption = table1.p_card_1.c_str();
Form1->Label27->Caption = table1.p_card_2.c_str();
check_t_cards( 1,table1.t_card_1,table1.t_card_2, table1.t_card_3,table1.t_card_4, table1.t_card_5);
Form1->Label11->Caption = table1.t_card_1.c_str();
Form1->Label12->Caption = table1.t_card_2.c_str();
Form1->Label13->Caption = table1.t_card_3.c_str();
Form1->Label14->Caption = table1.t_card_4.c_str();
Form1->Label15->Caption = table1.t_card_5.c_str();

Определяем место игрока в данной раздаче (большой блайнд/малый блайнд/etc):

check_position(1, table1);
Form1->Label62->Caption = table1.position.c_str();

Запускаем обработку имеющихся условий согласно выбранной стратегии. В данном случае стратегия игры будет напоминать шорт-стек на низких лимитах:

shortstack(1, table1);

В результате мы имеем конкретное решение в свойстве table1.action:

Form1->Label38->Caption = table1.action.c_str();
Form1->Label58->Caption = table1.combination.c_str();

Решение — это хорошо, но от нас клиентская программа покеррума все еще ждет действий. Действуем согласно решению:

mouse_click(1, table1);

Не стоит забывать, что бот работает автономно, а владельцу нужно знать, откуда появились или куда были спущены его кровные. Поэтому делаем запись о происходящем в файл статистики:

write_stat(1, table1);

Последние карты, с которыми мы играли, записываем в свойство стола.

table1.last_cards = table1.p_card_1 + table1.p_card_2;

На этом основной цикл заканчивается. Рассмотрим используемые функции более подробно. Создадим функцию для получения нужной нам картинки. В переменной outfile_name получим название файла, куда будет необходимо сохранить снимок. Здесь startX и startY — координаты верхней левой точки прямоугольника с высотой height и шириной width.

void PRINT_RECT_SVV (char* outfi le_name, int startX, int startY, int width, int height)
// Функция GetDC извлекает дескриптор
// дисплейного контекста устройства. 0 - экран
{
HDC hdc = GetDC(0);
if (hdc) //если дескриптор успешно получен
{
Graphics::TBitmap* bmp = new Graphics::TBitmap();
__try {
bmp->Width = width;
bmp->Height = height;
// Копиpует каpту бит из hdc в bmp, выполняя
// указанную pастpовую операцию, в данном случае SRCCOPY
BitBlt(bmp->Canvas->Handle, 0, 0, width, height, hdc, startX, startY, SRCCOPY);
bmp->SaveToFile(outfi le_name);
//сэйв BMP
}
__finally {
delete bmp; //освобождаем память
}
}
}

Теперь, когда у нас есть возможность сохранять нужные нам снимки, возникает вопрос: как установить их идентичность? Можно попиксельно сравнивать растровые изображения, но это займет много процессорного времени. Мы возьмем за основу совпадения контрольных MD5-сумм у двух изображений, пути к которым передаются как входящие параметры, а факт совпадения будет отражаться в возвращаемом функцией значении.

Непосредственно для подсчета MD5-сумм будем использовать готовую функцию, заново изобретать велосипед ни к чему:

bool CHECK_MD5_SVV (char* ET_file, char* newfile)
{
md5wrapper md5;
// Получим хэш сравниваемого файла
std::string hash1 = md5.getHashFromFile(newfile);
// Получим хэш файла, содержащего шаблон
std::string hash2 = md5.getHashFromFile(ET_file);
// Сравним хэши
if (hash1==hash2) return true;
else return false;
}

Сравниваем контрольные суммы:

void check_this_card (char* new_path, string &card) {
// A
if (CHECK_MD5_SVV(".\ET\ET_A_p.bmp", new_path))
{card = "Ap"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_k.bmp", new_path))
{card = "Ak"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_ch.bmp", new_path))
{card = "Ach"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_b.bmp", new_path))
{card = "Ab"; }
// К
...
else { card = "--"; }
}

Далее для наглядности кода (а значит, и для удобства работы) создадим функции проверки карт игрока и карт на столе. Принцип тот же — получаем снимок, сравниваем с шаблоном:

void check_p_cards(int table, string &card1, string &card2) {
if (table==1) {
//скринить первую карту игрока
PRINT_RECT_SVV(".\ET\ch_card1_t1.bmp", 37,150,12,22);
//скринить вторую карту игрока
PRINT_RECT_SVV(".\ET\ch_card2_t1.bmp", 55,150,12,22);
//распознать первую
check_this_card(".\ET\ch_card1_t1.bmp", card1);
//распознать вторую
check_this_card(".\ET\ch_card2_t1.bmp", card2);
}
if (table==2) {
...
}
void check_t_cards (int table, string &card1, string &card2, string &card3, string &card4, string &card5) {
if (table==1) {
PRINT_RECT_SVV(".\ET\t1c1.bmp",198,154,12,22);
PRINT_RECT_SVV(".\ET\t1c2.bmp",249,154,12,22);
PRINT_RECT_SVV(".\ET\t1c3.bmp",300,154,12,22);
PRINT_RECT_SVV(".\ET\t1c4.bmp",351,154,12,22);
PRINT_RECT_SVV(".\ET\t1c5.bmp",402,154,12,22);
//распознать
check_this_card(".\ET\t1c1.bmp", card1);
check_this_card(".\ET\t1c2.bmp", card2);
check_this_card(".\ET\t1c3.bmp", card3);
check_this_card(".\ET\t1c4.bmp", card4);
check_this_card(".\ET\t1c5.bmp", card5);
}
if (table==2) {
...
}

Не сможем мы обойтись и без функции проверки стола. Координаты и размер области, по которой идентифицируется стол, можно задавать на свой вкус, я предпочел не экономить на размере:

bool is_a_table (int table_number) {
if (table_number==1) {
PRINT_RECT_SVV(".\ET\is_a_table_1.bmp",5,5,95,25);
if (CHECK_MD5_SVV(".\ET\ET_is_table.bmp", ".\ET\is_a_table_1.bmp")) return true;
else return false;
}
if (table_number==2) {
...

Имитировать действия игрока будем программно, двигая курсор и кликая мышью. Естественно, не абы-куда, а по кнопочке, определяемой стратегией.

void mouse_click (int table_number, TABLE &this_table) {
...
if (this_table.action == "fold") {
SetCursorPos(x+380, y+410);
mouse_event(MOUSEEVENTF_LEFTDOWN, x+380, y+410,0,0);
Sleep(100);
mouse_event(MOUSEEVENTF_LEFTUP, x+380, y+410, 0, 0);
}
...
}

Как ты уже догадался, вся необходимая для игры на конкретном столе инфа хранится в экземпляре класса TABLE. Он предельно прост и откомментирован, поэтому оставим его на самостоятельное изучение. Также не будем рассматривать описание функций ведения статистики, определения ситуации на столе и позиции игрока.

Теперь: mov ah,86h; mov dx,cx; int 15h.

«Бред!» — скажешь ты, и будешь абсолютно прав! Если ты читаешь эти строки – значит, ты прошел почти весь долгий путь создания бота и можешь сделать передышку :). Но расслабляться все еще рано, впереди нас ждет самый ответственный этап — анализ и разработка стратегии игры. Все начинающие игроки, как правило, изучают стратегию коротких стеков (shortstack). Рассмотрим ситуацию на префлопе (карты розданы игрокам, но на столе все еще пусто), когда у нас на руках «карманка» (пара карт одинакового ранга).

// Итак, удостоверяемся что на столе нет карт
if (this_table.t_card_1 == "--") {
// А на руках у нас карты одинакового ранга:
if (card_rank(this_table.p_card_1)== card_rank(this_table.p_card_2)) {
//Если кто-то до нас повысил ставки
// или нас заставляют пойти ва-банк
if ((this_table.situation == "call_many") || (this_table.situation == "allin")) {
//Если карманка выше восьмерок и это уже
// второй круг торговли, идем ва-банк (all in)
if ((card_rank(this_table.p_card_1)>=9) && (this_table.trade_cycle>=2))
{this_table.action = "allin";}
// Если карманка начиная с десяток —
// не обращаем внимания на круг торговли
// и сразу идём all-in
else if (card_rank(this_table.p_card_1)>=10)
{ this_table.action = "allin"; }
else { this_table.action = "fold"; }
//Если до нас никто внятно не рейзил
// (ставка была не больше размера большого блайнда)
} else if ((this_table.situation == "check") || (this_table.situation == "call_0.05")||
(this_table.situation == "call_0.10")) {
// Если мы находимся в ранней
// позиции (с нас начинаются торги)
...

Заметь, мы рассмотрели лишь одну ситуацию. Полные сорцы стратегии, как и всего бота, я заботливо приготовил для тебя на диске. Разработка стратегии является ключевым этапом создания бота. От ее качества напрямую зависит твой возможный доход. Мой вариант позволяет боту играть в плюс на низких лимитах, но это далеко не предел. Чем больше усилий ты вложишь в разработку собственной стратегии, тем больше нулей будет появляться на твоем счету :).

 

River: all-in

Тестируя бота на протяжении двух месяцев, я заметил одну интересную деталь: PartyPoker имеет защиту от снятия информации со стола посредством снимка изображения. Пару раз в месяц наши друзья (привет администрации пати-покера!) меняют изображения трех-четырех карт, в результате чего работоспособность бота нарушается. Проблема решается довольно просто: достаточно заменить старые шаблоны карт на новые.

Напоследок отмечу, что написанный бот — всего лишь инструмент для получения фантиков с изображением американских президентов. Истинное удовольствие от игры в покер ты можешь испытать, только играя сам.

 

Tricks & Tips

  1. Тестируй технические детали действий бота в играх на фантики, а стратегии — в играх на реальные деньги. В первом случае ты сэкономишь деньги, а во втором — время. Дело в том, что в играх без реальных денежных затрат срабатывает психологический эффект aka «а и ладно, не корову проигрываю». Игроки действуют хаотично, не сбрасывают слабые руки, чаще блефуют и так далее.
  2. Регулярно просматривай статистику бота не только на предмет технических ошибок или неправильного следования стратегии, но и на предмет тенденций. Если игроки распознают шаблонное поведение спустя полчаса игры, они смогут использовать это в своих целях, и денежный счет твоего бота будет планомерно уменьшаться.
  3. Если есть возможность, используй несколько разных аккаунтов для покер-рума. Несмотря на то, что на многих ресурсах это запрещено правилами, никто не мешает сделать пару запасных акков — разумеется, исключительно ради спортивного интереса :).
  4. Не забывай, что администрация покер-рума может распознать работу бота по статистике, которая ведется для каждого игрока.
  5. Выбирай столы с новичками на низких ставках. Пусть мал выигрыш, зато част :).
 

Info

  • Помни, покер-румы регулярно обновляют свой софт, поэтому для полной работоспособности бота его необходимо периодически обновлять!
  • Не забывай, что сам бот — всего лишь автоматизированное средство для игры в покер, наиболее важная его часть стратегия игры.

Оставить мнение

Check Also

Цифровой паноптикум. Настоящее и будущее тотальной слежки за пользователями

Даже если ты тщательно заботишься о защите своих данных, это не даст тебе желаемой приватн…