コード例 #1
0
    def get_all_records(
        self,
        game: str,
        version: Optional[int] = None,
        userlist: Optional[List[UserID]] = None,
        locationlist: Optional[List[int]] = None,
    ) -> List[Tuple[UserID, Score]]:
        # First, pass off to local-only if this was called with parameters we don't support
        if (version is None or userlist is not None
                or locationlist is not None):
            return self.music.get_all_records(game, version, userlist,
                                              locationlist)

        # Now, fetch all records remotely and locally
        localcards, localscores, remotescores = Parallel.execute([
            self.user.get_all_cards,
            lambda: self.music.get_all_records(game, version, userlist,
                                               locationlist),
            lambda: Parallel.flatten(
                Parallel.call(
                    [client.get_records for client in self.clients],
                    game,
                    version,
                    APIConstants.ID_TYPE_SERVER,
                    [],
                )),
        ])

        return self.__merge_global_scores(game, version, localcards,
                                          localscores, remotescores)
コード例 #2
0
 def test_empty(self) -> None:
     results = Parallel.execute([])
     self.assertEqual(results, [])
     results = Parallel.map(lambda x: x, [])
     self.assertEqual(results, [])
     results = Parallel.call([])
     self.assertEqual(results, [])
     results = Parallel.flatten([])
     self.assertEqual(results, [])
コード例 #3
0
 def test_basic(self) -> None:
     results = Parallel.execute([
         lambda: 1,
         lambda: 2,
         lambda: 3,
         lambda: 4,
         lambda: 5,
     ])
     self.assertEqual(results, [1, 2, 3, 4, 5])
コード例 #4
0
    def test_function(self) -> None:
        def fun(x: int) -> int:
            return -x

        results = Parallel.execute([
            lambda: fun(1),
            lambda: fun(2),
            lambda: fun(3),
            lambda: fun(4),
            lambda: fun(5),
        ])
        self.assertEqual(results, [-1, -2, -3, -4, -5])
コード例 #5
0
    def get_score(self, game: str, version: int, userid: UserID, songid: int,
                  songchart: int) -> Optional[Score]:
        # Helper function so we can iterate over all servers for a single card
        def get_scores_for_card(cardid: str) -> List[Score]:
            return Parallel.flatten(
                Parallel.call(
                    [client.get_records for client in self.clients],
                    game,
                    version,
                    APIConstants.ID_TYPE_INSTANCE,
                    [songid, songchart, cardid],
                ))

        relevant_cards = self.__get_cardids(userid)
        if RemoteUser.is_remote(userid):
            # No need to look up local score for this user
            scores = Parallel.flatten(
                Parallel.map(
                    get_scores_for_card,
                    relevant_cards,
                ))
            localscore = None
        else:
            localscore, scores = Parallel.execute([
                lambda: self.music.get_score(game, version, userid, songid,
                                             songchart),
                lambda: Parallel.flatten(
                    Parallel.map(
                        get_scores_for_card,
                        relevant_cards,
                    )),
            ])

        topscore = localscore

        for score in scores:
            if int(score['song']) != songid:
                continue
            if int(score['chart']) != songchart:
                continue

            newscore = self.__format_score(game, version, songid, songchart,
                                           score)

            if topscore is None:
                # No merging needed
                topscore = newscore
                continue

            topscore = self.__merge_score(game, version, topscore, newscore)

        return topscore
