async def analysis_move(app, user, game, move, fen, ply): invalid_move = False board = FairyBoard(game.variant, fen, game.chess960) try: # san = board.get_san(move) lastmove = move board.push(move) dests, promotions = get_dests(board) check = board.is_checked() except Exception: invalid_move = True log.exception("!!! analysis_move() exception occured") if invalid_move: analysis_board_response = game.get_board(full=True) else: analysis_board_response = { "type": "analysis_board", "gameId": game.id, "fen": board.fen, "ply": ply, "lastMove": lastmove, "dests": dests, "promo": promotions, "check": check, } ws = user.game_sockets[game.id] await ws.send_json(analysis_board_response)
def test_fen_default(self): for variant in VARIANTS: chess960 = variant.endswith("960") variant_name = variant[:-3] if chess960 else variant board = FairyBoard(variant_name, chess960=chess960) fen = board.initial_fen valid, sanitized = sanitize_fen(variant_name, fen, chess960) self.assertTrue(valid)
def test_encode_decode(self): for idx, variant in enumerate(VARIANTS): print(idx, variant) variant = variant.rstrip("960") FEN = sf.start_fen(variant) # fill the pockets with possible pieces for empty_pocket in ("[]", "[-]"): if empty_pocket in FEN: pocket = "".join([ i for i in set(FEN.split()[0]) if i in string.ascii_letters and i not in "Kk" ]) parts = FEN.split(empty_pocket) FEN = "%s[%s]%s" % (parts[0], pocket, parts[1]) board = FairyBoard(variant, initial_fen=FEN) moves = board.legal_moves() saved_restored = decode_moves(encode_moves(moves, variant), variant) self.assertEqual(saved_restored, moves)
async def index(request): """ Create home html. """ users = request.app["users"] games = request.app["games"] db = request.app["db"] # Who made the request? session = await aiohttp_session.get_session(request) session_user = session.get("user_name") session["last_visit"] = datetime.now().isoformat() session["guest"] = True if session_user is not None: log.info("+++ Existing user %s connected.", session_user) doc = None try: doc = await db.user.find_one({"_id": session_user}) except Exception: log.error("Failed to get user %s from mongodb!", session_user) if doc is not None: session["guest"] = False if not doc.get("enabled", True): log.info("Closed account %s tried to connect.", session_user) session.invalidate() return web.HTTPFound("/") if session_user in users: user = users[session_user] else: if session_user.startswith("Anon-"): session.invalidate() return web.HTTPFound(request.rel_url) log.debug("New lichess user %s joined.", session_user) title = session["title"] if "title" in session else "" perfs = {variant: DEFAULT_PERF for variant in VARIANTS} user = User(request.app, username=session_user, anon=session["guest"], title=title, perfs=perfs) users[user.username] = user else: user = User(request.app, anon=True) log.info("+++ New guest user %s connected.", user.username) users[user.username] = user session["user_name"] = user.username lang = session.get("lang", "en") get_template = request.app["jinja"][lang].get_template view = "lobby" gameId = request.match_info.get("gameId") ply = request.rel_url.query.get("ply") tournamentId = request.match_info.get("tournamentId") if request.path == "/about": view = "about" elif request.path == "/faq": view = "faq" elif request.path == "/stats": view = "stats" elif request.path.startswith("/news"): view = "news" elif request.path.startswith("/variants"): view = "variants" elif request.path == "/players": view = "players" elif request.path == "/allplayers": view = "allplayers" elif request.path == "/games": view = "games" elif request.path == "/patron": view = "patron" elif request.path == "/patron/thanks": view = "thanks" elif request.path == "/level8win": view = "level8win" elif request.path == "/tv": view = "tv" gameId = await tv_game(db, request.app) elif request.path.startswith("/editor"): view = "editor" elif request.path.startswith("/analysis"): view = "analysis" elif request.path.startswith("/embed"): view = "embed" elif request.path == "/paste": view = "paste" elif request.path.endswith("/shields"): view = "shields" elif request.path.endswith("/winners"): view = "winners" elif request.path.startswith("/tournaments"): view = "tournaments" if user.username in ADMINS: if request.path.endswith("/new"): view = "arena-new" elif request.path.endswith("/edit"): view = "arena-new" tournament = await load_tournament(request.app, tournamentId) if tournament is None or tournament.status != T_CREATED: view = "tournaments" elif request.path.endswith("/arena"): data = await request.post() await create_or_update_tournament(request.app, user.username, data) elif request.path.startswith("/tournament"): view = "tournament" tournament = await load_tournament(request.app, tournamentId) if tournament is None: return web.HTTPFound("/") if user.username in ADMINS and tournament.status == T_CREATED: if request.path.endswith("/edit"): data = await request.post() await create_or_update_tournament(request.app, user.username, data, tournament=tournament) elif request.path.endswith("/cancel"): await tournament.abort() return web.HTTPFound("/tournaments") if request.path.endswith("/pause") and user in tournament.players: await tournament.pause(user) profileId = request.match_info.get("profileId") if profileId is not None and profileId not in users: await asyncio.sleep(3) raise web.HTTPNotFound() variant = request.match_info.get("variant") if (variant is not None) and ((variant not in VARIANTS) and variant != "terminology"): log.debug("Invalid variant %s in request", variant) raise web.HTTPNotFound() fen = request.rel_url.query.get("fen") rated = None if (fen is not None) and "//" in fen: log.debug("Invelid FEN %s in request", fen) raise web.HTTPNotFound() if profileId is not None: view = "profile" if request.path[-3:] == "/tv": view = "tv" # TODO: tv for variants gameId = await tv_game_user(db, users, profileId) elif request.path[-7:] == "/import": rated = IMPORTED elif request.path[-6:] == "/rated": rated = RATED elif "/challenge" in request.path: view = "lobby" if user.anon: return web.HTTPFound("/") # Do we have gameId in request url? if (gameId is not None) and gameId != "variants": if view not in ("tv", "analysis", "embed"): view = "round" invites = request.app["invites"] if (gameId not in games) and (gameId in invites): seek_id = invites[gameId].id seek = request.app["seeks"][seek_id] if request.path.startswith("/invite/accept/"): player = request.match_info.get("player") seek_status = await join_seek(request.app, user, seek_id, gameId, join_as=player) if seek_status["type"] == "seek_joined": view = "invite" inviter = "wait" elif seek_status["type"] == "seek_occupied": view = "invite" inviter = "occupied" elif seek_status["type"] == "seek_yourself": view = "invite" inviter = "yourself" elif seek_status["type"] == "new_game": try: # Put response data to sse subscribers queue channels = request.app["invite_channels"] for queue in channels: await queue.put(json.dumps({"gameId": gameId})) # return games[game_id] except ConnectionResetError: pass else: view = "invite" inviter = seek.creator.username if user.username != seek.creator.username else "" if view != "invite": game = await load_game(request.app, gameId) if game is None: raise web.HTTPNotFound() if (ply is not None) and (view != "embed"): view = "analysis" if user.username != game.wplayer.username and user.username != game.bplayer.username: game.spectators.add(user) if game.tournamentId is not None: tournament_name = await get_tournament_name( request.app, game.tournamentId) if view in ("profile", "level8win"): if (profileId in users) and not users[profileId].enabled: template = get_template("closed.html") else: template = get_template("profile.html") elif view == "players": template = get_template("players.html") elif view == "shields": template = get_template("shields.html") elif view == "winners": template = get_template("winners.html") elif view == "allplayers": template = get_template("allplayers.html") elif view == "tournaments": template = get_template("tournaments.html") elif view == "arena-new": template = get_template("arena-new.html") elif view == "news": template = get_template("news.html") elif view == "variants": template = get_template("variants.html") elif view == "patron": template = get_template("patron.html") elif view == "faq": template = get_template("FAQ.html") elif view == "analysis": template = get_template("analysis.html") elif view == "embed": template = get_template("embed.html") else: template = get_template("index.html") render = { "js": "/static/pychess-variants.js%s%s" % (BR_EXTENSION, SOURCE_VERSION), "dev": DEV, "app_name": "PyChess", "languages": LANGUAGES, "lang": lang, "title": view.capitalize(), "view": view, "asseturl": STATIC_ROOT, "view_css": ("round" if view == "tv" else view) + ".css", "home": URI, "user": user.username if session["guest"] else "", "anon": user.anon, "username": user.username, "guest": session["guest"], "profile": profileId if profileId is not None else "", "variant": variant if variant is not None else "", "fen": fen.replace(".", "+").replace("_", " ") if fen is not None else "", "variants": VARIANTS, "variant_display_name": variant_display_name, "tournamentdirector": user.username in TOURNAMENT_DIRECTORS, } if view in ("profile", "level8win"): if view == "level8win": profileId = "Fairy-Stockfish" render["trophies"] = [] else: hs = request.app["highscore"] render["trophies"] = [(v, "top10") for v in hs if profileId in hs[v].keys()[:10]] for i, (v, kind) in enumerate(render["trophies"]): if hs[v].peekitem(0)[0] == profileId: render["trophies"][i] = (v, "top1") render["trophies"] = sorted(render["trophies"], key=lambda x: x[1]) shield_owners = request.app["shield_owners"] render["trophies"] += [(v, "shield") for v in shield_owners if shield_owners[v] == profileId] if profileId in CUSTOM_TROPHY_OWNERS: v, kind = CUSTOM_TROPHY_OWNERS[profileId] if v in VARIANTS: render["trophies"].append((v, kind)) render["title"] = "Profile • " + profileId render["icons"] = VARIANT_ICONS render["cup"] = TROPHIES if profileId not in users or users[profileId].perfs is None: render["ratings"] = {} else: render["ratings"] = { k: ("%s%s" % (int(round(v["gl"]["r"], 0)), "?" if v["gl"]["d"] > PROVISIONAL_PHI else ""), v["nb"]) for (k, v) in sorted(users[profileId].perfs.items(), key=lambda x: x[1]["nb"], reverse=True) } if variant is not None: render["variant"] = variant render["profile_title"] = users[ profileId].title if profileId in users else "" render["rated"] = rated elif view == "players": online_users = [ u for u in users.values() if u.username == user.username or (u.online and not u.anon) ] anon_online = sum((1 for u in users.values() if u.anon and u.online)) render["icons"] = VARIANT_ICONS render["users"] = users render["online_users"] = online_users render["anon_online"] = anon_online # render["offline_users"] = offline_users hs = request.app["highscore"] render["highscore"] = { variant: dict(hs[variant].items()[:10]) for variant in hs } elif view in ("shields", "winners"): wi = await get_winners(request.app, shield=(view == "shields")) render["view_css"] = "players.css" render["users"] = users render["icons"] = VARIANT_ICONS render["winners"] = wi elif view == "allplayers": allusers = [u for u in users.values() if not u.anon] render["allusers"] = allusers elif view == "tournaments": render["icons"] = VARIANT_ICONS render["pairing_system_name"] = pairing_system_name render["time_control_str"] = time_control_str render["tables"] = await get_latest_tournaments(request.app) render["admin"] = user.username in ADMINS if (gameId is not None) and gameId != "variants": if view == "invite": render["gameid"] = gameId render["variant"] = seek.variant render["chess960"] = seek.chess960 render["rated"] = seek.rated render["base"] = seek.base render["inc"] = seek.inc render["byo"] = seek.byoyomi_period render["inviter"] = inviter render["seekempty"] = seek.player1 is None and seek.player2 is None else: render["gameid"] = gameId render["variant"] = game.variant render["wplayer"] = game.wplayer.username render["wtitle"] = game.wplayer.title render["wrating"] = game.wrating render["wrdiff"] = game.wrdiff render["chess960"] = game.chess960 render["rated"] = game.rated render["level"] = game.level render["bplayer"] = game.bplayer.username render["btitle"] = game.bplayer.title render["brating"] = game.brating render["brdiff"] = game.brdiff render["fen"] = game.board.fen render["base"] = game.base render["inc"] = game.inc render["byo"] = game.byoyomi_period render["result"] = game.result render["status"] = game.status render["date"] = game.date.isoformat() render["title"] = game.browser_title render["ply"] = ply if ply is not None else game.board.ply - 1 if game.tournamentId is not None: render["tournamentid"] = game.tournamentId render["tournamentname"] = tournament_name render["wberserk"] = game.wberserk render["bberserk"] = game.bberserk if tournamentId is not None: render["tournamentid"] = tournamentId render["tournamentname"] = tournament.name render["description"] = tournament.description render["variant"] = tournament.variant render["chess960"] = tournament.chess960 render["rated"] = tournament.rated render["base"] = tournament.base render["inc"] = tournament.inc render["byo"] = tournament.byoyomi_period render["fen"] = tournament.fen render["before_start"] = tournament.before_start render["minutes"] = tournament.minutes render["date"] = tournament.starts_at render["rounds"] = tournament.rounds render["frequency"] = tournament.frequency render["status"] = tournament.status if view == "level8win": render["level"] = 8 render["profile"] = "Fairy-Stockfish" elif view == "variants": render["icons"] = VARIANT_ICONS render["groups"] = VARIANT_GROUPS # variant None indicates intro.md if lang in ("es", "hu", "it", "pt", "fr"): locale = ".%s" % lang else: locale = "" if variant == "terminology": render["variant"] = "docs/terminology%s.html" % locale else: render["variant"] = "docs/" + ("terminology" if variant is None else variant) + "%s.html" % locale elif view == "news": news_item = request.match_info.get("news_item") if (news_item is None) or (news_item not in NEWS): news_item = list(NEWS.keys())[0] news_item = news_item.replace("_", " ") render["news"] = NEWS render["news_item"] = "news/%s.html" % news_item elif view == "faq": # TODO: make it translatable similar to above variant pages render["faq"] = "docs/faq.html" elif view == "editor" or (view == "analysis" and gameId is None): if fen is None: fen = FairyBoard(variant).start_fen(variant) else: fen = fen.replace(".", "+").replace("_", " ") render["variant"] = variant render["fen"] = fen elif view == "arena-new": render["edit"] = tournamentId is not None if tournamentId is None: render["rated"] = True try: text = await template.render_async(render) except Exception: return web.HTTPFound("/") # log.debug("Response: %s" % text) response = web.Response(text=html_minify(text), content_type="text/html") parts = urlparse(URI) response.set_cookie("user", session["user_name"], domain=parts.hostname, secure=parts.scheme == "https", samesite="Lax", max_age=None if user.anon else MAX_AGE) return response
def create_board(self, variant, initial_fen, chess960, count_started): return FairyBoard(variant, initial_fen, chess960, count_started)
async def index(request): """ Create home html. """ users = request.app["users"] games = request.app["games"] db = request.app["db"] # Who made the request? session = await aiohttp_session.get_session(request) session_user = session.get("user_name") session["last_visit"] = datetime.now().isoformat() session["guest"] = True if session_user is not None: log.info("+++ Existing user %s connected." % session_user) doc = None try: doc = await db.user.find_one({"_id": session_user}) except Exception: log.error("Failed to get user %s from mongodb!" % session_user) if doc is not None: session["guest"] = False if not doc.get("enabled", True): log.info("Closed account %s tried to connect." % session_user) session.invalidate() raise web.HTTPFound("/") if session_user in users: user = users[session_user] else: if session_user.startswith("Anon-"): session.invalidate() raise web.HTTPFound("/") log.debug("New lichess user %s joined." % session_user) title = session["title"] if "title" in session else "" perfs = {variant: DEFAULT_PERF for variant in VARIANTS} user = User(request.app, username=session_user, anon=session["guest"], title=title, perfs=perfs) users[user.username] = user user.ping_counter = 0 else: user = User(request.app, anon=True) log.info("+++ New guest user %s connected." % user.username) users[user.username] = user session["user_name"] = user.username lang = session.get("lang", "en") get_template = request.app["jinja"][lang].get_template view = "lobby" gameId = request.match_info.get("gameId") if request.path == "/about": view = "about" elif request.path.startswith("/variant"): view = "variant" elif request.path == "/players": view = "players" elif request.path == "/allplayers": view = "allplayers" elif request.path == "/games": view = "games" elif request.path == "/patron": view = "patron" elif request.path == "/patron/thanks": view = "thanks" elif request.path == "/level8win": view = "level8win" elif request.path == "/tv": view = "tv" gameId = await tv_game(db, request.app) elif request.path.startswith("/editor"): view = "editor" profileId = request.match_info.get("profileId") variant = request.match_info.get("variant") fen = request.rel_url.query.get("fen") if (fen is not None) and "//" in fen: return web.Response(status=404) if profileId is not None: view = "profile" if request.path[-3:] == "/tv": view = "tv" # TODO: tv for variants gameId = await tv_game_user(db, users, profileId) elif "/challenge" in request.path: view = "lobby" if user.anon: raise web.HTTPFound("/") # Do we have gameId in request url? if gameId is not None: if view != "tv": view = "round" game = await load_game(request.app, gameId) if game is None: log.debug("Requested game %s not in app['games']" % gameId) template = get_template("404.html") return web.Response(text=html_minify(template.render({"home": URI})), content_type="text/html") games[gameId] = game if game.status > STARTED: view = "analysis" if user.username != game.wplayer.username and user.username != game.bplayer.username: game.spectators.add(user) if view == "profile" or view == "level8win": if (profileId in users) and not users[profileId].enabled: template = get_template("closed.html") else: template = get_template("profile.html") elif view == "players": template = get_template("players.html") elif view == "allplayers": template = get_template("allplayers.html") elif view == "variant": template = get_template("variant.html") elif view == "patron": template = get_template("patron.html") else: template = get_template("index.html") render = { "app_name": "PyChess", "languages": LANGUAGES, "lang": lang, "title": view.capitalize(), "view": view, "home": URI, "user": user.username if session["guest"] else "", "anon": user.anon, "username": user.username, "country": session["country"] if "country" in session else "", "guest": session["guest"], "profile": profileId if profileId is not None else "", "variant": variant if variant is not None else "", "fen": fen.replace(".", "+").replace("_", " ") if fen is not None else "", } if view == "profile" or view == "level8win": if view == "level8win": profileId = "Fairy-Stockfish" render["title"] = "Profile • " + profileId render["icons"] = VARIANT_ICONS if profileId not in users or users[profileId].perfs is None: render["ratings"] = {} else: render["ratings"] = { k: ("%s%s" % (int(round(v["gl"]["r"], 0)), "?" if v["gl"]["d"] > PROVISIONAL_PHI else ""), v["nb"]) for (k, v) in sorted(users[profileId].perfs.items(), key=lambda x: x[1]["nb"], reverse=True) } if variant is not None: render["variant"] = variant render["profile_title"] = users[ profileId].title if profileId in users else "" if view == "players": online_users = [ u for u in users.values() if u.online(user.username) and not u.anon ] # offline_users = (u for u in users.values() if not u.online(user.username) and not u.anon) anon_online = sum( (1 for u in users.values() if u.anon and u.online(user.username))) render["icons"] = VARIANT_ICONS render["users"] = users render["online_users"] = online_users render["anon_online"] = anon_online # render["offline_users"] = offline_users render["highscore"] = request.app["highscore"] elif view == "allplayers": allusers = [u for u in users.values() if not u.anon] render["allusers"] = allusers if gameId is not None: render["gameid"] = gameId render["variant"] = game.variant render["wplayer"] = game.wplayer.username render["wtitle"] = game.wplayer.title render["wrating"] = game.wrating render["wrdiff"] = game.wrdiff render["chess960"] = game.chess960 render["rated"] = game.rated render["level"] = game.level render["bplayer"] = game.bplayer.username render["btitle"] = game.bplayer.title render["brating"] = game.brating render["brdiff"] = game.brdiff render["fen"] = game.board.fen render["base"] = game.base render["inc"] = game.inc render["byo"] = game.byoyomi_period render["result"] = game.result render["status"] = game.status render["date"] = game.date.isoformat() render[ "title"] = game.wplayer.username + ' vs ' + game.bplayer.username if view == "level8win": render["level"] = 8 render["profile"] = "Fairy-Stockfish" elif view == "variant": render["variants"] = VARIANTS render["icons"] = VARIANT_ICONS locale = ".%s" % lang if (variant is None) and lang in ("hu", ) else "" if variant == "terminology": render["variant"] = "terminology%s.html" % locale else: render["variant"] = ("intro" if variant is None else variant) + "%s.html" % locale elif view == "editor": if fen is None: fen = FairyBoard(variant).start_fen(variant) render["variant"] = variant render["fen"] = fen try: text = template.render(render) except Exception: raise web.HTTPFound("/") # log.debug("Response: %s" % text) response = web.Response(text=html_minify(text), content_type="text/html") hostname = urlparse(URI).hostname response.set_cookie("user", session["user_name"], domain=hostname, secure="." not in hostname, max_age=None if user.anon else MAX_AGE) return response
def sanitize_fen(variant, initial_fen, chess960): # Initial_fen needs validation to prevent segfaulting in pyffish sanitized_fen = initial_fen start_fen = sf.start_fen(variant) # self.board.start_fen(self.variant) start = start_fen.split() init = initial_fen.split() # Cut off tail if len(init) > 6: init = init[:6] sanitized_fen = " ".join(init) # We need starting color invalid0 = len(init) < 2 # Only piece types listed in variant start position can be used later if variant == "makruk" or variant == "cambodian": non_piece = "~+0123456789[]fF" else: non_piece = "~+0123456789[]" invalid1 = any((c not in start[0] + non_piece for c in init[0])) # Required number of rows invalid2 = start[0].count("/") != init[0].count("/") # Accept zh FEN in lichess format (they use / instead if [] for pockets) if invalid2 and variant == "crazyhouse": if (init[0].count("/") == 8) and ("[" not in init[0]) and ("]" not in init[0]): k = init[0].rfind("/") init[0] = init[0][:k] + "[" + init[0][k + 1:] + "]" sanitized_fen = " ".join(init) invalid2 = False # Allowed starting colors invalid3 = len(init) > 1 and init[1] not in "bw" # Castling rights (and piece virginity) check invalid4 = False if variant == "seirawan" or variant == "shouse": invalid4 = len(init) > 2 and any((c not in "KQABCDEFGHkqabcdefgh-" for c in init[2])) elif chess960: if all((c in "KQkq-" for c in init[2])): chess960 = False else: invalid4 = len(init) > 2 and any((c not in "ABCDEFGHIJabcdefghij-" for c in init[2])) elif variant[-5:] != "shogi": invalid4 = len(init) > 2 and any((c not in start[2] + "-" for c in init[2])) # Castling right need rooks and king placed in starting square if not invalid4: rows = init[0].split("/") backRankB = rows[1] if (variant == 'shako') else rows[0] backRankW = rows[-2] if (variant == 'shako') else rows[-1] rookPosQ = 1 if (variant == 'shako') else 0 rookPosK = -2 if (variant == 'shako') else -1 if ("q" in init[2] and backRankB[rookPosQ] != 'r') or \ ("k" in init[2] and backRankB[rookPosK] != 'r') or \ ("Q" in init[2] and backRankW[rookPosQ] != 'R') or \ ("K" in init[2] and backRankW[rookPosK] != 'R'): invalid4 = True # Number of kings invalid5 = init[0].count("k") != 1 or init[0].count("K") != 1 # Opp king already in check curr_color = init[1] opp_color = "w" if curr_color == "b" else "b" init[1] = init[1].replace(curr_color, opp_color) board = FairyBoard(variant, " ".join(init), chess960) invalid6 = board.is_checked() if invalid0 or invalid1 or invalid2 or invalid3 or invalid4 or invalid5 or invalid6: print(invalid0, invalid1, invalid2, invalid3, invalid4, invalid5, invalid6) sanitized_fen = start_fen return False, start_fen else: return True, sanitized_fen
def sanitize_fen(variant, initial_fen, chess960): # Prevent this particular one to fail on our general sastling check if variant == "capablanca" and initial_fen == CONSERVATIVE_CAPA_FEN: return True, initial_fen # Initial_fen needs validation to prevent segfaulting in pyffish sanitized_fen = initial_fen start_fen = sf.start_fen(variant) # self.board.start_fen(self.variant) start = start_fen.split() init = initial_fen.split() # Cut off tail if len(init) > 6: init = init[:6] sanitized_fen = " ".join(init) # We need starting color invalid0 = len(init) < 2 # Only piece types listed in variant start position can be used later if variant == "dobutsu": non_piece = "~+0123456789[]hH-" elif variant == "orda": non_piece = "~+0123456789[]qH-" else: non_piece = "~+0123456789[]-" invalid1 = any((c not in start[0] + non_piece for c in init[0])) # Required number of rows invalid2 = start[0].count("/") != init[0].count("/") # Accept zh FEN in lichess format (they use / instead if [] for pockets) if invalid2 and variant == "crazyhouse": if (init[0].count("/") == 8) and ("[" not in init[0]) and ("]" not in init[0]): k = init[0].rfind("/") init[0] = init[0][:k] + "[" + init[0][k + 1:] + "]" sanitized_fen = " ".join(init) invalid2 = False # Allowed starting colors invalid3 = len(init) > 1 and init[1] not in "bw" # Castling rights (and piece virginity) check invalid4 = False if len(init) > 2: if variant in ("seirawan", "shouse"): invalid4 = any((c not in "KQABCDEFGHkqabcdefgh-" for c in init[2])) elif chess960: if all((c in "KQkq-" for c in init[2])): chess960 = False else: invalid4 = any((c not in "ABCDEFGHIJabcdefghij-" for c in init[2])) elif variant[-5:] != "shogi" and variant not in ("dobutsu", "gorogoro", "gorogoroplus"): invalid4 = any((c not in start[2] + "-" for c in init[2])) # Castling right need rooks and king placed in starting square if (not invalid2) and (not invalid4) and not (chess960 and (variant in ("seirawan", "shouse"))): rows = init[0].split("/") backRankB = rows[1] if (variant == 'shako') else rows[0] backRankW = rows[-2] if (variant == 'shako') else rows[-1] # cut off pockets k = backRankW.rfind("[") if k > 0: backRankW = backRankW[:k] rookPosQ = 1 if (variant == 'shako') else 0 rookPosK = -2 if (variant == 'shako') else -1 if ("q" in init[2] and backRankB[rookPosQ] != 'r') or \ ("k" in init[2] and backRankB[rookPosK] != 'r') or \ ("Q" in init[2] and backRankW[rookPosQ] != 'R') or \ ("K" in init[2] and backRankW[rookPosK] != 'R'): invalid4 = True # Number of kings bking = "l" if variant == "dobutsu" else "k" wking = "L" if variant == "dobutsu" else "K" invalid5 = init[0].count(bking) != 1 or init[0].count(wking) != 1 # Opp king already in check curr_color = init[1] opp_color = "w" if curr_color == "b" else "b" init[1] = init[1].replace(curr_color, opp_color) board = FairyBoard(variant, " ".join(init), chess960) invalid6 = board.is_checked() if invalid0 or invalid1 or invalid2 or invalid3 or invalid4 or invalid5 or invalid6: print(invalid0, invalid1, invalid2, invalid3, invalid4, invalid5, invalid6) sanitized_fen = start_fen return False, start_fen return True, sanitized_fen
def create_board(self, variant, initial_fen, chess960): return FairyBoard(variant, initial_fen, chess960)
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()
async def index(request): """ Create home html. """ users = request.app["users"] games = request.app["games"] db = request.app["db"] # Who made the request? session = await aiohttp_session.get_session(request) session_user = session.get("user_name") session["last_visit"] = datetime.now().isoformat() session["guest"] = True if session_user is not None: log.info("+++ Existing user %s connected.", session_user) doc = None try: doc = await db.user.find_one({"_id": session_user}) except Exception: log.error("Failed to get user %s from mongodb!", session_user) if doc is not None: session["guest"] = False if not doc.get("enabled", True): log.info("Closed account %s tried to connect.", session_user) session.invalidate() return web.HTTPFound("/") if session_user in users: user = users[session_user] else: if session_user.startswith("Anon-"): session.invalidate() return web.HTTPFound(request.rel_url) log.debug("New lichess user %s joined.", session_user) title = session["title"] if "title" in session else "" perfs = {variant: DEFAULT_PERF for variant in VARIANTS} user = User(request.app, username=session_user, anon=session["guest"], title=title, perfs=perfs) users[user.username] = user else: user = User(request.app, anon=True) log.info("+++ New guest user %s connected.", user.username) users[user.username] = user session["user_name"] = user.username lang = session.get("lang", "en") get_template = request.app["jinja"][lang].get_template view = "lobby" gameId = request.match_info.get("gameId") ply = request.rel_url.query.get("ply") if request.path == "/about": view = "about" elif request.path == "/faq": view = "faq" elif request.path == "/stats": view = "stats" elif request.path.startswith("/news"): view = "news" elif request.path.startswith("/variant"): view = "variant" elif request.path == "/players": view = "players" elif request.path == "/allplayers": view = "allplayers" elif request.path == "/games": view = "games" elif request.path == "/patron": view = "patron" elif request.path == "/patron/thanks": view = "thanks" elif request.path == "/level8win": view = "level8win" elif request.path == "/tv": view = "tv" gameId = await tv_game(db, request.app) elif request.path.startswith("/editor"): view = "editor" elif request.path.startswith("/analysis"): view = "analysis" elif request.path.startswith("/embed"): view = "embed" elif request.path == "/paste": view = "paste" profileId = request.match_info.get("profileId") variant = request.match_info.get("variant") if (variant is not None) and ((variant not in VARIANTS) and variant != "terminology"): log.debug("Invalid variant %s in request", variant) return web.Response(status=404) fen = request.rel_url.query.get("fen") rated = None if (fen is not None) and "//" in fen: log.debug("Invelid FEN %s in request", fen) return web.Response(status=404) if profileId is not None: view = "profile" if request.path[-3:] == "/tv": view = "tv" # TODO: tv for variants gameId = await tv_game_user(db, users, profileId) elif request.path[-7:] == "/import": rated = IMPORTED elif request.path[-6:] == "/rated": rated = RATED elif "/challenge" in request.path: view = "lobby" if user.anon: return web.HTTPFound("/") # Do we have gameId in request url? if gameId is not None: if view not in ("tv", "analysis", "embed"): view = "round" invites = request.app["invites"] if (gameId not in games) and (gameId in invites): if not request.path.startswith("/invite/accept/"): seek_id = invites[gameId].id seek = request.app["seeks"][seek_id] view = "invite" inviter = seek.user.username if user.username != seek.user.username else "" if view != "invite": game = await load_game(request.app, gameId, user) if game is None: log.debug("Requested game %s not in app['games']", gameId) template = get_template("404.html") text = await template.render_async({"home": URI}) return web.Response(text=html_minify(text), content_type="text/html") if (ply is not None) and (view != "embed"): view = "analysis" if user.username != game.wplayer.username and user.username != game.bplayer.username: game.spectators.add(user) if view in ("profile", "level8win"): if (profileId in users) and not users[profileId].enabled: template = get_template("closed.html") else: template = get_template("profile.html") elif view == "players": template = get_template("players.html") elif view == "allplayers": template = get_template("allplayers.html") elif view == "news": template = get_template("news.html") elif view == "variant": template = get_template("variant.html") elif view == "patron": template = get_template("patron.html") elif view == "faq": template = get_template("FAQ.html") elif view == "analysis": template = get_template("analysis.html") elif view == "embed": template = get_template("embed.html") else: template = get_template("index.html") render = { "app_name": "PyChess", "languages": LANGUAGES, "lang": lang, "title": view.capitalize(), "view": view, "asseturl": STATIC_ROOT, "view_css": ("round" if view == "tv" else view) + ".css", "home": URI, "user": user.username if session["guest"] else "", "anon": user.anon, "username": user.username, "country": session["country"] if "country" in session else "", "guest": session["guest"], "profile": profileId if profileId is not None else "", "variant": variant if variant is not None else "", "fen": fen.replace(".", "+").replace("_", " ") if fen is not None else "", "variants": VARIANTS, } if view in ("profile", "level8win"): if view == "level8win": profileId = "Fairy-Stockfish" render["title"] = "Profile • " + profileId render["icons"] = VARIANT_ICONS if profileId not in users or users[profileId].perfs is None: render["ratings"] = {} else: render["ratings"] = { k: ("%s%s" % (int(round(v["gl"]["r"], 0)), "?" if v["gl"]["d"] > PROVISIONAL_PHI else ""), v["nb"]) for (k, v) in sorted(users[profileId].perfs.items(), key=lambda x: x[1]["nb"], reverse=True) } if variant is not None: render["variant"] = variant render["profile_title"] = users[ profileId].title if profileId in users else "" render["rated"] = rated render["variant_display_name"] = variant_display_name if view == "players": online_users = [ u for u in users.values() if u.username == user.username or (u.online and not u.anon) ] anon_online = sum((1 for u in users.values() if u.anon and u.online)) render["icons"] = VARIANT_ICONS render["users"] = users render["online_users"] = online_users render["anon_online"] = anon_online # render["offline_users"] = offline_users hs = request.app["highscore"] render["highscore"] = { variant: dict(hs[variant].items()[:10]) for variant in hs } render["variant_display_name"] = variant_display_name elif view == "allplayers": allusers = [u for u in users.values() if not u.anon] render["allusers"] = allusers if gameId is not None: if view == "invite": render["gameid"] = gameId render["variant"] = seek.variant render["chess960"] = seek.chess960 render["rated"] = seek.rated render["base"] = seek.base render["inc"] = seek.inc render["byo"] = seek.byoyomi_period render["inviter"] = inviter else: render["gameid"] = gameId render["variant"] = game.variant render["wplayer"] = game.wplayer.username render["wtitle"] = game.wplayer.title render["wrating"] = game.wrating render["wrdiff"] = game.wrdiff render["chess960"] = game.chess960 render["rated"] = game.rated render["level"] = game.level render["bplayer"] = game.bplayer.username render["btitle"] = game.bplayer.title render["brating"] = game.brating render["brdiff"] = game.brdiff render["fen"] = game.board.fen render["base"] = game.base render["inc"] = game.inc render["byo"] = game.byoyomi_period render["result"] = game.result render["status"] = game.status render["date"] = game.date.isoformat() render[ "title"] = game.wplayer.username + ' vs ' + game.bplayer.username if ply is not None: render["ply"] = ply if view == "level8win": render["level"] = 8 render["profile"] = "Fairy-Stockfish" elif view == "variant": render["icons"] = VARIANT_ICONS # variant None indicates intro.md if lang in ("hu", "it", "pt", "fr"): locale = ".%s" % lang elif lang == "zh": # Only intro.md locale = ".%s" % lang if variant in (None, ) else "" else: locale = "" if variant == "terminology": render["variant"] = "docs/terminology%s.html" % locale else: render["variant"] = "docs/" + ("intro" if variant is None else variant) + "%s.html" % locale render["variant_display_name"] = variant_display_name elif view == "news": news_item = request.match_info.get("news_item") if (news_item is None) or (news_item not in NEWS): news_item = NEWS[0] news_item = news_item.replace("_", " ") render["news"] = NEWS render["news_item"] = "news/%s.html" % news_item elif view == "faq": # TODO: make it translatable similar to above variant pages render["faq"] = "docs/faq.html" elif view == "editor" or (view == "analysis" and gameId is None): if fen is None: fen = FairyBoard(variant).start_fen(variant) else: fen = fen.replace(".", "+").replace("_", " ") render["variant"] = variant render["fen"] = fen try: text = await template.render_async(render) except Exception: return web.HTTPFound("/") # log.debug("Response: %s" % text) response = web.Response(text=html_minify(text), content_type="text/html") parts = urlparse(URI) response.set_cookie("user", session["user_name"], domain=parts.hostname, secure=parts.scheme == "https", samesite="Lax", max_age=None if user.anon else MAX_AGE) return response