Skip to content

Instantly share code, notes, and snippets.

@Hiroshiba
Last active January 31, 2018 15:20
Show Gist options
  • Save Hiroshiba/fa3fc71dfdcf3c42c4cb240ea0495358 to your computer and use it in GitHub Desktop.
Save Hiroshiba/fa3fc71dfdcf3c42c4cb240ea0495358 to your computer and use it in GitHub Desktop.
splatoonのフェスを想定したシミュレーション。 人数差が片方のチームに偏ると、人数が少ない方のチームが勝ちやすそうなことを検証した。
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# これは\n",
"splatoonのフェスを想定したシミュレーション。\n",
"人数差が片方のチームに偏ると、人数が少ない方のチームが勝ちやすいことを検証した。\n",
"\n",
"## 流れ\n",
"AとBのチームに別れさせ、強い人が入る確率を両チーム一定にして対戦させる。\n",
"簡単のため1人対1人の試合を行わせる。\n",
"試合結果は、片方が強い人であればその人の勝ち、同じレベルであれば半分の確率で勝ちにした。\n",
"マッチングには、待機時間を想定して**buffer size**を設けた。\n",
"マッチングの順序は以下の通り(敵チームであれば優先的に、かつ同じレベルの人を優先的にマッチさせる)。\n",
"\n",
"1. strong A vs strong B\n",
"1. weak A vs weak B\n",
"1. strong A vs weak B\n",
"1. weak A vs strong B\n",
"1. strong A vs strong A\n",
"1. strong B vs strong B\n",
"1. weak A vs weak A\n",
"1. weak B vs weak B\n",
"1. strong A vs weak A\n",
"1. strong B vs weak B\n",
"\n",
"## 結果\n",
"人数が少ないほうが勝ちやすそうだった。\n",
"\n",
"## 考察\n",
"この現象は、人数が多い方のチームが自チームに当たりやすくなり、強い人がすぐにいなくなるため起こる。\n",
"splatoonのフェスにおいても、自チームと当たりまくると、強い人はポイント貯めてチーム勝率に貢献せず早抜けする一方、弱い人は残って他チームと戦うから、チーム勝率を下げる現象が発生すると思われる。\n",
"\n",
"人数が少ない方を有利にしないためには、マッチング待機時間を伸ばしたり、チームごとの同士討ちの確率を揃えたりすれば良さそう。\n",
"もしくは、同士討ちの際にわざと負けてあげれば良い。\n",
"\n",
"## 謝辞\n",
"splathonの皆様、特に、ネタを提供してくださった後に議論を完全放棄して僕に託してくださったyuuyuuさんに心から感謝します。\n",
"\n",
"## その他\n",
"バグ報告等はGistのコメントか、twitterの @hiho_karuta まで。"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"from typing import List\n",
"from typing import NamedTuple\n",
"\n",
"class Person(NamedTuple):\n",
" team: str\n",
" strong: bool\n",
" num: int\n",
" \n",
" @property\n",
" def is_teamA(self):\n",
" return self.team=='A'\n",
"\n",
"class Matching(NamedTuple):\n",
" person1: Person\n",
" person2: Person\n",
" \n",
" def match_and_get_result(self):\n",
" if self.person1.strong and not self.person2.strong:\n",
" return {'win': self.person1, 'lose': self.person2}\n",
" if not self.person1.strong and self.person2.strong:\n",
" return {'win': self.person2, 'lose': self.person1}\n",
" if random.randint(0, 1) == 0:\n",
" return {'win': self.person1, 'lose': self.person2}\n",
" else:\n",
" return {'win': self.person2, 'lose': self.person1}"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# フィルタリング用\n",
"def get_strong_a(people: List[Person]): return [p for p in people if p.is_teamA and p.strong]\n",
"def get_strong_b(people: List[Person]): return [p for p in people if not p.is_teamA and p.strong]\n",
"def get_weak_a(people: List[Person]): return [p for p in people if p.is_teamA and not p.strong]\n",
"def get_weak_b(people: List[Person]): return [p for p in people if not p.is_teamA and not p.strong]\n",
"\n",
"def util_matching(people1: List[Person], people2: List[Person]):\n",
" \"\"\"\n",
" 少ない方に合わせてマッチングリストを作る\n",
" \"\"\"\n",
" m = min(len(people1), len(people2))\n",
" matching_list = [Matching(p1, p2) for p1, p2 in zip(people1[:m], people2[:m])]\n",
" matched = people1[:m] + people2[:m]\n",
" return matching_list, matched"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def make_matching_list(people: List[Person], buffer_size: int):\n",
" \"\"\"\n",
" buffer_sizeずつ区切って、マッチングリストを作る。\n",
" buffer_sizeずつ区切るのは、マッチング待機時間が有限なのを想定している。\n",
" buffer_sizeが小さいほど理不尽なマッチング(同士討ちや実力差のある試合)が発生しやすくなる。\n",
" \"\"\"\n",
" matching_list: List[Matching] = []\n",
" other: List[Person] = []\n",
" for i in range(0, len(people), buffer_size):\n",
" buffer = set(people[i:i+buffer_size])\n",
" \n",
" # strong A vs strong B\n",
" m_list, matched = util_matching(get_strong_a(buffer), get_strong_b(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # weak A vs weak B\n",
" m_list, matched = util_matching(get_weak_a(buffer), get_weak_b(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # strong A vs weak B\n",
" m_list, matched = util_matching(get_strong_a(buffer), get_weak_b(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # weak A vs strong B\n",
" m_list, matched = util_matching(get_weak_a(buffer), get_strong_b(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # strong A vs strong A\n",
" p = get_strong_a(buffer)\n",
" m_list, matched = util_matching(p[len(p)//2:], p[:len(p)//2])\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # strong B vs strong B\n",
" p = get_strong_b(buffer)\n",
" m_list, matched = util_matching(p[len(p)//2:], p[:len(p)//2])\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # weak A vs weak A\n",
" p = get_weak_a(buffer)\n",
" m_list, matched = util_matching(p[len(p)//2:], p[:len(p)//2])\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # weak B vs weak B\n",
" p = get_weak_b(buffer)\n",
" m_list, matched = util_matching(p[len(p)//2:], p[:len(p)//2])\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # strong A vs weak A\n",
" m_list, matched = util_matching(get_strong_a(buffer), get_weak_a(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" # strong B vs weak B\n",
" m_list, matched = util_matching(get_strong_b(buffer), get_weak_b(buffer))\n",
" matching_list += m_list\n",
" buffer -= set(matched)\n",
" \n",
" other += list(buffer)\n",
" \n",
" return matching_list, other"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def analyze(result_list: List, people: List[Person]):\n",
" print('Aの対B勝数', len([r for r in result_list if r['win'].is_teamA and not r['lose'].is_teamA]))\n",
" print('Bの対A勝数', len([r for r in result_list if not r['win'].is_teamA and r['lose'].is_teamA]))\n",
" print('Aの対A勝数', len([r for r in result_list if r['win'].is_teamA and r['lose'].is_teamA]))\n",
" print('Bの対B勝数', len([r for r in result_list if not r['win'].is_teamA and not r['lose'].is_teamA]))\n",
" \n",
" print('残り', len(people), '人')\n",
" print('Aの強い人', len(get_strong_a(people)), '人')\n",
" print('Bの強い人', len(get_strong_b(people)), '人')\n",
" print('Aの弱い人', len(get_weak_a(people)), '人')\n",
" print('Bの弱い人', len(get_weak_b(people)), '人')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def festival(\n",
" num_people = 2 ** 14, # AチームとBチームの合計人数\n",
" rate_team_a = 0.60, # Aチームの割合\n",
" rate_strong = 0.1, # 強い人がいる割合\n",
" buffer_size = 16, # make_matching_listを作る際の区切りサイズ\n",
" analyze_flag=False, # analyze関数を叩くかどうか\n",
"):\n",
" \"\"\"\n",
" フェス。\n",
" \"\"\"\n",
" num_team_a = int(num_people * rate_team_a)\n",
" num_team_b = num_people - num_team_a\n",
"\n",
" num_team_a_strong = int(num_team_a * rate_strong)\n",
" num_team_b_strong = int(num_team_b * rate_strong)\n",
"\n",
" people = []\n",
" people += [Person(team='A', strong=True, num=num+len(people)) for num in range(num_team_a_strong)]\n",
" people += [Person(team='A', strong=False, num=num+len(people)) for num in range(num_team_a - num_team_a_strong)] \n",
" people += [Person(team='B', strong=True, num=num+len(people)) for num in range(num_team_b_strong)] \n",
" people += [Person(team='B', strong=False, num=num+len(people)) for num in range(num_team_b - num_team_b_strong)]\n",
" \n",
" result_list = []\n",
" for i_match in range(100):\n",
" if analyze_flag:\n",
" print('--------------', i_match, '試合目', '--------------')\n",
" analyze(result_list, people)\n",
"\n",
" random.shuffle(people)\n",
"\n",
" matching_list, other = make_matching_list(people, buffer_size=buffer_size)\n",
" r_list = [m.match_and_get_result() for m in matching_list]\n",
" result_list += r_list\n",
"\n",
" people = [r['lose'] for r in r_list] + other\n",
" if len(people) <= 1:\n",
" break\n",
"\n",
" num_win_a = len([r for r in result_list if r['win'].is_teamA and not r['lose'].is_teamA])\n",
" num_win_b = len([r for r in result_list if not r['win'].is_teamA and r['lose'].is_teamA])\n",
" if num_win_a > num_win_b:\n",
" print('winner', 'A')\n",
" if num_win_a < num_win_b:\n",
" print('winner', 'B')"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-------------- 0 試合目 --------------\n",
"Aの対B勝数 0\n",
"Bの対A勝数 0\n",
"Aの対A勝数 0\n",
"Bの対B勝数 0\n",
"残り 16384 人\n",
"Aの強い人 983 人\n",
"Bの強い人 655 人\n",
"Aの弱い人 8847 人\n",
"Bの弱い人 5899 人\n",
"-------------- 1 試合目 --------------\n",
"Aの対B勝数 2968\n",
"Bの対A勝数 3106\n",
"Aの対A勝数 1878\n",
"Bの対B勝数 240\n",
"残り 8192 人\n",
"Aの強い人 313 人\n",
"Bの強い人 195 人\n",
"Aの弱い人 4671 人\n",
"Bの弱い人 3013 人\n",
"-------------- 2 試合目 --------------\n",
"Aの対B勝数 4438\n",
"Bの対A勝数 4648\n",
"Aの対A勝数 2864\n",
"Bの対B勝数 338\n",
"残り 4096 人\n",
"Aの強い人 78 人\n",
"Bの強い人 42 人\n",
"Aの弱い人 2450 人\n",
"Bの弱い人 1526 人\n",
"-------------- 3 試合目 --------------\n",
"Aの対B勝数 5209\n",
"Bの対A勝数 5377\n",
"Aの対A勝数 3378\n",
"Bの対B勝数 372\n",
"残り 2048 人\n",
"Aの強い人 16 人\n",
"Bの強い人 1 人\n",
"Aの弱い人 1227 人\n",
"Bの弱い人 804 人\n",
"-------------- 4 試合目 --------------\n",
"Aの対B勝数 5600\n",
"Bの対A勝数 5735\n",
"Aの対A勝数 3625\n",
"Bの対B勝数 400\n",
"残り 1024 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 605 人\n",
"Bの弱い人 419 人\n",
"-------------- 5 試合目 --------------\n",
"Aの対B勝数 5801\n",
"Bの対A勝数 5915\n",
"Aの対A勝数 3737\n",
"Bの対B勝数 419\n",
"残り 512 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 292 人\n",
"Bの弱い人 220 人\n",
"-------------- 6 試合目 --------------\n",
"Aの対B勝数 5896\n",
"Bの対A勝数 6014\n",
"Aの対A勝数 3786\n",
"Bの対B勝数 432\n",
"残り 256 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 148 人\n",
"Bの弱い人 108 人\n",
"-------------- 7 試合目 --------------\n",
"Aの対B勝数 5941\n",
"Bの対A勝数 6065\n",
"Aの対A勝数 3812\n",
"Bの対B勝数 438\n",
"残り 128 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 77 人\n",
"Bの弱い人 51 人\n",
"-------------- 8 試合目 --------------\n",
"Aの対B勝数 5963\n",
"Bの対A勝数 6088\n",
"Aの対A勝数 3828\n",
"Bの対B勝数 441\n",
"残り 64 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 39 人\n",
"Bの弱い人 25 人\n",
"-------------- 9 試合目 --------------\n",
"Aの対B勝数 5979\n",
"Bの対A勝数 6095\n",
"Aの対A勝数 3836\n",
"Bの対B勝数 442\n",
"残り 32 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 15 人\n",
"Bの弱い人 17 人\n",
"-------------- 10 試合目 --------------\n",
"Aの対B勝数 5985\n",
"Bの対A勝数 6104\n",
"Aの対A勝数 3836\n",
"Bの対B勝数 443\n",
"残り 16 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 9 人\n",
"Bの弱い人 7 人\n",
"-------------- 11 試合目 --------------\n",
"Aの対B勝数 5988\n",
"Bの対A勝数 6108\n",
"Aの対A勝数 3837\n",
"Bの対B勝数 443\n",
"残り 8 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 5 人\n",
"Bの弱い人 3 人\n",
"-------------- 12 試合目 --------------\n",
"Aの対B勝数 5989\n",
"Bの対A勝数 6110\n",
"Aの対A勝数 3838\n",
"Bの対B勝数 443\n",
"残り 4 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 3 人\n",
"Bの弱い人 1 人\n",
"-------------- 13 試合目 --------------\n",
"Aの対B勝数 5989\n",
"Bの対A勝数 6111\n",
"Aの対A勝数 3839\n",
"Bの対B勝数 443\n",
"残り 2 人\n",
"Aの強い人 0 人\n",
"Bの強い人 0 人\n",
"Aの弱い人 2 人\n",
"Bの弱い人 0 人\n",
"winner B\n"
]
}
],
"source": [
"festival(analyze_flag=True)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner A\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner A\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner A\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n",
"winner B\n"
]
}
],
"source": [
"# フェスを開催しまくる\n",
"for _ in range(100):\n",
" festival()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment