Моделируем игру в рулетку на Python

Недав­но у нас была ста­тья о мате­ма­ти­че­ском ожи­да­нии в рулет­ке. Сего­дня мы смо­де­ли­ру­ем игру на рулет­ке и пока­жем силу мате­ма­ти­ки на прак­ти­ке. 

👉 Вкрат­це: что нуж­но знать. В рулет­ку мож­но выиг­рать в крат­ко­сроч­ной пер­спек­ти­ве, но когда игра­ешь дол­го, ты все­гда теря­ешь день­ги. Поче­му так: 

  • У рулет­ки 37 сек­то­ров: 18 крас­ных, 18 чёр­ных и зеро. Самая про­стая став­ка — на крас­ное или на чёр­ное. Если став­ка сра­бо­та­ла, то она удва­и­ва­ет­ся. Если не сра­бо­та­ла, то став­ка сго­ра­ет.
  • По идее, в поло­вине слу­ча­ев став­ка долж­на при­не­сти при­быль, поэто­му играть в эту игру долж­но быть выгод­но. Поста­вил боль­шую сум­му, удво­ил, забрал. На пер­вый взгляд, шанс — 50%.
  • Но на самом деле крас­ное или чёр­ное выпа­да­ют не в поло­вине слу­ча­ев, а чуть реже — за счёт зеро. Из-за него веро­ят­ность крас­но­го или чёр­но­го не 50%, а 48,6%.
  • Полу­ча­ет­ся, при иде­аль­но чест­ных руле­точ­ных усло­ви­ях про­иг­ры­вать мы будем все­гда немно­го чаще, чем выиг­ры­вать. И в дол­го­сроч­ной пер­спек­ти­ве мы будем все­гда немно­го терять день­ги. 

Но так ли это на самом деле? Может быть, здесь есть какой-то мате­ма­ти­че­ский трюк, из-за кото­ро­го мы видим иска­жён­ные резуль­та­ты? Давай­те постро­им модель и про­те­сти­ру­ем всё. 

Что проверяем

Мы смо­де­ли­ру­ем на Python игру в рулет­ку по трём стра­те­ги­ям с раз­ны­ми мато­жи­да­ни­я­ми и посмот­рим, как они вли­я­ют на резуль­тат. Что­бы было нагляд­нее, мы постро­им гра­фик ста­вок и выиг­ры­шей для каж­дой стра­те­гии. Python мы выбе­рем имен­но для того, что­бы быст­ро постро­ить гра­фик. 

В каж­дой стра­те­гии мы все­гда начи­на­ем с мил­ли­о­на руб­лей, а ста­вим одну тысяч­ную от этой сум­мы. Если захо­ти­те, мож­но будет потом поме­нять это в коде. 

Мы сде­ла­ем три моде­ли рулет­ки:

  1. Клас­си­ка. В пер­вом слу­чае мы смо­де­ли­ру­ем клас­си­че­скую рулет­ку как в кази­но с отри­ца­тель­ным мато­жи­да­ни­ем. Для это­го нам пона­до­бит­ся 18 чёр­ных цифр, 18 крас­ных и зеро, а ста­вить будем толь­ко, напри­мер, на чёр­ное. Тео­ре­ти­че­ски, играя по этой стра­те­гии, мы долж­ны со вре­ме­нем про­иг­рать.
  2. Во вто­рой стра­те­гии мы сде­ла­ем нуле­вое мато­жи­да­ние — убе­рём зеро. Это даст нам рав­ные шан­сы при каж­дой став­ке, поэто­му, ско­рее все­го, при том же коли­че­стве ста­вок, что и в пер­вый раз, мы оста­нем­ся с плюс-минус той же сум­мой. Не ров­но той же, а при­мер­но такой же. 
  3. Тре­тья стра­те­гия — дела­ем поло­жи­тель­ное мато­жи­да­ние. Для это­го мы уби­ра­ем зеро и одну крас­ную циф­ру, что­бы у нас было 18 чёр­ных и 17 крас­ных. Это зна­чит, что мы веро­ят­нее выиг­ра­ем, чем про­иг­ра­ем, поэто­му при том же коли­че­стве ста­вок мы долж­ны непло­хо зара­бо­тать.

Но это в тео­рии. Как полу­чит­ся на прак­ти­ке — сей­час про­ве­рим.

⚠️ Это про Python

Мы дела­ем про­ект на Python, для его запус­ка нуж­на пай­то­нов­ская сре­да и про­грам­мы. Мы об этом уже писа­ли, про­чи­тай­те: Как уста­но­вить Python и начать на нём писать.

Графики

Что­бы постро­ить гра­фи­ки, нам пона­до­бят­ся две биб­лио­те­ки — Plotly и Pandas. 

Plotly отве­ча­ет за гра­фи­че­скую часть — рису­ет кра­си­вые гра­фи­ки в бра­у­зе­ре с помо­щью JavaScript. Их мож­но уве­ли­чи­вать, выде­лять фраг­мент для ана­ли­за, ска­чи­вать себе как кар­тин­ки и срав­ни­вать дан­ные меж­ду собой. Когда нуж­но нари­со­вать какой-то гра­фик, Plotly запус­ка­ет бра­у­зер и созда­ёт локаль­ную стра­ни­цу с интер­ак­тив­ны­ми дан­ны­ми.

Что­бы вся эта магия с дан­ны­ми рабо­та­ла и гра­фи­ки обра­ба­ты­ва­лись пра­виль­но, для Plotly нуж­на биб­лио­те­ка Pandas. Она отве­ча­ет за мате­ма­ти­ку, ана­лиз и обра­бот­ку всех чисел на гра­фи­ке.

Уста­нав­ли­ва­ют­ся биб­лио­те­ки так:

pip install plotly
pip install pandas

Эти коман­ды мож­но запу­стить пря­мо в тер­ми­на­ле VS Code, если вы пише­те код в нём, а если нет — то в команд­ной стро­ке ком­пью­те­ра.

Подготовка

Всё, что пона­до­бит­ся для про­вер­ки каж­дой стра­те­гии, мы выне­сем в отдель­ные пере­мен­ные. Это поз­во­лит нам лег­ко поме­нять нуж­ные чис­ла при необ­хо­ди­мо­сти в одном месте, а не искать по всей про­грам­ме.

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

Напи­шем это на Python:

# подключаем модуль случайных чисел
import random

# подключаем модуль для графиков
import plotly
import plotly.graph_objs as go

# сколько денег будет на старте для каждой стратегии
startmoney = 1000000

# коэффициент ставки
c1 = 0.001

# количество побед и проигрышей
win = 0
loose = 0

# количество игр, сыгранных по первой стратегии
games = 0

# статистика для первой стратегии
balance1 = []
games1 = []

# статистика для второй стратегии
balance2 = []
games2 = []

# статистика для третьей стратегии
balance3 = []
games3 = []

Три стратегии с матожиданиями

В каж­дой стра­те­гии будем ста­вить на цвет — крас­ное или чёр­ное. Что­бы понять, сыг­ра­ла став­ка или нет, мы каж­дый раз будем брать слу­чай­ное чис­ло от 1 до 37: 18 чёр­ных, 18 крас­ных и зеро. Что­бы было про­ще, пусть чис­ла от 1 до 18 у нас будут чёр­ные, с 19 по 36 — крас­ные, а 37 будет зеро. 

Пер­вая стра­те­гия: берём всё как есть и про­сто бро­са­ем шарик. Если выпа­ло чёр­ное — мы выиг­ра­ли. В этой стра­те­гии у нас 18 выиг­рыш­ных вари­ан­тов и 19 про­иг­рыш­ных, поэто­му мато­жи­да­ние будет отри­ца­тель­ным.

Вто­рая стра­те­гия: уби­ра­ем зеро. У нас полу­ча­ет­ся по 18 чёр­ных и крас­ных чисел, и веро­ят­ность выиг­рать — ров­но 50 на 50. Это даёт нам нуле­вое мато­жи­да­ние.

Тре­тья: уби­ра­ем зеро и одно крас­ное чис­ло, что­бы было 18 чёр­ных и 17 крас­ных. Так как веро­ят­ность попасть на чёр­ное выше, чем на крас­ное, то мато­жи­да­ние будет поло­жи­тель­ным.

👉 Когда буде­те читать код, обра­ти­те вни­ма­ние на такую стро­ку: if ball in range(1,19). Дело в том, что Python рабо­та­ет с диа­па­зо­ном range() так: пер­вое чис­ло вхо­дит в рабо­чий диа­па­зон, а конеч­ное — нет. Поэто­му, что­бы нам про­ве­рить, попа­ло ли наше чис­ло в диа­па­зон от 1 до 18 вклю­чи­тель­но, нам нуж­но ука­зать range(1,19).

