Skip to content

Instantly share code, notes, and snippets.

@ldacosta
Created January 26, 2018 16:52
Show Gist options
  • Save ldacosta/10578211aac9534e3bfb852d026602a8 to your computer and use it in GitHub Desktop.
Save ldacosta/10578211aac9534e3bfb852d026602a8 to your computer and use it in GitHub Desktop.
import itertools
from abc import abstractmethod
from typing import List, Set, Callable, Tuple, Dict
from enum import Enum, auto
class PlayerType(Enum):
DEFENSIVE = auto()
OFFENSIVE = auto()
NEUTRAL = auto()
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
class QValuesFetcher(object):
def lines_to_index(home_line: List[PlayerType], away_line: List[PlayerType]) -> str:
return ''.join(list(map(str, home_line + away_line)))
def __init__(self):
pass
@abstractmethod
def get(self, home_line: List[PlayerType], away_line: List[PlayerType]) -> float:
raise NotImplementedError
class QValuesFetcherFromDict(QValuesFetcher):
def __init__(self, a_dict: Dict):
QValuesFetcher.__init__(self)
self.dict = a_dict
@classmethod
def from_tuples(cls, q_value_tuples: List[Tuple[List[PlayerType], List[PlayerType], float]]):
q_value_dict = {}
for v in q_value_tuples:
home_line, away_line, q_value = v
for home_line_comb in list(set(itertools.permutations(home_line))):
for away_line_comb in list(set(itertools.permutations(away_line))):
q_value_dict[QValuesFetcher.lines_to_index(home_line_comb, away_line_comb)] = q_value
print("Q value dictionary has %d entries" % (len(q_value_dict)))
return cls(a_dict=q_value_dict)
def get(self, home_line: Set[PlayerType], away_line: Set[PlayerType]) -> float:
return self.dict[QValuesFetcher.lines_to_index(list(home_line), list(away_line))]
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
class CategoryFetcher(object):
def __init__(self):
pass
def category_of_player(self, player_id: int) -> PlayerType:
if player_id < 20:
return PlayerType.OFFENSIVE
else:
return PlayerType.NEUTRAL
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
class LineRecommender(object):
def __init__(self, player_category_fetcher: CategoryFetcher, q_values_fetcher: QValuesFetcher):
self.player_category_fetcher=player_category_fetcher
self.q_values_fetcher=q_values_fetcher
def recommend_lines(
self,
home_team_players_ids: Set[int],
away_team_lines: List[List[PlayerType]],
comb_q_values: Callable[[List[float]], float]) -> List[List[int]]:
"""
Builds optimal lines for home team.
Args:
home_team_players_ids: a list of id's for home team.
away_team_lines: 4 lines of away team. Each line is composed of 3 players, represented by their class (0 = defensive, 2 = offensive, 1 = neither)
q_values: a function that given 2 lines (represented by CLASSES of players) returns the q-value of the home team line.
comb_q_values: how to combine the q-values in order to obtain a "fitness" for a formation (larger is better).
Returns:
"""
import datetime
assert len(home_team_players_ids) == len(set(home_team_players_ids)), "There are repeated ids in the home team"
assert len(away_team_lines) == 4, "I need a formation (ie, 4 lines) for away team"
away_line_1, away_line_2, away_line_3, away_line_4 = away_team_lines
best_fitness = None
best_formation = None
how_many_first_lines_tried = 0
#
entry_timestamp = datetime.datetime.now().timestamp()
for home_line_1_ids in itertools.combinations(home_team_players_ids, 3):
best_formation_found = False
for home_line_2_ids in itertools.combinations(home_team_players_ids - set(home_line_1_ids), 3):
for home_line_3_ids in itertools.combinations(
home_team_players_ids - set(home_line_1_ids) - set(home_line_2_ids), 3):
for home_line_4_ids in itertools.combinations(
home_team_players_ids - set(home_line_1_ids) - set(home_line_2_ids) - set(home_line_3_ids), 3):
home_formation = [home_line_1_ids, home_line_2_ids, home_line_3_ids, home_line_4_ids]
# print(home_formation)
# get lines with CATEGORY of players
home_line_1 = list(map(self.player_category_fetcher.category_of_player, home_line_1_ids))
home_line_2 = list(map(self.player_category_fetcher.category_of_player, home_line_2_ids))
home_line_3 = list(map(self.player_category_fetcher.category_of_player, home_line_3_ids))
home_line_4 = list(map(self.player_category_fetcher.category_of_player, home_line_4_ids))
# ok, then:
qs = [self.q_values_fetcher.get(home_line_1, away_line_1),
self.q_values_fetcher.get(home_line_2, away_line_2),
self.q_values_fetcher.get(home_line_3, away_line_3),
self.q_values_fetcher.get(home_line_4, away_line_4)]
fitness = comb_q_values(qs)
# print(qs)
if (best_fitness is None) or (fitness > best_fitness):
best_fitness = fitness
best_formation = home_formation
best_formation_found = True
print("Best fitness: %.2f by formation %s" % (best_fitness, best_formation))
how_many_first_lines_tried += 1
if best_formation_found:
time_it_took = datetime.datetime.now().timestamp() - entry_timestamp
time_per_cycle = time_it_took / how_many_first_lines_tried
print("=======> Took %.2f secs. to look at %d first-lines; I think we have around %.2f secs. to go" % (
time_it_took, how_many_first_lines_tried, (220 - how_many_first_lines_tried) * time_per_cycle))
print("ALL DONE!!!!!!")
print("================================")
print("Best fitness: %.2f by formation \n%s, to play against \n%s" % (best_fitness, best_formation, away_team_lines))
print("================================")
return best_formation
def recommend_lines_maximize_average(
self,
home_team_players_ids: Set[int],
away_team_lines: List[List[PlayerType]]) -> List[List[int]]:
return self.recommend_lines(home_team_players_ids, away_team_lines, comb_q_values=(lambda a_list: sum(a_list) / len(a_list)))
def recommend_lines_maximize_max(
self,
home_team_players_ids: Set[int],
away_team_lines: List[List[PlayerType]]) -> List[List[int]]:
return self.recommend_lines(home_team_players_ids, away_team_lines, comb_q_values=(lambda a_list: max(a_list)))
# ###########################################################################################
# ###########################################################################################
# ###########################################################################################
# ############################################################
# PUT ALL THE FOLLOWING INTO TESTING
q_value_tuples = [
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.OFFENSIVE],
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE],
25),
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.OFFENSIVE],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
12),
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE],
20),
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
1),
]
line_rec = LineRecommender(
player_category_fetcher=CategoryFetcher(),
q_values_fetcher=QValuesFetcherFromDict.from_tuples(q_value_tuples))
# home_lines_rec = line_rec.recommend_lines(home_team_players_ids=set([1] + list(range(20, 31))),
# away_team_lines = [
# [PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE],
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
# ],
# comb_q_values=(lambda a_list: sum(a_list) / len(a_list)))
# print(home_lines_rec)
#
# ###
home_lines_rec = line_rec.recommend_lines_maximize_average(home_team_players_ids=set([1] + list(range(20, 31))),
away_team_lines = [
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
])
print(home_lines_rec)
# ###
home_lines_rec = line_rec.recommend_lines_maximize_max(home_team_players_ids=set([1] + list(range(20, 31))),
away_team_lines = [
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL],
])
print(home_lines_rec)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment