Beispiel #1
0
    def messages(self, gamedb: Database) -> List[SMSEventMessage]:
        player_messages = []
        losing_teams = gamedb.stream_teams(from_tribe=self.losing_tribe)
        winning_teams = gamedb.stream_teams(from_tribe=self.winning_tribe)

        for team in losing_teams:
            losing_players = []

            # TODO(brandon): optimize this.
            for player in gamedb.list_players(from_team=team):
                losing_players.append(player)

            options_map = messages.players_as_formatted_options_map(
                players=losing_players)

            for player in losing_players:
                gamedb.ballot(player_id=player.id,
                              options=options_map.options,
                              challenge_id=None)

            # NOTE: UX isn't perfect here because we'll show the player's own name
            # as an option to vote out. For MVP this helps with scale because the alternative
            # requires sending a different message to every player (as opposed to every team)
            # which is about a 5x cost increase for SMS.
            player_messages.append(
                SMSEventMessage(
                    content=messages.
                    NOTIFY_MULTI_TRIBE_COUNCIL_EVENT_LOSING_MSG_FMT.format(
                        header=messages.game_sms_header(gamedb=gamedb),
                        tribe=self.losing_tribe.name,
                        time=self.game_options.game_schedule.
                        localized_time_string(self.game_options.game_schedule.
                                              daily_tribal_council_end_time),
                        options=options_map.formatted_string),
                    recipient_phone_numbers=[
                        p.phone_number for p in losing_players
                    ]))

        winning_player_phone_numbers = []
        for team in winning_teams:
            winning_players = gamedb.list_players(from_team=team)
            winning_player_phone_numbers.extend(
                [p.phone_number for p in winning_players])

        player_messages.append(
            SMSEventMessage(
                content=messages.
                NOTIFY_MULTI_TRIBE_COUNCIL_EVENT_WINNING_MSG_FMT.format(
                    header=messages.game_sms_header(gamedb=gamedb),
                    winning_tribe=self.winning_tribe.name,
                    losing_tribe=self.losing_tribe.name,
                    time=self.game_options.game_schedule.localized_time_string(
                        self.game_options.game_schedule.
                        daily_tribal_council_end_time),
                    challenge_time=self.game_options.game_schedule.
                    localized_time_string(self.game_options.game_schedule.
                                          daily_challenge_start_time)),
                recipient_phone_numbers=winning_player_phone_numbers))
        return player_messages
Beispiel #2
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     player_messages = []
     players = gamedb.stream_players(active_player_predicate_value=True)
     # TODO(brandon): parallelize
     # NOTE(brandon) this is going to be problematic at scale. we're sending personalized links to
     # every player in the game, which costs 1 API call / player within Twilio. If we can make these links
     # standard then a single Notify API call can address all users in a single game. Non-critical for MVP.
     for player in players:
         player_messages.append(
             SMSEventMessage(
                 content=messages.NOTIFY_TRIBAL_CHALLENGE_EVENT_MSG_FMT.
                 format(
                     header=messages.game_sms_header(gamedb=gamedb),
                     challenge=self.challenge.name,
                     # TODO(brandon) refactor into common routes location
                     link=
                     "{hostname}/challenge-submission/{player_id}/{game_id}/{challenge_id}"
                     .format(hostname=messages.VIR_US_HOSTNAME,
                             game_id=self.game_id,
                             player_id=player.id,
                             challenge_id=self.challenge.id),
                     time=self.game_options.game_schedule.
                     localized_time_string(self.game_options.game_schedule.
                                           daily_challenge_end_time)),
                 recipient_phone_numbers=[player.phone_number]))
     return player_messages