# начинаем играть с полной суммой
# первая стратегия — отрицательное матожидание, как в казино
money = startmoney
# пока у нас ещё есть деньги
while money > 0:
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance1.append(money)
    games1.append(len(games1)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 18 красных и одно зеро. Мы ставим на чёрное
    ball = random.randint(1,37)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1
games = win + loose
# выводим результат игры по первой стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")

# началась вторая стратегия, тоже стартуем с полной суммой
# вторая стратегия — с нулевым матожиданием
money = startmoney
# обнуляем статистику
win = 0
loose = 0

# начинаем играть с полной суммой
money = startmoney
# играем, пока есть деньги или пока мы не сыграем столько же игр, как и в первый раз
while (money > 0) and (win + loose < games):
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance2.append(money)
    games2.append(len(games2)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 18 красных. Так как всего поровну, матожидание будет равно нулю.
    # Ставим, как и в прошлом случае, на чёрное
    ball = random.randint(1,36)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1

# выводим результат игры по второй  стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")



# началась третья стратегия, тоже стартуем с полной суммой
# третья стратегия — с положительным матожиданием
money = startmoney
# обнуляем статистику
win = 0
loose = 0

# начинаем играть с полной суммой
money = startmoney
# играем, пока есть деньги или пока мы не сыграем столько же игр, как и в первый раз
while (money > 0) and (win + loose < games):
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance3.append(money)
    games3.append(len(games3)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 17 красных. Так как чёрных больше, а мы ставим на чёрное, то матожидание будет положительным
    # Ставим, как и в прошлом случае, на чёрное
    ball = random.randint(1,35)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1

# выводим результат игры по третьей стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")

Если мы запу­стим этот код, то в кон­со­ли уви­дим резуль­тат для каж­до­го мато­жи­да­ния:

Моделируем игру в рулетку на Python: если мы запустим этот код, то в консоли увидим результат для каждого матожидания

Кажет­ся, что все три вари­ан­та дают при­мер­но оди­на­ко­вый резуль­тат, но на самом деле в пер­вом слу­чае вы оста­не­тесь без денег, а в тре­тьем — раз­бо­га­те­е­те. Что­бы было нагляд­нее — постро­им гра­фи­ки.

Строим графики

Для гра­фи­ков мы исполь­зу­ем биб­лио­те­ку Plotly и рису­ем сра­зу все три гра­фи­ка на одном поле — это даст нам нагляд­ность и пони­ма­ние, к чему при­во­дит раз­ное мато­жи­да­ние. Что­бы было понят­но, какой гра­фик отве­ча­ет за какую стра­те­гию, мы их под­пи­шем:

# строим графики
fig = go.Figure()
# для первой стратегии
fig.add_trace(go.Scatter(x=games1, y=balance1, name = "Отрицательное матожидание"))
# для второй
fig.add_trace(go.Scatter(x=games2, y=balance2, name = "Нулевое матожидание"))
# и для третьей
fig.add_trace(go.Scatter(x=games3, y=balance3, name = "Положительное матожидание"))
# выводим графики в браузер
fig.show()

Теперь посмот­рим на тот же резуль­тат, кото­рый у нас полу­чил­ся, но в виде гра­фи­ков:

Моделируем игру в рулетку на Python: теперь посмотрим на тот же результат, который у нас получился, но в виде графиков

Синяя линия — это баланс нашей пер­вой игры с отри­ца­тель­ным мато­жи­да­ни­ем. Мы пред­ска­зу­е­мо всё про­иг­ра­ли.

Оран­же­вая линия — гра­фик балан­са при нуле­вом мато­жи­да­нии. Вид­но, что мы то выиг­ры­ва­ли, то про­иг­ры­ва­ли, но в резуль­та­те мы всё рав­но оста­лись при­мер­но с той же сум­мой, что и начи­на­ли.

Зелё­ная линия — наш баланс при поло­жи­тель­ном мато­жи­да­нии. Мы выиг­ры­ва­ли чаще, чем про­иг­ры­ва­ли, поэто­му удво­и­ли нашу стар­то­вую сум­му.

Готовый код

# подключаем модуль случайных чисел
import random

# подключаем модуль для графиков
import plotly
import plotly.graph_objs as go

# сколько денег будет на старте для каждой стратегии
startmoney = 1000000

# коэффициент ставки
c1 = 0.001

# количество побед и проигрышей
win = 0
loose = 0

# количество игр, сыгранный по первой стратегии
games = 0

# статистика для первой стратегии
balance1 = []
games1 = []

# статистика для второй стратегии
balance2 = []
games2 = []

# статистика для третьей стратегии
balance3 = []
games3 = []

# начинаем играть с полной суммой
# первая стратегия — отрицательное матожидание, как в казино
money = startmoney
# пока у нас ещё есть деньги
while money > 0:
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance1.append(money)
    games1.append(len(games1)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 18 красных и одно зеро. Мы ставим на чёрное
    ball = random.randint(1,37)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1
games = win + loose
# выводим результат игры по первой стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")

# началась вторая стратегия, тоже стартуем с полной суммой
# вторая стратегия — с нулевым матожиданием
money = startmoney
# обнуляем статистику
win = 0
loose = 0

# начинаем играть с полной суммой
money = startmoney
# играем, пока есть деньги или пока мы не сыграем столько же игр, как и в первый раз
while (money > 0) and (win + loose < games):
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance2.append(money)
    games2.append(len(games2)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 18 красных. Так как всего поровну, матожидание будет равно нулю.
    # Ставим, как и в прошлом случае, на чёрное
    ball = random.randint(1,36)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1

# выводим результат игры по второй  стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")



# началась третья стратегия, тоже стартуем с полной суммой
# третья стратегия — с положительным матожиданием
money = startmoney
# обнуляем статистику
win = 0
loose = 0

# начинаем играть с полной суммой
money = startmoney
# играем, пока есть деньги или пока мы не сыграем столько же игр, как и в первый раз
while (money > 0) and (win + loose < games):
    # ставка —  постоянная часть от первоначальной суммы
    bet = startmoney * c1
    # если ставка получилась больше, чем у нас осталось денег — ставим всё, что осталось, чтобы не уйти в минус
    if bet > money:
        bet = money
    # после ставки количество денег уменьшилось
    money -= bet
    
    # записываем очередную игру в статистику — деньги и номер игры 
    balance3.append(money)
    games3.append(len(games3)+1)

    # крутим рулетку, на которой 18 чёрных чисел, 17 красных. Так как чёрных больше, а мы ставим на чёрное, то матожидание будет положительным
    # Ставим, как и в прошлом случае, на чёрное
    ball = random.randint(1,35)
    # пусть первые 18 будут чёрными — для простоты алгоритма
    # если наша ставка сыграла — мы попали в нужный диапазон
    if ball in range (1,19):
        # получаем назад нашу ставку в двойном размере
        money += bet * 2
        # увеличиваем количество побед
        win += 1
    else:
        # иначе — увеличиваем количество проигрышей
        loose += 1

# выводим результат игры по третьей  стратегии
print("Выиграно ставок: " + str(win) + " (" + str(win/games * 100) + "%). " + " Проиграно ставок: " + str(loose)  + " (" + str(loose/games * 100) + "%). ")

# строим графики
fig = go.Figure()
# для первой стратегии
fig.add_trace(go.Scatter(x=games1, y=balance1, name = "Отрицательное матожидание"))
# для второй
fig.add_trace(go.Scatter(x=games2, y=balance2, name = "Нулевое матожидание"))
# и для третьей
fig.add_trace(go.Scatter(x=games3, y=balance3, name = "Положительное матожидание"))
# выводим графики в браузер
fig.show()

Что дальше

Теперь у вас есть все инстру­мен­ты, что­бы про­ве­рять резуль­та­ты игр при любом мато­жи­да­нии. Этот код мож­но исполь­зо­вать не толь­ко для рулет­ки, а в прин­ци­пе для любых игр, где резуль­тат опре­де­ля­ет­ся слу­чай­ным обра­зом.

Текст и код:
Миша Поля­нин

Редак­тор:
Мак­сим Илья­хов

Кор­рек­тор:
Ира Михе­е­ва

Иллю­стра­тор:
Даня Бер­ков­ский

Вёрст­ка:
Маша Дро­но­ва

Достав­ка:
Олег Веш­кур­цев