예제 #1
0
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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
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
예제 #5
0
 def create_board(self, variant, initial_fen, chess960, count_started):
     return FairyBoard(variant, initial_fen, chess960, count_started)
예제 #6
0
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
예제 #7
0
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
예제 #8
0
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
예제 #9
0
 def create_board(self, variant, initial_fen, chess960):
     return FairyBoard(variant, initial_fen, chess960)
예제 #10
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()
예제 #11
0
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