Beispiel #1
0
    def _get_voted_out_player(self, team: Team,
                              gamedb: Database) -> [Player, None]:
        high = 0
        candidates = []
        log_message(message="Counting votes from team {}.".format(team),
                    game_id=self._game_id)
        team_votes = gamedb.count_votes(from_team=team)
        log_message(message="Got votes {}.".format(pprint.pformat(team_votes)),
                    game_id=self._game_id)

        for _, votes in team_votes.items():
            if votes > high:
                high = votes

        for id, votes in team_votes.items():
            if votes == high:
                candidates.append(id)

        num_candidates = len(candidates)
        if num_candidates == 1:
            return gamedb.player_from_id(candidates[0])
        elif num_candidates > 1:
            return gamedb.player_from_id(candidates[random.randint(
                0, num_candidates - 1)])
        else:
            raise GameError("Unable to determine voted out player.")
Beispiel #2
0
    def _score_entries_tribe_aggregate(self, tribe: Tribe,
                                       challenge: Challenge, gamedb: Database,
                                       engine: Engine):
        score_dict = {'score': 0}
        players = gamedb.count_players(from_tribe=tribe)
        entries = gamedb.stream_entries(from_tribe=tribe,
                                        from_challenge=challenge)

        with ThreadPoolExecutor(max_workers=self._options.
                                engine_worker_thread_count) as executor:
            executor.submit(self._score_entries_tribe_aggregate_fn,
                            entries=entries,
                            challenge=challenge,
                            score_dict=score_dict,
                            gamedb=gamedb,
                            engine=engine)

        # tribe score = avg score of all tribe members
        log_message(message="_score_entries_tribe_agg = {}.".format(
            score_dict['score']),
                    game_id=self._game_id)

        if players > 0:
            return score_dict['score'] / players
        return 0
Beispiel #3
0
    def _run_single_team_council(self, team: Team, losing_players: List[Player], gamedb: Database, engine: Engine):
        # announce winner and tribal council for losing teams
        gamedb.clear_votes()

        winning_player = [player for player in gamedb.list_players(
            from_team=team) if player not in losing_players][0]
        engine.add_event(events.NotifySingleTeamCouncilEvent(game_id=self._game_id, game_options=self._options,
                                                             winning_player=winning_player, losing_players=losing_players))
        tribal_council_start_timestamp = _unixtime()

        # wait for votes
        while (((_unixtime() - tribal_council_start_timestamp)
                < self._options.single_team_council_time_sec) and not self._stop.is_set()):
            log_message("Waiting for tribal council to end.")
            time.sleep(self._options.game_wait_sleep_interval_sec)

        # count votes
        voted_out_player = self._get_voted_out_player(team=team, gamedb=gamedb)
        if voted_out_player:
            gamedb.deactivate_player(player=voted_out_player)
            log_message("Deactivated player {}.".format(voted_out_player))
            engine.add_event(events.NotifyPlayerVotedOutEvent(game_id=self._game_id, game_options=self._options,
                                                              player=voted_out_player))

        # notify all players of what happened at tribal council
        engine.add_event(
            events.NotifyTribalCouncilCompletionEvent(game_id=self._game_id, game_options=self._options))
Beispiel #4
0
    def _score_entries_top_k_teams_fn(self, entries: Iterable,
                                      challenge: Challenge, score_dict: Dict,
                                      gamedb: Database, engine: Engine):
        entries_iter = iter(entries)
        while not self._stop_event.is_set():
            try:
                entry = next(entries_iter)
                log_message(message="Entry {}.".format(entry),
                            game_id=self._game_id)
                points = self._score_entry(entry=entry)
                player = gamedb.player_from_id(entry.player_id)
                engine.add_event(
                    events.NotifyPlayerScoreEvent(game_id=self._game_id,
                                                  game_options=self._options,
                                                  player=player,
                                                  challenge=challenge,
                                                  entry=entry,
                                                  points=points))

                if player.team_id not in score_dict:
                    score_dict[player.team_id] = points
                else:
                    score_dict[player.team_id] += points
            except StopIteration:
                break
Beispiel #5
0
 def set_stop(self):
     log_message("Received stop signal for matchmaker in region={}".format(
         self._region))
     self._stop.set()
     # Wait for thread to finish executing/sleeping. This may take a long time
     self._thread.join()
     self._daemon_started = False