Beispiel #3
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 #4
0
    def messages(self, gamedb: Database) -> List[SMSEventMessage]:
        player_messages = []
        count_players = gamedb.count_players(
            from_tribe=gamedb.tribe_from_id(self.winning_player.tribe_id))
        # TODO(brandon): unclear why this can't be done in a single bulk message.
        for player in self.losing_players:
            options_map = messages.players_as_formatted_options_map(
                players=self.losing_players, exclude_player=player)
            # NOTE(brandon): we perform this synchronously to guarantee that ballots are
            # created in the DB before SMS messages go out to users.
            gamedb.ballot(player_id=player.id,
                          options=options_map.options,
                          challenge_id=None)
            player_messages.append(
                SMSEventMessage(
                    content=messages.
                    NOTIFY_SINGLE_TEAM_COUNCIL_EVENT_LOSING_MSG_FMT.format(
                        header=messages.game_sms_header(gamedb=gamedb),
                        winner=messages.format_tiktok_username(
                            self.winning_player.tiktok),
                        players=count_players,
                        time=self.game_options.game_schedule.
                        localized_time_string(self.game_options.game_schedule.
                                              daily_challenge_end_time),
                        options=options_map.formatted_string),
                    recipient_phone_numbers=[player.phone_number]))

        options_map = messages.players_as_formatted_options_map(
            players=self.losing_players, exclude_player=self.winning_player)
        gamedb.ballot(player_id=self.winning_player.id,
                      options=options_map.options,
                      challenge_id=None)
        player_messages.append(
            SMSEventMessage(
                content=messages.
                NOTIFY_SINGLE_TEAM_COUNCIL_EVENT_WINNING_MSG_FMT.format(
                    header=messages.game_sms_header(gamedb=gamedb),
                    players=count_players,
                    time=self.game_options.game_schedule.localized_time_string(
                        self.game_options.game_schedule.
                        daily_challenge_end_time),
                    options=options_map.formatted_string),
                recipient_phone_numbers=[self.winning_player.phone_number]))

        return player_messages
Beispiel #5
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     game_hashtag = gamedb.game_from_id(id=self.game_id).hashtag
     return [
         SMSEventMessage(
             content=messages.
             NOTIFY_WINNER_ANNOUNCEMENT_EVENT_WINNER_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 game=game_hashtag),
             recipient_phone_numbers=[self.winner.phone_number]),
         SMSEventMessage(
             content=messages.
             NOTIFY_WINNER_ANNOUNCEMENT_EVENT_GENERAL_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 player=messages.format_tiktok_username(self.winner.tiktok),
                 game=game_hashtag),
             recipient_phone_numbers=[
                 p.phone_number for p in gamedb.stream_players(
                     active_player_predicate_value=False)
             ])
     ]
Beispiel #6
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     return [
         SMSEventMessage(
             content=messages.NOTIFY_PLAYER_SCORE_EVENT_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 points=self.points,
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_challenge_end_time)),
             recipient_phone_numbers=[self.player.phone_number])
     ]
Beispiel #7
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     player_messages = []
     team = gamedb.team_from_id(id=self.player.team_id)
     teammate_phone_numbers = [
         p.phone_number for p in gamedb.list_players(from_team=team)
         if p.id != self.player.id
     ]
     player_messages.append(
         SMSEventMessage(
             content=messages.NOTIFY_PLAYER_VOTED_OUT_TEAM_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 player=messages.format_tiktok_username(self.player.tiktok),
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_challenge_start_time)),
             recipient_phone_numbers=teammate_phone_numbers))
     player_messages.append(
         SMSEventMessage(
             content=messages.NOTIFY_PLAYER_VOTED_OUT_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb)),
             recipient_phone_numbers=[self.player.phone_number]))
     return player_messages
Beispiel #8
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     return [
         SMSEventMessage(
             content=messages.NOTIFY_IMMUNITY_AWARDED_EVENT_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 date=self.game_options.game_schedule.
                 tomorrow_localized_string,
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_challenge_start_time)),
             recipient_phone_numbers=[
                 p.phone_number
                 for p in gamedb.list_players(from_team=self.team)
             ])
     ]
Beispiel #9
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     team_players = gamedb.list_players(from_team=self.team)
     return [
         SMSEventMessage(
             content=messages.NOTIFY_TEAM_REASSIGNMENT_EVENT_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 team=messages.players_as_formatted_list(
                     players=team_players),
                 date=self.game_options.game_schedule.
                 tomorrow_localized_string,
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_challenge_start_time)),
             recipient_phone_numbers=[self.player.phone_number])
     ]
