Exemplo n.º 1
0
async def create_or_update_tournament(app, username, form, tournament=None):
    """Manual tournament creation from https://www.pychess.org/tournaments/new form input values"""

    variant = form["variant"]
    variant960 = variant.endswith("960")
    variant_name = variant[:-3] if variant960 else variant
    rated = form.get("rated", "") == "1" and form["position"] == ""
    base = float(form["clockTime"])
    inc = int(form["clockIncrement"])
    bp = int(form["byoyomiPeriod"])
    frequency = SHIELD if form["shield"] == "true" else ""

    if form["startDate"]:
        start_date = datetime.fromisoformat(form["startDate"].rstrip("Z")).replace(
            tzinfo=timezone.utc
        )
    else:
        start_date = None

    name = form["name"]
    # Create meningful tournament name in case we forget to change it :)
    if name == "":
        name = "%s Arena" % variant_display_name(variant).title()

    if frequency == SHIELD:
        name = "%s Shield Arena" % variant_display_name(variant).title()
    else:
        description = form["description"]

    data = {
        "name": name,
        "createdBy": username,
        "rated": rated,
        "variant": variant_name,
        "chess960": variant960,
        "base": base,
        "inc": inc,
        "bp": bp,
        "system": ARENA,
        "beforeStart": int(form["waitMinutes"]),
        "startDate": start_date,
        "frequency": frequency,
        "minutes": int(form["minutes"]),
        "fen": form["position"],
        "description": description,
    }
    if tournament is None:
        tournament = await new_tournament(app, data)
    else:
        # We want to update some data of the tournament created by new_tournament() before.
        # upsert=True will do this update at the end of upsert_tournament_to_db()
        await upsert_tournament_to_db(tournament, app)

    await broadcast_tournament_creation(app, tournament)
Exemplo n.º 2
0
async def load_tournament(app, tournament_id, tournament_klass=None):
    """Return Tournament object from app cache or from database"""
    db = app["db"]
    users = app["users"]
    tournaments = app["tournaments"]
    if tournament_id in tournaments:
        return tournaments[tournament_id]

    doc = await db.tournament.find_one({"_id": tournament_id})

    if doc is None:
        return None

    if doc["system"] == ARENA:
        tournament_class = ArenaTournament
    elif doc["system"] == SWISS:
        tournament_class = SwissTournament
    elif doc["system"] == RR:
        tournament_class = RRTournament
    elif tournament_klass is not None:
        tournament_class = tournament_klass

    if doc.get("fr") == SHIELD:
        doc["d"] = ("""
This Shield trophy is unique.
The winner keeps it for one month,
then must defend it during the next %s Shield tournament!
""" % variant_display_name(C2V[doc["v"]]).title())

    tournament = tournament_class(
        app,
        doc["_id"],
        C2V[doc["v"]],
        base=doc["b"],
        inc=doc["i"],
        byoyomi_period=int(bool(doc.get("bp"))),
        rated=doc.get("y"),
        chess960=bool(doc.get("z")),
        fen=doc.get("f"),
        rounds=doc["rounds"],
        created_by=doc["createdBy"],
        created_at=doc["createdAt"],
        before_start=doc.get("beforeStart", 0),
        minutes=doc["minutes"],
        starts_at=doc.get("startsAt"),
        name=doc["name"],
        description=doc.get("d", ""),
        frequency=doc.get("fr", ""),
        status=doc["status"],
    )

    tournaments[tournament_id] = tournament
    app["tourneysockets"][tournament_id] = {}
    app["tourneychat"][tournament_id] = collections.deque([], MAX_CHAT_LINES)

    tournament.winner = doc.get("winner", "")

    player_table = app["db"].tournament_player
    cursor = player_table.find({"tid": tournament_id})
    nb_players = 0

    if tournament.status == T_CREATED:
        try:
            cursor.sort("r", -1)
        except AttributeError:
            print("A unittest MagickMock cursor object")

    async for doc in cursor:
        uid = doc["uid"]
        if uid in users:
            user = users[uid]
        else:
            user = User(app,
                        username=uid,
                        title="TEST" if tournament_id == "12345678" else "")
            users[uid] = user

        withdrawn = doc.get("wd", False)

        tournament.players[user] = PlayerData(doc["r"], doc["pr"])
        tournament.players[user].id = doc["_id"]
        tournament.players[user].paused = doc["a"]
        tournament.players[user].withdrawn = withdrawn
        tournament.players[user].points = doc["p"]
        tournament.players[user].nb_games = doc["g"]
        tournament.players[user].nb_win = doc["w"]
        tournament.players[user].nb_berserk = doc.get("b", 0)
        tournament.players[user].performance = doc["e"]
        tournament.players[user].win_streak = doc["f"]

        if not withdrawn:
            tournament.leaderboard.update(
                {user: SCORE_SHIFT * (doc["s"]) + doc["e"]})
            nb_players += 1

    tournament.nb_players = nb_players

    tournament.print_leaderboard()

    pairing_table = app["db"].tournament_pairing
    cursor = pairing_table.find({"tid": tournament_id})
    try:
        cursor.sort("d", 1)
    except AttributeError:
        print("A unittest MagickMock cursor object")

    w_win, b_win, draw, berserk = 0, 0, 0, 0
    async for doc in cursor:
        res = doc["r"]
        result = C2R[res]
        # Skip aborted/unfinished games
        if result == "*":
            continue

        _id = doc["_id"]
        wp, bp = doc["u"]
        wrating = doc["wr"]
        brating = doc["br"]
        date = doc["d"]
        wberserk = doc.get("wb", False)
        bberserk = doc.get("bb", False)

        game_data = GameData(
            _id,
            users[wp],
            wrating,
            users[bp],
            brating,
            result,
            date,
            wberserk,
            bberserk,
        )

        tournament.players[users[wp]].games.append(game_data)
        tournament.players[users[bp]].games.append(game_data)

        if res == "a":
            w_win += 1
        elif res == "b":
            b_win += 1
        elif res == "c":
            draw += 1

        if wberserk:
            berserk += 1
        if bberserk:
            berserk += 1

        tournament.nb_games_finished += 1

    tournament.w_win = w_win
    tournament.b_win = b_win
    tournament.draw = draw
    tournament.nb_berserk = berserk

    return tournament
