Example #1
0
    def _run_finalist_tribe_council(self, finalists: List[Player],
                                    gamedb: Database,
                                    engine: Engine) -> Player:
        gamedb.clear_votes()
        self._wait_for_tribal_council_start_time()

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

        # 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)

        for player in gamedb.stream_players(
                active_player_predicate_value=True):
            if player.id != winner.id:
                gamedb.deactivate_player(player=player)

        # announce winner
        engine.add_event(
            events.NotifyWinnerAnnouncementEvent(game_id=self._game_id,
                                                 game_options=self._options,
                                                 winner=winner))
        return winner
Example #2
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
Example #3
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
Example #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
Example #5
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))
Example #6
0
    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)
Example #7
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)
Example #8
0
    def _score_entries_tribe_aggregate_fn(self, entries: Iterable, challenge: Challenge, score_dict: Dict, gamedb: Database, engine: Engine):
        """Note that all built-ins are thread safe in python, meaning we can
        atomically increment the score int held in score_dict."""

        entries_iter = iter(entries)
        while not self._stop.is_set():
            try:
                entry = next(entries_iter)
                pprint.pprint(entry)
                points = entry.likes / entry.views
                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))
                score_dict['score'] += points
            except StopIteration:
                break
Example #9
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)
Example #10
0
    def _run_multi_tribe_council(self, winning_tribe: Tribe,
                                 losing_tribe: Tribe, gamedb: Database,
                                 engine: Engine):
        self._wait_for_tribal_council_start_time()

        # fraction of teams in losing tribe must vote
        non_immune_teams = list()
        for team in gamedb.stream_teams(from_tribe=losing_tribe):
            log_message(message="Found losing team {}.".format(team),
                        game_id=self._game_id)
            immunity_granted = random.random(
            ) < self._options.multi_tribe_team_immunity_likelihood
            if not immunity_granted:
                non_immune_teams.append(team)
            else:
                engine.add_event(
                    events.NotifyImmunityAwardedEvent(
                        game_id=self._game_id,
                        game_options=self._options,
                        team=team))

        # announce winner and tribal council for losing tribe
        gamedb.clear_votes()
        engine.add_event(
            events.NotifyMultiTribeCouncilEvent(game_id=self._game_id,
                                                game_options=self._options,
                                                winning_tribe=winning_tribe,
                                                losing_tribe=losing_tribe))
        self._wait_for_tribal_council_end_time()

        # count votes
        for team in non_immune_teams:
            log_message(
                message="Counting votes for non-immune team {}.".format(team),
                game_id=self._game_id)
            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))
Example #11
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))
Example #12
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))
Example #13
0
 def test_event_processing(self, send_fn):
     engine = Engine(options=self._game_options,
                     sqs_config_path=_TEST_AMAZON_SQS_CONFIG_PATH,
                     twilio_config_path=_TEST_TWILIO_SMS_CONFIG_PATH,
                     gamedb=_gamedb)
     for event in _TEST_EVENTS:
         engine.add_event(event)
     time.sleep(_TEST_EVENT_WAIT_TIME)
     engine.stop()
Example #14
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))
Example #15
0
    def _run_multi_tribe_council(self, winning_tribe: Tribe, losing_tribe: Tribe, gamedb: Database, engine: Engine):
        non_immune_teams = list()

        for team in gamedb.stream_teams(from_tribe=losing_tribe):
            log_message("Found losing team {}.".format(team))
            immunity_granted = random.random() < self._options.multi_tribe_team_immunity_likelihood
            if not immunity_granted:
                non_immune_teams.append(team)
            else:
                engine.add_event(events.NotifyImmunityAwardedEvent(
                    game_id=self._game_id, game_options=self._options, team=team))

        # announce winner and tribal council for losing tribe
        tribal_council_start_timestamp = _unixtime()
        gamedb.clear_votes()
        engine.add_event(events.NotifyMultiTribeCouncilEvent(game_id=self._game_id, game_options=self._options,
                                                             winning_tribe=winning_tribe, losing_tribe=losing_tribe))

        # wait for votes
        while (((_unixtime() - tribal_council_start_timestamp)
                < self._options.multi_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 non_immune_teams:
            log_message("Counting votes for non-immune team {}.".format(team))
            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))
Example #16
0
    def _run_single_tribe_council(self, winning_teams: List[Team],
                                  losing_teams: List[Team], gamedb: Database,
                                  engine: Engine):
        self._wait_for_tribal_council_start_time()
        # keep top K teams

        # 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))
        self._wait_for_tribal_council_end_time()

        # 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(
                    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))
            else:
                log_message(message="For some reason no one got voted out...",
                            game_id=self._game_id)
                log_message(message="Players = {}.".format(
                    pprint.pformat(gamedb.list_players(from_team=team))),
                            game_id=self._game_id)

        # notify all players of what happened at tribal council
        engine.add_event(
            events.NotifyTribalCouncilCompletionEvent(
                game_id=self._game_id, game_options=self._options))
Example #17
0
    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))
Example #18
0
            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

    def _merge_tribes(self, tribe1: Tribe, tribe2: Tribe, new_tribe_name: Text, gamedb: Database) -> Tribe:
        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)
        return new_tribe


if __name__ == '__main__':
    options = GameOptions()
    game = Game(game_id=str(uuid.uuid4), options=options)
    # TODO(brandon) for production each game should instantiate a separate AWS FIFO
    # on the fly.
    engine = Engine(options=options,
                    sqs_config_path=_AMAZON_SQS_PROD_CONF_JSON_PATH)
    database = FirestoreDB(json_config_path=_FIRESTORE_PROD_CONF_JSON_PATH)
    game.play(tribe1=database.tribe_from_id(_TRIBE_1_ID),
              tribe2=database.tribe_from_id(_TRIBE_2_ID),
              gamedb=database,
              engine=engine)
Example #19
0
from game_engine.engine import Engine
from game.scripts.game_settings import GameSettings


Engine.start_game(GameSettings)