Beispiel #10
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     return [
         SMSEventMessage(
             content=messages.
             NOTIFY_TRIBAL_COUNCIL_COMPLETION_EVENT_MSG_FMT.format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 date=self.game_options.game_schedule.
                 tomorrow_localized_string,
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_challenge_start_time)),
             recipient_phone_numbers=[
                 p.phone_number for p in gamedb.stream_players(
                     active_player_predicate_value=True)
             ])
     ]
Beispiel #11
0
    def _reschedule_or_cancel_game(self, game_snap: DocumentSnapshot,
                                   game_dict: dict, players: list):
        log_message(message="Rescheduling or cancelling game",
                    game_id=game_dict.get("id"))

        now_date = datetime.datetime.utcnow().strftime('%Y-%m-%d')
        if 'times_rescheduled' not in game_dict:
            game_dict['times_rescheduled'] = 0
        if 'max_reschedules' not in game_dict:
            game_dict['max_reschedules'] = 1

        if (game_dict.get("times_rescheduled")
                if game_dict.get("times_rescheduled") else
                0) < game_dict.get("max_reschedules"):
            # Reschedule the game by setting current UTC date to last_checked_date.
            # Server will then not check the game until following week
            # Assume times_rescheduled is optional and max_reschedules is True
            times_rescheduled = game_dict["times_rescheduled"] + \
                1 if game_dict.get("times_rescheduled") else 1
            field_updates = {
                'last_checked_date': now_date,
                'times_rescheduled': times_rescheduled
            }
            try:
                game_snap.reference.update(field_updates)
                log_message(message="Game successfully rescheduled",
                            game_id=game_dict.get("id"))

                schedule = STV_I18N_TABLE[self._region]
                notif_message = messages.NOTIFY_GAME_RESCHEDULED_EVENT_MSG_FMT.format(
                    header=messages.game_sms_header(
                        hashtag=game_dict.get('hashtag')),
                    game=game_dict.get("hashtag"),
                    reason="insufficient players",
                    date=schedule.nextweek_localized_string,
                    time=schedule.localized_time_string(
                        schedule.daily_challenge_start_time))
                self._notify_players(game_id=game_dict.get("id"),
                                     players=players,
                                     message=notif_message)
            except Exception as e:
                log_message(message="Error rescheduling game: {}".format(e),
                            game_id=game_dict.get("id"))
        else:
            self._cancel_game(game_snap=game_snap, players=players)
Beispiel #12
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 #13
0
 def messages(self, gamedb: Database) -> List[SMSEventMessage]:
     options_map = messages.players_as_formatted_options_map(
         players=self.finalists)
     # TODO(brandon): this is slow and expensive, but it should work.
     for player in gamedb.stream_players():
         gamedb.ballot(player_id=player.id,
                       challenge_id=None,
                       options=options_map.options,
                       is_for_win=True)
     return [
         SMSEventMessage(
             content=messages.NOTIFY_FINAL_TRIBAL_COUNCIL_EVENT_MSG_FMT.
             format(
                 header=messages.game_sms_header(gamedb=gamedb),
                 players=len(self.finalists),
                 game=gamedb.game_from_id(id=self.game_id).hashtag,
                 time=self.game_options.game_schedule.localized_time_string(
                     self.game_options.game_schedule.
                     daily_tribal_council_end_time),
                 options=options_map.formatted_string),
             recipient_phone_numbers=[
                 p.phone_number for p in gamedb.stream_players()
             ])
     ]
Beispiel #14
0
 def message_content(self, gamedb: Database) -> str:
     return messages.NOTIFY_IMMUNITY_AWARDED_EVENT_MSG_FMT.format(
         header=messages.game_sms_header(gamedb=gamedb),
         date=self.game_options.game_schedule.tomorrow_localized_string,
         time=self.game_options.game_schedule.localized_time_string(
             self.game_options.game_schedule.daily_challenge_start_time))