コード例 #6
0
ファイル: user.py プロジェクト: vangar/bemaniutils
    def get_all_profiles(self, game: str, version: int) -> List[Tuple[UserID, ValidatedDict]]:
        # Fetch local and remote profiles, and then merge by adding remote profiles to local
        # profiles when we don't have a profile for that user ID yet.
        local_cards, local_profiles, remote_profiles = Parallel.execute([
            self.user.get_all_cards,
            lambda: self.user.get_all_profiles(game, version),
            lambda: Parallel.flatten(Parallel.call(
                [client.get_profiles for client in self.clients],
                game,
                version,
                APIConstants.ID_TYPE_SERVER,
                [],
            )),
        ])

        card_to_id = {cardid: userid for (cardid, userid) in local_cards}
        id_to_profile = {userid: profile for (userid, profile) in local_profiles}

        for profile in remote_profiles:
            cardids = sorted([card.upper() for card in profile.get('cards', [])])
            if len(cardids) == 0:
                # We don't care about anonymous profiles
                continue

            local_cards = [cardid for cardid in cardids if cardid in card_to_id]
            if len(local_cards) > 0:
                # We have a local version of this profile!
                continue

            # Create a fake user with this profile
            del profile['cards']

            exact_match = profile.get('match', 'partial') == 'exact'
            if not exact_match:
                continue

            userid = RemoteUser.card_to_userid(cardids[0])
            refid = self.user.get_refid(game, version, userid)
            extid = self.user.get_extid(game, version, userid)

            # Add in our defaults we always provide
            profile['game'] = game
            profile['version'] = version
            profile['refid'] = refid
            profile['extid'] = extid

            id_to_profile[userid] = self.__format_profile(ValidatedDict(profile))

        return [(userid, id_to_profile[userid]) for userid in id_to_profile]
コード例 #7
0
    def get_all_scores(
        self,
        game: str,
        version: Optional[int] = None,
        userid: Optional[UserID] = None,
        songid: Optional[int] = None,
        songchart: Optional[int] = None,
        since: Optional[int] = None,
        until: Optional[int] = None,
    ) -> List[Tuple[UserID, Score]]:
        # First, pass off to local-only if this was called with parameters we don't support
        if (version is None or userid is not None or songid is None):
            return self.music.get_all_scores(game, version, userid, songid,
                                             songchart, since, until)

        # Now, figure out the request key based on passed in parameters
        if songchart is None:
            songkey = [songid]
        else:
            songkey = [songid, songchart]

        # Now, fetch all the scores remotely and locally
        localcards, localscores, remotescores = Parallel.execute([
            self.user.get_all_cards,
            lambda: self.music.get_all_scores(game, version, userid, songid,
                                              songchart, since, until),
            lambda: Parallel.flatten(
                Parallel.call(
                    [client.get_records for client in self.clients],
                    game,
                    version,
                    APIConstants.ID_TYPE_SONG,
                    songkey,
                    since,
                    until,
                )),
        ])

        return self.__merge_global_scores(game, version, localcards,
                                          localscores, remotescores)
コード例 #8
0
ファイル: base.py プロジェクト: taewonkim/bemaniutils
    def get_clear_rates(
        self,
        songid: Optional[int] = None,
        songchart: Optional[int] = None,
    ) -> Dict[int, Dict[int, Dict[str, int]]]:
        """
        Returns a dictionary similar to the following:

        {
            musicid: {
                chart: {
                    total: total plays,
                    clears: total clears,
                    fcs: total full combos,
                },
            },
        }
        """
        all_attempts, remote_attempts = Parallel.execute([
            lambda: self.data.local.music.get_all_attempts(
                game=self.game,
                version=self.music_version,
                songid=songid,
                songchart=songchart,
            ),
            lambda: self.data.remote.music.get_clear_rates(
                game=self.game,
                version=self.music_version,
                songid=songid,
                songchart=songchart,
            ),
        ])

        attempts: Dict[int, Dict[int, Dict[str, int]]] = {}
        for (_, attempt) in all_attempts:
            if attempt.data.get_int(
                    'clear_status') == self.CLEAR_STATUS_NO_PLAY:
                # This attempt was outside of the clear infra, so don't bother with it.
                continue

            # Terrible temporary structure is terrible.
            if attempt.id not in attempts:
                attempts[attempt.id] = {}
            if attempt.chart not in attempts[attempt.id]:
                attempts[attempt.id][attempt.chart] = {
                    'total': 0,
                    'clears': 0,
                    'fcs': 0,
                }

            # We saw an attempt, keep the total attempts in sync.
            attempts[attempt.id][attempt.chart]['total'] = attempts[
                attempt.id][attempt.chart]['total'] + 1

            if attempt.data.get_int(
                    'clear_status',
                    self.CLEAR_STATUS_FAILED) == self.CLEAR_STATUS_FAILED:
                # This attempt was a failure, so don't count it against clears of full combos
                continue

            # It was at least a clear
            attempts[attempt.id][attempt.chart]['clears'] = attempts[
                attempt.id][attempt.chart]['clears'] + 1

            if attempt.data.get_int(
                    'clear_status') == self.CLEAR_STATUS_FULL_COMBO:
                # This was a full combo clear, so it also counts here
                attempts[attempt.id][attempt.chart]['fcs'] = attempts[
                    attempt.id][attempt.chart]['fcs'] + 1

        # Merge in remote attempts
        for songid in remote_attempts:
            if songid not in attempts:
                attempts[songid] = {}

            for songchart in remote_attempts[songid]:
                if songchart not in attempts[songid]:
                    attempts[songid][songchart] = {
                        'total': 0,
                        'clears': 0,
                        'fcs': 0,
                    }

                attempts[songid][songchart]['total'] += remote_attempts[
                    songid][songchart]['plays']
                attempts[songid][songchart]['clears'] += remote_attempts[
                    songid][songchart]['clears']
                attempts[songid][songchart]['fcs'] += remote_attempts[songid][
                    songchart]['combos']

        # If requesting a specific song/chart, make sure its in the dict
        if songid is not None:
            if songid not in attempts:
                attempts[songid] = {}

            if songchart is not None:
                if songchart not in attempts[songid]:
                    attempts[songid][songchart] = {
                        'total': 0,
                        'clears': 0,
                        'fcs': 0,
                    }

        return attempts
