예제 #1
0
    def knn_search(self, q=None, k=1):

        self.q = q
        self.visitedset = set()
        self.candidates = ValueSortedDict()
        self.result = ValueSortedDict()
        count = 0

        for i in range(self.m):
            v_ep = self.corpus[np.random.choice(list(self.corpus.keys()))]
            if self.dmat is None:
                cost = self.switch_metric(self.q.values, v_ep.values)
            else:
                cost = self.dmat[q.index][v_ep.index]
            count += 1
            self.candidates[v_ep.index] = cost
            self.visitedset.add(v_ep.index)
            tempres = ValueSortedDict()

            while True:

                # get element c closest from candidates to q, and remove c
                # from candidates
                if len(self.candidates) > 0:
                    c = self.get_closest()
                else:
                    break

                # check stop condition
                if len(self.result) >= k:
                    if self.check_stop_condition(c, k):
                        break
                tempres.update(c)

                # add neighbors of c if not in visitedset
                c = self.corpus[list(c.keys())[0]]

                for key in list(c.neighbors.keys()):
                    if key not in self.visitedset:
                        if self.dmat is None:
                            cost = self.switch_metric(self.q.values,
                                                      v_ep.values)
                        else:
                            cost = self.dmat[q.index][v_ep.index]
                        count += 1
                        self.visitedset.add(key)
                        self.candidates[key] = cost
                        tempres[key] = cost

            # add tempres to result
            self.result.update(tempres)
        # return k neighbors/result
        return self.result, count
예제 #2
0
class Tournament(ABC):
    """Abstract base class for Arena/Swisss/RR Tournament classes
    They have to implement create_pairing() for waiting_players"""

    system: ClassVar[int] = ARENA

    def __init__(
        self,
        app,
        tournamentId,
        variant="chess",
        chess960=False,
        rated=True,
        before_start=5,
        minutes=45,
        name="",
        description="",
        fen="",
        base=1,
        inc=0,
        byoyomi_period=0,
        rounds=0,
        created_by="",
        created_at=None,
        starts_at=None,
        status=None,
        with_clock=True,
        frequency="",
    ):
        self.app = app
        self.id = tournamentId
        self.name = name
        self.description = description
        self.variant = variant
        self.rated = rated
        self.before_start = before_start  # in minutes
        self.minutes = minutes  # in minutes
        self.fen = fen
        self.base = base
        self.inc = inc
        self.byoyomi_period = byoyomi_period
        self.chess960 = chess960
        self.rounds = rounds
        self.frequency = frequency

        self.created_by = created_by
        self.created_at = datetime.now(timezone.utc) if created_at is None else created_at
        if starts_at == "" or starts_at is None:
            self.starts_at = self.created_at + timedelta(seconds=int(before_start * 60))
        else:
            self.starts_at = starts_at

        # TODO: calculate wave from TC, variant, number of players
        self.wave = timedelta(seconds=3)
        self.wave_delta = timedelta(seconds=1)
        self.current_round = 0
        self.prev_pairing = None

        self.messages = collections.deque([], MAX_CHAT_LINES)
        self.spectators = set()
        self.players: dict[User, PlayerData] = {}
        self.leaderboard = ValueSortedDict(neg)
        self.leaderboard_keys_view = SortedKeysView(self.leaderboard)
        self.status = T_CREATED if status is None else status
        self.ongoing_games = 0
        self.nb_players = 0

        self.nb_games_finished = 0
        self.w_win = 0
        self.b_win = 0
        self.draw = 0
        self.nb_berserk = 0

        self.nb_games_cached = -1
        self.leaderboard_cache = {}

        self.first_pairing = False
        self.top_player = None
        self.top_game = None

        self.notify1 = False
        self.notify2 = False

        if minutes is None:
            self.ends_at = self.starts_at + timedelta(days=1)
        else:
            self.ends_at = self.starts_at + timedelta(minutes=minutes)

        if with_clock:
            self.clock_task = asyncio.create_task(self.clock())

    def __repr__(self):
        return " ".join((self.id, self.name, self.created_at.isoformat()))

    @abstractmethod
    def create_pairing(self, waiting_players):
        pass

    def user_status(self, user):
        if user in self.players:
            return (
                "paused"
                if self.players[user].paused
                else "withdrawn"
                if self.players[user].withdrawn
                else "joined"
            )
        else:
            return "spectator"

    def user_rating(self, user):
        if user in self.players:
            return self.players[user].rating
        else:
            return "%s%s" % user.get_rating(self.variant, self.chess960).rating_prov

    def players_json(self, page=None, user=None):
        if (page is None) and (user is not None) and (user in self.players):
            if self.players[user].page > 0:
                page = self.players[user].page
            else:
                div, mod = divmod(self.leaderboard.index(user) + 1, 10)
                page = div + (1 if mod > 0 else 0)
                if self.status == T_CREATED:
                    self.players[user].page = page
        if page is None:
            page = 1

        if self.nb_games_cached != self.nb_games_finished:
            # number of games changed (game ended)
            self.leaderboard_cache = {}
            self.nb_games_cached = self.nb_games_finished
        elif user is not None:
            if self.status == T_STARTED:
                # player status changed (JOIN/PAUSE)
                if page in self.leaderboard_cache:
                    del self.leaderboard_cache[page]
            elif self.status == T_CREATED:
                # number of players changed (JOIN/WITHDRAW)
                self.leaderboard_cache = {}

        if page in self.leaderboard_cache:
            return self.leaderboard_cache[page]

        def player_json(player, full_score):
            return {
                "paused": self.players[player].paused if self.status == T_STARTED else False,
                "title": player.title,
                "name": player.username,
                "rating": self.players[player].rating,
                "points": self.players[player].points,
                "fire": self.players[player].win_streak,
                "score": full_score,  # SCORE_SHIFT-ed + performance rating
                "perf": self.players[player].performance,
                "nbGames": self.players[player].nb_games,
                "nbWin": self.players[player].nb_win,
                "nbBerserk": self.players[player].nb_berserk,
            }

        start = (page - 1) * 10
        end = min(start + 10, self.nb_players)

        page_json = {
            "type": "get_players",
            "requestedBy": user.username if user is not None else "",
            "nbPlayers": self.nb_players,
            "nbGames": self.nb_games_finished,
            "page": page,
            "players": [
                player_json(player, full_score)
                for player, full_score in self.leaderboard.items()[start:end]
            ],
        }

        if self.status > T_STARTED:
            page_json["podium"] = [
                player_json(player, full_score)
                for player, full_score in self.leaderboard.items()[0:3]
            ]

        self.leaderboard_cache[page] = page_json
        return page_json

    # TODO: cache this
    def games_json(self, player_name):
        player = self.app["users"].get(player_name)
        return {
            "type": "get_games",
            "rank": self.leaderboard.index(player) + 1,
            "title": player.title,
            "name": player_name,
            "perf": self.players[player].performance,
            "nbGames": self.players[player].nb_games,
            "nbWin": self.players[player].nb_win,
            "nbBerserk": self.players[player].nb_berserk,
            "games": [game.game_json(player) for game in self.players[player].games],
        }

    @property
    def spectator_list(self):
        return spectators(self)

    @property
    def top_game_json(self):
        return {
            "type": "top_game",
            "gameId": self.top_game.id,
            "variant": self.top_game.variant,
            "fen": self.top_game.board.fen,
            "w": self.top_game.wplayer.username,
            "b": self.top_game.bplayer.username,
            "wr": self.leaderboard_keys_view.index(self.top_game.wplayer) + 1,
            "br": self.leaderboard_keys_view.index(self.top_game.bplayer) + 1,
            "chess960": self.top_game.chess960,
            "base": self.top_game.base,
            "inc": self.top_game.inc,
            "byoyomi": self.top_game.byoyomi_period,
        }

    def waiting_players(self):
        return [
            p
            for p in self.leaderboard
            if self.players[p].free
            and self.id in p.tournament_sockets
            and len(p.tournament_sockets[self.id]) > 0
            and not self.players[p].paused
            and not self.players[p].withdrawn
        ]

    async def clock(self):
        try:
            while self.status not in (T_ABORTED, T_FINISHED, T_ARCHIVED):
                now = datetime.now(timezone.utc)

                if self.status == T_CREATED:
                    remaining_time = self.starts_at - now
                    remaining_mins_to_start = int(
                        ((remaining_time.days * 3600 * 24) + remaining_time.seconds) / 60
                    )
                    if now >= self.starts_at:
                        if self.system != ARENA and len(self.players) < 3:
                            # Swiss and RR Tournaments need at least 3 players to start
                            await self.abort()
                            print("T_ABORTED: less than 3 player joined")
                            break

                        await self.start(now)
                        continue

                    elif (not self.notify2) and remaining_mins_to_start <= NOTIFY2_MINUTES:
                        self.notify1 = True
                        self.notify2 = True
                        await discord_message(
                            self.app,
                            "notify_tournament",
                            self.notify_discord_msg(remaining_mins_to_start),
                        )
                        continue

                    elif (not self.notify1) and remaining_mins_to_start <= NOTIFY1_MINUTES:
                        self.notify1 = True
                        await discord_message(
                            self.app,
                            "notify_tournament",
                            self.notify_discord_msg(remaining_mins_to_start),
                        )
                        continue

                elif (self.minutes is not None) and now >= self.ends_at:
                    await self.finish()
                    print("T_FINISHED: no more time left")
                    break

                elif self.status == T_STARTED:
                    if self.system == ARENA:
                        # In case of server restart
                        if self.prev_pairing is None:
                            self.prev_pairing = now - self.wave

                        if now >= self.prev_pairing + self.wave + random.uniform(
                            -self.wave_delta, self.wave_delta
                        ):
                            waiting_players = self.waiting_players()
                            nb_waiting_players = len(waiting_players)
                            if nb_waiting_players >= 2:
                                log.debug("Enough player (%s), do pairing", nb_waiting_players)
                                await self.create_new_pairings(waiting_players)
                                self.prev_pairing = now
                            else:
                                log.debug(
                                    "Too few player (%s) to make pairing",
                                    nb_waiting_players,
                                )
                        else:
                            log.debug("Waiting for new pairing wave...")

                    elif self.ongoing_games == 0:
                        if self.current_round < self.rounds:
                            self.current_round += 1
                            log.debug("Do %s. round pairing", self.current_round)
                            waiting_players = self.waiting_players()
                            await self.create_new_pairings(waiting_players)
                        else:
                            await self.finish()
                            log.debug("T_FINISHED: no more round left")
                            break
                    else:
                        print(
                            "%s has %s ongoing game(s)..."
                            % (
                                "RR" if self.system == RR else "Swiss",
                                self.ongoing_games,
                            )
                        )

                    log.debug("%s CLOCK %s", self.id, now.strftime("%H:%M:%S"))
                await asyncio.sleep(1)
        except Exception:
            log.exception("Exception in tournament clock()")

    async def start(self, now):
        self.status = T_STARTED

        self.first_pairing = True
        self.set_top_player()

        response = {
            "type": "tstatus",
            "tstatus": self.status,
            "secondsToFinish": (self.ends_at - now).total_seconds(),
        }
        await self.broadcast(response)

        # force first pairing wave in arena
        if self.system == ARENA:
            self.prev_pairing = now - self.wave

        if self.app["db"] is not None:
            print(
                await self.app["db"].tournament.find_one_and_update(
                    {"_id": self.id},
                    {"$set": {"status": self.status}},
                    return_document=ReturnDocument.AFTER,
                )
            )

    @property
    def summary(self):
        return {
            "type": "tstatus",
            "tstatus": self.status,
            "nbPlayers": self.nb_players,
            "nbGames": self.nb_games_finished,
            "wWin": self.w_win,
            "bWin": self.b_win,
            "draw": self.draw,
            "berserk": self.nb_berserk,
            "sumRating": sum(
                self.players[player].rating
                for player in self.players
                if not self.players[player].withdrawn
            ),
        }

    async def finalize(self, status):
        self.status = status

        if len(self.players) > 0:
            self.print_leaderboard()
            print("--- TOURNAMENT RESULT ---")
            for i in range(min(3, len(self.leaderboard))):
                player = self.leaderboard.peekitem(i)[0]
                print("--- #%s ---" % (i + 1), player.username)

        # remove latest games from players tournament if it was not finished in time
        for player in self.players:
            if len(self.players[player].games) == 0:
                continue
            latest = self.players[player].games[-1]
            if latest and latest.status in (CREATED, STARTED):
                self.players[player].games.pop()
                self.players[player].points.pop()
                self.players[player].nb_games -= 1

        # force to create new players json data
        self.nb_games_cached = -1

        await self.broadcast(self.summary)
        await self.save()

        await self.broadcast_spotlight()

    async def broadcast_spotlight(self):
        spotlights = tournament_spotlights(self.app["tournaments"])
        lobby_sockets = self.app["lobbysockets"]
        response = {"type": "spotlights", "items": spotlights}
        await lobby_broadcast(lobby_sockets, response)

    async def abort(self):
        await self.finalize(T_ABORTED)

    async def finish(self):
        await self.finalize(T_FINISHED)

    async def join(self, user):
        if user.anon:
            return

        if self.system == RR and len(self.players) > self.rounds + 1:
            raise EnoughPlayer

        if user not in self.players:
            # new player joined
            rating, provisional = user.get_rating(self.variant, self.chess960).rating_prov
            self.players[user] = PlayerData(rating, provisional)
        elif self.players[user].withdrawn:
            # withdrawn player joined again
            rating, provisional = user.get_rating(self.variant, self.chess960).rating_prov

        if user not in self.leaderboard:
            # new player joined or withdrawn player joined again
            if self.status == T_CREATED:
                self.leaderboard.setdefault(user, rating)
            else:
                self.leaderboard.setdefault(user, 0)
            self.nb_players += 1

        self.players[user].paused = False
        self.players[user].withdrawn = False

        response = self.players_json(user=user)
        await self.broadcast(response)

        if self.status == T_CREATED:
            await self.broadcast_spotlight()

        await self.db_update_player(user, self.players[user])

    async def withdraw(self, user):
        self.players[user].withdrawn = True

        self.leaderboard.pop(user)
        self.nb_players -= 1

        response = self.players_json(user=user)
        await self.broadcast(response)

        await self.broadcast_spotlight()

        await self.db_update_player(user, self.players[user])

    async def pause(self, user):
        self.players[user].paused = True

        # pause is different from withdraw and join because pause can be initiated from finished games page as well
        response = self.players_json(user=user)
        await self.broadcast(response)

        if (self.top_player is not None) and self.top_player.username == user.username:
            self.set_top_player()

        await self.db_update_player(user, self.players[user])

    def spactator_join(self, spectator):
        self.spectators.add(spectator)

    def spactator_leave(self, spectator):
        self.spectators.discard(spectator)

    async def create_new_pairings(self, waiting_players):
        pairing = self.create_pairing(waiting_players)

        if self.first_pairing:
            self.first_pairing = False
            # Before tournament starts leaderboard is ordered by ratings
            # After first pairing it will be sorted by score points and performance
            # so we have to make a clear (all 0) leaderboard here
            new_leaderboard = [(user, 0) for user in self.leaderboard]
            self.leaderboard = ValueSortedDict(neg, new_leaderboard)
            self.leaderboard_keys_view = SortedKeysView(self.leaderboard)

        games = await self.create_games(pairing)
        return (pairing, games)

    def set_top_player(self):
        idx = 0
        self.top_player = None
        while idx < self.nb_players:
            top_player = self.leaderboard.peekitem(idx)[0]
            if self.players[top_player].paused:
                idx += 1
                continue
            else:
                self.top_player = top_player
                break

    async def create_games(self, pairing):
        check_top_game = self.top_player is not None
        new_top_game = False

        games = []
        game_table = None if self.app["db"] is None else self.app["db"].game
        for wp, bp in pairing:
            game_id = await new_id(game_table)
            game = Game(
                self.app,
                game_id,
                self.variant,
                self.fen,
                wp,
                bp,
                base=self.base,
                inc=self.inc,
                byoyomi_period=self.byoyomi_period,
                rated=RATED if self.rated else CASUAL,
                tournamentId=self.id,
                chess960=self.chess960,
            )

            games.append(game)
            self.app["games"][game_id] = game
            await insert_game_to_db(game, self.app)

            # TODO: save new game to db
            if 0:  # self.app["db"] is not None:
                doc = {
                    "_id": game.id,
                    "tid": self.id,
                    "u": [game.wplayer.username, game.bplayer.username],
                    "r": "*",
                    "d": game.date,
                    "wr": game.wrating,
                    "br": game.brating,
                }
                await self.app["db"].tournament_pairing.insert_one(doc)

            self.players[wp].games.append(game)
            self.players[bp].games.append(game)

            self.players[wp].points.append("*")
            self.players[bp].points.append("*")

            self.ongoing_games += 1

            self.players[wp].free = False
            self.players[bp].free = False

            self.players[wp].nb_games += 1
            self.players[bp].nb_games += 1

            self.players[wp].prev_opp = game.bplayer.username
            self.players[bp].prev_opp = game.wplayer.username

            self.players[wp].color_balance += 1
            self.players[bp].color_balance -= 1

            self.players[wp].nb_not_paired = 0
            self.players[bp].nb_not_paired = 0

            response = {
                "type": "new_game",
                "gameId": game_id,
                "wplayer": wp.username,
                "bplayer": bp.username,
            }

            try:
                ws = next(iter(wp.tournament_sockets[self.id]))
                if ws is not None:
                    await ws.send_json(response)
            except Exception:
                self.pause(wp)
                log.debug("White player %s left the tournament", wp.username)

            try:
                ws = next(iter(bp.tournament_sockets[self.id]))
                if ws is not None:
                    await ws.send_json(response)
            except Exception:
                self.pause(bp)
                log.debug("Black player %s left the tournament", bp.username)

            if (
                check_top_game
                and (self.top_player is not None)
                and self.top_player.username in (game.wplayer.username, game.bplayer.username)
                and game.status != BYEGAME
            ):  # Bye game
                self.top_game = game
                check_top_game = False
                new_top_game = True

        if new_top_game:
            tgj = self.top_game_json
            await self.broadcast(tgj)

        return games

    def points_perfs(self, game: Game) -> Tuple[Point, Point, int, int]:
        wplayer = self.players[game.wplayer]
        bplayer = self.players[game.bplayer]

        wpoint = (0, SCORE)
        bpoint = (0, SCORE)
        wperf = game.black_rating.rating_prov[0]
        bperf = game.white_rating.rating_prov[0]

        if game.result == "1/2-1/2":
            if self.system == ARENA:
                if game.board.ply > 10:
                    wpoint = (2, SCORE) if wplayer.win_streak == 2 else (1, SCORE)
                    bpoint = (2, SCORE) if bplayer.win_streak == 2 else (1, SCORE)

                wplayer.win_streak = 0
                bplayer.win_streak = 0
            else:
                wpoint, bpoint = (1, SCORE), (1, SCORE)

        elif game.result == "1-0":
            wplayer.nb_win += 1
            if self.system == ARENA:
                if wplayer.win_streak == 2:
                    wpoint = (4, DOUBLE)
                else:
                    wplayer.win_streak += 1
                    wpoint = (2, STREAK if wplayer.win_streak == 2 else SCORE)

                bplayer.win_streak = 0
            else:
                wpoint = (2, SCORE)

            if game.wberserk and game.board.ply >= 13:
                wpoint = (wpoint[0] + 1, wpoint[1])

            wperf += 500
            bperf -= 500

        elif game.result == "0-1":
            bplayer.nb_win += 1
            if self.system == ARENA:
                if bplayer.win_streak == 2:
                    bpoint = (4, DOUBLE)
                else:
                    bplayer.win_streak += 1
                    bpoint = (2, STREAK if bplayer.win_streak == 2 else SCORE)

                wplayer.win_streak = 0
            else:
                bpoint = (2, SCORE)

            if game.bberserk and game.board.ply >= 14:
                bpoint = (bpoint[0] + 1, bpoint[1])

            wperf -= 500
            bperf += 500

        return (wpoint, bpoint, wperf, bperf)

    def points_perfs_janggi(self, game):
        wplayer = self.players[game.wplayer]
        bplayer = self.players[game.bplayer]

        wpoint = (0, SCORE)
        bpoint = (0, SCORE)
        wperf = game.black_rating.rating_prov[0]
        bperf = game.white_rating.rating_prov[0]

        if game.status == VARIANTEND:
            wplayer.win_streak = 0
            bplayer.win_streak = 0

            if game.result == "1-0":
                if self.system == ARENA:
                    wpoint = (4 * 2 if wplayer.win_streak == 2 else 4, SCORE)
                    bpoint = (2 * 2 if bplayer.win_streak == 2 else 2, SCORE)
                else:
                    wpoint = (4, SCORE)
                    bpoint = (2, SCORE)

            elif game.result == "0-1":
                if self.system == ARENA:
                    bpoint = (4 * 2 if bplayer.win_streak == 2 else 4, SCORE)
                    wpoint = (2 * 2 if wplayer.win_streak == 2 else 2, SCORE)
                else:
                    bpoint = (4, SCORE)
                    wpoint = (2, SCORE)

        elif game.result == "1-0":
            wplayer.nb_win += 1
            if self.system == ARENA:
                if wplayer.win_streak == 2:
                    wpoint = (7 * 2, DOUBLE)
                else:
                    wplayer.win_streak += 1
                    wpoint = (7, STREAK if wplayer.win_streak == 2 else SCORE)

                bplayer.win_streak = 0

                if game.wberserk and game.board.ply >= 13:
                    wpoint = (wpoint[0] + 3, wpoint[1])
            else:
                wpoint = (7, SCORE)
                bpoint = (0, SCORE)

            wperf += 500
            bperf -= 500

        elif game.result == "0-1":
            bplayer.nb_win += 1
            if self.system == ARENA:
                if bplayer.win_streak == 2:
                    bpoint = (7 * 2, DOUBLE)
                else:
                    bplayer.win_streak += 1
                    bpoint = (7, STREAK if bplayer.win_streak == 2 else SCORE)

                wplayer.win_streak = 0

                if game.bberserk and game.board.ply >= 14:
                    bpoint = (bpoint[0] + 3, bpoint[1])
            else:
                wpoint = (0, SCORE)
                bpoint = (7, SCORE)

            wperf -= 500
            bperf += 500

        return (wpoint, bpoint, wperf, bperf)

    async def game_update(self, game):
        """Called from Game.update_status()"""
        if self.status == T_FINISHED and self.status != T_ARCHIVED:
            return

        wplayer = self.players[game.wplayer]
        bplayer = self.players[game.bplayer]

        if game.wberserk:
            wplayer.nb_berserk += 1
            self.nb_berserk += 1

        if game.bberserk:
            bplayer.nb_berserk += 1
            self.nb_berserk += 1

        if game.variant == "janggi":
            wpoint, bpoint, wperf, bperf = self.points_perfs_janggi(game)
        else:
            wpoint, bpoint, wperf, bperf = self.points_perfs(game)

        wplayer.points[-1] = wpoint
        bplayer.points[-1] = bpoint
        if wpoint[1] == STREAK and len(wplayer.points) >= 2:
            wplayer.points[-2] = (wplayer.points[-2][0], STREAK)
        if bpoint[1] == STREAK and len(bplayer.points) >= 2:
            bplayer.points[-2] = (bplayer.points[-2][0], STREAK)

        wplayer.rating = game.white_rating.rating_prov[0] + (int(game.wrdiff) if game.wrdiff else 0)
        bplayer.rating = game.black_rating.rating_prov[0] + (int(game.brdiff) if game.brdiff else 0)

        # TODO: in Swiss we will need Berger instead of performance to calculate tie breaks
        nb = wplayer.nb_games
        wplayer.performance = int(round((wplayer.performance * (nb - 1) + wperf) / nb, 0))

        nb = bplayer.nb_games
        bplayer.performance = int(round((bplayer.performance * (nb - 1) + bperf) / nb, 0))

        wpscore = self.leaderboard.get(game.wplayer) // SCORE_SHIFT
        self.leaderboard.update(
            {game.wplayer: SCORE_SHIFT * (wpscore + wpoint[0]) + wplayer.performance}
        )

        bpscore = self.leaderboard.get(game.bplayer) // SCORE_SHIFT
        self.leaderboard.update(
            {game.bplayer: SCORE_SHIFT * (bpscore + bpoint[0]) + bplayer.performance}
        )

        self.nb_games_finished += 1

        if game.result == "1-0":
            self.w_win += 1
        elif game.result == "0-1":
            self.b_win += 1
        elif game.result == "1/2-1/2":
            self.draw += 1

        asyncio.create_task(self.delayed_free(game, wplayer, bplayer))

        # TODO: save player points to db
        # await self.db_update_player(wplayer, self.players[wplayer])
        # await self.db_update_player(bplayer, self.players[bplayer])

        self.set_top_player()

        await self.broadcast(
            {
                "type": "game_update",
                "wname": game.wplayer.username,
                "bname": game.bplayer.username,
            }
        )

        if self.top_game is not None and self.top_game.id == game.id:
            response = {
                "type": "gameEnd",
                "status": game.status,
                "result": game.result,
                "gameId": game.id,
            }
            await self.broadcast(response)

            if (self.top_player is not None) and self.top_player.username not in (
                game.wplayer.username,
                game.bplayer.username,
            ):
                top_game_candidate = self.players[self.top_player].games[-1]
                if top_game_candidate.status != BYEGAME:
                    self.top_game = top_game_candidate
                    if (self.top_game is not None) and (self.top_game.status <= STARTED):
                        tgj = self.top_game_json
                        await self.broadcast(tgj)

    async def delayed_free(self, game, wplayer, bplayer):
        if self.system == ARENA:
            await asyncio.sleep(3)

        wplayer.free = True
        bplayer.free = True

        if game.status == FLAG:
            # pause players when they don't start their game
            if game.board.ply == 0:
                wplayer.paused = True
            elif game.board.ply == 1:
                bplayer.paused = True

        self.ongoing_games -= 1

    async def broadcast(self, response):
        for spectator in self.spectators:
            try:
                for ws in spectator.tournament_sockets[self.id]:
                    try:
                        await ws.send_json(response)
                    except ConnectionResetError:
                        pass
            except KeyError:
                # spectator was removed
                pass
            except Exception:
                log.exception("Exception in tournament broadcast()")

    async def db_update_player(self, user, player_data):
        if self.app["db"] is None:
            return

        player_id = player_data.id
        player_table = self.app["db"].tournament_player

        if player_data.id is None:  # new player join
            player_id = await new_id(player_table)
            player_data.id = player_id

        if player_data.withdrawn:
            new_data = {
                "wd": True,
            }
        else:
            full_score = self.leaderboard[user]
            new_data = {
                "_id": player_id,
                "tid": self.id,
                "uid": user.username,
                "r": player_data.rating,
                "pr": player_data.provisional,
                "a": player_data.paused,
                "f": player_data.win_streak == 2,
                "s": int(full_score / SCORE_SHIFT),
                "g": player_data.nb_games,
                "w": player_data.nb_win,
                "b": player_data.nb_berserk,
                "e": player_data.performance,
                "p": player_data.points,
                "wd": False,
            }

        try:
            print(
                await player_table.find_one_and_update(
                    {"_id": player_id},
                    {"$set": new_data},
                    upsert=True,
                    return_document=ReturnDocument.AFTER,
                )
            )
        except Exception:
            if self.app["db"] is not None:
                log.error(
                    "db find_one_and_update tournament_player %s into %s failed !!!",
                    player_id,
                    self.id,
                )

        new_data = {"nbPlayers": self.nb_players, "nbBerserk": self.nb_berserk}
        print(
            await self.app["db"].tournament.find_one_and_update(
                {"_id": self.id},
                {"$set": new_data},
                return_document=ReturnDocument.AFTER,
            )
        )

    async def save(self):
        if self.app["db"] is None:
            return

        if self.nb_games_finished == 0:
            print(await self.app["db"].tournament.delete_many({"_id": self.id}))
            print("--- Deleted empty tournament %s" % self.id)
            return

        winner = self.leaderboard.peekitem(0)[0].username
        new_data = {
            "status": self.status,
            "nbPlayers": self.nb_players,
            "nbGames": self.nb_games_finished,
            "winner": winner,
        }

        print(
            await self.app["db"].tournament.find_one_and_update(
                {"_id": self.id},
                {"$set": new_data},
                return_document=ReturnDocument.AFTER,
            )
        )

        pairing_documents = []
        pairing_table = self.app["db"].tournament_pairing

        processed_games = set()

        for user, user_data in self.players.items():
            for game in user_data.games:
                if game.status == BYEGAME:  # ByeGame
                    continue
                if game.id not in processed_games:
                    pairing_documents.append(
                        {
                            "_id": game.id,
                            "tid": self.id,
                            "u": (game.wplayer.username, game.bplayer.username),
                            "r": R2C[game.result],
                            "d": game.date,
                            "wr": game.wrating,
                            "br": game.brating,
                            "wb": game.wberserk,
                            "bb": game.bberserk,
                        }
                    )
                processed_games.add(game.id)

        await pairing_table.insert_many(pairing_documents)

        for user in self.leaderboard:
            await self.db_update_player(user, self.players[user])

        if self.frequency == SHIELD:
            variant_name = self.variant + ("960" if self.chess960 else "")
            self.app["shield"][variant_name].append((winner, self.starts_at, self.id))
            self.app["shield_owners"][variant_name] = winner

    def print_leaderboard(self):
        print("--- LEADERBOARD ---", self.id)
        for player, full_score in self.leaderboard.items()[:10]:
            print(
                "%20s %4s %30s %2s %s"
                % (
                    player.username,
                    self.players[player].rating,
                    self.players[player].points,
                    full_score,
                    self.players[player].performance,
                )
            )

    @property
    def create_discord_msg(self):
        tc = time_control_str(self.base, self.inc, self.byoyomi_period)
        tail960 = "960" if self.chess960 else ""
        return "%s: **%s%s** %s tournament starts at UTC %s, duration will be **%s** minutes" % (
            self.created_by,
            self.variant,
            tail960,
            tc,
            self.starts_at.strftime("%Y.%m.%d %H:%M"),
            self.minutes,
        )

    def notify_discord_msg(self, minutes):
        tc = time_control_str(self.base, self.inc, self.byoyomi_period)
        tail960 = "960" if self.chess960 else ""
        url = "https://www.pychess.org/tournament/%s" % self.id
        if minutes >= 60:
            time = int(minutes / 60)
            time_text = "hours"
        else:
            time = minutes
            time_text = "minutes"
        return "**%s%s** %s tournament starts in **%s** %s! %s" % (
            self.variant,
            tail960,
            tc,
            time,
            time_text,
            url,
        )
예제 #3
0
class NSW:
    def __init__(self,
                 f: int = 1,
                 m: int = 1,
                 k: int = 1,
                 metric: object = "euclidean",
                 metric_params: dict = {},
                 random_seed: int = 1992) -> object:

        self.seed = random_seed
        self.f = f
        self.m = m
        self.k = k
        self.metric = metric
        self.metric_params = metric_params
        self.corpus = {}

    def get_params(self, deep=True):
        return {
            "f": self.f,
            "m": self.m,
            "k": self.k,
            "metric": self.metric,
            "metric_params": self.metric_params,
            "random_seed": self.seed
        }

    def set_params(self, **parameters):
        for parameter, value in parameters.items():
            setattr(self, parameter, value)
        return self

    def switch_metric(self, ts1=None, ts2=None):
        if self.metric == "euclidean":
            return np.linalg.norm(ts1 - ts2)
        elif self.metric == "dtw":
            return dtw(ts1, ts2, **self.metric_params)
        return None

    def nn_insert(self, index=int, values=[], label=None):
        # create node with the given values
        node = Node(index, values, label)

        # check if the corpus is empty
        if len(self.corpus) < 1:
            self.corpus[node.index] = node
            return self

        neighbors, _ = self.knn_search(node, self.f)

        for key, cost in list(neighbors.items())[:self.f]:
            # have the store the updated node back in the corpus
            neighbor = self.corpus[key]
            assert neighbor.index == key
            neighbor = neighbor.connect(node.index, cost, self.f)
            self.corpus[neighbor.index] = neighbor

            # connect new node to its neighbor
            node = node.connect(neighbor.index, cost, self.f)

        # storing new node in the corpus
        self.corpus[node.index] = node
        return self

    def batch_insert(self, indices=[]):

        for i in tqdm(list(range(self.X_train.shape[0]))):
            self.nn_insert(indices[i], self.X_train[i], self.y_train[i])

        return self

    def get_closest(self):
        k = next(iter(self.candidates))
        return {k: self.candidates.pop(k)}

    def check_stop_condition(self, c, k):
        # if c is further than the kth element in the result

        k_dist = self.result[list(self.result.keys())[k - 1]]
        c_dist = list(c.values())[0]

        return bool(c_dist > k_dist)

    def knn_search(self, q=None, k=1):

        self.q = q
        self.visitedset = set()
        self.candidates = ValueSortedDict()
        self.result = ValueSortedDict()
        count = 0

        for i in range(self.m):
            v_ep = self.corpus[np.random.choice(list(self.corpus.keys()))]
            if self.dmat is None:
                cost = self.switch_metric(self.q.values, v_ep.values)
            else:
                cost = self.dmat[q.index][v_ep.index]
            count += 1
            self.candidates[v_ep.index] = cost
            self.visitedset.add(v_ep.index)
            tempres = ValueSortedDict()

            while True:

                # get element c closest from candidates to q, and remove c
                # from candidates
                if len(self.candidates) > 0:
                    c = self.get_closest()
                else:
                    break

                # check stop condition
                if len(self.result) >= k:
                    if self.check_stop_condition(c, k):
                        break
                tempres.update(c)

                # add neighbors of c if not in visitedset
                c = self.corpus[list(c.keys())[0]]

                for key in list(c.neighbors.keys()):
                    if key not in self.visitedset:
                        if self.dmat is None:
                            cost = self.switch_metric(self.q.values,
                                                      v_ep.values)
                        else:
                            cost = self.dmat[q.index][v_ep.index]
                        count += 1
                        self.visitedset.add(key)
                        self.candidates[key] = cost
                        tempres[key] = cost

            # add tempres to result
            self.result.update(tempres)
        # return k neighbors/result
        return self.result, count

    def transform(self, s: int = 10) -> object:
        visited_set = set()
        for key, node in self.corpus.items():
            for nn, cost in node.neighbors.items():

                if key in visited_set and nn in visited_set:
                    continue

                neighbor = self.corpus[nn]
                snn = len(
                    set(list(node.neighbors.keys())[:s]).intersection(
                        set(list(neighbor.neighbors.keys())[:s])))
                simcos = snn / float(s)
                dist = math.acos(simcos)

                # simcorr = (self.X_train.shape[0]/(self.X_train.shape[0]-s))*((snn/s)*(s/self.X_train.shape[0]))
                # dist = simcorr

                self.corpus[key].neighbors[nn] = dist
                self.corpus[nn].neighbors[key] = dist
                visited_set.add(key)
                visited_set.add(nn)
        return self

    def fit(self,
            X_train,
            y_train,
            secondary_metric=False,
            s=10,
            dist_mat=None):
        np.random.seed(self.seed)

        self.X_train = X_train.astype("float32")
        self.y_train = y_train
        self.dmat = dist_mat

        indices = np.arange(len(X_train))
        self.batch_insert(indices)

        print("Model is fitted with the provided data.")
        if secondary_metric:
            return self.transform(s=s)
        return self

    def predict(self, X_test):
        self.X_test = X_test.astype("float32")

        y_hat = []

        for i in tqdm(range(len(self.X_test))):
            q_node = Node(0, self.X_test[i], None)

            neighbors, _ = self.knn_search(q_node, self.k)

            labels = [
                self.corpus[key].label
                for key in list(neighbors.keys())[:self.k]
            ]

            label = most_frequent(labels)

            y_hat.append(label)

        return y_hat

    def kneighbors(self,
                   X_test=None,
                   indices=[],
                   dist_mat=None,
                   return_prediction=False):
        self.X_test = X_test.astype("float32")
        self.dmat = dist_mat
        all_nns = []
        preds = []
        counts = []

        for i in tqdm(range(self.X_test.shape[0])):
            q_node = Node(indices[i], self.X_test[i], None)
            neighbors, count = self.knn_search(q_node, self.k)
            counts.append(count)
            neighbors = list(neighbors.keys())[:self.k]
            if return_prediction:
                preds.append(most_frequent(self.y_train[neighbors]))
            all_nns.append(neighbors)
        if return_prediction:
            return all_nns, preds, counts
        return all_nns