Exemplo n.º 3
0
async def create_or_update_tournament(app, username, form, tournament=None):
    variant = form["variant"]
    variant960 = variant.endswith("960")
    variant_name = variant[:-3] if variant960 else variant
    rated = form.get("rated", "") == "1" and form["position"] == ""
    base = float(form["clockTime"])
    inc = int(form["clockIncrement"])
    bp = int(form["byoyomiPeriod"])
    frequency = SHIELD if form["shield"] == "true" else ""

    if form["startDate"]:
        start_date = datetime.fromisoformat(form["startDate"].rstrip("Z")).replace(tzinfo=timezone.utc)
    else:
        start_date = None

    name = form["name"]
    # Create meningful tournament name in case we forget to change it :)
    if name in ADMINS:
        name = "%s %s Arena" % (variant_display_name(variant).title(), time_control_str(base, inc, bp))

    if frequency == SHIELD:
        name = "%s Shield Arena" % variant_display_name(variant).title()
        description = """
This Shield trophy is unique.
The winner keeps it for one month,
then must defend it during the next %s Shield tournament!
""" % variant_display_name(variant).title()
    else:
        description = form["description"]

    data = {
        "name": name,
        "createdBy": username,
        "rated": rated,
        "variant": variant_name,
        "chess960": variant960,
        "base": base,
        "inc": inc,
        "bp": bp,
        "system": ARENA,
        "beforeStart": int(form["waitMinutes"]),
        "startDate": start_date,
        "frequency": frequency,
        "minutes": int(form["minutes"]),
        "fen": form["position"],
        "description": description,
    }
    if tournament is None:
        tournament = await new_tournament(app, data)
    else:
        # We want to update some data of the tournament created by new_tournament() befor
        # upsert=True will do this update at the end of upsert_tournament_to_db()
        await upsert_tournament_to_db(tournament, app)

    await tournament.broadcast_spotlight()

    # Send msg to discord-relay BOT
    try:
        lobby_sockets = app["lobbysockets"]
        msg = tournament.discord_msg
        for dr_ws in lobby_sockets["Discord-Relay"]:
            await dr_ws.send_json({"type": "create_tournament", "message": msg})
            break
    except (KeyError, ConnectionResetError):
        # BOT disconnected
        log.error("--- Discord-Relay disconnected!")
Exemplo n.º 4
0
    def __init__(self,
                 app,
                 gameId,
                 variant,
                 initial_fen,
                 wplayer,
                 bplayer,
                 base=1,
                 inc=0,
                 byoyomi_period=0,
                 level=0,
                 rated=CASUAL,
                 chess960=False,
                 create=True,
                 tournamentId=None):
        self.app = app
        self.db = app["db"] if "db" in app else None
        self.users = app["users"]
        self.games = app["games"]
        self.highscore = app["highscore"]
        self.db_crosstable = app["crosstable"]

        self.saved = False
        self.remove_task = None

        self.variant = variant
        self.initial_fen = initial_fen
        self.wplayer = wplayer
        self.bplayer = bplayer
        self.rated = rated
        self.base = base
        self.inc = inc
        self.level = level if level is not None else 0
        self.tournamentId = tournamentId
        self.chess960 = chess960
        self.create = create
        self.imported_by = ""

        self.berserk_time = self.base * 1000 * 30

        self.browser_title = "%s • %s vs %s" % (
            variant_display_name(self.variant +
                                 ("960" if self.chess960 else "")).title(),
            self.wplayer.username, self.bplayer.username)

        # rating info
        self.white_rating = wplayer.get_rating(variant, chess960)
        self.wrating = "%s%s" % self.white_rating.rating_prov
        self.wrdiff = 0
        self.black_rating = bplayer.get_rating(variant, chess960)
        self.brating = "%s%s" % self.black_rating.rating_prov
        self.brdiff = 0

        # crosstable info
        self.need_crosstable_save = False
        self.bot_game = self.bplayer.bot or self.wplayer.bot
        if self.bot_game or self.wplayer.anon or self.bplayer.anon:
            self.crosstable = ""
        else:
            if self.wplayer.username < self.bplayer.username:
                self.s1player = self.wplayer.username
                self.s2player = self.bplayer.username
            else:
                self.s1player = self.bplayer.username
                self.s2player = self.wplayer.username
            self.ct_id = self.s1player + "/" + self.s2player
            self.crosstable = self.db_crosstable.get(self.ct_id, {
                "_id": self.ct_id,
                "s1": 0,
                "s2": 0,
                "r": []
            })

        self.spectators = set()
        self.draw_offers = set()
        self.rematch_offers = set()
        self.messages = collections.deque([], MAX_CHAT_LINES)
        self.date = datetime.now(timezone.utc)

        self.ply_clocks = [{
            "black": (base * 1000 * 60) + 0 if base > 0 else inc * 1000,
            "white": (base * 1000 * 60) + 0 if base > 0 else inc * 1000,
            "movetime":
            0
        }]
        self.dests = {}
        self.promotions = []
        self.lastmove = None
        self.check = False
        self.status = CREATED
        self.result = "*"
        self.last_server_clock = monotonic()

        self.id = gameId

        # Makruk manual counting
        use_manual_counting = self.variant in ("makruk", "makpong",
                                               "cambodian")
        self.manual_count = use_manual_counting and not self.bot_game
        self.manual_count_toggled = []

        # Calculate the start of manual counting
        count_started = 0
        if self.manual_count:
            count_started = -1
            if self.initial_fen:
                parts = self.initial_fen.split()
                board_state = parts[0]
                side_to_move = parts[1]
                counting_limit = int(
                    parts[3]) if len(parts) >= 4 and parts[3].isdigit() else 0
                counting_ply = int(parts[4]) if len(parts) >= 5 else 0
                move_number = int(parts[5]) if len(parts) >= 6 else 0

                white_pieces = sum(1 for c in board_state if c.isupper())
                black_pieces = sum(1 for c in board_state if c.islower())
                if counting_limit > 0 and counting_ply > 0:
                    if white_pieces <= 1 or black_pieces <= 1:
                        # Disable manual count if either side is already down to lone king
                        count_started = 0
                        self.manual_count = False
                    else:
                        last_ply = 2 * move_number - (2 if side_to_move == 'w'
                                                      else 1)
                        count_started = last_ply - counting_ply + 1
                        if count_started < 1:
                            # Move number is too small for the current count
                            count_started = 0
                            self.manual_count = False
                        else:
                            counting_player = self.bplayer if counting_ply % 2 == 0 else self.wplayer
                            self.draw_offers.add(counting_player.username)

        disabled_fen = ""
        if self.chess960 and self.initial_fen and self.create:
            if self.wplayer.fen960_as_white == self.initial_fen:
                disabled_fen = self.initial_fen
                self.initial_fen = ""

        self.board = FairyBoard(self.variant, self.initial_fen, self.chess960,
                                count_started, disabled_fen)

        # Janggi setup needed when player is not BOT
        if self.variant == "janggi":
            if self.initial_fen:
                self.bsetup = False
                self.wsetup = False
            else:
                self.bsetup = not self.bplayer.bot
                self.wsetup = not self.wplayer.bot
                if self.bplayer.bot:
                    self.board.janggi_setup("b")

        self.overtime = False
        self.byoyomi = byoyomi_period > 0
        self.byoyomi_period = byoyomi_period

        # Remaining byoyomi periods by players
        self.byoyomi_periods = {
            "white": byoyomi_period,
            "black": byoyomi_period
        }

        # On page refresh we have to add extra byoyomi times gained by current player to report correct clock time
        # We adjust this in "byoyomi" messages in wsr.py
        self.byo_correction = 0

        self.initial_fen = self.board.initial_fen
        self.wplayer.fen960_as_white = self.initial_fen

        self.random_mover = self.wplayer.username == "Random-Mover" or self.bplayer.username == "Random-Mover"
        self.random_move = ""

        self.set_dests()
        if self.board.move_stack:
            self.check = self.board.is_checked()

        self.steps = [{
            "fen":
            self.initial_fen if self.initial_fen else self.board.initial_fen,
            "san":
            None,
            "turnColor":
            "black" if self.board.color == BLACK else "white",
            "check":
            self.check
        }]

        self.stopwatch = Clock(self)

        if not self.bplayer.bot:
            self.bplayer.game_in_progress = self.id
        if not self.wplayer.bot:
            self.wplayer.game_in_progress = self.id

        self.wberserk = False
        self.bberserk = False

        self.move_lock = asyncio.Lock()
Exemplo n.º 5
0
def new_scheduled_tournaments(already_scheduled, now=None):
    """Create list for scheduled tournament data for one week from now on compared to what we already have"""

    # Cut off the latest element (_id) from all tournament data first
    # to let test if new plan data is already in already_scheduled or not.
    already_scheduled = [t[:5] for t in already_scheduled]

    if now is None:
        now = dt.datetime.now(dt.timezone.utc)
        # set time info to 0:0:0
        now = dt.datetime.combine(now, dt.time.min, tzinfo=dt.timezone.utc)

    to_date = dt.datetime.combine(
        now, dt.time.max,
        tzinfo=dt.timezone.utc) + dt.timedelta(days=SCHEDULE_MAX_DAYS)

    # 2 full month list of scheduled tournaments
    plans = Scheduler(now).schedule_plan() + Scheduler(
        go_month(now)).schedule_plan()

    new_tournaments_data = []

    for plan in plans:
        starts_at = dt.datetime(
            plan.date.year,
            plan.date.month,
            plan.date.day,
            hour=plan.hour,
            tzinfo=dt.timezone.utc,
        )

        if (starts_at >= now and starts_at <= to_date
                and (plan.freq, plan.variant, plan.is960, starts_at,
                     plan.duration) not in already_scheduled):

            variant_name = variant_display_name(
                plan.variant + ("960" if plan.is960 else "")).title()
            if plan.freq == SHIELD:
                name = "%s Shield Arena" % variant_name
            elif plan.freq == MONTHLY:
                if plan.variant in CATEGORIES["makruk"]:
                    name = "SEAturday %s Arena" % variant_name
                else:
                    name = "Monthly %s Arena" % variant_name
            elif plan.freq == WEEKLY:
                name = "Weekly %s Arena" % variant_name
            elif plan.freq == DAILY:
                name = "Daily %s Arena" % variant_name
            else:
                name = "%s Arena" % variant_name

            new_tournaments_data.append({
                "name": name,
                "createdBy": "PyChess",
                "frequency": plan.freq,
                "variant": plan.variant,
                "chess960": plan.is960,
                "base": plan.base,
                "inc": plan.inc,
                "bp": plan.byo,
                "system": ARENA,
                "startDate": starts_at,
                "minutes": plan.duration,
            })

    return new_tournaments_data