コード例 #9
0
    def get_scores(
        self,
        game: str,
        version: int,
        userid: UserID,
        since: Optional[int] = None,
        until: Optional[int] = None,
    ) -> List[Score]:
        relevant_cards = self.__get_cardids(userid)
        if RemoteUser.is_remote(userid):
            # No need to look up local score for this user
            scores = Parallel.flatten(
                Parallel.call(
                    [client.get_records for client in self.clients],
                    game,
                    version,
                    APIConstants.ID_TYPE_CARD,
                    relevant_cards,
                    since,
                    until,
                ))
            localscores: List[Score] = []
        else:
            localscores, scores = Parallel.execute([
                lambda: self.music.get_scores(game, version, userid, since,
                                              until),
                lambda: Parallel.flatten(
                    Parallel.call(
                        [client.get_records for client in self.clients],
                        game,
                        version,
                        APIConstants.ID_TYPE_CARD,
                        relevant_cards,
                        since,
                        until,
                    )),
            ])

        allscores: Dict[int, Dict[int, Score]] = {}

        def add_score(score: Score) -> None:
            if score.id not in allscores:
                allscores[score.id] = {}
            allscores[score.id][score.chart] = score

        def get_score(songid: int, songchart: int) -> Optional[Score]:
            return allscores.get(songid, {}).get(songchart)

        # First, seed with local scores
        for score in localscores:
            add_score(score)

        # Second, merge in remote scorse
        for remotescore in scores:
            songid = int(remotescore['song'])
            chart = int(remotescore['chart'])
            newscore = self.__format_score(game, version, songid, chart,
                                           remotescore)
            oldscore = get_score(songid, chart)

            if oldscore is None:
                add_score(newscore)
            else:
                add_score(self.__merge_score(game, version, oldscore,
                                             newscore))

        # Finally, flatten and return
        finalscores: List[Score] = []
        for songid in allscores:
            for chart in allscores[songid]:
                finalscores.append(allscores[songid][chart])

        return finalscores
