Last active
October 14, 2022 18:44
-
-
Save rishubil/add77c01e174a325cc87897c7ce24ecd to your computer and use it in GitHub Desktop.
스플래툰에서 팀을 바꾸는 좋은 전략이 있을까?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
스플래툰3에서 팀을 바꾸는 좋은 전략이 있을까? | |
--- | |
목표: 팀을 바꿀지 말지 결정할 때, 더 승률이 높은 전략을 찾는다. | |
조건: | |
- 기본적으로 스플래툰3의 카오폴리스 매치 (챌린지)를 기준으로 한다. | |
- 게임에서 획득한 금메달, 은메달로 얻을 수 있는 추가 포인트는 무시한다. | |
- 랭크 포인트 상한, 하한은 원래 9999, -9999이지만, 이를 무시한다. | |
- 모든 플레이어는 동시에 매치메이킹을 한다. | |
- 각 팀의 승률은 팀의 평균 실력(0.0 ~ 1.0)의 차이를 바탕으로 (여러 가지 방법으로) 계산한다. | |
""" | |
import math | |
import random | |
from tqdm import tqdm | |
def ease_in_out_sine(t: float) -> float: | |
return (1 - math.cos(math.pi * t)) / 2 | |
def ease_in_out_cubic(t: float) -> float: | |
return 4 * t**3 if t < 0.5 else 1 - math.pow(-2 * t + 2, 3) / 2 | |
def ease_in_out_quint(t: float) -> float: | |
return 16 * t**5 if t < 0.5 else 1 - math.pow(-2 * t + 2, 5) / 2 | |
class RankGroup: | |
win_additional_point_list = [0, 0, 5, 15, 30, 50] | |
def __init__( | |
self, | |
name: str, | |
index: int, | |
init_point: int, | |
rank_group_up_point: int, | |
win_point_base: int, | |
) -> None: | |
self.name = name | |
self.index = index | |
self.init_point = init_point | |
self.rank_group_up_point = rank_group_up_point | |
self.win_point_base = win_point_base | |
def __repr__(self) -> str: | |
return self.name | |
def calc_win_point(self, win_count: int) -> int: | |
return ( | |
self.win_point_base * win_count + self.win_additional_point_list[win_count] | |
) | |
class Rank: | |
def __init__( | |
self, | |
name: str, | |
index: int, | |
group: RankGroup, | |
rank_up_point: int, | |
enter_fee: int, | |
) -> None: | |
self.name = name | |
self.index = index | |
self.group = group | |
self.rank_up_point = rank_up_point | |
self.enter_fee = enter_fee | |
def __repr__(self) -> str: | |
return self.name | |
RANK_GROUPS = [ | |
RankGroup( | |
name="C Group", | |
index=0, | |
init_point=10, | |
rank_group_up_point=600, | |
win_point_base=20, | |
), | |
RankGroup( | |
name="B Group", | |
index=1, | |
init_point=100, | |
rank_group_up_point=850, | |
win_point_base=30, | |
), | |
RankGroup( | |
name="A Group", | |
index=2, | |
init_point=200, | |
rank_group_up_point=1100, | |
win_point_base=40, | |
), | |
RankGroup( | |
name="S Group", | |
index=3, | |
init_point=300, | |
rank_group_up_point=100, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+0 Group", | |
index=4, | |
init_point=300, | |
rank_group_up_point=3800, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+10 Group", | |
index=5, | |
init_point=300, | |
rank_group_up_point=3800, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+20 Group", | |
index=6, | |
init_point=300, | |
rank_group_up_point=3800, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+30 Group", | |
index=7, | |
init_point=300, | |
rank_group_up_point=3800, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+40 Group", | |
index=8, | |
init_point=300, | |
rank_group_up_point=3800, | |
win_point_base=50, | |
), | |
RankGroup( | |
name="S+50 Group", | |
index=9, | |
init_point=300, | |
rank_group_up_point=9999, | |
win_point_base=50, | |
), | |
] | |
LAST_RANK_GROUP_INDEX = len(RANK_GROUPS) - 1 | |
RANKS = [ | |
Rank( | |
name="C-", | |
index=0, | |
group=RANK_GROUPS[0], | |
rank_up_point=200, | |
enter_fee=0, | |
), | |
Rank( | |
name="C", | |
index=1, | |
group=RANK_GROUPS[0], | |
rank_up_point=400, | |
enter_fee=20, | |
), | |
Rank( | |
name="C+", | |
index=2, | |
group=RANK_GROUPS[0], | |
rank_up_point=600, | |
enter_fee=40, | |
), | |
Rank( | |
name="B-", | |
index=3, | |
group=RANK_GROUPS[1], | |
rank_up_point=350, | |
enter_fee=55, | |
), | |
Rank( | |
name="B", | |
index=4, | |
group=RANK_GROUPS[1], | |
rank_up_point=600, | |
enter_fee=70, | |
), | |
Rank( | |
name="B+", | |
index=5, | |
group=RANK_GROUPS[1], | |
rank_up_point=850, | |
enter_fee=85, | |
), | |
Rank( | |
name="A-", | |
index=6, | |
group=RANK_GROUPS[2], | |
rank_up_point=500, | |
enter_fee=100, | |
), | |
Rank( | |
name="A", | |
index=7, | |
group=RANK_GROUPS[2], | |
rank_up_point=800, | |
enter_fee=110, | |
), | |
Rank( | |
name="A+", | |
index=8, | |
group=RANK_GROUPS[2], | |
rank_up_point=1100, | |
enter_fee=120, | |
), | |
Rank( | |
name="S", | |
index=9, | |
group=RANK_GROUPS[3], | |
rank_up_point=1000, | |
enter_fee=150, | |
), | |
] | |
for s_group_index in range(5): | |
for s_rank_index in range(10): | |
RANKS.append( | |
Rank( | |
name=f"S+{s_group_index * 10 + s_rank_index}", | |
index=10 + (s_group_index * 10 + s_rank_index), | |
group=RANK_GROUPS[4 + s_group_index], | |
rank_up_point=650 + (s_rank_index * 350), | |
enter_fee=160, | |
) | |
) | |
RANKS.append( | |
Rank( | |
name=f"S+50", | |
index=len(RANKS), | |
group=RANK_GROUPS[9], | |
rank_up_point=9999, | |
enter_fee=160, | |
) | |
) | |
class GameLog: | |
def __init__(self, is_win: bool) -> None: | |
self.is_win = is_win | |
def __repr__(self) -> str: | |
return f"{'Win' if self.is_win else 'Lose'}" | |
class PlayerRankState: | |
def __init__( | |
self, | |
is_rank_group_up: bool, | |
) -> None: | |
self.is_rank_group_up = is_rank_group_up | |
self.win_count: int = 0 | |
self.lose_count: int = 0 | |
self.logs: list[GameLog] = [] | |
def is_ended(self) -> bool: | |
if self.is_lose(): | |
return True | |
if self.is_rank_group_up: | |
return self.win_count >= 3 | |
return self.win_count >= 5 | |
def is_lose(self) -> bool: | |
return self.lose_count >= 3 | |
class Team: | |
def __init__(self) -> None: | |
self.players: list["Player"] = [] | |
self.players_length: int = 0 | |
self.mean_performance: float = 0.0 | |
self.mean_win_count: float = 0.0 | |
self.min_rank_group_index: int = 9999 | |
def __repr__(self) -> str: | |
return f"Team(players_length={self.players_length}, mean_performance={self.mean_performance}, mean_win_count={self.mean_win_count}, min_rank_group_index={self.min_rank_group_index})" | |
def add_player(self, player: "Player") -> None: | |
self.players.append(player) | |
self.players_length += 1 | |
if player.rank.index < self.min_rank_group_index: | |
self.min_rank_group_index = player.rank.index | |
self.update_mean() | |
def remove_player(self, player: "Player") -> None: | |
self.players.remove(player) | |
self.players_length -= 1 | |
if player.rank.index == self.min_rank_group_index: | |
self.min_rank_group_index = min([p.rank.index for p in self.players]) | |
self.update_mean() | |
def pop_player(self) -> "Player": | |
player = self.players[-1] | |
self.remove_player(player) | |
return player | |
def update_mean(self) -> None: | |
self.mean_performance = 0.0 | |
self.mean_win_count = 0.0 | |
for player in self.players: | |
self.mean_performance += player.performance | |
self.mean_win_count += player.player_rank_state.win_count | |
self.mean_performance /= self.players_length | |
self.mean_win_count /= self.players_length | |
def merge(self, other: "Team") -> None: | |
self.players.extend(other.players) | |
self.players_length += other.players_length | |
if other.min_rank_group_index < self.min_rank_group_index: | |
self.min_rank_group_index = other.min_rank_group_index | |
self.update_mean() | |
def add_win(self) -> None: | |
for player in self.players: | |
player.add_win() | |
if player.is_rank_ended(): | |
player.update_rank() | |
self.remove_player(player) | |
player.enter_rank() | |
else: | |
if not player.is_want_to_keep_team(): | |
self.remove_player(player) | |
player.reenter_rank() | |
def add_lose(self) -> None: | |
for player in self.players: | |
player.add_lose() | |
if player.is_rank_ended(): | |
player.update_rank() | |
self.remove_player(player) | |
player.enter_rank() | |
else: | |
if not player.is_want_to_keep_team(): | |
self.remove_player(player) | |
player.reenter_rank() | |
class Game: | |
def __init__(self) -> None: | |
self.team_map: dict[int, dict[int, list[Team]]] = { | |
team_size: {rank_group_index: [] for rank_group_index in range(len(RANKS))} | |
for team_size in range(1, 5) | |
} | |
self.team_map_size: dict[int, int] = {team_size: 0 for team_size in range(1, 5)} | |
self.matched_teams: list[list[Team]] = [] | |
def clear_team_map(self) -> None: | |
self.team_map = { | |
team_size: {rank_group_index: [] for rank_group_index in range(len(RANKS))} | |
for team_size in range(1, 5) | |
} | |
self.team_map_size = {team_size: 0 for team_size in range(1, 5)} | |
def enqueue_team(self, team: Team) -> None: | |
self.team_map[team.players_length][team.min_rank_group_index].append(team) | |
self.team_map_size[team.players_length] += 1 | |
def is_build_team_completed(self) -> bool: | |
return ( | |
self.team_map_size[1] == 0 | |
and self.team_map_size[2] == 0 | |
and self.team_map_size[3] == 0 | |
) | |
def should_break_team(self) -> bool: | |
return ( | |
self.team_map_size[1] == 0 | |
and 0 <= self.team_map_size[2] <= 1 | |
and 0 <= self.team_map_size[3] | |
) | |
def merge_teams( | |
self, | |
team_1_size: int, | |
team_1_rank_group_index: int, | |
team_1_list_index: int, | |
team_2_size: int, | |
team_2_rank_group_index: int, | |
team_2_list_index: int, | |
) -> None: | |
team_1 = self.team_map[team_1_size][team_1_rank_group_index][team_1_list_index] | |
team_1.merge( | |
self.team_map[team_2_size][team_2_rank_group_index][team_2_list_index] | |
) | |
self.team_map[team_1_size][team_1_rank_group_index][team_1_list_index] = None | |
self.team_map_size[team_1_size] -= 1 | |
assert self.team_map_size[team_1_size] >= 0 | |
self.team_map[team_2_size][team_2_rank_group_index][team_2_list_index] = None | |
self.team_map_size[team_2_size] -= 1 | |
assert self.team_map_size[team_2_size] >= 0 | |
self.team_map[team_1.players_length][team_1.min_rank_group_index].append(team_1) | |
self.team_map_size[team_1.players_length] += 1 | |
def find_index_of_nearest_performance_team( | |
self, | |
source_team_size: int, | |
source_rank_group_index: int, | |
source_list_index: int, | |
team_size: int, | |
rank_group_index: int, | |
) -> int: | |
performance = self.team_map[source_team_size][source_rank_group_index][ | |
source_list_index | |
].mean_performance | |
if ( | |
source_team_size == team_size | |
and source_rank_group_index == rank_group_index | |
): | |
index = min( | |
range(len(self.team_map[team_size][rank_group_index])), | |
key=lambda i: abs( | |
( | |
self.team_map[team_size][rank_group_index][i].mean_performance | |
if ( | |
source_list_index != i | |
and self.team_map[team_size][rank_group_index][i] | |
is not None | |
) | |
else 9999 | |
) | |
- performance | |
), | |
) | |
if index == source_list_index: | |
return -1 | |
else: | |
index = min( | |
range(len(self.team_map[team_size][rank_group_index])), | |
key=lambda i: abs( | |
( | |
self.team_map[team_size][rank_group_index][i].mean_performance | |
if self.team_map[team_size][rank_group_index][i] is not None | |
else 9999 | |
) | |
- performance | |
), | |
) | |
if self.team_map[team_size][rank_group_index][index] is None: | |
return -1 | |
return index | |
def break_team_map(self) -> None: | |
for rank_group_index in self.team_map[3]: | |
for list_index in range(len(self.team_map[3][rank_group_index])): | |
team = self.team_map[3][rank_group_index][list_index] | |
player = team.pop_player() | |
new_team = Team() | |
new_team.add_player(player) | |
self.enqueue_team(new_team) | |
self.team_map[3][rank_group_index][list_index] = None | |
self.team_map_size[3] -= 1 | |
assert self.team_map_size[3] >= 0 | |
self.team_map[2][rank_group_index].append(team) | |
self.team_map_size[2] += 1 | |
assert self.team_map_size[2] >= 0 | |
def rebuild_team_map(self) -> None: | |
for team_size in self.team_map: | |
count = 0 | |
for rank_group_index in self.team_map[team_size]: | |
new_list = [ | |
x | |
for x in self.team_map[team_size][rank_group_index] | |
if x is not None | |
] | |
self.team_map[team_size][rank_group_index] = new_list | |
count += len(new_list) | |
self.team_map_size[team_size] = count | |
def build_team(self) -> None: | |
count_to_try = 0 | |
while not self.is_build_team_completed() and count_to_try < 100: | |
count_to_try += 1 | |
if self.should_break_team(): | |
self.break_team_map() | |
for target_team_size in range(3, 0, -1): | |
for rank_group_index in self.team_map[target_team_size]: | |
for target_team_index in range( | |
len(self.team_map[target_team_size][rank_group_index]) | |
): | |
if not self.team_map[target_team_size][rank_group_index][ | |
target_team_index | |
]: | |
continue | |
is_merged = False | |
for team_size_to_merge in range(4 - target_team_size, 0, -1): | |
if is_merged: | |
break | |
if ( | |
len(self.team_map[team_size_to_merge][rank_group_index]) | |
> 0 | |
): | |
index_to_merge = ( | |
self.find_index_of_nearest_performance_team( | |
source_team_size=target_team_size, | |
source_rank_group_index=rank_group_index, | |
source_list_index=target_team_index, | |
team_size=team_size_to_merge, | |
rank_group_index=rank_group_index, | |
) | |
) | |
if index_to_merge != -1: | |
self.merge_teams( | |
team_1_size=target_team_size, | |
team_1_rank_group_index=rank_group_index, | |
team_1_list_index=target_team_index, | |
team_2_size=team_size_to_merge, | |
team_2_rank_group_index=rank_group_index, | |
team_2_list_index=index_to_merge, | |
) | |
is_merged = True | |
continue | |
if ( | |
rank_group_index < LAST_RANK_GROUP_INDEX | |
and len( | |
self.team_map[team_size_to_merge][ | |
rank_group_index + 1 | |
] | |
) | |
> 0 | |
): | |
index_to_merge = ( | |
self.find_index_of_nearest_performance_team( | |
source_team_size=target_team_size, | |
source_rank_group_index=rank_group_index, | |
source_list_index=target_team_index, | |
team_size=team_size_to_merge, | |
rank_group_index=rank_group_index + 1, | |
) | |
) | |
if index_to_merge != -1: | |
self.merge_teams( | |
team_1_size=target_team_size, | |
team_1_rank_group_index=rank_group_index, | |
team_1_list_index=target_team_index, | |
team_2_size=team_size_to_merge, | |
team_2_rank_group_index=rank_group_index + 1, | |
team_2_list_index=index_to_merge, | |
) | |
is_merged = True | |
continue | |
self.rebuild_team_map() | |
if self.is_build_team_completed(): | |
return | |
while not self.is_build_team_completed(): | |
if self.should_break_team(): | |
self.break_team_map() | |
for target_team_size in range(3, 0, -1): | |
for rank_group_index in self.team_map[target_team_size]: | |
for target_team_index in range( | |
len(self.team_map[target_team_size][rank_group_index]) | |
): | |
if not self.team_map[target_team_size][rank_group_index][ | |
target_team_index | |
]: | |
continue | |
is_merged = False | |
for team_size_to_merge in range(4 - target_team_size, 0, -1): | |
if is_merged: | |
break | |
for rank_group_index_to_merge in self.team_map[ | |
team_size_to_merge | |
]: | |
if is_merged: | |
break | |
if ( | |
len( | |
self.team_map[team_size_to_merge][ | |
rank_group_index_to_merge | |
] | |
) | |
> 0 | |
): | |
index_to_merge = ( | |
self.find_index_of_nearest_performance_team( | |
source_team_size=target_team_size, | |
source_rank_group_index=rank_group_index, | |
source_list_index=target_team_index, | |
team_size=team_size_to_merge, | |
rank_group_index=rank_group_index_to_merge, | |
) | |
) | |
if index_to_merge != -1: | |
self.merge_teams( | |
team_1_size=target_team_size, | |
team_1_rank_group_index=rank_group_index, | |
team_1_list_index=target_team_index, | |
team_2_size=team_size_to_merge, | |
team_2_rank_group_index=rank_group_index_to_merge, | |
team_2_list_index=index_to_merge, | |
) | |
is_merged = True | |
continue | |
self.rebuild_team_map() | |
def matchmaking(self) -> None: | |
self.matched_teams = [] | |
remain_teams: list[Team] = [] | |
for rank_group_index in sorted(self.team_map[4].keys()): | |
teams = self.team_map[4][rank_group_index] | |
sorted_teams = sorted(teams, key=lambda team: team.mean_win_count) | |
while len(sorted_teams) >= 2: | |
team_1 = sorted_teams.pop() | |
team_2 = sorted_teams.pop() | |
self.matched_teams.append([team_1, team_2]) | |
if sorted_teams: | |
remain_teams.extend(sorted_teams) | |
while len(remain_teams) >= 2: | |
team_1 = remain_teams.pop() | |
team_2 = remain_teams.pop() | |
self.matched_teams.append([team_1, team_2]) | |
assert len(remain_teams) == 0 | |
def play_games(self) -> None: | |
self.clear_team_map() | |
for teams in self.matched_teams: | |
team_1 = teams[0] | |
team_2 = teams[1] | |
performance_diff = team_1.mean_performance - team_2.mean_performance | |
# team_1_win_prob = ease_in_out_sine((performance_diff + 1) / 2) | |
# team_1_win_prob = ease_in_out_cubic((performance_diff + 1) / 2) | |
team_1_win_prob = ease_in_out_quint((performance_diff + 1) / 2) | |
# is_team_1_win = performance_diff > 0 | |
is_team_1_win = random.uniform(0, 1) < team_1_win_prob | |
if is_team_1_win: | |
team_1.add_win() | |
team_2.add_lose() | |
else: | |
team_1.add_lose() | |
team_2.add_win() | |
if team_1.players_length > 0: | |
self.enqueue_team(team_1) | |
if team_1.players_length > 0: | |
self.enqueue_team(team_2) | |
GAME = Game() | |
class BaseStrategy: | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
raise NotImplementedError | |
@property | |
def name(self): | |
raise NotImplementedError | |
def __repr__(self) -> str: | |
return self.name | |
class Player: | |
def __init__( | |
self, | |
uid: int, | |
performance: float, | |
strategy: "BaseStrategy", | |
) -> None: | |
self.uid = uid | |
self.performance = performance | |
self.strategy = strategy | |
self.rank: Rank = RANKS[0] | |
self.point: int = 10 | |
self.total_point: int = 10 | |
self.player_rank_state: PlayerRankState = None | |
self.enter_rank() | |
def __repr__(self) -> str: | |
return f"Player(uid={self.uid}, performance={self.performance}, rank={self.rank}, point={self.point}, total_point={self.total_point}, strategy={self.strategy})" | |
def is_want_to_keep_team(self) -> bool: | |
return self.strategy.is_want_to_keep_team(self) | |
def rank_group_up(self) -> None: | |
self.rank = RANKS[self.rank.index + 1] | |
self.point = self.rank.group.init_point | |
def rank_up(self) -> None: | |
self.rank = RANKS[self.rank.index + 1] | |
def should_next_rank_group_up(self) -> bool: | |
return ( | |
self.point >= self.rank.group.rank_group_up_point | |
and self.rank.group.index < (len(RANK_GROUPS) - 1) | |
) | |
def enter_rank(self) -> None: | |
self.point -= self.rank.enter_fee | |
self.total_point -= self.rank.enter_fee | |
self.player_rank_state = PlayerRankState( | |
is_rank_group_up=self.should_next_rank_group_up(), | |
) | |
new_team = Team() | |
new_team.add_player(self) | |
GAME.enqueue_team(new_team) | |
def reenter_rank(self) -> None: | |
new_team = Team() | |
new_team.add_player(self) | |
GAME.enqueue_team(new_team) | |
def update_rank(self) -> None: | |
if self.player_rank_state.is_rank_group_up: | |
is_win = not self.player_rank_state.is_lose() | |
if is_win: | |
self.rank_group_up() | |
else: | |
earn_point = self.rank.group.calc_win_point( | |
self.player_rank_state.win_count | |
) | |
self.point += earn_point | |
self.total_point += earn_point | |
if ( | |
not self.should_next_rank_group_up() | |
and self.point > self.rank.rank_up_point | |
and self.rank.index < (len(RANKS) - 1) | |
): | |
self.rank_up() | |
def add_win(self) -> None: | |
self.player_rank_state.win_count += 1 | |
self.player_rank_state.logs.append(GameLog(is_win=True)) | |
def add_lose(self) -> None: | |
self.player_rank_state.lose_count += 1 | |
self.player_rank_state.logs.append(GameLog(is_win=False)) | |
def is_rank_ended(self) -> bool: | |
return self.player_rank_state.is_ended() | |
class RandomStrategy(BaseStrategy): | |
"""랜덤하게 팀을 바꾼다.""" | |
@property | |
def name(self): | |
return "Random" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
return random.random() < 0.5 | |
class TitForTatStrategy(BaseStrategy): | |
"""이기면 계속 같은 팀, 지면 팀을 바꾼다.""" | |
@property | |
def name(self): | |
return "TitForTat" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
logs = player.player_rank_state.logs | |
if len(logs) == 0: | |
return True | |
return logs[-1].is_win | |
class TitFor2TatStrategy(BaseStrategy): | |
"""이기면 계속 같은 팀, 2번 연속 지면 팀을 바꾼다.""" | |
@property | |
def name(self): | |
return "TitFor2Tat" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
logs = player.player_rank_state.logs | |
if len(logs) < 2: | |
return True | |
return logs[-1].is_win or logs[-2].is_win | |
class AlwaysChangeStrategy(BaseStrategy): | |
"""항상 팀을 바꾼다.""" | |
@property | |
def name(self): | |
return "AlwaysChange" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
return False | |
class AlwaysKeepStrategy(BaseStrategy): | |
"""항상 같은 팀을 유지한다.""" | |
@property | |
def name(self): | |
return "AlwaysKeep" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
return True | |
class WinStayLoseShiftStrategy(BaseStrategy): | |
"""팀을 바꾸지 않아서 이기면 계속 팀을 바꾸지 않고, 팀을 바꿔서 이기면 팀을 바꾼다.""" | |
def __init__(self) -> None: | |
super().__init__() | |
self.shifter = True | |
@property | |
def name(self): | |
return "WinStayLoseShift" | |
def is_want_to_keep_team(self, player: "Player") -> bool: | |
logs = player.player_rank_state.logs | |
if len(logs) == 0: | |
return True | |
if not logs[-1].is_win: | |
self.shifter = not self.shifter | |
if self.shifter: | |
return logs[-1].is_win | |
return not logs[-1].is_win | |
if __name__ == "__main__": | |
NUM_OF_TEAM = 600 | |
NUM_OF_GAMEPLAY = 3000 | |
strategy_classes = [ | |
RandomStrategy, | |
TitForTatStrategy, | |
TitFor2TatStrategy, | |
AlwaysChangeStrategy, | |
AlwaysKeepStrategy, | |
WinStayLoseShiftStrategy, | |
] | |
players = [ | |
Player( | |
uid=i, | |
performance=min(1, max(0, random.gauss(0.5, 0.12))), | |
strategy=strategy_classes[i % len(strategy_classes)](), | |
) | |
for i in range(4 * NUM_OF_TEAM) | |
] | |
for _ in tqdm(range(NUM_OF_GAMEPLAY), desc="Gameplay"): | |
GAME.build_team() | |
GAME.matchmaking() | |
GAME.play_games() | |
for strategy_class in strategy_classes: | |
strategy_players = [ | |
x for x in players if x.strategy.__class__ == strategy_class | |
] | |
name = strategy_class().name | |
size = len(strategy_players) | |
mean_performance = sum([x.performance for x in strategy_players]) / size | |
mean_total_point = sum([x.total_point for x in strategy_players]) / size | |
mean_rank_index = sum([x.rank.index for x in strategy_players]) / size | |
print( | |
f"{name:20} ({size}) - performance: {mean_performance:.8f}, rank: {mean_rank_index:2.8f}({RANKS[round(mean_rank_index)]}), total_point: {mean_total_point:6.8f}" | |
) | |
# 실행 결과 예시 | |
# Without Randomness | |
# Gameplay: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3000/3000 [02:39<00:00, 18.77it/s] | |
# Random (400) - performance: 0.49155536, rank: 18.42000000(S+8), total_point: 3478.63750000 | |
# TitForTat (400) - performance: 0.49418261, rank: 18.78750000(S+9), total_point: 3701.83750000 | |
# TitFor2Tat (400) - performance: 0.50404856, rank: 18.87500000(S+9), total_point: 3674.96250000 | |
# AlwaysChange (400) - performance: 0.50543801, rank: 19.27500000(S+9), total_point: 4080.11250000 | |
# AlwaysKeep (400) - performance: 0.49695183, rank: 18.50250000(S+9), total_point: 3166.33750000 | |
# WinStayLoseShift (400) - performance: 0.49912069, rank: 18.95750000(S+9), total_point: 3868.17500000 | |
# With ease_in_out_sine | |
# Gameplay: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3000/3000 [02:54<00:00, 17.19it/s] | |
# Random (400) - performance: 0.49994154, rank: 20.04000000(S+10), total_point: 5287.25000000 | |
# TitForTat (400) - performance: 0.49548483, rank: 19.85250000(S+10), total_point: 5256.10000000 | |
# TitFor2Tat (400) - performance: 0.51182825, rank: 20.38750000(S+10), total_point: 5410.90000000 | |
# AlwaysChange (400) - performance: 0.49063044, rank: 19.78500000(S+10), total_point: 5245.61250000 | |
# AlwaysKeep (400) - performance: 0.49973944, rank: 19.84500000(S+10), total_point: 5258.98750000 | |
# WinStayLoseShift (400) - performance: 0.49879708, rank: 20.08500000(S+10), total_point: 5288.83750000 | |
# With ease_in_out_cubic | |
# Gameplay: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3000/3000 [02:57<00:00, 16.93it/s] | |
# Random (400) - performance: 0.49849383, rank: 20.18750000(S+10), total_point: 5262.45000000 | |
# TitForTat (400) - performance: 0.49335503, rank: 19.92000000(S+10), total_point: 5275.65000000 | |
# TitFor2Tat (400) - performance: 0.49527133, rank: 19.95750000(S+10), total_point: 5255.73750000 | |
# AlwaysChange (400) - performance: 0.49818314, rank: 20.10750000(S+10), total_point: 5332.12500000 | |
# AlwaysKeep (400) - performance: 0.50013418, rank: 20.22500000(S+10), total_point: 5355.62500000 | |
# WinStayLoseShift (400) - performance: 0.50111006, rank: 20.17750000(S+10), total_point: 5319.33750000 | |
# With ease_in_out_quint | |
# Gameplay: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3000/3000 [02:49<00:00, 17.73it/s] | |
# Random (400) - performance: 0.50468686, rank: 19.88000000(S+10), total_point: 5190.11250000 | |
# TitForTat (400) - performance: 0.49820626, rank: 19.81250000(S+10), total_point: 5178.57500000 | |
# TitFor2Tat (400) - performance: 0.49942388, rank: 19.83250000(S+10), total_point: 5184.18750000 | |
# AlwaysChange (400) - performance: 0.51059320, rank: 20.27500000(S+10), total_point: 5463.20000000 | |
# AlwaysKeep (400) - performance: 0.50127540, rank: 19.57000000(S+10), total_point: 5074.16250000 | |
# WinStayLoseShift (400) - performance: 0.50879255, rank: 20.16250000(S+10), total_point: 5388.98750000 | |
# 결론 | |
# - 사실상 각 전략별 의미있는 수준의 차이는 없는 것 같다. | |
# - 이기면 계속 같은 팀, 지면 팀을 바꾸는 전략이 나쁘지 않아 보인다. (심리적으로도...) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment