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)
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
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!")
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()
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