コード例 #10
0
ファイル: user.py プロジェクト: vangar/bemaniutils
    def get_any_profiles(self, game: str, version: int, userids: List[UserID]) -> List[Tuple[UserID, Optional[ValidatedDict]]]:
        if len(userids) == 0:
            return []

        remote_ids = [
            userid for userid in userids
            if RemoteUser.is_remote(userid)
        ]
        local_ids = [
            userid for userid in userids
            if not RemoteUser.is_remote(userid)
        ]

        if len(remote_ids) == 0:
            # We only have local profiles here, just pass on to the underlying layer
            return self.user.get_any_profiles(game, version, local_ids)
        else:
            # We have to fetch some local profiles and some remote profiles, and then
            # merge them together
            card_to_userid = {
                RemoteUser.userid_to_card(userid): userid
                for userid in remote_ids
            }

            local_profiles, remote_profiles = Parallel.execute([
                lambda: self.user.get_any_profiles(game, version, local_ids),
                lambda: Parallel.flatten(Parallel.call(
                    [client.get_profiles for client in self.clients],
                    game,
                    version,
                    APIConstants.ID_TYPE_CARD,
                    [RemoteUser.userid_to_card(userid) for userid in remote_ids],
                ))
            ])

            for profile in remote_profiles:
                cards = [card.upper() for card in profile.get('cards', [])]
                for card in cards:
                    # Map it back to the requested user
                    userid = card_to_userid.get(card)
                    if userid is None:
                        continue

                    # Sanitize the returned data
                    profile = copy.deepcopy(profile)
                    del profile['cards']

                    exact_match = profile.get('match', 'partial') == 'exact'

                    if 'match' in profile:
                        del profile['match']

                    refid = self.user.get_refid(game, version, userid)
                    extid = self.user.get_extid(game, version, userid)

                    # Add in our defaults we always provide
                    profile['game'] = game
                    profile['version'] = version if exact_match else 0
                    profile['refid'] = refid
                    profile['extid'] = extid

                    local_profiles.append(
                        (userid, self.__format_profile(ValidatedDict(profile))),
                    )

                    # Mark that we saw this card/user
                    del card_to_userid[card]

            # Finally, mark all missing remote profiles as None
            for card in card_to_userid:
                local_profiles.append((card_to_userid[card], None))

            return local_profiles
コード例 #11
0
    def get_clear_rates(self) -> Dict[int, Dict[int, Dict[str, int]]]:
        """
        Returns a dictionary similar to the following:

        {
            musicid: {
                chart: {
                    total: total plays,
                    clears: total clears,
                    average: average score,
                },
            },
        }
        """
        all_attempts, remote_attempts = Parallel.execute([
            lambda: self.data.local.music.get_all_attempts(
                game=self.game,
                version=self.version,
            ), lambda: self.data.remote.music.get_clear_rates(
                game=self.game,
                version=self.version,
            )
        ])
        attempts: Dict[int, Dict[int, Dict[str, int]]] = {}
        for (_, attempt) in all_attempts:
            # Terrible temporary structure is terrible.
            if attempt.id not in attempts:
                attempts[attempt.id] = {}
            if attempt.chart not in attempts[attempt.id]:
                attempts[attempt.id][attempt.chart] = {
                    'total': 0,
                    'clears': 0,
                    'average': 0,
                }

            # We saw an attempt, keep the total attempts in sync.
            attempts[attempt.id][attempt.chart]['average'] = int(
                ((attempts[attempt.id][attempt.chart]['average'] *
                  attempts[attempt.id][attempt.chart]['total']) +
                 attempt.points) /
                (attempts[attempt.id][attempt.chart]['total'] + 1))
            attempts[attempt.id][attempt.chart]['total'] += 1

            if attempt.data.get_int('clear_type', self.CLEAR_TYPE_NO_PLAY) in [
                    self.CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED
            ]:
                # This attempt was a failure, so don't count it against clears of full combos
                continue

            # It was at least a clear
            attempts[attempt.id][attempt.chart]['clears'] += 1

        # Merge in remote attempts
        for songid in remote_attempts:
            if songid not in attempts:
                attempts[songid] = {}

            for songchart in remote_attempts[songid]:
                if songchart not in attempts[songid]:
                    attempts[songid][songchart] = {
                        'total': 0,
                        'clears': 0,
                        'average': 0,
                    }

                attempts[songid][songchart]['total'] += remote_attempts[
                    songid][songchart]['plays']
                attempts[songid][songchart]['clears'] += remote_attempts[
                    songid][songchart]['clears']

        return attempts