Beispiel #6
0
    def _score_entries_top_k_players(self, team: Team, challenge: Challenge, gamedb: Database, engine: Engine) -> List[Player]:
        player_scores = {}
        top_scores = list()
        losing_players = list()
        entries = gamedb.stream_entries(
            from_team=team, from_challenge=challenge)

        with ThreadPoolExecutor(max_workers=self._options.engine_worker_thread_count) as executor:
            executor.submit(self._score_entries_top_k_players_fn,
                            entries=entries, challenge=challenge, score_dict=player_scores, gamedb=gamedb, engine=engine)

        for player_id, score in player_scores.items():
            heapq.heappush(top_scores, (score, player_id))

        # note that the default python heap pops in ascending order,
        # so the rank here is actually worst to best.
        num_scores = len(top_scores)
        if num_scores == 1:
            raise GameError(
                "Unable to rank losing players with team size = 1.")
        else:
            for rank in range(num_scores):
                score, player_id = heapq.heappop(top_scores)
                log_message("Player {} rank {} with score {}.".format(
                    player_id, rank, score))

                # all but the highest scorer lose
                if rank < (num_scores - 1):
                    losing_players.append(gamedb.player_from_id(player_id))

        return losing_players
Beispiel #7
0
    def _run_finalist_tribe_council(self, finalists: List[Player], gamedb: Database, engine: Engine) -> Player:
        gamedb.clear_votes()

        engine.add_event(
            events.NotifyFinalTribalCouncilEvent(
                game_id=self._game_id, game_options=self._options, finalists=finalists))
        tribal_council_start_timestamp = _unixtime()

        # wait for votes
        while (((_unixtime() - tribal_council_start_timestamp)
                < self._options.final_tribal_council_time_sec) and not self._stop.is_set()):
            log_message("Waiting for tribal council to end.")
            time.sleep(self._options.game_wait_sleep_interval_sec)

        # count votes
        player_votes = gamedb.count_votes(is_for_win=True)
        max_votes = 0
        winner = None

        for player_id, votes in player_votes.items():
            if votes > max_votes:
                max_votes = votes
                winner = gamedb.player_from_id(id=player_id)

        # announce winner
        engine.add_event(events.NotifyWinnerAnnouncementEvent(
            game_id=self._game_id, game_options=self._options, winner=winner))
        return winner
Beispiel #8
0
    def _play_game(self,
                   game: Game,
                   game_snap: DocumentSnapshot,
                   players: list,
                   game_dict: dict,
                   is_test: bool = False):
        log_message("Starting a game",
                    game_id=game_dict.get("id"),
                    additional_tags=game_dict)

        if is_test:
            database = MockDatabase()
            engine = MockPlayEngine().CreateEngine(database)
        else:
            # NOTE(brandon): the game DB instance used by the matchmaker is for searching over all games. when we create
            # a game instance, we also supply new game DB and engine objects that have the specific game ID.
            database = FirestoreDB(json_config_path=json_config_path,
                                   game_id=game._game_id)
            engine = Engine(options=game._options,
                            game_id=game._game_id,
                            sqs_config_path=_TEST_AMAZON_SQS_CONFIG_PATH,
                            twilio_config_path=_TEST_TWILIO_SMS_CONFIG_PATH,
                            gamedb=database)
        try:
            game_data = self._matchmaker.generate_tribes(
                game_id=game._game_id,
                players=players,
                game_options=game._options,
                gamedb=database)
            tribes = game_data['tribes']
            message = messages.NOTIFY_GAME_STARTED_EVENT_MSG_FMT.format(
                header=messages.game_sms_header(
                    hashtag=game_dict.get('hashtag')),
                game=game_dict.get('hashtag'))
            self._notify_players(game_id=game._game_id,
                                 players=players,
                                 message=message)
            if self._is_mvp:
                # NOTE(brandon): changing to thread for now. can't pickle non-primitive engine object.
                game_thread = threading.Thread(target=game.play,
                                               args=(tribes[0], tribes[1],
                                                     database, engine))
                game_thread.start()
            else:
                # start on new GCP instance
                pass
        except MatchMakerError as e:
            # Catches error from matchmaker algorithm
            message = "Matchmaker Error: {}".format(e)
            log_message(message=message, game_id=game._game_id)
            self._set_game_has_started(game_snap=game_snap,
                                       game=game,
                                       value=False)
            self._notify_players(game_id=game._game_id,
                                 players=players,
                                 message=message)
            self._reschedule_or_cancel_game(game_snap=game_snap,
                                            game_dict=game_dict,
                                            players=players)
