Your network blocks the Lichess assets!

lichess.org
Donate

Почему дуэли - ХУДШИЕ турниры на личессе? (часть 2)

Tournament
Копаем ещё глубже в эту тему

Идею для продолжения этой рубрики мне подкинул данный человек:
Снимок экрана (417).pngДавайте разбираться, что к чему.

Что, если в дуэль зашло одинаковое количество человек?

Если вы не видели 1 часть этой рубрики, прошу её посмотреть, иначе вы можете не понять то, что я буду говорить.


В прошлой части я говорил, что на исход дуэли влияет не количество игроков, как в нормальном межклубном турнире, а их средний рейтинг (разумеется, я говорю только про тех игроков, которые играли). А что, если их одинаковое количество и их средний рейтинг одинаковый? Будет ничья или у какой-то из команд есть скрытое преимущество? Для ответа на эти вопросы смоделируем эту ситуацию на простом примере:


За Клуб А играют: 1600, 1700, 2100 (средний рейтинг 1800)
За Клуб Б играют: 1750, 1800, 1850 (средний рейтинг тоже 1800)


Кому интересно, вот формула расчёта вероятности победы игрока в зависимости от рейтингов:
screenshot-2021-07-15-at-14.03.00.webpгде E_A = вероятность победы игрока А,
R_B - рейтинг игрока Б,
R_A - рейтинг игрока А;
400 - коэффициент разброса рейтингов (сам не шарю, что это).

Чтобы не считать среднее количество очков вручную, что есть сущий ад, я написал пару строк говнокода не самого лучшего кода:

import pprint

def win_probability(player_a, player_b):
    return 1 / (1 + 10 ** ((player_b - player_a)/400))

def count_score(sequence):
    score = 0
    streak = 0
    for result in sequence:
        if result == "1":
            streak += 1
            score += 2 if streak < 3 else 4
        else:
            streak = 0
    return score

def count_probability(sequence, rating, opponents):
    probability = 1
    for index, result in enumerate(sequence):
        win = win_probability(rating, opponents[index])
        probability *= win if result == "1" else (1-win)
    return count_score(sequence), probability

## Рейтинги игроков
a = (1600, 1700, 2100)  # Команда А
b = (1750, 1800, 1850)  # Команда Б

games = 15 # число должно быть множителем тройки

"""МОДЕЛИРОВАНИЕ"""

scores_probability = dict()

for i, player_a in enumerate(a):
    scores_probability[f"A{i+1}"] = dict()
    for score in range(0, 4 * (games-1) + 1, 2):
        scores_probability[f"A{i+1}"][score] = 0

for i, player_b in enumerate(b):
    scores_probability[f"B{i+1}"] = dict()
    for score in range(0, 4 * (games-1) + 1, 2):
        scores_probability[f"B{i+1}"][score] = 0

## Порядок партий:
## A1: Б1, Б2, Б3
## A2: Б2, Б3, Б1
## A3: Б3, Б1, Б2
## Б1: А1, А3, А2
## Б2: А2, А1, А3
## Б3: А3, А2, А1

for combo in range(2**games):
    sequence = format(combo, f'0{games}b')