예제 #4
0
class HACModel(object):
    """

    Parameters
    ----------
    is_symmetric : bool, optional
        Defaults to False


    Attributes
    ----------

    _similarity : ValueSortedDict
    _models : dict

    """

    def __init__(self, is_symmetric=False):
        super(HACModel, self).__init__()
        self.is_symmetric = is_symmetric

    def __getitem__(self, cluster):
        return self._models[cluster]

    # models

    def compute_model(self, cluster, parent=None):
        """Compute model of cluster given current parent state

        Parameters
        ----------
        cluster : hashable
            Cluster identifier
        parent : HierarchicalAgglomerativeClustering, optional

        Returns
        -------
        model : anything
            Cluster model
        """
        raise NotImplementedError('Missing method compute_model')

    def compute_merged_model(self, clusters, parent=None):
        raise NotImplementedError('Missing method compute_merged_model')

    # 1 vs. 1 similarity

    def compute_similarity(self, cluster1, cluster2, parent=None):
        raise NotImplementedError('Missing method compute_similarity')

    # 1 vs. N similarity

    def compute_similarities(self, cluster, clusters, parent=None):
        raise NotImplementedError('')

    # N vs. N similarity

    def compute_similarity_matrix(self, parent=None):
        raise NotImplementedError('')

    def initialize(self, parent=None):

        # one model per cluster in current_state
        self._models = {}
        for cluster in parent.current_state.labels():
            self._models[cluster] = self.compute_model(cluster, parent=parent)

        # list of clusters
        clusters = list(self._models)

        try:
            self._similarity = self.compute_similarity_matrix(parent=parent)

        except NotImplementedError as e:

            n_clusters = len(clusters)

            self._similarity = ValueSortedDict()

            for i, j in combinations(clusters, 2):

                # compute similarity if (and only if) clusters are mergeable
                if not parent.constraint.mergeable([i, j], parent=parent):
                    self._similarity[i, j] = -np.inf
                    self._similarity[j, i] = -np.inf
                    continue

                similarity = self.compute_similarity(i, j, parent=parent)
                self._similarity[i, j] = similarity

                if not self.is_symmetric:
                    similarity = self.compute_similarity(j, i, parent=parent)

                self._similarity[j, i] = similarity

    # NOTE - for now this (get_candidates / block) combination assumes
    # that we merge clusters two-by-two...

    def get_candidates(self, parent=None):
        """
        Returns
        -------
        clusters : tuple
        similarity : float

        """
        return self._similarity.peekitem(index=-1)

    def block(self, clusters, parent=None):
        if len(clusters) > 2:
            raise NotImplementedError(
                'Constrained clustering merging 3+ clusters is not supported.'
            )
        i, j = clusters
        self._similarity[i, j] = -np.inf
        self._similarity[j, i] = -np.inf

    def update(self, merged_clusters, into, parent=None):

        # compute merged model
        self._models[into] = self.compute_merged_model(merged_clusters,
                                                       parent=parent)

        # remove old models and corresponding similarity
        removed_clusters = list(set(merged_clusters) - set([into]))
        for cluster in removed_clusters:
            del self._models[cluster]

        for i, j in product(removed_clusters, self._models):
            self._similarity.pop((i, j), default=None)
            self._similarity.pop((j, i), default=None)

        # compute new similarities
        # * all at once if model implements compute_similarities
        # * one by one otherwise

        remaining_clusters = list(set(self._models) - set([into]))

        if not remaining_clusters:
            return

        try:

            # all at once (when available)
            similarities = self.compute_similarities(
                into, remaining_clusters, parent=parent)

        except NotImplementedError as e:

            similarities = dict()

            for cluster in remaining_clusters:

                # compute similarity if (and only if) clusters are mergeable
                if not parent.constraint.mergeable([into, cluster], parent=parent):
                    continue

                similarity = self.compute_similarity(into, cluster, parent=parent)
                similarities[into, cluster] = similarity

                if not self.is_symmetric:
                    similarity = self.compute_similarity(cluster, into, parent=parent)
                similarities[cluster, into] = similarity

        self._similarity.update(similarities)