Ваши ставки, господа, бота радуют всегда! Пишем бота для partypoker.com
Содержание статьи
Онлайн-покер с каждым днем набирает все большую популярность. Это и неудивительно, ведь азартные игры всегда манили людей возможностью быстрой и легкой наживы. В результате — появление на просторах Сети покерных ботов, способных самостоятельно вести игру.
Но, как известно, в последнее время крупнейшие покер-румы начали активно закручивать гайки, выкидывая любителей нечестной игры из-за столов. В такой ситуации разработка покер-ботов превращается в весьма неблагодарное занятие, а порой и просто в пустую трату времени. Казалось бы, овчинка не стоит выделки, и тему можно смело закрывать. Однако не стоит опускать руки раньше времени. Устраивайся поудобнее, сейчас я покажу тебе, как научить собственного бота играть на partypoker.com :).
Flop aka вливаемся в игру
Как ты уже понял, речь в статье пойдет о написании покерного бота. Он рассчитан на работу во время твоего сна/отдыха/etc. Представь, что для улучшения своего благосостояния тебе достаточно лишь запустить софтину на забугорном дедике. Прогуливаешься в парке — бот работает, спишь — бот наигрывает кэш. Бот полностью имитирует действия игрока мышью, а всю информацию со стола собирает снимками необходимых областей.
Мы не будем лезть в код клиентского софта покер-рума. Это понижа ет удобство работы взамен на гарантии безопасности и стабильности функционирования бота. Однако все не так сложно, как тебе кажется :).
Turn — реализуем бота
Итак, перед запуском бота необходимо произвести следующие действия:
- Сливаем официальный бесплатный клиент с partypoker.com.
- Запускаем его и регистрируемся в покер-руме.
- В настройках клиента ставим четырехцветную колоду карт.
- Открываем четыре любых игровых стола и устанавливаем автоматическое расположение окон.
Обрати внимание, что окна должны располагаться именно в автоматическом режиме, иначе бот не сможет ориентироваться в игре. Бот имеет собственную панель управления. Она рассчитана на разрешение экрана 1280х1024, размещается ниже игровых столов и выше панели задач винды, что позволяет одновременно наблюдать за игрой и контролировать работу бота, не мешая ему считывать информацию со столов. Согласно концепции, бот может играть одновременно на четырех столах, для каждого из которых выделена область, активируемая кликом мыши. Таким образом, мы можем выбрать, на каких именно столах будет играть бот.
Подготовка завершена, но запускать бота пока рано, для начала разберемся в начинке софтины. Логика работы бота станет понятна после просмотра сорца Unit3.cpp. Для экономии места похожие строчки кода мы заменим на «. ».
Для удобства хранения инфы создадим четыре объекта TABLE, хранящие данные по каждому столу:
Позиция игрока за столом и предыдущие карты выставляются по-дефолту:
table1.position = «1»;
.
table1.last_cards = «start»;
.
Забираем из боксов и присваиваем позиции для каждого стола:
Запускаем основной цикл. Задержка в начале цикла выставлена не случайно. Дело в том, что работа с нашими снимками занимает приличное количество времени. Данная задержка оптимальна для рабочей лошадки P4 2800MHZ, 1ГБ ОЗУ.
Далее следует проверка и прорисовка в окне бота ситуации на столах:
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 (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();
Запускаем обработку имеющихся условий согласно выбранной стратегии. В данном случае стратегия игры будет напоминать шорт-стек на низких лимитах:
В результате мы имеем конкретное решение в свойстве table1.action:
Form1->Label38->Caption = table1.action.c_str();
Form1->Label58->Caption = table1.combination.c_str();
Решение — это хорошо, но от нас клиентская программа покеррума все еще ждет действий. Действуем согласно решению:
Не стоит забывать, что бот работает автономно, а владельцу нужно знать, откуда появились или куда были спущены его кровные. Поэтому делаем запись о происходящем в файл статистики:
Последние карты, с которыми мы играли, записываем в свойство стола.
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))
else if (CHECK_MD5_SVV(«.\ET\ET_A_k.bmp», new_path))
else if (CHECK_MD5_SVV(«.\ET\ET_A_ch.bmp», new_path))
else if (CHECK_MD5_SVV(«.\ET\ET_A_b.bmp», new_path))
// К
.
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))
// не обращаем внимания на круг торговли
// и сразу идём 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 имеет защиту от снятия информации со стола посредством снимка изображения. Пару раз в месяц наши друзья (привет администрации пати-покера!) меняют изображения трех-четырех карт, в результате чего работоспособность бота нарушается. Проблема решается довольно просто: достаточно заменить старые шаблоны карт на новые.
Напоследок отмечу, что написанный бот — всего лишь инструмент для получения фантиков с изображением американских президентов. Истинное удовольствие от игры в покер ты можешь испытать, только играя сам.
Источник статьи: http://xakep.ru/2011/10/03/57044/
Написание покерного бота
Внимание материал носит чисто ознакомительный характер, и автор не несет ответственности за закрытие аккаунтов покерными румами. По законам стран создание и использование ботов не запрещено, однако по правилам покерных румов они запрещены.
В данной статье полных исходных кодов не будет, только теоретически что и как можно использовать, и некоторые куски функций. Если ты интересуешься этим, тебе не составит собрать все в единую картину и написать свое. Так же не буду учить стратегиям игры, про термины или стратегии а так же правила можно в интернете найти много информации.
Немного истории.
Начал играть около 5 лет назад, за это время отыграно более 1 миллиона рук. В основном это No Limit Holdem (нелимитированный холдем) за короткими столами (от 2 до 6 человек за столом), есть опыт игры в Омаху, HU NL Holdem. Игра вроде как хобби и отдых. И вот как то с друзьями появилась идея написать бота, первая идея была написать под лимит холдем, и когда наполовину бот был написан, приняли закон запрещающий играть американцам в покер, в итоге с лимитом закинули бота. Первая версия была написано на нейронной сети с распознаванием образов, что это значит – обучили бота распознавать по снимкам с экрана карты, но это сами понимаете неточный метод, но при достаточно хорошем обучении распознавал с точностью 98-99%.Далее версии ботов уже работали на прямую с окнами румов, используя их ресурсы., а так же в тандеме с программами для анализа и сбора статистики игры.
Покер румы не спят.
Практически во всех покер румах есть та или иная защита и система обнаружения покерных ботов. Рассмотрим часть из них. Долгая игра – когда человек слишком долго играет, становится подозрительным, поэтому в некоторых румах могут появляться окошки с вопросами. Как защита не делать долгих сессий игры. Действия за столом – нажатие кнопок, выбор окон, действий не должно происходить не передвигая мышь, в свернутом окне. Траектории мыши лучше задать немного нелинейно, нажатия в разные места кнопок, скорость движения тоже не моментальная. Сканы запущенных процессов и скриншоты экрана – защита как говорил выше, не играть в свернутых окнах, процесс бота называть не покер-бот, и тому подобное, не оставлять окно бота развернутым на экране, имя процесса менять спустя какое то время (например через х минут перезапустить бота с новым именем процесса).
Схема покер бота.
Схему можно разделить на 3 части:
Блок 1 — блок взаимодействия с клиентом для игры в покер.
Блок 2 — блок принятия решения.
Блок 3 — блок сбора статистики, на мой взгляд лучше использовать сторонний софт, например PokerTracker3, отключив при этом вывод статистики на экран.
Далее рассмотрим каждый блок более подробно.
Блок 1 — блок взаимодействия с клиентом для игры в покер.
Этот блок служит для сбора информации на игровом столе и передачи в блок принятия решения, а так же получив ответ с решением выполнить то или иное действие как Fold, Raise, Call или All-In. Теперь рассмотрим этот блок. Часть этой части это взаимодействие с главным окном программы, это такие действия как выбор лимита, выбор стола за которым будем играть, это тоже большая часть, но на ней не будем останавливаться. Рассмотрим более подробно часть уже со столом. Сначала нам нужно найти хэндлы всех игровых открытых столов (окон), можно это сделать при помощи функции EnumWindows.
Функция EnumWindows перечисляет все окна верхнего уровня на экране, передавая дескриптор каждого окна, в свою очередь, в определяемую программой функцию повторного вызова. EnumWindows действует до тех пор, пока последнее окно верхнего уровня не будет перечислено, или пока функция повторного вызова не возвратит значение ЛОЖЬ (FALSE).
Параметры:
lpEnumFunc — указывает на определяемую программой функцию повторного вызова. Для получения дополнительной информации, см. функцию повторного вызова EnumWindowsProc.
lParam — устанавливает 32-разрядное, определяемое программой значение, которое будет передано в функцию повторного вызова.
Возвращаемые значения: если функция завершилась успешно, возвращается значение отличное от нуля. Если функция потерпела неудачу, возвращаемое значение — ноль.
Если функция отработала успешно, мы можем получить имя окна GetWindowText, и проанализировав его понять нужное или нет окно, обычно в заголовке окна присутствует ваш ник, название покер рума и лимит. Далее не мешало бы хэндлы всех найденных окон сохранить, для дальнейшего использования, без повторного поиска, поиск нужно будет делать лишь в случае открытия нового окна.
Теперь когда мы имеем хэндл игрового окна мы можем вытягивать информацию о столе. Большая часть информации хранится в дилерском окне. Из него мы можем узнать все участников за столом, кто зашел, кто вышел со стола, кто сделал какое действие, карты на столе и свои карты. Для этого нам понадобится парсер текста, как его писать рассказывать не буду, это отдельная тема, главное что идея ясна J. Но перед парсером нам нужно сначала найти элементы окна, и среди них найти дилерское окошко. Очень часто дилерское окно это класс производный от Internet Explorer_Server, чтобы его найти воспользуемся функцией EnumChildWindows, а затем GetClassName.
EnumChildWindows перечисляет дочерние окна, которые принадлежат определенному родительскому окну, в свою очередь, передавая дескриптор каждого дочернего окна в функцию повторного вызова, определяемую программой. Функция EnumChildWindows работает до тех пор, пока не будет перечислено последнее дочернее окно или функция повторного вызова не возвратит значение ЛОЖЬ (FALSE).
Параметры:
hWndParent — идентифицирует родительское окно, чьи дочерние окна должны перечисляться.
lpEnumFunc — указывает на определяемую программой функцию повторного вызова. Для получения дополнительной информации относительно функции повторного вызова, см. функцию повторного вызова EnumChildProc.
lParam — устанавливает 32-разрядное, определяемое программой значение, которое будет передано в функцию повторного вызова.
Возвращаемые значения: если функция завершилась успешно, возвращается значение отличное от нуля. Если функция потерпела неудачу, возвращаемое значение — ноль.
Теперь можно приступать и к парсингу текста. Наш ход или нет можно анализировать исходя из того какие кнопки доступны нам в данный момент. Это можно сделать как через ресурсы так и и просто по точкам на экране в этом окне. Далее если наш ход то передаем известные данные в блок принятия решения. При получении ответа делаем соответствующее действие. В этой части еще не рассмотрено как нажимать на кнопки, но это думаю и сами осилите.
Блок 2 — блок принятия решения
Этот блок, сердце всего бота, т.к. от него зависит насколько он успешен в игре, насколько правильные принимает решения. В журнале Хакер 137 от 06.2010 рассматривался пример полностью опирающийся на теорию вероятности и это хорошо, но чтобы получить достаточно достоверный результат надо провести достаточно много итераций. Чтобы играть достаточно уверенно против противника мы должны знать его диапазон и как он играет(это кусок привязан к статистике), так же есть определенные стратегии игры, зависящие от количества денег у вас, от количества игроков за столом, и самой стратегии игры. От всего этого зависит стартовый диапазон рук, и действия на последующих улицах, например человек играющий по стратегии коротких стеков (SSS) редко делает какие то ходы на ривере (когда выложена 5 карта на стол), т.к. к этому моменту он часто уже в Аллине (пошел в ва-банк). Оценка ситуаций так же идет в подсчете аутов, ауты это количество карт, которое улучшит наше положение на последующих улицах, отсюда вытекаю такие комбинации как стрит дро, стрит в дырку, флэшь дро и т.д. Дро это тоже комбинация и в зависимости от количества аутов имеет цену.И имея определенную комбинацию, даже в данный момент не победную, мы уже строим линию игры. Благодаря этим дополнительным данным мы уже можем принимать решения в зависимости от выбранной стратегии, если добавить поверх еще статистику игрока они будут еще точнее, а при добавлении полного просчета эту выборку еще можно дополнить. Надеюсь донес свою мысль о сокращении выборок и принятия решений, все это складывается на личном опыте игры и принятия решения, т.к. в несколько предложений не расскажешь теории игры в покер, которая издается во многих томах, о разных ситуациях, который бесчисленное множество, хотя можно и выделить по основным группам…
Сначала нам надо узнать ситуацию на столе, и узнать какая комбинация у нас имеется. Вот пример кода определения комбинации(переписал более понятно, но может не оптимально):
В итоге в соответствующих переменных мы имеем данные о текущем положении дел. При чем заметьте понятия как фул хауз нету, это производная из пара и трипс или сет. Тут еще нужен кусок который определяет готовность руки, т.е. готовая, не готовая, полуготовая. Например такой код:
Исходя из этих данных идем к части, которая получает статистику об оппонентах, расчеты о шансах банка и другие необходимые, принимаем решение о действии и переходим к первому блоку – выполнить действие.
Блок 3 — блок сбора статистики
Это немаловажный блок, помогающий в принятии решений, вы можете как сами собирать статистику, как воспользоваться сторонними программами. Я опишу пример запроса к PokerTracker3, программа для сбора и анализа статистики игры, параметров в программе более 100, но нам хотя бы для примера достаточно и нескольких основных. В PokerTracker3 база хранится в Postges базе. Пример запроса:
На выходе получим по определенному игроку выборку, сайтов на которых такой ник имеется в базе, можно сократить по нашему сайту только, количество рук оппонента (нужна для точности показаний), VPIP(% рук с которыми заходит в игру), PFR (preflop raise –префлоп рейз ), ATS (attemptto steel – сколько крадет блайндов на префлопе), CBET (c-bet on flop – ставка в продолжение на флопе),BBS (big blind steel –кража большого блайнда ), SBS (small blind steel – кража малого блайнда). Эти параметры в основном используются при расширенной стратегии коротких стеков, для полных стеков этих параметров мало, но в качестве примера как их получить хватит. Для чего все эти параметры нужны и как их использовать советую почитать соответствующие ресурсы.
После того как мы получили дополнительную статистику переходим ко второму блоку и корректируем наше решение об игре.
В заключение.
На написание этой статьи подвинули статьи из журнала Хакер 137 от 06.2010 «Натягиваем сетевые poker room’ы» и Хакер 139 от 08.2010 «Симуляция покерного оргазма». Так как с автором я не полностью согласен и тему считаю полностью не раскрытой и хотелось поделиться своим мнением.
Источник статьи: http://habr.com/ru/post/130805/