## A1
    (score, probability) = count_probability(sequence, a[0], (b[0], b[1], b[2]) * (games//3))
    scores_probability["A1"][score] += probability
## A2
    (score, probability) = count_probability(sequence, a[1], (b[1], b[2], b[0]) * (games//3))
    scores_probability["A2"][score] += probability
## A3
    (score, probability) = count_probability(sequence, a[2], (b[2], b[0], b[1]) * (games//3))
    scores_probability["A3"][score] += probability
## Б1
    (score, probability) = count_probability(sequence, b[0], (a[0], a[2], a[1]) * (games//3))
    scores_probability["B1"][score] += probability
## Б2
    (score, probability) = count_probability(sequence, b[1], (a[1], a[0], a[2]) * (games//3))
    scores_probability["B2"][score] += probability
## Б3
    (score, probability) = count_probability(sequence, b[2], (a[2], a[1], a[0]) * (games//3))
    scores_probability["B3"][score] += probability

print("Распределение вероятностей:")
pprint.pprint(scores_probability) # вероятность набрать определённое кол-во очков
## Расчет суммарных очков для каждой команды
team_A_total = sum(score * prob for player in ["A1", "A2", "A3"] 
                   for score, prob in scores_probability[player].items())
team_B_total = sum(score * prob for player in ["B1", "B2", "B3"] 
                   for score, prob in scores_probability[player].items())

winner = "А" if team_A_total >= team_B_total else "Б"

print(f"\nСуммарные очки Клуба А: {team_A_total}")
print(f"Суммарные очки Клуба Б: {team_B_total}")
print(f"Преимущество Клуба {winner}: {max(team_A_total, team_B_total) / min(team_A_total, team_B_total) * 100 - 100}%")

Код открыт, формула - тоже. Никакого мухлежа. Можете сами экспериментировать, если шарите в программировании и разберёте мой марсианский.

Симуляция

Пусть каждый игрок сыграет по 15 партий, по 5 с каждым из противоположной команды. Как вы думаете, кто в среднем будет набирать больше очков?

Суммарные очки Клуба А: 60.82159930763277
Суммарные очки Клуба Б: 52.43899064168677
Преимущество Клуба А: 15.985450069441612%

Смотрите: количество участников в клубах одинаково, средний рейтинг - тоже, но всё же Клуб А в среднем набирает на 16% больше очков, чем Клуб Б. Может, это так из-за "звезды" клуба А - 2100-ника? Давайте проверим это предположение. Возьмём экстремальный сценарий: состав Клуба Б останется тот же, а состав Клуба А будет следующим: 1000, 1700, 2700 (средний рейтинг по-прежнему 1800). Симуляция:

Суммарные очки Клуба А: 67.72753575885487
Суммарные очки Клуба Б: 49.3052531600275
Преимущество Клуба А: 37.363731890869985%

Преимущество Клуба А выросло аж до 37%. И это понятно, почему: хоть Клуб Б и набивается на 1000-нике, но партия с 2700-ником почти гарантированно лишает Клуб Б вплоть до 8 очков: 4 за потенциальную победу на серии, и по 2 за две последующие победы, которые могли быть на серии.

Я решил поэкспериментировать и взял состав Клуба А следующим: 1000, 2200, 2200, т.к. я ожидал, что этот состав будет сильнее. И я был прав, но не ожидал, насколько он будет сильнее:

Суммарные очки Клуба А: 93.53936402726261
Суммарные очки Клуба Б: 35.956420457286384
Преимущество Клуба А: 160.14648521084177%

Преимущество Клуба А - 160%. 160 процентов, Карл! И это тоже понятно, почему - из-за 2200-ников игроки за Клуб Б просто не успевают выстроить серию, побеждая против 1000-ника, пока 2200-ники выстраивают себе огромные серии.

То есть, преимущество состоит не в силе звёзд, а в их количестве. В этом сценарии двух 2200-ников с лихвой хватило, чтобы компенсировать слабого 1000-ника.


А теперь сделаем эксперимент: заменим одного 2200-ника из Клуба А на 1675-ника. И вот результаты:

Суммарные очки Клуба А: 57.70589825490841
Суммарные очки Клуба Б: 57.47450283626221
Преимущество Клуба А: 0.40260534189468444%

Посчитаем средний рейтинг игроков за Клуб А:
(1000 + 1675 + 2200) / 3 = 1625, что, на секундочку, на 175 пунктов меньше, чем средний рейтинг Клуба Б! И всё же, даже при таком неравенстве сил, у Клуба А есть маленькое, пренебрежимое преимущество.

Подводя итоги

Итак, в результате моего эксперимента я нашёл случай, когда средний рейтинг играющих за Клуб А на 175 пунктов ниже, чем у играющих за Клуб Б, но Клуб А идёт на равных с клубом Б. Получается, что даже в больших дуэлях, где за каждый клуб зашли десятки человек, нечестность никто не отменял. Если у одной команды есть больше сильных игроков (при условии равенства средних рейтингов), то соперник точно проиграет, хотя в крупном межклубном турнире с большим количеством команд обе эти команды набрали бы примерно одинаковое количество очков.

И вот вы по этому мерите силу клубов? Серьёзно?!

Закончим на весёлой ноте

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

На этом всё! Спасибо, что прочитали!