Однажды, солнечным весенним утром, почитывая городской форум, я наткнулся на ссылку с простенькой игрой от известной торговой сети. Игра (акция), посвящённая чемпионату мира по футболу, представляла собой незамысловатое поле три на три, заполненное футбольными мячами. Кликая по мячу, мы открывали картинку с тем или иным товаром. При открытии трёх одинаковых картинок участнику гарантировалось бесплатное получение данного товара в одном из магазинов сети. Также под одним из мячей имелось изображение красной карточки, открытие которой означало конец игры.
Интерес к игре быстро угасал по причине крайне редких случаев выигрыша. Играя вчетвером с 6 номеров телефона за несколько дней (около 15 партий) выиграть не удалось никому. “А какова вообще вероятность выиграть в данной игре?” — спросил я себя и достал лист бумаги, на ходу вспоминая курс высшей математики. Расписывая формулы, выяснилось, что каждая игра может содержать от 1 до 9 ходов, а каждый ход приводит к одному из трёх состояний — победа, поражение или продолжение игры (за исключением первых двух ходов, которые могут привести только к двум состояниям). Быстро поняв, что формула для 9 полей слишком сложна, я начал с простого — 4 поля (три одинаковых продукта и красная карточка). Быстрый набросок формул на салфетке, и выяснилось, что вероятность выигрыша — 1/4. Для 5 полей пришлось повозиться, но расчётная вероятность получилась также 25%. В этот момент, я задумался и трижды перепроверил расчёт. Всё верно. Не сказать, что я сильно удивился, ещё со времён учёбы в ВУЗе я привык, что в теории вероятности возможны самые неожиданные результаты. Расчёт для 9 полей занял бы несколько листов бумаги и не один час времени, поэтому было принято более простое решение. Смоделировать игру скриптом. Несколько десятков минут, кружка кофе, и скрипт готов. Использовался PowerShell, как инструмент, который всегда под рукой у системного администратора.
$fail = 0
$win = 0
for ($m=1; $m -lt 1001; $m++)
{
$mas = 1, 2, 3, 4, 5, 6, 7, 8, 9
$sum = 0
$result = ""
for ($i=0; $i -lt 8; $i++)
{
$j = Get-Random -Minimum $i -Maximum 9
if ($mas[$j] -eq 9)
{
$result = "FAIL"
break
}
if ($mas[$j] -eq 1 -or $mas[$j] -eq 2 -or $mas[$j] -eq 3) { $sum++ }
if ($sum -eq 3)
{
$result = "WIN"
break
}
$mas[$j] = 0
$mas = $mas | Sort-Object
}
$result
if ($result -eq "WIN") { $win++ }
if ($result -eq "FAIL") { $fail++ }
}
$fail
$win
Выигрышные номера я принял за 1, 2 и 3, а красную карточку за 9. Забегу немного вперёд, как выяснилось позже, программисты, которые писали эту игру, мыслили примерно в том же ключе.
Запустив скрипт, я получил неожиданный результат — 25% выигрышей. Поиграв с количеством выигрышных элементов и общим количеством полей, я выяснил, что вероятность выигрыша в подобной игре не зависит от количества полей и равна единице, поделенной на количество выигрышных элементов, увеличенных на единицу.
В этот момент в мою голову закрались большие сомнения в честности игры. Ведь я должен был выигрывать каждый четвёртый раз. Но к тому времени я проиграл уже раз 10. Вероятность такого развития была крайне низка, и я начал исследовать скрипты игры.
А параллельно открыл правила.
Правила участия в маркетинговом мероприятии
1.3 Организатор гарантирует, что при определении возможности получения поощрений не используется алгоритмов или процедур, которые могут определить результаты Акции до начала ее проведения.
Отлично! Значит, доказав наличие этих алгоритмов и процедур, мы поймаем организатора за руку.
F12 в Chrome, и начинаем исследование. Играем до конца, одни глазом поглядывая в мониторинг сети. Достаточно стандартная работа приложения, загрузка страницы, скриптов, спрайтов и нескольких наборов данных в формате JSON. Но странное дело, от момента нажатия кнопки “Начать игру” до её завершения нет никакого обмена данными с сервером. Вторая странность — выигрышный спрайт даже не грузится, грузится только спрайт “Вы проиграли”. Очевидно, что скрипт ещё до начала игры “знает” её итог. Осталось поймать его за руку.
Основной JS с игрой очень большой, 1.5 Мб, без форматирования, всё «в кашу». Беглый поиск в Google выдал нам сервис JS Beautifier, и вот мы уже читаем отформатированный код. Но объём его очень велик, более 40 000 строк. Беглый просмотр JSON не дал результатов, слишком много данных, было решено идти другим путём — от обратного. Поиском по именам файлов спрайтов был найден JSON с нужными данными.
Спрайты 0, 1 и 2 — выигрышные, спрайт 3 — красная карта, остальные не имеют значения.
Поиск по имени массива спрайтов приводит нас в нужный блок скрипта игры.
Меня заинтересовал массив Outcome в скрипте, я ещё раз глянул в JSON, и о, чудо! Вы не поверите!
Да это же порядок выпадения спрайтов! И он заканчивается крайной картой! Абсолютно не важно, в какие поля вы кликаете мышкой, спрайты отображаются в заданном порядке, сгенерированном сервером. Сам сервер управляет процессом и регулирует вероятность. Никакой случайности не прослеживается.
Снова обратимся к правилам игры:
8. Призовой фонд:
8.1. Доступные призы: названия и количества
…
Итого 166000
Очевидно, что при честной игре призы закончатся ориентировочно через 664000 игр, что, видимо, не устроило руководство торговой сети с учётом времени проведения акции и потенциального количества участников.
Выводы (немного очевидные).
Играйте в честные оффлайн игры (например, шахматы). Не верьте организаторам онлайн-розыгрышей. Все врут. (с)
Автор: Александр Попов