Beispiel #9
0
 def _get_challenge(self, gamedb: Database) -> Challenge:
     available_challenge_count = 0
     while available_challenge_count == 0 and not self._stop.is_set():
         log_message("Waiting for next challenge to become available.")
         time.sleep(self._options.game_wait_sleep_interval_sec)
         available_challenges = gamedb.list_challenges(
             challenge_completed_predicate_value=False)
         available_challenge_count = len(available_challenges)
     return available_challenges[0]
Beispiel #10
0
    def _score_entries_top_k_teams(
            self, k: float, tribe: Tribe, challenge: Challenge,
            gamedb: Database, engine: Engine) -> Tuple[List[Team], List[Team]]:
        team_scores = {}
        top_scores = list()
        winning_teams = list()
        losing_teams = list()

        entries = gamedb.stream_entries(from_tribe=tribe,
                                        from_challenge=challenge)

        with ThreadPoolExecutor(max_workers=self._options.
                                engine_worker_thread_count) as executor:
            executor.submit(self._score_entries_top_k_teams_fn,
                            entries=entries,
                            challenge=challenge,
                            score_dict=team_scores,
                            gamedb=gamedb,
                            engine=engine)

        for team_id, score in team_scores.items():
            heapq.heappush(
                top_scores,
                (score /
                 gamedb.count_players(from_team=gamedb.team_from_id(team_id)),
                 team_id))

        rank_threshold = float(k * len(top_scores))
        log_message(message="Rank threshold = {}".format(rank_threshold),
                    game_id=self._game_id)

        # note that the default python heap pops in ascending order,
        # so the rank here is actually worst to best.
        num_scores = len(top_scores)
        if num_scores == 1:
            score, team_id = heapq.heappop(top_scores)
            log_message(message="Winner {}.".format(team_id),
                        game_id=self._game_id)
            winning_teams = [gamedb.team_from_id(team_id)]
        else:
            for rank in range(num_scores):
                score, team_id = heapq.heappop(top_scores)
                log_message(message="Team {} rank {} with score {}.".format(
                    team_id, rank, score),
                            game_id=self._game_id)
                if rank >= rank_threshold:
                    log_message(message="Winner {}.".format(team_id),
                                game_id=self._game_id)
                    winning_teams.append(gamedb.team_from_id(team_id))
                else:
                    log_message(message="Loser {}.".format(team_id),
                                game_id=self._game_id)
                    losing_teams.append(gamedb.team_from_id(team_id))

        return (winning_teams, losing_teams)
Beispiel #11
0
 def put_fn(self, event: SMSEvent) -> None:
     # TODO(brandon) add retry logic and error handling.
     log_message("Putting {} on queue {}.".format(
         event.to_json(), self._url))
     response = self._client.send_message(
         QueueUrl=self._url,
         MessageBody=event.to_json(),
         MessageGroupId=event.game_id,
         MessageDeduplicationId=str(uuid.uuid4())
     )
     log_message(response)
Beispiel #12
0
 def start_matchmaker_daemon(self,
                             sleep_seconds: int = 60,
                             is_test: bool = False):
     if not self._daemon_started and not self._stop.is_set():
         self._thread = threading.Thread(target=self._matchmaker_function,
                                         args=(sleep_seconds, is_test))
         self._daemon_started = True
         self._thread.start()
     else:
         log_message(
             "Failed to start new matchmaker for region={} (matchmaker already running)"
             .format(self._region))
Beispiel #13
0
    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))
