def login_quiz(*, lecture: Lecture, error: Optional[str] = None) -> Frag: return login( title=lecture.title, h1=frag("Login for ", h("em")(lecture.title), " self assessment quiz"), error=error, )
def login_lecture(*, lecture: Lecture, error: Optional[str] = None) -> Frag: return login( title=lecture.title, h1=frag("Register for the next ", h("em")(lecture.title), " lecture (step 1/3)"), error=error, )
def endgames(*, development: bool) -> Frag: def item(material: str) -> Frag: return h("li", klass="maximal" if syzygy_tables_info.stats.is_maximal(material) else None)(h( "a", href=fen_url( syzygy_tables_info.stats.longest_fen(material)))( h("strong")(material) if syzygy_tables_info.stats.is_maximal(material) else material, ), ) return layout( development=development, title="Endgames", left=frag( h("h1")("Endgames"), h("p")("These are the longest endgames (maximum ", h("a", href="/stats")(dtz50_pp), ") for each material configuration."), h("p")( h("a", href="/endgames.pgn")( h("span", klass="icon icon-download")(), " endgames.pgn", ), " (", kib(4396), ")", ), back_to_board(), ), right=frag( h("section", id=f"{piece_count}-pieces") (h("h2")(piece_count, " pieces"), h("ul", klass="endgames")( item(material) for material in syzygy_tables_info.stats.STATS if len(material) == piece_count + 1) if piece_count < 5 else (frag( h("h3")("No pawns" if pawns == 0 else ( "1 pawn" if pawns == 1 else f"{pawns} pawns")), h("ul", klass="endgames")( item(material) for material in syzygy_tables_info.stats.STATS if len(material) == piece_count + 1 and material.count("P") == pawns), ) for pawns in range(0, piece_count - 2 + 1))) for piece_count in range(3, 7 + 1)), )
def spare(color: ColorName) -> Frag: return h( "div", klass=["spare", "bottom" if color == "white" else "top"] )(frag( h("piece", klass=[color, role], data_color=color, data_role=role) (), " ", ) for role in ["pawn", "knight", "bishop", "rook", "queen", "king"])
def index() -> Frag: return layout( None, frag( h("h1")("CIG Lectures WS2020"), h("ul")(h("li")(h("a", href=url(name))(lecture.title)) for name, lecture in cig.data.LECTURES.items()), ))
def link_sent(*, title: str, email_text: Optional[str]) -> Frag: return layout( title, frag( h("h1")(title), h("section")( h("p")("Check your inbox."), h("p")( "Development mode enabled. This email would have been sent: ", h("pre")(email_text), ) if email_text else None, )))
def login(*, title: str, h1: Frag, error: Optional[str] = None) -> Frag: return layout( title, frag( h("h1")(h1), h("section")(h("form", method="POST")( error and h("p", klass="error")(error), h("p")( h("label", for_="email")("Email:"), " ", h("input", type="email", placeholder="*****@*****.**", id="email", name="email", required=True), ), h("p")(h("button", type="submit")("Send login link")), ))))
def stats(*, development: bool) -> Frag: return layout( development=development, title="Machine readable endgame statistics", left=frag( h("h1")("Machine readable endgame statistics"), back_to_board(), ), right=frag( h("section", id="api")( h("h2")("API"), h("div", klass="panel panel-default")( h("div", klass="panel-heading")( "GET ", h("a", href="/stats.json")("/stats.json"), ), h("div", klass="panel-body")( "All endgame stats with keys such as ", h("code")("KRNvKNN"), ".", ), ), h("div", klass="panel panel-default")( h("div", klass="panel-heading")( "GET ", h("a", href="/stats/KRNvKNN.json")("/stats/KRNvKNN.json"), ), h("div", klass="panel-body")( "Endgame stats for a specific endgame, e.g., ", h("code")("KRNvKNN"), ". ", "Redirects to normalized endgame names.", ), ), ), h("section", id="example")( h("h2")("Example (KRNvKNN)"), h("pre")(h("code")(textwrap.dedent("""\ { "rtbw": { "bytes": 290002640, // file size "tbcheck": "a320ac...", // internal checksum "md5": "6ee435...", "sha1": "07a0e4...", "sha256": "f3386d...", "sha512": "c4bf73...", "b2": "b970e0...", // blake 2 "ipfs": "QmXW4S..." }, "rtbz": { // ... }, "longest": [ // longest winning endgames for black/white // with/without 50-move rule { "epd": "3n1n2/8/8/8/4R3/8/8/NK1k4 b - -", "ply": 100, "wdl": -2 }, { "epd": "6k1/5n2/8/8/8/5n2/1RK5/1N6 w - -", "ply": 485, "wdl": 1 }, { "epd": "8/8/8/8/8/8/N1nk4/RKn5 b - -", "ply": 7, "wdl": 2 } ], "histogram": { "white": { // white to move "win": [ 0, 1924310948, 35087, 363845772, 37120, 138550471, // ... ] "loss": [ 98698, // # of positions losing in 0 144, // # of positions losing in 1 3810, // # of positions losing in 2 0, // 3 596, // 4 0, // 5 58 // 6 ], "wdl": { // # of positions with each wdl "-2": 0, "-1": 103306, "0": 1333429189, "1": 162344388, "2": 2959977091 } }, "black": { // black to move // ... }, } }""")), ), ), ), )
def metrics(*, development: bool) -> Frag: n = h("var")("n") example1 = "1kb5/8/1KN5/3N4/8/8/8/8 b - -" example2 = "8/8/2N5/8/3k4/7N/p2K4/8 b - -" def example_board(epd: str, check: str) -> Frag: board_fen = epd.split(" ")[0] return h("a", href=f"/?fen={epd.replace(' ', '_')}_0_1")(h( "img", width=300, height=300, alt=epd, src= f"https://backscattering.de/web-boardimage/board.svg?fen={board_fen}&check={check}" ), ) def example_link(epd: str) -> Frag: return h("a", href=f"/?fen={epd.replace(' ', '_')}_0_1")(epd) return layout( development=development, title="Metrics", left=frag( h("h1")("Metrics"), h("p")("Information stored in Syzygy tablebases"), back_to_board(), ), right=frag( h("section", id="wdl")( h("h2")(wdl50), h("p")( "5-valued ", h("em")("Win/Draw/Loss"), " information can be used to decide which positions to aim for.", ), h("div", klass="list-group stats")( h("div", klass="li white-win")("Win (+2)"), h("div", klass="li white-win frustrated")( "Win prevented by 50-move rule (+1)"), h("div", klass="li draws")("Drawn (0)"), h("div", klass="li black-win frustrated")( "Loss saved by 50-move rule (-1)"), h("div", klass="li black-win")("Loss (-2)"), ), ), h("section", id="dtz")( h("h2")(dtz50_pp, " with rounding"), h("p")( "Once a tablebase position has been reached, the ", h("em")("Distance To Zeroing"), " ", "(of the fifty-move counter by a capture or pawn move) ", "can be used to reliably make progress in favorable positions and stall ", "in unfavorable positions.", ), h("p")("The precise meanings are as follows:"), h("p")( "A DTZ value ", n, " with 100 ≥ ", n, " ≥ 1 means the position is winning, ", "and a zeroing move or checkmate can be forced in ", n, " or ", n, " + 1 half-moves.", ), example_board(example1, "b8"), h("p")( "For an example of this ambiguity, see how the DTZ repeats after the only-move Ka8 in ", example_link(example1), ". ", "This is due to the fact that some Syzygy tables store rounded moves ", "instead of half-moves, to save space. This implies some primary tablebase lines ", "may waste up to 1 ply. Rounding is never used for endgame phases where it would ", "change the game theoretical outcome (", wdl50, ").", ), h("p")( "Users need to be careful in positions ", "that are nearly drawn under ", "the 50-move rule! Carelessly wasting 1 more ply ", "by not following the tablebase recommendation, for a total of 2 wasted plies, ", "may change the outcome of the game.", ), h("p")( "A DTZ value ", n, " > 100 means the position is winning, but drawn under the 50-move rule. ", "A zeroing move or checkmate can be forced in ", n, " or ", n, " + 1 half-moves, ", "or in ", n, " - 100 or ", n, " + 1 - 100 half-moves ", "if a later phase is responsible for the draw.", ), example_board("8/8/2N5/8/3k4/7N/p2K4/8 b - -", "d4"), h("p")( "For example, in ", example_link(example2), " ", "black promotes the pawn in 7 ply, but the DTZ is 107, ", "indicating that white can hold a draw under the 50-move rule ", "in a later phase of the endgame.", ), h("p")( "An in-depth discussion of rounding can be found in ", h("a", href= "http://www.talkchess.com/forum3/viewtopic.php?f=7&t=58488#p651293" )("this thread"), ".", ), ), ), )
def legal(*, development: bool = True) -> Frag: return layout( development=development, title="Legal", left=frag( h("h1")("Legal"), h("p") ("The tablebase lookup is provided on a best-effort basis, without guarantees of correctness or availability. Feedback or questions are welcome." ), h("p") ("Records no personal information other than standard server logs. The logs are kept no longer than 48 hours." ), back_to_board(), ), right=frag( h("section", id="contact")( h("h2")("Contact"), h("p")( h("a", href="mailto:[email protected]")( "*****@*****.**"), " ", "(", h("a", href= "https://pgp.mit.edu/pks/lookup?op=get&search=0x2ECA66C65B255138" )("pgp"), ")", ), ), h("section", id="imprint")( h("h2")("Imprint"), h("p")( "Niklas Fiekas", h("br"), "Tannenhöhe 16", h("br"), "38678 Clausthal-Zellerfeld", h("br"), "Germany", ), ), h("section", id="thanks")( h("h2")("Software licenses"), h("p")( "The ", h("a", href="https://github.com/niklasf/syzygy-tables.info")( "code for the website itself"), " is ", h("a", href= "https://github.com/niklasf/syzygy-tables.info/blob/master/LICENSE" )("licensed under the AGPL-3.0+"), ".", ), h("table", id="jslicense-labels1")( h("thead")(h("tr")( h("th")("Script"), h("th")("License"), h("th")("Source"), ), ), h("tbody")(h("tr")( h("td")(h("a", href=asset_url("js/client.min.js"))( "client.min.js"), ), h("td") (h("a", href="https://www.gnu.org/licenses/agpl-3.0.en.html" )("AGPL-3.0+"), ), h("td") (h("a", href= "https://github.com/niklasf/syzygy-tables.info/blob/master/src/client.ts" )("client.ts"), ), ), ), ), h("p")("It also uses the following software/artwork:"), h("ul")( h("li")( h("a", href="https://github.com/ornicar/chessground")( "chessground"), " (GPL-3.0+)", ), h("li")( h("a", href="https://github.com/niklasf/chessops")( "chessops"), " (GPL-3.0+)", ), h("li")( h("a", href="https://github.com/niklasf/python-chess")( "python-chess"), " (GPL-3.0+)", ), h("li")( h("a", href="https://github.com/niklasf/python-tinyhtml")( "tinyhtml"), " (MIT/Apache-2.0)", ), h("li")( h("a", href="http://aiohttp.readthedocs.org/en/stable/")( "aiohttp"), " (Apache-2.0)", ), h("li")( "Selected icons from ", h("a", href="https://fontawesome.com/")("Font Awesome"), " (SIL)", ), h("li")( "A few styles from ", h("a", href="https://getbootstrap.com/")("Bootstrap"), " (MIT)", ), ), ), ), )
def section_stats(render: Render, stats: RenderStats) -> Frag: return h("section", id="stats")( frag( h("h3")( f"Histogram: {stats['material_side']} {stats['verb']} vs. {stats['material_other']} ", raw("(log scale)"), ), h("div", klass="histogram")((h( "div", klass="empty", title=f"{row['empty']} empty rows skipped", )("⋮") if row["empty"] else h( "div", style=f"width:{row['width']}%;", klass="active" if row["active"] else None, title= f"{row['num']:,} unique positions with {stats['material_side']} {stats['verb']} in {row['ply']} (DTZ)" )()) for row in stats["histogram"]), ) if stats.get("histogram") else None, frag( h("h3")(f"Longest {render['material']} phases"), h("ul")(h("li")(h("a", href=fen_url(longest["fen"]))( longest["label"]), ) for longest in stats["longest"]), ) if stats["longest"] else None, h("h3")(f"{render['material']} statistics (unique positions)"), h("div", klass="list-group stats")( h("div", klass="li white-win", title="Unique positions with white wins")( "White wins:", h("br"), f"{stats['white']:,} ({stats['white_pct']}%)") if stats["white"] else None, h("div", klass="li white-win frustrated", title="Unique positions with frustrated white wins")( "Frustrated white wins:", h("br"), f"{stats['cursed']:,} ({stats['cursed_pct']}%)") if stats["cursed"] else None, h("div", klass="li draws", title="Unique drawn positions")( "Draws:", h("br"), f"{stats['draws']:,} ({stats['draws_pct']}%)") if stats["draws"] else None, h("div", klass="li black-win frustrated", title="Unique positions with frustrated black wins")( "Frustrated black wins:", h("br"), f"{stats['blessed']:,} ({stats['blessed_pct']}%)") if stats["blessed"] else None, h("div", klass="li black-win", title="Unique positions with black wins")( "Black wins:", h("br"), f"{stats['black']:,} ({stats['black_pct']}%)") if stats["black"] else None, ), h("a", href=f"/stats/{render['material']}.json", title=f"Machine readable endgame statistics for {render['material']}" )( h("span", klass="icon icon-stats", aria_hidden="true")(), f" {render['material']}.json ", h("a", href="/stats")("(?)"), ), )
def xhr_probe(render: Render) -> Frag: def move(m: RenderMove) -> Frag: return h("a", klass="li", href=fen_url(m["fen"]), data_uci=m["uci"])( m["san"], h("span", klass="badge")(f"DTM {m['dtm']}") if m["dtm"] else None, h("span", klass="badge")(m["badge"]), ) middot = raw(" · ") stats = render["stats"] return h("section")( # Status. h("h2", id="status", klass=[ f"{render['winning_side']}-win" if render["winning_side"] else None, "frustrated" if render["frustrated"] else None, ])(render["status"]), # Move lists. h("div", id="winning", klass=f"list-group {render['turn']}-turn") (move(m) for m in render["winning_moves"]), h("div", id="cursed", klass=f"list-group {render['turn']}-turn")( move(m) for m in render["cursed_moves"]), h("div", id="drawing", klass="list-group")(move(m) for m in render["drawing_moves"]), h("div", id="unknown", klass="list-group")(move(m) for m in render["unknown_moves"]), h("div", id="blessed", klass=f"list-group {render['turn']}-turn")( move(m) for m in render["blessed_moves"]), h("div", id="losing", klass=f"list-group {render['turn']}-turn")( move(m) for m in render["losing_moves"]), # Info. h("div", id="info") (h("p")("The given position is not a legal chess position.") if render["illegal"] else h("p") (h("strong")("The game is drawn"), " because with the remaining material no sequence of legal moves can lead to checkmate." ) if render["insufficient_material"] else frag( h("p")(h("a", href="https://en.wikipedia.org/wiki/Solving_chess") ("Chess is not yet solved."), ) if render["fen"] == chess.STARTING_FEN else None, h("p") ("Syzygy tables only provide information for positions with up to 7 pieces and no castling rights." ), ) if render["unknown_moves"] else h("p")( h("strong")("This is a blessed loss."), " Mate can be forced, but a draw can be achieved under the fifty-move rule.", ) if render["blessed_loss"] else h("p")( h("strong")("This is a cursed win."), " Mate can be forced, but a draw can be achieved under the fifty-move rule.", ) if render["cursed_win"] else None, ), frag( h("a", klass="meta-link", href= f"/syzygy-vs-syzygy/{render['material']}.pgn?fen={render['fen'].replace(' ', '_')}", title="Download DTZ mainline")( h("span", klass="icon icon-download", aria_hidden="true")(), f" {render['material']}.pgn", ), " ", h("a", klass="meta-link", href= f"https://lichess.org/analysis/standard/{render['fen'].replace(' ', '_')}#explorer" )( h("span", klass="icon icon-external", aria_hidden="true")(), " lichess.org", ), ) if not render["illegal"] else None, # Stats. section_stats(render, stats) if stats else None, # Dependencies. h("section", id="dependencies")( h("h3")(f"{render['material']} dependencies"), frag( h("p") (f"To probe all {render['normalized_material']} positions, these tables and their transitive dependencies are also required:" ), h("p")(frag( middot if i > 0 else None, h("a", href=fen_url(dep["longest_fen"]))(dep["material"]), ) for i, dep in enumerate(render["deps"]))) if render["deps"] else None, h("p")( h("a", klass="meta-link", href= f"/download/{render['normalized_material']}.txt?source=lichess&dtz=root", title="Download list")( h("span", klass="icon icon-list", aria_hidden="true")(), f" {render['normalized_material']}.txt", ), " ", h("a", klass="meta-link", href=f"/graph/{render['normalized_material']}.dot", title="Dependency graph")( h("span", klass="icon icon-graph", aria_hidden="true")(), f" {render['normalized_material']}.dot", ), ), ) if render["is_table"] else None, # Homepage. frag( h("section", id="syzygy")( h("h2")("Syzygy tablebases"), h("h3")("About"), h("p")( "Syzygy tablebases allow perfect play with up to 7 pieces, ", "both with and without the fifty-move drawing rule, ", "i.e., they allow winning all won ", "positions and bringing all drawn positions over the fifty-move line.", ), h("p")( "The tables provide ", h("a", href="/metrics")(wdl50, " and ", dtz50_pp, " information"), ". ", "Forcing captures or pawn moves while keeping a win in hand ", "ensures that progress is being made.", ), h("p")( "DTZ optimal play is not always the shortest way to mate ", "(", h("abbr", title="depth-to-mate")("DTM"), ") ", "and can even look unintuitive: ", "For example sometimes pieces can be sacrificed to reset the fifty-move ", "counter as soon as possible. However, unlike DTM it achieves the best ", "possible result even with the fifty-move rule.", ), h("p")( "6-piece tables were ", h("a", href= "http://www.talkchess.com/forum3/viewtopic.php?t=47681")( "released"), " by Ronald de Man in April 2013, including ", h("a", href="https://github.com/syzygy1/tb")( "probing code and the generator"), ".", ), h("p")( "From May to August 2018 Bojun Guo ", h("a", href= "http://www.talkchess.com/forum/viewtopic.php?start=0&t=66797&topic_view=flat" )("generated"), " 7-piece tables. ", "The 7-piece tablebase contains 423,836,835,667,331 ", h("a", href="https://kirill-kryukov.com/chess/nulp/results.html" )("unique legal positions"), " in about 18 Terabytes.", ), h("h3")("Selected positions"), h("ul")( h("li")( h("a", href="/endgames")("Longest endgames"), ": ", h("a", href="/?fen=8/8/8/8/8/8/2Rk4/1K6_b_-_-_0_1")("3"), ", ", h("a", href="/?fen=8/8/8/6B1/8/8/4k3/1K5N_b_-_-_0_1")("4"), ", ", h("a", href="/?fen=K7/N7/k7/8/3p4/8/N7/8_w_-_-_0_1")("5"), ", ", h("a", href="/?fen=6N1/5KR1/2n5/8/8/8/2n5/1k6_w_-_-_0_1")( "6"), ", ", h("a", href="/?fen=QN4n1/6r1/3k4/8/b2K4/8/8/8_b_-_-_0_1")( "7 pieces"), ), h("li")(h( "a", href="/?fen=8/6B1/8/8/B7/8/K1pk4/8_b_-_-_0_1" )("Black escapes to a blessed loss with an underpromotion" ), ), h("li")(h( "a", href="/?fen=k7/2QR4/8/8/8/4N3/2r4Q/1K6_b_-_-_0_1" )("Black rook chasing king to force stalemate, without avail" ), ), h("li")(h( "a", href="/?fen=6B1/3B4/5R2/6q1/P7/1k6/8/3K4_b_-_-_0_1" )("656 half-moves before the winning side can safely move a pawn" ), ), ), ), h("section", id="download")( h("h2")("Download"), h("p") ("If you want to use tablebases in a chess engine you certainly need a local copy." ), h("p")( "Most of the time (during search) only WDL tables are used. ", "Keep these on SSD storage if you can. ", "DTZ tables are generally only used to finish the final phase of the game (\"at the root\").", ), h("table")( h("thead")(h("tr")( h("th")("Pieces"), h("th")("WDL"), h("th")("DTZ"), h("th")("Total"), ), ), h("tbody")( h("tr")( h("td")("3-5"), h("td")(kib(387124)), h("td")(kib(574384)), h("td")(kib(961508)), ), h("tr")( h("td")("6"), h("td")(kib(71127940)), h("td")(kib(85344200)), h("td")(kib(156472140)), ), h("tr")( h("td")("7"), h("td")(kib(9098389892)), h("td")(kib(8859535148)), h("td")(kib(17957925040)), ), ), ), h("p")( h("a", href="https://github.com/syzygy1/tb")("Generating"), " the tablebases requires considerable computational resources. ", "It is more efficient to download them from a mirror:", ), h("table")( h("thead")(h("tr")( h("th")("Host"), h("th")("Info"), h("th")("#"), h("th")("List"), ), ), h("tbody")( h("tr")( h("td")(h("a", href="http://tablebase.sesse.net/")( "tablebase.sesse.net")), h("td")("http, EU"), h("td")("7"), h("td")(h( "a", href="/download.txt?source=sesse&max-pieces=7", title="List of URLs (txt)")(h( "span", klass="icon icon-list")())), ), h("tr")( h("td")(h( "a", href="https://tablebase.lichess.ovh/tables/")( "tablebase.lichess.ovh")), h("td")("http, https, EU"), h("td")("7"), h("td") (h("a", href="/download.txt?source=lichess&max-pieces=7", title="List of URLs (txt)")(h( "span", klass="icon icon-list")())), ), h("tr")( h("td")(h("a", href="https://ipfs.syzygy-tables.info/")( "ipfs.syzygy-tables.info")), h("td")("ipfs, Cloudflare"), h("td")("7"), h("td")(h( "a", href="/download.txt?source=ipfs&max-pieces=7", title="List of URLs (txt)")(h( "span", klass="icon icon-list")())), ), h("tr")( h("td")(h("a", href="http://oics.olympuschess.com/")( "oics.olympuschess.com")), h("td")("BitTorrent"), h("td")("6"), h("td")(), ), ), ), h("h3")("Checksums"), h("a", href="/checksums/bytes.tsv", title="du --bytes")("file sizes"), middot, h("a", href="/checksums/tbcheck.txt", title="Internal non-cryptographic checksums")("tbcheck"), middot, h("a", href="/checksums/MD5SUM")("md5"), middot, h("a", href="/checksums/SHA1SUM")("sha1"), middot, h("a", href="/checksums/SHA256SUM")("sha256"), middot, h("a", href="/checksums/SHA512SUM")("sha512"), middot, h("a", href="/checksums/B2SUM")("blake2"), middot, h("a", href="/checksums/PackManifest", title="PackManifest")("ipfs"), ), h("section", id="contact")( h("h2")("Contact"), h("p")("Feedback ", h("a", href="/legal#contact")("via mail"), ", bug reports and ", h("a", href="https://github.com/niklasf/syzygy-tables.info")( "pull requests"), " are welcome."), ), ) if render["fen"] == DEFAULT_FEN else None, )
def index(*, development: bool = True, render: Render) -> Frag: def spare(color: ColorName) -> Frag: return h( "div", klass=["spare", "bottom" if color == "white" else "top"] )(frag( h("piece", klass=[color, role], data_color=color, data_role=role) (), " ", ) for role in ["pawn", "knight", "bishop", "rook", "queen", "king"]) return layout( development=development, title=render["material"], head=frag( h("meta", name="description", content= "User interface and public API for probing Syzygy endgame tablebases" ), h("meta", property="og:image", content=render["thumbnail_url"]), h("meta", property="twitter:image", content=render["thumbnail_url"]), ), left=frag( h("h1", klass="main")(h("a", href="/")("Syzygy endgame tablebases"), ), h("nav")( h("div", id="side-to-move-toolbar")( h("div", id="side-to-move", klass="btn-group", role="group", aria_label="Side to move")( h("a", klass={ "btn": True, "btn-default": True, "active": render["turn"] == "white", }, id="btn-white", href=fen_url( render["white_fen"]))("White to move"), h("a", klass={ "btn": True, "btn-default": True, "active": render["turn"] == "black", }, id="btn-black", href=fen_url( render["black_fen"]))("Black to move"), ), " ", h("div", klass="btn-group") (h("a", id="btn-edit", klass="btn btn-default", title="Edit mode: Do not switch sides when playing moves" )(h("span", klass="icon icon-lock-open")(), ), ), ), spare("black"), h("div", id="board", data_fen=render["fen"])(h("noscript")( "JavaScript required for interactive board view."), ), spare("white"), h("div", id="board-toolbar", role="toolbar")( h("div", klass="btn-group")(h( "a", id="btn-flip-board", type_="button", klass="btn btn-default", title="Flip board")(h( "span", klass="icon icon-rotate")(), ), ), " ", h("div", klass="btn-group")(h( "a", id="btn-clear-board", href=fen_url(render["clear_fen"]), klass="btn btn-default", title="Clear board")(h( "span", klass="icon icon-eraser")(), ), ), " ", h("div", klass="btn-group")( h("a", id="btn-swap-colors", href=fen_url(render["swapped_fen"]), klass="btn btn-default", title="Swap colors")(h( "span", klass="icon icon-black-white")(), ), h("a", id="btn-mirror-horizontal", href=fen_url(render["horizontal_fen"]), klass="btn btn-default", title="Mirror horizontally")(h( "span", klass="icon icon-horizontal")(), ), h("a", id="btn-mirror-vertical", href=fen_url(render["vertical_fen"]), klass="btn btn-default", title="Mirror vertically")(h( "span", klass="icon icon-vertical")(), ), ), ), h("form", id="form-set-fen", action="/")(h("div", klass="input-group")( h("input", id="fen", name="fen", type="text", value=render["fen_input"], klass="form-control", aria_label="FEN", placeholder=DEFAULT_FEN), h("span", klass="input-group-btn")(h("input", type="submit", klass="btn btn-default", value="Set FEN"), ), ), ), ), ), right=xhr_probe(render), scripts=h("script", src=asset_url("js/client.min.js"), async_=True, defer=True)(), )
h("a", href="https://fontawesome.com/")("Font Awesome"), " (SIL)", ), h("li")( "A few styles from ", h("a", href="https://getbootstrap.com/")("Bootstrap"), " (MIT)", ), ), ), ), ) wdl50 = frag("WDL", h("sub")(50)) dtz50_pp = frag("DTZ", h("sub")(50), "′′") def metrics(*, development: bool) -> Frag: n = h("var")("n") example1 = "1kb5/8/1KN5/3N4/8/8/8/8 b - -" example2 = "8/8/2N5/8/3k4/7N/p2K4/8 b - -" def example_board(epd: str, check: str) -> Frag: board_fen = epd.split(" ")[0] return h("a", href=f"/?fen={epd.replace(' ', '_')}_0_1")(h( "img", width=300, height=300, alt=epd,
def quiz(*, email: Optional[str], statements: List[Statement], answers: Optional[List[bool]], correct: Optional[int]) -> Frag: return layout( "Complexity Theory", frag( h("h1")(h("em")("Complexity Theory"), " self assessment quiz"), frag( h("h2")("What is saved?"), h("ul")( h("li")("That you, ", h("strong")(email), ", participated"), h("li")("Your answers"), h("li")("But no connection between these two"), ), ) if email is not None else None, h("h2")("True or false?"), h("p")( "These following are considered basic questions from ", h("em")("Informatics III"), ". ", "Use these in your decision, if you're ready to take the course. ", "Your answers are anonymous (and therefore obviously not graded).", ), h("form", method="POST")( h("table")( h("tr", klass={ "correct": answer is statement.truth, "incorrect": answer is (not statement.truth), })( h("td")(i, "."), h("td")(statement.text), h("td")( h("input", type="radio", name=f"stmt-{i}", id=f"stmt-{i}-1", value=1, required=True, checked=answer is True, disabled=answer is not None), h("label", for_=f"stmt-{i}-1")("True"), ), h("td")( h("input", type="radio", name=f"stmt-{i}", id=f"stmt-{i}-0", value=0, required=True, checked=answer is False, disabled=answer is not None), h("label", for_=f"stmt-{i}-0")("False"), ), ) for i, statement, answer in itertools.zip_longest( range(len(statements)), statements, answers or [])), h("button", type="submit")("Submit answers") if not answers else None, h("p")("You scored ", h("strong")( round(correct / len(statements) * 100), "%"), ".") if correct is not None else None, ), ))
def register(*, lecture: Lecture, email: str, events: List[Registrations], admin: bool = False, today: datetime.date) -> Frag: def modifier(row: Row) -> Callable[[str], Frag]: if row.deleted and row.admin: return lambda *children: h("del")(h("ins")(*children)) elif row.deleted: return h("del") elif row.admin: return h("ins") else: return h("span") return layout( lecture.title, frag( h("h1", klass="no-print")("Register for the next ", h("em")(lecture.title), " lecture (step 3/3)"), h("section")( h("h2")("We are moving online until further notice"), h("p")("So no seat reservations are required."), #h("h2")("Signup not open, yet"), #h("p")("Signup opens on the day of each lecture. Please come only after you have successfully reserved a seat."), h("p")(h("button", disabled=True)("Reserve seat"), ), ) if not events else [ h("section", klass={ "not-today": registrations.event.date != today, }) ( h("h2", id=f"event-{registrations.event.id}")( registrations.event.title, " (", registrations.event.date.strftime("%a, %d.%m."), ")", ), h("p") ("Please reserve a seat only if you will physically attend this lecture in ", h("strong")(registrations.event.location), " on this particular day."), h("p") ("Please come only after you successfully reserved a seat. There are ", h("strong")(f"{registrations.event.seats} seats"), " in total."), h("table")(h("thead")(h("tr")( h("th")("Seat"), h("th")("Name"), h("th")("Status"), h("th", klass="no-print")("Admin") if admin else None, )), h("tbody")([ h("tr", klass={ "me": row.name == email, "overhang": row.n is not None and row.n <= 0, }) ( h("td")(modifier(row)(f"#{row.n}") if row .n is not None else ""), h("td")(modifier(row)(row.name)), h("td") ("Reservation deleted by admin" if row.deleted else row.time. strftime( "Successfully registered %d.%m. %H:%M" if row.n is not None and row.n > 0 else "Seat not available (%d.%m. %H:%M). We will make sure to provide the lecture materials online." ), ), h("td", klass="no-print")(h("form", method="POST")( h("input", type="hidden", name="name", value=row.name), h("input", type="hidden", name="restore" if row.deleted else "delete", value=registrations.event.id), h("button")( "Restore" if row.deleted else "Delete"), )) if admin else None, ) for row in registrations.rows() if row.name == email or admin ])) if admin or registrations.has(email) else None, h("form", method="POST", onsubmit= "return confirm('Please register only if you will physically attend the lecture on this particular day.')" if not admin else None) ( h("input", type="text", name="name", placeholder=email) if admin else None, h("input", type="hidden", name="reserve", value=registrations.event.id), h("button", type="submit") ("Reserve seat (admin)" if admin else "Reserve seat"), ) if admin or not registrations.has(email) else None, ) for registrations in events ], h("section", klass="no-print")( h("h2")("Your contact information"), h("p")("You are logged in as ", h("strong")(email), "."), h("p") ("We do not need additional contact information at this time. But please keep your details updated with the Studentensekretariat." ), ), ))