Exemple #1
0
    def _matchmaker_function(self,
                             sleep_seconds: int = 60,
                             is_test: bool = False):
        log_message("Starting matchmaker for region={}".format(self._region))
        while not self._stop.is_set():
            games = self._gamedb.find_matchmaker_games(region=self._region)
            if len(games) >= 1:
                for game_snap in games:
                    game_dict = game_snap.to_dict()
                    players_stream = game_snap.reference.collection(
                        "players").stream()
                    players_list = []
                    for player in players_stream:
                        players_list.append(player)

                    if self._region in STV_I18N_TABLE:
                        schedule = STV_I18N_TABLE[self._region]
                    else:
                        schedule = STV_I18N_TABLE['US']

                    try:
                        now_utc = datetime.datetime.utcnow().strftime(
                            '%Y-%m-%d')
                        if self._check_start_time(
                                schedule=schedule,
                                now_dt_with_tz=datetime.datetime.now(
                                ).astimezone(),
                                is_test=is_test) and now_utc != game_dict.get(
                                    "last_checked_date"
                                ):  # TODO: Do these checks in query

                            if game_dict["count_players"] >= self._min_players:
                                if self._game_options is None:
                                    self._game_options = GameOptions(
                                        game_schedule=schedule,
                                        game_wait_sleep_interval_sec=1
                                        if is_test else 30)
                                g = Game(game_id=game_dict["id"],
                                         options=self._game_options)
                                self._start_game(game=g,
                                                 game_snap=game_snap,
                                                 players=players_list,
                                                 game_dict=game_dict,
                                                 is_test=is_test)
                            else:
                                self._reschedule_or_cancel_game(
                                    game_snap=game_snap,
                                    game_dict=game_dict,
                                    players=players_list)
                    except Exception as e:
                        log_message(
                            f"Game {str(game_dict)} is corrupt: {str(e)} Cancelling."
                        )
                        self._cancel_game(
                            game_snap=game_snap,
                            players=players_list,
                            reason="an internal data corruption error")

            time.sleep(sleep_seconds)
        log_message("Stopped matchmaker for region={}".format(self._region))
Exemple #2
0
 def __init__(self, json_config_path: Text, game_options: GameOptions = None) -> None:
     with open(json_config_path, 'r') as f:
         config = json.loads(f.read())
         self._url = config['url']
         self._client = boto3.client(
             'sqs',
             aws_access_key_id=config['aws_access_key_id'],
             aws_secret_access_key=config['aws_secret_access_key'],
             region_name='us-east-2'
         )
     # store a reference to game options for timezone specific
     # event deserialization from the AWS queue.
     self._game_options = game_options if game_options else GameOptions(
         game_schedule=STV_I18N_TABLE['US']
     )
Exemple #3
0
    def test_generate_tribes(self):
        gamedb = MockDatabase()
        gamedb.find_user = mock.MagicMock(return_value=mock.MagicMock())
        gamedb._games[_TEST_GAME_ID] = Game()
        players = self.generate_players(gamedb=gamedb)
        options = GameOptions(target_team_size=_TEAM_SIZE)
        data = MatchMakerRoundRobin.generate_tribes(game_id=_TEST_GAME_ID,
                                                    players=players,
                                                    game_options=options,
                                                    gamedb=gamedb)

        players = data['players']
        teams = data['teams']
        tribes = data['tribes']

        self.assertEqual(len(players), 20)
        self.assertEqual(len(teams), 2)
        self.assertEqual(len(tribes), 2)