Beispiel #14
0
    def _run_single_tribe_council(self, winning_teams: List[Team], losing_teams: List[Team],
                                  gamedb: Database, engine: Engine):

        # announce winner and tribal council for losing teams
        gamedb.clear_votes()
        engine.add_event(events.NotifySingleTribeCouncilEvent(
            game_id=self._game_id, game_options=self._options,
            winning_teams=winning_teams, losing_teams=losing_teams))
        tribal_council_start_timestamp = _unixtime()

        # wait for votes
        while (((_unixtime() - tribal_council_start_timestamp)
                < self._options.single_tribe_council_time_sec) and not self._stop.is_set()):
            log_message("Waiting for tribal council to end.")
            time.sleep(self._options.game_wait_sleep_interval_sec)

        # count votes
        for team in losing_teams:
            voted_out_player = self._get_voted_out_player(
                team=team, gamedb=gamedb)
            if voted_out_player:
                gamedb.deactivate_player(player=voted_out_player)
                log_message("Deactivated player {}.".format(voted_out_player))
                engine.add_event(events.NotifyPlayerVotedOutEvent(game_id=self._game_id, game_options=self._options,
                                                                  player=voted_out_player))
            else:
                log_message("For some reason no one got voted out...")
                log_message("Players = {}.".format(
                    pprint.pformat(gamedb.list_players(from_team=team))))

        # notify all players of what happened at tribal council
        engine.add_event(
            events.NotifyTribalCouncilCompletionEvent(game_id=self._game_id, game_options=self._options))
Beispiel #15
0
 def _get_next_challenge(self, gamedb: Database) -> Challenge:
     available_challenge_count = 0
     while available_challenge_count == 0 and not self._stop_event.is_set():
         log_message("Waiting for next challenge to become available.")
         time.sleep(self._options.game_wait_sleep_interval_sec)
         available_challenges = gamedb.list_challenges(
             challenge_completed_predicate_value=False)
         available_challenge_count = len(available_challenges)
     challenge = available_challenges[0]
     # return serializable challenge since this gets placed on the event queue.
     return Challenge(id=challenge.id,
                      name=challenge.name,
                      message=challenge.message,
                      complete=challenge.complete)
Beispiel #16
0
    def play(self, tribe1: Tribe, tribe2: Tribe, gamedb: Database,
             engine: Engine) -> Player:
        self._wait_for_game_start_time()

        last_tribe_standing = self._play_multi_tribe(tribe1=tribe1,
                                                     tribe2=tribe2,
                                                     gamedb=gamedb,
                                                     engine=engine)
        log_message(
            message="Last tribe standing is {}.".format(last_tribe_standing),
            game_id=self._game_id)
        last_team_standing = self._play_single_tribe(tribe=last_tribe_standing,
                                                     gamedb=gamedb,
                                                     engine=engine)

        log_message(
            message="Last team standing is {}.".format(last_team_standing),
            game_id=self._game_id)
        finalists = self._play_single_team(team=last_team_standing,
                                           gamedb=gamedb,
                                           engine=engine)

        log_message(message="Finalists are {}.".format(
            pprint.pformat(finalists)),
                    game_id=self._game_id)
        winner = self._run_finalist_tribe_council(finalists=finalists,
                                                  gamedb=gamedb,
                                                  engine=engine)

        log_message(message="Winner is {}.".format(winner),
                    game_id=self._game_id)

        engine.stop()
        return winner
Beispiel #17
0
 def send_bulk_sms(self, message: str,
                   recipient_addresses: Iterable[str]) -> None:
     notification = self._client.notify.services(
         self._notify_service_sid).notifications.create(
             to_binding=[
                 json.dumps({
                     'binding_type': 'sms',
                     'address': self._normalize_sms_address(address)
                 }) for address in recipient_addresses
             ],
             body=self._normalize_sms_message(message))
     log_message(message=str(notification.sid),
                 game_id=self._game_id,
                 additional_tags={"phone_number": self._phone_number})
Beispiel #18
0
    def _notify_players(self, game_id: Text, players: list, message: Text):
        twilio = self._get_sms_notifier(game_id=game_id)

        # iterate over players and get their phone numbers
        recipient_phone_numbers = list(
            map(lambda player: player.to_dict().get("phone_number"), players))
        # filter out players with no phone number
        filtered_phone_numbers = list(
            filter(lambda number: not not number, recipient_phone_numbers))

        twilio.send_bulk_sms(message=message,
                             recipient_addresses=filtered_phone_numbers)
        log_message(message="Notified players with message:{}".format(message),
                    game_id=game_id)
Beispiel #19
0
 def _wait_for_game_start_time(self) -> None:
     if self._options.game_clock_mode == GameClockMode.SYNC:
         game_start_time_sec = _unixtime(
         ) + self._options.game_schedule.localized_time_delta_sec(
             end_time=self._options.game_schedule.game_start_time)
         while ((_unixtime() < game_start_time_sec)
                and not self._stop_event.is_set()
                and not self._wait_for_game_start_event.is_set()):
             log_message("Waiting until {} for game start.".format(
                 game_start_time_sec))
             time.sleep(self._options.game_wait_sleep_interval_sec)
     elif self._options.game_clock_mode == GameClockMode.ASYNC:
         # start immediately.
         log_message("Initiating game {} with timing mode.".format(
             self._options.game_clock_mode))
