def _merge_teams(self, target_team_size: int, tribe: Tribe, gamedb: Database, engine: Engine): # team merging is only necessary when the size of the team == 2 # once a team size == 2, it should be merged with another team. the optimal # choice is to keep team sizes as close to the intended size as possible # find all teams with size = 2, these players need to be merged small_teams = gamedb.stream_teams( from_tribe=tribe, team_size_predicate_value=2) merge_candidates = Queue() for team in small_teams: log_message("Found team of 2. Deacticating team {}.".format(team)) # do not deactivate the last active team in the tribe if gamedb.count_teams(from_tribe=tribe, active_team_predicate_value=True) > 1: gamedb.deactivate_team(team) for player in gamedb.list_players(from_team=team): log_message("Adding merge candidate {}.".format(player)) merge_candidates.put(player) sorted_teams = gamedb.stream_teams( from_tribe=tribe, order_by_size=True, descending=False) log_message("Redistributing merge candidates...") # round robin redistribution strategy # simplest case, could use more thought. visited = {} while not merge_candidates.empty() and sorted_teams: for team in sorted_teams: other_options_available = team.id not in visited visited[team.id] = True if (team.size >= target_team_size and other_options_available): log_message("Team {} has size >= target {} and other options are available. " "Continuing search...".format(team, target_team_size)) continue player = merge_candidates.get() if player.team_id == team.id: continue log_message("Merging player {} from team {} into team {}.".format( player, player.team_id, team.id)) player.team_id = team.id team.size = team.size + 1 gamedb.save(team) gamedb.save(player) # notify player of new team assignment engine.add_event(events.NotifyTeamReassignmentEvent(game_id=self._game_id, game_options=self._options, player=player, team=team))
def _merge_tribes(self, tribe1: Tribe, tribe2: Tribe, new_tribe_name: Text, gamedb: Database, engine: Engine) -> Tribe: log_message(message=f"Merging tribes into {new_tribe_name}.") with engine: new_tribe = gamedb.tribe(name=new_tribe_name) gamedb.batch_update_tribe(from_tribe=tribe1, to_tribe=new_tribe) gamedb.batch_update_tribe(from_tribe=tribe2, to_tribe=new_tribe) # after tribes merge, sweep the teams to ensure no size of 2 self._merge_teams(target_team_size=self._options.target_team_size, tribe=new_tribe, gamedb=gamedb, engine=engine) game = gamedb.game_from_id(gamedb.get_game_id()) game.count_tribes = 1 gamedb.save(game) return new_tribe
def _run_challenge(self, challenge: Challenge, gamedb: Database, engine: Engine): # wait for challenge to begin self._wait_for_challenge_start_time(challenge=challenge) # notify players engine.add_event( events.NotifyTribalChallengeEvent(game_id=self._game_id, game_options=self._options, challenge=challenge)) # wait for challenge to end self._wait_for_challenge_end_time(challenge=challenge) challenge.complete = True gamedb.save(challenge)
def _run_challenge(self, challenge: Challenge, gamedb: Database, engine: Engine): # wait for challenge to begin while (_unixtime() < challenge.start_timestamp) and not self._stop.is_set(): log_message("Waiting {}s for challenge to {} to begin.".format( challenge.start_timestamp - _unixtime(), challenge)) time.sleep(self._options.game_wait_sleep_interval_sec) # notify players engine.add_event( events.NotifyTribalChallengeEvent(game_id=self._game_id, game_options=self._options, challenge=challenge)) # wait for challenge to end while (_unixtime() < challenge.end_timestamp) and not self._stop.is_set(): log_message("Waiting {}s for challenge to {} to end.".format( challenge.end_timestamp - _unixtime(), challenge)) time.sleep(self._options.game_wait_sleep_interval_sec) challenge.complete = True gamedb.save(challenge)
def generate_tribes(cls, game_id: str, players: List[DocumentSnapshot], game_options: GameOptions, gamedb: Database) -> dict: tribes = list() teams = list() count_players = len(players) if count_players < game_options.target_team_size: raise MatchMakerError("Insufficient players for given team size") if count_players < game_options.multi_tribe_min_tribe_size * 2: raise MatchMakerError("Insufficient players to make two tribes") # generate tribes for tribe_name in [ _DEFAULT_TRIBE_NAMES[int(n)] for n in random.sample(range(0, len(_DEFAULT_TRIBE_NAMES)), 2) ]: tribe = database.Tribe(id=str(uuid.uuid4()), name="{}".format(tribe_name)) tribes.append(tribe) # generate teams for n in range( 0, math.floor(count_players / game_options.target_team_size)): team = database.Team( id=str(uuid.uuid4()), name="{}".format(n), ) teams.append(team) count_tribes = len(tribes) count_teams = len(teams) team_to_tribe_map = {} # randomly assign team, tribe to each player set_of_mutable_players = set() set_of_mutable_users = set() for n, player in enumerate(players): mutable_user = gamedb.find_user( phone_number=player.get('phone_number')) mutable_user.game_id = game_id mutable_player = gamedb.player_from_id(player.id) tribe = tribes[n % count_tribes] team = teams[n % count_teams] if team.id not in team_to_tribe_map: team_to_tribe_map[team.id] = tribe tribe.count_teams += 1 mutable_player.tribe_id = tribe.id mutable_player.team_id = team.id team.tribe_id = tribe.id tribe.count_players += 1 team.count_players += 1 set_of_mutable_players.add(mutable_player) set_of_mutable_users.add(mutable_user) # Save data game = gamedb.game_from_id(game_id) game.count_tribes = count_tribes game.count_teams = count_teams game.count_players = count_players gamedb.save(game) for tribe in tribes: gamedb.save(tribe) for team in teams: gamedb.save(team) for player in set_of_mutable_players: gamedb.save(player) for user in set_of_mutable_users: gamedb.save(user) d = {} d['players'] = players d['teams'] = teams d['tribes'] = tribes d['game'] = game return d
def _merge_teams(self, target_team_size: int, tribe: Tribe, gamedb: Database, engine: Engine): with engine: # team merging is only necessary when the size of the team == 2 # once a team size == 2, it should be merged with another team, because # a self preservation vote lands in a deadlock. in general, the optimal # choice is to keep team sizes as close to the intended size as possible # up until a merge becomes necessary. # find all teams with size == 2, these players need to be merged small_teams = gamedb.stream_teams(from_tribe=tribe, team_size_predicate_value=2) merge_candidates = Queue() for team in small_teams: # do not deactivate the last active team in the tribe count_teams = gamedb.count_teams( from_tribe=tribe, active_team_predicate_value=True) if count_teams > 1: log_message( message="Found team of 2. Deactivating team {}.". format(team), game_id=self._game_id) gamedb.deactivate_team(team) for player in gamedb.list_players(from_team=team): log_message( message="Adding merge candidate {}.".format(player), game_id=self._game_id) merge_candidates.put(player) sorted_teams = gamedb.stream_teams(from_tribe=tribe, order_by_size=True, descending=False) log_message(message="Redistributing merge candidates...", game_id=self._game_id) # round robin redistribution strategy # simplest case, could use more thought. visited = {} while not merge_candidates.empty() and sorted_teams: for team in itertools.cycle(sorted_teams): team.count_players = _team_count_players(team, gamedb) other_options_available = team.id not in visited visited[team.id] = True if (team.count_players >= target_team_size and other_options_available): log_message( message= "Team {} has size >= target {} and other options are available. " "Continuing search...".format( team, target_team_size), game_id=self._game_id) continue player = None try: player = merge_candidates.get_nowait() except Empty: log_message(message="Merge candidates empty.") return if player.team_id == team.id: log_message( message= f"Player {str(player)} already on team {str(team)}. Continuing." ) continue log_message( message="Merging player {} from team {} into team {}.". format(player, player.team_id, team.id), game_id=self._game_id) player.team_id = team.id team.count_players += 1 gamedb.save(team) gamedb.save(player) # notify player of new team assignment engine.add_event( events.NotifyTeamReassignmentEvent( game_id=self._game_id, game_options=self._options, player=player, team=team))