Exemple #4
0
 def __init__(self,
              json_config_path: str,
              game_id: str,
              game_options: GameOptions = None) -> None:
     self.game_id = game_id
     with open(json_config_path, 'r') as f:
         name = f'VIRUS-{game_id}-{str(uuid.uuid4())}.fifo'
         config = json.loads(f.read())
         self._client = boto3.client(
             'sqs',
             aws_access_key_id=config['aws_access_key_id'],
             aws_secret_access_key=config['aws_secret_access_key'],
             region_name='us-east-2')
         self._client.create_queue(QueueName=name,
                                   Attributes={'FifoQueue': 'true'})
         # After you create a queue, you must wait at least one second after the queue
         # is created to be able to use the queue.
         time.sleep(_AWS_SQS_INIT_TIME_SEC)
         self._url = self._client.get_queue_url(QueueName=name)['QueueUrl']
     # store a reference to game options for timezone specific
     # event deserialization from the AWS queue.
     self._game_options = game_options if game_options else GameOptions(
         game_schedule=STV_I18N_TABLE['US'])
    def test_integration(self):
        test_id = str(uuid.uuid4())
        # Inject a game ID (test uuid) into the gamedb. use a globally unique country code to ensure
        # that only this game gets scheduled.
        game_id = FirestoreDB.add_game(
            json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
            hashtag=_TEST_GAME_HASHTAG,
            country_code=f'US-{test_id}')
        gamedb = FirestoreDB(
            json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
            game_id=game_id)
        log_message(f'Running integration test for game {game_id}')

        # create an integration test subsystem log stream that specifically captures
        # game inputs and outputs at the SMS boundary.
        test_log_stream = GameIntegrationTestLogStream(game_id=game_id,
                                                       test_id=test_id)

        # add test challenges
        for challenge in _TEST_CHALLENGES:
            gamedb.add_challenge(challenge=challenge)

        # if non-existent, create users associated with players. NOTE: critical for FE code
        # to create users when players sign up.
        for player_info in [*_EMULATED_PLAYERS, *_REAL_PLAYERS]:
            FirestoreDB.add_user(
                json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
                name=player_info[0],
                tiktok=player_info[1],
                phone_number=player_info[2])

        # Inject players into gamedb. set the phone number of the players to a known, or emulated device.
        emulated_players = []
        for player_info in _EMULATED_PLAYERS:
            player = gamedb.player(name=player_info[0],
                                   tiktok=player_info[1],
                                   phone_number=player_info[2])
            emulated_players.append(
                EmulatedPlayer(id=player.id,
                               name=player.name,
                               tiktok=player.tiktok,
                               phone_number=player.phone_number,
                               test_stream=test_log_stream,
                               gamedb=gamedb))
        for player_info in _REAL_PLAYERS:
            player = gamedb.player(name=player_info[0],
                                   tiktok=player_info[1],
                                   phone_number=player_info[2])

        # mock the scheduling method in MM service so that we can force schedule
        # after initialization.
        save_check_start_time_fn = MatchmakerService._check_start_time
        check_start_time_fn = mock.MagicMock()
        check_start_time_fn.return_value = False
        MatchmakerService._check_start_time = check_start_time_fn

        # create device emulator for Twilio SMS events. patch this
        # into the game engine class for the test.
        notification_emulator = FakeTwilioSMSNotifier(
            json_config_path=_TEST_TWILIO_SMS_CONFIG_PATH,
            game_id=game_id,
            emulated_devices=emulated_players)
        build_notifier_fn = mock.MagicMock()
        build_notifier_fn.return_value = notification_emulator
        Engine._get_sms_notifier = build_notifier_fn

        # Instantiate a local matchmaker service. make sure all services have the right configs,
        # e.g. twilio should be using the prod service phone number so that it can actually send messages.
        # set the clock mode on (2) to async so that game events happen immediately during testing.
        service = MatchmakerService(
            matchmaker=MatchMakerRoundRobin(),
            region=f'US-{test_id}',
            gamedb=gamedb,
            game_options=GameOptions(
                game_clock_mode=GameClockMode.ASYNC,
                game_wait_sleep_interval_sec=_TEST_SLEEP_INTERVAL,
                multi_tribe_min_tribe_size=2,
                engine_worker_thread_count=5,
                tribe_council_time_sec=_TEST_SLEEP_INTERVAL))
        # mock the MM Twilio client
        service._get_sms_notifier = mock.MagicMock(
            return_value=notification_emulator)
        try:
            service.start_matchmaker_daemon(sleep_seconds=1)
            # force schedule the game in MM (1).
            check_start_time_fn.return_value = True
            MatchmakerService._check_start_time = check_start_time_fn
            while gamedb.game_from_id(game_id).count_players > 1:
                time.sleep(_TEST_SLEEP_INTERVAL)
        finally:
            MatchmakerService._check_start_time = save_check_start_time_fn
            service.set_stop()
            test_dict = test_log_stream.to_dict()
            if _SAVE_TEST_LOGS:
                persisted_test_logs = test_log_stream.persist()
                log_message(persisted_test_logs)
                with open(f'game_logs/stream.{test_id}.json', 'w+') as f:
                    f.write(persisted_test_logs)
            active_players = list()
            for player in gamedb.stream_players(
                    active_player_predicate_value=True):
                active_players.append(player)
            winner = active_players[0]
            found_winner_message = False
            for output in test_dict['outputs']:
                if 'You are the last survivor and WINNER' in output[
                        'message'] and output['name'] == winner.name:
                    found_winner_message = True
            self.assertTrue(found_winner_message)
Exemple #6
0
 def setUp(self):
     self._game_options = GameOptions()
     self._game_options.game_schedule = STV_I18N_TABLE['US']
Exemple #7
0
    player_id='player/foo',
    tribe_id='tribe/bar',
    challenge_id='challenge/foo',
    team_id='team/bar',
    url='https://www.tiktok.com/@ifrc/video/6816427450801736965')
_TEST_TEAM1 = Team(id='id/foo1',
                   name='team/bar',
                   count_players=5,
                   tribe_id='tribe/foo')
_TEST_TEAM2 = Team(id='id/foo2',
                   name='team/bar',
                   count_players=5,
                   tribe_id='tribe/foo')
_TEST_TRIBE1 = Tribe(id='id/foo1', name='SIDAMA', count_players=1e6)
_TEST_TRIBE2 = Tribe(id='id/foo2', name='TIGRAWAY', count_players=500e3)
_TEST_GAME_OPTIONS = GameOptions(game_schedule=STV_I18N_TABLE['US'])

_gamedb = FirestoreDB(json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
                      game_id=_TEST_GAME_ID)

_TEST_EVENTS = [
    events.NotifyTribalChallengeEvent(game_id=_TEST_GAME_ID,
                                      game_options=_TEST_GAME_OPTIONS,
                                      challenge=_TEST_CHALLENGE),
    events.NotifyPlayerScoreEvent(game_id=_TEST_GAME_ID,
                                  game_options=_TEST_GAME_OPTIONS,
                                  player=_TEST_PLAYER1,
                                  challenge=_TEST_CHALLENGE,
                                  entry=_TEST_ENTRY,
                                  points=100),
    events.NotifyTeamReassignmentEvent(game_id=_TEST_GAME_ID,
 def setUp(self):
     self.game_options = GameOptions()
     self.game_options.game_schedule = STV_I18N_TABLE['US']
     self.twilio = TwilioSMSNotifier(
         json_config_path=_TEST_TWILIO_SMS_CONFIG_PATH)
Exemple #9
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)
Exemple #10
0
 def __init__(self):
     options = GameOptions()
     self._game = Game(game_id=str(uuid.uuid4), options=options)
     self._frontend = MockFrontend()
     self._sms_endpoint = MockSMSEndpoint()
Exemple #11
0
class MatchMakerRoundRobinTest(unittest.TestCase):
    @staticmethod
    def generate_players(gamedb: MockDatabase = None):
        players = [_TEST_PLAYER1]
        for x in range(2, 21):
            player = copy.deepcopy(_TEST_PLAYER1)
            player.id = str(uuid.uuid4())
            players.append(player)
        if gamedb:
            for player in players:
                gamedb._players[player.id] = player
        return players

    @mock.patch.object(FirestoreDB, 'find_user', return_value=mock.MagicMock())
    def test_generate_teams(self, _):
        players = self.generate_players()
        teams = MatchMakerRoundRobin.generate_teams(game_id=_TEST_GAME_ID,
                                                    players=players,
                                                    team_size=_TEAM_SIZE)
        self.assertEqual(len(teams), 2)

    def test_generate_teams_insufficient_players(self):
        players = [_TEST_PLAYER1]
        with self.assertRaises(Exception) as context:
            MatchMakerRoundRobin.generate_teams(game_id=_TEST_GAME_ID,
                                                players=players,
                                                team_size=_TEAM_SIZE)
        self.assertTrue(context.exception)

    def test_generate_tribes(self):
        gamedb = MockDatabase()
        gamedb.find_user = mock.MagicMock(return_value=mock.MagicMock())
        gamedb._games[_TEST_GAME_ID] = Game()
        players = self.generate_players(gamedb=gamedb)
        options = GameOptions(target_team_size=_TEAM_SIZE)
        data = MatchMakerRoundRobin.generate_tribes(game_id=_TEST_GAME_ID,
                                                    players=players,
                                                    game_options=options,
                                                    gamedb=gamedb)

        players = data['players']
        teams = data['teams']
        tribes = data['tribes']

        self.assertEqual(len(players), 20)
        self.assertEqual(len(teams), 2)
        self.assertEqual(len(tribes), 2)

    @parameterized.expand([
        (MatchMakerRoundRobin, 10, GameOptions(target_team_size=5), 2, 2, 10,
         1, 5, 5),
        (MatchMakerRoundRobin, 20, GameOptions(target_team_size=5), 2, 4, 20,
         2, 10, 5),
        (MatchMakerRoundRobin, 100, GameOptions(target_team_size=10), 2, 10,
         100, 5, 50, 10),
    ])
    def test_generate_tribes_count_initialization(
            self, algorithm_type, number_of_joined_players, game_options,
            expected_game_count_tribes, expected_game_count_teams,
            expected_game_count_players, expected_tribe_count_teams,
            expected_tribe_count_players, expected_team_count_players):
        # create a game
        game_id = FirestoreDB.add_game(
            json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
            hashtag='hashtag/foo')
        _gamedb._game_id = game_id

        # generate mock players with an id attribute and add the players to the game
        players = list()
        for i in range(0, number_of_joined_players):
            name = 'name/foo'
            tiktok = 'tiktok/bar'
            phone_number = f'+1000000000{i}'
            player = _gamedb.player(name=name,
                                    tiktok=tiktok,
                                    phone_number=phone_number)
            FirestoreDB.add_user(
                json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH,
                name=name,
                tiktok=tiktok,
                phone_number=phone_number)
            players.append(player)

        # read counts for game, all tribes, all teams and verify that they are correct
        game_info_dict = algorithm_type.generate_tribes(
            game_id=game_id,
            players=players,
            game_options=game_options,
            gamedb=_gamedb)

        game = _gamedb.game_from_id(game_id)
        self.assertEqual(game.count_tribes, expected_game_count_tribes)
        self.assertEqual(game.count_teams, expected_game_count_teams)
        self.assertEqual(game.count_players, expected_game_count_players)

        for tribe in game_info_dict['tribes']:
            tribe_ref = _gamedb.tribe_from_id(tribe.id)
            self.assertEqual(tribe_ref.count_teams, expected_tribe_count_teams)
            self.assertEqual(tribe_ref.count_players,
                             expected_tribe_count_players)
        for team in game_info_dict['teams']:
            self.assertEqual(
                _gamedb.team_from_id(team.id).count_players,
                expected_team_count_players)