Beispiel #20
0
 def put_fn(self, event: SMSEvent) -> None:
     try:
         # TODO(brandon) add retry logic and error handling.
         log_message(message="Putting {} on queue {}.".format(
             event.to_json(), self._url),
                     game_id=self.game_id)
         response = self._client.send_message(QueueUrl=self._url,
                                              MessageBody=event.to_json(),
                                              MessageGroupId=event.game_id,
                                              MessageDeduplicationId=str(
                                                  uuid.uuid4()))
     except Exception as e:
         log_message(
             messages=
             f'put_fn failed for event {event} with exception {str(e)}.')
Beispiel #21
0
 def _wait_for_challenge_end_time(self, challenge: Challenge) -> None:
     if self._options.game_clock_mode == GameClockMode.SYNC:
         challenge_end_time_sec = _unixtime(
         ) + self._options.game_schedule.localized_time_delta_sec(
             end_time=self._options.game_schedule.daily_challenge_end_time)
         while not self._stop_event.is_set(
         ) and not self._wait_for_challenge_end_event.is_set():
             log_message(
                 "Waiting until {} for daily challenge start.".format(
                     challenge_end_time_sec))
             time.sleep(self._options.game_wait_sleep_interval_sec)
     elif self._options.game_clock_mode == GameClockMode.ASYNC:
         log_message(
             f"Waiting {self._options.game_wait_sleep_interval_sec}s for challenge to {str(challenge)} to end..."
         )
         time.sleep(self._options.game_wait_sleep_interval_sec)
Beispiel #22
0
 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
Beispiel #23
0
 def _set_game_has_started(self,
                           game_snap: DocumentSnapshot,
                           game: Game,
                           value: bool = True):
     field_updates = {'game_has_started': value}
     try:
         game_snap.reference.update(field_updates)
         log_message(
             message="Set game_has_started field to {}".format(value),
             game_id=game._game_id)
     except Exception as e:
         log_message(
             message=
             "Error setting game document game_has_started field to {}: {}".
             format(value, e),
             game_id=game._game_id)
         raise RuntimeError(str(e))
Beispiel #24
0
 def _wait_for_tribal_council_start_time(self) -> None:
     if self._options.game_clock_mode == GameClockMode.SYNC:
         tribal_council_start_time_sec = _unixtime(
         ) + self._options.game_schedule.localized_time_delta_sec(
             end_time=self._options.game_schedule.
             daily_tribal_council_start_time)
         while ((_unixtime() < tribal_council_start_time_sec)
                and not self._stop_event.is_set()
                and not self._wait_for_tribal_council_start_event.is_set()):
             log_message("Waiting until {} for tribal council.".format(
                 tribal_council_start_time_sec))
             time.sleep(self._options.game_wait_sleep_interval_sec)
     elif self._options.game_clock_mode == GameClockMode.ASYNC:
         # start immediately.
         log_message(
             "Initiating tribal council in {} game timing mode.".format(
                 self._options.game_clock_mode))
         return
Beispiel #25
0
 def _cancel_game(self,
                  game_snap: DocumentSnapshot,
                  players: list,
                  reason: str = "insufficient players") -> None:
     # Cancel the game
     game_dict = game_snap.to_dict()
     field_updates = {
         'to_be_deleted': True,
     }
     game_snap.reference.update(field_updates)
     log_message(message="Cancelled the game (set to_be_deleted flag)",
                 game_id=game_dict.get("id"))
     notif_message = messages.NOTIFY_GAME_CANCELLED_EVENT_MSG_FMT.format(
         header=messages.game_sms_header(hashtag=game_dict.get('hashtag')),
         game=game_dict.get("hashtag"),
         reason=reason)
     self._notify_players(game_id=game_dict.get("id"),
                          players=players,
                          message=notif_message)
Beispiel #26
0
    def get(self) -> SMSEvent:
        response = self._client.receive_message(QueueUrl=self._url,
                                                MessageAttributeNames=[
                                                    'string',
                                                ],
                                                MaxNumberOfMessages=1,
                                                WaitTimeSeconds=20)

        if 'Messages' in response:
            message = response['Messages'][0]
            message_body = message['Body']
            log_message(message='Received event with message body {}'.format(
                message_body),
                        game_id=self.game_id)
            self._delete_message(message['ReceiptHandle'])
            return SMSEvent.from_json(json_text=message_body,
                                      game_options=self._game_options)
        else:
            raise EventQueueError('Queue empty.')
Beispiel #27
0
    def _run_single_team_council(self, team: Team,
                                 losing_players: List[Player],
                                 gamedb: Database, engine: Engine):
        self._wait_for_tribal_council_start_time()

        # announce winner and tribal council for losing teams
        gamedb.clear_votes()

        winning_players = [
            player for player in gamedb.list_players(from_team=team)
            if player not in losing_players
        ]
        if len(winning_players) > 0:
            winning_player = winning_players[0]
        else:
            engine.stop()
            raise GameError(
                "Unable to determine a winning player for the challenge. Have any entries been submitted?"
            )

        engine.add_event(
            events.NotifySingleTeamCouncilEvent(game_id=self._game_id,
                                                game_options=self._options,
                                                winning_player=winning_player,
                                                losing_players=losing_players))
        self._wait_for_tribal_council_end_time()

        # count votes
        voted_out_player = self._get_voted_out_player(team=team, gamedb=gamedb)
        if voted_out_player:
            gamedb.deactivate_player(player=voted_out_player)
            log_message(
                message="Deactivated player {}.".format(voted_out_player),
                game_id=self._game_id)
            engine.add_event(
                events.NotifyPlayerVotedOutEvent(game_id=self._game_id,
                                                 game_options=self._options,
                                                 player=voted_out_player))

        # notify all players of what happened at tribal council
        engine.add_event(
            events.NotifyTribalCouncilCompletionEvent(
                game_id=self._game_id, game_options=self._options))
Beispiel #28
0
    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)
Beispiel #29
0
 def _wait_for_tribal_council_end_time(self) -> None:
     if self._options.game_clock_mode == GameClockMode.SYNC:
         tribal_council_end_time_sec = _unixtime(
         ) + self._options.game_schedule.localized_time_delta_sec(
             end_time=self._options.game_schedule.
             daily_tribal_council_end_time)
         while ((_unixtime() < tribal_council_end_time_sec)
                and not self._stop_event.is_set()
                and not self._wait_for_tribal_council_start_event.is_set()):
             log_message("Waiting until {} for tribal council.".format(
                 tribal_council_end_time_sec))
             time.sleep(self._options.game_wait_sleep_interval_sec)
     elif self._options.game_clock_mode == GameClockMode.ASYNC:
         tribal_council_start_timestamp = _unixtime()
         while (((_unixtime() - tribal_council_start_timestamp) <
                 self._options.tribe_council_time_sec)
                and not self._stop_event.is_set()
                and not self._wait_for_tribal_council_end_event.is_set()):
             log_message("Waiting for tribal council to end.")
             time.sleep(self._options.game_wait_sleep_interval_sec)
Beispiel #30
0
    def _do_work_fn(self) -> None:
        event = None
        notifier = self._get_sms_notifier()
        queue = self._output_events
        while not self._stop.is_set():
            try:
                # leave events on the queue until the critical section lock
                # is released. this prevents async workers from acting on event messages
                # before the database is reconciled by the main game thread. critical
                # for periods of large mutations like team and tribe merges.
                if not self._engine_locked():
                    event = queue.get()
                    game_id = ""
                    if hasattr(event, "game_id"):
                        game_id = event.game_id
                    log_message(
                        message='Engine worker processing event {}'.format(
                            event.to_json()),
                        game_id=self.game_id)
                    notifier.send(sms_event_messages=event.messages(
                        gamedb=self._gamedb))
            except EventQueueError:
                pass
            except Exception as e:
                # TODO(brandon): we need to save this exception to the main thread and
                # cancel the game, otherwise it will crash.
                log_message(
                    message=f'Engine worker failed with exception {str(e)} {traceback.format_stack()}.',
                    game_id=self.game_id)
                self.stop()
                raise

        log_message(message='Shutting down workder thread {}.'.format(
            threading.current_thread().ident),
            game_id=self.game_id)